From 2d7809b4e0748e1f74640f3b68b002b64a12209b Mon Sep 17 00:00:00 2001 From: kent Date: Fri, 9 Jan 2026 10:18:57 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20[=EC=8B=9C=EA=B3=B5=EA=B4=80=EB=A6=AC]?= =?UTF-8?q?=20=EA=B3=84=EC=95=BD=EA=B4=80=EB=A6=AC=20Frontend=20API=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - actions.ts Mock 데이터 → 실제 API 호출로 전환 - apiRequest 헬퍼 함수 구현 (인증, 에러 처리) - API 응답 snake_case → camelCase 변환 함수 추가 - CRUD 전체 기능 API 연동 완료 --- .../business/construction/contract/actions.ts | 746 ++++++++---------- 1 file changed, 337 insertions(+), 409 deletions(-) diff --git a/src/components/business/construction/contract/actions.ts b/src/components/business/construction/contract/actions.ts index 92f9172b..a7229f94 100644 --- a/src/components/business/construction/contract/actions.ts +++ b/src/components/business/construction/contract/actions.ts @@ -1,5 +1,6 @@ 'use server'; +import { cookies } from 'next/headers'; import type { Contract, ContractDetail, @@ -10,327 +11,189 @@ import type { ContractFormData, } from './types'; -// 목업 데이터 -const MOCK_CONTRACTS: Contract[] = [ - { - id: '1', - contractCode: 'CT-2025-001', - partnerId: '1', - partnerName: '통신공사', - projectName: '강남역 통신시설 구축', - contractManagerId: 'hong', - contractManagerName: '홍길동', - constructionPMId: 'kim', - constructionPMName: '김PM', - totalLocations: 15, - contractAmount: 150000000, - contractStartDate: '2025-12-17', - contractEndDate: '2026-06-17', - status: 'pending', - stage: 'estimate_selected', - remarks: '', - createdAt: '2025-01-01', - updatedAt: '2025-01-01', - createdBy: 'system', - biddingId: '1', - biddingCode: 'BID-2025-001', - }, - { - id: '2', - contractCode: 'CT-2025-002', - partnerId: '2', - partnerName: '야사건설', - projectName: '판교 IT단지 배선공사', - contractManagerId: 'hong', - contractManagerName: '홍길동', - constructionPMId: 'lee', - constructionPMName: '이PM', - totalLocations: 28, - contractAmount: 280000000, - contractStartDate: '2025-11-01', - contractEndDate: '2026-03-31', - status: 'pending', - stage: 'estimate_progress', - remarks: '', - createdAt: '2025-01-02', - updatedAt: '2025-01-02', - createdBy: 'system', - biddingId: '2', - biddingCode: 'BID-2025-002', - }, - { - id: '3', - contractCode: 'CT-2025-003', - partnerId: '3', - partnerName: '여의건설', - projectName: '여의도 오피스빌딩 통신설비', - contractManagerId: 'kim', - contractManagerName: '김철수', - constructionPMId: 'park', - constructionPMName: '박PM', - totalLocations: 42, - contractAmount: 420000000, - contractStartDate: '2025-10-15', - contractEndDate: '2026-04-15', - status: 'pending', - stage: 'delivery', - remarks: '', - createdAt: '2025-01-03', - updatedAt: '2025-01-03', - createdBy: 'system', - biddingId: '3', - biddingCode: 'BID-2025-003', - }, - { - id: '4', - contractCode: 'CT-2025-004', - partnerId: '1', - partnerName: '통신공사', - projectName: '송파 데이터센터 증설', - contractManagerId: 'hong', - contractManagerName: '홍길동', - constructionPMId: 'kim', - constructionPMName: '김PM', - totalLocations: 58, - contractAmount: 580000000, - contractStartDate: '2025-09-01', - contractEndDate: '2026-02-28', - status: 'completed', - stage: 'inspection', - remarks: '', - createdAt: '2025-01-04', - updatedAt: '2025-01-04', - createdBy: 'system', - biddingId: '4', - biddingCode: 'BID-2025-004', - }, - { - id: '5', - contractCode: 'CT-2025-005', - partnerId: '2', - partnerName: '야사건설', - projectName: '분당 스마트빌딩 LAN공사', - contractManagerId: 'lee', - contractManagerName: '이영희', - constructionPMId: 'lee', - constructionPMName: '이PM', - totalLocations: 12, - contractAmount: 95000000, - contractStartDate: '2025-12-01', - contractEndDate: '2026-01-31', - status: 'pending', - stage: 'installation', - remarks: '', - createdAt: '2025-01-05', - updatedAt: '2025-01-05', - createdBy: 'system', - biddingId: '5', - biddingCode: 'BID-2025-005', - }, - { - id: '6', - contractCode: 'CT-2025-006', - partnerId: '3', - partnerName: '여의건설', - projectName: '마포 복합시설 CCTV설치', - contractManagerId: 'hong', - contractManagerName: '홍길동', - constructionPMId: 'park', - constructionPMName: '박PM', - totalLocations: 8, - contractAmount: 75000000, - contractStartDate: '2025-08-01', - contractEndDate: '2025-10-31', - status: 'completed', - stage: 'estimate_selected', - remarks: '', - createdAt: '2025-01-06', - updatedAt: '2025-01-06', - createdBy: 'system', - biddingId: '6', - biddingCode: 'BID-2025-006', - }, - { - id: '7', - contractCode: 'CT-2025-007', - partnerId: '1', - partnerName: '통신공사', - projectName: '용산 아파트 인터폰교체', - contractManagerId: 'kim', - contractManagerName: '김철수', - constructionPMId: 'kim', - constructionPMName: '김PM', - totalLocations: 120, - contractAmount: 45000000, - contractStartDate: '2025-07-15', - contractEndDate: '2025-09-15', - status: 'completed', - stage: 'estimate_progress', - remarks: '', - createdAt: '2025-01-07', - updatedAt: '2025-01-07', - createdBy: 'system', - biddingId: '7', - biddingCode: 'BID-2025-007', - }, - { - id: '8', - contractCode: 'CT-2025-008', - partnerId: '2', - partnerName: '야사건설', - projectName: '성수동 공장 방범설비', - contractManagerId: 'lee', - contractManagerName: '이영희', - constructionPMId: 'lee', - constructionPMName: '이PM', - totalLocations: 24, - contractAmount: 120000000, - contractStartDate: '2025-11-15', - contractEndDate: '2026-02-15', - status: 'pending', - stage: 'other', - remarks: '', - createdAt: '2025-01-08', - updatedAt: '2025-01-08', - createdBy: 'system', - biddingId: '8', - biddingCode: 'BID-2025-008', - }, - { - id: '9', - contractCode: 'CT-2025-009', - partnerId: '3', - partnerName: '여의건설', - projectName: '강서 물류센터 네트워크', - contractManagerId: 'hong', - contractManagerName: '홍길동', - constructionPMId: 'park', - constructionPMName: '박PM', - totalLocations: 35, - contractAmount: 320000000, - contractStartDate: '2025-06-01', - contractEndDate: '2025-11-30', - status: 'completed', - stage: 'inspection', - remarks: '', - createdAt: '2025-01-09', - updatedAt: '2025-01-09', - createdBy: 'system', - biddingId: '9', - biddingCode: 'BID-2025-009', - }, -]; +/** + * 주일 기업 - 계약관리 Server Actions + * API 연동 버전 + */ -// 계약 목록 조회 +// API 기본 URL +const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://api.sam.kr'; +const API_KEY = process.env.API_KEY || ''; + +/** + * API 요청 헬퍼 함수 + */ +async function apiRequest( + endpoint: string, + options: RequestInit = {} +): Promise<{ success: boolean; data?: T; error?: string; message?: string }> { + try { + const cookieStore = await cookies(); + const accessToken = cookieStore.get('access_token')?.value; + + const headers: Record = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'X-API-KEY': API_KEY, + }; + + if (accessToken) { + headers['Authorization'] = `Bearer ${accessToken}`; + } + + const url = `${API_BASE_URL}/api/v1${endpoint}`; + console.log('🔵 [Contract API]', options.method || 'GET', url); + + const response = await fetch(url, { + ...options, + headers: { + ...headers, + ...options.headers, + }, + }); + + const result = await response.json(); + console.log('🔵 [Contract API] Response status:', response.status); + + if (!response.ok) { + return { + success: false, + error: result.message || `API 오류: ${response.status}`, + }; + } + + return { + success: result.success ?? true, + data: result.data, + message: result.message, + }; + } catch (error) { + console.error('API request error:', error); + return { + success: false, + error: error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.', + }; + } +} + +/** + * API 응답 → 프론트엔드 타입 변환 + */ +function transformContract(apiData: Record): Contract { + return { + id: String(apiData.id), + contractCode: String(apiData.contract_code || ''), + partnerId: String(apiData.partner_id || ''), + partnerName: String(apiData.partner_name || ''), + projectName: String(apiData.project_name || ''), + contractManagerId: String(apiData.contract_manager_id || ''), + contractManagerName: String(apiData.contract_manager_name || ''), + constructionPMId: String(apiData.construction_pm_id || ''), + constructionPMName: String(apiData.construction_pm_name || ''), + totalLocations: Number(apiData.total_locations || 0), + contractAmount: Number(apiData.contract_amount || 0), + contractStartDate: apiData.contract_start_date ? String(apiData.contract_start_date) : null, + contractEndDate: apiData.contract_end_date ? String(apiData.contract_end_date) : null, + status: (apiData.status as 'pending' | 'completed') || 'pending', + stage: (apiData.stage as Contract['stage']) || 'estimate_selected', + remarks: String(apiData.remarks || ''), + createdAt: String(apiData.created_at || ''), + updatedAt: String(apiData.updated_at || ''), + createdBy: String(apiData.created_by || ''), + biddingId: String(apiData.bidding_id || ''), + biddingCode: String(apiData.bidding_code || ''), + }; +} + +/** + * 프론트엔드 → API 요청 타입 변환 + */ +function transformToApiRequest(data: Partial): Record { + const apiData: Record = {}; + + if (data.contractCode !== undefined) apiData.contract_code = data.contractCode; + if (data.projectName !== undefined) apiData.project_name = data.projectName; + if (data.partnerId !== undefined) apiData.partner_id = data.partnerId || null; + if (data.partnerName !== undefined) apiData.partner_name = data.partnerName || null; + if (data.contractManagerId !== undefined) apiData.contract_manager_id = data.contractManagerId || null; + if (data.contractManagerName !== undefined) apiData.contract_manager_name = data.contractManagerName || null; + if (data.totalLocations !== undefined) apiData.total_locations = data.totalLocations; + if (data.contractAmount !== undefined) apiData.contract_amount = data.contractAmount; + if (data.contractStartDate !== undefined) apiData.contract_start_date = data.contractStartDate || null; + if (data.contractEndDate !== undefined) apiData.contract_end_date = data.contractEndDate || null; + if (data.status !== undefined) apiData.status = data.status; + if (data.remarks !== undefined) apiData.remarks = data.remarks || null; + + return apiData; +} + +/** + * 계약 목록 조회 + */ export async function getContractList(filter?: ContractFilter): Promise<{ success: boolean; data?: ContractListResponse; error?: string; }> { try { - await new Promise((resolve) => setTimeout(resolve, 300)); + const params = new URLSearchParams(); - let filteredData = [...MOCK_CONTRACTS]; + if (filter?.search) params.append('search', filter.search); + if (filter?.status && filter.status !== 'all') params.append('status', filter.status); + if (filter?.stage && filter.stage !== 'all') params.append('stage', filter.stage); + if (filter?.partnerId && filter.partnerId !== 'all') params.append('partner_id', filter.partnerId); + if (filter?.contractManagerId && filter.contractManagerId !== 'all') params.append('contract_manager_id', filter.contractManagerId); + if (filter?.constructionPMId && filter.constructionPMId !== 'all') params.append('construction_pm_id', filter.constructionPMId); + if (filter?.startDate) params.append('start_date', filter.startDate); + if (filter?.endDate) params.append('end_date', filter.endDate); + if (filter?.page) params.append('page', String(filter.page)); + if (filter?.size) params.append('per_page', String(filter.size)); - // 검색 필터 - if (filter?.search) { - const search = filter.search.toLowerCase(); - filteredData = filteredData.filter( - (item) => - item.contractCode.toLowerCase().includes(search) || - item.partnerName.toLowerCase().includes(search) || - item.projectName.toLowerCase().includes(search) - ); + // 정렬 파라미터 변환 + if (filter?.sortBy) { + const sortMap: Record = { + contractDateDesc: { field: 'created_at', dir: 'desc' }, + contractDateAsc: { field: 'created_at', dir: 'asc' }, + partnerNameAsc: { field: 'partner_name', dir: 'asc' }, + partnerNameDesc: { field: 'partner_name', dir: 'desc' }, + projectNameAsc: { field: 'project_name', dir: 'asc' }, + projectNameDesc: { field: 'project_name', dir: 'desc' }, + amountDesc: { field: 'contract_amount', dir: 'desc' }, + amountAsc: { field: 'contract_amount', dir: 'asc' }, + }; + const sort = sortMap[filter.sortBy]; + if (sort) { + params.append('sort_by', sort.field); + params.append('sort_dir', sort.dir); + } } - // 상태 필터 - if (filter?.status && filter.status !== 'all') { - filteredData = filteredData.filter((item) => item.status === filter.status); + const queryString = params.toString(); + const endpoint = `/construction/contracts${queryString ? `?${queryString}` : ''}`; + + const result = await apiRequest<{ + data: Record[]; + current_page: number; + per_page: number; + total: number; + last_page: number; + }>(endpoint); + + if (!result.success || !result.data) { + return { success: false, error: result.error || '계약 목록 조회에 실패했습니다.' }; } - // 단계 필터 - if (filter?.stage && filter.stage !== 'all') { - filteredData = filteredData.filter((item) => item.stage === filter.stage); - } - - // 거래처 필터 - if (filter?.partnerId && filter.partnerId !== 'all') { - filteredData = filteredData.filter((item) => item.partnerId === filter.partnerId); - } - - // 계약담당자 필터 - if (filter?.contractManagerId && filter.contractManagerId !== 'all') { - filteredData = filteredData.filter((item) => item.contractManagerId === filter.contractManagerId); - } - - // 공사PM 필터 - if (filter?.constructionPMId && filter.constructionPMId !== 'all') { - filteredData = filteredData.filter((item) => item.constructionPMId === filter.constructionPMId); - } - - // 날짜 필터 - if (filter?.startDate) { - filteredData = filteredData.filter( - (item) => item.contractStartDate && item.contractStartDate >= filter.startDate! - ); - } - if (filter?.endDate) { - filteredData = filteredData.filter( - (item) => item.contractEndDate && item.contractEndDate <= filter.endDate! - ); - } - - // 정렬 - const sortBy = filter?.sortBy || 'contractDateDesc'; - switch (sortBy) { - case 'contractDateDesc': - filteredData.sort((a, b) => { - if (!a.contractStartDate) return 1; - if (!b.contractStartDate) return -1; - return new Date(b.contractStartDate).getTime() - new Date(a.contractStartDate).getTime(); - }); - break; - case 'contractDateAsc': - filteredData.sort((a, b) => { - if (!a.contractStartDate) return 1; - if (!b.contractStartDate) return -1; - return new Date(a.contractStartDate).getTime() - new Date(b.contractStartDate).getTime(); - }); - break; - case 'partnerNameAsc': - filteredData.sort((a, b) => a.partnerName.localeCompare(b.partnerName, 'ko')); - break; - case 'partnerNameDesc': - filteredData.sort((a, b) => b.partnerName.localeCompare(a.partnerName, 'ko')); - break; - case 'projectNameAsc': - filteredData.sort((a, b) => a.projectName.localeCompare(b.projectName, 'ko')); - break; - case 'projectNameDesc': - filteredData.sort((a, b) => b.projectName.localeCompare(a.projectName, 'ko')); - break; - case 'amountDesc': - filteredData.sort((a, b) => b.contractAmount - a.contractAmount); - break; - case 'amountAsc': - filteredData.sort((a, b) => a.contractAmount - b.contractAmount); - break; - } - - // 페이지네이션 - const page = filter?.page || 1; - const size = filter?.size || 20; - const startIndex = (page - 1) * size; - const paginatedData = filteredData.slice(startIndex, startIndex + size); + const apiData = result.data; + const items = (apiData.data || []).map(transformContract); return { success: true, data: { - items: paginatedData, - total: filteredData.length, - page, - size, - totalPages: Math.ceil(filteredData.length / size), + items, + total: apiData.total || 0, + page: apiData.current_page || 1, + size: apiData.per_page || 20, + totalPages: apiData.last_page || 1, }, }; } catch (error) { @@ -339,85 +202,214 @@ export async function getContractList(filter?: ContractFilter): Promise<{ } } -// 계약 통계 조회 +/** + * 계약 통계 조회 + */ export async function getContractStats(): Promise<{ success: boolean; data?: ContractStats; error?: string; }> { try { - await new Promise((resolve) => setTimeout(resolve, 100)); + const result = await apiRequest<{ + total_count: number; + pending_count: number; + completed_count: number; + }>('/construction/contracts/stats'); - const stats: ContractStats = { - total: MOCK_CONTRACTS.length, - pending: MOCK_CONTRACTS.filter((c) => c.status === 'pending').length, - completed: MOCK_CONTRACTS.filter((c) => c.status === 'completed').length, + if (!result.success || !result.data) { + return { success: false, error: result.error || '통계를 불러오는데 실패했습니다.' }; + } + + return { + success: true, + data: { + total: result.data.total_count || 0, + pending: result.data.pending_count || 0, + completed: result.data.completed_count || 0, + }, }; - - return { success: true, data: stats }; } catch (error) { console.error('getContractStats error:', error); return { success: false, error: '통계를 불러오는데 실패했습니다.' }; } } -// 단계별 건수 조회 +/** + * 단계별 건수 조회 + */ export async function getContractStageCounts(): Promise<{ success: boolean; data?: ContractStageCount; error?: string; }> { try { - await new Promise((resolve) => setTimeout(resolve, 100)); + const result = await apiRequest<{ + estimate_selected: number; + estimate_progress: number; + delivery: number; + installation: number; + inspection: number; + other: number; + }>('/construction/contracts/stage-counts'); - const counts: ContractStageCount = { - estimateSelected: MOCK_CONTRACTS.filter((c) => c.stage === 'estimate_selected').length, - estimateProgress: MOCK_CONTRACTS.filter((c) => c.stage === 'estimate_progress').length, - delivery: MOCK_CONTRACTS.filter((c) => c.stage === 'delivery').length, - installation: MOCK_CONTRACTS.filter((c) => c.stage === 'installation').length, - inspection: MOCK_CONTRACTS.filter((c) => c.stage === 'inspection').length, - other: MOCK_CONTRACTS.filter((c) => c.stage === 'other').length, + if (!result.success || !result.data) { + return { success: false, error: result.error || '단계별 건수를 불러오는데 실패했습니다.' }; + } + + return { + success: true, + data: { + estimateSelected: result.data.estimate_selected || 0, + estimateProgress: result.data.estimate_progress || 0, + delivery: result.data.delivery || 0, + installation: result.data.installation || 0, + inspection: result.data.inspection || 0, + other: result.data.other || 0, + }, }; - - return { success: true, data: counts }; } catch (error) { console.error('getContractStageCounts error:', error); return { success: false, error: '단계별 건수를 불러오는데 실패했습니다.' }; } } -// 계약 단건 조회 +/** + * 계약 단건 조회 + */ export async function getContract(id: string): Promise<{ success: boolean; data?: Contract; error?: string; }> { try { - await new Promise((resolve) => setTimeout(resolve, 200)); + const result = await apiRequest>(`/construction/contracts/${id}`); - const contract = MOCK_CONTRACTS.find((c) => c.id === id); - if (!contract) { - return { success: false, error: '계약 정보를 찾을 수 없습니다.' }; + if (!result.success || !result.data) { + return { success: false, error: result.error || '계약 정보를 찾을 수 없습니다.' }; } - return { success: true, data: contract }; + return { success: true, data: transformContract(result.data) }; } catch (error) { console.error('getContract error:', error); return { success: false, error: '계약 정보를 불러오는데 실패했습니다.' }; } } -// 계약 삭제 +/** + * 계약 상세 조회 (첨부파일 포함) + */ +export async function getContractDetail(id: string): Promise<{ + success: boolean; + data?: ContractDetail; + error?: string; +}> { + try { + const result = await apiRequest>(`/construction/contracts/${id}`); + + if (!result.success || !result.data) { + return { success: false, error: result.error || '계약 정보를 찾을 수 없습니다.' }; + } + + const contract = transformContract(result.data); + + // 첨부파일 정보 변환 (API에서 반환하는 경우) + const contractFile = result.data.contract_file as Record | null; + const attachmentsData = result.data.attachments as Record[] | undefined; + + const detail: ContractDetail = { + ...contract, + contractFile: contractFile ? { + id: String(contractFile.id || ''), + fileName: String(contractFile.file_name || contractFile.fileName || ''), + fileUrl: String(contractFile.file_url || contractFile.fileUrl || ''), + uploadedAt: String(contractFile.uploaded_at || contractFile.uploadedAt || ''), + } : null, + attachments: (attachmentsData || []).map((att) => ({ + id: String(att.id || ''), + fileName: String(att.file_name || att.fileName || ''), + fileSize: Number(att.file_size || att.fileSize || 0), + fileUrl: String(att.file_url || att.fileUrl || ''), + uploadedAt: String(att.uploaded_at || att.uploadedAt || ''), + })), + }; + + return { success: true, data: detail }; + } catch (error) { + console.error('getContractDetail error:', error); + return { success: false, error: '계약 상세 정보를 불러오는데 실패했습니다.' }; + } +} + +/** + * 계약 등록 + */ +export async function createContract( + data: ContractFormData +): Promise<{ success: boolean; data?: Contract; error?: string }> { + try { + const apiData = transformToApiRequest(data); + + const result = await apiRequest>('/construction/contracts', { + method: 'POST', + body: JSON.stringify(apiData), + }); + + if (!result.success || !result.data) { + return { success: false, error: result.error || '계약 등록에 실패했습니다.' }; + } + + return { success: true, data: transformContract(result.data) }; + } catch (error) { + console.error('createContract error:', error); + return { success: false, error: '계약 등록에 실패했습니다.' }; + } +} + +/** + * 계약 수정 + */ +export async function updateContract( + id: string, + data: Partial +): Promise<{ + success: boolean; + data?: Contract; + error?: string; +}> { + try { + const apiData = transformToApiRequest(data); + + const result = await apiRequest>(`/construction/contracts/${id}`, { + method: 'PUT', + body: JSON.stringify(apiData), + }); + + if (!result.success || !result.data) { + return { success: false, error: result.error || '계약 수정에 실패했습니다.' }; + } + + return { success: true, data: transformContract(result.data) }; + } catch (error) { + console.error('updateContract error:', error); + return { success: false, error: '계약 수정에 실패했습니다.' }; + } +} + +/** + * 계약 삭제 + */ export async function deleteContract(id: string): Promise<{ success: boolean; error?: string; }> { try { - await new Promise((resolve) => setTimeout(resolve, 300)); + const result = await apiRequest(`/construction/contracts/${id}`, { + method: 'DELETE', + }); - const index = MOCK_CONTRACTS.findIndex((c) => c.id === id); - if (index === -1) { - return { success: false, error: '계약 정보를 찾을 수 없습니다.' }; + if (!result.success) { + return { success: false, error: result.error || '계약 삭제에 실패했습니다.' }; } return { success: true }; @@ -427,91 +419,27 @@ export async function deleteContract(id: string): Promise<{ } } -// 계약 일괄 삭제 +/** + * 계약 일괄 삭제 + */ export async function deleteContracts(ids: string[]): Promise<{ success: boolean; deletedCount?: number; error?: string; }> { try { - await new Promise((resolve) => setTimeout(resolve, 500)); + const result = await apiRequest('/construction/contracts/bulk', { + method: 'DELETE', + body: JSON.stringify({ ids: ids.map(id => Number(id)) }), + }); + + if (!result.success) { + return { success: false, error: result.error || '일괄 삭제에 실패했습니다.' }; + } return { success: true, deletedCount: ids.length }; } catch (error) { console.error('deleteContracts error:', error); return { success: false, error: '일괄 삭제에 실패했습니다.' }; } -} - -// 계약 상세 조회 (첨부파일 포함) -export async function getContractDetail(id: string): Promise<{ - success: boolean; - data?: ContractDetail; - error?: string; -}> { - try { - await new Promise((resolve) => setTimeout(resolve, 200)); - - const contract = MOCK_CONTRACTS.find((c) => c.id === id); - if (!contract) { - return { success: false, error: '계약 정보를 찾을 수 없습니다.' }; - } - - // ContractDetail로 변환 (첨부파일 목데이터 포함) - const contractDetail: ContractDetail = { - ...contract, - // 계약서 파일 목업 데이터 - contractFile: { - id: '100', - fileName: '계약서_CT-2025-001.pdf', - fileUrl: '/files/contract.pdf', - uploadedAt: contract.createdAt, - }, - attachments: [ - { - id: 'att-1', - fileName: '견적서.pdf', - fileSize: 1024000, - fileUrl: '/files/estimate.pdf', - uploadedAt: contract.createdAt, - }, - { - id: 'att-2', - fileName: '시방서.pdf', - fileSize: 2048000, - fileUrl: '/files/spec.pdf', - uploadedAt: contract.createdAt, - }, - ], - }; - - return { success: true, data: contractDetail }; - } catch (error) { - console.error('getContractDetail error:', error); - return { success: false, error: '계약 상세 정보를 불러오는데 실패했습니다.' }; - } -} - -// 계약 수정 -export async function updateContract( - id: string, - _data: Partial -): Promise<{ - success: boolean; - error?: string; -}> { - try { - await new Promise((resolve) => setTimeout(resolve, 500)); - - const index = MOCK_CONTRACTS.findIndex((c) => c.id === id); - if (index === -1) { - return { success: false, error: '계약 정보를 찾을 수 없습니다.' }; - } - - // TODO: 실제 API 연동 시 데이터 업데이트 로직 - return { success: true }; - } catch (error) { - console.error('updateContract error:', error); - return { success: false, error: '계약 수정에 실패했습니다.' }; - } } \ No newline at end of file