'use server'; /** * 실적신고관리 Server Actions * * API Endpoints: * - GET /api/v1/performance-reports - 분기별 실적신고 목록 * - GET /api/v1/performance-reports/stats - 통계 * - GET /api/v1/performance-reports/missed - 누락체크 목록 * - PATCH /api/v1/performance-reports/confirm - 선택 확정 * - PATCH /api/v1/performance-reports/unconfirm - 확정 해제 * - POST /api/v1/performance-reports/distribute - 배포 * - PATCH /api/v1/performance-reports/memo - 메모 일괄 적용 */ import { executeServerAction } from '@/lib/api/execute-server-action'; import type { PerformanceReport, PerformanceReportStats, MissedReport, Quarter, } from './types'; import { mockPerformanceReports, mockPerformanceReportStats, mockMissedReports, } from './mockData'; // 개발환경 Mock 데이터 fallback 플래그 const USE_MOCK_FALLBACK = true; // ===== 페이지네이션 ===== interface PaginationMeta { currentPage: number; lastPage: number; perPage: number; total: number; } const API_BASE = `${process.env.NEXT_PUBLIC_API_URL}/api/v1/performance-reports`; // ===== 분기별 실적신고 목록 조회 ===== export async function getPerformanceReports(params?: { page?: number; size?: number; q?: string; year?: number; quarter?: Quarter | '전체'; }): Promise<{ success: boolean; data: PerformanceReport[]; pagination: PaginationMeta; error?: string; __authError?: boolean; }> { const defaultPagination = { currentPage: 1, lastPage: 1, perPage: 20, total: 0 }; const searchParams = new URLSearchParams(); if (params?.page) searchParams.set('page', String(params.page)); if (params?.size) searchParams.set('per_page', String(params.size)); if (params?.q) searchParams.set('q', params.q); if (params?.year) searchParams.set('year', String(params.year)); if (params?.quarter && params.quarter !== '전체') { searchParams.set('quarter', params.quarter); } const queryString = searchParams.toString(); interface ApiListData { items?: PerformanceReport[]; current_page?: number; last_page?: number; per_page?: number; total?: number } const result = await executeServerAction({ url: `${API_BASE}${queryString ? `?${queryString}` : ''}`, errorMessage: '실적신고 목록 조회에 실패했습니다.', }); if (!result.success) { if (USE_MOCK_FALLBACK) { let filtered = [...mockPerformanceReports]; if (params?.year) filtered = filtered.filter(i => i.year === params.year); if (params?.quarter && params.quarter !== '전체') filtered = filtered.filter(i => i.quarter === params.quarter); if (params?.q) { const q = params.q.toLowerCase(); filtered = filtered.filter(i => i.siteName.toLowerCase().includes(q) || i.client.toLowerCase().includes(q) || i.qualityDocNumber.toLowerCase().includes(q) ); } const page = params?.page || 1; const size = params?.size || 20; const start = (page - 1) * size; return { success: true, data: filtered.slice(start, start + size), pagination: { currentPage: page, lastPage: Math.ceil(filtered.length / size), perPage: size, total: filtered.length }, }; } return { success: false, data: [], pagination: defaultPagination, error: result.error, __authError: result.__authError }; } const d = result.data; return { success: true, data: d?.items || [], pagination: { currentPage: d?.current_page || 1, lastPage: d?.last_page || 1, perPage: d?.per_page || 20, total: d?.total || 0, }, }; } // ===== 통계 조회 ===== export async function getPerformanceReportStats(params?: { year?: number; quarter?: Quarter | '전체'; }): Promise<{ success: boolean; data?: PerformanceReportStats; error?: string; __authError?: boolean; }> { const searchParams = new URLSearchParams(); if (params?.year) searchParams.set('year', String(params.year)); if (params?.quarter && params.quarter !== '전체') { searchParams.set('quarter', params.quarter); } const queryString = searchParams.toString(); const result = await executeServerAction({ url: `${API_BASE}/stats${queryString ? `?${queryString}` : ''}`, errorMessage: '실적신고 통계 조회에 실패했습니다.', }); if (!result.success) { if (USE_MOCK_FALLBACK) return { success: true, data: mockPerformanceReportStats }; return { success: false, error: result.error, __authError: result.__authError }; } return { success: true, data: result.data }; } // ===== 누락체크 목록 조회 ===== export async function getMissedReports(params?: { page?: number; size?: number; q?: string; }): Promise<{ success: boolean; data: MissedReport[]; pagination: PaginationMeta; error?: string; __authError?: boolean; }> { const defaultPagination = { currentPage: 1, lastPage: 1, perPage: 20, total: 0 }; const searchParams = new URLSearchParams(); if (params?.page) searchParams.set('page', String(params.page)); if (params?.size) searchParams.set('per_page', String(params.size)); if (params?.q) searchParams.set('q', params.q); const queryString = searchParams.toString(); interface ApiMissedData { items?: MissedReport[]; current_page?: number; last_page?: number; per_page?: number; total?: number } const result = await executeServerAction({ url: `${API_BASE}/missed${queryString ? `?${queryString}` : ''}`, errorMessage: '누락체크 목록 조회에 실패했습니다.', }); if (!result.success) { if (USE_MOCK_FALLBACK) { let filtered = [...mockMissedReports]; if (params?.q) { const q = params.q.toLowerCase(); filtered = filtered.filter(i => i.siteName.toLowerCase().includes(q) || i.client.toLowerCase().includes(q) || i.qualityDocNumber.toLowerCase().includes(q) ); } const page = params?.page || 1; const size = params?.size || 20; const start = (page - 1) * size; return { success: true, data: filtered.slice(start, start + size), pagination: { currentPage: page, lastPage: Math.ceil(filtered.length / size), perPage: size, total: filtered.length }, }; } return { success: false, data: [], pagination: defaultPagination, error: result.error, __authError: result.__authError }; } const d = result.data; return { success: true, data: d?.items || [], pagination: { currentPage: d?.current_page || 1, lastPage: d?.last_page || 1, perPage: d?.per_page || 20, total: d?.total || 0, }, }; } // ===== 선택 확정 ===== export async function confirmReports(ids: string[]): Promise<{ success: boolean; error?: string; __authError?: boolean; }> { const result = await executeServerAction({ url: `${API_BASE}/confirm`, method: 'PATCH', body: { ids }, errorMessage: '확정 처리에 실패했습니다.', }); if (!result.success && USE_MOCK_FALLBACK) return { success: true }; return { success: result.success, error: result.error, __authError: result.__authError }; } // ===== 확정 해제 ===== export async function unconfirmReports(ids: string[]): Promise<{ success: boolean; error?: string; __authError?: boolean; }> { const result = await executeServerAction({ url: `${API_BASE}/unconfirm`, method: 'PATCH', body: { ids }, errorMessage: '확정 해제에 실패했습니다.', }); if (!result.success && USE_MOCK_FALLBACK) return { success: true }; return { success: result.success, error: result.error, __authError: result.__authError }; } // ===== 배포 ===== export async function distributeReports(ids: string[]): Promise<{ success: boolean; error?: string; __authError?: boolean; }> { const result = await executeServerAction({ url: `${API_BASE}/distribute`, method: 'POST', body: { ids }, errorMessage: '배포에 실패했습니다.', }); if (!result.success && USE_MOCK_FALLBACK) return { success: true }; return { success: result.success, error: result.error, __authError: result.__authError }; } // ===== 메모 일괄 적용 ===== export async function updateMemo(ids: string[], memo: string): Promise<{ success: boolean; error?: string; __authError?: boolean; }> { const result = await executeServerAction({ url: `${API_BASE}/memo`, method: 'PATCH', body: { ids, memo }, errorMessage: '메모 저장에 실패했습니다.', }); if (!result.success && USE_MOCK_FALLBACK) return { success: true }; return { success: result.success, error: result.error, __authError: result.__authError }; }