feat: [품질관리] 프론트엔드 API 연동 (Mock → 실제 API 전환)

- InspectionManagement/actions.ts: API 경로 /quality/documents로 변경, transformFormToApi options JSON 구조 매핑
- PerformanceReportManagement/actions.ts: API 경로 /quality/performance-reports로 변경, /missed→/missing
- InspectionManagement/types.ts: InspectionFormData에 clientId/inspectorId/receptionDate 추가
- USE_MOCK_FALLBACK = false 설정
This commit is contained in:
2026-03-05 19:43:08 +09:00
parent eb18a3facb
commit 50e4c72c8a
3 changed files with 130 additions and 132 deletions

View File

@@ -4,15 +4,15 @@
* 제품검사 관리 Server Actions
*
* API Endpoints:
* - GET /api/v1/inspections - 목록 조회
* - GET /api/v1/inspections/stats - 통계 조회
* - GET /api/v1/inspections/calendar - 캘린더 스케줄 조회
* - GET /api/v1/inspections/{id} - 상세 조회
* - POST /api/v1/inspections - 등록
* - PUT /api/v1/inspections/{id} - 수정
* - DELETE /api/v1/inspections/{id} - 삭제
* - PATCH /api/v1/inspections/{id}/complete - 검사 완료 처리
* - GET /api/v1/orders/select - 수주 선택 목록 조회
* - GET /api/v1/quality/documents - 목록 조회
* - GET /api/v1/quality/documents/stats - 통계 조회
* - GET /api/v1/quality/documents/calendar - 캘린더 스케줄 조회
* - GET /api/v1/quality/documents/{id} - 상세 조회
* - POST /api/v1/quality/documents - 등록
* - PUT /api/v1/quality/documents/{id} - 수정
* - DELETE /api/v1/quality/documents/{id} - 삭제
* - PATCH /api/v1/quality/documents/{id}/complete - 검사 완료 처리
* - GET /api/v1/quality/documents/available-orders - 수주 선택 목록 조회
*/
import { executeServerAction } from '@/lib/api/execute-server-action';
@@ -33,7 +33,7 @@ import {
} from './mockData';
// 개발환경 Mock 데이터 fallback 플래그
const USE_MOCK_FALLBACK = true;
const USE_MOCK_FALLBACK = false;
// ===== API 응답 타입 =====
@@ -237,53 +237,49 @@ function transformApiToFrontend(api: ProductInspectionApi): ProductInspection {
function transformFormToApi(data: InspectionFormData): Record<string, unknown> {
return {
quality_doc_number: data.qualityDocNumber,
site_name: data.siteName,
client: data.client,
manager: data.manager,
manager_contact: data.managerContact,
construction_site: {
site_name: data.constructionSite.siteName,
land_location: data.constructionSite.landLocation,
lot_number: data.constructionSite.lotNumber,
client_id: data.clientId ?? null,
inspector_id: data.inspectorId ?? null,
received_date: data.receptionDate ?? null,
options: {
manager: {
name: data.manager || '',
phone: data.managerContact || '',
},
inspection: {
request_date: data.scheduleInfo?.visitRequestDate || '',
start_date: data.scheduleInfo?.startDate || '',
end_date: data.scheduleInfo?.endDate || '',
},
site_address: {
postal_code: data.scheduleInfo?.sitePostalCode || '',
address: data.scheduleInfo?.siteAddress || '',
detail: data.scheduleInfo?.siteAddressDetail || '',
},
construction_site: {
name: data.constructionSite?.siteName || '',
land_location: data.constructionSite?.landLocation || '',
lot_number: data.constructionSite?.lotNumber || '',
},
material_distributor: {
company: data.materialDistributor?.companyName || '',
address: data.materialDistributor?.companyAddress || '',
ceo: data.materialDistributor?.representativeName || '',
phone: data.materialDistributor?.phone || '',
},
contractor: {
company: data.constructorInfo?.companyName || '',
address: data.constructorInfo?.companyAddress || '',
name: data.constructorInfo?.name || '',
phone: data.constructorInfo?.phone || '',
},
supervisor: {
office: data.supervisor?.officeName || '',
address: data.supervisor?.officeAddress || '',
name: data.supervisor?.name || '',
phone: data.supervisor?.phone || '',
},
},
material_distributor: {
company_name: data.materialDistributor.companyName,
company_address: data.materialDistributor.companyAddress,
representative_name: data.materialDistributor.representativeName,
phone: data.materialDistributor.phone,
},
constructor_info: {
company_name: data.constructorInfo.companyName,
company_address: data.constructorInfo.companyAddress,
name: data.constructorInfo.name,
phone: data.constructorInfo.phone,
},
supervisor: {
office_name: data.supervisor.officeName,
office_address: data.supervisor.officeAddress,
name: data.supervisor.name,
phone: data.supervisor.phone,
},
schedule_info: {
visit_request_date: data.scheduleInfo.visitRequestDate,
start_date: data.scheduleInfo.startDate,
end_date: data.scheduleInfo.endDate,
inspector: data.scheduleInfo.inspector,
site_postal_code: data.scheduleInfo.sitePostalCode,
site_address: data.scheduleInfo.siteAddress,
site_address_detail: data.scheduleInfo.siteAddressDetail,
},
order_items: data.orderItems.map((item) => ({
order_number: item.orderNumber,
floor: item.floor,
symbol: item.symbol,
order_width: item.orderWidth,
order_height: item.orderHeight,
construction_width: item.constructionWidth,
construction_height: item.constructionHeight,
change_reason: item.changeReason,
})),
};
}
@@ -306,7 +302,7 @@ export async function getInspections(params?: {
const defaultPagination = { currentPage: 1, lastPage: 1, perPage: 20, total: 0 };
const result = await executeServerAction<PaginatedResponse>({
url: buildApiUrl('/api/v1/inspections', {
url: buildApiUrl('/api/v1/quality/documents', {
page: params?.page,
per_page: params?.size,
q: params?.q,
@@ -369,7 +365,7 @@ export async function getInspectionStats(params?: {
__authError?: boolean;
}> {
const result = await executeServerAction<InspectionStatsApi>({
url: buildApiUrl('/api/v1/inspections/stats', {
url: buildApiUrl('/api/v1/quality/documents/stats', {
date_from: params?.dateFrom,
date_to: params?.dateTo,
}),
@@ -406,7 +402,7 @@ export async function getInspectionCalendar(params?: {
__authError?: boolean;
}> {
const result = await executeServerAction<CalendarItemApi[]>({
url: buildApiUrl('/api/v1/inspections/calendar', {
url: buildApiUrl('/api/v1/quality/documents/calendar', {
year: params?.year,
month: params?.month,
inspector: params?.inspector,
@@ -443,7 +439,7 @@ export async function getInspectionById(id: string): Promise<{
__authError?: boolean;
}> {
const result = await executeServerAction<ProductInspectionApi>({
url: buildApiUrl(`/api/v1/inspections/${id}`),
url: buildApiUrl(`/api/v1/quality/documents/${id}`),
errorMessage: '제품검사 상세 조회에 실패했습니다.',
});
@@ -471,7 +467,7 @@ export async function createInspection(data: InspectionFormData): Promise<{
}> {
const apiData = transformFormToApi(data);
const result = await executeServerAction<ProductInspectionApi>({
url: buildApiUrl('/api/v1/inspections'),
url: buildApiUrl('/api/v1/quality/documents'),
method: 'POST',
body: apiData,
errorMessage: '제품검사 등록에 실패했습니다.',
@@ -496,69 +492,70 @@ export async function updateInspection(
}> {
const apiData: Record<string, unknown> = {};
if (data.qualityDocNumber !== undefined) apiData.quality_doc_number = data.qualityDocNumber;
if (data.siteName !== undefined) apiData.site_name = data.siteName;
if (data.client !== undefined) apiData.client = data.client;
if (data.manager !== undefined) apiData.manager = data.manager;
if (data.managerContact !== undefined) apiData.manager_contact = data.managerContact;
if (data.clientId !== undefined) apiData.client_id = data.clientId;
if (data.inspectorId !== undefined) apiData.inspector_id = data.inspectorId;
if (data.receptionDate !== undefined) apiData.received_date = data.receptionDate;
if (data.constructionSite) {
apiData.construction_site = {
site_name: data.constructionSite.siteName,
land_location: data.constructionSite.landLocation,
lot_number: data.constructionSite.lotNumber,
};
}
if (data.materialDistributor) {
apiData.material_distributor = {
company_name: data.materialDistributor.companyName,
company_address: data.materialDistributor.companyAddress,
representative_name: data.materialDistributor.representativeName,
phone: data.materialDistributor.phone,
};
}
if (data.constructorInfo) {
apiData.constructor_info = {
company_name: data.constructorInfo.companyName,
company_address: data.constructorInfo.companyAddress,
name: data.constructorInfo.name,
phone: data.constructorInfo.phone,
};
}
if (data.supervisor) {
apiData.supervisor = {
office_name: data.supervisor.officeName,
office_address: data.supervisor.officeAddress,
name: data.supervisor.name,
phone: data.supervisor.phone,
// options 필드들은 백엔드에서 array_replace_recursive로 병합됨
const options: Record<string, unknown> = {};
if (data.manager !== undefined || data.managerContact !== undefined) {
options.manager = {
name: data.manager || '',
phone: data.managerContact || '',
};
}
if (data.scheduleInfo) {
apiData.schedule_info = {
visit_request_date: data.scheduleInfo.visitRequestDate,
start_date: data.scheduleInfo.startDate,
end_date: data.scheduleInfo.endDate,
inspector: data.scheduleInfo.inspector,
site_postal_code: data.scheduleInfo.sitePostalCode,
site_address: data.scheduleInfo.siteAddress,
site_address_detail: data.scheduleInfo.siteAddressDetail,
options.inspection = {
request_date: data.scheduleInfo.visitRequestDate || '',
start_date: data.scheduleInfo.startDate || '',
end_date: data.scheduleInfo.endDate || '',
};
options.site_address = {
postal_code: data.scheduleInfo.sitePostalCode || '',
address: data.scheduleInfo.siteAddress || '',
detail: data.scheduleInfo.siteAddressDetail || '',
};
}
if (data.orderItems) {
apiData.order_items = data.orderItems.map((item) => ({
order_number: item.orderNumber,
floor: item.floor,
symbol: item.symbol,
order_width: item.orderWidth,
order_height: item.orderHeight,
construction_width: item.constructionWidth,
construction_height: item.constructionHeight,
change_reason: item.changeReason,
}));
if (data.constructionSite) {
options.construction_site = {
name: data.constructionSite.siteName || '',
land_location: data.constructionSite.landLocation || '',
lot_number: data.constructionSite.lotNumber || '',
};
}
if (data.materialDistributor) {
options.material_distributor = {
company: data.materialDistributor.companyName || '',
address: data.materialDistributor.companyAddress || '',
ceo: data.materialDistributor.representativeName || '',
phone: data.materialDistributor.phone || '',
};
}
if (data.constructorInfo) {
options.contractor = {
company: data.constructorInfo.companyName || '',
address: data.constructorInfo.companyAddress || '',
name: data.constructorInfo.name || '',
phone: data.constructorInfo.phone || '',
};
}
if (data.supervisor) {
options.supervisor = {
office: data.supervisor.officeName || '',
address: data.supervisor.officeAddress || '',
name: data.supervisor.name || '',
phone: data.supervisor.phone || '',
};
}
if (Object.keys(options).length > 0) {
apiData.options = options;
}
const result = await executeServerAction<ProductInspectionApi>({
url: buildApiUrl(`/api/v1/inspections/${id}`),
url: buildApiUrl(`/api/v1/quality/documents/${id}`),
method: 'PUT',
body: apiData,
errorMessage: '제품검사 수정에 실패했습니다.',
@@ -578,7 +575,7 @@ export async function deleteInspection(id: string): Promise<{
__authError?: boolean;
}> {
const result = await executeServerAction({
url: buildApiUrl(`/api/v1/inspections/${id}`),
url: buildApiUrl(`/api/v1/quality/documents/${id}`),
method: 'DELETE',
errorMessage: '제품검사 삭제에 실패했습니다.',
});
@@ -626,7 +623,7 @@ export async function getOrderSelectList(params?: {
__authError?: boolean;
}> {
const result = await executeServerAction<OrderSelectItemApi[]>({
url: buildApiUrl('/api/v1/orders/select', { q: params?.q }),
url: buildApiUrl('/api/v1/quality/documents/available-orders', { q: params?.q }),
errorMessage: '수주 선택 목록 조회에 실패했습니다.',
});

View File

@@ -181,6 +181,9 @@ export interface InspectionFormData {
qualityDocNumber: string;
siteName: string;
client: string;
clientId?: number; // 수주처 ID (API용)
inspectorId?: number; // 검사자 ID (API용)
receptionDate?: string; // 접수일 (API용)
manager: string;
managerContact: string;
constructionSite: ConstructionSiteInfo;

View File

@@ -4,13 +4,12 @@
* 실적신고관리 Server Actions
*
* API Endpoints:
* - GET /api/v1/performance-reports - 분기별 실적신고 목록
* - GET /api/v1/performance-reports/stats - 통계
* - GET /api/v1/performance-reports/missed - 누락체크 목록
* - PATCH /api/v1/performance-reports/confirm - 선택 확정
* - PATCH /api/v1/performance-reports/unconfirm - 확정 해제
* - POST /api/v1/performance-reports/distribute - 배포
* - PATCH /api/v1/performance-reports/memo - 메모 일괄 적용
* - GET /api/v1/quality/performance-reports - 분기별 실적신고 목록
* - GET /api/v1/quality/performance-reports/stats - 통계
* - GET /api/v1/quality/performance-reports/missing - 누락체크 목록
* - PATCH /api/v1/quality/performance-reports/confirm - 선택 확정
* - PATCH /api/v1/quality/performance-reports/unconfirm - 확정 해제
* - PATCH /api/v1/quality/performance-reports/memo - 메모 일괄 적용
*/
import { executeServerAction } from '@/lib/api/execute-server-action';
@@ -28,7 +27,7 @@ import {
} from './mockData';
// 개발환경 Mock 데이터 fallback 플래그
const USE_MOCK_FALLBACK = true;
const USE_MOCK_FALLBACK = false;
// ===== 페이지네이션 =====
@@ -58,7 +57,7 @@ export async function getPerformanceReports(params?: {
interface ApiListData { items?: PerformanceReport[]; current_page?: number; last_page?: number; per_page?: number; total?: number }
const result = await executeServerAction<ApiListData>({
url: buildApiUrl('/api/v1/performance-reports', {
url: buildApiUrl('/api/v1/quality/performance-reports', {
page: params?.page,
per_page: params?.size,
q: params?.q,
@@ -116,7 +115,7 @@ export async function getPerformanceReportStats(params?: {
__authError?: boolean;
}> {
const result = await executeServerAction<PerformanceReportStats>({
url: buildApiUrl('/api/v1/performance-reports/stats', {
url: buildApiUrl('/api/v1/quality/performance-reports/stats', {
year: params?.year,
quarter: params?.quarter && params.quarter !== '전체' ? params.quarter : undefined,
}),
@@ -147,7 +146,7 @@ export async function getMissedReports(params?: {
interface ApiMissedData { items?: MissedReport[]; current_page?: number; last_page?: number; per_page?: number; total?: number }
const result = await executeServerAction<ApiMissedData>({
url: buildApiUrl('/api/v1/performance-reports/missed', {
url: buildApiUrl('/api/v1/quality/performance-reports/missing', {
page: params?.page,
per_page: params?.size,
q: params?.q,
@@ -197,7 +196,7 @@ export async function confirmReports(ids: string[]): Promise<{
__authError?: boolean;
}> {
const result = await executeServerAction({
url: buildApiUrl('/api/v1/performance-reports/confirm'),
url: buildApiUrl('/api/v1/quality/performance-reports/confirm'),
method: 'PATCH',
body: { ids },
errorMessage: '확정 처리에 실패했습니다.',
@@ -214,7 +213,7 @@ export async function unconfirmReports(ids: string[]): Promise<{
__authError?: boolean;
}> {
const result = await executeServerAction({
url: buildApiUrl('/api/v1/performance-reports/unconfirm'),
url: buildApiUrl('/api/v1/quality/performance-reports/unconfirm'),
method: 'PATCH',
body: { ids },
errorMessage: '확정 해제에 실패했습니다.',
@@ -223,7 +222,7 @@ export async function unconfirmReports(ids: string[]): Promise<{
return { success: result.success, error: result.error, __authError: result.__authError };
}
// ===== 배포 =====
// ===== 배포 (TODO: 백엔드 API 미구현 - 추후 추가 예정) =====
export async function distributeReports(ids: string[]): Promise<{
success: boolean;
@@ -231,12 +230,11 @@ export async function distributeReports(ids: string[]): Promise<{
__authError?: boolean;
}> {
const result = await executeServerAction({
url: buildApiUrl('/api/v1/performance-reports/distribute'),
url: buildApiUrl('/api/v1/quality/performance-reports/distribute'),
method: 'POST',
body: { ids },
errorMessage: '배포에 실패했습니다.',
});
if (!result.success && USE_MOCK_FALLBACK) return { success: true };
return { success: result.success, error: result.error, __authError: result.__authError };
}
@@ -248,7 +246,7 @@ export async function updateMemo(ids: string[], memo: string): Promise<{
__authError?: boolean;
}> {
const result = await executeServerAction({
url: buildApiUrl('/api/v1/performance-reports/memo'),
url: buildApiUrl('/api/v1/quality/performance-reports/memo'),
method: 'PATCH',
body: { ids, memo },
errorMessage: '메모 저장에 실패했습니다.',