refactor(construction): 건설관리 3개 모듈 apiClient 표준화

- contract/actions.ts: 커스텀 apiRequest → apiClient 변환
- partners/actions.ts: 커스텀 apiRequest → apiClient 변환
- site-management/actions.ts: 커스텀 apiRequest → apiClient 변환

공통 변경사항:
- cookies() 직접 import 제거
- API_BASE_URL, API_KEY 상수 제거
- import { apiClient } from '@/lib/api' 사용
- 명시적 API 타입 정의 추가 (ApiContract, ApiPartner, ApiSite 등)
This commit is contained in:
2026-01-09 19:21:34 +09:00
parent dcd79a2863
commit 5db6e59bbc
4 changed files with 532 additions and 623 deletions

View File

@@ -1,5 +1,67 @@
# SAM React 작업 현황 # SAM React 작업 현황
## 2026-01-09 (목) - Phase 1.3-1.5 건설관리 apiClient 표준화
### 작업 목표
- 건설관리 모듈의 커스텀 `apiRequest` 함수를 표준 `apiClient` 패턴으로 변환
- Phase 1.3: 계약관리(contract), Phase 1.4: 거래처관리(partners), Phase 1.5: 현장관리(site-management)
### 수정된 파일
| 파일명 | 설명 |
|--------|------|
| `src/components/business/construction/contract/actions.ts` | 커스텀 apiRequest → apiClient 표준화 |
| `src/components/business/construction/partners/actions.ts` | 커스텀 apiRequest → apiClient 표준화 |
| `src/components/business/construction/site-management/actions.ts` | 커스텀 apiRequest → apiClient 표준화 |
### 주요 변경 내용
#### 1. 제거된 코드 (각 파일에서)
- 커스텀 `apiRequest()` 함수 전체
- `import { cookies } from 'next/headers'`
- `const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL`
- `const API_KEY = process.env.API_KEY`
#### 2. 추가된 코드
- `import { apiClient } from '@/lib/api'`
- 명시적 API 타입 정의:
- **contract**: `ApiContract`, `ApiContractFile`, `ApiAttachment`, `ApiContractStats`, `ApiContractStageCount`
- **partners**: `ApiPartner`, `ApiPartnerStats`
- **site-management**: `ApiSite`, `ApiSiteStats`
#### 3. API 엔드포인트 (변경 없음)
**계약관리 (contract)**
- `GET /construction/contracts` - 목록
- `GET /construction/contracts/stats` - 통계
- `GET /construction/contracts/stage-counts` - 단계별 건수
- `GET /construction/contracts/{id}` - 상세
- `POST /construction/contracts` - 등록
- `PUT /construction/contracts/{id}` - 수정
- `DELETE /construction/contracts/{id}` - 삭제
- `DELETE /construction/contracts/bulk` - 일괄 삭제
**거래처관리 (partners)**
- `GET /clients` - 목록
- `GET /clients/stats` - 통계
- `GET /clients/{id}` - 상세
- `POST /clients` - 등록
- `PUT /clients/{id}` - 수정
- `DELETE /clients/{id}` - 삭제
- `DELETE /clients/bulk` - 일괄 삭제
**현장관리 (site-management)**
- `GET /sites` - 목록
- `GET /sites/stats` - 통계
- `DELETE /sites/{id}` - 삭제
- `DELETE /sites/bulk` - 일괄 삭제
### 빌드 검증
✅ Next.js 빌드 성공 (349 페이지)
### Git 커밋
- 대기 중
---
## 2026-01-09 (목) - Phase 1.2 인수인계보고서 API 표준화 ## 2026-01-09 (목) - Phase 1.2 인수인계보고서 API 표준화
### 작업 목표 ### 작업 목표
@@ -34,6 +96,9 @@
### 빌드 검증 ### 빌드 검증
✅ Next.js 빌드 성공 (349 페이지) ✅ Next.js 빌드 성공 (349 페이지)
### Git 커밋
- React: `b7b8b90` refactor(handover-report): 커스텀 fetch → apiClient 표준화
--- ---
## 2026-01-09 (목) - Phase 2.4 수주관리 API 연동 ## 2026-01-09 (목) - Phase 2.4 수주관리 API 연동

View File

