feat(construction): 구조검토관리 Frontend API 연동
- Mock 데이터 제거 - apiClient 기반 실제 API 호출로 전환 - 타입 변환 함수 구현 (snake_case ↔ camelCase) API Functions: - getStructureReviewList: 목록 조회 + 검색/필터/정렬/페이지네이션 - getStructureReviewStats: 통계 조회 - getStructureReview: 상세 조회 - createStructureReview: 생성 - updateStructureReview: 수정 - deleteStructureReview: 단일 삭제 - deleteStructureReviews: 일괄 삭제
This commit is contained in:
@@ -1,184 +1,256 @@
|
||||
'use server';
|
||||
|
||||
import type { StructureReview, StructureReviewStats } from './types';
|
||||
import type { StructureReview, StructureReviewStats, StructureReviewStatus } from './types';
|
||||
import { apiClient } from '@/lib/api';
|
||||
|
||||
// 목업 데이터
|
||||
const MOCK_STRUCTURE_REVIEWS: StructureReview[] = [
|
||||
{
|
||||
id: '1',
|
||||
reviewNumber: '123123',
|
||||
partnerId: '1',
|
||||
partnerName: '회사명',
|
||||
siteId: '1',
|
||||
siteName: '현장명',
|
||||
requestDate: '2025-12-12',
|
||||
reviewCompany: '회사명',
|
||||
reviewerName: '홍길동',
|
||||
reviewDate: '2025-12-15',
|
||||
completionDate: '2025-12-15',
|
||||
status: 'pending',
|
||||
createdAt: '2025-12-01T00:00:00Z',
|
||||
updatedAt: '2025-12-01T00:00:00Z',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
reviewNumber: '123123',
|
||||
partnerId: '1',
|
||||
partnerName: '회사명',
|
||||
siteId: '2',
|
||||
siteName: '현장명',
|
||||
requestDate: '2025-12-12',
|
||||
reviewCompany: '회사명',
|
||||
reviewerName: '홍길동',
|
||||
reviewDate: '2025-12-15',
|
||||
completionDate: null,
|
||||
status: 'pending',
|
||||
createdAt: '2025-12-02T00:00:00Z',
|
||||
updatedAt: '2025-12-02T00:00:00Z',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
reviewNumber: '123123',
|
||||
partnerId: '2',
|
||||
partnerName: '회사명',
|
||||
siteId: '3',
|
||||
siteName: '현장명',
|
||||
requestDate: '2025-12-12',
|
||||
reviewCompany: '회사명',
|
||||
reviewerName: '홍길동',
|
||||
reviewDate: null,
|
||||
completionDate: null,
|
||||
status: 'pending',
|
||||
createdAt: '2025-12-03T00:00:00Z',
|
||||
updatedAt: '2025-12-03T00:00:00Z',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
reviewNumber: '123123',
|
||||
partnerId: '2',
|
||||
partnerName: '회사명',
|
||||
siteId: '4',
|
||||
siteName: '현장명',
|
||||
requestDate: '2025-12-12',
|
||||
reviewCompany: '회사명',
|
||||
reviewerName: '홍길동',
|
||||
reviewDate: '2025-12-15',
|
||||
completionDate: '2025-12-15',
|
||||
status: 'completed',
|
||||
createdAt: '2025-12-04T00:00:00Z',
|
||||
updatedAt: '2025-12-04T00:00:00Z',
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
reviewNumber: '123123',
|
||||
partnerId: '3',
|
||||
partnerName: '회사명',
|
||||
siteId: '5',
|
||||
siteName: '현장명',
|
||||
requestDate: '2025-12-12',
|
||||
reviewCompany: '회사명',
|
||||
reviewerName: '홍길동',
|
||||
reviewDate: '2025-12-15',
|
||||
completionDate: '2025-12-15',
|
||||
status: 'completed',
|
||||
createdAt: '2025-12-05T00:00:00Z',
|
||||
updatedAt: '2025-12-05T00:00:00Z',
|
||||
},
|
||||
];
|
||||
/**
|
||||
* 구조검토관리 Server Actions
|
||||
* 표준화된 apiClient 사용 버전
|
||||
*/
|
||||
|
||||
// 구조검토 목록 조회
|
||||
export async function getStructureReviewList(params?: {
|
||||
size?: number;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
}): Promise<{
|
||||
success: boolean;
|
||||
data?: { items: StructureReview[]; total: number };
|
||||
error?: string;
|
||||
}> {
|
||||
// TODO: API 연동
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
// ========================================
|
||||
// API 응답 타입
|
||||
// ========================================
|
||||
|
||||
interface ApiStructureReview {
|
||||
id: number;
|
||||
review_number: string | null;
|
||||
partner_id: number | null;
|
||||
partner_name: string | null;
|
||||
site_id: number | null;
|
||||
site_name: string | null;
|
||||
request_date: string | null;
|
||||
review_company: string | null;
|
||||
reviewer_name: string | null;
|
||||
review_date: string | null;
|
||||
completion_date: string | null;
|
||||
status: StructureReviewStatus;
|
||||
file_url: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
interface ApiStructureReviewStats {
|
||||
total: number;
|
||||
pending: number;
|
||||
completed: number;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 타입 변환 함수
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* API 응답 → StructureReview 타입 변환
|
||||
*/
|
||||
function transformStructureReview(apiData: ApiStructureReview): StructureReview {
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
items: MOCK_STRUCTURE_REVIEWS,
|
||||
total: MOCK_STRUCTURE_REVIEWS.length,
|
||||
},
|
||||
id: String(apiData.id),
|
||||
reviewNumber: apiData.review_number || '',
|
||||
partnerId: apiData.partner_id ? String(apiData.partner_id) : '',
|
||||
partnerName: apiData.partner_name || '',
|
||||
siteId: apiData.site_id ? String(apiData.site_id) : '',
|
||||
siteName: apiData.site_name || '',
|
||||
requestDate: apiData.request_date || '',
|
||||
reviewCompany: apiData.review_company || '',
|
||||
reviewerName: apiData.reviewer_name || '',
|
||||
reviewDate: apiData.review_date || null,
|
||||
completionDate: apiData.completion_date || null,
|
||||
status: apiData.status || 'pending',
|
||||
fileUrl: apiData.file_url || undefined,
|
||||
createdAt: apiData.created_at || '',
|
||||
updatedAt: apiData.updated_at || '',
|
||||
};
|
||||
}
|
||||
|
||||
// 구조검토 통계 조회
|
||||
/**
|
||||
* StructureReview → API 요청 데이터 변환
|
||||
*/
|
||||
function transformToApiData(data: Partial<StructureReview>): Record<string, unknown> {
|
||||
return {
|
||||
review_number: data.reviewNumber || null,
|
||||
partner_id: data.partnerId ? Number(data.partnerId) : null,
|
||||
partner_name: data.partnerName || null,
|
||||
site_id: data.siteId ? Number(data.siteId) : null,
|
||||
site_name: data.siteName || null,
|
||||
request_date: data.requestDate || null,
|
||||
review_company: data.reviewCompany || null,
|
||||
reviewer_name: data.reviewerName || null,
|
||||
review_date: data.reviewDate || null,
|
||||
completion_date: data.completionDate || null,
|
||||
status: data.status || 'pending',
|
||||
file_url: data.fileUrl || null,
|
||||
};
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// API 함수
|
||||
// ========================================
|
||||
|
||||
interface GetStructureReviewListParams {
|
||||
size?: number;
|
||||
page?: number;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
search?: string;
|
||||
status?: string;
|
||||
partnerId?: string;
|
||||
siteId?: string;
|
||||
sortBy?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 구조검토 목록 조회
|
||||
* GET /api/v1/construction/structure-reviews
|
||||
*/
|
||||
export async function getStructureReviewList(params: GetStructureReviewListParams = {}): Promise<{
|
||||
success: boolean;
|
||||
data?: {
|
||||
items: StructureReview[];
|
||||
total: number;
|
||||
page: number;
|
||||
size: number;
|
||||
totalPages: number;
|
||||
};
|
||||
error?: string;
|
||||
}> {
|
||||
try {
|
||||
const queryParams: Record<string, string> = {};
|
||||
|
||||
// 검색
|
||||
if (params.search) queryParams.search = params.search;
|
||||
|
||||
// 필터
|
||||
if (params.status && params.status !== 'all') queryParams.status = params.status;
|
||||
if (params.partnerId && params.partnerId !== 'all') queryParams.partner_id = params.partnerId;
|
||||
if (params.siteId && params.siteId !== 'all') queryParams.site_id = params.siteId;
|
||||
|
||||
// 날짜 범위
|
||||
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) {
|
||||
const sortMap: Record<string, { field: string; dir: string }> = {
|
||||
latest: { field: 'created_at', dir: 'desc' },
|
||||
oldest: { field: 'created_at', dir: 'asc' },
|
||||
partnerNameAsc: { field: 'partner_name', dir: 'asc' },
|
||||
partnerNameDesc: { field: 'partner_name', dir: 'desc' },
|
||||
siteNameAsc: { field: 'site_name', dir: 'asc' },
|
||||
siteNameDesc: { field: 'site_name', dir: 'desc' },
|
||||
};
|
||||
const sort = sortMap[params.sortBy];
|
||||
if (sort) {
|
||||
queryParams.sort_by = sort.field;
|
||||
queryParams.sort_dir = sort.dir;
|
||||
}
|
||||
}
|
||||
|
||||
const response = await apiClient.get<{
|
||||
data: ApiStructureReview[];
|
||||
current_page: number;
|
||||
per_page: number;
|
||||
total: number;
|
||||
last_page: number;
|
||||
}>('/construction/structure-reviews', { params: queryParams });
|
||||
|
||||
const items = (response.data || []).map(transformStructureReview);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
items,
|
||||
total: response.total || 0,
|
||||
page: response.current_page || 1,
|
||||
size: response.per_page || 20,
|
||||
totalPages: response.last_page || 1,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('구조검토 목록 조회 오류:', error);
|
||||
return { success: false, error: '구조검토 목록을 불러오는데 실패했습니다.' };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 구조검토 통계 조회
|
||||
* GET /api/v1/construction/structure-reviews/stats
|
||||
*/
|
||||
export async function getStructureReviewStats(): Promise<{
|
||||
success: boolean;
|
||||
data?: StructureReviewStats;
|
||||
error?: string;
|
||||
}> {
|
||||
// TODO: API 연동
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
try {
|
||||
const response = await apiClient.get<ApiStructureReviewStats>('/construction/structure-reviews/stats');
|
||||
|
||||
const pending = MOCK_STRUCTURE_REVIEWS.filter((r) => r.status === 'pending').length;
|
||||
const completed = MOCK_STRUCTURE_REVIEWS.filter((r) => r.status === 'completed').length;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
total: MOCK_STRUCTURE_REVIEWS.length,
|
||||
pending,
|
||||
completed,
|
||||
},
|
||||
};
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
total: response.total || 0,
|
||||
pending: response.pending || 0,
|
||||
completed: response.completed || 0,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('구조검토 통계 조회 오류:', error);
|
||||
return { success: false, error: '구조검토 통계를 불러오는데 실패했습니다.' };
|
||||
}
|
||||
}
|
||||
|
||||
// 구조검토 상세 조회
|
||||
/**
|
||||
* 구조검토 상세 조회
|
||||
* GET /api/v1/construction/structure-reviews/{id}
|
||||
*/
|
||||
export async function getStructureReview(id: string): Promise<{
|
||||
success: boolean;
|
||||
data?: StructureReview;
|
||||
error?: string;
|
||||
}> {
|
||||
// TODO: API 연동
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
try {
|
||||
const response = await apiClient.get<ApiStructureReview>(`/construction/structure-reviews/${id}`);
|
||||
|
||||
const review = MOCK_STRUCTURE_REVIEWS.find((r) => r.id === id);
|
||||
|
||||
if (!review) {
|
||||
return {
|
||||
success: true,
|
||||
data: transformStructureReview(response),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('구조검토 상세 조회 오류:', error);
|
||||
return { success: false, error: '구조검토 정보를 찾을 수 없습니다.' };
|
||||
}
|
||||
|
||||
return { success: true, data: review };
|
||||
}
|
||||
|
||||
// 구조검토 생성
|
||||
/**
|
||||
* 구조검토 생성
|
||||
* POST /api/v1/construction/structure-reviews
|
||||
*/
|
||||
export async function createStructureReview(data: Partial<StructureReview>): Promise<{
|
||||
success: boolean;
|
||||
data?: StructureReview;
|
||||
error?: string;
|
||||
}> {
|
||||
// TODO: API 연동
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
try {
|
||||
const apiData = transformToApiData(data);
|
||||
const response = await apiClient.post<ApiStructureReview>('/construction/structure-reviews', apiData);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
id: String(Date.now()),
|
||||
reviewNumber: data.reviewNumber || '',
|
||||
partnerId: data.partnerId || '',
|
||||
partnerName: data.partnerName || '',
|
||||
siteId: data.siteId || '',
|
||||
siteName: data.siteName || '',
|
||||
requestDate: data.requestDate || '',
|
||||
reviewCompany: data.reviewCompany || '',
|
||||
reviewerName: data.reviewerName || '',
|
||||
reviewDate: data.reviewDate || null,
|
||||
completionDate: data.completionDate || null,
|
||||
status: data.status || 'pending',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
return {
|
||||
success: true,
|
||||
data: transformStructureReview(response),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('구조검토 생성 오류:', error);
|
||||
return { success: false, error: '구조검토 등록에 실패했습니다.' };
|
||||
}
|
||||
}
|
||||
|
||||
// 구조검토 수정
|
||||
/**
|
||||
* 구조검토 수정
|
||||
* PUT /api/v1/construction/structure-reviews/{id}
|
||||
*/
|
||||
export async function updateStructureReview(
|
||||
id: string,
|
||||
data: Partial<StructureReview>
|
||||
@@ -187,43 +259,53 @@ export async function updateStructureReview(
|
||||
data?: StructureReview;
|
||||
error?: string;
|
||||
}> {
|
||||
// TODO: API 연동
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
try {
|
||||
const apiData = transformToApiData(data);
|
||||
const response = await apiClient.put<ApiStructureReview>(`/construction/structure-reviews/${id}`, apiData);
|
||||
|
||||
const existing = MOCK_STRUCTURE_REVIEWS.find((r) => r.id === id);
|
||||
if (!existing) {
|
||||
return { success: false, error: '구조검토 정보를 찾을 수 없습니다.' };
|
||||
return {
|
||||
success: true,
|
||||
data: transformStructureReview(response),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('구조검토 수정 오류:', error);
|
||||
return { success: false, error: '구조검토 수정에 실패했습니다.' };
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
...existing,
|
||||
...data,
|
||||
updatedAt: new Date().toISOString(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// 구조검토 삭제
|
||||
/**
|
||||
* 구조검토 삭제
|
||||
* DELETE /api/v1/construction/structure-reviews/{id}
|
||||
*/
|
||||
export async function deleteStructureReview(id: string): Promise<{
|
||||
success: boolean;
|
||||
error?: string;
|
||||
}> {
|
||||
// TODO: API 연동
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
return { success: true };
|
||||
try {
|
||||
await apiClient.delete(`/construction/structure-reviews/${id}`);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('구조검토 삭제 오류:', error);
|
||||
return { success: false, error: '구조검토 삭제에 실패했습니다.' };
|
||||
}
|
||||
}
|
||||
|
||||
// 구조검토 일괄 삭제
|
||||
/**
|
||||
* 구조검토 일괄 삭제
|
||||
* DELETE /api/v1/construction/structure-reviews/bulk
|
||||
*/
|
||||
export async function deleteStructureReviews(ids: string[]): Promise<{
|
||||
success: boolean;
|
||||
deletedCount?: number;
|
||||
error?: string;
|
||||
}> {
|
||||
// TODO: API 연동
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
|
||||
return { success: true, deletedCount: ids.length };
|
||||
try {
|
||||
await apiClient.delete('/construction/structure-reviews/bulk', {
|
||||
data: { ids: ids.map((id) => Number(id)) },
|
||||
});
|
||||
return { success: true, deletedCount: ids.length };
|
||||
} catch (error) {
|
||||
console.error('구조검토 일괄 삭제 오류:', error);
|
||||
return { success: false, error: '구조검토 일괄 삭제에 실패했습니다.' };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user