From 8d1f49005db89b92486e6cc338c99f04848a6135 Mon Sep 17 00:00:00 2001 From: jungmin Date: Mon, 13 Jul 2026 14:27:50 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=ED=8F=B4=EB=8D=94=20=EC=88=9C?= =?UTF-8?q?=EC=84=9C=20=EB=B3=80=EA=B2=BD=20API=20=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../profile/components/ManagementSection.tsx | 26 ++++++++++++++----- .../components/SettingFolderButton.tsx | 14 ++++++++-- src/hooks/mutations/useUpdateFolderRank.ts | 16 ++++++++++++ src/services/folder.ts | 8 ++++++ src/types/folder.ts | 12 +++++++++ 5 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 src/hooks/mutations/useUpdateFolderRank.ts diff --git a/src/features/profile/components/ManagementSection.tsx b/src/features/profile/components/ManagementSection.tsx index 6a3e76ca..52f917c4 100644 --- a/src/features/profile/components/ManagementSection.tsx +++ b/src/features/profile/components/ManagementSection.tsx @@ -34,6 +34,8 @@ import { useUpdateDeadlineAlarmSetting, useUpdateTaskAlarmSetting, } from '@/hooks/mutations/useUpdateAlarmTimeSetting'; +import { useUpdateFolderRank } from '@/hooks/mutations/useUpdateFolderRank'; +import { useAlertStore } from '@/stores/alert-store'; import type { Folder } from '@/types/folder'; const UNASSIGNED_FOLDER_NAME = '지정안함'; @@ -75,6 +77,8 @@ export const ManagementSection = () => { const { data, isLoading } = useMyInfo(); const updateDeadlineAlarmSetting = useUpdateDeadlineAlarmSetting(); const updateTaskAlarmSetting = useUpdateTaskAlarmSetting(); + const updateFolderRank = useUpdateFolderRank(); + const { showAlert } = useAlertStore(); const folders = useMemo(() => data?.folders ?? [], [data?.folders]); // 서버에서 받아온 폴더 목록을 로컬 순서 상태에 동기화 @@ -116,12 +120,6 @@ export const ManagementSection = () => { setIsReorder(false); }; - // 저장: 현재 순서 유지 후 모드 종료 (순서 저장 API 연동 시 이곳에서 호출) - const handleSaveReorder = () => { - console.log(folders) - setIsReorder(false); - }; - const reorderableFolders = orderedFolders.filter( (folder) => folder.name !== UNASSIGNED_FOLDER_NAME, ); @@ -129,6 +127,21 @@ export const ManagementSection = () => { (folder) => folder.name === UNASSIGNED_FOLDER_NAME, ); + // 저장: 지정안함 폴더를 항상 1순위로 고정하고, 그 아래 순서만 서버에 반영 + const handleSaveReorder = () => { + const payload = [...pinnedFolders, ...reorderableFolders].map( + (folder, index) => ({ folderId: folder.id, rank: index + 1 }), + ); + + updateFolderRank.mutate(payload, { + onSuccess: () => setIsReorder(false), + onError: () => { + setOrderedFolders(snapshotFolders); + showAlert('폴더 순서 변경에 실패했습니다.', 'x'); + }, + }); + }; + // 드래그 완료 시 화면에서만 폴더 순서 재정렬 (지정안함 폴더는 항상 고정) const handleDragEnd = (event: DragEndEvent) => { const { active, over } = event; @@ -194,6 +207,7 @@ export const ManagementSection = () => { onStartReorder={handleStartReorder} onCancelReorder={handleCancelReorder} onSaveReorder={handleSaveReorder} + isSaving={updateFolderRank.isPending} /> {isReorder ? ( diff --git a/src/features/profile/components/SettingFolderButton.tsx b/src/features/profile/components/SettingFolderButton.tsx index 6114b605..aa487f39 100644 --- a/src/features/profile/components/SettingFolderButton.tsx +++ b/src/features/profile/components/SettingFolderButton.tsx @@ -12,6 +12,7 @@ interface SettingFolderButtonProps { onStartReorder: () => void; onCancelReorder: () => void; onSaveReorder: () => void; + isSaving?: boolean; } export const SettingFolderButton = ({ @@ -19,6 +20,7 @@ export const SettingFolderButton = ({ onStartReorder, onCancelReorder, onSaveReorder, + isSaving = false, }: SettingFolderButtonProps) => { const openModal = useModalStore((state) => state.openModal); @@ -33,11 +35,19 @@ export const SettingFolderButton = ({ if (isReorder) { return (
- - diff --git a/src/hooks/mutations/useUpdateFolderRank.ts b/src/hooks/mutations/useUpdateFolderRank.ts new file mode 100644 index 00000000..45b6b879 --- /dev/null +++ b/src/hooks/mutations/useUpdateFolderRank.ts @@ -0,0 +1,16 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { updateFolderPriority } from '@/services/folder'; +import type { FolderRankRequest } from '@/types/folder'; + +// 폴더 순서 변경 커스텀 훅 +export const useUpdateFolderRank = () => { + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: (orderedFolders: FolderRankRequest['orderedFolders']) => + updateFolderPriority({ orderedFolders }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['myInfo'] }); + }, + }); +}; diff --git a/src/services/folder.ts b/src/services/folder.ts index 290feee4..5e2d9ce0 100644 --- a/src/services/folder.ts +++ b/src/services/folder.ts @@ -5,6 +5,7 @@ import type { UpdateFolderRequest, DeleteFolderResponse, } from '@/types/api/folder'; +import type { FolderRankRequest } from '@/types/folder'; import axiosInstance from '@/lib/axiosInstance'; import { folderColorToHex } from '@/lib/folder-color'; @@ -40,3 +41,10 @@ export const deleteFolder = async ( params: { moveTasks }, }); }; + +// 폴더 순서 변경 API 호출 +export const updateFolderPriority = async ( + body: FolderRankRequest, +): Promise => { + await axiosInstance.patch('/user/folder/priority', body); +}; diff --git a/src/types/folder.ts b/src/types/folder.ts index 3bc33f42..b4bb0c45 100644 --- a/src/types/folder.ts +++ b/src/types/folder.ts @@ -16,3 +16,15 @@ export interface Folder { name: string; color: FolderColor; } + +export interface FolderRankRequest { + orderedFolders: { + folderId: number; + rank: number + }[]; +} + +export interface FolderRankResponse { + success: boolean; + message: string; +} From 1145e307c4914c41d584f48352f8fd925a35208e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=84=B1=ED=83=9C=EA=B2=BD?= Date: Tue, 14 Jul 2026 15:35:30 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20=ED=99=88=20=ED=95=84=ED=84=B0?= =?UTF-8?q?=EC=B9=A9=20=EC=A0=95=EB=A0=AC=EC=97=90=20folderRank=20?= =?UTF-8?q?=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/home/components/FilterChipGroup.tsx | 11 +++++++++-- src/hooks/queries/useHomeTaskList.ts | 2 ++ src/types/task.ts | 1 + 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/features/home/components/FilterChipGroup.tsx b/src/features/home/components/FilterChipGroup.tsx index 8a931a50..0b2d7dbc 100644 --- a/src/features/home/components/FilterChipGroup.tsx +++ b/src/features/home/components/FilterChipGroup.tsx @@ -9,6 +9,7 @@ interface Assignment { folderId: number; folderName: string; folderColor: FolderColor; + folderRank: number | null; } interface FilterChipGroupProps { @@ -22,7 +23,7 @@ export const FilterChipGroup = ({ selectedIds, onSelectionChange, }: FilterChipGroupProps) => { - // 중복 제거 + folderId 기준 고정 순서 정렬 + // 중복 제거 + 폴더 순서 기준 정렬 const folders = useMemo(() => { const seen = new Set(); return assignments @@ -31,7 +32,13 @@ export const FilterChipGroup = ({ seen.add(a.folderId); return true; }) - .sort((a, b) => a.folderId - b.folderId); + .sort((a, b) => { + const rankA = a.folderRank ?? Number.MAX_SAFE_INTEGER; + const rankB = b.folderRank ?? Number.MAX_SAFE_INTEGER; + + if (rankA !== rankB) return rankA - rankB; + return a.folderId - b.folderId; + }); }, [assignments]); // 폴더 선택/해제 토글 diff --git a/src/hooks/queries/useHomeTaskList.ts b/src/hooks/queries/useHomeTaskList.ts index 53c71d33..2759bdd7 100644 --- a/src/hooks/queries/useHomeTaskList.ts +++ b/src/hooks/queries/useHomeTaskList.ts @@ -10,6 +10,7 @@ export interface HomeAssignment { folderId: number; folderName: string; folderColor: FolderColor; + folderRank: number | null; dDay: string; dueDate: string; deadlineTime?: string; // 시간이 있는 경우 "HH:mm:ss" @@ -45,6 +46,7 @@ export const useHomeTaskList = (sort?: TaskSort) => { folderId: task.folderId ?? 0, folderName: task.folderTitle ?? '', folderColor: resolveFolderColor(task.foldercolor ?? ''), + folderRank: task.folderRank ?? null, dDay: task.dDay, dueDate: task.deadline ? task.deadline.split('T')[0] : '', // 시간 제거 (ISO 8601) deadlineTime: task.deadline?.includes('T') diff --git a/src/types/task.ts b/src/types/task.ts index f2c6defb..ec91737e 100644 --- a/src/types/task.ts +++ b/src/types/task.ts @@ -25,6 +25,7 @@ export interface Task { folderId?: number; folderTitle?: string; foldercolor?: string; + folderRank?: number | null; title: string; type: TaskType; // PERSONAL | TEAM status?: TaskStatus; // 과제 상세 조회 시 반환 (PROGRESS | COMPLETED) From dc06e5feebff21680985a45920b5f790535a60fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=84=B1=ED=83=9C=EA=B2=BD?= Date: Tue, 14 Jul 2026 15:42:09 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20=ED=8F=B4=EB=8D=94=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=20=EB=B9=8C=EB=93=9C=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/services/folder.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/services/folder.ts b/src/services/folder.ts index d20438f6..8a3a2fcc 100644 --- a/src/services/folder.ts +++ b/src/services/folder.ts @@ -15,10 +15,10 @@ import { folderColorToHex } from '@/lib/folder-color'; export const createFolder = async ( body: CreateFolderRequest, ): Promise => { - const res = await axiosInstance.post( - '/user/folder', - { ...body, color: folderColorToHex(body.color) }, - ); + const res = await axiosInstance.post('/user/folder', { + ...body, + color: folderColorToHex(body.color), + }); return res.data.data; }; @@ -49,8 +49,9 @@ export const updateFolderPriority = async ( body: FolderRankRequest, ): Promise => { await axiosInstance.patch('/user/folder/priority', body); +}; -// 폴더별 과제 목록 조회 API 호출 +// 폴더별 과제 목록 조회 API 호출 export const getFolderTasks = async ( folderId: number[], ): Promise => {