- CEO 대시보드 섹션별 컴포넌트 분리 (건설/생산/매출/매입/미출하/출근) - LazySection 지연 로딩 패턴 적용 - DashboardSettingsDialog 섹션 표시/순서 설정 확장 - 캘린더 관리 페이지 신규 추가 (settings/calendar-management) - useCalendarScheduleStore Zustand 스토어 추가 - CalendarHeader 일정 추가/관리 기능 강화 - 거래처 관리 상세 화면 개선 (VendorDetail/VendorDetailClient) - 카드 관리 상세 화면 리팩토링 - FormField 기능 확장 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
123 lines
4.2 KiB
TypeScript
123 lines
4.2 KiB
TypeScript
/**
|
|
* 달력 일정 Zustand Store
|
|
*
|
|
* 달력관리(CalendarManagement)에서 등록한 공휴일/세무일정/회사일정을
|
|
* 프로젝트 전체 ScheduleCalendar, DatePicker 등에 전파하는 공유 스토어.
|
|
*
|
|
* 데이터 흐름:
|
|
* 1. ScheduleCalendar 마운트 시 해당 연도 fetchSchedules(year) 호출
|
|
* 2. API 응답 → schedulesByYear에 캐시
|
|
* 3. calendarEvents.ts 유틸 함수(isHoliday 등)가 스토어에서 읽음
|
|
* 4. API 미응답 시 하드코딩된 calendarEvents.ts 상수를 폴백으로 사용
|
|
*/
|
|
|
|
import { create } from 'zustand';
|
|
import { devtools } from 'zustand/middleware';
|
|
import type { CalendarSchedule } from '@/components/hr/CalendarManagement/types';
|
|
import { getCalendarSchedules } from '@/components/hr/CalendarManagement/actions';
|
|
|
|
interface CalendarScheduleStore {
|
|
/** 연도별 캐시된 일정 */
|
|
schedulesByYear: Record<number, CalendarSchedule[]>;
|
|
/** 연도별 로딩 완료 여부 */
|
|
loadedYears: Record<number, boolean>;
|
|
/** 연도별 로딩 중 여부 */
|
|
loadingYears: Record<number, boolean>;
|
|
|
|
/** 연도별 일정 조회 (캐시 히트 시 API 호출 안 함) */
|
|
fetchSchedules: (year: number) => Promise<CalendarSchedule[]>;
|
|
/** 이미 가져온 데이터를 스토어에 직접 설정 (추가 API 호출 없음) */
|
|
setSchedulesForYear: (year: number, schedules: CalendarSchedule[]) => void;
|
|
/** 특정 연도 캐시 무효화 (달력관리에서 등록/수정/삭제 후 호출) */
|
|
invalidateYear: (year: number) => void;
|
|
}
|
|
|
|
export const useCalendarScheduleStore = create<CalendarScheduleStore>()(
|
|
devtools(
|
|
(set, get) => ({
|
|
schedulesByYear: {},
|
|
loadedYears: {},
|
|
loadingYears: {},
|
|
|
|
fetchSchedules: async (year: number) => {
|
|
const { loadedYears, loadingYears, schedulesByYear } = get();
|
|
|
|
// 이미 로드됨
|
|
if (loadedYears[year]) {
|
|
return schedulesByYear[year] || [];
|
|
}
|
|
|
|
// 이미 로딩 중 (중복 호출 방지)
|
|
if (loadingYears[year]) {
|
|
return schedulesByYear[year] || [];
|
|
}
|
|
|
|
set(
|
|
(state) => ({
|
|
loadingYears: { ...state.loadingYears, [year]: true },
|
|
}),
|
|
false,
|
|
'fetchSchedules/start'
|
|
);
|
|
|
|
try {
|
|
const result = await getCalendarSchedules(year);
|
|
const schedules = result.success && result.data ? result.data : [];
|
|
|
|
set(
|
|
(state) => ({
|
|
schedulesByYear: { ...state.schedulesByYear, [year]: schedules },
|
|
loadedYears: { ...state.loadedYears, [year]: true },
|
|
loadingYears: { ...state.loadingYears, [year]: false },
|
|
}),
|
|
false,
|
|
'fetchSchedules/success'
|
|
);
|
|
|
|
return schedules;
|
|
} catch {
|
|
// API 실패 시에도 loaded 처리 → 같은 연도 반복 호출 방지
|
|
// (폴백 상수 데이터 사용, 달력관리에서 등록 시 invalidateYear로 재시도)
|
|
set(
|
|
(state) => ({
|
|
schedulesByYear: { ...state.schedulesByYear, [year]: [] },
|
|
loadedYears: { ...state.loadedYears, [year]: true },
|
|
loadingYears: { ...state.loadingYears, [year]: false },
|
|
}),
|
|
false,
|
|
'fetchSchedules/error'
|
|
);
|
|
return [];
|
|
}
|
|
},
|
|
|
|
setSchedulesForYear: (year: number, schedules: CalendarSchedule[]) => {
|
|
set(
|
|
(state) => ({
|
|
schedulesByYear: { ...state.schedulesByYear, [year]: schedules },
|
|
loadedYears: { ...state.loadedYears, [year]: true },
|
|
loadingYears: { ...state.loadingYears, [year]: false },
|
|
}),
|
|
false,
|
|
'setSchedulesForYear'
|
|
);
|
|
},
|
|
|
|
invalidateYear: (year: number) => {
|
|
set(
|
|
(state) => {
|
|
const newSchedules = { ...state.schedulesByYear };
|
|
const newLoaded = { ...state.loadedYears };
|
|
delete newSchedules[year];
|
|
delete newLoaded[year];
|
|
return { schedulesByYear: newSchedules, loadedYears: newLoaded };
|
|
},
|
|
false,
|
|
'invalidateYear'
|
|
);
|
|
},
|
|
}),
|
|
{ name: 'CalendarScheduleStore' }
|
|
)
|
|
);
|