From 6f3c3a9aaa63c8686f23a656a32cb7ae816309a9 Mon Sep 17 00:00:00 2001 From: dmdgpdi <33450285+dmdgpdi@users.noreply.github.com> Date: Thu, 7 Nov 2024 20:02:06 +0900 Subject: [PATCH 01/18] =?UTF-8?q?chore:=20=EB=B0=B1=EC=97=94=EB=93=9C=20ap?= =?UTF-8?q?i=20=ED=83=80=EC=9E=85=20=EC=B5=9C=EC=8B=A0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/schema/schema.d.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/schema/schema.d.ts b/frontend/schema/schema.d.ts index c944b76db..eae663f59 100644 --- a/frontend/schema/schema.d.ts +++ b/frontend/schema/schema.d.ts @@ -320,7 +320,10 @@ export interface components { /** Format: int64 */ id?: number; name?: string; - /** @enum {string} */ + /** + * @example GENERAL + * @enum {string} + */ folderType?: "UNCLASSIFIED" | "RECYCLE_BIN" | "ROOT" | "GENERAL"; /** Format: int64 */ parentFolderId?: number; @@ -740,7 +743,7 @@ export interface operations { [name: string]: unknown; }; content: { - "*/*": components["schemas"]["techpick.api.application.folder.dto.FolderApiResponse"][]; + "application/json": unknown; }; }; /** @description 본인 폴더만 조회할 수 있습니다. */ From 5a5ec73d9bdb10564ca0e8fac01f99a9e73ae48e Mon Sep 17 00:00:00 2001 From: dmdgpdi <33450285+dmdgpdi@users.noreply.github.com> Date: Thu, 7 Nov 2024 20:05:28 +0900 Subject: [PATCH 02/18] =?UTF-8?q?chore:=20util=20=EC=8A=A4=ED=8E=A0?= =?UTF-8?q?=EB=A7=81=20=EC=A0=95=ED=99=95=ED=95=98=EA=B2=8C=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 --- frontend/techpick/src/apis/pick/pickApi.type.ts | 2 +- frontend/techpick/src/types/folder.type.ts | 2 +- frontend/techpick/src/types/index.ts | 1 + frontend/techpick/src/types/tag.type.ts | 2 +- frontend/techpick/src/types/{uitl.type.ts => util.type.ts} | 0 5 files changed, 4 insertions(+), 3 deletions(-) rename frontend/techpick/src/types/{uitl.type.ts => util.type.ts} (100%) diff --git a/frontend/techpick/src/apis/pick/pickApi.type.ts b/frontend/techpick/src/apis/pick/pickApi.type.ts index 77902c4e5..d6187bc72 100644 --- a/frontend/techpick/src/apis/pick/pickApi.type.ts +++ b/frontend/techpick/src/apis/pick/pickApi.type.ts @@ -1,4 +1,4 @@ -import type { Concrete } from '@/types/uitl.type'; +import type { Concrete } from '@/types/util.type'; import type { components } from '@/schema'; export type GetPickResponseType = Concrete< diff --git a/frontend/techpick/src/types/folder.type.ts b/frontend/techpick/src/types/folder.type.ts index ba5676d6a..f6cbff05b 100644 --- a/frontend/techpick/src/types/folder.type.ts +++ b/frontend/techpick/src/types/folder.type.ts @@ -1,4 +1,4 @@ -import type { Concrete } from './uitl.type'; +import type { Concrete } from './util.type'; import type { UniqueIdentifier } from '@dnd-kit/core'; import type { components } from '@/schema'; diff --git a/frontend/techpick/src/types/index.ts b/frontend/techpick/src/types/index.ts index f891fa9f9..d1036e63b 100644 --- a/frontend/techpick/src/types/index.ts +++ b/frontend/techpick/src/types/index.ts @@ -1,3 +1,4 @@ export type { NodeData, DirectoryNodeProps } from './NodeData'; export type * from './tag.type'; export type * from './folder.type'; +export type * from './pick.type'; diff --git a/frontend/techpick/src/types/tag.type.ts b/frontend/techpick/src/types/tag.type.ts index b4371368d..e1dcd0a75 100644 --- a/frontend/techpick/src/types/tag.type.ts +++ b/frontend/techpick/src/types/tag.type.ts @@ -1,4 +1,4 @@ -import type { Concrete } from './uitl.type'; +import type { Concrete } from './util.type'; import type { components } from '@/schema'; export type TagType = Concrete< diff --git a/frontend/techpick/src/types/uitl.type.ts b/frontend/techpick/src/types/util.type.ts similarity index 100% rename from frontend/techpick/src/types/uitl.type.ts rename to frontend/techpick/src/types/util.type.ts From fb66bba22c6e1ea00113883e235cdd372c881d83 Mon Sep 17 00:00:00 2001 From: dmdgpdi <33450285+dmdgpdi@users.noreply.github.com> Date: Thu, 7 Nov 2024 20:05:40 +0900 Subject: [PATCH 03/18] =?UTF-8?q?feat:=20pickType=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/techpick/src/types/pick.type.ts | 30 ++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 frontend/techpick/src/types/pick.type.ts diff --git a/frontend/techpick/src/types/pick.type.ts b/frontend/techpick/src/types/pick.type.ts new file mode 100644 index 000000000..a2702844a --- /dev/null +++ b/frontend/techpick/src/types/pick.type.ts @@ -0,0 +1,30 @@ +import type { Concrete } from './util.type'; +import type { components } from '@/schema'; + +export type PickInfoType = Concrete< + components['schemas']['techpick.api.domain.pick.dto.PickResult$Pick'] +>; + +export type PickInfoRecordType = { + [pickId: string]: PickInfoType | undefined; +}; + +export type PickIdOrderedListType = number[]; + +export type PickRecordValueType = { + pickIdOrderedList: PickIdOrderedListType; + pickInfoRecord: PickInfoRecordType; +}; + +export type PickRecordType = { + [folderId: string]: PickRecordValueType | undefined; +}; + +export type PickListType = Concrete< + components['schemas']['techpick.api.domain.pick.dto.PickResult$Pick'] +>[]; + +export type GetPicksByFolderIdResponseType = { + folderId: number; + pickList: PickListType; +}[]; From 430200a3a999967061bed6c1666ae2f9d29cbf55 Mon Sep 17 00:00:00 2001 From: dmdgpdi <33450285+dmdgpdi@users.noreply.github.com> Date: Thu, 7 Nov 2024 20:06:04 +0900 Subject: [PATCH 04/18] =?UTF-8?q?feat:=20getPicksByFolderId=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/techpick/src/apis/apiConstants.ts | 3 ++ .../src/apis/pick/getPicksByFolderId.ts | 46 +++++++++++++++++++ frontend/techpick/src/apis/pick/index.ts | 1 + 3 files changed, 50 insertions(+) create mode 100644 frontend/techpick/src/apis/pick/getPicksByFolderId.ts diff --git a/frontend/techpick/src/apis/apiConstants.ts b/frontend/techpick/src/apis/apiConstants.ts index a6f876233..c526d0021 100644 --- a/frontend/techpick/src/apis/apiConstants.ts +++ b/frontend/techpick/src/apis/apiConstants.ts @@ -2,6 +2,7 @@ const API_ENDPOINTS = { FOLDERS: 'folders', LOCATION: 'location', BASIC: 'basic', + PICKS: 'picks', }; export const API_URLS = { @@ -11,4 +12,6 @@ export const API_URLS = { UPDATE_FOLDER: API_ENDPOINTS.FOLDERS, MOVE_FOLDER: `${API_ENDPOINTS.FOLDERS}/${API_ENDPOINTS.LOCATION}`, GET_BASIC_FOLDERS: `${API_ENDPOINTS.FOLDERS}/${API_ENDPOINTS.BASIC}`, + GET_PICKS_BY_FOLDER_ID: (folderId: number) => + `${API_ENDPOINTS.PICKS}?folderIdList=${folderId}`, }; diff --git a/frontend/techpick/src/apis/pick/getPicksByFolderId.ts b/frontend/techpick/src/apis/pick/getPicksByFolderId.ts new file mode 100644 index 000000000..521581b1a --- /dev/null +++ b/frontend/techpick/src/apis/pick/getPicksByFolderId.ts @@ -0,0 +1,46 @@ +import { HTTPError } from 'ky'; +import { apiClient, returnErrorFromHTTPError } from '@/apis'; +import { API_URLS } from '../apiConstants'; +import type { + GetPicksByFolderIdResponseType, + PickIdOrderedListType, + PickInfoRecordType, + PickListType, +} from '@/types'; + +export const getPicksByFolderId = async (folderId: number) => { + const data = await getPickListByFolderId(folderId); + + const pickRecordData = generatePickRecordData(data[0]['pickList']); + + return pickRecordData; +}; + +const getPickListByFolderId = async (folderId: number) => { + try { + const response = await apiClient.get( + API_URLS.GET_PICKS_BY_FOLDER_ID(folderId) + ); + const data = await response.json(); + + return data; + } catch (httpError) { + if (httpError instanceof HTTPError) { + const error = returnErrorFromHTTPError(httpError); + throw error; + } + throw httpError; + } +}; + +const generatePickRecordData = (pickList: PickListType) => { + const pickIdOrderedList: PickIdOrderedListType = []; + const pickInfoRecord = {} as PickInfoRecordType; + + for (const pickInfo of pickList) { + pickIdOrderedList.push(pickInfo.id); + pickInfoRecord[`${pickInfo.id}`] = pickInfo; + } + + return { pickIdOrderedList, pickInfoRecord }; +}; diff --git a/frontend/techpick/src/apis/pick/index.ts b/frontend/techpick/src/apis/pick/index.ts index a25035844..9de1167a3 100644 --- a/frontend/techpick/src/apis/pick/index.ts +++ b/frontend/techpick/src/apis/pick/index.ts @@ -1,2 +1,3 @@ export { useGetPickQuery } from './getPick/useGetPickQuery'; export { useUpdatePickMutation } from './updatePick/useUpdatePickMutation'; +export { getPicksByFolderId } from './getPicksByFolderId'; From 9c21353d01171b5048f73933d93e065f94460cd1 Mon Sep 17 00:00:00 2001 From: dmdgpdi <33450285+dmdgpdi@users.noreply.github.com> Date: Thu, 7 Nov 2024 20:07:12 +0900 Subject: [PATCH 05/18] =?UTF-8?q?refactor:=20viewTemplate=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PickListViewerPanel.tsx | 7 +++++- .../template/view/ViewTemplate.ts | 17 +++++++------ .../view/component/Card/SimpleCard.tsx | 25 ++++--------------- .../view/component/Record/SimpleRecord.tsx | 24 +++++------------- .../PickListViewerPanel/types/common.type.ts | 6 +++-- 5 files changed, 31 insertions(+), 48 deletions(-) diff --git a/frontend/techpick/src/components/PickListViewerPanel/PickListViewerPanel.tsx b/frontend/techpick/src/components/PickListViewerPanel/PickListViewerPanel.tsx index 5481f182f..f332f9385 100644 --- a/frontend/techpick/src/components/PickListViewerPanel/PickListViewerPanel.tsx +++ b/frontend/techpick/src/components/PickListViewerPanel/PickListViewerPanel.tsx @@ -9,6 +9,8 @@ import { ViewTemplate } from './template/view/ViewTemplate'; import { Pick } from './types/common.type'; import { getStream } from './util'; +/// activeOptions.viewTemplate 이 현재 어떤 viewTemplate인지. + /** * @todo * 자세한 이름으로 변경 필요 @@ -49,7 +51,10 @@ function PickListWidget({ activeFilters, viewTemplate }: ListWidgetProps) { return (
{processedPickList.map((pick, idx) => ( - + hi
}} + key={idx} + /> ))} ); diff --git a/frontend/techpick/src/components/PickListViewerPanel/template/view/ViewTemplate.ts b/frontend/techpick/src/components/PickListViewerPanel/template/view/ViewTemplate.ts index c593cc48a..3661fd29a 100644 --- a/frontend/techpick/src/components/PickListViewerPanel/template/view/ViewTemplate.ts +++ b/frontend/techpick/src/components/PickListViewerPanel/template/view/ViewTemplate.ts @@ -1,17 +1,20 @@ +import type { ReactNode } from 'react'; import { ListBulletIcon, ViewGridIcon } from '@radix-ui/react-icons'; import { SimpleCard } from './component/Card/SimpleCard'; import { SimpleRecord } from './component/Record/SimpleRecord'; import { gridLayout } from './layout/GridLayout.css'; import { listLayout } from './layout/ListLayout.css'; -import { - Pick, - UiIcon, - UiLabel, - UiListComponent, -} from '../../types/common.type'; +import { UiIcon, UiLabel, UiListComponent } from '../../types/common.type'; -export type ViewTemplate = UiIcon & UiLabel & UiListComponent; +export interface ListProps { + pickId: number; + children: ReactNode; +} +// 여기 +export type ViewTemplate = UiIcon & UiLabel & UiListComponent; + +// ViewTemplateType을 가져간다. export type ViewTemplateType = 'GRID' | 'LIST'; /** diff --git a/frontend/techpick/src/components/PickListViewerPanel/template/view/component/Card/SimpleCard.tsx b/frontend/techpick/src/components/PickListViewerPanel/template/view/component/Card/SimpleCard.tsx index 0c2e8f412..76d9b0502 100644 --- a/frontend/techpick/src/components/PickListViewerPanel/template/view/component/Card/SimpleCard.tsx +++ b/frontend/techpick/src/components/PickListViewerPanel/template/view/component/Card/SimpleCard.tsx @@ -1,25 +1,10 @@ import { ReactElement } from 'react'; -import { - Pick, - UiProps, -} from '@/components/PickListViewerPanel/types/common.type'; -import { - ChipItem, - ChipItemList, -} from '@/components/PickListViewerPanel/ui/SelectedTagItem'; +import { UiProps } from '@/components/PickListViewerPanel/types/common.type'; import { cardLayout } from './SimpleCard.css'; +import { ListProps } from '../../ViewTemplate'; -interface CardProps extends UiProps {} +export function SimpleCard(props: UiProps): ReactElement { + console.log('props', props); -export function SimpleCard({ uiData: pick }: CardProps): ReactElement { - return ( -
- {pick.title} - - {pick.tagList.map((tag, idx) => ( - - ))} - -
- ); + return
{}
; } diff --git a/frontend/techpick/src/components/PickListViewerPanel/template/view/component/Record/SimpleRecord.tsx b/frontend/techpick/src/components/PickListViewerPanel/template/view/component/Record/SimpleRecord.tsx index 6b35e8f90..344e19fe8 100644 --- a/frontend/techpick/src/components/PickListViewerPanel/template/view/component/Record/SimpleRecord.tsx +++ b/frontend/techpick/src/components/PickListViewerPanel/template/view/component/Record/SimpleRecord.tsx @@ -1,27 +1,15 @@ import { ReactElement } from 'react'; -import { - Pick, - UiProps, -} from '@/components/PickListViewerPanel/types/common.type'; -import { - ChipItem, - ChipItemList, -} from '@/components/PickListViewerPanel/ui/SelectedTagItem'; +import { UiProps } from '@/components/PickListViewerPanel/types/common.type'; +import { ChipItemList } from '@/components/PickListViewerPanel/ui/SelectedTagItem'; import { recordLayout } from './SimpleRecord.css'; +import { ListProps } from '../../ViewTemplate'; -export interface SimpleRecordProps extends UiProps {} +export function SimpleRecord(props: UiProps): ReactElement { + console.log(props); -export function SimpleRecord({ - uiData: pick, -}: SimpleRecordProps): ReactElement { return (
- {pick.title} - - {pick.tagList.map((tag, idx) => ( - - ))} - +
); } diff --git a/frontend/techpick/src/components/PickListViewerPanel/types/common.type.ts b/frontend/techpick/src/components/PickListViewerPanel/types/common.type.ts index 75b723bf1..09ec1949f 100644 --- a/frontend/techpick/src/components/PickListViewerPanel/types/common.type.ts +++ b/frontend/techpick/src/components/PickListViewerPanel/types/common.type.ts @@ -44,13 +44,15 @@ export interface UiLabel { export interface UiListComponent { listLayoutStyle: string; - renderComponent: >(props: Props) => ReactElement; + renderComponent: (props: UiProps) => ReactElement; } export interface UiProps { - uiData: T; + props: T; } +export interface DraggablePickUiProps extends UiProps {} + export interface Comparable { compare: CompareFn; } From bbe2832b870da5bb3be00bf96d49ec2f1c87c7b4 Mon Sep 17 00:00:00 2001 From: dmdgpdi <33450285+dmdgpdi@users.noreply.github.com> Date: Sat, 9 Nov 2024 15:08:23 +0900 Subject: [PATCH 06/18] =?UTF-8?q?refactor:=20=EB=B0=94=EB=A1=9C=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C=EC=9D=B4=20=EC=95=84=EB=8B=8C,=20useEffect?= =?UTF-8?q?=20=EB=82=B4=EC=97=90=EC=84=9C=20=EB=B9=84=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=ED=98=B8=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/techpick/src/components/FolderTree/index.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/techpick/src/components/FolderTree/index.tsx b/frontend/techpick/src/components/FolderTree/index.tsx index d173ebff0..4e4c1b46a 100644 --- a/frontend/techpick/src/components/FolderTree/index.tsx +++ b/frontend/techpick/src/components/FolderTree/index.tsx @@ -1,5 +1,6 @@ 'use client'; +import { useEffect } from 'react'; import { DragOverlay } from '@dnd-kit/core'; import { useCreateFolderInputStore } from '@/stores/createFolderInputStore'; import { useTreeStore } from '@/stores/dndTreeStore/dndTreeStore'; @@ -12,12 +13,14 @@ import { TreeNode } from './TreeNode'; export function FolderTree() { const { newFolderParentId } = useCreateFolderInputStore(); - const { getFolders, getBasicFolders } = useTreeStore.getState(); + const { getFolders, getBasicFolders } = useTreeStore(); const rootFolderId = useTreeStore((state) => state.rootFolderId); const isShow = newFolderParentId !== rootFolderId; - getFolders(); - getBasicFolders(); + useEffect(() => { + getFolders(); + getBasicFolders(); + }, [getBasicFolders, getFolders]); return ( From 957ae8221a4ceb9b840a4dde669cd73ca38e5374 Mon Sep 17 00:00:00 2001 From: dmdgpdi <33450285+dmdgpdi@users.noreply.github.com> Date: Sat, 9 Nov 2024 15:08:51 +0900 Subject: [PATCH 07/18] =?UTF-8?q?chore:=20git=20pull=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EC=BB=A4=EB=B0=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(signed)/folders/unclassified/page.tsx | 15 +++- .../src/stores/pickStore/pickStore.ts | 77 +++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 frontend/techpick/src/stores/pickStore/pickStore.ts diff --git a/frontend/techpick/src/app/(signed)/folders/unclassified/page.tsx b/frontend/techpick/src/app/(signed)/folders/unclassified/page.tsx index 381d87ab3..6ee4f3359 100644 --- a/frontend/techpick/src/app/(signed)/folders/unclassified/page.tsx +++ b/frontend/techpick/src/app/(signed)/folders/unclassified/page.tsx @@ -2,8 +2,10 @@ import { useEffect } from 'react'; import { useTreeStore } from '@/stores/dndTreeStore/dndTreeStore'; +import { usePickStore } from '@/stores/pickStore/pickStore'; export default function UnclassifiedFolderPage() { + const { fetchPickDataByFolderId, pickRecord } = usePickStore(); const selectSingleFolder = useTreeStore((state) => state.selectSingleFolder); const basicFolderMap = useTreeStore((state) => state.basicFolderMap); @@ -14,9 +16,20 @@ export default function UnclassifiedFolderPage() { } selectSingleFolder(basicFolderMap['UNCLASSIFIED'].id); + fetchPickDataByFolderId(basicFolderMap['UNCLASSIFIED'].id); }, - [basicFolderMap, selectSingleFolder] + [basicFolderMap, selectSingleFolder, fetchPickDataByFolderId] ); + useEffect(() => { + if (!basicFolderMap) { + return; + } + + if (pickRecord[basicFolderMap['UNCLASSIFIED'].id]) { + console.log('pickRecord', pickRecord[basicFolderMap['UNCLASSIFIED'].id]); + } + }, [basicFolderMap, pickRecord]); + return

UnclassifiedFolderPage page

; } diff --git a/frontend/techpick/src/stores/pickStore/pickStore.ts b/frontend/techpick/src/stores/pickStore/pickStore.ts new file mode 100644 index 000000000..97850cfc0 --- /dev/null +++ b/frontend/techpick/src/stores/pickStore/pickStore.ts @@ -0,0 +1,77 @@ +import { create } from 'zustand'; +import { subscribeWithSelector } from 'zustand/middleware'; +import { immer } from 'zustand/middleware/immer'; +import { getPicksByFolderId } from '@/apis/pick'; +import type { + PickRecordType, + PickInfoType, + PickRecordValueType, +} from '@/types'; + +type PickState = { + pickRecord: PickRecordType; +}; + +type PickAction = { + fetchPickDataByFolderId: (folderId: number) => Promise; + getOrderedPickListByFolderId: (folderId: number) => PickInfoType[]; + hasPickRecordValue: ( + pickRecordValue: PickRecordValueType | undefined + ) => pickRecordValue is PickRecordValueType; +}; + +const initialState: PickState = { + pickRecord: {}, +}; + +export const usePickStore = create()( + subscribeWithSelector( + immer((set, get) => ({ + ...initialState, + fetchPickDataByFolderId: async (folderId) => { + try { + const { pickInfoRecord, pickIdOrderedList } = + await getPicksByFolderId(folderId); + + set((state) => { + state.pickRecord[folderId] = { + pickIdOrderedList, + pickInfoRecord, + }; + }); + } catch (error) { + console.log('fetchPickDataByFolderId error', error); + } + }, + getOrderedPickListByFolderId: (folderId: number) => { + const pickRecordValue = get().pickRecord[`${folderId}`]; + + if (!get().hasPickRecordValue(pickRecordValue)) { + return []; + } + + const { pickIdOrderedList, pickInfoRecord } = pickRecordValue; + const pickOrderedList: PickInfoType[] = []; + + for (const pickId of pickIdOrderedList) { + const pickInfo = pickInfoRecord[`${pickId}`]; + + if (pickInfo) { + pickOrderedList.push(pickInfo); + } + } + + return pickOrderedList; + }, + hasPickRecordValue: ( + pickRecordValue + ): pickRecordValue is PickRecordValueType => { + if (!pickRecordValue) { + return false; + } + + return true; + }, + })) + ) +); From e29a84e9312487ff752a745c357436692d8560b6 Mon Sep 17 00:00:00 2001 From: dmdgpdi <33450285+dmdgpdi@users.noreply.github.com> Date: Sat, 9 Nov 2024 17:43:45 +0900 Subject: [PATCH 08/18] =?UTF-8?q?refactor:=20pickCardListViewer=EB=A1=9C?= =?UTF-8?q?=20PickCard=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/app/(signed)/folders/layout.css.ts | 1 + .../(signed)/folders/unclassified/page.tsx | 34 ++++++---- .../src/components/PickCard/PickCard.tsx | 67 ------------------- .../PickCardGridLayout/PickCardGridLayout.tsx | 8 --- .../pickCardGridLayout.css.ts | 10 --- .../components/PickListViewer/PickCard.tsx | 44 ++++++++++++ .../PickListViewer/PickCardListLayout.tsx | 8 +++ .../src/components/PickListViewer/index.tsx | 53 +++++++++++++++ .../pickCard.css.ts | 0 .../PickListViewer/pickCardGridLayout.css.ts | 14 ++++ frontend/techpick/src/components/index.ts | 3 +- 11 files changed, 143 insertions(+), 99 deletions(-) delete mode 100644 frontend/techpick/src/components/PickCard/PickCard.tsx delete mode 100644 frontend/techpick/src/components/PickCardGridLayout/PickCardGridLayout.tsx delete mode 100644 frontend/techpick/src/components/PickCardGridLayout/pickCardGridLayout.css.ts create mode 100644 frontend/techpick/src/components/PickListViewer/PickCard.tsx create mode 100644 frontend/techpick/src/components/PickListViewer/PickCardListLayout.tsx create mode 100644 frontend/techpick/src/components/PickListViewer/index.tsx rename frontend/techpick/src/components/{PickCard => PickListViewer}/pickCard.css.ts (100%) create mode 100644 frontend/techpick/src/components/PickListViewer/pickCardGridLayout.css.ts diff --git a/frontend/techpick/src/app/(signed)/folders/layout.css.ts b/frontend/techpick/src/app/(signed)/folders/layout.css.ts index dd817df44..8717f0bfc 100644 --- a/frontend/techpick/src/app/(signed)/folders/layout.css.ts +++ b/frontend/techpick/src/app/(signed)/folders/layout.css.ts @@ -3,4 +3,5 @@ import { style } from '@vanilla-extract/css'; export const pageContainerLayout = style({ display: 'flex', flexDirection: 'row', + height: '100vh', }); diff --git a/frontend/techpick/src/app/(signed)/folders/unclassified/page.tsx b/frontend/techpick/src/app/(signed)/folders/unclassified/page.tsx index 6ee4f3359..48aa0eea9 100644 --- a/frontend/techpick/src/app/(signed)/folders/unclassified/page.tsx +++ b/frontend/techpick/src/app/(signed)/folders/unclassified/page.tsx @@ -1,11 +1,13 @@ 'use client'; import { useEffect } from 'react'; +import { PickListViewer } from '@/components'; import { useTreeStore } from '@/stores/dndTreeStore/dndTreeStore'; import { usePickStore } from '@/stores/pickStore/pickStore'; export default function UnclassifiedFolderPage() { - const { fetchPickDataByFolderId, pickRecord } = usePickStore(); + const { fetchPickDataByFolderId, getOrderedPickListByFolderId } = + usePickStore(); const selectSingleFolder = useTreeStore((state) => state.selectSingleFolder); const basicFolderMap = useTreeStore((state) => state.basicFolderMap); @@ -16,20 +18,28 @@ export default function UnclassifiedFolderPage() { } selectSingleFolder(basicFolderMap['UNCLASSIFIED'].id); - fetchPickDataByFolderId(basicFolderMap['UNCLASSIFIED'].id); }, - [basicFolderMap, selectSingleFolder, fetchPickDataByFolderId] + [basicFolderMap, selectSingleFolder] ); - useEffect(() => { - if (!basicFolderMap) { - return; - } + useEffect( + function loadPickDataFromRemote() { + if (!basicFolderMap) { + return; + } + + fetchPickDataByFolderId(basicFolderMap['UNCLASSIFIED'].id); + }, + [basicFolderMap, fetchPickDataByFolderId] + ); - if (pickRecord[basicFolderMap['UNCLASSIFIED'].id]) { - console.log('pickRecord', pickRecord[basicFolderMap['UNCLASSIFIED'].id]); - } - }, [basicFolderMap, pickRecord]); + if (!basicFolderMap) { + return
loading...
; + } - return

UnclassifiedFolderPage page

; + return ( + + ); } diff --git a/frontend/techpick/src/components/PickCard/PickCard.tsx b/frontend/techpick/src/components/PickCard/PickCard.tsx deleted file mode 100644 index fb64941b9..000000000 --- a/frontend/techpick/src/components/PickCard/PickCard.tsx +++ /dev/null @@ -1,67 +0,0 @@ -'use client'; - -import { PropsWithChildren } from 'react'; - -export function PickCard({ children }: PropsWithChildren) { - return { children }; - - // 나중에 픽 리스트를 조회할 때 다시 사용할 예정입니다. - - // const { - // data: pickData, - // isLoading, - // isError, - // } = useGetPickQuery(node.data.pickId); - // const ref = useDragHook(node); - - // if (isLoading) { - // return ( - //
- //
- //
- //
- //
- // ); - // } - - // if (isError || !pickData) { - // return

oops! something is wrong

; - // } - - // const { memo, title, linkInfo } = pickData; - // const { imageUrl, url } = linkInfo; - - // return ( - // - //
} - // > - //
- // {imageUrl ? ( - // - // ) : ( - //
- // )} - //
- - //
- //

