refactor(frontend): API 레이어에 데이터 변환 로직 통합
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
e136f3c74b
commit
55096c8e43
2 changed files with 47 additions and 49 deletions
|
|
@ -4,40 +4,62 @@
|
||||||
import { fetchApi } from "../index";
|
import { fetchApi } from "../index";
|
||||||
import { getTodayKST } from "../../utils/date";
|
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) {
|
export async function getSchedules(year, month) {
|
||||||
const data = await fetchApi(`/api/schedules?year=${year}&month=${month}`);
|
const data = await fetchApi(`/api/schedules?year=${year}&month=${month}`);
|
||||||
|
return (data.schedules || []).map(transformSchedule);
|
||||||
// 날짜별 그룹화된 응답을 플랫 배열로 변환
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 다가오는 일정 조회 (오늘 이후)
|
// 다가오는 일정 조회 (오늘 이후)
|
||||||
export async function getUpcomingSchedules(limit = 3) {
|
export async function getUpcomingSchedules(limit = 3) {
|
||||||
const todayStr = getTodayKST();
|
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)
|
// 일정 검색 (Meilisearch)
|
||||||
export async function searchSchedules(query, { offset = 0, limit = 20 } = {}) {
|
export async function searchSchedules(query, { offset = 0, limit = 20 } = {}) {
|
||||||
return fetchApi(
|
const data = await fetchApi(
|
||||||
`/api/schedules?search=${encodeURIComponent(
|
`/api/schedules?search=${encodeURIComponent(query)}&offset=${offset}&limit=${limit}`
|
||||||
query
|
|
||||||
)}&offset=${offset}&limit=${limit}`
|
|
||||||
);
|
);
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
schedules: (data.schedules || []).map(transformSchedule),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 일정 상세 조회
|
// 일정 상세 조회
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import { useVirtualizer } from '@tanstack/react-virtual';
|
||||||
import { useInView } from 'react-intersection-observer';
|
import { useInView } from 'react-intersection-observer';
|
||||||
import confetti from 'canvas-confetti';
|
import confetti from 'canvas-confetti';
|
||||||
import { getTodayKST } from '../../../utils/date';
|
import { getTodayKST } from '../../../utils/date';
|
||||||
import { getSchedules } from '../../../api/public/schedules';
|
import { getSchedules, searchSchedules } from '../../../api/public/schedules';
|
||||||
import useScheduleStore from '../../../stores/useScheduleStore';
|
import useScheduleStore from '../../../stores/useScheduleStore';
|
||||||
|
|
||||||
// HTML 엔티티 디코딩 함수
|
// HTML 엔티티 디코딩 함수
|
||||||
|
|
@ -257,11 +257,7 @@ function Schedule() {
|
||||||
} = useInfiniteQuery({
|
} = useInfiniteQuery({
|
||||||
queryKey: ['scheduleSearch', searchTerm],
|
queryKey: ['scheduleSearch', searchTerm],
|
||||||
queryFn: async ({ pageParam = 0 }) => {
|
queryFn: async ({ pageParam = 0 }) => {
|
||||||
const response = await fetch(
|
return searchSchedules(searchTerm, { offset: pageParam, limit: SEARCH_LIMIT });
|
||||||
`/api/schedules?search=${encodeURIComponent(searchTerm)}&offset=${pageParam}&limit=${SEARCH_LIMIT}`
|
|
||||||
);
|
|
||||||
if (!response.ok) throw new Error('Search failed');
|
|
||||||
return response.json();
|
|
||||||
},
|
},
|
||||||
getNextPageParam: (lastPage) => {
|
getNextPageParam: (lastPage) => {
|
||||||
if (lastPage.hasMore) {
|
if (lastPage.hasMore) {
|
||||||
|
|
@ -272,30 +268,10 @@ function Schedule() {
|
||||||
enabled: !!searchTerm && isSearchMode,
|
enabled: !!searchTerm && isSearchMode,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Flatten search results and normalize format to match monthly data
|
// Flatten search results (already transformed by API layer)
|
||||||
const searchResults = useMemo(() => {
|
const searchResults = useMemo(() => {
|
||||||
if (!searchData?.pages) return [];
|
if (!searchData?.pages) return [];
|
||||||
return searchData.pages.flatMap(page =>
|
return searchData.pages.flatMap(page => page.schedules);
|
||||||
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,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}, [searchData]);
|
}, [searchData]);
|
||||||
|
|
||||||
const searchTotal = searchData?.pages?.[0]?.total || 0;
|
const searchTotal = searchData?.pages?.[0]?.total || 0;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue