From 41fc136b56fa6681b173c3a8cf8e9f4196612618 Mon Sep 17 00:00:00 2001 From: Seok93 Date: Wed, 3 Jul 2024 21:32:33 +0900 Subject: [PATCH 1/6] =?UTF-8?q?Config:=20#33=20react-beautiful-dnd?= =?UTF-8?q?=EB=A5=BC=20=EA=B3=84=EC=8A=B9=ED=95=9C=20@hello-pangea/dnd=20?= =?UTF-8?q?=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + yarn.lock | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 918fdcb6..dcddc0f3 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "prepare": "husky" }, "dependencies": { + "@hello-pangea/dnd": "^16.6.0", "@tanstack/react-query": "^5.36.2", "axios": "^1.6.8", "react": "^18.2.0", diff --git a/yarn.lock b/yarn.lock index 1d6a3404..55edd68d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35,6 +35,13 @@ js-tokens "^4.0.0" picocolors "^1.0.0" +"@babel/runtime@^7.12.1", "@babel/runtime@^7.24.1": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" + integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.23.2", "@babel/runtime@^7.9.2": version "7.24.5" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz" @@ -203,6 +210,19 @@ resolved "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz" integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== +"@hello-pangea/dnd@^16.6.0": + version "16.6.0" + resolved "https://registry.yarnpkg.com/@hello-pangea/dnd/-/dnd-16.6.0.tgz#7509639c7bd13f55e537b65a9dcfcd54e7c99ac7" + integrity sha512-vfZ4GydqbtUPXSLfAvKvXQ6xwRzIjUSjVU0Sx+70VOhc2xx6CdmJXJ8YhH70RpbTUGjxctslQTHul9sIOxCfFQ== + dependencies: + "@babel/runtime" "^7.24.1" + css-box-model "^1.2.1" + memoize-one "^6.0.0" + raf-schd "^4.0.3" + react-redux "^8.1.3" + redux "^4.2.1" + use-memo-one "^1.1.3" + "@humanwhocodes/config-array@^0.11.14": version "0.11.14" resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz" @@ -623,6 +643,14 @@ resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== +"@types/hoist-non-react-statics@^3.3.1": + version "3.3.5" + resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz#dab7867ef789d87e2b4b0003c9d65c49cc44a494" + integrity sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg== + dependencies: + "@types/react" "*" + hoist-non-react-statics "^3.3.0" + "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.15", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" @@ -677,6 +705,11 @@ resolved "https://registry.npmjs.org/@types/statuses/-/statuses-2.0.5.tgz" integrity sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A== +"@types/use-sync-external-store@^0.0.3": + version "0.0.3" + resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" + integrity sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA== + "@types/wrap-ansi@^3.0.0": version "3.0.0" resolved "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz" @@ -1492,6 +1525,13 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +css-box-model@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1" + integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw== + dependencies: + tiny-invariant "^1.0.6" + css.escape@^1.5.1: version "1.5.1" resolved "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz" @@ -2437,6 +2477,13 @@ headers-polyfill@^4.0.2: resolved "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-4.0.3.tgz" integrity sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ== +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + html-encoding-sniffer@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz" @@ -2984,6 +3031,11 @@ magic-string@^0.30.5: dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" +memoize-one@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" + integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw== + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" @@ -3525,6 +3577,11 @@ queue-microtask@^1.2.2: resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +raf-schd@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a" + integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ== + react-dom@^18.2.0: version "18.3.1" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz" @@ -3543,7 +3600,7 @@ react-icons@^5.2.1: resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.2.1.tgz#28c2040917b2a2eda639b0f797bff1888e018e4a" integrity sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw== -react-is@^16.13.1: +react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -3558,6 +3615,18 @@ react-is@^18.0.0: resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== +react-redux@^8.1.3: + version "8.1.3" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.1.3.tgz#4fdc0462d0acb59af29a13c27ffef6f49ab4df46" + integrity sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw== + dependencies: + "@babel/runtime" "^7.12.1" + "@types/hoist-non-react-statics" "^3.3.1" + "@types/use-sync-external-store" "^0.0.3" + hoist-non-react-statics "^3.3.2" + react-is "^18.0.0" + use-sync-external-store "^1.0.0" + react-router-dom@^6.23.1: version "6.23.1" resolved "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.1.tgz" @@ -3602,6 +3671,13 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" +redux@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" + integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== + dependencies: + "@babel/runtime" "^7.9.2" + reflect.getprototypeof@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz#3ab04c32a8390b770712b7a8633972702d278859" @@ -4098,6 +4174,11 @@ thenify-all@^1.0.0: dependencies: any-promise "^1.0.0" +tiny-invariant@^1.0.6: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + tinybench@^2.5.1: version "2.8.0" resolved "https://registry.npmjs.org/tinybench/-/tinybench-2.8.0.tgz" @@ -4298,11 +4379,21 @@ url-parse@^1.5.3: querystringify "^2.1.1" requires-port "^1.0.0" +use-memo-one@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.3.tgz#2fd2e43a2169eabc7496960ace8c79efef975e99" + integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ== + use-sync-external-store@1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== +use-sync-external-store@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz#c3b6390f3a30eba13200d2302dcdf1e7b57b2ef9" + integrity sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw== + util-deprecate@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" From c268db0cbb6df75a34eb9d596324bc1e01f68f2d Mon Sep 17 00:00:00 2001 From: Seok93 Date: Wed, 3 Jul 2024 21:40:26 +0900 Subject: [PATCH 2/6] =?UTF-8?q?Chore:=20#33=20mock=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20Types=20=EC=B6=94=EA=B0=80=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mocks/mockData.ts | 143 +++++++++++++++++++++++++++++++++++ src/types/TodoStatusType.tsx | 1 + src/types/TodoType.tsx | 14 ++++ 3 files changed, 158 insertions(+) create mode 100644 src/mocks/mockData.ts create mode 100644 src/types/TodoType.tsx diff --git a/src/mocks/mockData.ts b/src/mocks/mockData.ts new file mode 100644 index 00000000..20b85a53 --- /dev/null +++ b/src/mocks/mockData.ts @@ -0,0 +1,143 @@ +import type { TodoStatus } from '@/types/TodoStatusType'; +import type { TodoWithStatus } from '@/types/TodoType'; + +export const USER_DUMMY = [ + { + userId: 1, + email: 'seok@naver.com', + nickname: '꾸르', + bio: '풀스택 개발자를 목표중', + }, + { + userId: 2, + email: 'jinju@naver.com', + nickname: '무드메이커', + bio: '디자이너 + 프론트엔드 육각형 인재', + }, + { + userId: 3, + email: 'yesol@naver.com', + nickname: 'SOL천사', + bio: '프론트엔드 취준생', + }, +]; + +export const STATUS_DUMMY: TodoStatus[] = [ + { + statusId: 1, + name: 'To Do', + color: '#c83c00', + order: 1, + }, + { + statusId: 2, + name: 'In Progress', + color: '#dab700', + order: 2, + }, + { + statusId: 3, + name: 'Done', + color: '#237700', + order: 3, + }, +]; + +export const TODO_DUMMY: TodoWithStatus[] = [ + { + statusId: 1, + name: 'To Do', + color: '#c83c00', + order: 1, + tasks: [ + { + taskId: 7, + name: '할일 추가 모달 구현하기', + order: 1, + userId: 3, + files: [], + startDate: '2024-06-26', + endDate: '2024-07-02', + }, + { + taskId: 8, + name: 'ID 찾기 페이지 작성하기', + order: 2, + userId: 3, + files: [], + startDate: '2024-07-03', + endDate: '2024-07-05', + }, + { + taskId: 9, + name: 'DnD 구현하기', + order: 3, + userId: 1, + files: [], + startDate: '2024-06-30', + endDate: '2024-07-02', + }, + ], + }, + { + statusId: 2, + name: 'In Progress', + color: '#dab700', + order: 2, + tasks: [ + { + taskId: 5, + name: 'DnD 기술 조사하기', + order: 1, + userId: 1, + files: [], + startDate: '2024-06-27', + endDate: '2024-06-29', + }, + { + taskId: 4, + name: 'API 명세서 작성하기', + order: 2, + userId: 2, + files: [], + startDate: '2024-06-27', + endDate: '2024-06-29', + }, + ], + }, + { + statusId: 3, + name: 'Done', + color: '#237700', + order: 3, + tasks: [ + { + taskId: 1, + name: 'todo 상태 추가 모달 작업하기', + order: 1, + userId: 2, + files: [], + startDate: '2024-06-22', + endDate: '2024-06-26', + }, + { + taskId: 2, + name: 'project layout 작성하기', + order: 2, + userId: 1, + files: [], + startDate: '2024-06-18', + endDate: '2024-06-21', + }, + { + taskId: 3, + name: 'tailwindcss 설정하기', + order: 3, + userId: 3, + files: [], + startDate: '2024-06-14', + endDate: '2024-06-18', + }, + ], + }, +]; diff --git a/src/types/TodoStatusType.tsx b/src/types/TodoStatusType.tsx index 3974665e..ec760f8f 100644 --- a/src/types/TodoStatusType.tsx +++ b/src/types/TodoStatusType.tsx @@ -3,6 +3,7 @@ export type TodoStatus = { statusId: number; name: string; color: string; + order: number; }; export type TodoStatusForm = { diff --git a/src/types/TodoType.tsx b/src/types/TodoType.tsx new file mode 100644 index 00000000..7ae1b364 --- /dev/null +++ b/src/types/TodoType.tsx @@ -0,0 +1,14 @@ +import { TodoStatus } from './TodoStatusType'; + +// ToDo: API 설계 완료시 데이터 타입 변경할 것 +export type Todo = { + taskId: number; + name: string; + order: number; + userId: number; + files: string[]; + startDate: string; + endDate: string; +}; + +export type TodoWithStatus = TodoStatus & { tasks: Todo[] }; From af7d948dcc25261a7c18d8927166e47356256cac Mon Sep 17 00:00:00 2001 From: Seok93 Date: Wed, 3 Jul 2024 21:56:11 +0900 Subject: [PATCH 3/6] =?UTF-8?q?Config:=20#33=20ESLint=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=20=EC=99=84=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .eslintrc.cjs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index c718d31d..61e4af5b 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -21,6 +21,9 @@ module.exports = { 'consistent-return': 'off', 'object-curly-newline': 'off', '@typescript-eslint/no-unused-vars': 'warn', + '@typescript-eslint/no-shadow': 'warn', + 'no-param-reassign': 'warn', + 'no-return-assign': 'warn', 'no-unused-vars': 'warn', 'react/react-in-jsx-scope': 'off', 'react/prop-types': 'off', From 56f4ef654498daf94cd08c81dea5f8dfc460d735 Mon Sep 17 00:00:00 2001 From: Seok93 Date: Wed, 3 Jul 2024 21:57:46 +0900 Subject: [PATCH 4/6] =?UTF-8?q?Feat:=20#33=20=EC=B9=B8=EB=B0=98=20?= =?UTF-8?q?=EB=B3=B4=EB=93=9C=20UI=20=EC=9E=91=EC=84=B1=20&=20=EC=B9=B8?= =?UTF-8?q?=EB=B0=98=20=EB=B3=B4=EB=93=9C=20=ED=95=A0=EC=9D=BC=20=EB=93=9C?= =?UTF-8?q?=EB=9E=98=EA=B7=B8=20=EC=95=A4=20=EB=93=9C=EB=A1=AD=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/globals.css | 3 +- src/pages/project/KanbanPage.tsx | 142 ++++++++++++++++++++++++++++++- src/utils/deepClone.ts | 22 +++++ 3 files changed, 165 insertions(+), 2 deletions(-) create mode 100644 src/utils/deepClone.ts diff --git a/src/globals.css b/src/globals.css index b351b9dc..657b5d61 100644 --- a/src/globals.css +++ b/src/globals.css @@ -78,6 +78,7 @@ /* ========= Scrollbar Custom ========= */ ::-webkit-scrollbar { width: 10px; + height: 10px; } ::-webkit-scrollbar-track { border: 1px solid var(--border-scroll); @@ -93,6 +94,6 @@ @layer components { .selected::before { - @apply absolute left-0 top-0 block h-30 w-4 bg-main content-['']; + @apply absolute left-0 top-0 block h-30 w-3 bg-main content-['']; } } diff --git a/src/pages/project/KanbanPage.tsx b/src/pages/project/KanbanPage.tsx index d4696480..013473fd 100644 --- a/src/pages/project/KanbanPage.tsx +++ b/src/pages/project/KanbanPage.tsx @@ -1,3 +1,143 @@ +import { useState } from 'react'; +import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'; +import { BsPencil } from 'react-icons/bs'; +import deepClone from '@utils/deepClone'; + +import { TODO_DUMMY } from '@mocks/mockData'; +import type { DropResult } from '@hello-pangea/dnd'; +import type { Todo, TodoWithStatus } from '@/types/TodoType'; + +// ToDo: 유틸리티로 분리할지 고려하기 +function generatorPrefixId(id: number | string, prefix: string, delimiter: string = '-') { + const result = prefix + delimiter + id; + return result; +} +function parserPrefixId(prefixId: string, delimiter: string = '-') { + const result = prefixId.split(delimiter); + return result[result.length - 1]; +} + +function createChangedTodoForSameStatus(todo: TodoWithStatus[], dropResult: DropResult) { + const { source, draggableId } = dropResult; + + const newTodo = deepClone(todo); + const sourceStatusId = Number(parserPrefixId(source.droppableId)); + const taskId = Number(parserPrefixId(draggableId)); + + const { tasks: sourceTasks } = newTodo.find((data) => data.statusId === sourceStatusId)! as TodoWithStatus; + const task = sourceTasks.find((data) => data.taskId === taskId)! as Todo; + + sourceTasks.splice(source.index, 1); + sourceTasks.splice(source.index, 0, task); + sourceTasks.forEach((task, index) => (task.order = index + 1)); + + return newTodo; +} + +function createChangedTodoForOtherStatus(todo: TodoWithStatus[], dropResult: DropResult) { + const { source, destination, draggableId } = dropResult; + + // ToDo: 메세지 포맷 정하고 수정하기 + if (!destination) throw Error('Error: DnD destination is null'); + + const newTodo = deepClone(todo); + const sourceStatusId = Number(parserPrefixId(source.droppableId)); + const destinationStatusId = Number(parserPrefixId(destination.droppableId)); + const taskId = Number(parserPrefixId(draggableId)); + + const { tasks: sourceTasks } = newTodo.find((data) => data.statusId === sourceStatusId)! as TodoWithStatus; + const { tasks: destinationTasks } = newTodo.find((data) => data.statusId === destinationStatusId)! as TodoWithStatus; + const task = sourceTasks.find((data) => data.taskId === taskId)! as Todo; + + sourceTasks.splice(source.index, 1); + destinationTasks.splice(destination.index, 0, task); + + sourceTasks.forEach((task, index) => { + task.order = index + 1; + }); + destinationTasks.forEach((task, index) => { + task.order = index + 1; + }); + + return newTodo; +} + +// ToDo: 할일 상태 Vertical DnD 추가할 것 +// ToDo: DnD시 가시성을 위한 애니메이션 처리 추가할 것 +// ToDo: 칸반보드 ItemList, Item 컴포넌트로 분리할 것 export default function KanbanPage() { - return
KanbanPage
; + const [todo, setTodo] = useState(TODO_DUMMY); + + const handleDragEnd = (dropResult: DropResult) => { + const { source, destination } = dropResult; + + if (!destination) return; + if (source.droppableId === destination.droppableId && source.index === destination.index) return; + + const newTodo = + source.droppableId !== destination.droppableId + ? createChangedTodoForOtherStatus(todo, dropResult) + : createChangedTodoForSameStatus(todo, dropResult); + + setTodo(newTodo); + }; + + return ( +
+ + {todo.map((data) => { + const { statusId, name, color, tasks } = data; + const droppableId = generatorPrefixId(statusId, 'status'); + return ( +
+
+

{name}

+ + + +
+
+ + {(dropProvided) => { + return ( +
+ {tasks.map((task) => { + const { taskId, name, order } = task; + const draggableId = generatorPrefixId(taskId, 'task'); + const index = order - 1; + return ( + + {(dragProvided) => { + return ( +
+
+
{name}
+
+ ); + }} + + ); + })} + {dropProvided.placeholder} +
+ ); + }} +
+
+
+ ); + })} +
+
+ ); } diff --git a/src/utils/deepClone.ts b/src/utils/deepClone.ts new file mode 100644 index 00000000..670434f8 --- /dev/null +++ b/src/utils/deepClone.ts @@ -0,0 +1,22 @@ +export default function deepClone(obj: T): T { + if (obj === undefined || obj === null) return obj; + + if (typeof obj !== 'object') return obj; + + if (obj instanceof Date) return new Date(obj.getTime()) as T; + + if (Array.isArray(obj)) { + const arrCopy = [] as unknown[]; + obj.forEach((item) => arrCopy.push(deepClone(item))); + return arrCopy as T; + } + + const objCopy = {} as { [key: string]: unknown }; + Object.keys(obj).forEach((key) => { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + objCopy[key] = deepClone((obj as { [key: string]: unknown })[key]); + } + }); + + return objCopy as T; +} From 5f3c0e5e691ed7a5472a45145c24b17bb947a7c7 Mon Sep 17 00:00:00 2001 From: Seok93 Date: Wed, 3 Jul 2024 21:58:46 +0900 Subject: [PATCH 5/6] =?UTF-8?q?UI:=20#33=20ProjectLayout=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/layouts/page/ProjectLayout.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/layouts/page/ProjectLayout.tsx b/src/layouts/page/ProjectLayout.tsx index 763b8381..57099a61 100644 --- a/src/layouts/page/ProjectLayout.tsx +++ b/src/layouts/page/ProjectLayout.tsx @@ -57,15 +57,15 @@ export default function ProjectLayout() { Project Setting -
-
-
    -
  • +
    +
    +
      +
    • (isActive ? 'text-main' : 'text-emphasis')}> Calendar
    • -
    • +
    • (isActive ? 'text-main' : 'text-emphasis')}> Kanban From c51ca003cbe4361797403e502200d298ad01a32c Mon Sep 17 00:00:00 2001 From: Seok93 Date: Wed, 3 Jul 2024 22:35:44 +0900 Subject: [PATCH 6/6] =?UTF-8?q?Formmating:=20#33=20deepClone=EC=9D=98=20?= =?UTF-8?q?=EB=A7=A4=EA=B0=9C=EB=B3=80=EC=88=98=EB=AA=85=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/deepClone.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/utils/deepClone.ts b/src/utils/deepClone.ts index 670434f8..5898a1c8 100644 --- a/src/utils/deepClone.ts +++ b/src/utils/deepClone.ts @@ -1,20 +1,20 @@ -export default function deepClone(obj: T): T { - if (obj === undefined || obj === null) return obj; +export default function deepClone(data: T): T { + if (data === undefined || data === null) return data; - if (typeof obj !== 'object') return obj; + if (typeof data !== 'object') return data; - if (obj instanceof Date) return new Date(obj.getTime()) as T; + if (data instanceof Date) return new Date(data.getTime()) as T; - if (Array.isArray(obj)) { + if (Array.isArray(data)) { const arrCopy = [] as unknown[]; - obj.forEach((item) => arrCopy.push(deepClone(item))); + data.forEach((item) => arrCopy.push(deepClone(item))); return arrCopy as T; } const objCopy = {} as { [key: string]: unknown }; - Object.keys(obj).forEach((key) => { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - objCopy[key] = deepClone((obj as { [key: string]: unknown })[key]); + Object.keys(data).forEach((key) => { + if (Object.prototype.hasOwnProperty.call(data, key)) { + objCopy[key] = deepClone((data as { [key: string]: unknown })[key]); } });