taskProgressBar를 만들어서 현재 진행률을 확인할 수 있도록 만들고 싶었다.
기존에 indexedDB에 있던 DailyTask에 isDone이라는 변수를 만들고 boolean 값을 가지도록 수정했다.
DB를 확인하여 isDone이 false이면 '완료하기'버튼이 나오고 true면 '완료됨'으로 변경되도록 만들었다.
taskProgressBar는 총 100으로 두고 DailyTask만큼 나눈뒤 DailyTask의 isDone의 true값 만큼 활성화되도록 만들었다.
추가적으로 디자인을 손 볼 예정이다.
그리고 taskProgressBar가 100까지 가득차면 아래의 화면이 나타나도록 만들었다.
몇몇 게임들의 ui가 이뻐서 todolist를 만들고 싶었다. 점점 어느정도 보이니 재미가 있다.
//관련된 코드
남은 시간
const calculateRemainingTime = () => {
const now = new Date();
const endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 24, 0, 0);
const diffMs = endOfDay - now;
const diffHrs = Math.floor((diffMs % 86400000) / 3600000);
const diffMins = Math.round(((diffMs % 86400000) % 3600000) / 60000);
return `${diffHrs}시간 ${diffMins}분 남음`;
};
taskProgressBar
// 완료하기 관련 로직
const markTaskAsDone = async (taskId, e) => {
e.stopPropagation();
const updatedTasks = dailyTasks.map(task =>
task.id === taskId ? { ...task, isDone: true } : task
);
setDailyTasks(updatedTasks);
const taskToUpdate = updatedTasks.find(task => task.id === taskId);
await updateData('DailyTasks', taskId, taskToUpdate);
};
//남은 시간(하루기준)
const calculateRemainingTime = () => {
const now = new Date();
const endOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 24, 0, 0);
const diffMs = endOfDay - now;
const diffHrs = Math.floor((diffMs % 86400000) / 3600000);
const diffMins = Math.round(((diffMs % 86400000) % 3600000) / 60000);
return `${diffHrs}시간 ${diffMins}분 남음`;
};
//taskProgressBar
const calculateProgress = (tasks) => {
const totalTasks = tasks.length;
const completedTasks = tasks.filter(task => task.isDone).length;
return (completedTasks / totalTasks) * 100;
};
//선택한 Task의 id를 확인
const handleTaskClick = (taskId) => {
setSelectedTaskId(selectedTaskId === taskId ? null : taskId);
};
// Task삭제
const handleDeleteTask = async (taskId) => {
try {
await deleteData('DailyTasks', taskId);
setDailyTasks(dailyTasks.filter(task => task.id !== taskId));
setSelectedTaskId(null);
} catch (error) {
console.error('태스크 삭제에 실패했습니다:', error);
}
};
// Task를 전부 완료했을 때의 이벤트
const allTasksCompleted = dailyTasks.length > 0 && dailyTasks.every(task => task.isDone);
return (
<div className="todoListSection">
<div className="headerSection">
<h2>DAILY MISSION</h2>
<h5>I</h5>
<div className="missionRequirement">
<div className="timeRemaining">{calculateRemainingTime()}</div>
</div>
<div className="taskProgressBar">
<div className="progress" style={{ width: `${calculateProgress(dailyTasks)}%` }}></div>
</div>
</div>
<div className="DailyTasksSection">
<ul>
{dailyTasks.map(task => (
<li key={task.id} className={`taskItem ${selectedTaskId === task.id ? 'selected' : ''}`}>
<div className={`taskContent ${selectedTaskId === task.id ? 'open' : ''}`} onClick={() => handleTaskClick(task.id)}>
<span className="taskEmoticon">{selectedTaskId === task.id ? '▼' : '▶'}</span>
<div className="taskMainContent">
<span className="taskTitle">{task.title}</span>
</div>
<button onClick={(e) => markTaskAsDone(task.id, e)} className="taskActionButton">
{task.isDone ? '완료됨' : '완료하기'}
</button>
</div>
{selectedTaskId === task.id && (
<div className={`taskDetails ${selectedTaskId === task.id ? 'open' : ''}`}>
<p className="taskDescription">{task.description}</p>
<button onClick={(e) => handleDeleteTask(task.id, e)} className="taskDeleteButton">삭제</button>
</div>
)}
</li>
))}
</ul>
//다 완성하면 컴포넌트 MissionComplete를 불러옴.
{/* {allTasksCompleted && <MissionComplete />} */}
</div>
</div>
);
//scss
/* TodoListSection.scss */
.todoListSection {
width: 80%;
background: #FFF;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 20px;
}
.headerSection {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
h5 {
position: absolute;
font-size: 3em;
color: #3b4be2fd;
}
h2 {
padding-left: 4%;
font-size: 1.5em;
color: #000;
}
.missionRequirement {
display: flex;
align-items: center;
font-size: 0.9em;
color: #555;
.timeRemaining {
margin-left: 10px;
color: #007bff;
}
}
.taskProgressBar {
top: 10%;
position: absolute;
width: 60%;
background-color: #dcdce6;
border-radius: 10px;
height: 20px;
.progress {
background-color: #4e90ff;
height: 100%;
border-radius: 10px;
width: 0;
transition: width 0.3s ease;
}
}
}
.DailyTasksSection {
.taskItem {
padding-top: 10px;
list-style: none;
background-color: #f9f9f9;
margin-bottom: 10px;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: background-color 0.3s;
&:hover {
background-color: #f4f4f8;
}
&.selected {
background-color: #e7e7ee;
}
&.done {
background-color: #e0ffe0;
.taskEmoticon {
color: green;
}
.taskTitle {
text-decoration: line-through;
}
.taskActionButton {
background-color: #ccc;
cursor: default;
}
}
.taskContent {
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
.taskMainContent {
display: flex;
align-items: center;
}
.taskEmoticon {
font-size: 1.5rem;
margin-right: 10px;
}
.taskTitle {
font-weight: bold;
margin-right: 20px;
}
.taskActionButton {
background-color: #4e90ff;
color: white;
padding: 5px 20px;
border: none;
border-radius: 20px;
cursor: pointer;
}
}
.taskDetails {
padding-top: 10px;
border-top: 1px solid #dcdce6;
justify-content: space-between;
align-items: flex-start;
display: none;
&.open {
display: block;
}
.taskDescription {
flex-grow: 1;
text-align: left;
font-size: 0.9rem;
color: #555;
margin-right: 10px;
}
.taskDeleteButton {
padding: 5px 20px;
background-color: #ff4e4e;
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
align-self: flex-end;
}
}
}
}
'개인프로젝트 > listPlz' 카테고리의 다른 글
listplz 데이터 상호작용 (1) | 2024.02.02 |
---|---|
indexedDB (0) | 2024.01.31 |
react-electron 환경설정(윈도우) (0) | 2024.01.23 |