From 65e05adb77ae02789dbd2051ab35b021e124ebe6 Mon Sep 17 00:00:00 2001 From: ramong26 Date: Mon, 20 Apr 2026 14:54:18 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=84=9C=EB=B2=84=20=EC=A7=91=EA=B3=84?= =?UTF-8?q?=20=ED=95=A8=EC=88=98=EC=97=90=20goalIds=20=ED=95=84=ED=84=B0?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/api/dashboard/detail/route.ts | 14 +++++++++++-- .../components/DashboardDetail/index.tsx | 21 ++++++++++++++----- .../components/TaskCardWrapper/index.tsx | 10 ++++----- src/shared/lib/api/fetchDashboard.ts | 11 ++++++++-- .../lib/customApi/getDashboardDetailTodos.ts | 21 ++++++++++++++----- src/shared/lib/query/keyFactory.ts | 1 + src/shared/lib/query/queryFunction.ts | 7 +++++++ 7 files changed, 66 insertions(+), 19 deletions(-) diff --git a/src/app/api/dashboard/detail/route.ts b/src/app/api/dashboard/detail/route.ts index 9534e37..5fbfb07 100644 --- a/src/app/api/dashboard/detail/route.ts +++ b/src/app/api/dashboard/detail/route.ts @@ -2,9 +2,19 @@ import { NextResponse } from 'next/server'; import { getDashboardDetailTodos } from '@/shared/lib/customApi/getDashboardDetailTodos'; -export async function GET() { +const parseGoalIds = (rawGoalIds: string | null): number[] => { + if (!rawGoalIds) return []; + return rawGoalIds + .split(',') + .map((value) => Number(value.trim())) + .filter((id) => Number.isInteger(id) && id > 0); +}; + +export async function GET(request: Request) { try { - const result = await getDashboardDetailTodos(); + const { searchParams } = new URL(request.url); + const goalIds = parseGoalIds(searchParams.get('goalIds')); + const result = await getDashboardDetailTodos(goalIds); return NextResponse.json(result, { status: result.hasAnySuccess ? 200 : 502, diff --git a/src/features/dashboard/components/DashboardDetail/index.tsx b/src/features/dashboard/components/DashboardDetail/index.tsx index 2f0679c..5359ac4 100644 --- a/src/features/dashboard/components/DashboardDetail/index.tsx +++ b/src/features/dashboard/components/DashboardDetail/index.tsx @@ -8,14 +8,14 @@ import Empty from '@/shared/components/Empty'; import PageSubTitle from '@/shared/components/PageSubTitle'; import GoalBox from '../GoalBox'; -import { dashboardQueries } from '@/shared/lib/query/queryFunction'; +import { dashboardQueries, goalQueries } from '@/shared/lib/query/queryFunction'; import { useTodoModeStore } from '@/shared/stores/useTodoModeStore'; import { useLanguage } from '@/shared/contexts/LanguageContext'; import { GITHUB_DISCONNECTED_SESSION_KEY } from '@/shared/constants/github'; export default function DashboardDetail() { const mode = useTodoModeStore((state) => state.mode); - const { data: goalDetail } = useQuery(dashboardQueries.detailTodos()); + const { data: goals, isFetched: isGoalsFetched } = useQuery(goalQueries.list({ limit: 100 })); const { t } = useLanguage(); const [isGithubDisconnectedSession] = useState(() => { @@ -23,12 +23,23 @@ export default function DashboardDetail() { return window.sessionStorage.getItem(GITHUB_DISCONNECTED_SESSION_KEY) === 'true'; }); - const visibleGoals = + const visibleGoalIds = mode === 'GITHUB' && isGithubDisconnectedSession ? [] - : (goalDetail?.items?.filter((item) => item.goal.source === mode) ?? []); + : (goals?.goals?.filter((goal) => goal.source === mode).map((goal) => goal.id) ?? []); - if (mode === 'MANUAL' && visibleGoals.length === 0) { + const { data: goalDetail } = useQuery({ + ...dashboardQueries.detailTodosByGoals(visibleGoalIds), + enabled: visibleGoalIds.length > 0, + }); + + const visibleGoals = goalDetail?.items ?? []; + + if (!isGoalsFetched) { + return null; + } + + if (mode === 'MANUAL' && visibleGoalIds.length === 0) { return {t.dashboard.noFirstGoal}; } diff --git a/src/features/dashboard/components/TaskCardWrapper/index.tsx b/src/features/dashboard/components/TaskCardWrapper/index.tsx index 5d104e6..dfee088 100644 --- a/src/features/dashboard/components/TaskCardWrapper/index.tsx +++ b/src/features/dashboard/components/TaskCardWrapper/index.tsx @@ -60,13 +60,13 @@ export default function TaskCardWrapper({ error instanceof ApiError ? error.message : todoDetail.type === 'ISSUE' - ? 'GitHub Issue close�� �����߽��ϴ�. ��� �� �ٽ� �õ����ּ���.' - : 'GitHub PR merge�� �����߽��ϴ�. ��� �� �ٽ� �õ����ּ���.'; + ? 'GitHub Issue close에 실패했습니다. 다시 시도해주세요.' + : 'GitHub PR merge에 실패했습니다. 다시 시도해주세요.'; showToast(message, 'fail'); } else { - showToast('���� ���� ������Ʈ�� �����߽��ϴ�.', 'fail'); + showToast('할 일 상태 업데이트에 실패했습니다.', 'fail'); } - console.error('���� ���� ������Ʈ ����:', error); + console.error('할 일 상태 업데이트 오류:', error); } }; @@ -83,7 +83,7 @@ export default function TaskCardWrapper({ showToast(nextStarred ? t.mutations.favoriteAdded : t.mutations.favoriteRemoved); }, onError: (error) => { - console.error(error); + console.error('즐겨찾기 상태 업데이트 오류:', error); setStarred(!nextStarred); }, }); diff --git a/src/shared/lib/api/fetchDashboard.ts b/src/shared/lib/api/fetchDashboard.ts index 6cfc630..90436a9 100644 --- a/src/shared/lib/api/fetchDashboard.ts +++ b/src/shared/lib/api/fetchDashboard.ts @@ -36,8 +36,15 @@ class FetchDashboard { return payload.data; }; - getDashboardDetailTodos = async (): Promise<{ items: DashboardDetailTodosResponse[] }> => { - const response = await fetch('/api/dashboard/detail', { + getDashboardDetailTodos = async (goalIds?: number[]): Promise<{ items: DashboardDetailTodosResponse[] }> => { + const params = new URLSearchParams(); + const normalizedGoalIds = (goalIds ?? []).filter((id) => Number.isInteger(id) && id > 0); + if (normalizedGoalIds.length > 0) { + params.set('goalIds', normalizedGoalIds.join(',')); + } + + const requestUrl = params.size > 0 ? `/api/dashboard/detail?${params.toString()}` : '/api/dashboard/detail'; + const response = await fetch(requestUrl, { method: 'GET', headers: { Accept: 'application/json', diff --git a/src/shared/lib/customApi/getDashboardDetailTodos.ts b/src/shared/lib/customApi/getDashboardDetailTodos.ts index 23e8900..1ece065 100644 --- a/src/shared/lib/customApi/getDashboardDetailTodos.ts +++ b/src/shared/lib/customApi/getDashboardDetailTodos.ts @@ -19,14 +19,25 @@ const toProgressPercent = (completedCount: number, todoCount: number): number => return Math.round((completedCount / todoCount) * 100); }; -export const getDashboardDetailTodos = async (): Promise => { +const normalizeGoalIds = (goalIds?: number[]): number[] => { + if (!goalIds) return []; + return [...new Set(goalIds.filter((id) => Number.isInteger(id) && id > 0))]; +}; + +export const getDashboardDetailTodos = async (goalIds?: number[]): Promise => { + const targetGoalIds = normalizeGoalIds(goalIds); + const targetGoalIdSet = new Set(targetGoalIds); + const todoFetchLimit = Math.max(SAFE_LIMIT * Math.max(targetGoalIds.length, 1), SAFE_LIMIT * 5); + const [goalsRes, openTodosRes, doneTodosRes] = await Promise.allSettled([ - fetchGoals.getGoals(), - fetchTodos.getTodos({ sort: 'LATEST', search: '', limit: 300, done: false }), - fetchTodos.getTodos({ sort: 'LATEST', search: '', limit: 300, done: true }), + fetchGoals.getGoals({ limit: Math.max(targetGoalIds.length, 50) }), + fetchTodos.getTodos({ sort: 'LATEST', search: '', limit: todoFetchLimit, done: false }), + fetchTodos.getTodos({ sort: 'LATEST', search: '', limit: todoFetchLimit, done: true }), ]); - const goals = goalsRes.status === 'fulfilled' ? goalsRes.value.goals : []; + const allGoals = goalsRes.status === 'fulfilled' ? goalsRes.value.goals : []; + const goals = + targetGoalIds.length > 0 ? allGoals.filter((goal) => targetGoalIdSet.has(goal.id)) : allGoals; const openTodos = openTodosRes.status === 'fulfilled' ? openTodosRes.value.todos : []; const doneTodos = doneTodosRes.status === 'fulfilled' ? doneTodosRes.value.todos : []; diff --git a/src/shared/lib/query/keyFactory.ts b/src/shared/lib/query/keyFactory.ts index 2c19a9c..b56e160 100644 --- a/src/shared/lib/query/keyFactory.ts +++ b/src/shared/lib/query/keyFactory.ts @@ -59,4 +59,5 @@ export const dashboardKeys = { all: ['dashboard'] as const, summary: () => [...dashboardKeys.all, 'summary'] as const, detailTodos: () => [...dashboardKeys.all, 'detailTodos'] as const, + detailTodosByGoals: (goalIds: number[]) => [...dashboardKeys.detailTodos(), goalIds] as const, }; diff --git a/src/shared/lib/query/queryFunction.ts b/src/shared/lib/query/queryFunction.ts index 20aa8e3..838961d 100644 --- a/src/shared/lib/query/queryFunction.ts +++ b/src/shared/lib/query/queryFunction.ts @@ -177,4 +177,11 @@ export const dashboardQueries = { queryFn: () => fetchDashboard.getDashboardDetailTodos(), staleTime: DASHBOARD_STALE_TIME, }), + + detailTodosByGoals: (goalIds: number[]) => + queryOptions({ + queryKey: dashboardKeys.detailTodosByGoals(goalIds), + queryFn: () => fetchDashboard.getDashboardDetailTodos(goalIds), + staleTime: DASHBOARD_STALE_TIME, + }), };