diff --git a/package-lock.json b/package-lock.json index ab6e28a..b009eca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1073,7 +1073,6 @@ "version": "7.12.5", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", - "dev": true, "requires": { "regenerator-runtime": "^0.13.4" } @@ -2445,7 +2444,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", - "dev": true, "requires": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.0" @@ -2618,6 +2616,11 @@ } } }, + "classnames": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", + "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" + }, "clean-css": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", @@ -2870,6 +2873,15 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "create-react-context": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.3.0.tgz", + "integrity": "sha512-dNldIoSuNSvlTJ7slIKC/ZFGKexBMBrrcc+TTe1NdmROnaASuLPvqpwj9v4XS4uXZ8+YPu0sNmShX2rXI5LNsw==", + "requires": { + "gud": "^1.0.0", + "warning": "^4.0.3" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2963,6 +2975,11 @@ "type": "^1.0.1" } }, + "date-fns": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.17.0.tgz", + "integrity": "sha512-ZEhqxUtEZeGgg9eHNSOAJ8O9xqSgiJdrL0lzSSfMF54x6KXWJiOH/xntSJ9YomJPrYH/p08t6gWjGWq1SDJlSA==" + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2988,7 +3005,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "dev": true, "requires": { "is-arguments": "^1.0.4", "is-date-object": "^1.0.1", @@ -3102,7 +3118,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, "requires": { "object-keys": "^1.0.12" } @@ -3408,7 +3423,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, "requires": { "is-callable": "^1.1.4", "is-date-object": "^1.0.1", @@ -4025,8 +4039,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "gaxios": { "version": "4.1.0", @@ -4065,7 +4078,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.2.tgz", "integrity": "sha512-aeX0vrFm21ILl3+JpFFRNe9aUvp6VFZb2/CTbgLb8j75kOhvoNYjt9d8KA/tJG4gSo8nzEDedRl0h7vDmBYRVg==", - "dev": true, "requires": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -4195,6 +4207,11 @@ "jws": "^4.0.0" } }, + "gud": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", + "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" + }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -4205,7 +4222,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -4219,8 +4235,7 @@ "has-symbols": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" }, "has-value": { "version": "1.0.0", @@ -4622,7 +4637,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", - "dev": true, "requires": { "call-bind": "^1.0.0" } @@ -4645,8 +4659,7 @@ "is-callable": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", - "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", - "dev": true + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==" }, "is-core-module": { "version": "2.2.0", @@ -4680,8 +4693,7 @@ "is-date-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" }, "is-descriptor": { "version": "0.1.6", @@ -4786,7 +4798,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", - "dev": true, "requires": { "has-symbols": "^1.0.1" } @@ -4800,7 +4811,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, "requires": { "has-symbols": "^1.0.1" } @@ -5436,14 +5446,12 @@ "object-inspect": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", - "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", - "dev": true + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==" }, "object-is": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.4.tgz", "integrity": "sha512-1ZvAZ4wlF7IyPVOcE1Omikt7UpaFlOQq0HlSti+ZvDH3UiD2brwGMwDbyV43jao2bKJ+4+WdPJHSd7kgzKYVqg==", - "dev": true, "requires": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" @@ -5452,8 +5460,7 @@ "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object-visit": { "version": "1.0.1", @@ -5468,7 +5475,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, "requires": { "call-bind": "^1.0.0", "define-properties": "^1.1.3", @@ -5778,6 +5784,11 @@ } } }, + "popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" + }, "portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", @@ -5905,6 +5916,16 @@ "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==" }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, "protobufjs": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.2.tgz", @@ -6020,6 +6041,18 @@ "object-assign": "^4.1.1" } }, + "react-datepicker": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-3.4.1.tgz", + "integrity": "sha512-ASyVb7UmVx1vzeITidD7Cr/EXRXhKyjjbSkBndPc1MipYq4rqQ3eMFgvRQzpsXc3JmIMFgICm7nmN6Otc1GE/Q==", + "requires": { + "classnames": "^2.2.6", + "date-fns": "^2.0.1", + "prop-types": "^15.7.2", + "react-onclickoutside": "^6.9.0", + "react-popper": "^1.3.4" + } + }, "react-dom": { "version": "17.0.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.1.tgz", @@ -6030,6 +6063,30 @@ "scheduler": "^0.20.1" } }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "react-onclickoutside": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.10.0.tgz", + "integrity": "sha512-7i2L3ef+0ILXpL6P+Hg304eCQswh4jl3ynwR71BSlMU49PE2uk31k8B2GkP6yE9s2D4jTGKnzuSpzWxu4YxfQQ==" + }, + "react-popper": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.7.tgz", + "integrity": "sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww==", + "requires": { + "@babel/runtime": "^7.1.2", + "create-react-context": "^0.3.0", + "deep-equal": "^1.1.1", + "popper.js": "^1.14.4", + "prop-types": "^15.6.1", + "typed-styles": "^0.0.7", + "warning": "^4.0.2" + } + }, "readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -6089,8 +6146,7 @@ "regenerator-runtime": { "version": "0.13.7", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" }, "regenerator-transform": { "version": "0.14.5", @@ -6115,7 +6171,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", - "dev": true, "requires": { "define-properties": "^1.1.3", "es-abstract": "^1.17.0-next.1" @@ -6125,7 +6180,6 @@ "version": "1.17.7", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", - "dev": true, "requires": { "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", @@ -6897,7 +6951,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", - "dev": true, "requires": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" @@ -6907,7 +6960,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", - "dev": true, "requires": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" @@ -7098,6 +7150,11 @@ "mime-types": "~2.1.24" } }, + "typed-styles": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz", + "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==" + }, "typescript": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz", @@ -7327,6 +7384,14 @@ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", "dev": true }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + }, "watchpack": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.1.0.tgz", diff --git a/package.json b/package.json index b765576..c030424 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "dotenv": "^8.2.0", "firebase": "^8.2.6", "react": "^17.0.1", + "react-datepicker": "^3.4.1", "react-dom": "^17.0.1" } } diff --git a/public/data/data.json b/public/data/data.json index 90c4901..705b583 100644 --- a/public/data/data.json +++ b/public/data/data.json @@ -50,59 +50,79 @@ "image": "/images/cau-logo.png" } ], - "todoConfig": { - "isShow": true, - "todoSectionList": [ - { - "id": 1, - "isShow": true, - "title": "To Do", - "todoList": [ - { - "id": 1, - "title": "과제 제출하기", - "dueDate": "2021-02-11 23:59:59", - "isMain": true - }, - { - "id": 2, - "title": "스테이크 굽기", - "dueDate": "2021-02-07 18:30:00", - "isMain": true - }, - { - "id": 3, - "title": "과제 제출하기", - "dueDate": "2021-02-11 23:59:59", - "isMain": true - } - ] - }, - { - "id": 2, - "isShow": false, - "title": "Done", - "todoList": [ - { - "id": 1, - "title": "과제 제출하기", - "dueDate": "2021-02-11 23:59:59", - "isMain": true - }, - { - "id": 2, - "title": "스테이크 굽기", - "dueDate": "2021-02-07 18:30:00", - "isMain": true - }, - { - "id": 3, - "title": "과제 제출하기", - "dueDate": "2021-02-11 23:59:59", - "isMain": true - } - ] - } - ] + "todoSectionList": [ + { + "id": 1, + "isShow": true, + "isAddable": true, + "title": "To Do" + }, + { + "id": 2, + "isShow": false, + "isAddable": false, + "title": "Done" + } + ], + "todoList": [ + { + "sectionId": 1, + "id": 1, + "title": "과제 제출하기", + "dueDate": "2021-02-11 23:59:59", + "isMain": false + }, + { + "sectionId": 1, + "id": 2, + "title": "스테이크 굽기", + "dueDate": "2021-02-07 18:30:00", + "isMain": false + }, + { + "sectionId": 1, + "id": 3, + "title": "과제 제출하기", + "dueDate": "2021-02-11 23:59:59", + "isMain": false + }, + { + "sectionId": 1, + "id": 4, + "title": "과제하기", + "dueDate": "2021-02-11 23:59:59", + "isMain": false + }, + { + "sectionId": 1, + "id": 5, + "title": "개발", + "dueDate": "2021-02-11 23:59:59", + "isMain": false + }, + { + "sectionId": 2, + "id": 6, + "title": "과제 제출하기", + "dueDate": "2021-02-11 23:59:59", + "isMain": false + }, + { + "sectionId": 2, + "id": 7, + "title": "스테이크 굽기", + "dueDate": "2021-02-07 18:30:00", + "isMain": false + }, + { + "sectionId": 2, + "id": 8, + "title": "과제 제출하기", + "dueDate": "2021-02-11 23:59:59", + "isMain": false + } + ], + "toggleConfig": { + "todo": true } } diff --git a/public/images/bg/1.jpg b/public/images/bg/1.jpg new file mode 100644 index 0000000..a962622 Binary files /dev/null and b/public/images/bg/1.jpg differ diff --git a/public/images/bg/2.jpg b/public/images/bg/2.jpg new file mode 100644 index 0000000..866c74c Binary files /dev/null and b/public/images/bg/2.jpg differ diff --git a/src/App.tsx b/src/App.tsx index 94109ca..df6ea54 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,46 +1,19 @@ -import React, { useEffect, useState } from 'react' -import axios from 'axios' +import React from 'react' +import { Store } from '@/store' import { Timer } from '@/components/Timer' import { SideMenuBar } from '@/components/SideMenuBar' -import { BookmarkProps } from '@/components/common/Bookmark' -import { TodoContainer, TodoContainerProps } from '@/components/TodoContainer' +import { TodoContainer } from '@/components/TodoContainer' import '@/style/fonts.scss' import '@/style/global.scss' -type Data = { - sideBookmarkList: BookmarkProps[] - mainDate: Date - todoConfig: TodoContainerProps -} - -const getData = async () => { - const response = await axios.get('/data/data.json') - return response.data -} - const App: React.FC = () => { - const [data, setData] = useState() - - useEffect(() => { - const fetchData = async () => { - const fetchedData = await getData() - setData({ ...fetchedData, mainDate: new Date(fetchedData.mainDate) }) - } - fetchData() - }, []) - return ( -
- {data ? ( - <> - - - - - ) : ( - <> - )} -
+ +
+ + + +
) } diff --git a/src/components/SideMenuBar/index.tsx b/src/components/SideMenuBar/index.tsx index 87d8717..8a2850a 100644 --- a/src/components/SideMenuBar/index.tsx +++ b/src/components/SideMenuBar/index.tsx @@ -1,16 +1,17 @@ import React from 'react' -import { Bookmark, BookmarkProps } from '@/components/common/Bookmark' +import { useStore } from '@/store' +import { Bookmark } from '@/components/common/Bookmark' import './style.scss' -export interface SideMenuBarProps { - bookmarkList: BookmarkProps[] -} +export const SideMenuBar: React.FC = () => { + const { + store: { sideBookmarkList }, + } = useStore() -export const SideMenuBar: React.FC = ({ bookmarkList }) => { return (
- {bookmarkList.map((bookmark) => ( + {sideBookmarkList.map((bookmark) => ( ))}
diff --git a/src/components/Timer/index.tsx b/src/components/Timer/index.tsx index 2b707cd..ee7b195 100644 --- a/src/components/Timer/index.tsx +++ b/src/components/Timer/index.tsx @@ -1,29 +1,28 @@ -import { getDDay } from '@/utils/time' -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' +import { useStore } from '@/store' +import { getDDay, getTwoDigit } from '@/utils/time' import { DisplayTime } from './DisplayTime' import './style.scss' -export interface TimerProps { - dateTime: Date -} - -function getTwoDigit(num: number): string { - return num < 10 ? `0${num}` : num.toString() -} +export const Timer: React.FC = () => { + const { + store: { mainDate }, + } = useStore() -export const Timer: React.FC = ({ dateTime }) => { const [currentDateTime, setCurrentDateTime] = useState(new Date()) - setTimeout(() => { - setCurrentDateTime(new Date()) - }, 1000) + useEffect(() => { + setTimeout(() => { + setCurrentDateTime(new Date()) + }, 1000) + }, [currentDateTime]) - const timeGap: Date = new Date(dateTime.getTime() - currentDateTime.getTime()) + const timeGap: Date = new Date(mainDate.getTime() - currentDateTime.getTime()) const [restDay, hour, minute, second] = getDDay(timeGap).map((v) => getTwoDigit(v) ) const nextTimeGap: Date = new Date( - dateTime.getTime() - currentDateTime.getTime() - 1000 + mainDate.getTime() - currentDateTime.getTime() - 1000 ) const [nextRestDay, nextHour, nextMinute, nextSecond] = getDDay( nextTimeGap diff --git a/src/components/TodoContainer/TodoSection/Todo/index.tsx b/src/components/TodoContainer/TodoSection/Todo/index.tsx index 6c713dc..7bb50d4 100644 --- a/src/components/TodoContainer/TodoSection/Todo/index.tsx +++ b/src/components/TodoContainer/TodoSection/Todo/index.tsx @@ -1,24 +1,90 @@ -import React from 'react' +import React, { useEffect, useState } from 'react' +import { useStore } from '@/store' import { getFormattedDate } from '@/utils/time' import './style.scss' export interface TodoProps { + sectionId: number id: number title: string dueDate: Date isMain: boolean + isEdit?: boolean + isNew?: boolean } -export const Todo: React.FC = ({ title, dueDate, isMain }) => { +export const Todo: React.FC = ({ id, title, dueDate, isMain }) => { + const { + store: { todoList }, + dispatchStore, + } = useStore() + const thisTodo = todoList.find((todo) => todo.id === id) + + const [isShowMenu, setIsShowMenu] = useState(false) + useEffect(() => { + return () => setIsShowMenu(false) + }, []) + + const closeMenu = (e) => { + const todoComponent = e.target.closest('.todo') + if (todoComponent && +todoComponent.dataset.component === id) { + return + } + + setIsShowMenu(false) + window.removeEventListener('click', closeMenu) + } + + const toggleMenu = (e) => { + setIsShowMenu(!isShowMenu) + window.addEventListener('click', closeMenu) + } + + const editTodo = () => { + if (!thisTodo) return + + thisTodo.isEdit = true + dispatchStore('todoList', [...todoList]) + } + + const deleteTodo = () => { + if (!thisTodo) return + + dispatchStore( + 'todoList', + todoList.filter((todo) => todo.id !== id) + ) + } + return ( -
+

{title}

- ellipsis_vertical + { + toggleMenu(e) + }} + > + ellipsis_vertical + + {isShowMenu ? ( +
+ + + +
+ ) : ( + <> + )}
diff --git a/src/components/TodoContainer/TodoSection/Todo/style.scss b/src/components/TodoContainer/TodoSection/Todo/style.scss index 57428cc..5b634ec 100644 --- a/src/components/TodoContainer/TodoSection/Todo/style.scss +++ b/src/components/TodoContainer/TodoSection/Todo/style.scss @@ -1,6 +1,6 @@ @use '@/style/package' as *; -.todo { +.todo[data-component] { background-color: $color-white; border-radius: 0.4rem; width: 100%; @@ -14,21 +14,52 @@ size: 1.5rem; weight: 500; } - letter-spacing: -0.05em; + letter-spacing: -0.025em; line-height: 1.2; color: $color-gray-900; } } .menu-wrapper { + position: relative; display: flex; flex-direction: column; align-items: center; - .menu { + .menu-btn { font-size: 1.8rem; margin-right: -0.5rem; cursor: pointer; + user-select: none; + } + + .menu-wrapper { + position: absolute; + top: 0; + right: 100%; + width: fit-content; + padding: 0.8rem 1.2rem; + border-radius: 0.4rem; + background-color: $color-white; + box-shadow: 0 0.2rem 0.8rem unquote($color-black + '50'); + z-index: 10; + + .menu { + white-space: pre; + transition: 0.2s; + font-size: 1.4rem; + color: $color-gray-700; + margin-bottom: 0.6rem; + user-select: none; + + &:hover { + color: $color-gray-900; + } + + &:last-child { + margin-bottom: 0; + } + } } } diff --git a/src/components/TodoContainer/TodoSection/TodoEdit/TimeInput.tsx b/src/components/TodoContainer/TodoSection/TodoEdit/TimeInput.tsx new file mode 100644 index 0000000..9e574e8 --- /dev/null +++ b/src/components/TodoContainer/TodoSection/TodoEdit/TimeInput.tsx @@ -0,0 +1,47 @@ +import React from 'react' +import { autoScaleInputHandler } from '@/utils/events' +import { TIME_INFO } from '@/utils/constants' +import { getTwoDigit } from '@/utils/time' + +export type TimeInputProps = { + className: string + value: string + setValue: Function +} + +export const TimeInput: React.FC = ({ + className, + value, + setValue, +}) => { + const inputTime = (e) => { + const [targetValue, min, max] = [ + e.target.value, + +e.target.min, + +e.target.max, + ] + + const newValue: number = + targetValue < min ? min : targetValue > max ? max : targetValue + + setValue(getTwoDigit(newValue)) + + setTimeout(() => { + autoScaleInputHandler(e) + }, 0) + } + + return ( + { + inputTime(e) + }} + /> + ) +} diff --git a/src/components/TodoContainer/TodoSection/TodoEdit/index.tsx b/src/components/TodoContainer/TodoSection/TodoEdit/index.tsx new file mode 100644 index 0000000..63d39b1 --- /dev/null +++ b/src/components/TodoContainer/TodoSection/TodoEdit/index.tsx @@ -0,0 +1,138 @@ +import React, { useEffect, useRef, useState } from 'react' +import DatePicker from 'react-datepicker' +import ko from 'date-fns/locale/ko' +import { useStore } from '@/store' +import { LENGTH } from '@/utils/constants' +import { autoScaleInputHandler } from '@/utils/events' +import { getFormattedDate, getTwoDigit } from '@/utils/time' +import { TodoProps } from '../Todo' +import { TimeInput } from './TimeInput' +import './style.scss' +import 'react-datepicker/dist/react-datepicker.css' + +export const TodoEdit: React.FC = (props) => { + const { + store: { todoList }, + dispatchStore, + } = useStore() + const thisTodo = todoList.find((todo) => todo.id === props.id) + + const thisComponent = useRef(null) + const datePicker = useRef< + HTMLDivElement & { + onInputClick: () => void + input: HTMLInputElement + } + >() + + const [title, setTitle] = useState(props.title) + const [date, setDate] = useState(props.dueDate) + const [hour, setHour] = useState( + getTwoDigit(props.dueDate.getHours()) + ) + const [minute, setMinute] = useState( + getTwoDigit(props.dueDate.getMinutes()) + ) + + useEffect(() => { + const inputs: + | NodeListOf + | undefined = thisComponent.current?.querySelectorAll('.date-row input') + inputs?.forEach((input) => { + autoScaleInputHandler({ target: input }) + }) + }, [thisComponent]) + + const inputTitle = (e) => { + if (e.target.value.length > LENGTH.MAX_TODO_TITLE) return + setTitle(e.target.value) + } + + const changeDate = (newDate) => { + setDate(newDate) + setTimeout(() => { + if (datePicker.current?.input) { + autoScaleInputHandler({ target: datePicker.current?.input }) + } + }, 0) + } + + const saveTodo = () => { + if (!thisTodo || title === '') return + + thisTodo.isEdit = false + thisTodo.isNew = false + thisTodo.title = title + thisTodo.dueDate = new Date( + `${getFormattedDate(date, 'YYYY-MM-DD')} ${hour}:${minute}:00` + ) + dispatchStore('todoList', [...todoList]) + } + + const cancelTodo = () => { + if (props.isNew) { + dispatchStore( + 'todoList', + todoList.filter((todo) => todo.id !== props.id) + ) + return + } + + if (!thisTodo) return + thisTodo.isEdit = false + dispatchStore('todoList', [...todoList]) + } + + return ( +
+
+ + star_fill +
+
+ { + datePicker.current?.onInputClick() + }} + > + calendar + + { + changeDate(newDate) + }} + placeholderText="YYYY.MM.DD" + locale={ko} + /> +
+ + : + +
+
+
+ + +
+
+ ) +} diff --git a/src/components/TodoContainer/TodoSection/TodoEdit/style.scss b/src/components/TodoContainer/TodoSection/TodoEdit/style.scss new file mode 100644 index 0000000..2681fc4 --- /dev/null +++ b/src/components/TodoContainer/TodoSection/TodoEdit/style.scss @@ -0,0 +1,119 @@ +@use '@/style/package' as *; + +.todo-edit[data-component] { + background-color: $color-white; + border-radius: 0.4rem; + width: 100%; + padding: 0.6rem 0.8rem; + + .title-input-row { + padding-bottom: 0.2rem; + border-bottom: 0.1rem solid $color-gray-300; + margin-bottom: 0.6rem; + + input { + font-size: 1.4rem; + color: $color-gray-800; + border: none; + width: 100%; + margin-right: 0.4rem; + + &::placeholder { + color: $color-gray-500; + } + } + + i.star { + font-size: 1.2rem; + color: $color-white; + background-color: $color-gray-300; + padding: 0.2rem; + border-radius: 0.2rem; + user-select: none; + cursor: pointer; + } + } + + .date-row { + margin-bottom: 0.8rem; + + input { + font-size: 1.2rem; + border: none; + } + + i.calendar { + font-size: 1.6rem; + margin-right: 0.4rem; + user-select: none; + cursor: pointer; + } + + .react-datepicker-wrapper { + input { + width: 8rem; + } + } + + .react-datepicker { + transform: scale(1.25) translate(5%, 12.5%); + } + + .time-input-wrapper { + margin-left: 0.8rem; + + input { + width: 1.8rem; + + &.minute { + width: 2.1rem; + } + } + + .colon { + font-size: 1.2rem; + color: $color-gray-800; + margin: 0 0.2rem; + } + } + } + + .button-row { + gap: 0.4rem; + + button { + transition: 0.2s; + flex: 1 1 0px; + height: 2.4rem; + border-radius: 0.4rem; + font: { + size: 1.3rem; + weight: 500; + } + + &.cancel { + background-color: $color-gray-100; + color: $color-gray-800; + + &:hover { + background-color: $color-gray-200; + } + } + + &.save { + transition: 0.5s; + background-color: $color-gray-400; + color: $color-white; + + &.active { + transition: 0.2s; + background-color: $color-blue-lighten-1; + + &:hover { + background-color: $color-blue-lighten-0; + } + } + } + } + } +} diff --git a/src/components/TodoContainer/TodoSection/index.tsx b/src/components/TodoContainer/TodoSection/index.tsx index cb24280..0728d70 100644 --- a/src/components/TodoContainer/TodoSection/index.tsx +++ b/src/components/TodoContainer/TodoSection/index.tsx @@ -1,44 +1,91 @@ import React, { useState } from 'react' -import './style.scss' +import { useStore } from '@/store' import { Todo, TodoProps } from './Todo' +import { TodoEdit } from './TodoEdit' +import './style.scss' export type TodoSectionProps = { id: number isShow: boolean + isAddable: boolean title: string - todoList: TodoProps[] } -export const TodoSection: React.FC = ({ - id, - isShow, - title, - todoList, -}) => { - const [isShowTodo, setIsShowTodo] = useState(isShow) +export const TodoSection: React.FC = (props) => { + const { + store: { todoList }, + dispatchStore, + } = useStore() + + const [isShow, setIsShow] = useState(props.isShow) + const defaultTodo: TodoProps = { + sectionId: props.id, + id: 0, + title: '', + dueDate: new Date(), + isMain: false, + isEdit: false, + } + + const addTodo = () => { + const newTodo: TodoProps = { + ...defaultTodo, + id: todoList.length + 1, + isEdit: true, + isNew: true, + } + dispatchStore('todoList', [newTodo, ...todoList]) + + if (!isShow) setIsShow(true) + } return (
-
{title}
-
Add
+
{props.title}
+ {props.isAddable ? ( +
+ Add +
+ ) : ( + <> + )}
{ - setIsShowTodo(!isShowTodo) + setIsShow(!isShow) }} > chevron_up
-
- {todoList.map((todo) => ( - - ))} +
+ {todoList + .filter((todo) => todo.sectionId === props.id && todo.isNew) + .map((todo) => ( + + ))} + {todoList + .filter((todo) => todo.sectionId === props.id && !todo.isNew) + .map((todo) => + todo.isEdit ? ( + + ) : ( + + ) + )}
) diff --git a/src/components/TodoContainer/TodoSection/style.scss b/src/components/TodoContainer/TodoSection/style.scss index 2711a5e..214d32a 100644 --- a/src/components/TodoContainer/TodoSection/style.scss +++ b/src/components/TodoContainer/TodoSection/style.scss @@ -22,6 +22,7 @@ border-radius: 0.4rem; background-color: $color-gray-100; cursor: pointer; + user-select: none; } } diff --git a/src/components/TodoContainer/index.tsx b/src/components/TodoContainer/index.tsx index a88f109..4d0e23d 100644 --- a/src/components/TodoContainer/index.tsx +++ b/src/components/TodoContainer/index.tsx @@ -1,36 +1,33 @@ -import React, { useState } from 'react' +import React from 'react' +import { useStore } from '@/store' +import { TodoSection } from './TodoSection' import './style.scss' -import { TodoSection, TodoSectionProps } from './TodoSection' -export type TodoContainerProps = { - isShow: boolean - todoSectionList: TodoSectionProps[] -} +export const TodoContainer: React.FC = () => { + const { + store: { todoSectionList, toggleConfig }, + dispatchStore, + } = useStore() -export const TodoContainer: React.FC = ({ - isShow, - todoSectionList, -}) => { - const [isShowTodo, setIsShowTodo] = useState(isShow) const toggleContainer = () => { - setIsShowTodo(!isShowTodo) + dispatchStore('toggleConfig', { ...toggleConfig, todo: !toggleConfig.todo }) } return (
To Do List
- - plus_square - - - minus_square - +
+ plus_square + minus_square +
-
+
{todoSectionList.map((todoSection) => ( ))} diff --git a/src/components/TodoContainer/style.scss b/src/components/TodoContainer/style.scss index f789c23..cdb1795 100644 --- a/src/components/TodoContainer/style.scss +++ b/src/components/TodoContainer/style.scss @@ -7,7 +7,6 @@ background-color: rgba($color-black, 0.5); padding: 1rem 1.2rem; width: 25rem; - overflow-y: auto; border-radius: 0.8rem; &.plus { @@ -57,8 +56,14 @@ .section-container { display: grid; grid-template-columns: 1fr; + max-height: 45vh; + overflow-y: auto; margin-top: 1rem; + &::-webkit-scrollbar { + display: none !important; + } + .todo-section { padding-top: 1.2rem; border-top: 0.1rem solid $color-white; diff --git a/src/store/index.tsx b/src/store/index.tsx new file mode 100644 index 0000000..3886c8e --- /dev/null +++ b/src/store/index.tsx @@ -0,0 +1,104 @@ +import axios from 'axios' +import React, { createContext, useContext, useEffect, useState } from 'react' +import { BookmarkProps } from '@/components/common/Bookmark' +import { TodoSectionProps } from '@/components/TodoContainer/TodoSection' +import { TodoProps } from '@/components/TodoContainer/TodoSection/Todo' + +export type Toggle = { + todo: boolean +} + +export type StoreType = { + sideBookmarkList: BookmarkProps[] + mainDate: Date + todoSectionList: TodoSectionProps[] + todoList: TodoProps[] + toggleConfig: Toggle +} + +const preProccess = (key: keyof StoreType, store: StoreType): StoreType => { + if (key === 'todoList') { + store.todoList.sort((a, b) => { + return a.dueDate > b.dueDate + ? 1 + : a.dueDate < b.dueDate + ? -1 + : a.title > b.title + ? 1 + : a.id > b.id + ? 1 + : -1 + }) + } + return store +} + +const StoreContext = createContext(undefined) +const SetStoreContext = createContext< + React.Dispatch> | undefined +>(undefined) + +export const useStore = () => { + const store = useContext(StoreContext) + const setStore = useContext(SetStoreContext) + if (store === undefined || setStore === undefined) { + throw new Error('Store is not initialized') + } + + function dispatchStore( + key: K, + newValue: StoreType[K] + ) { + if (store === undefined || setStore === undefined) return + + store[key] = newValue + const newStore = { ...preProccess(key, store) } + + setStore(newStore) + } + + return { + store, + dispatchStore, + } +} + +const getInitStore = async (): Promise => { + const response = await axios.get('/data/data.json') + + const defaultStore: StoreType = { + ...response.data, + todoList: response.data.todoList.map((todo) => ({ + ...todo, + dueDate: new Date(todo.dueDate), + })), + mainDate: new Date(response.data.mainDate), + } + + return (Object.keys(defaultStore) as Array).reduce( + (reducedStore, key) => { + return { ...preProccess(key, reducedStore) } + }, + defaultStore + ) +} + +export const Store: React.FC = (props) => { + const [store, setStore] = useState() + + useEffect(() => { + const fetchInitStore = async () => { + const defaultStore: StoreType = await getInitStore() + setStore(defaultStore) + } + fetchInitStore() + }, []) + + return ( + + + {store !== undefined ? props.children : <>} + + + ) +} diff --git a/src/style/global.scss b/src/style/global.scss index cbe7145..0f9f2ad 100644 --- a/src/style/global.scss +++ b/src/style/global.scss @@ -1,6 +1,18 @@ @use '@/style/package' as *; /* Default Settings */ +#app { + #bg { + width: 100vw; + height: 100vh; + opacity: 0.5; + background: { + position: center; + size: cover; + } + } +} + html { font-size: $font-size; } diff --git a/src/style/variables.scss b/src/style/variables.scss index 7fbc55f..989d705 100644 --- a/src/style/variables.scss +++ b/src/style/variables.scss @@ -4,6 +4,28 @@ $font-size: 10px; $color-white: #ffffff; $color-black: #000000; +$color-red-lighten-5: #ffebee; +$color-red-lighten-4: #ffcdd2; +$color-red-lighten-3: #ef9a9a; +$color-red-lighten-2: #e57373; +$color-red-lighten-1: #ef5350; +$color-red-lighten-0: #f44336; +$color-red-darken-4: #b71c1c; +$color-red-darken-3: #c62828; +$color-red-darken-2: #d32f2f; +$color-red-darken-1: #e53935; + +$color-blue-lighten-5: #e3f2fd; +$color-blue-lighten-4: #bbdefb; +$color-blue-lighten-3: #90caf9; +$color-blue-lighten-2: #64b5f6; +$color-blue-lighten-1: #42a5f5; +$color-blue-lighten-0: #2196f3; +$color-blue-darken-4: #0d47a1; +$color-blue-darken-3: #1565c0; +$color-blue-darken-2: #1976d2; +$color-blue-darken-1: #1e88e5; + $color-gray-50: #fafafa; $color-gray-100: #e6e6e6; $color-gray-200: #cccccc; diff --git a/src/utils/constants.ts b/src/utils/constants.ts new file mode 100644 index 0000000..011e465 --- /dev/null +++ b/src/utils/constants.ts @@ -0,0 +1,16 @@ +export const LENGTH = { + MAX_TODO_TITLE: 20, +} + +export const TIME_INFO = { + hour: { + min: 0, + max: 23, + placeholder: 'HH', + }, + minute: { + min: 0, + max: 59, + placeholder: 'MM', + }, +} diff --git a/src/utils/events.ts b/src/utils/events.ts new file mode 100644 index 0000000..8b08f23 --- /dev/null +++ b/src/utils/events.ts @@ -0,0 +1,16 @@ +export const autoScaleInputHandler = ({ + target, +}: { + target: HTMLInputElement | HTMLTextAreaElement +}) => { + const span = document.createElement('span') + span.style.fontSize = getComputedStyle(target).fontSize + span.style.letterSpacing = getComputedStyle(target).letterSpacing + + span.textContent = target.value || target.placeholder + document.body.appendChild(span) + const width = span.offsetWidth + + span.remove() + target.style.width = `${width}px` +} diff --git a/src/utils/time.ts b/src/utils/time.ts index 1b87e8f..a3f91be 100644 --- a/src/utils/time.ts +++ b/src/utils/time.ts @@ -1,34 +1,38 @@ export function getDDay(dateTime: Date): number[] { const [restDay, hour, minute, second] = [ Math.floor(dateTime.getTime() / (1000 * 60 * 60 * 24)), - dateTime.getUTCHours(), - dateTime.getUTCMinutes(), - dateTime.getUTCSeconds(), + dateTime.getHours(), + dateTime.getMinutes(), + dateTime.getSeconds(), ] return [restDay, hour, minute, second] } +export function getTwoDigit(number: number | string): string { + return +number === 0 ? '00' : +number < 10 ? `0${+number}` : `${+number}` +} + export function getFormattedDate(date: Date, format: string): string { const [year, month, day, hour, minute, second] = [ date.getFullYear(), date.getMonth() + 1, date.getDate(), - date.getUTCHours(), - date.getUTCMinutes(), - date.getUTCSeconds(), + date.getHours(), + date.getMinutes(), + date.getSeconds(), ] const DATE_FORMAT = { YYYY: year, YY: year % 100, - MM: month < 10 ? `0${month}` : month, + MM: getTwoDigit(month), M: month, - DD: day < 10 ? `0${day}` : day, + DD: getTwoDigit(day), D: day, - hh: hour < 10 ? `0${hour}` : hour, + hh: getTwoDigit(hour), h: hour, - mm: minute < 10 ? `0${minute}` : minute, + mm: getTwoDigit(minute), m: minute, - ss: second < 10 ? `0${second}` : second, + ss: getTwoDigit(second), s: second, }