diff --git a/myTask.js b/myTask.js index f99d48d..ef99dc8 100644 --- a/myTask.js +++ b/myTask.js @@ -1,5 +1,5 @@ -// 할 일 관리 어플리케이션 step1 modified 1 +// 할일 관리 애플리케이션 step3 // 네임 스페이스 const todo = { @@ -7,75 +7,443 @@ const todo = { // 할일 목록 todoList: [], - // 할 일 id + // 할일 id taskId: 0, - // 현재 상태를 보여주는 메소드 + // undo 스택 + undoStack: [], + + // redo 스택 + redoStack: [], + + // 현재 상태 출력 showStatus() { - const result = {}; - result.todo = 0; - result.doing = 0; - result.done = 0; + const countMap = { + todo: 0, + doing: 0, + done: 0 + } this.todoList.forEach(val => { - if (result[val.status] === 0) { - result[val.status] = 1; - } else if (result[val.status] !== 0) { - result[val.status]++; - } + countMap[val.status] === 0 ? countMap[val.status] = 1 : countMap[val.status]++ }); - console.log(`현재상태 : todo:${result.todo}개, doing:${result.doing}개, done:${result.done}개`); + console.log(`"현재상태 : todo:${countMap.todo}개, doing:${countMap.doing}개, done:${countMap.done}개"\n\n`); }, - // 할 일 추가 메소드 + // 할일 추가 addTodo(todoInfo) { + if (this.checkIsSameName(todoInfo)) return; // 에러 검출 메소드 호출 const info = todoInfo; + let recentHistory; // 변경 후 내역(할일 추가) this.taskId++; + info.taskId = this.taskId; info.status = "todo"; + info.addedTime = new Date().getTime(); // 할일 추가 시간 기록 + this.todoList.push(info); - console.log(`id: ${info.taskId}, "${info.name}" 항목이 새로 추가되었습니다.`); + for (let val in info) { + recentHistory = { + taskId: info.taskId, name: info.name, tag: info.tag, status: info.status, + addedTime: info.addedTime, startTime: info.startTime, endTime: info.endTime, runningTime: info.runningTime + }; + } + console.log(`"id: ${info.taskId}, '${info.name}' 항목이 새로 추가되었습니다."`); + this.moveUndoStack(info.taskId, "added", null, recentHistory); // 할일 추가시 undo 스택에 내역 전달 this.showStatus(); }, - // 할 일 업데이트 메소드 + // 할일 업데이트 updateTodo(updateInfo) { const info = updateInfo; - let prevStatus; - let taskName; - info.nextStatus = info.nextStatus.toLowerCase(); - this.todoList.filter(val => - val.taskId === info.id).forEach(val => { + info.nextStatus = info.nextStatus.toLowerCase(); // 상태를 소문자로 변경하고 대치 + + // 에러 검출 메소드 호출 + if (this.checkNotFoundId(info)) return; + if (this.checkIsSameStatus(info)) return; + if (this.checkInvalidUpdate(info)) return; + + let prevStatus; // 콘솔출력을 위한 변수 + let taskName; // 콘솔출력을 위한 변수 + let prevHistory; // 변경 전 내역 + let recentHistory; // 변경 후 내역 + this.todoList.filter(val => val.taskId === info.id) + .forEach(val => { taskName = val.name; + prevHistory = { + name: val.name, tag: val.tag, taskId: val.taskId, status: val.status, + addedTime: val.addedTime, startTime: val.startTime, endTime: val.endTime, runningTime: val.runningTime + }; prevStatus = val.status; - val.status = info.nextStatus; + val.status = info.nextStatus; // 상태 업데이트 + this.recordTime(val); // 경과시간 계산 메소드 호출 + recentHistory = { + name: val.name, tag: val.tag, taskId: val.taskId, status: val.status, + addedTime: val.addedTime, startTime: val.startTime, endTime: val.endTime, runningTime: val.runningTime + }; }); - console.log(`id: ${info.id}, "${taskName}" 항목이 ${prevStatus} => ${info.nextStatus} 상태로 업데이트 됐습니다.`); + console.log(`"id: ${info.id}, '${taskName}' 항목이 ${prevStatus} => ${info.nextStatus} 상태로 업데이트되었습니다."` + ); + this.moveUndoStack(info.id, "updated", prevHistory, recentHistory); // 할일 업데이트 시 undo 스택에 내역 전달 this.showStatus(); }, - // 할 일 삭제 메소드 + // 업데이트 시 시작시간, 종료시간 기록 + recordTime(todo) { // doing, done 상태 변경시 경과시간을 계산하는 메소드 + if (todo.status === "doing") { + todo.startTime = new Date().getTime(); // 시작시간 기록 + } + if (todo.status === "done" && !todo.startTime) { // todo => done으로 변경된 경우 + todo.endTime = new Date().getTime(); // 종료시간 기록 + todo.runningTime = `doing 없이 종료되었습니다. 할일을 추가한 지 ${this.calRunningTime(todo.addedTime, todo.endTime)}가 경과하였습니다.`; + // 시작시간이 없으므로 추가한 시간을 기준으로 경과시간 계산 + } + if (todo.status === "done" && todo.startTime) { // doing => done으로 변경된 경우 + todo.endTime = new Date().getTime(); // 종료시간 기록 + todo.runningTime = this.calRunningTime(todo.startTime, todo.endTime); + } + }, + + // 기록된 시간을 바탕으로 경과시간 계산(종료시간 - 시작시간) + calRunningTime(addedOrStartTime, endTime) { + let interval = endTime - addedOrStartTime; + let days = Math.floor(interval / (1000 * 60 * 60 * 24)); // 경과시간 구하기(일) + interval -= days * (1000 * 60 * 60 * 24); + let hours = Math.floor(interval / (1000 * 60 * 60)); // 경과시간 구하기(시간) + interval -= hours * (1000 * 60 * 60); + let minutes = Math.floor(interval / (1000 * 60)); // 경과시간 구하기(분) + interval -= minutes * (1000 * 60); + let seconds = Math.floor(interval / 1000); // 경과시간 구하기(초) + + return `${days != 0 ? `${days}일` : ""} ${hours != 0 ? `${hours}시간` : ""} ${minutes != 0 ? `${minutes}분` : ""} ${seconds != 0 ? `${seconds}초` : ""}`.trim(); + }, + + // 할 일 삭제 removeTodo(removeInfo) { + if (this.checkNotFoundId(removeInfo)) return; const info = removeInfo; - let taskName; - this.todoList.forEach((val, idx) => { - if (val.taskId === info.id) { - taskName = val.name; - this.todoList.splice(idx, 1); + const prevHistory = this.todoList.filter(val => val.taskId === info.id)[0]; + let filterdTodo = this.todoList.filter((val) => + val.taskId === info.id + ); + let taskName = filterdTodo[0].name; + this.todoList.splice(this.todoList.indexOf(filterdTodo[0]), 1); + console.log(`"id: ${info.id}, '${taskName}' 삭제완료"`); + this.moveUndoStack(info.id, "removed", prevHistory, null); // 결과 기록하기 + this.showStatus(); + }, + + // (스택) 활동 내역에 대한 변경전, 변경후 상태를 undo스택에 추가 + moveUndoStack(taskId, statusLog, prevHistory, recentHistory) { + logObj = {}; + logObj.taskId = taskId; + logObj.statusLog = statusLog; + logObj.prevHistory = prevHistory; + logObj.recentHistory = recentHistory; + this.manageUndoStack(logObj); + }, + + // (스택)undo스택 관리 + manageUndoStack(logObj) { + if (this.undoStack.length < 3) { + this.undoStack.push(logObj); + } else if (this.undoStack.length >= 3) { + this.undoStack.splice(0, 1); + this.undoStack.push(logObj); + } + }, + + // (스택)redo스택 관리 + manageRedoStack() { + if (this.redoStack.length > 3) { + this.redoStack.splice(this.redoStack.length - 1, 1); + } + }, + + // (스택)undo 기능 + undo() { // 변경 후 상태를 변경 전 상태로 대치 + let endIndex = this.undoStack.length - 1 + if (this.undoStack.length != 0) { // 스택이 차지 않았을 경우 코드 실행 + let searchId = this.undoStack[endIndex].taskId; // 해당 ID 찾기 + let statusLog = this.undoStack[endIndex].statusLog; + this.todoList.forEach((val, idx) => { + if (val.taskId === searchId && statusLog === "updated") { + this.todoList[idx] = this.undoStack[endIndex].prevHistory; + console.log(`"${searchId}번, '${this.undoStack[endIndex].prevHistory.name}' 항목이`); + console.log(`${this.undoStack[endIndex].recentHistory.status} => ${this.undoStack[endIndex].prevHistory.status} 상태로`) + console.log(`업데이트 복구되었습니다."`); + } + if (val.taskId === searchId && statusLog === "added") { + this.todoList.splice(idx, 1); + console.log(`"${searchId}번, '${this.undoStack[endIndex].recentHistory.name}'(이)가 삭제되었습니다"`); + } + }); + if (statusLog === "removed") { + this.todoList.push(this.undoStack[endIndex].prevHistory); + console.log(`"${searchId}번, '${this.undoStack[endIndex].prevHistory.name}' 항목을 복구했습니다. "`); + } + this.redoStack.unshift(this.undoStack.splice(endIndex, 1)[0]); // redo 스택으로 이동 + this.manageRedoStack() // redo 스택 횟수 초과시 마지막 내역 삭제 + } else if (this.undoStack.length === 0) { // 스택이 꽉 찰 경우 undo 불가 + console.log(`"더 이상 undo할 수 없습니다."`) + } + }, + + // (스택)redo 기능 + redo() { // 다시 변경 후 상태로 대치 + if (this.redoStack.length != 0) { // 스택이 차지 않았을 경우 코드 실행 + let searchId = this.redoStack[0].taskId; + let statusLog = this.redoStack[0].statusLog; + this.todoList.forEach((val, idx) => { + if (val.taskId === searchId && statusLog === "updated") { + this.todoList[idx] = this.redoStack[0].recentHistory; + console.log(`"${searchId}번, '${this.redoStack[0].prevHistory.name}' 항목이`); + console.log(`${this.redoStack[0].prevHistory.status} => ${this.redoStack[0].recentHistory.status} 상태로 다시 업데이트되었습니다."`); + this.showStatus() + } + if (val.taskId === searchId && statusLog === "removed") { + this.todoList.splice(idx, 1); + console.log(`"${searchId}번, '${this.redoStack[0].prevHistory.name}'(이)가`); + console.log(`${this.redoStack[0].prevHistory.status}상태에서 다시 삭제되었습니다."`); + this.showStatus() + } + }); + if (statusLog === "added") { + this.todoList.push(this.redoStack[0].recentHistory); + console.log(`"${searchId}번, '${this.redoStack[0].recentHistory.name}' 항목이 다시 추가되었습니다."`); + this.showStatus() + } + this.undoStack.push(this.redoStack.splice(0, 1)[0]); // undo 스택으로 이동 + } else if (this.redoStack.length === 0) { // 스택이 꽉 찰 경우 redo 불가 + console.log(`"더 이상 redo할 수 없습니다."`); + } + }, + + // (에러 검출)존재하지 않는 아이디의 할일 삭제시 에러 메시지 출력 + checkNotFoundId(target) { + if (this.todoList.every(val => + val.taskId !== target.id)) { + console.log(`"[error] ${target.id}번 아이디는 존재하지 않습니다."`) + return true; + } else { + return false; + } + }, + + // (에러 검출)같은 이름의 할일 추가시 에러 메시지 출력 + checkIsSameName(target) { + if (this.todoList.some(val => + val.name === target.name)) { + console.log(`"[error] 이미 같은 이름의 할일이 존재합니다."`) + return true; + } else { + return false; + } + }, + + // (에러 검출)같은 상태로 업데이트하려는 경우 에러 메시지 출력 + checkIsSameStatus(target) { + if (this.todoList.filter(val => val.taskId === target.id) + .filter(val => val.status === target.nextStatus).length === 1 + ) { + console.log(`"[error] ${target.id}번은 이미 ${target.nextStatus}입니다."`) + return true; + } else { + return false; + } + }, + + // (에러 검출)부적절한 상태로 업데이트하려는 경우 에러 메시지 출력 + checkInvalidUpdate(target) { + if (this.todoList.filter(val => val.taskId === target.id) + .filter(val => + (val.status === "done" && target.nextStatus === "doing") || + (val.status === "done" && target.nextStatus === "todo") + ).length === 1) { + console.log(`"[error] done 상태에서 ${target.nextStatus} 상태로 갈 수 없습니다."`) + return true; + } else { + return false; + } + }, + + // 특정 태그를 기준으로 할일 리스트 생성 + showListByTag(tagName) { + const listObj = { + todo: [], + doing: [], + done: [] + } + this.todoList.filter(val => val.tag === tagName) + .forEach(val => { + val.status === "todo" ? listObj.todo.push(val) + : val.status === "doing" ? listObj.doing.push(val) + : val.status === "done" ? listObj.done.push(val) : undefined; + }); + this.sortByTaskId(listObj); + this.printListByTag(listObj); + }, + + // 모든 태그를 기준으로 할일 리스트 생성 + showListByAlltheTags() { + const listObj = {} + this.todoList.filter(val => val.tag) + .forEach(val => { + if (!listObj[val.tag]) { + listObj[val.tag] = [val]; + } else if (listObj[val.tag]) { + listObj[val.tag].push(val); + } + }); + this.sortByTaskId(listObj); + this.printListByAlltheTags(listObj); + }, + + // 생성된 할일 리스트를 아이디 순서로 정렬 + sortByTaskId(listObj) { + for (let key in listObj) { + listObj[key].sort((a, b) => a.taskId - b.taskId); + } + }, + + // 모든 태그를 기준으로 생성 및 정렬된 할일 리스트를 출력 + printListByTag(listObj) { + for (let key in listObj) { + if (listObj[key].length !== 0) { + console.log(`\n[ ${key} , 총 ${listObj[key].length}개 ]`); } + listObj[key].forEach(val => + console.log(`- ${val.taskId}번, ${val.name}${val.status === "done" ? `, 경과시간: ${val.runningTime}` : ""}`) + ) + } + }, + + // 특정 태그를 기준으로 생성 및 정렬된 할일 리스트 출력 + printListByAlltheTags(listObj) { + for (let key in listObj) { + if (listObj[key].length !== 0) { + console.log(`\n[ ${key} , 총 ${listObj[key].length}개 ]`); + } + listObj[key].forEach(val => + console.log(`- ${val.taskId}번, ${val.name}, [${val.status}]${val.status === "done" ? `, 경과시간: ${val.runningTime}` : ""}`) + ) + } + }, + + // 현재 상태에 따른 할일 리스트 생성 및 출력 + showListByStatus(status) { + this.todoList.filter(val => val.status === status) + .sort((a, b) => a.taskId - b.taskId) // id 순으로 정렬 + .forEach(val => + console.log(`- ${val.taskId}번, ${val.name}, [${val.tag}]${val.status === "done" ? `, 경과시간: ${val.runningTime}` : ""}`) + ) + }, + + // 모든 현재 상태에 따른 할일 리스트 생성 + showAllListByStatus() { + const listObj = { + todo: [], + doing: [], + done: [] + } + this.todoList.forEach(val => { + val.status === "todo" ? listObj.todo.push(val) + : val.status === "doing" ? listObj.doing.push(val) + : val.status === "done" ? listObj.done.push(val) : undefined; }); - console.log(`id: ${info.id}, "${taskName}" 삭제완료`); - this.showStatus(); - } -} + this.taskTotalCount = listObj.todo.length + listObj.doing.length + listObj.done.length; + this.sortByTaskId(listObj); + this.generateSetTimeout(listObj); + }, + + // 리스트 지연출력 설정 + generateSetTimeout(listObj) { + console.log(`"총 ${this.taskTotalCount}개의 리스트를 가져왔습니다. 2초뒤에 todo내역을 출력합니다....."`); + + let repeatCount = 0; + let milliSec = 2000; + let status = "todo"; + + function repeatSelf(listObj) { + setTimeout(() => { + todo.printListBySetTimeout(listObj, status); + if (repeatCount === 2) return; // 재귀 종료조건 + repeatCount === 0 ? milliSec = 3000 : repeatCount === 1 ? milliSec = 2000 : ""; + repeatCount === 0 ? status = "doing" : repeatCount === 1 ? status = "done" : ""; + console.log(`\n"지금부터 ${milliSec / 1000}초뒤에 ${status}내역을 출력합니다...."`); + repeatCount += 1; + repeatSelf(listObj); + }, milliSec); + } + repeatSelf(listObj); + }, + + // 설정된 지연시간에 따라 리스트 출력 + printListBySetTimeout(listObj, status) { + if (listObj[status].length === 0) { + console.log(`[ ${status} 항목이 없습니다. ]`); + return; + } else { + console.log(`[ ${status} , 총 ${listObj[status].length}개 ]`); + } + listObj[status].forEach(val => { + console.log(`- ${val.taskId}번, ${val.name}, [${val.tag}]${val.status === "done" ? `, 경과시간: ${val.runningTime}` : ""}`) + }); + }, + +} // end todo // 메소드 호출 및 실행 -todo.addTodo({ name: "자바스크립트 공부하기", tag: "programming" }); -todo.addTodo({ name: "알고리즘 문제풀기", tag: "programming" }); -todo.addTodo({ name: "사진 보정하기", tag: "hobby" }); -todo.updateTodo({ id: 1, nextStatus: "doNe" }); -todo.updateTodo({ id: 2, nextStatus: "Doing" }); -todo.updateTodo({ id: 3, nextStatus: "doInG" }); -todo.removeTodo({ id: 1 }); -todo.removeTodo({ id: 2 }); -todo.removeTodo({ id: 3 }); +// 할일 추가 +// todo.addTodo({ name: "자바스크립트 공부", tag: "programming" }); +// todo.addTodo({ name: "알고리즘 문제", tag: "programming" }); +// todo.addTodo({ name: "스켈레톤 코드 디자인", tag: "programming" }); +// todo.addTodo({ name: "ajax와 비동기 처리", tag: "programming" }); +// todo.addTodo({ name: "산책", tag: "hobby" }); +// todo.addTodo({ name: "헌혈", tag: "hobby" }); +// todo.addTodo({ name: "사진보정", tag: "hobby" }); +// todo.addTodo({ name: "사진찍기", tag: "hobby" }); +// todo.addTodo({ name: "서점들르기", tag: "life" }); +// todo.addTodo({ name: "맨손운동", tag: "life" }); +// todo.addTodo({ name: "편의점 생수 1팩 구입", tag: "life" }); +// todo.addTodo({ name: "친구약속(광화문)", tag: "life" }); +// todo.addTodo({ name: "현대사 읽기", tag: "study" }); + + +// 할일 업데이트 +// todo => doing +// todo.updateTodo({ id: 1, nextStatus: "DoinG" }); +// todo.updateTodo({ id: 2, nextStatus: "DoinG" }); +// todo.updateTodo({ id: 3, nextStatus: "DoinG" }); +// todo.updateTodo({ id: 5, nextStatus: "DoinG" }); +// todo.updateTodo({ id: 7, nextStatus: "dOiNg" }); + +// doing => done + +// todo.updateTodo({ id: 1, nextStatus: "DoNe" }); +// todo.updateTodo({ id: 2, nextStatus: "DoNe" }); +// todo.updateTodo({ id: 3, nextStatus: "DoNe" }); +// todo.updateTodo({ id: 5, nextStatus: "DoNe" }); +// todo.updateTodo({ id: 7, nextStatus: "DoNe" }); + +// todo = done +// todo.updateTodo({ id: 9, nextStatus: "donE" }); +// todo.updateTodo({ id: 10, nextStatus: "doNE" }); + +// 할일 삭제(및 중복 삭제) +// todo.removeTodo({ id: 1 }); +// todo.removeTodo({ id: 13 }); +// todo.removeTodo({ id: 1 }); +// todo.removeTodo({ id: 13 }); + + +// todo.showListByTag("programming"); +// todo.showListByTag("hobby"); +// todo.showListByTag("life"); +// todo.showListByTag("study"); + +//todo.showListByStatus("todo"); +// todo.showListByStatus("doing"); +// todo.showListByStatus("done"); + +//todo.showAllListByStatus(); \ No newline at end of file