diff --git a/src/components/business/CEODashboard/CEODashboard.tsx b/src/components/business/CEODashboard/CEODashboard.tsx index 6d15f5c6..c72bd8a3 100644 --- a/src/components/business/CEODashboard/CEODashboard.tsx +++ b/src/components/business/CEODashboard/CEODashboard.tsx @@ -25,7 +25,7 @@ import { DEFAULT_DASHBOARD_SETTINGS } from './types'; import { ScheduleDetailModal, DetailModal } from './modals'; import { DashboardSettingsDialog } from './dialogs/DashboardSettingsDialog'; import { mockData } from './mockData'; -import { useCEODashboard } from '@/hooks/useCEODashboard'; +import { useCEODashboard, useTodayIssue, useCalendar, useVat, useEntertainment, useWelfare } from '@/hooks/useCEODashboard'; import { getMonthlyExpenseModalConfig, getCardManagementModalConfig, @@ -42,6 +42,21 @@ export function CEODashboard() { cardManagementFallback: mockData.cardManagement, }); + // TodayIssue API Hook (Phase 2) + const todayIssueData = useTodayIssue(30); + + // Calendar API Hook (Phase 2) + const calendarData = useCalendar(); + + // Vat API Hook (Phase 2) + const vatData = useVat(); + + // Entertainment API Hook (Phase 2) + const entertainmentData = useEntertainment(); + + // Welfare API Hook (Phase 2) + const welfareData = useWelfare(); + // 전체 로딩 상태 (모든 API 호출 중일 때) const isLoading = useMemo(() => { return ( @@ -49,9 +64,15 @@ export function CEODashboard() { apiData.receivable.loading && apiData.debtCollection.loading && apiData.monthlyExpense.loading && - apiData.cardManagement.loading + apiData.cardManagement.loading && + apiData.statusBoard.loading && + todayIssueData.loading && + calendarData.loading && + vatData.loading && + entertainmentData.loading && + welfareData.loading ); - }, [apiData]); + }, [apiData, todayIssueData.loading, calendarData.loading, vatData.loading, entertainmentData.loading, welfareData.loading]); // API 데이터와 mockData를 병합 (API 우선, 실패 시 fallback) const data = useMemo(() => ({ @@ -62,9 +83,14 @@ export function CEODashboard() { debtCollection: apiData.debtCollection.data ?? mockData.debtCollection, monthlyExpense: apiData.monthlyExpense.data ?? mockData.monthlyExpense, cardManagement: apiData.cardManagement.data ?? mockData.cardManagement, - // Phase 2 섹션들: 아직 mockData 사용 - // todayIssue, todayIssueList, entertainment, welfare, vat, calendarSchedules - }), [apiData, mockData]); + // Phase 2 섹션들 + todayIssue: apiData.statusBoard.data ?? mockData.todayIssue, + todayIssueList: todayIssueData.data?.items ?? mockData.todayIssueList, + calendarSchedules: calendarData.data?.items ?? mockData.calendarSchedules, + vat: vatData.data ?? mockData.vat, + entertainment: entertainmentData.data ?? mockData.entertainment, + welfare: welfareData.data ?? mockData.welfare, + }), [apiData, todayIssueData.data, calendarData.data, vatData.data, entertainmentData.data, welfareData.data, mockData]); // 일정 상세 모달 상태 const [isScheduleModalOpen, setIsScheduleModalOpen] = useState(false); diff --git a/src/components/business/CEODashboard/modalConfigs/welfareConfigs.ts b/src/components/business/CEODashboard/modalConfigs/welfareConfigs.ts index 40c26789..00f6ad67 100644 --- a/src/components/business/CEODashboard/modalConfigs/welfareConfigs.ts +++ b/src/components/business/CEODashboard/modalConfigs/welfareConfigs.ts @@ -3,9 +3,9 @@ import type { DetailModalConfig } from '../types'; /** * 복리후생비 현황 모달 설정 * 모든 카드가 동일한 상세 모달 - * @param calculationType - 계산 방식 ('fixed': 직원당 정액 금액/월, 'percentage': 연봉 총액 비율) + * @param calculationType - 계산 방식 ('fixed': 직원당 정액 금액/월, 'ratio': 연봉 총액 비율) */ -export function getWelfareModalConfig(calculationType: 'fixed' | 'percentage'): DetailModalConfig { +export function getWelfareModalConfig(calculationType: 'fixed' | 'ratio'): DetailModalConfig { // 계산 방식에 따른 조건부 calculationCards 생성 const calculationCards = calculationType === 'fixed' ? { diff --git a/src/hooks/useCEODashboard.ts b/src/hooks/useCEODashboard.ts index 38b06ff5..55377f16 100644 --- a/src/hooks/useCEODashboard.ts +++ b/src/hooks/useCEODashboard.ts @@ -15,6 +15,12 @@ import type { BadDebtApiResponse, ExpectedExpenseApiResponse, CardTransactionApiResponse, + StatusBoardApiResponse, + TodayIssueApiResponse, + CalendarApiResponse, + VatApiResponse, + EntertainmentApiResponse, + WelfareApiResponse, } from '@/lib/api/dashboard/types'; import { @@ -23,6 +29,12 @@ import { transformDebtCollectionResponse, transformMonthlyExpenseResponse, transformCardManagementResponse, + transformStatusBoardResponse, + transformTodayIssueResponse, + transformCalendarResponse, + transformVatResponse, + transformEntertainmentResponse, + transformWelfareResponse, } from '@/lib/api/dashboard/transformers'; import type { @@ -31,6 +43,12 @@ import type { DebtCollectionData, MonthlyExpenseData, CardManagementData, + TodayIssueItem, + TodayIssueListItem, + CalendarScheduleItem, + VatData, + EntertainmentData, + WelfareData, } from '@/components/business/CEODashboard/types'; // ============================================ @@ -223,6 +241,318 @@ export function useCardManagement(fallbackData?: CardManagementData) { return { data, loading, error, refetch: fetchData }; } +// ============================================ +// 6. StatusBoard Hook +// ============================================ + +export function useStatusBoard() { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchData = useCallback(async () => { + try { + setLoading(true); + setError(null); + + const apiData = await fetchApi('status-board/summary'); + const transformed = transformStatusBoardResponse(apiData); + setData(transformed); + + } catch (err) { + const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; + setError(errorMessage); + console.error('StatusBoard API Error:', err); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + return { data, loading, error, refetch: fetchData }; +} + +// ============================================ +// 7. TodayIssue Hook +// ============================================ + +export interface TodayIssueData { + items: TodayIssueListItem[]; + totalCount: number; +} + +export function useTodayIssue(limit: number = 30) { + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchData = useCallback(async () => { + try { + setLoading(true); + setError(null); + + const apiData = await fetchApi(`today-issues/summary?limit=${limit}`); + const transformed = transformTodayIssueResponse(apiData); + setData(transformed); + + } catch (err) { + const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; + setError(errorMessage); + console.error('TodayIssue API Error:', err); + } finally { + setLoading(false); + } + }, [limit]); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + return { data, loading, error, refetch: fetchData }; +} + +// ============================================ +// 8. Calendar Hook +// ============================================ + +export interface CalendarData { + items: CalendarScheduleItem[]; + totalCount: number; +} + +export interface UseCalendarOptions { + /** 조회 시작일 (Y-m-d, 기본: 이번 달 1일) */ + startDate?: string; + /** 조회 종료일 (Y-m-d, 기본: 이번 달 말일) */ + endDate?: string; + /** 일정 타입 필터 (schedule|order|construction|other|null=전체) */ + type?: 'schedule' | 'order' | 'construction' | 'other' | null; + /** 부서 필터 (all|department|personal) */ + departmentFilter?: 'all' | 'department' | 'personal'; +} + +export function useCalendar(options: UseCalendarOptions = {}) { + const { startDate, endDate, type, departmentFilter = 'all' } = options; + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchData = useCallback(async () => { + try { + setLoading(true); + setError(null); + + // 쿼리 파라미터 구성 + const params = new URLSearchParams(); + if (startDate) params.append('start_date', startDate); + if (endDate) params.append('end_date', endDate); + if (type) params.append('type', type); + if (departmentFilter) params.append('department_filter', departmentFilter); + + const queryString = params.toString(); + const endpoint = queryString ? `calendar/schedules?${queryString}` : 'calendar/schedules'; + + const apiData = await fetchApi(endpoint); + const transformed = transformCalendarResponse(apiData); + setData(transformed); + + } catch (err) { + const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; + setError(errorMessage); + console.error('Calendar API Error:', err); + } finally { + setLoading(false); + } + }, [startDate, endDate, type, departmentFilter]); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + return { data, loading, error, refetch: fetchData }; +} + +// ============================================ +// 9. Vat Hook +// ============================================ + +export interface UseVatOptions { + /** 기간 타입 (quarter: 분기, half: 반기, year: 연간) */ + periodType?: 'quarter' | 'half' | 'year'; + /** 연도 (기본: 현재 연도) */ + year?: number; + /** 기간 번호 (quarter: 1-4, half: 1-2) */ + period?: number; +} + +export function useVat(options: UseVatOptions = {}) { + const { periodType = 'quarter', year, period } = options; + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchData = useCallback(async () => { + try { + setLoading(true); + setError(null); + + // 쿼리 파라미터 구성 + const params = new URLSearchParams(); + params.append('period_type', periodType); + if (year) params.append('year', year.toString()); + if (period) params.append('period', period.toString()); + + const queryString = params.toString(); + const endpoint = `vat/summary?${queryString}`; + + const apiData = await fetchApi(endpoint); + const transformed = transformVatResponse(apiData); + setData(transformed); + + } catch (err) { + const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; + setError(errorMessage); + console.error('Vat API Error:', err); + } finally { + setLoading(false); + } + }, [periodType, year, period]); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + return { data, loading, error, refetch: fetchData }; +} + +// ============================================ +// 10. Entertainment Hook (접대비) +// ============================================ + +export interface UseEntertainmentOptions { + /** 기간 타입 (annual: 연간, quarterly: 분기) */ + limitType?: 'annual' | 'quarterly'; + /** 기업 유형 (large: 대기업, medium: 중견기업, small: 중소기업) */ + companyType?: 'large' | 'medium' | 'small'; + /** 연도 (기본: 현재 연도) */ + year?: number; + /** 분기 번호 (1-4, 기본: 현재 분기) */ + quarter?: number; +} + +export function useEntertainment(options: UseEntertainmentOptions = {}) { + const { limitType = 'quarterly', companyType = 'medium', year, quarter } = options; + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchData = useCallback(async () => { + try { + setLoading(true); + setError(null); + + // 쿼리 파라미터 구성 + const params = new URLSearchParams(); + params.append('limit_type', limitType); + params.append('company_type', companyType); + if (year) params.append('year', year.toString()); + if (quarter) params.append('quarter', quarter.toString()); + + const queryString = params.toString(); + const endpoint = `entertainment/summary?${queryString}`; + + const apiData = await fetchApi(endpoint); + const transformed = transformEntertainmentResponse(apiData); + setData(transformed); + + } catch (err) { + const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; + setError(errorMessage); + console.error('Entertainment API Error:', err); + } finally { + setLoading(false); + } + }, [limitType, companyType, year, quarter]); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + return { data, loading, error, refetch: fetchData }; +} + +// ============================================ +// 11. Welfare Hook (복리후생비) +// ============================================ + +export interface UseWelfareOptions { + /** 기간 타입 (annual: 연간, quarterly: 분기) */ + limitType?: 'annual' | 'quarterly'; + /** 계산 방식 (fixed: 1인당 정액, ratio: 급여 대비 비율) */ + calculationType?: 'fixed' | 'ratio'; + /** 1인당 월 정액 (calculation_type=fixed일 때 사용, 기본: 200000) */ + fixedAmountPerMonth?: number; + /** 급여 대비 비율 (calculation_type=ratio일 때 사용, 기본: 0.05) */ + ratio?: number; + /** 연도 (기본: 현재 연도) */ + year?: number; + /** 분기 번호 (1-4, 기본: 현재 분기) */ + quarter?: number; +} + +export function useWelfare(options: UseWelfareOptions = {}) { + const { + limitType = 'quarterly', + calculationType = 'fixed', + fixedAmountPerMonth, + ratio, + year, + quarter, + } = options; + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchData = useCallback(async () => { + try { + setLoading(true); + setError(null); + + // 쿼리 파라미터 구성 + const params = new URLSearchParams(); + params.append('limit_type', limitType); + params.append('calculation_type', calculationType); + if (fixedAmountPerMonth) params.append('fixed_amount_per_month', fixedAmountPerMonth.toString()); + if (ratio) params.append('ratio', ratio.toString()); + if (year) params.append('year', year.toString()); + if (quarter) params.append('quarter', quarter.toString()); + + const queryString = params.toString(); + const endpoint = `welfare/summary?${queryString}`; + + const apiData = await fetchApi(endpoint); + const transformed = transformWelfareResponse(apiData); + setData(transformed); + + } catch (err) { + const errorMessage = err instanceof Error ? err.message : '데이터 로딩 실패'; + setError(errorMessage); + console.error('Welfare API Error:', err); + } finally { + setLoading(false); + } + }, [limitType, calculationType, fixedAmountPerMonth, ratio, year, quarter]); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + return { data, loading, error, refetch: fetchData }; +} + // ============================================ // 통합 Dashboard Hook (선택적 사용) // ============================================ @@ -240,6 +570,8 @@ export interface UseCEODashboardOptions { cardManagement?: boolean; /** CardManagement fallback 데이터 */ cardManagementFallback?: CardManagementData; + /** StatusBoard 섹션 활성화 */ + statusBoard?: boolean; } export interface CEODashboardState { @@ -268,6 +600,11 @@ export interface CEODashboardState { loading: boolean; error: string | null; }; + statusBoard: { + data: TodayIssueItem[] | null; + loading: boolean; + error: string | null; + }; refetchAll: () => void; } @@ -283,6 +620,7 @@ export function useCEODashboard(options: UseCEODashboardOptions = {}): CEODashbo monthlyExpense: enableMonthlyExpense = true, cardManagement: enableCardManagement = true, cardManagementFallback, + statusBoard: enableStatusBoard = true, } = options; // 각 섹션별 상태 @@ -306,6 +644,10 @@ export function useCEODashboard(options: UseCEODashboardOptions = {}): CEODashbo const [cardManagementLoading, setCardManagementLoading] = useState(enableCardManagement); const [cardManagementError, setCardManagementError] = useState(null); + const [statusBoardData, setStatusBoardData] = useState(null); + const [statusBoardLoading, setStatusBoardLoading] = useState(enableStatusBoard); + const [statusBoardError, setStatusBoardError] = useState(null); + // 개별 fetch 함수들 const fetchDailyReport = useCallback(async () => { if (!enableDailyReport) return; @@ -377,6 +719,20 @@ export function useCEODashboard(options: UseCEODashboardOptions = {}): CEODashbo } }, [enableCardManagement, cardManagementFallback]); + const fetchStatusBoard = useCallback(async () => { + if (!enableStatusBoard) return; + try { + setStatusBoardLoading(true); + setStatusBoardError(null); + const apiData = await fetchApi('status-board/summary'); + setStatusBoardData(transformStatusBoardResponse(apiData)); + } catch (err) { + setStatusBoardError(err instanceof Error ? err.message : '데이터 로딩 실패'); + } finally { + setStatusBoardLoading(false); + } + }, [enableStatusBoard]); + // 전체 refetch const refetchAll = useCallback(() => { fetchDailyReport(); @@ -384,7 +740,8 @@ export function useCEODashboard(options: UseCEODashboardOptions = {}): CEODashbo fetchDebtCollection(); fetchMonthlyExpense(); fetchCardManagement(); - }, [fetchDailyReport, fetchReceivable, fetchDebtCollection, fetchMonthlyExpense, fetchCardManagement]); + fetchStatusBoard(); + }, [fetchDailyReport, fetchReceivable, fetchDebtCollection, fetchMonthlyExpense, fetchCardManagement, fetchStatusBoard]); // 초기 로드 useEffect(() => { @@ -417,6 +774,11 @@ export function useCEODashboard(options: UseCEODashboardOptions = {}): CEODashbo loading: cardManagementLoading, error: cardManagementError, }, + statusBoard: { + data: statusBoardData, + loading: statusBoardLoading, + error: statusBoardError, + }, refetchAll, }; } \ No newline at end of file diff --git a/src/lib/api/dashboard/transformers.ts b/src/lib/api/dashboard/transformers.ts index 118c314f..f8bc53db 100644 --- a/src/lib/api/dashboard/transformers.ts +++ b/src/lib/api/dashboard/transformers.ts @@ -10,6 +10,12 @@ import type { BadDebtApiResponse, ExpectedExpenseApiResponse, CardTransactionApiResponse, + StatusBoardApiResponse, + TodayIssueApiResponse, + CalendarApiResponse, + VatApiResponse, + EntertainmentApiResponse, + WelfareApiResponse, } from './types'; import type { @@ -18,8 +24,16 @@ import type { DebtCollectionData, MonthlyExpenseData, CardManagementData, + TodayIssueItem, + TodayIssueListItem, + TodayIssueListBadgeType, + CalendarScheduleItem, CheckPoint, CheckPointType, + VatData, + EntertainmentData, + WelfareData, + HighlightColor, } from '@/components/business/CEODashboard/types'; // ============================================ @@ -428,4 +442,201 @@ export function transformCardManagementResponse( ], checkPoints: generateCardManagementCheckPoints(api), }; +} + +// ============================================ +// 6. StatusBoard 변환 +// ============================================ + +/** + * StatusBoard API 응답 → Frontend 타입 변환 + * API 응답 형식이 TodayIssueItem과 거의 동일하므로 단순 매핑 + */ +export function transformStatusBoardResponse(api: StatusBoardApiResponse): TodayIssueItem[] { + return api.items.map((item) => ({ + id: item.id, + label: item.label, + count: item.count, + path: item.path, + isHighlighted: item.isHighlighted, + })); +} + +// ============================================ +// 7. TodayIssue 변환 +// ============================================ + +/** 유효한 뱃지 타입 목록 */ +const VALID_BADGE_TYPES: TodayIssueListBadgeType[] = [ + '수주 성공', + '주식 이슈', + '직정 제고', + '지출예상내역서', + '세금 신고', + '결재 요청', + '기타', +]; + +/** + * API 뱃지 문자열 → Frontend 뱃지 타입 변환 + * 유효하지 않은 뱃지는 '기타'로 폴백 + */ +function validateBadgeType(badge: string): TodayIssueListBadgeType { + if (VALID_BADGE_TYPES.includes(badge as TodayIssueListBadgeType)) { + return badge as TodayIssueListBadgeType; + } + return '기타'; +} + +/** + * TodayIssue API 응답 → Frontend 타입 변환 + * 오늘의 이슈 리스트 데이터 변환 + */ +export function transformTodayIssueResponse(api: TodayIssueApiResponse): { + items: TodayIssueListItem[]; + totalCount: number; +} { + return { + items: api.items.map((item) => ({ + id: item.id, + badge: validateBadgeType(item.badge), + content: item.content, + time: item.time, + date: item.date, + needsApproval: item.needsApproval ?? false, + path: item.path, + })), + totalCount: api.total_count, + }; +} + +// ============================================ +// 8. Calendar 변환 +// ============================================ + +/** + * Calendar API 응답 → Frontend 타입 변환 + * API 응답 형식이 CalendarScheduleItem과 동일하므로 단순 매핑 + */ +export function transformCalendarResponse(api: CalendarApiResponse): { + items: CalendarScheduleItem[]; + totalCount: number; +} { + return { + items: api.items.map((item) => ({ + id: item.id, + title: item.title, + startDate: item.startDate, + endDate: item.endDate, + startTime: item.startTime, + endTime: item.endTime, + isAllDay: item.isAllDay, + type: item.type, + department: item.department, + personName: item.personName, + color: item.color, + })), + totalCount: api.total_count, + }; +} + +// ============================================ +// 9. Vat 변환 +// ============================================ + +/** 유효한 하이라이트 색상 목록 */ +const VALID_HIGHLIGHT_COLORS: HighlightColor[] = ['red', 'green', 'blue']; + +/** + * API 색상 문자열 → Frontend 하이라이트 색상 변환 + * 유효하지 않은 색상은 'blue'로 폴백 + */ +function validateHighlightColor(color: string): HighlightColor { + if (VALID_HIGHLIGHT_COLORS.includes(color as HighlightColor)) { + return color as HighlightColor; + } + return 'blue'; +} + +/** + * Vat API 응답 → Frontend 타입 변환 + * 부가세 현황 데이터 변환 + */ +export function transformVatResponse(api: VatApiResponse): VatData { + return { + cards: api.cards.map((card) => ({ + id: card.id, + label: card.label, + amount: card.amount, + subLabel: card.subLabel, + unit: card.unit, + })), + checkPoints: api.check_points.map((cp) => ({ + id: cp.id, + type: cp.type as CheckPointType, + message: cp.message, + highlights: cp.highlights?.map((h) => ({ + text: h.text, + color: validateHighlightColor(h.color), + })), + })), + }; +} + +// ============================================ +// 10. Entertainment 변환 +// ============================================ + +/** + * Entertainment API 응답 → Frontend 타입 변환 + * 접대비 현황 데이터 변환 + */ +export function transformEntertainmentResponse(api: EntertainmentApiResponse): EntertainmentData { + return { + cards: api.cards.map((card) => ({ + id: card.id, + label: card.label, + amount: card.amount, + subLabel: card.subLabel, + unit: card.unit, + })), + checkPoints: api.check_points.map((cp) => ({ + id: cp.id, + type: cp.type as CheckPointType, + message: cp.message, + highlights: cp.highlights?.map((h) => ({ + text: h.text, + color: validateHighlightColor(h.color), + })), + })), + }; +} + +// ============================================ +// 11. Welfare 변환 +// ============================================ + +/** + * Welfare API 응답 → Frontend 타입 변환 + * 복리후생비 현황 데이터 변환 + */ +export function transformWelfareResponse(api: WelfareApiResponse): WelfareData { + return { + cards: api.cards.map((card) => ({ + id: card.id, + label: card.label, + amount: card.amount, + subLabel: card.subLabel, + unit: card.unit, + })), + checkPoints: api.check_points.map((cp) => ({ + id: cp.id, + type: cp.type as CheckPointType, + message: cp.message, + highlights: cp.highlights?.map((h) => ({ + text: h.text, + color: validateHighlightColor(h.color), + })), + })), + }; } \ No newline at end of file diff --git a/src/lib/api/dashboard/types.ts b/src/lib/api/dashboard/types.ts index 717b2cb3..2cddb9c1 100644 --- a/src/lib/api/dashboard/types.ts +++ b/src/lib/api/dashboard/types.ts @@ -117,4 +117,170 @@ export interface DashboardErrorState { debtCollection: string | null; monthlyExpense: string | null; cardManagement: string | null; +} + +// ============================================ +// 6. StatusBoard (현황판) API 응답 타입 +// ============================================ + +/** 현황판 카드 아이템 */ +export interface StatusBoardItemApiResponse { + id: string; // 카드 ID (orders, bad_debts, etc.) + label: string; // 카드 라벨 + count: number | string; // 건수 또는 텍스트 (예: "부가세 신고 D-15") + path: string; // 이동 경로 + isHighlighted: boolean; // 강조 표시 여부 +} + +/** GET /api/proxy/status-board/summary 응답 */ +export interface StatusBoardApiResponse { + items: StatusBoardItemApiResponse[]; +} + +// ============================================ +// 7. TodayIssue (오늘의 이슈 리스트) API 응답 타입 +// ============================================ + +/** 오늘의 이슈 아이템 */ +export interface TodayIssueItemApiResponse { + id: string; // 항목 고유 ID + badge: string; // 이슈 카테고리 뱃지 + content: string; // 이슈 내용 + time: string; // 상대 시간 (예: "10분 전") + date?: string; // 날짜 (ISO 형식) + needsApproval?: boolean; // 승인/반려 버튼 표시 여부 + path?: string; // 클릭 시 이동할 경로 +} + +/** GET /api/proxy/today-issues/summary 응답 */ +export interface TodayIssueApiResponse { + items: TodayIssueItemApiResponse[]; + total_count: number; // 전체 이슈 건수 +} + +// ============================================ +// 8. Calendar (캘린더) API 응답 타입 +// ============================================ + +/** 캘린더 일정 타입 */ +export type CalendarScheduleType = 'schedule' | 'order' | 'construction' | 'other'; + +/** 캘린더 일정 아이템 */ +export interface CalendarScheduleItemApiResponse { + id: string; // 일정 ID (타입_ID 형식, 예: "wo_123") + title: string; // 일정 제목 + startDate: string; // 시작일 (Y-m-d) + endDate: string; // 종료일 (Y-m-d) + startTime: string | null; // 시작 시간 (HH:mm) 또는 null + endTime: string | null; // 종료 시간 (HH:mm) 또는 null + isAllDay: boolean; // 종일 여부 + type: CalendarScheduleType; // 일정 타입 + department: string | null; // 부서명 + personName: string | null; // 담당자명 + color: string | null; // 일정 색상 +} + +/** GET /api/proxy/calendar/schedules 응답 */ +export interface CalendarApiResponse { + items: CalendarScheduleItemApiResponse[]; + total_count: number; // 총 일정 수 +} + +// ============================================ +// 9. Vat (부가세) API 응답 타입 +// ============================================ + +/** 부가세 금액 카드 아이템 */ +export interface VatAmountCardApiResponse { + id: string; // 카드 ID (vat_sales_tax, vat_purchases_tax, etc.) + label: string; // 카드 라벨 + amount: number; // 금액 또는 건수 + subLabel?: string; // 부가 라벨 (예: "환급") + unit?: string; // 단위 (예: "건") +} + +/** 부가세 체크포인트 하이라이트 아이템 */ +export interface VatHighlightItemApiResponse { + text: string; // 하이라이트 텍스트 + color: string; // 색상 (red, blue, green 등) +} + +/** 부가세 체크포인트 아이템 */ +export interface VatCheckPointApiResponse { + id: string; // 체크포인트 ID + type: string; // 타입 (success, warning, error) + message: string; // 메시지 + highlights?: VatHighlightItemApiResponse[]; // 하이라이트 아이템 목록 +} + +/** GET /api/proxy/vat/summary 응답 */ +export interface VatApiResponse { + cards: VatAmountCardApiResponse[]; + check_points: VatCheckPointApiResponse[]; +} + +// ============================================ +// 10. Entertainment (접대비) API 응답 타입 +// ============================================ + +/** 접대비 금액 카드 아이템 */ +export interface EntertainmentAmountCardApiResponse { + id: string; // 카드 ID (et_sales, et_limit, etc.) + label: string; // 카드 라벨 + amount: number; // 금액 + subLabel?: string; // 부가 라벨 + unit?: string; // 단위 +} + +/** 접대비 체크포인트 하이라이트 아이템 */ +export interface EntertainmentHighlightItemApiResponse { + text: string; // 하이라이트 텍스트 + color: string; // 색상 (red, green, orange 등) +} + +/** 접대비 체크포인트 아이템 */ +export interface EntertainmentCheckPointApiResponse { + id: string; // 체크포인트 ID + type: string; // 타입 (success, warning, error) + message: string; // 메시지 + highlights?: EntertainmentHighlightItemApiResponse[]; // 하이라이트 아이템 목록 +} + +/** GET /api/proxy/entertainment/summary 응답 */ +export interface EntertainmentApiResponse { + cards: EntertainmentAmountCardApiResponse[]; + check_points: EntertainmentCheckPointApiResponse[]; +} + +// ============================================ +// 11. Welfare (복리후생비) API 응답 타입 +// ============================================ + +/** 복리후생비 금액 카드 아이템 */ +export interface WelfareAmountCardApiResponse { + id: string; // 카드 ID (wf_annual_limit, wf_period_limit, etc.) + label: string; // 카드 라벨 + amount: number; // 금액 + subLabel?: string; // 부가 라벨 + unit?: string; // 단위 +} + +/** 복리후생비 체크포인트 하이라이트 아이템 */ +export interface WelfareHighlightItemApiResponse { + text: string; // 하이라이트 텍스트 + color: string; // 색상 (red, green, orange 등) +} + +/** 복리후생비 체크포인트 아이템 */ +export interface WelfareCheckPointApiResponse { + id: string; // 체크포인트 ID + type: string; // 타입 (success, warning, error) + message: string; // 메시지 + highlights?: WelfareHighlightItemApiResponse[]; // 하이라이트 아이템 목록 +} + +/** GET /api/proxy/welfare/summary 응답 */ +export interface WelfareApiResponse { + cards: WelfareAmountCardApiResponse[]; + check_points: WelfareCheckPointApiResponse[]; } \ No newline at end of file