From 55096c8e435eb23aad31b1fa5f164f4998c35885 Mon Sep 17 00:00:00 2001 From: caadiq Date: Wed, 21 Jan 2026 23:33:32 +0900 Subject: [PATCH] =?UTF-8?q?refactor(frontend):=20API=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=96=B4=EC=97=90=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B3=80?= =?UTF-8?q?=ED=99=98=20=EB=A1=9C=EC=A7=81=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - schedules.js에 transformSchedule 함수 추가 - datetime → date, time 분리 - category 객체 → category_id, category_name, category_color 플랫화 - members 배열 → member_names 문자열 변환 - PC Schedule.jsx의 검색 로직을 searchSchedules 함수 사용으로 변경 - 중복 변환 로직 제거로 코드 간소화 Co-Authored-By: Claude Opus 4.5 --- frontend/src/api/public/schedules.js | 64 +++++++++++++++-------- frontend/src/pages/pc/public/Schedule.jsx | 32 ++---------- 2 files changed, 47 insertions(+), 49 deletions(-) diff --git a/frontend/src/api/public/schedules.js b/frontend/src/api/public/schedules.js index 0eeb301..56fbbe5 100644 --- a/frontend/src/api/public/schedules.js +++ b/frontend/src/api/public/schedules.js @@ -4,40 +4,62 @@ import { fetchApi } from "../index"; import { getTodayKST } from "../../utils/date"; +/** + * API 응답을 프론트엔드 형식으로 변환 + * - datetime → date, time 분리 + * - category 객체 → category_id, category_name, category_color 플랫화 + * - members 배열 → member_names 문자열 + */ +function transformSchedule(schedule) { + const category = schedule.category || {}; + + // datetime에서 date와 time 분리 + let date = ''; + let time = null; + if (schedule.datetime) { + const parts = schedule.datetime.split('T'); + date = parts[0]; + time = parts[1] || null; + } + + // members 배열을 문자열로 (기존 코드 호환성) + const memberNames = Array.isArray(schedule.members) + ? schedule.members.join(',') + : ''; + + return { + ...schedule, + date, + time, + category_id: category.id, + category_name: category.name, + category_color: category.color, + member_names: memberNames, + }; +} + // 일정 목록 조회 (월별) export async function getSchedules(year, month) { const data = await fetchApi(`/api/schedules?year=${year}&month=${month}`); - - // 날짜별 그룹화된 응답을 플랫 배열로 변환 - const schedules = []; - for (const [date, dayData] of Object.entries(data)) { - for (const schedule of dayData.schedules) { - const category = schedule.category || {}; - schedules.push({ - ...schedule, - date, - category_id: category.id, - category_name: category.name, - category_color: category.color, - }); - } - } - return schedules; + return (data.schedules || []).map(transformSchedule); } // 다가오는 일정 조회 (오늘 이후) export async function getUpcomingSchedules(limit = 3) { const todayStr = getTodayKST(); - return fetchApi(`/api/schedules?startDate=${todayStr}&limit=${limit}`); + const data = await fetchApi(`/api/schedules?startDate=${todayStr}&limit=${limit}`); + return (data.schedules || []).map(transformSchedule); } // 일정 검색 (Meilisearch) export async function searchSchedules(query, { offset = 0, limit = 20 } = {}) { - return fetchApi( - `/api/schedules?search=${encodeURIComponent( - query - )}&offset=${offset}&limit=${limit}` + const data = await fetchApi( + `/api/schedules?search=${encodeURIComponent(query)}&offset=${offset}&limit=${limit}` ); + return { + ...data, + schedules: (data.schedules || []).map(transformSchedule), + }; } // 일정 상세 조회 diff --git a/frontend/src/pages/pc/public/Schedule.jsx b/frontend/src/pages/pc/public/Schedule.jsx index ef81dc8..eb97a38 100644 --- a/frontend/src/pages/pc/public/Schedule.jsx +++ b/frontend/src/pages/pc/public/Schedule.jsx @@ -7,7 +7,7 @@ import { useVirtualizer } from '@tanstack/react-virtual'; import { useInView } from 'react-intersection-observer'; import confetti from 'canvas-confetti'; import { getTodayKST } from '../../../utils/date'; -import { getSchedules } from '../../../api/public/schedules'; +import { getSchedules, searchSchedules } from '../../../api/public/schedules'; import useScheduleStore from '../../../stores/useScheduleStore'; // HTML 엔티티 디코딩 함수 @@ -257,11 +257,7 @@ function Schedule() { } = useInfiniteQuery({ queryKey: ['scheduleSearch', searchTerm], queryFn: async ({ pageParam = 0 }) => { - const response = await fetch( - `/api/schedules?search=${encodeURIComponent(searchTerm)}&offset=${pageParam}&limit=${SEARCH_LIMIT}` - ); - if (!response.ok) throw new Error('Search failed'); - return response.json(); + return searchSchedules(searchTerm, { offset: pageParam, limit: SEARCH_LIMIT }); }, getNextPageParam: (lastPage) => { if (lastPage.hasMore) { @@ -272,30 +268,10 @@ function Schedule() { enabled: !!searchTerm && isSearchMode, }); - // Flatten search results and normalize format to match monthly data + // Flatten search results (already transformed by API layer) const searchResults = useMemo(() => { if (!searchData?.pages) return []; - return searchData.pages.flatMap(page => - page.schedules.map(s => { - // datetime → date + time 분리 - const dateTime = s.datetime || ''; - const [date, time] = dateTime.includes('T') - ? dateTime.split('T') - : [dateTime, null]; - - return { - ...s, - date, - time, - // category 객체 → flat 구조 - category_id: s.category?.id, - category_name: s.category?.name, - category_color: s.category?.color, - // members 배열 → 쉼표 구분 문자열 (기존 호환) - member_names: Array.isArray(s.members) ? s.members.map(m => m.name).join(',') : s.member_names, - }; - }) - ); + return searchData.pages.flatMap(page => page.schedules); }, [searchData]); const searchTotal = searchData?.pages?.[0]?.total || 0;