@@ -1,6 +1,5 @@
'use server'; 'use server';
import { cookies } from 'next/headers';
import type { import type {
Contract, Contract,
ContractDetail, ContractDetail,
@@ -10,103 +9,134 @@ import type {
ContractFilter, ContractFilter,
ContractFormData, ContractFormData,
} from './types'; } from './types';
import { apiClient } from '@/lib/api';
/** /**
* 주일 기업 - 계약관리 Server Actions * 주일 기업 - 계약관리 Server Actions
* API 연동 버전 * 표준화된 apiClient 사용 버전
*/ */
// API 기본 URL // ========================================
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://api.sam.kr'; // API 응답 타입
const API_KEY = process.env.API_KEY || ''; // ========================================
/** interface ApiContract {
* API 요청 헬퍼 함수 id: number;
*/ contract_code: string;
async function apiRequest<T>( partner_id: number | null;
endpoint: string, partner_name: string | null;
options: RequestInit = {} project_name: string;
): Promise<{ success: boolean; data?: T; error?: string; message?: string }> { contract_manager_id: number | null;
try { contract_manager_name: string | null;
const cookieStore = await cookies(); construction_pm_id: number | null;
const accessToken = cookieStore.get('access_token')?.value; construction_pm_name: string | null;
total_locations: number;
const headers: Record<string, string> = { contract_amount: number;
'Accept': 'application/json', contract_start_date: string | null;
'Content-Type': 'application/json', contract_end_date: string | null;
'X-API-KEY': API_KEY, status: 'pending' | 'completed';
}; stage: string;
remarks: string | null;
if (accessToken) { created_at: string;
headers['Authorization'] = `Bearer ${accessToken}`; updated_at: string;
} created_by: string | null;
bidding_id: number | null;
const url = `${API_BASE_URL}/api/v1${endpoint}`; bidding_code: string | null;
console.log('🔵 [Contract API]', options.method || 'GET', url); contract_file?: ApiContractFile | null;
attachments?: ApiAttachment[];
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 : '알 수 없는 오류가 발생했습니다.',
};
}
} }
interface ApiContractFile {
id: number;
file_name: string;
file_url: string;
uploaded_at: string;
}
interface ApiAttachment {
id: number;
file_name: string;
file_size: number;
file_url: string;
uploaded_at: string;
}
interface ApiContractStats {
total_count: number;
pending_count: number;
completed_count: number;
}
interface ApiContractStageCount {
estimate_selected: number;
estimate_progress: number;
delivery: number;
installation: number;
inspection: number;
other: number;
}
// ========================================
// 타입 변환 함수
// ========================================
/** /**
* API 응답 → 프론트엔드 타입 변환 * API 응답 → Contract 타입 변환
*/ */
function transformContract(apiData: Record<string, unknown>): Contract { function transformContract(apiData: ApiContract): Contract {
return { return {
id: String(apiData.id), id: String(apiData.id),
contractCode: String(apiData.contract_code || ''), contractCode: apiData.contract_code || '',
partnerId: String(apiData.partner_id || ''), partnerId: apiData.partner_id ? String(apiData.partner_id) : '',
partnerName: String(apiData.partner_name || ''), partnerName: apiData.partner_name || '',
projectName: String(apiData.project_name || ''), projectName: apiData.project_name || '',
contractManagerId: String(apiData.contract_manager_id || ''), contractManagerId: apiData.contract_manager_id ? String(apiData.contract_manager_id) : '',
contractManagerName: String(apiData.contract_manager_name || ''), contractManagerName: apiData.contract_manager_name || '',
constructionPMId: String(apiData.construction_pm_id || ''), constructionPMId: apiData.construction_pm_id ? String(apiData.construction_pm_id) : '',
constructionPMName: String(apiData.construction_pm_name || ''), constructionPMName: apiData.construction_pm_name || '',
totalLocations: Number(apiData.total_locations || 0), totalLocations: apiData.total_locations || 0,
contractAmount: Number(apiData.contract_amount || 0), contractAmount: apiData.contract_amount || 0,
contractStartDate: apiData.contract_start_date ? String(apiData.contract_start_date) : null, contractStartDate: apiData.contract_start_date || null,
contractEndDate: apiData.contract_end_date ? String(apiData.contract_end_date) : null, contractEndDate: apiData.contract_end_date || null,
status: (apiData.status as 'pending' | 'completed') || 'pending', status: apiData.status || 'pending',
stage: (apiData.stage as Contract['stage']) || 'estimate_selected', stage: (apiData.stage as Contract['stage']) || 'estimate_selected',
remarks: String(apiData.remarks || ''), remarks: apiData.remarks || '',
createdAt: String(apiData.created_at || ''), createdAt: apiData.created_at || '',
updatedAt: String(apiData.updated_at || ''), updatedAt: apiData.updated_at || '',
createdBy: String(apiData.created_by || ''), createdBy: apiData.created_by || '',
biddingId: String(apiData.bidding_id || ''), biddingId: apiData.bidding_id ? String(apiData.bidding_id) : '',
biddingCode: String(apiData.bidding_code || ''), biddingCode: apiData.bidding_code || '',
}; };
} }
/** /**
* 프론트엔드 → API 요청 타입 변환 * API 응답 → ContractDetail 타입 변환
*/
function transformContractDetail(apiData: ApiContract): ContractDetail {
const contract = transformContract(apiData);
return {
...contract,
contractFile: apiData.contract_file
? {
id: String(apiData.contract_file.id),
fileName: apiData.contract_file.file_name || '',
fileUrl: apiData.contract_file.file_url || '',
uploadedAt: apiData.contract_file.uploaded_at || '',
}
: null,
attachments: (apiData.attachments || []).map((att) => ({
id: String(att.id),
fileName: att.file_name || '',
fileSize: att.file_size || 0,
fileUrl: att.file_url || '',
uploadedAt: att.uploaded_at || '',
})),
};
}
/**
* ContractFormData → API 요청 데이터 변환
*/ */
function transformToApiRequest(data: Partial<ContractFormData>): Record<string, unknown> { function transformToApiRequest(data: Partial<ContractFormData>): Record<string, unknown> {
const apiData: Record<string, unknown> = {}; const apiData: Record<string, unknown> = {};
@@ -127,8 +157,13 @@ function transformToApiRequest(data: Partial<ContractFormData>): Record<string,
return apiData; return apiData;
} }
// ========================================
// API 함수
// ========================================
/** /**
* 계약 목록 조회 * 계약 목록 조회
* GET /api/v1/construction/contracts
*/ */
export async function getContractList(filter?: ContractFilter): Promise<{ export async function getContractList(filter?: ContractFilter): Promise<{
success: boolean; success: boolean;
@@ -136,20 +171,31 @@ export async function getContractList(filter?: ContractFilter): Promise<{
error?: string; error?: string;
}> { }> {
try { try {
const params = new URLSearchParams(); const queryParams: Record<string, string> = {};
if (filter?.search) params.append('search', filter.search); // 검색
if (filter?.status && filter.status !== 'all') params.append('status', filter.status); if (filter?.search) queryParams.search = filter.search;
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?.status && filter.status !== 'all') queryParams.status = filter.status;
if (filter?.stage && filter.stage !== 'all') queryParams.stage = filter.stage;
if (filter?.partnerId && filter.partnerId !== 'all') queryParams.partner_id = filter.partnerId;
if (filter?.contractManagerId && filter.contractManagerId !== 'all') {
queryParams.contract_manager_id = filter.contractManagerId;
}
if (filter?.constructionPMId && filter.constructionPMId !== 'all') {
queryParams.construction_pm_id = filter.constructionPMId;
}
// 날짜 범위
if (filter?.startDate) queryParams.start_date = filter.startDate;
if (filter?.endDate) queryParams.end_date = filter.endDate;
// 페이지네이션
if (filter?.page) queryParams.page = String(filter.page);
if (filter?.size) queryParams.per_page = String(filter.size);
// 정렬
if (filter?.sortBy) { if (filter?.sortBy) {
const sortMap: Record<string, { field: string; dir: string }> = { const sortMap: Record<string, { field: string; dir: string }> = {
contractDateDesc: { field: 'created_at', dir: 'desc' }, contractDateDesc: { field: 'created_at', dir: 'desc' },
@@ -163,47 +209,40 @@ export async function getContractList(filter?: ContractFilter): Promise<{
}; };
const sort = sortMap[filter.sortBy]; const sort = sortMap[filter.sortBy];
if (sort) { if (sort) {
params.append('sort_by', sort.field); queryParams.sort_by = sort.field;
params.append('sort_dir', sort.dir); queryParams.sort_dir = sort.dir;
} }
} }
const queryString = params.toString(); const response = await apiClient.get<{
const endpoint = `/construction/contracts${queryString ? `?${queryString}` : ''}`; data: ApiContract[];
const result = await apiRequest<{
data: Record<string, unknown>[];
current_page: number; current_page: number;
per_page: number; per_page: number;
total: number; total: number;
last_page: number; last_page: number;
}>(endpoint); }>('/construction/contracts', { params: queryParams });
if (!result.success || !result.data) { const items = (response.data || []).map(transformContract);
return { success: false, error: result.error || '계약 목록 조회에 실패했습니다.' };
}
const apiData = result.data;
const items = (apiData.data || []).map(transformContract);
return { return {
success: true, success: true,
data: { data: {
items, items,
total: apiData.total || 0, total: response.total || 0,
page: apiData.current_page || 1, page: response.current_page || 1,
size: apiData.per_page || 20, size: response.per_page || 20,
totalPages: apiData.last_page || 1, totalPages: response.last_page || 1,
}, },
}; };
} catch (error) { } catch (error) {
console.error('getContractList error:', error); console.error('계약 목록 조회 오류:', error);
return { success: false, error: '계약 목록을 불러오는데 실패했습니다.' }; return { success: false, error: '계약 목록을 불러오는데 실패했습니다.' };
} }
} }
/** /**
* 계약 통계 조회 * 계약 통계 조회
* GET /api/v1/construction/contracts/stats
*/ */
export async function getContractStats(): Promise<{ export async function getContractStats(): Promise<{
success: boolean; success: boolean;
@@ -211,32 +250,25 @@ export async function getContractStats(): Promise<{
error?: string; error?: string;
}> { }> {
try { try {
const result = await apiRequest<{ const response = await apiClient.get<ApiContractStats>('/construction/contracts/stats');
total_count: number;
pending_count: number;
completed_count: number;
}>('/construction/contracts/stats');
if (!result.success || !result.data) {
return { success: false, error: result.error || '통계를 불러오는데 실패했습니다.' };
}
return { return {
success: true, success: true,
data: { data: {
total: result.data.total_count || 0, total: response.total_count || 0,
pending: result.data.pending_count || 0, pending: response.pending_count || 0,
completed: result.data.completed_count || 0, completed: response.completed_count || 0,
}, },
}; };
} catch (error) { } catch (error) {
console.error('getContractStats error:', error); console.error('계약 통계 조회 오류:', error);
return { success: false, error: '통계를 불러오는데 실패했습니다.' }; return { success: false, error: '통계를 불러오는데 실패했습니다.' };
} }
} }
/** /**
* 단계별 건수 조회 * 단계별 건수 조회
* GET /api/v1/construction/contracts/stage-counts
*/ */
export async function getContractStageCounts(): Promise<{ export async function getContractStageCounts(): Promise<{
success: boolean; success: boolean;
@@ -244,38 +276,28 @@ export async function getContractStageCounts(): Promise<{
error?: string; error?: string;
}> { }> {
try { try {
const result = await apiRequest<{ const response = await apiClient.get<ApiContractStageCount>('/construction/contracts/stage-counts');
estimate_selected: number;
estimate_progress: number;
delivery: number;
installation: number;
inspection: number;
other: number;
}>('/construction/contracts/stage-counts');
if (!result.success || !result.data) {
return { success: false, error: result.error || '단계별 건수를 불러오는데 실패했습니다.' };
}
return { return {
success: true, success: true,
data: { data: {
estimateSelected: result.data.estimate_selected || 0, estimateSelected: response.estimate_selected || 0,
estimateProgress: result.data.estimate_progress || 0, estimateProgress: response.estimate_progress || 0,
delivery: result.data.delivery || 0, delivery: response.delivery || 0,
installation: result.data.installation || 0, installation: response.installation || 0,
inspection: result.data.inspection || 0, inspection: response.inspection || 0,
other: result.data.other || 0, other: response.other || 0,
}, },
}; };
} catch (error) { } catch (error) {
console.error('getContractStageCounts error:', error); console.error('단계별 건수 조회 오류:', error);
return { success: false, error: '단계별 건수를 불러오는데 실패했습니다.' }; return { success: false, error: '단계별 건수를 불러오는데 실패했습니다.' };
} }
} }
/** /**
* 계약 단건 조회 * 계약 단건 조회
* GET /api/v1/construction/contracts/{id}
*/ */
export async function getContract(id: string): Promise<{ export async function getContract(id: string): Promise<{
success: boolean; success: boolean;
@@ -283,21 +305,17 @@ export async function getContract(id: string): Promise<{
error?: string; error?: string;
}> { }> {
try { try {
const result = await apiRequest<Record<string, unknown>>(`/construction/contracts/${id}`); const response = await apiClient.get<ApiContract>(`/construction/contracts/${id}`);
return { success: true, data: transformContract(response) };
if (!result.success || !result.data) {
return { success: false, error: result.error || '계약 정보를 찾을 수 없습니다.' };
}
return { success: true, data: transformContract(result.data) };
} catch (error) { } catch (error) {
console.error('getContract error:', error); console.error('계약 조회 오류:', error);
return { success: false, error: '계약 정보를 불러오는데 실패했습니다.' }; return { success: false, error: '계약 정보를 찾을 수 없습니다.' };
} }
} }
/** /**
* 계약 상세 조회 (첨부파일 포함) * 계약 상세 조회 (첨부파일 포함)
* GET /api/v1/construction/contracts/{id}
*/ */
export async function getContractDetail(id: string): Promise<{ export async function getContractDetail(id: string): Promise<{
success: boolean; success: boolean;
@@ -305,69 +323,36 @@ export async function getContractDetail(id: string): Promise<{
error?: string; error?: string;
}> { }> {
try { try {
const result = await apiRequest<Record<string, unknown>>(`/construction/contracts/${id}`); const response = await apiClient.get<ApiContract>(`/construction/contracts/${id}`);
return { success: true, data: transformContractDetail(response) };
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<string, unknown> | null;
const attachmentsData = result.data.attachments as Record<string, unknown>[] | 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) { } catch (error) {
console.error('getContractDetail error:', error); console.error('계약 상세 조회 오류:', error);
return { success: false, error: '계약 상세 정보를 불러오는데 실패했습니다.' }; return { success: false, error: '계약 상세 정보를 불러오는데 실패했습니다.' };
} }
} }
/** /**
* 계약 등록 * 계약 등록
* POST /api/v1/construction/contracts
*/ */
export async function createContract( export async function createContract(data: ContractFormData): Promise<{
data: ContractFormData success: boolean;
): Promise<{ success: boolean; data?: Contract; error?: string }> { data?: Contract;
error?: string;
}> {
try { try {
const apiData = transformToApiRequest(data); const apiData = transformToApiRequest(data);
const response = await apiClient.post<ApiContract>('/construction/contracts', apiData);
const result = await apiRequest<Record<string, unknown>>('/construction/contracts', { return { success: true, data: transformContract(response) };
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) { } catch (error) {
console.error('createContract error:', error); console.error('계약 등록 오류:', error);
return { success: false, error: '계약 등록에 실패했습니다.' }; return { success: false, error: '계약 등록에 실패했습니다.' };
} }
} }
/** /**
* 계약 수정 * 계약 수정
* PUT /api/v1/construction/contracts/{id}
*/ */
export async function updateContract( export async function updateContract(
id: string, id: string,
@@ -379,48 +364,34 @@ export async function updateContract(
}> { }> {
try { try {
const apiData = transformToApiRequest(data); const apiData = transformToApiRequest(data);
const response = await apiClient.put<ApiContract>(`/construction/contracts/${id}`, apiData);
const result = await apiRequest<Record<string, unknown>>(`/construction/contracts/${id}`, { return { success: true, data: transformContract(response) };
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) { } catch (error) {
console.error('updateContract error:', error); console.error('계약 수정 오류:', error);
return { success: false, error: '계약 수정에 실패했습니다.' }; return { success: false, error: '계약 수정에 실패했습니다.' };
} }
} }
/** /**
* 계약 삭제 * 계약 삭제
* DELETE /api/v1/construction/contracts/{id}
*/ */
export async function deleteContract(id: string): Promise<{ export async function deleteContract(id: string): Promise<{
success: boolean; success: boolean;
error?: string; error?: string;
}> { }> {
try { try {
const result = await apiRequest(`/construction/contracts/${id}`, { await apiClient.delete(`/construction/contracts/${id}`);
method: 'DELETE',
});
if (!result.success) {
return { success: false, error: result.error || '계약 삭제에 실패했습니다.' };
}
return { success: true }; return { success: true };
} catch (error) { } catch (error) {
console.error('deleteContract error:', error); console.error('계약 삭제 오류:', error);
return { success: false, error: '계약 삭제에 실패했습니다.' }; return { success: false, error: '계약 삭제에 실패했습니다.' };
} }
} }
/** /**
* 계약 일괄 삭제 * 계약 일괄 삭제
* DELETE /api/v1/construction/contracts/bulk
*/ */
export async function deleteContracts(ids: string[]): Promise<{ export async function deleteContracts(ids: string[]): Promise<{
success: boolean; success: boolean;
@@ -428,18 +399,12 @@ export async function deleteContracts(ids: string[]): Promise<{
error?: string; error?: string;
}> { }> {
try { try {
const result = await apiRequest('/construction/contracts/bulk', { await apiClient.delete('/construction/contracts/bulk', {
method: 'DELETE', data: { ids: ids.map((id) => Number(id)) },
body: JSON.stringify({ ids: ids.map(id => Number(id)) }),
}); });
if (!result.success) {
return { success: false, error: result.error || '일괄 삭제에 실패했습니다.' };
}
return { success: true, deletedCount: ids.length }; return { success: true, deletedCount: ids.length };
} catch (error) { } catch (error) {
console.error('deleteContracts error:', error); console.error('계약 일괄 삭제 오류:', error);
return { success: false, error: '일괄 삭제에 실패했습니다.' }; return { success: false, error: '일괄 삭제에 실패했습니다.' };
} }
} }

View File

@@ -1,81 +1,63 @@
'use server'; 'use server';
import { cookies } from 'next/headers';
import type { Partner, PartnerStats, PartnerFilter, PartnerListResponse, PartnerFormData } from './types'; import type { Partner, PartnerStats, PartnerFilter, PartnerListResponse, PartnerFormData } from './types';
import { apiClient } from '@/lib/api';
/** /**
* 주일 기업 - 거래처 관리 Server Actions * 주일 기업 - 거래처 관리 Server Actions
* API 연동 버전 * 표준화된 apiClient 사용 버전
*/ */
// API 기본 URL // ========================================
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://api.sam.kr'; // API 응답 타입
const API_KEY = process.env.API_KEY || ''; // ========================================
/** interface ApiPartner {
* API 요청 헬퍼 함수 id: number;
*/ client_code: string | null;
async function apiRequest<T>( business_no: string | null;
endpoint: string, name: string;
options: RequestInit = {} contact_person: string | null;
): Promise<{ success: boolean; data?: T; error?: string; message?: string }> { client_type: string | null;
try { business_type: string | null;
const cookieStore = await cookies(); business_item: string | null;
const accessToken = cookieStore.get('access_token')?.value; address: string | null;
phone: string | null;
const headers: Record<string, string> = { mobile: string | null;
'Accept': 'application/json', fax: string | null;
'Content-Type': 'application/json', email: string | null;
'X-API-KEY': API_KEY, manager_name: string | null;
}; manager_tel: string | null;
system_manager: string | null;
if (accessToken) { outstanding_amount: number | null;
headers['Authorization'] = `Bearer ${accessToken}`; is_overdue: boolean;
} has_bad_debt: boolean;
is_active: boolean;
const url = `${API_BASE_URL}/api/v1${endpoint}`; created_at: string;
console.log('🔵 [Partner API]', options.method || 'GET', url); updated_at: string;
const response = await fetch(url, {
...options,
headers: {
...headers,
...options.headers,
},
});
const result = await response.json();
console.log('🔵 [Partner 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 : '알 수 없는 오류가 발생했습니다.',
};
}
} }
interface ApiPartnerStats {
total: number;
sales: number;
purchase: number;
both: number;
badDebt: number;
normal: number;
}
// ========================================
// 타입 변환 함수
// ========================================
/** /**
* client_type API → Frontend partnerType 변환 * client_type API → Frontend partnerType 변환
*/ */
function transformClientType(clientType: string | null | undefined): Partner['partnerType'] { function transformClientType(clientType: string | null | undefined): Partner['partnerType'] {
const typeMap: Record<string, Partner['partnerType']> = { const typeMap: Record<string, Partner['partnerType']> = {
'SALES': 'sales', SALES: 'sales',
'PURCHASE': 'purchase', PURCHASE: 'purchase',
'BOTH': 'both', BOTH: 'both',
}; };
return typeMap[clientType || ''] || 'sales'; return typeMap[clientType || ''] || 'sales';
} }
@@ -85,59 +67,59 @@ function transformClientType(clientType: string | null | undefined): Partner['pa
*/ */
function transformPartnerType(partnerType: Partner['partnerType']): string { function transformPartnerType(partnerType: Partner['partnerType']): string {
const typeMap: Record<Partner['partnerType'], string> = { const typeMap: Record<Partner['partnerType'], string> = {
'sales': 'SALES', sales: 'SALES',
'purchase': 'PURCHASE', purchase: 'PURCHASE',
'both': 'BOTH', both: 'BOTH',
}; };
return typeMap[partnerType] || 'SALES'; return typeMap[partnerType] || 'SALES';
} }
/** /**
* API 응답 → 프론트엔드 Partner 타입 변환 * API 응답 → Partner 타입 변환
*/ */
function transformPartner(apiData: Record<string, unknown>): Partner { function transformPartner(apiData: ApiPartner): Partner {
return { return {
id: String(apiData.id), id: String(apiData.id),
partnerCode: String(apiData.client_code || ''), partnerCode: apiData.client_code || '',
businessNumber: String(apiData.business_no || ''), businessNumber: apiData.business_no || '',
partnerName: String(apiData.name || ''), partnerName: apiData.name || '',
representative: String(apiData.contact_person || ''), representative: apiData.contact_person || '',
partnerType: transformClientType(apiData.client_type as string | null), partnerType: transformClientType(apiData.client_type),
businessType: String(apiData.business_type || ''), businessType: apiData.business_type || '',
businessCategory: String(apiData.business_item || ''), businessCategory: apiData.business_item || '',
zipCode: '', // API에 없는 필드 zipCode: '',
address1: String(apiData.address || ''), address1: apiData.address || '',
address2: '', // API에 없는 필드 address2: '',
phone: String(apiData.phone || ''), phone: apiData.phone || '',
mobile: String(apiData.mobile || ''), mobile: apiData.mobile || '',
fax: String(apiData.fax || ''), fax: apiData.fax || '',
email: String(apiData.email || ''), email: apiData.email || '',
manager: String(apiData.manager_name || ''), manager: apiData.manager_name || '',
managerPhone: String(apiData.manager_tel || ''), managerPhone: apiData.manager_tel || '',
systemManager: String(apiData.system_manager || ''), systemManager: apiData.system_manager || '',
logoUrl: null, // API에 없는 필드 logoUrl: null,
logoBlob: null, // API에 없는 필드 logoBlob: null,
salesPaymentDay: 0, // API에 없는 필드 salesPaymentDay: 0,
creditRating: '', // API에 없는 필드 creditRating: '',
transactionGrade: '', // API에 없는 필드 transactionGrade: '',
taxInvoiceEmail: String(apiData.email || ''), // 동일한 이메일 사용 taxInvoiceEmail: apiData.email || '',
outstandingAmount: Number(apiData.outstanding_amount || 0), outstandingAmount: apiData.outstanding_amount || 0,
overdueDays: apiData.is_overdue ? 30 : 0, // 연체 여부만 있음 overdueDays: apiData.is_overdue ? 30 : 0,
overdueToggle: Boolean(apiData.is_overdue), overdueToggle: apiData.is_overdue,
badDebtToggle: Boolean(apiData.has_bad_debt), badDebtToggle: apiData.has_bad_debt,
memos: [], // API에 없는 필드 memos: [],
documents: [], // API에 없는 필드 documents: [],
category: '', // API에 없는 필드 category: '',
paymentDay: 0, // API에 없는 필드 paymentDay: 0,
isBadDebt: Boolean(apiData.has_bad_debt), isBadDebt: apiData.has_bad_debt,
isActive: apiData.is_active !== false, isActive: apiData.is_active !== false,
createdAt: String(apiData.created_at || ''), createdAt: apiData.created_at || '',
updatedAt: String(apiData.updated_at || ''), updatedAt: apiData.updated_at || '',
}; };
} }
/** /**
* 프론트엔드 PartnerFormData → API 요청 데이터 변환 * PartnerFormData → API 요청 데이터 변환
*/ */
function transformPartnerToApi(data: PartnerFormData): Record<string, unknown> { function transformPartnerToApi(data: PartnerFormData): Record<string, unknown> {
return { return {
@@ -160,57 +142,45 @@ function transformPartnerToApi(data: PartnerFormData): Record<string, unknown> {
}; };
} }
// ============================================================ // ========================================
// API 연동 함수 // API 함수
// ============================================================ // ========================================
/** /**
* 거래처 목록 조회 * 거래처 목록 조회
* GET /api/v1/clients
*/ */
export async function getPartnerList( export async function getPartnerList(filter?: PartnerFilter): Promise<{
filter?: PartnerFilter success: boolean;
): Promise<{ success: boolean; data?: PartnerListResponse; error?: string }> { data?: PartnerListResponse;
error?: string;
}> {
try { try {
const queryParams = new URLSearchParams(); const queryParams: Record<string, string> = {};
// 검색어 // 검색어
if (filter?.search) { if (filter?.search) queryParams.q = filter.search;
queryParams.append('q', filter.search);
}
// 악성채권 필터 (Frontend badDebtFilter → 백엔드는 별도 필터 없음, 목록에서 처리)
// API는 전체 데이터 반환, 프론트에서 필터링
// 페이지네이션 // 페이지네이션
if (filter?.page) queryParams.append('page', String(filter.page)); if (filter?.page) queryParams.page = String(filter.page);
if (filter?.size) queryParams.append('size', String(filter.size)); if (filter?.size) queryParams.size = String(filter.size);
const queryString = queryParams.toString(); const response = await apiClient.get<{
const endpoint = `/clients${queryString ? `?${queryString}` : ''}`; data: ApiPartner[];
const result = await apiRequest<{
data: Record<string, unknown>[];
current_page: number; current_page: number;
per_page: number; per_page: number;
total: number; total: number;
last_page: number; last_page: number;
}>(endpoint); }>('/clients', { params: queryParams });
if (!result.success || !result.data) { let items = (response.data || []).map(transformPartner);
return { success: false, error: result.error || '거래처 목록 조회에 실패했습니다.' };
}
const apiData = result.data;
let items = (apiData.data || []).map(transformPartner);
// 악성채권 필터 (프론트엔드에서 처리) // 악성채권 필터 (프론트엔드에서 처리)
if (filter?.badDebtFilter && filter.badDebtFilter !== 'all') { if (filter?.badDebtFilter && filter.badDebtFilter !== 'all') {
items = items.filter((p) => items = items.filter((p) => (filter.badDebtFilter === 'badDebt' ? p.isBadDebt : !p.isBadDebt));
filter.badDebtFilter === 'badDebt' ? p.isBadDebt : !p.isBadDebt
);
} }
// 정렬 (프론트엔드에서 처리 - API가 sort 미지원 시) // 정렬 (프론트엔드에서 처리)
if (filter?.sortBy) { if (filter?.sortBy) {
switch (filter.sortBy) { switch (filter.sortBy) {
case 'latest': case 'latest':
@@ -232,162 +202,134 @@ export async function getPartnerList(
success: true, success: true,
data: { data: {
items, items,
total: apiData.total || 0, total: response.total || 0,
page: apiData.current_page || 1, page: response.current_page || 1,
size: apiData.per_page || 20, size: response.per_page || 20,
totalPages: apiData.last_page || 1, totalPages: response.last_page || 1,
}, },
}; };
} catch (error) { } catch (error) {
console.error('getPartnerList error:', error); console.error('거래처 목록 조회 오류:', error);
return { success: false, error: '거래처 목록 조회에 실패했습니다.' }; return { success: false, error: '거래처 목록을 불러오는데 실패했습니다.' };
} }
} }
/** /**
* 거래처 상세 조회 * 거래처 상세 조회
* GET /api/v1/clients/{id}
*/ */
export async function getPartner( export async function getPartner(id: string): Promise<{
id: string success: boolean;
): Promise<{ success: boolean; data?: Partner; error?: string }> { data?: Partner;
error?: string;
}> {
try { try {
const result = await apiRequest<Record<string, unknown>>(`/clients/${id}`); const response = await apiClient.get<ApiPartner>(`/clients/${id}`);
return { success: true, data: transformPartner(response) };
if (!result.success || !result.data) {
return { success: false, error: result.error || '거래처를 찾을 수 없습니다.' };
}
return { success: true, data: transformPartner(result.data) };
} catch (error) { } catch (error) {
console.error('getPartner error:', error); console.error('거래처 조회 오류:', error);
return { success: false, error: '거래처 조회에 실패했습니다.' }; return { success: false, error: '거래처를 찾을 수 없습니다.' };
} }
} }
/** /**
* 거래처 등록 * 거래처 등록
* POST /api/v1/clients
*/ */
export async function createPartner( export async function createPartner(data: PartnerFormData): Promise<{
data: PartnerFormData success: boolean;
): Promise<{ success: boolean; data?: Partner; error?: string }> { data?: Partner;
error?: string;
}> {
try { try {
const apiData = transformPartnerToApi(data); const apiData = transformPartnerToApi(data);
const response = await apiClient.post<ApiPartner>('/clients', apiData);
const result = await apiRequest<Record<string, unknown>>('/clients', { return { success: true, data: transformPartner(response) };
method: 'POST',
body: JSON.stringify(apiData),
});
if (!result.success || !result.data) {
return { success: false, error: result.error || '거래처 등록에 실패했습니다.' };
}
return { success: true, data: transformPartner(result.data) };
} catch (error) { } catch (error) {
console.error('createPartner error:', error); console.error('거래처 등록 오류:', error);
return { success: false, error: '거래처 등록에 실패했습니다.' }; return { success: false, error: '거래처 등록에 실패했습니다.' };
} }
} }
/** /**
* 거래처 수정 * 거래처 수정
* PUT /api/v1/clients/{id}
*/ */
export async function updatePartner( export async function updatePartner(id: string, data: PartnerFormData): Promise<{
id: string, success: boolean;
data: PartnerFormData data?: Partner;
): Promise<{ success: boolean; data?: Partner; error?: string }> { error?: string;
}> {
try { try {
const apiData = transformPartnerToApi(data); const apiData = transformPartnerToApi(data);
const response = await apiClient.put<ApiPartner>(`/clients/${id}`, apiData);
const result = await apiRequest<Record<string, unknown>>(`/clients/${id}`, { return { success: true, data: transformPartner(response) };
method: 'PUT',
body: JSON.stringify(apiData),
});
if (!result.success || !result.data) {
return { success: false, error: result.error || '거래처 수정에 실패했습니다.' };
}
return { success: true, data: transformPartner(result.data) };
} catch (error) { } catch (error) {
console.error('updatePartner error:', error); console.error('거래처 수정 오류:', error);
return { success: false, error: '거래처 수정에 실패했습니다.' }; return { success: false, error: '거래처 수정에 실패했습니다.' };
} }
} }
/** /**
* 거래처 통계 조회 * 거래처 통계 조회
* GET /api/v1/clients/stats
*/ */
export async function getPartnerStats(): Promise<{ success: boolean; data?: PartnerStats; error?: string }> { export async function getPartnerStats(): Promise<{
success: boolean;
data?: PartnerStats;
error?: string;
}> {
try { try {
const result = await apiRequest<{ const response = await apiClient.get<ApiPartnerStats>('/clients/stats');
total: number;
sales: number;
purchase: number;
both: number;
badDebt: number;
normal: number;
}>('/clients/stats');
if (!result.success || !result.data) {
return { success: false, error: result.error || '통계 조회에 실패했습니다.' };
}
return { return {
success: true, success: true,
data: { data: {
total: result.data.total || 0, total: response.total || 0,
unregistered: 0, // Client API에서 미지원 (거래처는 등록 완료 상태만) unregistered: 0,
badDebt: result.data.badDebt || 0, badDebt: response.badDebt || 0,
normal: result.data.normal || 0, normal: response.normal || 0,
}, },
}; };
} catch (error) { } catch (error) {
console.error('getPartnerStats error:', error); console.error('거래처 통계 조회 오류:', error);
return { success: false, error: '통계 조회에 실패했습니다.' }; return { success: false, error: '통계를 불러오는데 실패했습니다.' };
} }
} }
/** /**
* 거래처 삭제 * 거래처 삭제
* DELETE /api/v1/clients/{id}
*/ */
export async function deletePartner(id: string): Promise<{ success: boolean; error?: string }> { export async function deletePartner(id: string): Promise<{
success: boolean;
error?: string;
}> {
try { try {
const result = await apiRequest(`/clients/${id}`, { await apiClient.delete(`/clients/${id}`);
method: 'DELETE',
});
if (!result.success) {
return { success: false, error: result.error || '거래처 삭제에 실패했습니다.' };
}
return { success: true }; return { success: true };
} catch (error) { } catch (error) {
console.error('deletePartner error:', error); console.error('거래처 삭제 오류:', error);
return { success: false, error: '거래처 삭제에 실패했습니다.' }; return { success: false, error: '거래처 삭제에 실패했습니다.' };
} }
} }
/** /**
* 거래처 일괄 삭제 * 거래처 일괄 삭제
* DELETE /api/v1/clients/bulk
*/ */
export async function deletePartners(ids: string[]): Promise<{ success: boolean; deletedCount?: number; error?: string }> { export async function deletePartners(ids: string[]): Promise<{
success: boolean;
deletedCount?: number;
error?: string;
}> {
try { try {
const result = await apiRequest<{ deleted_count: number }>('/clients/bulk', { await apiClient.delete('/clients/bulk', {
method: 'DELETE', data: { ids: ids.map((id) => Number(id)) },
body: JSON.stringify({ ids: ids.map((id) => Number(id)) }),
}); });
return { success: true, deletedCount: ids.length };
if (!result.success) {
return { success: false, error: result.error || '일괄 삭제에 실패했습니다.' };
}
return {
success: true,
deletedCount: result.data?.deleted_count || ids.length,
};
} catch (error) { } catch (error) {
console.error('deletePartners error:', error); console.error('거래처 일괄 삭제 오류:', error);
return { success: false, error: '일괄 삭제에 실패했습니다.' }; return { success: false, error: '일괄 삭제에 실패했습니다.' };
} }
} }

View File

@@ -1,96 +1,64 @@
'use server'; 'use server';
import { cookies } from 'next/headers';
import type { Site, SiteStats, SiteStatus } from './types'; import type { Site, SiteStats, SiteStatus } from './types';
import { apiClient } from '@/lib/api';
/** /**
* 주일 기업 - 현장관리 Server Actions * 주일 기업 - 현장관리 Server Actions
* API 연동 버전 * 표준화된 apiClient 사용 버전
*/ */
// API 기본 URL // ========================================
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://api.sam.kr'; // API 응답 타입
const API_KEY = process.env.API_KEY || ''; // ========================================
/** interface ApiSite {
* API 요청 헬퍼 함수 id: number;
*/ site_code: string | null;
async function apiRequest<T>( client_id: number | null;
endpoint: string, name: string;
options: RequestInit = {} address: string | null;
): Promise<{ success: boolean; data?: T; error?: string; message?: string }> { status: SiteStatus;
try { created_at: string;
const cookieStore = await cookies(); updated_at: string;
const accessToken = cookieStore.get('access_token')?.value; client?: {
id: number;
const headers: Record<string, string> = { name: string;
'Accept': 'application/json', } | null;
'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('🔵 [Site API]', options.method || 'GET', url);
const response = await fetch(url, {
...options,
headers: {
...headers,
...options.headers,
},
});
const result = await response.json();
console.log('🔵 [Site 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 : '알 수 없는 오류가 발생했습니다.',
};
}
} }
/** interface ApiSiteStats {
* API 응답 → 프론트엔드 타입 변환 total: number;
*/ construction: number;
function transformSite(apiData: Record<string, unknown>): Site { unregistered: number;
// client 관계 데이터 추출 suspended: number;
const client = apiData.client as Record<string, unknown> | null | undefined; pending: number;
}
// ========================================
// 타입 변환 함수
// ========================================
/**
* API 응답 → Site 타입 변환
*/
function transformSite(apiData: ApiSite): Site {
return { return {
id: String(apiData.id), id: String(apiData.id),
siteCode: String(apiData.site_code || ''), siteCode: apiData.site_code || '',
partnerId: apiData.client_id ? String(apiData.client_id) : '', partnerId: apiData.client_id ? String(apiData.client_id) : '',
partnerName: client ? String(client.name || '') : '', partnerName: apiData.client?.name || '',
siteName: String(apiData.name || ''), siteName: apiData.name || '',
address: String(apiData.address || ''), address: apiData.address || '',
status: (apiData.status as SiteStatus) || 'unregistered', status: apiData.status || 'unregistered',
createdAt: String(apiData.created_at || ''), createdAt: apiData.created_at || '',
updatedAt: String(apiData.updated_at || ''), updatedAt: apiData.updated_at || '',
}; };
} }
// ============================================================ // ========================================
// API 연동 함수 // API 함수
// ============================================================ // ========================================
interface GetSiteListParams { interface GetSiteListParams {
size?: number; size?: number;
@@ -103,7 +71,11 @@ interface GetSiteListParams {
sortBy?: string; sortBy?: string;
} }
interface GetSiteListResult { /**
* 현장 목록 조회
* GET /api/v1/sites
*/
export async function getSiteList(params: GetSiteListParams = {}): Promise<{
success: boolean; success: boolean;
data?: { data?: {
items: Site[]; items: Site[];
@@ -113,24 +85,26 @@ interface GetSiteListResult {
totalPages: number; totalPages: number;
}; };
error?: string; error?: string;
} }> {
/**
* 현장 목록 조회
*/
export async function getSiteList(params: GetSiteListParams = {}): Promise<GetSiteListResult> {
try { try {
const queryParams = new URLSearchParams(); const queryParams: Record<string, string> = {};
if (params.search) queryParams.append('search', params.search); // 검색
if (params.status && params.status !== 'all') queryParams.append('status', params.status); if (params.search) queryParams.search = params.search;
if (params.clientId && params.clientId !== 'all') queryParams.append('client_id', params.clientId);
if (params.startDate) queryParams.append('start_date', params.startDate);
if (params.endDate) queryParams.append('end_date', params.endDate);
if (params.page) queryParams.append('page', String(params.page));
if (params.size) queryParams.append('per_page', String(params.size));
// 정렬 파라미터 변환 // 필터
if (params.status && params.status !== 'all') queryParams.status = params.status;
if (params.clientId && params.clientId !== 'all') queryParams.client_id = params.clientId;
// 날짜 범위
if (params.startDate) queryParams.start_date = params.startDate;
if (params.endDate) queryParams.end_date = params.endDate;
// 페이지네이션
if (params.page) queryParams.page = String(params.page);
if (params.size) queryParams.per_page = String(params.size);
// 정렬
if (params.sortBy) { if (params.sortBy) {
const sortMap: Record<string, { field: string; dir: string }> = { const sortMap: Record<string, { field: string; dir: string }> = {
latest: { field: 'created_at', dir: 'desc' }, latest: { field: 'created_at', dir: 'desc' },
@@ -142,135 +116,98 @@ export async function getSiteList(params: GetSiteListParams = {}): Promise<GetSi
}; };
const sort = sortMap[params.sortBy]; const sort = sortMap[params.sortBy];
if (sort) { if (sort) {
queryParams.append('sort_by', sort.field); queryParams.sort_by = sort.field;
queryParams.append('sort_dir', sort.dir); queryParams.sort_dir = sort.dir;
} }
} }
const queryString = queryParams.toString(); const response = await apiClient.get<{
const endpoint = `/sites${queryString ? `?${queryString}` : ''}`; data: ApiSite[];
const result = await apiRequest<{
data: Record<string, unknown>[];
current_page: number; current_page: number;
per_page: number; per_page: number;
total: number; total: number;
last_page: number; last_page: number;
}>(endpoint); }>('/sites', { params: queryParams });
if (!result.success || !result.data) { const items = (response.data || []).map(transformSite);
return { success: false, error: result.error || '현장 목록 조회에 실패했습니다.' };
}
const apiData = result.data;
const items = (apiData.data || []).map(transformSite);
return { return {
success: true, success: true,
data: { data: {
items, items,
total: apiData.total || 0, total: response.total || 0,
page: apiData.current_page || 1, page: response.current_page || 1,
size: apiData.per_page || 20, size: response.per_page || 20,
totalPages: apiData.last_page || 1, totalPages: response.last_page || 1,
}, },
}; };
} catch (error) { } catch (error) {
console.error('getSiteList error:', error); console.error('현장 목록 조회 오류:', error);
return { success: false, error: '현장 목록을 불러오는데 실패했습니다.' }; return { success: false, error: '현장 목록을 불러오는데 실패했습니다.' };
} }
} }
interface GetSiteStatsResult { /**
* 현장 통계 조회
* GET /api/v1/sites/stats
*/
export async function getSiteStats(): Promise<{
success: boolean; success: boolean;
data?: SiteStats; data?: SiteStats;
error?: string; error?: string;
} }> {
/**
* 현장 통계 조회
*/
export async function getSiteStats(): Promise<GetSiteStatsResult> {
try { try {
const result = await apiRequest<{ const response = await apiClient.get<ApiSiteStats>('/sites/stats');
total: number;
construction: number;
unregistered: number;
suspended: number;
pending: number;
}>('/sites/stats');
if (!result.success || !result.data) {
return { success: false, error: result.error || '현장 통계 조회에 실패했습니다.' };
}
return { return {
success: true, success: true,
data: { data: {
total: result.data.total || 0, total: response.total || 0,
construction: result.data.construction || 0, construction: response.construction || 0,
unregistered: result.data.unregistered || 0, unregistered: response.unregistered || 0,
suspended: result.data.suspended || 0, suspended: response.suspended || 0,
pending: result.data.pending || 0, pending: response.pending || 0,
}, },
}; };
} catch (error) { } catch (error) {
console.error('getSiteStats error:', error); console.error('현장 통계 조회 오류:', error);
return { success: false, error: '현장 통계를 불러오는데 실패했습니다.' }; return { success: false, error: '현장 통계를 불러오는데 실패했습니다.' };
} }
} }
interface DeleteSiteResult {
success: boolean;
error?: string;
}
/** /**
* 현장 삭제 * 현장 삭제
* DELETE /api/v1/sites/{id}
*/ */
export async function deleteSite(id: string): Promise<DeleteSiteResult> { export async function deleteSite(id: string): Promise<{
success: boolean;
error?: string;
}> {
try { try {
const result = await apiRequest(`/sites/${id}`, { await apiClient.delete(`/sites/${id}`);
method: 'DELETE',
});
if (!result.success) {
return { success: false, error: result.error || '현장 삭제에 실패했습니다.' };
}
return { success: true }; return { success: true };
} catch (error) { } catch (error) {
console.error('deleteSite error:', error); console.error('현장 삭제 오류:', error);
return { success: false, error: '현장 삭제에 실패했습니다.' }; return { success: false, error: '현장 삭제에 실패했습니다.' };
} }
} }
interface DeleteSitesResult { /**
* 현장 일괄 삭제
* DELETE /api/v1/sites/bulk
*/
export async function deleteSites(ids: string[]): Promise<{
success: boolean; success: boolean;
deletedCount?: number; deletedCount?: number;
error?: string; error?: string;
} }> {
/**
* 현장 일괄 삭제
*/
export async function deleteSites(ids: string[]): Promise<DeleteSitesResult> {
try { try {
const result = await apiRequest<{ deleted_count: number }>('/sites/bulk', { await apiClient.delete('/sites/bulk', {
method: 'DELETE', data: { ids: ids.map((id) => Number(id)) },
body: JSON.stringify({ ids: ids.map((id) => Number(id)) }),
}); });
return { success: true, deletedCount: ids.length };
if (!result.success) {
return { success: false, error: result.error || '현장 일괄 삭제에 실패했습니다.' };
}
return {
success: true,
deletedCount: result.data?.deleted_count || ids.length,
};
} catch (error) { } catch (error) {
console.error('deleteSites error:', error); console.error('현장 일괄 삭제 오류:', error);
return { success: false, error: '현장 일괄 삭제에 실패했습니다.' }; return { success: false, error: '현장 일괄 삭제에 실패했습니다.' };
} }
} }