Skip to content

Commit

Permalink
[FEAT] 같은 폴더 내에서 Pick Drag&Drop 구현 (#420)
Browse files Browse the repository at this point in the history
* chore: 백엔드 api 타입 최신화

* chore: util 스펠링 정확하게 변경

* feat: pickType 추가

* feat: getPicksByFolderId 추가

* refactor: viewTemplate 코드 변경

* refactor: 바로 호출이 아닌, useEffect 내에서 비동기함수 호출

* chore: git pull을 위한 커밋

* refactor: pickCardListViewer로 PickCard 컴포넌트 이동

* design: card link style 변경

* feat: 더블 클릭시 링크 이동

* feat: dnd가 가능한 list와 그렇지 않은 list분리

* refactor: dnd여부에 따라 PickListViewer분리

* refactor: DnDCurrentType을 dnd.type으로 이동

* refactor: 공통 utils함수로 추출

* feat: 픽에서 픽으로 이동(api 미연결)

* feat: pick multi-select 구현

* feat: multi-select drag&drop 구현(api 미연결)

* feat: 같은 폴더 내 픽 drag&drop api 연결
  • Loading branch information
dmdgpdi authored Nov 11, 2024
1 parent 04c97d2 commit 0c078a5
Show file tree
Hide file tree
Showing 46 changed files with 912 additions and 226 deletions.
7 changes: 5 additions & 2 deletions frontend/schema/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -740,7 +743,7 @@ export interface operations {
[name: string]: unknown;
};
content: {
"*/*": components["schemas"]["techpick.api.application.folder.dto.FolderApiResponse"][];
"application/json": unknown;
};
};
/** @description 본인 폴더만 조회할 수 있습니다. */
Expand Down
2 changes: 2 additions & 0 deletions frontend/techpick-shared/themes/commonTheme.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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). */
Expand Down
4 changes: 4 additions & 0 deletions frontend/techpick/src/apis/apiConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const API_ENDPOINTS = {
FOLDERS: 'folders',
LOCATION: 'location',
BASIC: 'basic',
PICKS: 'picks',
};

export const API_URLS = {
Expand All @@ -11,4 +12,7 @@ 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}`,
MOVE_PICKS: `${API_ENDPOINTS.PICKS}/${API_ENDPOINTS.LOCATION}`,
};
46 changes: 46 additions & 0 deletions frontend/techpick/src/apis/pick/getPicksByFolderId.ts
Original file line number Diff line number Diff line change
@@ -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<GetPicksByFolderIdResponseType>(
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 };
};
2 changes: 2 additions & 0 deletions frontend/techpick/src/apis/pick/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export { useGetPickQuery } from './getPick/useGetPickQuery';
export { useUpdatePickMutation } from './updatePick/useUpdatePickMutation';
export { getPicksByFolderId } from './getPicksByFolderId';
export { movePicks } from './movePicks';
16 changes: 16 additions & 0 deletions frontend/techpick/src/apis/pick/movePicks.ts
Original file line number Diff line number Diff line change
@@ -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;
}
};
2 changes: 1 addition & 1 deletion frontend/techpick/src/apis/pick/pickApi.type.ts
Original file line number Diff line number Diff line change
@@ -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<
Expand Down
1 change: 1 addition & 0 deletions frontend/techpick/src/app/(signed)/folders/layout.css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ import { style } from '@vanilla-extract/css';
export const pageContainerLayout = style({
display: 'flex',
flexDirection: 'row',
height: '100vh',
});
27 changes: 25 additions & 2 deletions frontend/techpick/src/app/(signed)/folders/unclassified/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
'use client';

import { useEffect } from 'react';
import { PickListViewerPanel } from '@/components/PickListViewerPanel/PickListViewerPanel';
import { DraggablePickListViewer } from '@/components';
import { useTreeStore } from '@/stores/dndTreeStore/dndTreeStore';
import { usePickStore } from '@/stores/pickStore/pickStore';

export default function UnclassifiedFolderPage() {
const { fetchPickDataByFolderId, getOrderedPickListByFolderId } =
usePickStore();
const selectSingleFolder = useTreeStore((state) => state.selectSingleFolder);
const basicFolderMap = useTreeStore((state) => state.basicFolderMap);

Expand All @@ -19,5 +22,25 @@ export default function UnclassifiedFolderPage() {
[basicFolderMap, selectSingleFolder]
);

return <PickListViewerPanel />;
useEffect(
function loadPickDataFromRemote() {
if (!basicFolderMap) {
return;
}

fetchPickDataByFolderId(basicFolderMap['UNCLASSIFIED'].id);
},
[basicFolderMap, fetchPickDataByFolderId]
);

if (!basicFolderMap) {
return <div>loading...</div>;
}

return (
<DraggablePickListViewer
pickList={getOrderedPickListByFolderId(basicFolderMap['UNCLASSIFIED'].id)}
folderId={basicFolderMap['UNCLASSIFIED'].id}
/>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
9 changes: 6 additions & 3 deletions frontend/techpick/src/components/FolderTree/index.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 (
<HorizontalResizableContainer>
Expand Down
67 changes: 0 additions & 67 deletions frontend/techpick/src/components/PickCard/PickCard.tsx

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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 (
<PickViewItemListLayoutComponent folderId={folderId}>
{pickList.map((pickInfo) => (
<PickViewItemComponent key={pickInfo.id} pickInfo={pickInfo} />
))}
</PickViewItemListLayoutComponent>
);
}

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;
Loading

0 comments on commit 0c078a5

Please sign in to comment.