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:
@@ -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 연동
|
||||||
|
|||||||
@@ -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: '일괄 삭제에 실패했습니다.' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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: '일괄 삭제에 실패했습니다.' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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: '현장 일괄 삭제에 실패했습니다.' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user