feat(construction): 구조검토관리 Frontend API 연동

- Mock 데이터 제거
- apiClient 기반 실제 API 호출로 전환
- 타입 변환 함수 구현 (snake_case ↔ camelCase)

API Functions:
- getStructureReviewList: 목록 조회 + 검색/필터/정렬/페이지네이션
- getStructureReviewStats: 통계 조회
- getStructureReview: 상세 조회
- createStructureReview: 생성
- updateStructureReview: 수정
- deleteStructureReview: 단일 삭제
- deleteStructureReviews: 일괄 삭제
This commit is contained in:
2026-01-09 21:31:42 +09:00
parent 626c138fd2
commit ae90bd7c52

View File

@@ -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: '구조검토 일괄 삭제에 실패했습니다.' };
}
}