38ade7562e
- Removed the "정보조회" option from the default configuration. - Refactored the ProcessWorkStandardComponent to handle work item selection independently for each phase. - Updated the WorkPhaseSection to pass phase-specific parameters for work item selection and detail management. - Enhanced the useProcessWorkStandard hook to maintain separate states for selected work items and details by phase, improving data handling and user experience.
356 lines
10 KiB
TypeScript
356 lines
10 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useCallback } from "react";
|
|
import { apiClient } from "@/lib/api/client";
|
|
import {
|
|
ProcessWorkStandardConfig,
|
|
ItemData,
|
|
RoutingVersion,
|
|
WorkItem,
|
|
WorkItemDetail,
|
|
SelectionState,
|
|
} from "../types";
|
|
|
|
const API_BASE = "/process-work-standard";
|
|
|
|
export function useProcessWorkStandard(config: ProcessWorkStandardConfig) {
|
|
const [items, setItems] = useState<ItemData[]>([]);
|
|
const [routings, setRoutings] = useState<RoutingVersion[]>([]);
|
|
const [workItems, setWorkItems] = useState<WorkItem[]>([]);
|
|
// 섹션(phase)별 독립적인 선택 상태 관리
|
|
const [selectedWorkItemIdByPhase, setSelectedWorkItemIdByPhase] = useState<Record<string, string | null>>({});
|
|
const [selectedDetailsByPhase, setSelectedDetailsByPhase] = useState<Record<string, WorkItemDetail[]>>({});
|
|
const [loading, setLoading] = useState(false);
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
const [selection, setSelection] = useState<SelectionState>({
|
|
itemCode: null,
|
|
itemName: null,
|
|
routingVersionId: null,
|
|
routingVersionName: null,
|
|
routingDetailId: null,
|
|
processName: null,
|
|
});
|
|
|
|
// 품목 목록 조회
|
|
const fetchItems = useCallback(
|
|
async (search?: string) => {
|
|
try {
|
|
setLoading(true);
|
|
const ds = config.dataSource;
|
|
const params = new URLSearchParams({
|
|
tableName: ds.itemTable,
|
|
nameColumn: ds.itemNameColumn,
|
|
codeColumn: ds.itemCodeColumn,
|
|
routingTable: ds.routingVersionTable,
|
|
routingFkColumn: ds.routingFkColumn,
|
|
...(search ? { search } : {}),
|
|
});
|
|
const res = await apiClient.get(`${API_BASE}/items?${params}`);
|
|
if (res.data?.success) {
|
|
setItems(res.data.data);
|
|
}
|
|
} catch (err) {
|
|
console.error("품목 조회 실패", err);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
},
|
|
[config.dataSource]
|
|
);
|
|
|
|
// 라우팅 + 공정 조회
|
|
const fetchRoutings = useCallback(
|
|
async (itemCode: string) => {
|
|
try {
|
|
const ds = config.dataSource;
|
|
const params = new URLSearchParams({
|
|
routingVersionTable: ds.routingVersionTable,
|
|
routingDetailTable: ds.routingDetailTable,
|
|
routingFkColumn: ds.routingFkColumn,
|
|
processTable: ds.processTable,
|
|
processNameColumn: ds.processNameColumn,
|
|
processCodeColumn: ds.processCodeColumn,
|
|
});
|
|
const res = await apiClient.get(
|
|
`${API_BASE}/items/${encodeURIComponent(itemCode)}/routings?${params}`
|
|
);
|
|
if (res.data?.success) {
|
|
setRoutings(res.data.data);
|
|
}
|
|
} catch (err) {
|
|
console.error("라우팅 조회 실패", err);
|
|
}
|
|
},
|
|
[config.dataSource]
|
|
);
|
|
|
|
// 작업 항목 조회
|
|
const fetchWorkItems = useCallback(async (routingDetailId: string) => {
|
|
try {
|
|
setLoading(true);
|
|
const res = await apiClient.get(
|
|
`${API_BASE}/routing-detail/${routingDetailId}/work-items`
|
|
);
|
|
if (res.data?.success) {
|
|
setWorkItems(res.data.items || []);
|
|
}
|
|
} catch (err) {
|
|
console.error("작업 항목 조회 실패", err);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
// 작업 항목 상세 조회 (phase별 독립 저장)
|
|
const fetchWorkItemDetails = useCallback(async (workItemId: string, phaseKey: string) => {
|
|
try {
|
|
const res = await apiClient.get(
|
|
`${API_BASE}/work-items/${workItemId}/details`
|
|
);
|
|
if (res.data?.success) {
|
|
setSelectedDetailsByPhase(prev => ({ ...prev, [phaseKey]: res.data.data }));
|
|
setSelectedWorkItemIdByPhase(prev => ({ ...prev, [phaseKey]: workItemId }));
|
|
}
|
|
} catch (err) {
|
|
console.error("상세 조회 실패", err);
|
|
}
|
|
}, []);
|
|
|
|
// 품목 선택
|
|
const selectItem = useCallback(
|
|
async (itemCode: string, itemName: string) => {
|
|
setSelection((prev) => ({
|
|
...prev,
|
|
itemCode,
|
|
itemName,
|
|
routingVersionId: null,
|
|
routingVersionName: null,
|
|
routingDetailId: null,
|
|
processName: null,
|
|
}));
|
|
setWorkItems([]);
|
|
setSelectedDetailsByPhase({});
|
|
setSelectedWorkItemIdByPhase({});
|
|
await fetchRoutings(itemCode);
|
|
},
|
|
[fetchRoutings]
|
|
);
|
|
|
|
// 공정 선택
|
|
const selectProcess = useCallback(
|
|
async (
|
|
routingDetailId: string,
|
|
processName: string,
|
|
routingVersionId: string,
|
|
routingVersionName: string
|
|
) => {
|
|
setSelection((prev) => ({
|
|
...prev,
|
|
routingVersionId,
|
|
routingVersionName,
|
|
routingDetailId,
|
|
processName,
|
|
}));
|
|
setSelectedDetailsByPhase({});
|
|
setSelectedWorkItemIdByPhase({});
|
|
await fetchWorkItems(routingDetailId);
|
|
},
|
|
[fetchWorkItems]
|
|
);
|
|
|
|
// 작업 항목 추가
|
|
const createWorkItem = useCallback(
|
|
async (data: {
|
|
work_phase: string;
|
|
title: string;
|
|
is_required: string;
|
|
description?: string;
|
|
details?: Array<{
|
|
detail_type?: string;
|
|
content: string;
|
|
is_required: string;
|
|
sort_order: number;
|
|
}>;
|
|
}) => {
|
|
if (!selection.routingDetailId) return null;
|
|
|
|
try {
|
|
const nextOrder =
|
|
workItems.filter((wi) => wi.work_phase === data.work_phase).length + 1;
|
|
|
|
const res = await apiClient.post(`${API_BASE}/work-items`, {
|
|
routing_detail_id: selection.routingDetailId,
|
|
work_phase: data.work_phase,
|
|
title: data.title,
|
|
is_required: data.is_required,
|
|
sort_order: nextOrder,
|
|
description: data.description,
|
|
});
|
|
|
|
if (res.data?.success && res.data.data) {
|
|
const newItem = res.data.data;
|
|
|
|
// 상세 항목도 함께 생성
|
|
if (data.details && data.details.length > 0) {
|
|
for (const detail of data.details) {
|
|
await apiClient.post(`${API_BASE}/work-item-details`, {
|
|
work_item_id: newItem.id,
|
|
...detail,
|
|
});
|
|
}
|
|
}
|
|
|
|
await fetchWorkItems(selection.routingDetailId);
|
|
return newItem;
|
|
}
|
|
} catch (err) {
|
|
console.error("작업 항목 생성 실패", err);
|
|
}
|
|
return null;
|
|
},
|
|
[selection.routingDetailId, workItems, fetchWorkItems]
|
|
);
|
|
|
|
// 작업 항목 수정
|
|
const updateWorkItem = useCallback(
|
|
async (id: string, data: Partial<WorkItem>) => {
|
|
try {
|
|
const res = await apiClient.put(`${API_BASE}/work-items/${id}`, data);
|
|
if (res.data?.success && selection.routingDetailId) {
|
|
await fetchWorkItems(selection.routingDetailId);
|
|
}
|
|
} catch (err) {
|
|
console.error("작업 항목 수정 실패", err);
|
|
}
|
|
},
|
|
[selection.routingDetailId, fetchWorkItems]
|
|
);
|
|
|
|
// 작업 항목 삭제
|
|
const deleteWorkItem = useCallback(
|
|
async (id: string) => {
|
|
try {
|
|
const res = await apiClient.delete(`${API_BASE}/work-items/${id}`);
|
|
if (res.data?.success && selection.routingDetailId) {
|
|
await fetchWorkItems(selection.routingDetailId);
|
|
// 삭제된 항목이 선택되어 있던 phase의 선택 상태 초기화
|
|
setSelectedWorkItemIdByPhase(prev => {
|
|
const next = { ...prev };
|
|
for (const phaseKey of Object.keys(next)) {
|
|
if (next[phaseKey] === id) {
|
|
next[phaseKey] = null;
|
|
}
|
|
}
|
|
return next;
|
|
});
|
|
setSelectedDetailsByPhase(prev => {
|
|
const next = { ...prev };
|
|
for (const phaseKey of Object.keys(next)) {
|
|
if (selectedWorkItemIdByPhase[phaseKey] === id) {
|
|
next[phaseKey] = [];
|
|
}
|
|
}
|
|
return next;
|
|
});
|
|
}
|
|
} catch (err) {
|
|
console.error("작업 항목 삭제 실패", err);
|
|
}
|
|
},
|
|
[selection.routingDetailId, selectedWorkItemIdByPhase, fetchWorkItems]
|
|
);
|
|
|
|
// 상세 추가
|
|
const createDetail = useCallback(
|
|
async (workItemId: string, data: Partial<WorkItemDetail>, phaseKey: string) => {
|
|
try {
|
|
const res = await apiClient.post(`${API_BASE}/work-item-details`, {
|
|
work_item_id: workItemId,
|
|
...data,
|
|
});
|
|
if (res.data?.success) {
|
|
await fetchWorkItemDetails(workItemId, phaseKey);
|
|
if (selection.routingDetailId) {
|
|
await fetchWorkItems(selection.routingDetailId);
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error("상세 생성 실패", err);
|
|
}
|
|
},
|
|
[fetchWorkItemDetails, fetchWorkItems, selection.routingDetailId]
|
|
);
|
|
|
|
// 상세 수정
|
|
const updateDetail = useCallback(
|
|
async (id: string, data: Partial<WorkItemDetail>, phaseKey: string) => {
|
|
try {
|
|
const res = await apiClient.put(
|
|
`${API_BASE}/work-item-details/${id}`,
|
|
data
|
|
);
|
|
if (res.data?.success) {
|
|
const workItemId = selectedWorkItemIdByPhase[phaseKey];
|
|
if (workItemId) {
|
|
await fetchWorkItemDetails(workItemId, phaseKey);
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error("상세 수정 실패", err);
|
|
}
|
|
},
|
|
[selectedWorkItemIdByPhase, fetchWorkItemDetails]
|
|
);
|
|
|
|
// 상세 삭제
|
|
const deleteDetail = useCallback(
|
|
async (id: string, phaseKey: string) => {
|
|
try {
|
|
const res = await apiClient.delete(
|
|
`${API_BASE}/work-item-details/${id}`
|
|
);
|
|
if (res.data?.success) {
|
|
const workItemId = selectedWorkItemIdByPhase[phaseKey];
|
|
if (workItemId) {
|
|
await fetchWorkItemDetails(workItemId, phaseKey);
|
|
}
|
|
if (selection.routingDetailId) {
|
|
await fetchWorkItems(selection.routingDetailId);
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error("상세 삭제 실패", err);
|
|
}
|
|
},
|
|
[
|
|
selectedWorkItemIdByPhase,
|
|
selection.routingDetailId,
|
|
fetchWorkItemDetails,
|
|
fetchWorkItems,
|
|
]
|
|
);
|
|
|
|
return {
|
|
items,
|
|
routings,
|
|
workItems,
|
|
selectedWorkItemIdByPhase,
|
|
selectedDetailsByPhase,
|
|
selection,
|
|
loading,
|
|
saving,
|
|
fetchItems,
|
|
selectItem,
|
|
selectProcess,
|
|
fetchWorkItems,
|
|
fetchWorkItemDetails,
|
|
createWorkItem,
|
|
updateWorkItem,
|
|
deleteWorkItem,
|
|
createDetail,
|
|
updateDetail,
|
|
deleteDetail,
|
|
};
|
|
}
|