{title}

- //
- //
- //

{memo}

- //
- //
{children}
- //
- // - // ); -} -interface PickCardProps { - pickId: number; -} diff --git a/frontend/techpick/src/components/PickCardGridLayout/PickCardGridLayout.tsx b/frontend/techpick/src/components/PickCardGridLayout/PickCardGridLayout.tsx deleted file mode 100644 index 4d77d2650..000000000 --- a/frontend/techpick/src/components/PickCardGridLayout/PickCardGridLayout.tsx +++ /dev/null @@ -1,8 +0,0 @@ -'use client'; - -import { PropsWithChildren } from 'react'; -import { pickCardGridLayoutStyle } from './pickCardGridLayout.css'; - -export function PickCardGridLayout({ children }: PropsWithChildren) { - return
{children}
; -} diff --git a/frontend/techpick/src/components/PickCardGridLayout/pickCardGridLayout.css.ts b/frontend/techpick/src/components/PickCardGridLayout/pickCardGridLayout.css.ts deleted file mode 100644 index 864cfbfe9..000000000 --- a/frontend/techpick/src/components/PickCardGridLayout/pickCardGridLayout.css.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { style } from '@vanilla-extract/css'; -import { space } from 'techpick-shared'; - -export const pickCardGridLayoutStyle = style({ - display: 'grid', - gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', // 280px는 PickCard의 width - padding: space[16], - rowGap: space[16], - columnGap: space[16], -}); diff --git a/frontend/techpick/src/components/PickListViewer/PickCard.tsx b/frontend/techpick/src/components/PickListViewer/PickCard.tsx new file mode 100644 index 000000000..babdc3b2d --- /dev/null +++ b/frontend/techpick/src/components/PickListViewer/PickCard.tsx @@ -0,0 +1,44 @@ +import Image from 'next/image'; +import Link from 'next/link'; +import { PickViewItemComponentProps } from '.'; +import { + cardDescriptionSectionStyle, + cardImageSectionStyle, + cardImageStyle, + cardTitleSectionStyle, + defaultCardImageSectionStyle, + linkStyle, + pickCardLayout, +} from './pickCard.css'; + +export function PickCard({ pickInfo }: PickViewItemComponentProps) { + const { memo, title, linkInfo } = pickInfo; + const { imageUrl, url } = linkInfo; + + return ( + +
+
+ {imageUrl ? ( + + ) : ( +
+ )} +
+ +
+

{title}

+
+
+

{memo}

+
+
+ + ); +} diff --git a/frontend/techpick/src/components/PickListViewer/PickCardListLayout.tsx b/frontend/techpick/src/components/PickListViewer/PickCardListLayout.tsx new file mode 100644 index 000000000..7453c9b1a --- /dev/null +++ b/frontend/techpick/src/components/PickListViewer/PickCardListLayout.tsx @@ -0,0 +1,8 @@ +import { PickViewItemListLayoutComponentProps } from '.'; +import { pickCardGridLayoutStyle } from './pickCardGridLayout.css'; + +export function PickCardListLayout({ + children, +}: PickViewItemListLayoutComponentProps) { + return
{children}
; +} diff --git a/frontend/techpick/src/components/PickListViewer/index.tsx b/frontend/techpick/src/components/PickListViewer/index.tsx new file mode 100644 index 000000000..59aead0c0 --- /dev/null +++ b/frontend/techpick/src/components/PickListViewer/index.tsx @@ -0,0 +1,53 @@ +import type { PropsWithChildren, ReactNode } from 'react'; +import { PickCard } from './PickCard'; +import { PickCardListLayout } from './PickCardListLayout'; +import type { PickInfoType } from '@/types'; + +export function PickListViewer({ + pickList, + viewType = 'card', +}: PickListViewerProps) { + const { PickViewItemComponent, PickViewItemListLayoutComponent } = + PICK_LIST_VIEW_TEMPLATES[viewType]; + + return ( + + {pickList.map((pickInfo) => ( + + ))} + + ); +} + +interface PickListViewerProps { + pickList: PickInfoType[]; + viewType?: ViewTemplateType; +} + +const PICK_LIST_VIEW_TEMPLATES: Record< + ViewTemplateType, + ViewTemplateValueType +> = { + card: { + PickViewItemListLayoutComponent: PickCardListLayout, + PickViewItemComponent: PickCard, + }, +}; + +/** + * @description ViewTemplateType은 pickInfo를 어떤 UI로 보여줄지 나타냅니다. ex) card, list + */ +type ViewTemplateType = 'card'; + +interface ViewTemplateValueType { + PickViewItemListLayoutComponent: ( + props: PickViewItemListLayoutComponentProps + ) => ReactNode; + PickViewItemComponent: (props: PickViewItemComponentProps) => ReactNode; +} + +export type PickViewItemListLayoutComponentProps = PropsWithChildren; + +export interface PickViewItemComponentProps { + pickInfo: PickInfoType; +} diff --git a/frontend/techpick/src/components/PickCard/pickCard.css.ts b/frontend/techpick/src/components/PickListViewer/pickCard.css.ts similarity index 100% rename from frontend/techpick/src/components/PickCard/pickCard.css.ts rename to frontend/techpick/src/components/PickListViewer/pickCard.css.ts diff --git a/frontend/techpick/src/components/PickListViewer/pickCardGridLayout.css.ts b/frontend/techpick/src/components/PickListViewer/pickCardGridLayout.css.ts new file mode 100644 index 000000000..cbab9037e --- /dev/null +++ b/frontend/techpick/src/components/PickListViewer/pickCardGridLayout.css.ts @@ -0,0 +1,14 @@ +import { style } from '@vanilla-extract/css'; +import { space, sizes } from 'techpick-shared'; + +export const pickCardGridLayoutStyle = style({ + display: 'grid', + // 280px는 PickCard의 width + gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', + padding: space[8], + rowGap: space[8], + columnGap: space[8], + width: sizes['full'], + height: sizes['full'], + overflowY: 'scroll', +}); diff --git a/frontend/techpick/src/components/index.ts b/frontend/techpick/src/components/index.ts index ceb5fa687..b75b4d44d 100644 --- a/frontend/techpick/src/components/index.ts +++ b/frontend/techpick/src/components/index.ts @@ -2,8 +2,6 @@ export * from './Text'; export * from './Button'; export * from './Gap'; export { DeferredComponent } from './DeferredComponent'; -export { PickCard } from './PickCard/PickCard'; -export { PickCardGridLayout } from './PickCardGridLayout/PickCardGridLayout'; export { SelectedTagItem } from './SelectedTagItem'; export { SelectedTagListLayout } from './SelectedTagListLayout/SelectedTagListLayout'; export { DeleteTagDialog } from './DeleteTagDialog'; @@ -13,3 +11,4 @@ export { ToggleThemeButton } from './ToggleThemeButton'; export { FeaturedSection } from './FeaturedSection/FeaturedSection'; export { TagPicker } from './TagPicker'; export { FolderTree } from './FolderTree'; +export { PickListViewer } from './PickListViewer'; From caeaac31c304a624dddc5b08ac8cc2f9ef59627e Mon Sep 17 00:00:00 2001 From: dmdgpdi <33450285+dmdgpdi@users.noreply.github.com> Date: Sat, 9 Nov 2024 17:48:30 +0900 Subject: [PATCH 09/18] =?UTF-8?q?design:=20card=20link=20style=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 --- .../techpick-shared/themes/commonTheme.css.ts | 2 ++ .../components/PickListViewer/pickCard.css.ts | 19 ++----------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/frontend/techpick-shared/themes/commonTheme.css.ts b/frontend/techpick-shared/themes/commonTheme.css.ts index bbe082590..82d98f7fa 100644 --- a/frontend/techpick-shared/themes/commonTheme.css.ts +++ b/frontend/techpick-shared/themes/commonTheme.css.ts @@ -112,6 +112,8 @@ export const [commonThemeClass, commonTheme] = createTheme({ max: 'max-content', /** @type {string} Minimum size, set to `min-content`. */ min: 'min-content', + /** @type {string} fit size, set to `fit-content`. */ + fit: 'fit-content', /** @type {string} Full size, set to `100%`. */ full: '100%', /** @type {string} Size for ultra extra extra extra extra small screens (2rem, 32px). */ diff --git a/frontend/techpick/src/components/PickListViewer/pickCard.css.ts b/frontend/techpick/src/components/PickListViewer/pickCard.css.ts index f35e59946..f33284cdc 100644 --- a/frontend/techpick/src/components/PickListViewer/pickCard.css.ts +++ b/frontend/techpick/src/components/PickListViewer/pickCard.css.ts @@ -1,23 +1,8 @@ import { keyframes, style } from '@vanilla-extract/css'; -import { space, color } from 'techpick-shared'; +import { space, color, sizes } from 'techpick-shared'; export const linkStyle = style({ - color: 'inherit', // 부모의 텍스트 색상 따르기 - textDecoration: 'none', // 밑줄 제거 - selectors: { - '&:hover': { - color: 'inherit', - textDecoration: 'none', - }, - '&:active': { - color: 'inherit', - textDecoration: 'none', - }, - '&:visited': { - color: 'inherit', - textDecoration: 'none', - }, - }, + width: sizes['fit'], }); export const pickCardLayout = style({ From 7af73bb630315af9828924f57cea32919a65ea97 Mon Sep 17 00:00:00 2001 From: dmdgpdi <33450285+dmdgpdi@users.noreply.github.com> Date: Sat, 9 Nov 2024 18:06:43 +0900 Subject: [PATCH 10/18] =?UTF-8?q?feat:=20=EB=8D=94=EB=B8=94=20=ED=81=B4?= =?UTF-8?q?=EB=A6=AD=EC=8B=9C=20=EB=A7=81=ED=81=AC=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/PickListViewer/PickCard.tsx | 51 ++++++++++--------- .../components/PickListViewer/pickCard.css.ts | 7 +-- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/frontend/techpick/src/components/PickListViewer/PickCard.tsx b/frontend/techpick/src/components/PickListViewer/PickCard.tsx index babdc3b2d..7e5e98b37 100644 --- a/frontend/techpick/src/components/PickListViewer/PickCard.tsx +++ b/frontend/techpick/src/components/PickListViewer/PickCard.tsx @@ -1,5 +1,7 @@ +'use client'; + +import { useCallback } from 'react'; import Image from 'next/image'; -import Link from 'next/link'; import { PickViewItemComponentProps } from '.'; import { cardDescriptionSectionStyle, @@ -7,7 +9,6 @@ import { cardImageStyle, cardTitleSectionStyle, defaultCardImageSectionStyle, - linkStyle, pickCardLayout, } from './pickCard.css'; @@ -15,30 +16,32 @@ export function PickCard({ pickInfo }: PickViewItemComponentProps) { const { memo, title, linkInfo } = pickInfo; const { imageUrl, url } = linkInfo; + const openUrl = useCallback(() => { + window.open(url, '_blank'); + }, [url]); + return ( - -
-
- {imageUrl ? ( - - ) : ( -
- )} -
+
+
+ {imageUrl ? ( + + ) : ( +
+ )} +
-
-

{title}

-
-
-

{memo}

-
+
+

{title}

+
+
+

{memo}

- +
); } diff --git a/frontend/techpick/src/components/PickListViewer/pickCard.css.ts b/frontend/techpick/src/components/PickListViewer/pickCard.css.ts index f33284cdc..18e2bc7ab 100644 --- a/frontend/techpick/src/components/PickListViewer/pickCard.css.ts +++ b/frontend/techpick/src/components/PickListViewer/pickCard.css.ts @@ -1,9 +1,5 @@ import { keyframes, style } from '@vanilla-extract/css'; -import { space, color, sizes } from 'techpick-shared'; - -export const linkStyle = style({ - width: sizes['fit'], -}); +import { space, color } from 'techpick-shared'; export const pickCardLayout = style({ display: 'flex', @@ -15,6 +11,7 @@ export const pickCardLayout = style({ border: `1px solid ${color.border}`, borderRadius: '4px', backgroundColor: color.background, + cursor: 'pointer', }); export const cardImageSectionStyle = style({ From f0754e6fd02a8a0b2d91719706f821b7cd77fe03 Mon Sep 17 00:00:00 2001 From: dmdgpdi <33450285+dmdgpdi@users.noreply.github.com> Date: Sun, 10 Nov 2024 16:22:41 +0900 Subject: [PATCH 11/18] =?UTF-8?q?feat:=20dnd=EA=B0=80=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=9C=20list=EC=99=80=20=EA=B7=B8=EB=A0=87=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=9D=80=20list=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PickListViewer/PickCardDropZone.tsx | 65 ++++++++++++++++++ .../PickListViewer/PickCardListLayout.tsx | 2 + .../components/PickListViewer/PickDnDCard.tsx | 47 +++++++++++++ .../PickListViewer/PickDnDCardListLayout.tsx | 16 +++++ .../src/components/PickListViewer/index.tsx | 66 ++++++++++++++++--- 5 files changed, 187 insertions(+), 9 deletions(-) create mode 100644 frontend/techpick/src/components/PickListViewer/PickCardDropZone.tsx create mode 100644 frontend/techpick/src/components/PickListViewer/PickDnDCard.tsx create mode 100644 frontend/techpick/src/components/PickListViewer/PickDnDCardListLayout.tsx diff --git a/frontend/techpick/src/components/PickListViewer/PickCardDropZone.tsx b/frontend/techpick/src/components/PickListViewer/PickCardDropZone.tsx new file mode 100644 index 000000000..6dcbd1ebd --- /dev/null +++ b/frontend/techpick/src/components/PickListViewer/PickCardDropZone.tsx @@ -0,0 +1,65 @@ +'use client'; + +import { + closestCenter, + DndContext, + MouseSensor, + TouchSensor, + useSensor, + useSensors, +} from '@dnd-kit/core'; +import { SortableContext } from '@dnd-kit/sortable'; +import { usePickStore } from '@/stores/pickStore/pickStore'; +import { PickViewDnDItemListLayoutComponentProps } from '.'; +import type { DragEndEvent, DragStartEvent } from '@dnd-kit/core'; + +export function PickCardDropZone({ + folderId, + children, +}: PickViewDnDItemListLayoutComponentProps) { + const { getOrderedPickListByFolderId } = usePickStore(); + const orderedPickList = getOrderedPickListByFolderId(folderId); + const orderedPickIdList = orderedPickList.map((pickInfo) => pickInfo.id); + + const mouseSensor = useSensor(MouseSensor, { + activationConstraint: { + distance: 10, // MouseSensor: 10px 이동해야 드래그 시작 + }, + }); + const touchSensor = useSensor(TouchSensor, { + activationConstraint: { + delay: 250, + tolerance: 5, + }, + }); + const sensors = useSensors(mouseSensor, touchSensor); + + const onDragStart = (event: DragStartEvent) => { + const { active } = event; + + console.log('onDragStart! active:', active); + }; + + const onDragEnd = (event: DragEndEvent) => { + const { active, over } = event; + + if (!over) return; // 드래그 중 놓은 위치가 없을 때 종료 + + console.log('onDragEnd!'); + console.log('active', active); + console.log('over', over); + }; + + return ( + + + {children} + + + ); +} diff --git a/frontend/techpick/src/components/PickListViewer/PickCardListLayout.tsx b/frontend/techpick/src/components/PickListViewer/PickCardListLayout.tsx index 7453c9b1a..2c4c0ef22 100644 --- a/frontend/techpick/src/components/PickListViewer/PickCardListLayout.tsx +++ b/frontend/techpick/src/components/PickListViewer/PickCardListLayout.tsx @@ -1,3 +1,5 @@ +'use client'; + import { PickViewItemListLayoutComponentProps } from '.'; import { pickCardGridLayoutStyle } from './pickCardGridLayout.css'; diff --git a/frontend/techpick/src/components/PickListViewer/PickDnDCard.tsx b/frontend/techpick/src/components/PickListViewer/PickDnDCard.tsx new file mode 100644 index 000000000..205cdeb71 --- /dev/null +++ b/frontend/techpick/src/components/PickListViewer/PickDnDCard.tsx @@ -0,0 +1,47 @@ +'use client'; + +import { useCallback } from 'react'; +import Image from 'next/image'; +import { PickViewDnDItemComponentProps } from '.'; +import { + cardDescriptionSectionStyle, + cardImageSectionStyle, + cardImageStyle, + cardTitleSectionStyle, + defaultCardImageSectionStyle, + pickCardLayout, +} from './pickCard.css'; + +export function PickDnDCard({ pickInfo }: PickViewDnDItemComponentProps) { + const { memo, title, linkInfo } = pickInfo; + const { imageUrl, url } = linkInfo; + + const openUrl = useCallback(() => { + window.open(url, '_blank'); + }, [url]); + + return ( +
+
+ {imageUrl ? ( + + ) : ( +
+ )} +
+ +
+

{title}

+
+
+

{memo}

+
+
+ ); +} diff --git a/frontend/techpick/src/components/PickListViewer/PickDnDCardListLayout.tsx b/frontend/techpick/src/components/PickListViewer/PickDnDCardListLayout.tsx new file mode 100644 index 000000000..d89169a43 --- /dev/null +++ b/frontend/techpick/src/components/PickListViewer/PickDnDCardListLayout.tsx @@ -0,0 +1,16 @@ +'use client'; + +import { PickViewDnDItemListLayoutComponentProps } from '.'; +import { PickCardDropZone } from './PickCardDropZone'; +import { pickCardGridLayoutStyle } from './pickCardGridLayout.css'; + +export function PickDnDCardListLayout({ + children, + folderId, +}: PickViewDnDItemListLayoutComponentProps) { + return ( +
+ {children} +
+ ); +} diff --git a/frontend/techpick/src/components/PickListViewer/index.tsx b/frontend/techpick/src/components/PickListViewer/index.tsx index 59aead0c0..a75ec05e6 100644 --- a/frontend/techpick/src/components/PickListViewer/index.tsx +++ b/frontend/techpick/src/components/PickListViewer/index.tsx @@ -1,17 +1,28 @@ import type { PropsWithChildren, ReactNode } from 'react'; +import { UNKNOWN_FOLDER_ID } from '@/constants'; import { PickCard } from './PickCard'; import { PickCardListLayout } from './PickCardListLayout'; +import { PickDnDCard } from './PickDnDCard'; +import { PickDnDCardListLayout } from './PickDnDCardListLayout'; import type { PickInfoType } from '@/types'; export function PickListViewer({ pickList, viewType = 'card', + dnd: isDnD = false, + folderId, }: PickListViewerProps) { + const PICK_LIST_VIEW_TEMPLATES = isDnD + ? DND_PICK_LIST_VIEW_TEMPLATES + : NORMAL_PICK_LIST_VIEW_TEMPLATES; + const { PickViewItemComponent, PickViewItemListLayoutComponent } = PICK_LIST_VIEW_TEMPLATES[viewType]; return ( - + {pickList.map((pickInfo) => ( ))} @@ -19,12 +30,21 @@ export function PickListViewer({ ); } -interface PickListViewerProps { - pickList: PickInfoType[]; - viewType?: ViewTemplateType; -} +type PickListViewerProps = + | { + pickList: PickInfoType[]; + dnd?: false; + folderId?: never; + viewType?: ViewTemplateType; + } + | { + pickList: PickInfoType[]; + dnd: true; + folderId: number; + viewType?: DnDViewTemplateType; + }; -const PICK_LIST_VIEW_TEMPLATES: Record< +const NORMAL_PICK_LIST_VIEW_TEMPLATES: Record< ViewTemplateType, ViewTemplateValueType > = { @@ -34,11 +54,26 @@ const PICK_LIST_VIEW_TEMPLATES: Record< }, }; +const DND_PICK_LIST_VIEW_TEMPLATES: Record< + DnDViewTemplateType, + DnDViewTemplateValueType +> = { + card: { + PickViewItemComponent: PickDnDCard, + PickViewItemListLayoutComponent: PickDnDCardListLayout, + }, +}; + /** * @description ViewTemplateType은 pickInfo를 어떤 UI로 보여줄지 나타냅니다. ex) card, list */ type ViewTemplateType = 'card'; +/** + * @description DnDViewTemplateType은 Drag&Drop이 가능한 UI 중 무엇을 보여줄지 나타냅니다. ex) card, list + */ +type DnDViewTemplateType = 'card'; + interface ViewTemplateValueType { PickViewItemListLayoutComponent: ( props: PickViewItemListLayoutComponentProps @@ -46,8 +81,21 @@ interface ViewTemplateValueType { PickViewItemComponent: (props: PickViewItemComponentProps) => ReactNode; } -export type PickViewItemListLayoutComponentProps = PropsWithChildren; +interface DnDViewTemplateValueType { + PickViewItemListLayoutComponent: ( + props: PickViewDnDItemListLayoutComponentProps + ) => ReactNode; + PickViewItemComponent: (props: PickViewDnDItemComponentProps) => ReactNode; +} + +export type PickViewItemListLayoutComponentProps = + PropsWithChildren; -export interface PickViewItemComponentProps { +export type PickViewItemComponentProps = { pickInfo: PickInfoType; -} +} & ExtraProps; + +export type PickViewDnDItemListLayoutComponentProps = + PickViewItemListLayoutComponentProps<{ folderId: number }>; + +export type PickViewDnDItemComponentProps = PickViewItemComponentProps; From c59e6b1a4c2c4ba7b87617b614079c3ec009c7fb Mon Sep 17 00:00:00 2001 From: dmdgpdi <33450285+dmdgpdi@users.noreply.github.com> Date: Mon, 11 Nov 2024 12:24:24 +0900 Subject: [PATCH 12/18] =?UTF-8?q?refactor:=20dnd=EC=97=AC=EB=B6=80?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=9D=BC=20PickListViewer=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DraggablePickListViewer.tsx | 58 ++++++++++ .../components/PickListViewer/PickCard.tsx | 2 +- .../PickListViewer/PickCardDropZone.tsx | 2 +- .../PickListViewer/PickCardListLayout.tsx | 2 +- .../components/PickListViewer/PickDnDCard.tsx | 2 +- .../PickListViewer/PickDnDCardListLayout.tsx | 2 +- .../PickListViewer/PickListViewer.tsx | 56 ++++++++++ .../src/components/PickListViewer/index.tsx | 103 +----------------- 8 files changed, 121 insertions(+), 106 deletions(-) create mode 100644 frontend/techpick/src/components/PickListViewer/DraggablePickListViewer.tsx create mode 100644 frontend/techpick/src/components/PickListViewer/PickListViewer.tsx diff --git a/frontend/techpick/src/components/PickListViewer/DraggablePickListViewer.tsx b/frontend/techpick/src/components/PickListViewer/DraggablePickListViewer.tsx new file mode 100644 index 000000000..e850c965e --- /dev/null +++ b/frontend/techpick/src/components/PickListViewer/DraggablePickListViewer.tsx @@ -0,0 +1,58 @@ +import type { ReactNode } from 'react'; +import { PickDnDCard } from './PickDnDCard'; +import { PickDnDCardListLayout } from './PickDnDCardListLayout'; +import type { + PickViewItemComponentProps, + PickViewItemListLayoutComponentProps, +} from './PickListViewer'; +import type { PickInfoType } from '@/types'; + +export function DraggablePickListViewer({ + pickList, + viewType = 'card', + folderId, +}: PickListViewerProps) { + const { PickViewItemComponent, PickViewItemListLayoutComponent } = + DND_PICK_LIST_VIEW_TEMPLATES[viewType]; + + return ( + + {pickList.map((pickInfo) => ( + + ))} + + ); +} + +interface PickListViewerProps { + pickList: PickInfoType[]; + folderId: number; + viewType?: DnDViewTemplateType; +} + +const DND_PICK_LIST_VIEW_TEMPLATES: Record< + DnDViewTemplateType, + DnDViewTemplateValueType +> = { + card: { + PickViewItemComponent: PickDnDCard, + PickViewItemListLayoutComponent: PickDnDCardListLayout, + }, +}; + +/** + * @description DnDViewTemplateType은 Drag&Drop이 가능한 UI 중 무엇을 보여줄지 나타냅니다. ex) card, list + */ +type DnDViewTemplateType = 'card'; + +interface DnDViewTemplateValueType { + PickViewItemListLayoutComponent: ( + props: PickViewDnDItemListLayoutComponentProps + ) => ReactNode; + PickViewItemComponent: (props: PickViewDnDItemComponentProps) => ReactNode; +} + +export type PickViewDnDItemListLayoutComponentProps = + PickViewItemListLayoutComponentProps<{ folderId: number }>; + +export type PickViewDnDItemComponentProps = PickViewItemComponentProps; diff --git a/frontend/techpick/src/components/PickListViewer/PickCard.tsx b/frontend/techpick/src/components/PickListViewer/PickCard.tsx index 7e5e98b37..cc1ea0761 100644 --- a/frontend/techpick/src/components/PickListViewer/PickCard.tsx +++ b/frontend/techpick/src/components/PickListViewer/PickCard.tsx @@ -2,7 +2,6 @@ import { useCallback } from 'react'; import Image from 'next/image'; -import { PickViewItemComponentProps } from '.'; import { cardDescriptionSectionStyle, cardImageSectionStyle, @@ -11,6 +10,7 @@ import { defaultCardImageSectionStyle, pickCardLayout, } from './pickCard.css'; +import { PickViewItemComponentProps } from './PickListViewer'; export function PickCard({ pickInfo }: PickViewItemComponentProps) { const { memo, title, linkInfo } = pickInfo; diff --git a/frontend/techpick/src/components/PickListViewer/PickCardDropZone.tsx b/frontend/techpick/src/components/PickListViewer/PickCardDropZone.tsx index 6dcbd1ebd..00d6b51af 100644 --- a/frontend/techpick/src/components/PickListViewer/PickCardDropZone.tsx +++ b/frontend/techpick/src/components/PickListViewer/PickCardDropZone.tsx @@ -10,7 +10,7 @@ import { } from '@dnd-kit/core'; import { SortableContext } from '@dnd-kit/sortable'; import { usePickStore } from '@/stores/pickStore/pickStore'; -import { PickViewDnDItemListLayoutComponentProps } from '.'; +import { PickViewDnDItemListLayoutComponentProps } from './DraggablePickListViewer'; import type { DragEndEvent, DragStartEvent } from '@dnd-kit/core'; export function PickCardDropZone({ diff --git a/frontend/techpick/src/components/PickListViewer/PickCardListLayout.tsx b/frontend/techpick/src/components/PickListViewer/PickCardListLayout.tsx index 2c4c0ef22..627249e22 100644 --- a/frontend/techpick/src/components/PickListViewer/PickCardListLayout.tsx +++ b/frontend/techpick/src/components/PickListViewer/PickCardListLayout.tsx @@ -1,7 +1,7 @@ 'use client'; -import { PickViewItemListLayoutComponentProps } from '.'; import { pickCardGridLayoutStyle } from './pickCardGridLayout.css'; +import { PickViewItemListLayoutComponentProps } from './PickListViewer'; export function PickCardListLayout({ children, diff --git a/frontend/techpick/src/components/PickListViewer/PickDnDCard.tsx b/frontend/techpick/src/components/PickListViewer/PickDnDCard.tsx index 205cdeb71..d74af39a9 100644 --- a/frontend/techpick/src/components/PickListViewer/PickDnDCard.tsx +++ b/frontend/techpick/src/components/PickListViewer/PickDnDCard.tsx @@ -2,7 +2,6 @@ import { useCallback } from 'react'; import Image from 'next/image'; -import { PickViewDnDItemComponentProps } from '.'; import { cardDescriptionSectionStyle, cardImageSectionStyle, @@ -11,6 +10,7 @@ import { defaultCardImageSectionStyle, pickCardLayout, } from './pickCard.css'; +import { PickViewDnDItemComponentProps } from './PickListViewer'; export function PickDnDCard({ pickInfo }: PickViewDnDItemComponentProps) { const { memo, title, linkInfo } = pickInfo; diff --git a/frontend/techpick/src/components/PickListViewer/PickDnDCardListLayout.tsx b/frontend/techpick/src/components/PickListViewer/PickDnDCardListLayout.tsx index d89169a43..33dd3895d 100644 --- a/frontend/techpick/src/components/PickListViewer/PickDnDCardListLayout.tsx +++ b/frontend/techpick/src/components/PickListViewer/PickDnDCardListLayout.tsx @@ -1,6 +1,6 @@ 'use client'; -import { PickViewDnDItemListLayoutComponentProps } from '.'; +import { PickViewDnDItemListLayoutComponentProps } from './DraggablePickListViewer'; import { PickCardDropZone } from './PickCardDropZone'; import { pickCardGridLayoutStyle } from './pickCardGridLayout.css'; diff --git a/frontend/techpick/src/components/PickListViewer/PickListViewer.tsx b/frontend/techpick/src/components/PickListViewer/PickListViewer.tsx new file mode 100644 index 000000000..ac9e2eb71 --- /dev/null +++ b/frontend/techpick/src/components/PickListViewer/PickListViewer.tsx @@ -0,0 +1,56 @@ +import type { PropsWithChildren, ReactNode } from 'react'; +import { PickCard } from './PickCard'; +import { PickCardListLayout } from './PickCardListLayout'; +import type { PickInfoType } from '@/types'; + +export function PickListViewer({ + pickList, + viewType = 'card', +}: PickListViewerProps) { + const { PickViewItemComponent, PickViewItemListLayoutComponent } = + NORMAL_PICK_LIST_VIEW_TEMPLATES[viewType]; + + return ( + + {pickList.map((pickInfo) => ( + + ))} + + ); +} + +interface PickListViewerProps { + pickList: PickInfoType[]; + viewType?: ViewTemplateType; +} + +const NORMAL_PICK_LIST_VIEW_TEMPLATES: Record< + ViewTemplateType, + ViewTemplateValueType +> = { + card: { + PickViewItemListLayoutComponent: PickCardListLayout, + PickViewItemComponent: PickCard, + }, +}; + +/** + * @description ViewTemplateType은 pickInfo를 어떤 UI로 보여줄지 나타냅니다. ex) card, list + */ +type ViewTemplateType = 'card'; + +interface ViewTemplateValueType { + PickViewItemListLayoutComponent: ( + props: PickViewItemListLayoutComponentProps + ) => ReactNode; + PickViewItemComponent: (props: PickViewItemComponentProps) => ReactNode; +} + +export type PickViewItemListLayoutComponentProps = + PropsWithChildren; + +export type PickViewItemComponentProps = { + pickInfo: PickInfoType; +} & ExtraProps; + +export type PickViewDnDItemComponentProps = PickViewItemComponentProps; diff --git a/frontend/techpick/src/components/PickListViewer/index.tsx b/frontend/techpick/src/components/PickListViewer/index.tsx index a75ec05e6..d2269feb7 100644 --- a/frontend/techpick/src/components/PickListViewer/index.tsx +++ b/frontend/techpick/src/components/PickListViewer/index.tsx @@ -1,101 +1,2 @@ -import type { PropsWithChildren, ReactNode } from 'react'; -import { UNKNOWN_FOLDER_ID } from '@/constants'; -import { PickCard } from './PickCard'; -import { PickCardListLayout } from './PickCardListLayout'; -import { PickDnDCard } from './PickDnDCard'; -import { PickDnDCardListLayout } from './PickDnDCardListLayout'; -import type { PickInfoType } from '@/types'; - -export function PickListViewer({ - pickList, - viewType = 'card', - dnd: isDnD = false, - folderId, -}: PickListViewerProps) { - const PICK_LIST_VIEW_TEMPLATES = isDnD - ? DND_PICK_LIST_VIEW_TEMPLATES - : NORMAL_PICK_LIST_VIEW_TEMPLATES; - - const { PickViewItemComponent, PickViewItemListLayoutComponent } = - PICK_LIST_VIEW_TEMPLATES[viewType]; - - return ( - - {pickList.map((pickInfo) => ( - - ))} - - ); -} - -type PickListViewerProps = - | { - pickList: PickInfoType[]; - dnd?: false; - folderId?: never; - viewType?: ViewTemplateType; - } - | { - pickList: PickInfoType[]; - dnd: true; - folderId: number; - viewType?: DnDViewTemplateType; - }; - -const NORMAL_PICK_LIST_VIEW_TEMPLATES: Record< - ViewTemplateType, - ViewTemplateValueType -> = { - card: { - PickViewItemListLayoutComponent: PickCardListLayout, - PickViewItemComponent: PickCard, - }, -}; - -const DND_PICK_LIST_VIEW_TEMPLATES: Record< - DnDViewTemplateType, - DnDViewTemplateValueType -> = { - card: { - PickViewItemComponent: PickDnDCard, - PickViewItemListLayoutComponent: PickDnDCardListLayout, - }, -}; - -/** - * @description ViewTemplateType은 pickInfo를 어떤 UI로 보여줄지 나타냅니다. ex) card, list - */ -type ViewTemplateType = 'card'; - -/** - * @description DnDViewTemplateType은 Drag&Drop이 가능한 UI 중 무엇을 보여줄지 나타냅니다. ex) card, list - */ -type DnDViewTemplateType = 'card'; - -interface ViewTemplateValueType { - PickViewItemListLayoutComponent: ( - props: PickViewItemListLayoutComponentProps - ) => ReactNode; - PickViewItemComponent: (props: PickViewItemComponentProps) => ReactNode; -} - -interface DnDViewTemplateValueType { - PickViewItemListLayoutComponent: ( - props: PickViewDnDItemListLayoutComponentProps - ) => ReactNode; - PickViewItemComponent: (props: PickViewDnDItemComponentProps) => ReactNode; -} - -export type PickViewItemListLayoutComponentProps = - PropsWithChildren; - -export type PickViewItemComponentProps = { - pickInfo: PickInfoType; -} & ExtraProps; - -export type PickViewDnDItemListLayoutComponentProps = - PickViewItemListLayoutComponentProps<{ folderId: number }>; - -export type PickViewDnDItemComponentProps = PickViewItemComponentProps; +export { PickListViewer } from './PickListViewer'; +export { DraggablePickListViewer } from './DraggablePickListViewer'; From a6d6dd9594eff7d700a7295ed2e45764dad08eea Mon Sep 17 00:00:00 2001 From: dmdgpdi <33450285+dmdgpdi@users.noreply.github.com> Date: Mon, 11 Nov 2024 14:31:19 +0900 Subject: [PATCH 13/18] =?UTF-8?q?refactor:=20DnDCurrentType=EC=9D=84=20dnd?= =?UTF-8?q?.type=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/techpick/src/types/dnd.type.ts | 10 ++++++++++ frontend/techpick/src/types/folder.type.ts | 10 ---------- frontend/techpick/src/types/index.ts | 1 + 3 files changed, 11 insertions(+), 10 deletions(-) create mode 100644 frontend/techpick/src/types/dnd.type.ts diff --git a/frontend/techpick/src/types/dnd.type.ts b/frontend/techpick/src/types/dnd.type.ts new file mode 100644 index 000000000..edc2c1cba --- /dev/null +++ b/frontend/techpick/src/types/dnd.type.ts @@ -0,0 +1,10 @@ +import type { UniqueIdentifier } from '@dnd-kit/core'; + +export type DnDCurrentType = { + id: UniqueIdentifier; + sortable: { + containerId: string | null; + items: UniqueIdentifier[]; + index: number; + }; +}; diff --git a/frontend/techpick/src/types/folder.type.ts b/frontend/techpick/src/types/folder.type.ts index f6cbff05b..d52cf5b50 100644 --- a/frontend/techpick/src/types/folder.type.ts +++ b/frontend/techpick/src/types/folder.type.ts @@ -1,20 +1,10 @@ import type { Concrete } from './util.type'; -import type { UniqueIdentifier } from '@dnd-kit/core'; import type { components } from '@/schema'; export type SelectedFolderListType = number[]; export type ChildFolderListType = number[]; -export type DnDCurrentType = { - id: UniqueIdentifier; - sortable: { - containerId: string | null; - items: UniqueIdentifier[]; - index: number; - }; -}; - export type GetFolderListResponseType = Concrete< components['schemas']['techpick.api.application.folder.dto.FolderApiResponse'] >[]; diff --git a/frontend/techpick/src/types/index.ts b/frontend/techpick/src/types/index.ts index d1036e63b..6569da7d6 100644 --- a/frontend/techpick/src/types/index.ts +++ b/frontend/techpick/src/types/index.ts @@ -2,3 +2,4 @@ export type { NodeData, DirectoryNodeProps } from './NodeData'; export type * from './tag.type'; export type * from './folder.type'; export type * from './pick.type'; +export type * from './dnd.type'; From 63845529f32fd50cfc4adac96f06a82dce24c5a5 Mon Sep 17 00:00:00 2001 From: dmdgpdi <33450285+dmdgpdi@users.noreply.github.com> Date: Mon, 11 Nov 2024 14:32:25 +0900 Subject: [PATCH 14/18] =?UTF-8?q?refactor:=20=EA=B3=B5=ED=86=B5=20utils?= =?UTF-8?q?=ED=95=A8=EC=88=98=EB=A1=9C=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/FolderTree/FolderDropZone.tsx | 2 +- .../components/FolderTree/FolderListItem.tsx | 2 +- .../FolderTree/folderListItem.util.ts | 2 - .../src/stores/dndTreeStore/dndTreeStore.ts | 7 ++- .../utils/reorderFoldersInSameParent.ts | 44 ----------------- .../utils => utils/dnd}/isDnDCurrentData.ts | 0 .../src/utils/dnd/isSelectionActive.ts | 1 + .../src/utils/dnd/reorderSortableList.ts | 47 +++++++++++++++++++ frontend/techpick/src/utils/index.ts | 3 ++ 9 files changed, 56 insertions(+), 52 deletions(-) delete mode 100644 frontend/techpick/src/stores/dndTreeStore/utils/reorderFoldersInSameParent.ts rename frontend/techpick/src/{stores/dndTreeStore/utils => utils/dnd}/isDnDCurrentData.ts (100%) create mode 100644 frontend/techpick/src/utils/dnd/isSelectionActive.ts create mode 100644 frontend/techpick/src/utils/dnd/reorderSortableList.ts diff --git a/frontend/techpick/src/components/FolderTree/FolderDropZone.tsx b/frontend/techpick/src/components/FolderTree/FolderDropZone.tsx index 8a2b2dae6..dacbac6f4 100644 --- a/frontend/techpick/src/components/FolderTree/FolderDropZone.tsx +++ b/frontend/techpick/src/components/FolderTree/FolderDropZone.tsx @@ -8,7 +8,7 @@ import { useSensors, } from '@dnd-kit/core'; import { useTreeStore } from '@/stores/dndTreeStore/dndTreeStore'; -import { isDnDCurrentData } from '@/stores/dndTreeStore/utils/isDnDCurrentData'; +import { isDnDCurrentData } from '@/utils'; import type { DragEndEvent, DragOverEvent, diff --git a/frontend/techpick/src/components/FolderTree/FolderListItem.tsx b/frontend/techpick/src/components/FolderTree/FolderListItem.tsx index df33bd343..60a37b5c2 100644 --- a/frontend/techpick/src/components/FolderTree/FolderListItem.tsx +++ b/frontend/techpick/src/components/FolderTree/FolderListItem.tsx @@ -2,13 +2,13 @@ import { useState } from 'react'; import type { MouseEvent } from 'react'; import { ROUTES } from '@/constants'; import { useTreeStore } from '@/stores/dndTreeStore/dndTreeStore'; +import { isSelectionActive } from '@/utils'; import { FolderContextMenu } from './FolderContextMenu'; import { FolderInput } from './FolderInput'; import { FolderLinkItem } from './FolderLinkItem'; import { getSelectedFolderRange, isSameParentFolder, - isSelectionActive, } from './folderListItem.util'; import type { FolderMapType } from '@/types'; diff --git a/frontend/techpick/src/components/FolderTree/folderListItem.util.ts b/frontend/techpick/src/components/FolderTree/folderListItem.util.ts index 8c134903f..78451b179 100644 --- a/frontend/techpick/src/components/FolderTree/folderListItem.util.ts +++ b/frontend/techpick/src/components/FolderTree/folderListItem.util.ts @@ -1,8 +1,6 @@ import { hasIndex } from '@/utils'; import type { FolderMapType } from '@/types'; -export const isSelectionActive = (length: number) => 0 < length; - export const isSameParentFolder = ( id: number, selectedId: number, diff --git a/frontend/techpick/src/stores/dndTreeStore/dndTreeStore.ts b/frontend/techpick/src/stores/dndTreeStore/dndTreeStore.ts index 7c12f7f58..dc92bf1ee 100644 --- a/frontend/techpick/src/stores/dndTreeStore/dndTreeStore.ts +++ b/frontend/techpick/src/stores/dndTreeStore/dndTreeStore.ts @@ -10,10 +10,9 @@ import { } from '@/apis/folder'; import { getEntries } from '@/components/PickListViewerPanel/types/common.type'; import { UNKNOWN_FOLDER_ID } from '@/constants'; +import { isDnDCurrentData, reorderSortableIdList } from '@/utils'; import { changeParentFolderId } from './utils/changeParentFolderId'; -import { isDnDCurrentData } from './utils/isDnDCurrentData'; import { moveFolderToDifferentParent } from './utils/moveFolderToDifferentParent'; -import { reorderFolderInSameParent } from './utils/reorderFoldersInSameParent'; import type { Active, Over, UniqueIdentifier } from '@dnd-kit/core'; import type { FolderType, @@ -205,8 +204,8 @@ export const useTreeStore = create()( prevChildFolderList = [...childFolderList]; state.treeDataMap[parentId].childFolderIdOrderedList = - reorderFolderInSameParent({ - childFolderList, + reorderSortableIdList({ + sortableIdList: childFolderList, fromId, toId, selectedFolderList, diff --git a/frontend/techpick/src/stores/dndTreeStore/utils/reorderFoldersInSameParent.ts b/frontend/techpick/src/stores/dndTreeStore/utils/reorderFoldersInSameParent.ts deleted file mode 100644 index 4e7870c6c..000000000 --- a/frontend/techpick/src/stores/dndTreeStore/utils/reorderFoldersInSameParent.ts +++ /dev/null @@ -1,44 +0,0 @@ -// utils/reorderFolder.ts -import { hasIndex } from '@/utils'; -import type { UniqueIdentifier } from '@dnd-kit/core'; -import type { ChildFolderListType, SelectedFolderListType } from '@/types'; - -export function reorderFolderInSameParent({ - childFolderList, - fromId, - toId, - selectedFolderList, -}: ReorderFolderPayload): ChildFolderListType { - const curIndex = childFolderList.findIndex((item) => item === fromId); - const targetIndex = childFolderList.findIndex((item) => item === toId); - - const nextIndex = - curIndex < targetIndex - ? Math.min(targetIndex + 1, childFolderList.length) - : targetIndex; - - if (!hasIndex(curIndex) || !hasIndex(nextIndex)) return childFolderList; - - const beforeNextIndexList = childFolderList - .slice(0, nextIndex) - .filter((index) => !selectedFolderList.includes(index)); - const afterNextIndexList = childFolderList - .slice(nextIndex) - .filter((index) => !selectedFolderList.includes(index)); - - // 새로운 리스트를 생성하여 반환합니다. - childFolderList = [ - ...beforeNextIndexList, - ...selectedFolderList, - ...afterNextIndexList, - ]; - - return childFolderList; -} - -interface ReorderFolderPayload { - childFolderList: ChildFolderListType; - fromId: UniqueIdentifier; - toId: UniqueIdentifier; - selectedFolderList: SelectedFolderListType; -} diff --git a/frontend/techpick/src/stores/dndTreeStore/utils/isDnDCurrentData.ts b/frontend/techpick/src/utils/dnd/isDnDCurrentData.ts similarity index 100% rename from frontend/techpick/src/stores/dndTreeStore/utils/isDnDCurrentData.ts rename to frontend/techpick/src/utils/dnd/isDnDCurrentData.ts diff --git a/frontend/techpick/src/utils/dnd/isSelectionActive.ts b/frontend/techpick/src/utils/dnd/isSelectionActive.ts new file mode 100644 index 000000000..9d76aeca2 --- /dev/null +++ b/frontend/techpick/src/utils/dnd/isSelectionActive.ts @@ -0,0 +1 @@ +export const isSelectionActive = (length: number) => 0 < length; diff --git a/frontend/techpick/src/utils/dnd/reorderSortableList.ts b/frontend/techpick/src/utils/dnd/reorderSortableList.ts new file mode 100644 index 000000000..42dffdd1d --- /dev/null +++ b/frontend/techpick/src/utils/dnd/reorderSortableList.ts @@ -0,0 +1,47 @@ +import { hasIndex } from '@/utils'; +import type { UniqueIdentifier } from '@dnd-kit/core'; +import type { + ChildFolderListType, + SelectedFolderListType, + SelectedPickIdListType, +} from '@/types'; + +export const reorderSortableIdList = ({ + sortableIdList, + fromId, + toId, + selectedFolderList, +}: ReorderSortableIdListPayload) => { + const curIndex = sortableIdList.findIndex((item) => item === fromId); + const targetIndex = sortableIdList.findIndex((item) => item === toId); + + const nextIndex = + curIndex < targetIndex + ? Math.min(targetIndex + 1, sortableIdList.length) + : targetIndex; + + if (!hasIndex(curIndex) || !hasIndex(nextIndex)) return sortableIdList; + + const beforeNextIndexList = sortableIdList + .slice(0, nextIndex) + .filter((index) => !selectedFolderList.includes(index)); + const afterNextIndexList = sortableIdList + .slice(nextIndex) + .filter((index) => !selectedFolderList.includes(index)); + + // 새로운 리스트를 생성하여 반환합니다. + const newSortableIdList = [ + ...beforeNextIndexList, + ...selectedFolderList, + ...afterNextIndexList, + ]; + + return newSortableIdList; +}; + +interface ReorderSortableIdListPayload { + sortableIdList: ChildFolderListType | number[]; + fromId: UniqueIdentifier; + toId: UniqueIdentifier; + selectedFolderList: SelectedFolderListType | SelectedPickIdListType; +} diff --git a/frontend/techpick/src/utils/index.ts b/frontend/techpick/src/utils/index.ts index 0e2303e26..e42d6bb1e 100644 --- a/frontend/techpick/src/utils/index.ts +++ b/frontend/techpick/src/utils/index.ts @@ -4,3 +4,6 @@ export { getClientCookie } from './getClientCookie'; export { hasIndex } from './array'; export { isEmptyString } from './string'; export { getPortalContainer } from './portal'; +export { isDnDCurrentData } from './dnd/isDnDCurrentData'; +export { reorderSortableIdList } from './dnd/reorderSortableList'; +export { isSelectionActive } from './dnd/isSelectionActive'; From dad4cc49647b8f1d5c986113ff71e92b8c11ab9c Mon Sep 17 00:00:00 2001 From: dmdgpdi <33450285+dmdgpdi@users.noreply.github.com> Date: Mon, 11 Nov 2024 14:33:00 +0900 Subject: [PATCH 15/18] =?UTF-8?q?feat:=20=ED=94=BD=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=ED=94=BD=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=8F=99(api=20?= =?UTF-8?q?=EB=AF=B8=EC=97=B0=EA=B2=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(signed)/folders/unclassified/page.tsx | 5 +- .../PickListViewer/PickCardDropZone.tsx | 24 +++-- .../components/PickListViewer/PickDnDCard.tsx | 87 ++++++++++++++----- frontend/techpick/src/components/index.ts | 2 +- .../src/stores/pickStore/pickStore.ts | 64 ++++++++++++++ frontend/techpick/src/types/pick.type.ts | 2 + 6 files changed, 156 insertions(+), 28 deletions(-) diff --git a/frontend/techpick/src/app/(signed)/folders/unclassified/page.tsx b/frontend/techpick/src/app/(signed)/folders/unclassified/page.tsx index 48aa0eea9..af4420b68 100644 --- a/frontend/techpick/src/app/(signed)/folders/unclassified/page.tsx +++ b/frontend/techpick/src/app/(signed)/folders/unclassified/page.tsx @@ -1,7 +1,7 @@ 'use client'; import { useEffect } from 'react'; -import { PickListViewer } from '@/components'; +import { DraggablePickListViewer } from '@/components'; import { useTreeStore } from '@/stores/dndTreeStore/dndTreeStore'; import { usePickStore } from '@/stores/pickStore/pickStore'; @@ -38,8 +38,9 @@ export default function UnclassifiedFolderPage() { } return ( - ); } diff --git a/frontend/techpick/src/components/PickListViewer/PickCardDropZone.tsx b/frontend/techpick/src/components/PickListViewer/PickCardDropZone.tsx index 00d6b51af..133b91901 100644 --- a/frontend/techpick/src/components/PickListViewer/PickCardDropZone.tsx +++ b/frontend/techpick/src/components/PickListViewer/PickCardDropZone.tsx @@ -17,7 +17,12 @@ export function PickCardDropZone({ folderId, children, }: PickViewDnDItemListLayoutComponentProps) { - const { getOrderedPickListByFolderId } = usePickStore(); + const { + getOrderedPickListByFolderId, + movePick, + selectedPickIdList, + selectSinglePick, + } = usePickStore(); const orderedPickList = getOrderedPickListByFolderId(folderId); const orderedPickIdList = orderedPickList.map((pickInfo) => pickInfo.id); @@ -36,8 +41,15 @@ export function PickCardDropZone({ const onDragStart = (event: DragStartEvent) => { const { active } = event; + const pickId = Number(active.id); - console.log('onDragStart! active:', active); + if (!selectedPickIdList.includes(pickId)) { + selectSinglePick(pickId); + + return; + } + + // console.log('onDragStart! active:', active); }; const onDragEnd = (event: DragEndEvent) => { @@ -45,9 +57,11 @@ export function PickCardDropZone({ if (!over) return; // 드래그 중 놓은 위치가 없을 때 종료 - console.log('onDragEnd!'); - console.log('active', active); - console.log('over', over); + movePick({ folderId, from: active, to: over }); + + // console.log('onDragEnd!'); + // console.log('active', active); + // console.log('over', over); }; return ( diff --git a/frontend/techpick/src/components/PickListViewer/PickDnDCard.tsx b/frontend/techpick/src/components/PickListViewer/PickDnDCard.tsx index d74af39a9..939e3897c 100644 --- a/frontend/techpick/src/components/PickListViewer/PickDnDCard.tsx +++ b/frontend/techpick/src/components/PickListViewer/PickDnDCard.tsx @@ -1,7 +1,12 @@ 'use client'; import { useCallback } from 'react'; +import type { CSSProperties, MouseEvent } from 'react'; import Image from 'next/image'; +import { useSortable } from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { usePickStore } from '@/stores/pickStore/pickStore'; +import { isSelectionActive } from '@/utils'; import { cardDescriptionSectionStyle, cardImageSectionStyle, @@ -13,34 +18,76 @@ import { import { PickViewDnDItemComponentProps } from './PickListViewer'; export function PickDnDCard({ pickInfo }: PickViewDnDItemComponentProps) { - const { memo, title, linkInfo } = pickInfo; + const { memo, title, linkInfo, id: pickId } = pickInfo; const { imageUrl, url } = linkInfo; + const { selectedPickIdList, selectSinglePick } = usePickStore(); + + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + // isDragging: isActiveDragging, 나중에 multi-select 때 동작하자 + } = useSortable({ + id: pickId, + data: { + id: `pick ${pickId}`, + }, + }); + + const style: CSSProperties = { + transform: CSS.Transform.toString(transform), + transition, + opacity: 1, + }; + const openUrl = useCallback(() => { window.open(url, '_blank'); }, [url]); + const handleClick = ( + pickId: number, + event: MouseEvent + ) => { + event.preventDefault(); + + if (event.shiftKey && isSelectionActive(selectedPickIdList.length)) { + // 니중에 다중선택 + return; + } + + selectSinglePick(pickId); + }; + return ( -
-
- {imageUrl ? ( - - ) : ( -
- )} -
+
+
handleClick(pickId, event)} + > +
+ {imageUrl ? ( + + ) : ( +
+ )} +
-
-

{title}

-
-
-

{memo}

+
+

{title}

+
+
+

{memo}

+
); diff --git a/frontend/techpick/src/components/index.ts b/frontend/techpick/src/components/index.ts index b75b4d44d..dea5d7850 100644 --- a/frontend/techpick/src/components/index.ts +++ b/frontend/techpick/src/components/index.ts @@ -11,4 +11,4 @@ export { ToggleThemeButton } from './ToggleThemeButton'; export { FeaturedSection } from './FeaturedSection/FeaturedSection'; export { TagPicker } from './TagPicker'; export { FolderTree } from './FolderTree'; -export { PickListViewer } from './PickListViewer'; +export { PickListViewer, DraggablePickListViewer } from './PickListViewer'; diff --git a/frontend/techpick/src/stores/pickStore/pickStore.ts b/frontend/techpick/src/stores/pickStore/pickStore.ts index 97850cfc0..9a3d4536c 100644 --- a/frontend/techpick/src/stores/pickStore/pickStore.ts +++ b/frontend/techpick/src/stores/pickStore/pickStore.ts @@ -2,14 +2,20 @@ import { create } from 'zustand'; import { subscribeWithSelector } from 'zustand/middleware'; import { immer } from 'zustand/middleware/immer'; import { getPicksByFolderId } from '@/apis/pick'; +import { isDnDCurrentData, reorderSortableIdList } from '@/utils'; +import type { Active, Over } from '@dnd-kit/core'; import type { PickRecordType, PickInfoType, PickRecordValueType, + SelectedPickIdListType, } from '@/types'; type PickState = { pickRecord: PickRecordType; + focusPickId: number | null; + selectedPickIdList: SelectedPickIdListType; + isDragging: boolean; }; type PickAction = { @@ -18,10 +24,18 @@ type PickAction = { hasPickRecordValue: ( pickRecordValue: PickRecordValueType | undefined ) => pickRecordValue is PickRecordValueType; + movePick: (movePickPayload: MovePickPayload) => void; + setSelectedPickIdList: ( + newSelectedPickIdList: SelectedPickIdListType + ) => void; + selectSinglePick: (pickId: number) => void; }; const initialState: PickState = { pickRecord: {}, + focusPickId: null, + selectedPickIdList: [], + isDragging: false, }; export const usePickStore = create()( @@ -72,6 +86,56 @@ export const usePickStore = create()( return true; }, + movePick: ({ from, to }) => { + const fromData = from.data.current; + const toData = to.data.current; + + if (!isDnDCurrentData(fromData) || !isDnDCurrentData(toData)) return; + // SortableContext에 id가 없으면 종료 + if (!fromData.sortable.containerId || !toData.sortable.containerId) + return; + + const folderId = fromData.sortable.containerId; + const pickRecordValue = get().pickRecord[folderId]; + + if (!get().hasPickRecordValue(pickRecordValue)) { + return; + } + + const prevPickIdOrderedList = pickRecordValue.pickIdOrderedList; + const fromId = from.id; + const toId = to.id; + + set((state) => { + if (!state.pickRecord[folderId]) { + return; + } + + state.pickRecord[folderId].pickIdOrderedList = reorderSortableIdList({ + sortableIdList: prevPickIdOrderedList, + fromId, + toId, + selectedFolderList: state.selectedPickIdList, + }); + }); + }, + setSelectedPickIdList: (newSelectedPickIdList) => { + set((state) => { + state.selectedPickIdList = newSelectedPickIdList; + }); + }, + selectSinglePick: (pickId) => { + set((state) => { + state.focusPickId = pickId; + state.selectedPickIdList = [pickId]; + }); + }, })) ) ); + +type MovePickPayload = { + folderId: number; + from: Active; + to: Over; +}; diff --git a/frontend/techpick/src/types/pick.type.ts b/frontend/techpick/src/types/pick.type.ts index a2702844a..d0c47f0ec 100644 --- a/frontend/techpick/src/types/pick.type.ts +++ b/frontend/techpick/src/types/pick.type.ts @@ -28,3 +28,5 @@ export type GetPicksByFolderIdResponseType = { folderId: number; pickList: PickListType; }[]; + +export type SelectedPickIdListType = number[]; From 76076270baed06a228c9bff129a0c786ec2c22cf Mon Sep 17 00:00:00 2001 From: dmdgpdi <33450285+dmdgpdi@users.noreply.github.com> Date: Mon, 11 Nov 2024 15:38:01 +0900 Subject: [PATCH 16/18] =?UTF-8?q?feat:=20pick=20multi-select=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/PickListViewer/PickDnDCard.tsx | 38 +++++++++++++++---- .../PickListViewer/pickDnDCard.css.ts | 7 ++++ .../PickListViewer/pickDnDCard.util.ts | 32 ++++++++++++++++ .../src/stores/pickStore/pickStore.ts | 11 ++++++ frontend/techpick/src/types/pick.type.ts | 1 + 5 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 frontend/techpick/src/components/PickListViewer/pickDnDCard.css.ts create mode 100644 frontend/techpick/src/components/PickListViewer/pickDnDCard.util.ts diff --git a/frontend/techpick/src/components/PickListViewer/PickDnDCard.tsx b/frontend/techpick/src/components/PickListViewer/PickDnDCard.tsx index 939e3897c..1b3abb55a 100644 --- a/frontend/techpick/src/components/PickListViewer/PickDnDCard.tsx +++ b/frontend/techpick/src/components/PickListViewer/PickDnDCard.tsx @@ -15,13 +15,21 @@ import { defaultCardImageSectionStyle, pickCardLayout, } from './pickCard.css'; +import { selectedDragItemStyle } from './pickDnDCard.css'; +import { getSelectedPickRange } from './pickDnDCard.util'; import { PickViewDnDItemComponentProps } from './PickListViewer'; export function PickDnDCard({ pickInfo }: PickViewDnDItemComponentProps) { - const { memo, title, linkInfo, id: pickId } = pickInfo; + const { + selectedPickIdList, + selectSinglePick, + getOrderedPickIdListByFolderId, + focusPickId, + setSelectedPickIdList, + } = usePickStore(); + const { memo, title, linkInfo, id: pickId, parentFolderId } = pickInfo; const { imageUrl, url } = linkInfo; - - const { selectedPickIdList, selectSinglePick } = usePickStore(); + const isSelected = selectedPickIdList.includes(pickId); const { attributes, @@ -47,14 +55,30 @@ export function PickDnDCard({ pickInfo }: PickViewDnDItemComponentProps) { window.open(url, '_blank'); }, [url]); + const handleShiftSelect = (parentFolderId: number, pickId: number) => { + if (!focusPickId) { + return; + } + + // 새로운 선택된 배열 만들기. + const orderedPickIdList = getOrderedPickIdListByFolderId(parentFolderId); + + const newSelectedPickIdList = getSelectedPickRange({ + orderedPickIdList, + startPickId: focusPickId, + endPickId: pickId, + }); + + setSelectedPickIdList(newSelectedPickIdList); + }; + const handleClick = ( pickId: number, event: MouseEvent ) => { - event.preventDefault(); - if (event.shiftKey && isSelectionActive(selectedPickIdList.length)) { - // 니중에 다중선택 + event.preventDefault(); + handleShiftSelect(parentFolderId, pickId); return; } @@ -64,7 +88,7 @@ export function PickDnDCard({ pickInfo }: PickViewDnDItemComponentProps) { return (
handleClick(pickId, event)} > diff --git a/frontend/techpick/src/components/PickListViewer/pickDnDCard.css.ts b/frontend/techpick/src/components/PickListViewer/pickDnDCard.css.ts new file mode 100644 index 000000000..5926e47b8 --- /dev/null +++ b/frontend/techpick/src/components/PickListViewer/pickDnDCard.css.ts @@ -0,0 +1,7 @@ +import { style } from '@vanilla-extract/css'; +import { colorThemeContract } from 'techpick-shared'; + +export const selectedDragItemStyle = style({ + backgroundColor: colorThemeContract.primary, + userSelect: 'none', +}); diff --git a/frontend/techpick/src/components/PickListViewer/pickDnDCard.util.ts b/frontend/techpick/src/components/PickListViewer/pickDnDCard.util.ts new file mode 100644 index 000000000..d253870aa --- /dev/null +++ b/frontend/techpick/src/components/PickListViewer/pickDnDCard.util.ts @@ -0,0 +1,32 @@ +import { hasIndex } from '@/utils'; +import type { OrderedPickIdListType } from '@/types'; + +export const getSelectedPickRange = ({ + orderedPickIdList, + startPickId, + endPickId, +}: GetSelectedPickRangePayload) => { + const firstSelectedIndex = orderedPickIdList.findIndex( + (orderedPickId) => orderedPickId === startPickId + ); + const lastSelectedIndex = orderedPickIdList.findIndex( + (orderedPickId) => orderedPickId === endPickId + ); + + if (!hasIndex(firstSelectedIndex) || !hasIndex(lastSelectedIndex)) return []; + + const startIndex = Math.min(firstSelectedIndex, lastSelectedIndex); + const endIndex = Math.max(firstSelectedIndex, lastSelectedIndex); + const newSelectedPickIdList = orderedPickIdList.slice( + startIndex, + endIndex + 1 + ); + + return newSelectedPickIdList; +}; + +interface GetSelectedPickRangePayload { + orderedPickIdList: OrderedPickIdListType; + startPickId: number; + endPickId: number; +} diff --git a/frontend/techpick/src/stores/pickStore/pickStore.ts b/frontend/techpick/src/stores/pickStore/pickStore.ts index 9a3d4536c..9cac59d3b 100644 --- a/frontend/techpick/src/stores/pickStore/pickStore.ts +++ b/frontend/techpick/src/stores/pickStore/pickStore.ts @@ -20,6 +20,7 @@ type PickState = { type PickAction = { fetchPickDataByFolderId: (folderId: number) => Promise; + getOrderedPickIdListByFolderId: (folderId: number) => number[]; getOrderedPickListByFolderId: (folderId: number) => PickInfoType[]; hasPickRecordValue: ( pickRecordValue: PickRecordValueType | undefined @@ -57,6 +58,16 @@ export const usePickStore = create()( console.log('fetchPickDataByFolderId error', error); } }, + getOrderedPickIdListByFolderId: (folderId) => { + const pickRecordValue = get().pickRecord[`${folderId}`]; + + if (!get().hasPickRecordValue(pickRecordValue)) { + return []; + } + const { pickIdOrderedList } = pickRecordValue; + + return pickIdOrderedList; + }, getOrderedPickListByFolderId: (folderId: number) => { const pickRecordValue = get().pickRecord[`${folderId}`]; diff --git a/frontend/techpick/src/types/pick.type.ts b/frontend/techpick/src/types/pick.type.ts index d0c47f0ec..f2bafc4e3 100644 --- a/frontend/techpick/src/types/pick.type.ts +++ b/frontend/techpick/src/types/pick.type.ts @@ -29,4 +29,5 @@ export type GetPicksByFolderIdResponseType = { pickList: PickListType; }[]; +export type OrderedPickIdListType = number[]; export type SelectedPickIdListType = number[]; From 4ef42d1f936ceb8383a332ec9021cf0257b0fa80 Mon Sep 17 00:00:00 2001 From: dmdgpdi <33450285+dmdgpdi@users.noreply.github.com> Date: Mon, 11 Nov 2024 17:17:21 +0900 Subject: [PATCH 17/18] =?UTF-8?q?feat:=20multi-select=20drag&drop=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84(api=20=EB=AF=B8=EC=97=B0=EA=B2=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PickListViewer/PickCardDropZone.tsx | 44 ++++++++++++++----- .../components/PickListViewer/PickDnDCard.tsx | 15 +++++-- .../PickListViewer/pickDnDCard.css.ts | 4 ++ .../src/stores/pickStore/pickStore.ts | 31 +++++++++++++ 4 files changed, 79 insertions(+), 15 deletions(-) diff --git a/frontend/techpick/src/components/PickListViewer/PickCardDropZone.tsx b/frontend/techpick/src/components/PickListViewer/PickCardDropZone.tsx index 133b91901..c8dcf11d6 100644 --- a/frontend/techpick/src/components/PickListViewer/PickCardDropZone.tsx +++ b/frontend/techpick/src/components/PickListViewer/PickCardDropZone.tsx @@ -3,14 +3,16 @@ import { closestCenter, DndContext, + DragOverlay, MouseSensor, TouchSensor, useSensor, useSensors, } from '@dnd-kit/core'; -import { SortableContext } from '@dnd-kit/sortable'; +import { SortableContext, rectSortingStrategy } from '@dnd-kit/sortable'; import { usePickStore } from '@/stores/pickStore/pickStore'; import { PickViewDnDItemListLayoutComponentProps } from './DraggablePickListViewer'; +import { PickCard } from './PickCard'; import type { DragEndEvent, DragStartEvent } from '@dnd-kit/core'; export function PickCardDropZone({ @@ -18,13 +20,27 @@ export function PickCardDropZone({ children, }: PickViewDnDItemListLayoutComponentProps) { const { - getOrderedPickListByFolderId, + getOrderedPickIdListByFolderId, + getPickInfoByFolderIdAndPickId, movePick, selectedPickIdList, selectSinglePick, + setIsDragging, + setFocusedPickId, + isDragging, + focusPickId, } = usePickStore(); - const orderedPickList = getOrderedPickListByFolderId(folderId); - const orderedPickIdList = orderedPickList.map((pickInfo) => pickInfo.id); + const orderedPickIdList = getOrderedPickIdListByFolderId(folderId); + const orderedPickIdListWithoutSelectedIdList = isDragging + ? orderedPickIdList.filter( + (orderedPickId) => + !selectedPickIdList.includes(orderedPickId) || + orderedPickId === focusPickId + ) + : orderedPickIdList; + const draggingPickInfo = focusPickId + ? getPickInfoByFolderIdAndPickId(folderId, focusPickId) + : null; const mouseSensor = useSensor(MouseSensor, { activationConstraint: { @@ -40,16 +56,16 @@ export function PickCardDropZone({ const sensors = useSensors(mouseSensor, touchSensor); const onDragStart = (event: DragStartEvent) => { + setIsDragging(true); const { active } = event; const pickId = Number(active.id); if (!selectedPickIdList.includes(pickId)) { selectSinglePick(pickId); - return; } - // console.log('onDragStart! active:', active); + setFocusedPickId(pickId); }; const onDragEnd = (event: DragEndEvent) => { @@ -58,10 +74,7 @@ export function PickCardDropZone({ if (!over) return; // 드래그 중 놓은 위치가 없을 때 종료 movePick({ folderId, from: active, to: over }); - - // console.log('onDragEnd!'); - // console.log('active', active); - // console.log('over', over); + setIsDragging(false); }; return ( @@ -71,9 +84,18 @@ export function PickCardDropZone({ onDragStart={onDragStart} onDragEnd={onDragEnd} > - + {children} + {isDragging && draggingPickInfo && ( + + + + )} ); } diff --git a/frontend/techpick/src/components/PickListViewer/PickDnDCard.tsx b/frontend/techpick/src/components/PickListViewer/PickDnDCard.tsx index 1b3abb55a..f685eeb47 100644 --- a/frontend/techpick/src/components/PickListViewer/PickDnDCard.tsx +++ b/frontend/techpick/src/components/PickListViewer/PickDnDCard.tsx @@ -15,7 +15,10 @@ import { defaultCardImageSectionStyle, pickCardLayout, } from './pickCard.css'; -import { selectedDragItemStyle } from './pickDnDCard.css'; +import { + selectedDragItemStyle, + isActiveDraggingItemStyle, +} from './pickDnDCard.css'; import { getSelectedPickRange } from './pickDnDCard.util'; import { PickViewDnDItemComponentProps } from './PickListViewer'; @@ -26,18 +29,18 @@ export function PickDnDCard({ pickInfo }: PickViewDnDItemComponentProps) { getOrderedPickIdListByFolderId, focusPickId, setSelectedPickIdList, + isDragging, } = usePickStore(); const { memo, title, linkInfo, id: pickId, parentFolderId } = pickInfo; const { imageUrl, url } = linkInfo; const isSelected = selectedPickIdList.includes(pickId); - const { attributes, listeners, setNodeRef, transform, transition, - // isDragging: isActiveDragging, 나중에 multi-select 때 동작하자 + isDragging: isActiveDragging, } = useSortable({ id: pickId, data: { @@ -85,10 +88,14 @@ export function PickDnDCard({ pickInfo }: PickViewDnDItemComponentProps) { selectSinglePick(pickId); }; + if (isDragging && isSelected && !isActiveDragging) { + return null; + } + return (
handleClick(pickId, event)} > diff --git a/frontend/techpick/src/components/PickListViewer/pickDnDCard.css.ts b/frontend/techpick/src/components/PickListViewer/pickDnDCard.css.ts index 5926e47b8..8c135f5b7 100644 --- a/frontend/techpick/src/components/PickListViewer/pickDnDCard.css.ts +++ b/frontend/techpick/src/components/PickListViewer/pickDnDCard.css.ts @@ -5,3 +5,7 @@ export const selectedDragItemStyle = style({ backgroundColor: colorThemeContract.primary, userSelect: 'none', }); + +export const isActiveDraggingItemStyle = style({ + opacity: 0, +}); diff --git a/frontend/techpick/src/stores/pickStore/pickStore.ts b/frontend/techpick/src/stores/pickStore/pickStore.ts index 9cac59d3b..08a0552d1 100644 --- a/frontend/techpick/src/stores/pickStore/pickStore.ts +++ b/frontend/techpick/src/stores/pickStore/pickStore.ts @@ -22,6 +22,10 @@ type PickAction = { fetchPickDataByFolderId: (folderId: number) => Promise; getOrderedPickIdListByFolderId: (folderId: number) => number[]; getOrderedPickListByFolderId: (folderId: number) => PickInfoType[]; + getPickInfoByFolderIdAndPickId: ( + folderId: number, + pickId: number + ) => PickInfoType | null | undefined; hasPickRecordValue: ( pickRecordValue: PickRecordValueType | undefined ) => pickRecordValue is PickRecordValueType; @@ -30,6 +34,8 @@ type PickAction = { newSelectedPickIdList: SelectedPickIdListType ) => void; selectSinglePick: (pickId: number) => void; + setIsDragging: (isDragging: boolean) => void; + setFocusedPickId: (focusedPickId: number) => void; }; const initialState: PickState = { @@ -88,6 +94,21 @@ export const usePickStore = create()( return pickOrderedList; }, + getPickInfoByFolderIdAndPickId: (folderId, pickId) => { + const pickRecordValue = get().pickRecord[`${folderId}`]; + + if (!get().hasPickRecordValue(pickRecordValue)) { + return null; + } + + const { pickIdOrderedList, pickInfoRecord } = pickRecordValue; + + if (!pickIdOrderedList.includes(pickId)) { + return null; + } + + return pickInfoRecord[`${pickId}`]; + }, hasPickRecordValue: ( pickRecordValue ): pickRecordValue is PickRecordValueType => { @@ -141,6 +162,16 @@ export const usePickStore = create()( state.selectedPickIdList = [pickId]; }); }, + setIsDragging: (isDragging) => { + set((state) => { + state.isDragging = isDragging; + }); + }, + setFocusedPickId: (focusedPickId) => { + set((state) => { + state.focusPickId = focusedPickId; + }); + }, })) ) ); From 402c74fa7bda2577270255055fb2f986f10d6ab5 Mon Sep 17 00:00:00 2001 From: dmdgpdi <33450285+dmdgpdi@users.noreply.github.com> Date: Mon, 11 Nov 2024 18:18:35 +0900 Subject: [PATCH 18/18] =?UTF-8?q?feat:=20=EA=B0=99=EC=9D=80=20=ED=8F=B4?= =?UTF-8?q?=EB=8D=94=20=EB=82=B4=20=ED=94=BD=20drag&drop=20api=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/techpick/src/apis/apiConstants.ts | 1 + frontend/techpick/src/apis/pick/index.ts | 1 + frontend/techpick/src/apis/pick/movePicks.ts | 16 ++++++++++++ .../PickListViewer/PickCardDropZone.tsx | 4 +-- .../src/stores/pickStore/pickStore.ts | 25 ++++++++++++++++--- frontend/techpick/src/types/pick.type.ts | 3 +++ 6 files changed, 45 insertions(+), 5 deletions(-) create mode 100644 frontend/techpick/src/apis/pick/movePicks.ts diff --git a/frontend/techpick/src/apis/apiConstants.ts b/frontend/techpick/src/apis/apiConstants.ts index c526d0021..445e223e4 100644 --- a/frontend/techpick/src/apis/apiConstants.ts +++ b/frontend/techpick/src/apis/apiConstants.ts @@ -14,4 +14,5 @@ export const API_URLS = { GET_BASIC_FOLDERS: `${API_ENDPOINTS.FOLDERS}/${API_ENDPOINTS.BASIC}`, GET_PICKS_BY_FOLDER_ID: (folderId: number) => `${API_ENDPOINTS.PICKS}?folderIdList=${folderId}`, + MOVE_PICKS: `${API_ENDPOINTS.PICKS}/${API_ENDPOINTS.LOCATION}`, }; diff --git a/frontend/techpick/src/apis/pick/index.ts b/frontend/techpick/src/apis/pick/index.ts index 9de1167a3..b0f3faae8 100644 --- a/frontend/techpick/src/apis/pick/index.ts +++ b/frontend/techpick/src/apis/pick/index.ts @@ -1,3 +1,4 @@ export { useGetPickQuery } from './getPick/useGetPickQuery'; export { useUpdatePickMutation } from './updatePick/useUpdatePickMutation'; export { getPicksByFolderId } from './getPicksByFolderId'; +export { movePicks } from './movePicks'; diff --git a/frontend/techpick/src/apis/pick/movePicks.ts b/frontend/techpick/src/apis/pick/movePicks.ts new file mode 100644 index 000000000..dae6b4e7f --- /dev/null +++ b/frontend/techpick/src/apis/pick/movePicks.ts @@ -0,0 +1,16 @@ +import { HTTPError } from 'ky'; +import { apiClient, returnErrorFromHTTPError } from '@/apis'; +import { API_URLS } from '../apiConstants'; +import type { MovePicksRequestType } from '@/types'; + +export const movePicks = async (movePicksInfo: MovePicksRequestType) => { + try { + await apiClient.patch(API_URLS.MOVE_PICKS, { json: movePicksInfo }); + } catch (httpError) { + if (httpError instanceof HTTPError) { + const error = returnErrorFromHTTPError(httpError); + throw error; + } + throw httpError; + } +}; diff --git a/frontend/techpick/src/components/PickListViewer/PickCardDropZone.tsx b/frontend/techpick/src/components/PickListViewer/PickCardDropZone.tsx index c8dcf11d6..8a793cc9c 100644 --- a/frontend/techpick/src/components/PickListViewer/PickCardDropZone.tsx +++ b/frontend/techpick/src/components/PickListViewer/PickCardDropZone.tsx @@ -22,7 +22,7 @@ export function PickCardDropZone({ const { getOrderedPickIdListByFolderId, getPickInfoByFolderIdAndPickId, - movePick, + movePicks, selectedPickIdList, selectSinglePick, setIsDragging, @@ -73,7 +73,7 @@ export function PickCardDropZone({ if (!over) return; // 드래그 중 놓은 위치가 없을 때 종료 - movePick({ folderId, from: active, to: over }); + movePicks({ folderId, from: active, to: over }); setIsDragging(false); }; diff --git a/frontend/techpick/src/stores/pickStore/pickStore.ts b/frontend/techpick/src/stores/pickStore/pickStore.ts index 08a0552d1..6483f3213 100644 --- a/frontend/techpick/src/stores/pickStore/pickStore.ts +++ b/frontend/techpick/src/stores/pickStore/pickStore.ts @@ -1,7 +1,7 @@ import { create } from 'zustand'; import { subscribeWithSelector } from 'zustand/middleware'; import { immer } from 'zustand/middleware/immer'; -import { getPicksByFolderId } from '@/apis/pick'; +import { getPicksByFolderId, movePicks } from '@/apis/pick'; import { isDnDCurrentData, reorderSortableIdList } from '@/utils'; import type { Active, Over } from '@dnd-kit/core'; import type { @@ -29,7 +29,7 @@ type PickAction = { hasPickRecordValue: ( pickRecordValue: PickRecordValueType | undefined ) => pickRecordValue is PickRecordValueType; - movePick: (movePickPayload: MovePickPayload) => void; + movePicks: (movePickPayload: MovePickPayload) => Promise; setSelectedPickIdList: ( newSelectedPickIdList: SelectedPickIdListType ) => void; @@ -118,7 +118,7 @@ export const usePickStore = create()( return true; }, - movePick: ({ from, to }) => { + movePicks: async ({ from, to }) => { const fromData = from.data.current; const toData = to.data.current; @@ -150,6 +150,25 @@ export const usePickStore = create()( selectedFolderList: state.selectedPickIdList, }); }); + + try { + await movePicks({ + idList: get().selectedPickIdList, + orderIdx: toData.sortable.index, + destinationFolderId: Number(folderId), + }); + } catch { + set((state) => { + const curPickRecordValue = state.pickRecord[`${folderId}`]; + + if (!get().hasPickRecordValue(curPickRecordValue)) { + return; + } + + curPickRecordValue.pickIdOrderedList = prevPickIdOrderedList; + state.pickRecord[`${folderId}`] = curPickRecordValue; + }); + } }, setSelectedPickIdList: (newSelectedPickIdList) => { set((state) => { diff --git a/frontend/techpick/src/types/pick.type.ts b/frontend/techpick/src/types/pick.type.ts index f2bafc4e3..e4245316d 100644 --- a/frontend/techpick/src/types/pick.type.ts +++ b/frontend/techpick/src/types/pick.type.ts @@ -31,3 +31,6 @@ export type GetPicksByFolderIdResponseType = { export type OrderedPickIdListType = number[]; export type SelectedPickIdListType = number[]; + +export type MovePicksRequestType = + components['schemas']['techpick.api.application.pick.dto.PickApiRequest$Move'];