diff --git a/backend-node/src/controllers/moldController.ts b/backend-node/src/controllers/moldController.ts index 25e49186..19ef1eb2 100644 --- a/backend-node/src/controllers/moldController.ts +++ b/backend-node/src/controllers/moldController.ts @@ -512,13 +512,36 @@ export async function getMoldSerialSummary(req: AuthenticatedRequest, res: Respo const companyCode = req.user!.companyCode; const { moldCode } = req.params; + // 카테고리 코드/영문코드/한글라벨 모두 대응 + // 먼저 카테고리 값 조회하여 매핑 + // mold_serial.status + mold_mng.operation_status 양쪽 카테고리 모두 조회 + const catSql = `SELECT value_code, value_label FROM category_values + WHERE ((table_name='mold_serial' AND column_name='status') OR (table_name='mold_mng' AND column_name='operation_status')) + AND company_code=$1`; + const catRows = await query(catSql, [companyCode]); + + // 카테고리 라벨 기준으로 그룹핑할 코드 목록 생성 + const codesByLabel: Record = { "사용중": ["IN_USE"], "수리중": ["REPAIR"], "보관중": ["STORED"], "폐기": ["DISPOSED"] }; + for (const cat of catRows) { + const label = cat.value_label || ""; + if (label.includes("사용")) (codesByLabel["사용중"] = codesByLabel["사용중"] || []).push(cat.value_code); + else if (label.includes("수리")) (codesByLabel["수리중"] = codesByLabel["수리중"] || []).push(cat.value_code); + else if (label.includes("보관") || label.includes("미사용")) (codesByLabel["보관중"] = codesByLabel["보관중"] || []).push(cat.value_code); + else if (label.includes("폐기")) (codesByLabel["폐기"] = codesByLabel["폐기"] || []).push(cat.value_code); + } + + const inUseCodes = codesByLabel["사용중"].map(c => `'${c}'`).join(","); + const repairCodes = codesByLabel["수리중"].map(c => `'${c}'`).join(","); + const storedCodes = codesByLabel["보관중"].map(c => `'${c}'`).join(","); + const disposedCodes = codesByLabel["폐기"].map(c => `'${c}'`).join(","); + const sql = ` SELECT COUNT(*) as total, - COUNT(*) FILTER (WHERE status = 'IN_USE') as in_use, - COUNT(*) FILTER (WHERE status = 'REPAIR') as repair, - COUNT(*) FILTER (WHERE status = 'STORED') as stored, - COUNT(*) FILTER (WHERE status = 'DISPOSED') as disposed + COUNT(*) FILTER (WHERE status IN (${inUseCodes})) as in_use, + COUNT(*) FILTER (WHERE status IN (${repairCodes})) as repair, + COUNT(*) FILTER (WHERE status IN (${storedCodes})) as stored, + COUNT(*) FILTER (WHERE status IN (${disposedCodes})) as disposed FROM mold_serial WHERE mold_code = $1 AND company_code = $2 `; diff --git a/backend-node/src/controllers/packagingController.ts b/backend-node/src/controllers/packagingController.ts index d87c0c7e..037ab46a 100644 --- a/backend-node/src/controllers/packagingController.ts +++ b/backend-node/src/controllers/packagingController.ts @@ -228,10 +228,11 @@ export async function deletePkgUnitItem( const { id } = req.params; const pool = getPool(); - const result = await pool.query( - `DELETE FROM pkg_unit_item WHERE id=$1 AND company_code=$2 RETURNING id`, - [id, companyCode] - ); + const query = companyCode === "*" + ? `DELETE FROM pkg_unit_item WHERE id=$1 RETURNING id` + : `DELETE FROM pkg_unit_item WHERE id=$1 AND company_code=$2 RETURNING id`; + const params = companyCode === "*" ? [id] : [id, companyCode]; + const result = await pool.query(query, params); if (result.rowCount === 0) { res.status(404).json({ success: false, message: "데이터를 찾을 수 없습니다." }); @@ -471,10 +472,11 @@ export async function deleteLoadingUnitPkg( const { id } = req.params; const pool = getPool(); - const result = await pool.query( - `DELETE FROM loading_unit_pkg WHERE id=$1 AND company_code=$2 RETURNING id`, - [id, companyCode] - ); + const query = companyCode === "*" + ? `DELETE FROM loading_unit_pkg WHERE id=$1 RETURNING id` + : `DELETE FROM loading_unit_pkg WHERE id=$1 AND company_code=$2 RETURNING id`; + const params = companyCode === "*" ? [id] : [id, companyCode]; + const result = await pool.query(query, params); if (result.rowCount === 0) { res.status(404).json({ success: false, message: "데이터를 찾을 수 없습니다." }); diff --git a/frontend/app/(main)/COMPANY_10/equipment/info/page.tsx b/frontend/app/(main)/COMPANY_10/equipment/info/page.tsx index c2f3c0ca..17d81598 100644 --- a/frontend/app/(main)/COMPANY_10/equipment/info/page.tsx +++ b/frontend/app/(main)/COMPANY_10/equipment/info/page.tsx @@ -147,17 +147,17 @@ export default function EquipmentInfoPage() { const colProps: Record> = { equipment_code: { width: "w-[110px]" }, equipment_name: { minWidth: "min-w-[130px]", truncate: true, render: (v) => v || "-" }, - equipment_type: { width: "w-[90px]", render: (v) => v || "-" }, + equipment_type: { width: "w-[90px]", render: (v) => resolve("equipment_type", v) || v || "-" }, manufacturer: { width: "w-[100px]", render: (v) => v || "-" }, installation_location: { width: "w-[100px]", render: (v) => v || "-" }, - operation_status: { width: "w-[80px]", render: (v) => v || "-" }, + operation_status: { width: "w-[80px]", render: (v) => resolve("operation_status", v) || v || "-" }, }; return ts.visibleColumns.map((col) => ({ key: col.key, label: col.label, ...colProps[col.key], })); - }, [ts.visibleColumns]); + }, [ts.visibleColumns, catOptions]); // 설비 조회 const fetchEquipments = useCallback(async () => { @@ -170,11 +170,7 @@ export default function EquipmentInfoPage() { autoFilter: true, }); const raw = res.data?.data?.data || res.data?.data?.rows || []; - setEquipments(raw.map((r: any) => ({ - ...r, - equipment_type: resolve("equipment_type", r.equipment_type), - operation_status: resolve("operation_status", r.operation_status), - }))); + setEquipments(raw); setEquipCount(res.data?.data?.total || raw.length); } catch { toast.error("설비 목록 조회 실패"); } finally { setEquipLoading(false); } }, [searchFilters, catOptions]); @@ -437,9 +433,9 @@ export default function EquipmentInfoPage() { const handleExcelDownload = async () => { if (equipments.length === 0) return; await exportToExcel(equipments.map((e) => ({ - 설비코드: e.equipment_code, 설비명: e.equipment_name, 설비유형: e.equipment_type, + 설비코드: e.equipment_code, 설비명: e.equipment_name, 설비유형: resolve("equipment_type", e.equipment_type), 제조사: e.manufacturer, 모델명: e.model_name, 설치장소: e.installation_location, - 도입일자: e.introduction_date, 가동상태: e.operation_status, + 도입일자: e.introduction_date, 가동상태: resolve("operation_status", e.operation_status), })), "설비정보.xlsx", "설비"); toast.success("다운로드 완료"); }; diff --git a/frontend/app/(main)/COMPANY_10/master-data/company/page.tsx b/frontend/app/(main)/COMPANY_10/master-data/company/page.tsx index a607b7ea..4cf60273 100644 --- a/frontend/app/(main)/COMPANY_10/master-data/company/page.tsx +++ b/frontend/app/(main)/COMPANY_10/master-data/company/page.tsx @@ -563,10 +563,6 @@ export default function CompanyPage() { {/* 기본 정보 그리드 (2열) */}
-
- - -
@@ -470,7 +500,7 @@ export default function MoldInfoPage() {

{mold.mold_code}

{mold.mold_name}

{mold.mold_type && ( - {mold.mold_type} + {resolveMoldType(mold.mold_type)} )}
@@ -531,10 +561,7 @@ export default function MoldInfoPage() { 전체 - 사출금형 - 프레스금형 - 다이캐스팅 - 단조금형 + {moldTypeCatOptions.map((o) => {o.label})} @@ -546,10 +573,7 @@ export default function MoldInfoPage() { 전체 - 사용중 - 점검중 - 수리중 - 폐기 + {operationStatusCatOptions.map((o) => {o.label})} @@ -670,13 +694,13 @@ export default function MoldInfoPage() {

{selectedMold.mold_name}

{selectedMold.mold_type && ( - {selectedMold.mold_type} + {resolveMoldType(selectedMold.mold_type)} )} {selectedMold.category && ( {selectedMold.category} )} - - {STATUS_MAP[selectedMold.operation_status]?.label || selectedMold.operation_status || "-"} + + {resolveOpStatus(selectedMold.operation_status) || "-"}
@@ -811,15 +835,15 @@ export default function MoldInfoPage() { {serials.map((s: any) => { - const ss = SERIAL_STATUS_MAP[s.status] || { label: s.status || "-", variant: "secondary" as const }; - const maxShot = detail?.shot_count || 0; + const ssLabel = resolveOpStatus(s.status); + const maxShot = selectedMold?.shot_count || 0; const curShot = s.current_shot_count || 0; const pct = maxShot > 0 ? Math.min(Math.round((curShot / maxShot) * 100), 100) : 0; return ( {s.serial_number} - {ss.label} + {ssLabel} {maxShot > 0 ? ( @@ -1043,10 +1067,7 @@ export default function MoldInfoPage() { - 사출금형 - 프레스금형 - 다이캐스팅 - 단조금형 + {moldTypeCatOptions.map((o) => {o.label})} @@ -1117,10 +1138,7 @@ export default function MoldInfoPage() { - 사용중 - 점검중 - 수리중 - 폐기 + {operationStatusCatOptions.map((o) => {o.label})} @@ -1175,10 +1193,7 @@ export default function MoldInfoPage() { - 사용중 - 보관중 - 수리중 - 폐기 + {operationStatusCatOptions.map((o) => {o.label})} diff --git a/frontend/app/(main)/COMPANY_10/production/bom/page.tsx b/frontend/app/(main)/COMPANY_10/production/bom/page.tsx index ef441cfc..8c7b582b 100644 --- a/frontend/app/(main)/COMPANY_10/production/bom/page.tsx +++ b/frontend/app/(main)/COMPANY_10/production/bom/page.tsx @@ -1530,53 +1530,6 @@ export default function BomManagementPage() { ) : (
- {/* 상세 카드 */} -
-
-

BOM 상세정보

- -
- {detailLoading ? ( -
- -
- ) : bomHeader ? ( -
-
- 품목코드 - {bomHeader.item_code || bomHeader.item_number || "-"} -
-
- 품명 - {bomHeader.item_name || "-"} -
-
- BOM 유형 - {BOM_TYPE_OPTIONS.find((o) => o.code === bomHeader.bom_type)?.label || bomHeader.bom_type || "-"} -
-
- 버전 - {bomHeader.version || "-"} -
-
- 기준수량 - {bomHeader.base_qty || "1"} {bomHeader.unit || ""} -
-
- 상태 - {renderStatusBadge(bomHeader.status)} -
-
- 메모 - {bomHeader.remark || "-"} -
-
- ) : null} -
- {/* 하단 탭: 트리뷰 / 버전 / 이력 */}
{ diff --git a/frontend/app/(main)/COMPANY_10/production/process-info/ItemRoutingTab.tsx b/frontend/app/(main)/COMPANY_10/production/process-info/ItemRoutingTab.tsx index e50e27d5..4eefd66c 100644 --- a/frontend/app/(main)/COMPANY_10/production/process-info/ItemRoutingTab.tsx +++ b/frontend/app/(main)/COMPANY_10/production/process-info/ItemRoutingTab.tsx @@ -92,6 +92,7 @@ export function ItemRoutingTab() { const [formWorkType, setFormWorkType] = useState("내부"); const [formStandardTime, setFormStandardTime] = useState(""); const [formOutsource, setFormOutsource] = useState(""); + const [subcontractorOptions, setSubcontractorOptions] = useState<{ code: string; name: string }[]>([]); const [detailSubmitting, setDetailSubmitting] = useState(false); const [registerDialogOpen, setRegisterDialogOpen] = useState(false); @@ -107,6 +108,19 @@ export function ItemRoutingTab() { return () => window.clearTimeout(t); }, [searchInput]); + // 외주사 목록 로드 + useEffect(() => { + (async () => { + try { + const res = await apiClient.post("/table-management/tables/subcontractor_mng/data", { + page: 1, size: 500, autoFilter: true, + }); + const rows = res.data?.data?.data || res.data?.data?.rows || []; + setSubcontractorOptions(rows.map((r: any) => ({ code: r.subcontractor_code || r.id, name: r.subcontractor_name || "" }))); + } catch { /* skip */ } + })(); + }, []); + useEffect(() => { const t = window.setTimeout(() => setRegisterSearchDebounced(registerSearch.trim()), 300); return () => window.clearTimeout(t); @@ -469,9 +483,9 @@ export function ItemRoutingTab() { details.map((d) => ({ ...d, process_display: d.process_name || d.process_code, - outsource_display: d.outsource_supplier || "—", + outsource_display: subcontractorOptions.find((s) => s.code === d.outsource_supplier)?.name || d.outsource_supplier || "—", })), - [details], + [details, subcontractorOptions], ); return ( @@ -896,12 +910,14 @@ export function ItemRoutingTab() { {showOutsourceField && (
- setFormOutsource(e.target.value)} - placeholder="외주 업체명" - className="h-9" - /> +
)}
diff --git a/frontend/app/(main)/COMPANY_10/production/process-info/ProcessMasterTab.tsx b/frontend/app/(main)/COMPANY_10/production/process-info/ProcessMasterTab.tsx index cfbee962..207fa3ad 100644 --- a/frontend/app/(main)/COMPANY_10/production/process-info/ProcessMasterTab.tsx +++ b/frontend/app/(main)/COMPANY_10/production/process-info/ProcessMasterTab.tsx @@ -221,18 +221,28 @@ export function ProcessMasterTab() { }; const openEdit = () => { - if (!selectedProcess) { - toast.message("수정할 공정을 좌측 목록에서 선택해주세요"); + if (selectedIds.size === 0) { + toast.message("수정할 공정을 체크박스로 선택해주세요"); + return; + } + if (selectedIds.size > 1) { + toast.message("수정은 1건만 선택해주세요"); + return; + } + const targetId = Array.from(selectedIds)[0]; + const target = processes.find((p) => p.id === targetId); + if (!target) { + toast.error("선택한 공정을 찾을 수 없습니다"); return; } setFormMode("edit"); - setEditingId(selectedProcess.id); - setFormProcessCode(selectedProcess.process_code); - setFormProcessName(selectedProcess.process_name); - setFormProcessType(selectedProcess.process_type); - setFormStandardTime(selectedProcess.standard_time ?? ""); - setFormWorkerCount(selectedProcess.worker_count ?? ""); - setFormUseYn(selectedProcess.use_yn); + setEditingId(target.id); + setFormProcessCode(target.process_code); + setFormProcessName(target.process_name); + setFormProcessType(target.process_type); + setFormStandardTime(target.standard_time ?? ""); + setFormWorkerCount(target.worker_count ?? ""); + setFormUseYn(target.use_yn); setFormOpen(true); }; diff --git a/frontend/app/(main)/COMPANY_16/equipment/info/page.tsx b/frontend/app/(main)/COMPANY_16/equipment/info/page.tsx index 4eb21404..0b297655 100644 --- a/frontend/app/(main)/COMPANY_16/equipment/info/page.tsx +++ b/frontend/app/(main)/COMPANY_16/equipment/info/page.tsx @@ -150,17 +150,17 @@ export default function EquipmentInfoPage() { const colProps: Record> = { equipment_code: { width: "w-[110px]" }, equipment_name: { minWidth: "min-w-[130px]", truncate: true, render: (v) => v || "-" }, - equipment_type: { width: "w-[90px]", render: (v) => v || "-" }, + equipment_type: { width: "w-[90px]", render: (v) => resolve("equipment_type", v) || v || "-" }, manufacturer: { width: "w-[100px]", render: (v) => v || "-" }, installation_location: { width: "w-[100px]", render: (v) => v || "-" }, - operation_status: { width: "w-[80px]", render: (v) => v || "-" }, + operation_status: { width: "w-[80px]", render: (v) => resolve("operation_status", v) || v || "-" }, }; return ts.visibleColumns.map((col) => ({ key: col.key, label: col.label, ...colProps[col.key], })); - }, [ts.visibleColumns]); + }, [ts.visibleColumns, catOptions]); // 설비 조회 const fetchEquipments = useCallback(async () => { @@ -173,11 +173,7 @@ export default function EquipmentInfoPage() { autoFilter: true, }); const raw = res.data?.data?.data || res.data?.data?.rows || []; - setEquipments(raw.map((r: any) => ({ - ...r, - equipment_type: resolve("equipment_type", r.equipment_type), - operation_status: resolve("operation_status", r.operation_status), - }))); + setEquipments(raw); setEquipCount(res.data?.data?.total || raw.length); } catch { toast.error("설비 목록 조회 실패"); } finally { setEquipLoading(false); } }, [searchFilters, catOptions]); @@ -482,9 +478,9 @@ export default function EquipmentInfoPage() { const handleExcelDownload = async () => { if (equipments.length === 0) return; await exportToExcel(equipments.map((e) => ({ - 설비코드: e.equipment_code, 설비명: e.equipment_name, 설비유형: e.equipment_type, + 설비코드: e.equipment_code, 설비명: e.equipment_name, 설비유형: resolve("equipment_type", e.equipment_type), 제조사: e.manufacturer, 모델명: e.model_name, 설치장소: e.installation_location, - 도입일자: e.introduction_date, 가동상태: e.operation_status, + 도입일자: e.introduction_date, 가동상태: resolve("operation_status", e.operation_status), })), "설비정보.xlsx", "설비"); toast.success("다운로드 완료"); }; diff --git a/frontend/app/(main)/COMPANY_16/master-data/company/page.tsx b/frontend/app/(main)/COMPANY_16/master-data/company/page.tsx index a607b7ea..4cf60273 100644 --- a/frontend/app/(main)/COMPANY_16/master-data/company/page.tsx +++ b/frontend/app/(main)/COMPANY_16/master-data/company/page.tsx @@ -563,10 +563,6 @@ export default function CompanyPage() { {/* 기본 정보 그리드 (2열) */}
-
- - -
@@ -470,7 +500,7 @@ export default function MoldInfoPage() {

{mold.mold_code}

{mold.mold_name}

{mold.mold_type && ( - {mold.mold_type} + {resolveMoldType(mold.mold_type)} )}
@@ -531,10 +561,7 @@ export default function MoldInfoPage() { 전체 - 사출금형 - 프레스금형 - 다이캐스팅 - 단조금형 + {moldTypeCatOptions.map((o) => {o.label})}
@@ -546,10 +573,7 @@ export default function MoldInfoPage() { 전체 - 사용중 - 점검중 - 수리중 - 폐기 + {operationStatusCatOptions.map((o) => {o.label})} @@ -670,13 +694,13 @@ export default function MoldInfoPage() {

{selectedMold.mold_name}

{selectedMold.mold_type && ( - {selectedMold.mold_type} + {resolveMoldType(selectedMold.mold_type)} )} {selectedMold.category && ( {selectedMold.category} )} - - {STATUS_MAP[selectedMold.operation_status]?.label || selectedMold.operation_status || "-"} + + {resolveOpStatus(selectedMold.operation_status) || "-"}
@@ -811,15 +835,15 @@ export default function MoldInfoPage() { {serials.map((s: any) => { - const ss = SERIAL_STATUS_MAP[s.status] || { label: s.status || "-", variant: "secondary" as const }; - const maxShot = detail?.shot_count || 0; + const ssLabel = resolveOpStatus(s.status); + const maxShot = selectedMold?.shot_count || 0; const curShot = s.current_shot_count || 0; const pct = maxShot > 0 ? Math.min(Math.round((curShot / maxShot) * 100), 100) : 0; return ( {s.serial_number} - {ss.label} + {ssLabel} {maxShot > 0 ? ( @@ -1043,10 +1067,7 @@ export default function MoldInfoPage() { - 사출금형 - 프레스금형 - 다이캐스팅 - 단조금형 + {moldTypeCatOptions.map((o) => {o.label})} @@ -1117,10 +1138,7 @@ export default function MoldInfoPage() { - 사용중 - 점검중 - 수리중 - 폐기 + {operationStatusCatOptions.map((o) => {o.label})} @@ -1175,10 +1193,7 @@ export default function MoldInfoPage() { - 사용중 - 보관중 - 수리중 - 폐기 + {operationStatusCatOptions.map((o) => {o.label})} diff --git a/frontend/app/(main)/COMPANY_16/production/bom/page.tsx b/frontend/app/(main)/COMPANY_16/production/bom/page.tsx index ef441cfc..8c7b582b 100644 --- a/frontend/app/(main)/COMPANY_16/production/bom/page.tsx +++ b/frontend/app/(main)/COMPANY_16/production/bom/page.tsx @@ -1530,53 +1530,6 @@ export default function BomManagementPage() { ) : (
- {/* 상세 카드 */} -
-
-

BOM 상세정보

- -
- {detailLoading ? ( -
- -
- ) : bomHeader ? ( -
-
- 품목코드 - {bomHeader.item_code || bomHeader.item_number || "-"} -
-
- 품명 - {bomHeader.item_name || "-"} -
-
- BOM 유형 - {BOM_TYPE_OPTIONS.find((o) => o.code === bomHeader.bom_type)?.label || bomHeader.bom_type || "-"} -
-
- 버전 - {bomHeader.version || "-"} -
-
- 기준수량 - {bomHeader.base_qty || "1"} {bomHeader.unit || ""} -
-
- 상태 - {renderStatusBadge(bomHeader.status)} -
-
- 메모 - {bomHeader.remark || "-"} -
-
- ) : null} -
- {/* 하단 탭: 트리뷰 / 버전 / 이력 */}
{ diff --git a/frontend/app/(main)/COMPANY_16/production/process-info/ItemRoutingTab.tsx b/frontend/app/(main)/COMPANY_16/production/process-info/ItemRoutingTab.tsx index e50e27d5..4eefd66c 100644 --- a/frontend/app/(main)/COMPANY_16/production/process-info/ItemRoutingTab.tsx +++ b/frontend/app/(main)/COMPANY_16/production/process-info/ItemRoutingTab.tsx @@ -92,6 +92,7 @@ export function ItemRoutingTab() { const [formWorkType, setFormWorkType] = useState("내부"); const [formStandardTime, setFormStandardTime] = useState(""); const [formOutsource, setFormOutsource] = useState(""); + const [subcontractorOptions, setSubcontractorOptions] = useState<{ code: string; name: string }[]>([]); const [detailSubmitting, setDetailSubmitting] = useState(false); const [registerDialogOpen, setRegisterDialogOpen] = useState(false); @@ -107,6 +108,19 @@ export function ItemRoutingTab() { return () => window.clearTimeout(t); }, [searchInput]); + // 외주사 목록 로드 + useEffect(() => { + (async () => { + try { + const res = await apiClient.post("/table-management/tables/subcontractor_mng/data", { + page: 1, size: 500, autoFilter: true, + }); + const rows = res.data?.data?.data || res.data?.data?.rows || []; + setSubcontractorOptions(rows.map((r: any) => ({ code: r.subcontractor_code || r.id, name: r.subcontractor_name || "" }))); + } catch { /* skip */ } + })(); + }, []); + useEffect(() => { const t = window.setTimeout(() => setRegisterSearchDebounced(registerSearch.trim()), 300); return () => window.clearTimeout(t); @@ -469,9 +483,9 @@ export function ItemRoutingTab() { details.map((d) => ({ ...d, process_display: d.process_name || d.process_code, - outsource_display: d.outsource_supplier || "—", + outsource_display: subcontractorOptions.find((s) => s.code === d.outsource_supplier)?.name || d.outsource_supplier || "—", })), - [details], + [details, subcontractorOptions], ); return ( @@ -896,12 +910,14 @@ export function ItemRoutingTab() { {showOutsourceField && (
- setFormOutsource(e.target.value)} - placeholder="외주 업체명" - className="h-9" - /> +
)}
diff --git a/frontend/app/(main)/COMPANY_16/production/process-info/ProcessMasterTab.tsx b/frontend/app/(main)/COMPANY_16/production/process-info/ProcessMasterTab.tsx index cfbee962..207fa3ad 100644 --- a/frontend/app/(main)/COMPANY_16/production/process-info/ProcessMasterTab.tsx +++ b/frontend/app/(main)/COMPANY_16/production/process-info/ProcessMasterTab.tsx @@ -221,18 +221,28 @@ export function ProcessMasterTab() { }; const openEdit = () => { - if (!selectedProcess) { - toast.message("수정할 공정을 좌측 목록에서 선택해주세요"); + if (selectedIds.size === 0) { + toast.message("수정할 공정을 체크박스로 선택해주세요"); + return; + } + if (selectedIds.size > 1) { + toast.message("수정은 1건만 선택해주세요"); + return; + } + const targetId = Array.from(selectedIds)[0]; + const target = processes.find((p) => p.id === targetId); + if (!target) { + toast.error("선택한 공정을 찾을 수 없습니다"); return; } setFormMode("edit"); - setEditingId(selectedProcess.id); - setFormProcessCode(selectedProcess.process_code); - setFormProcessName(selectedProcess.process_name); - setFormProcessType(selectedProcess.process_type); - setFormStandardTime(selectedProcess.standard_time ?? ""); - setFormWorkerCount(selectedProcess.worker_count ?? ""); - setFormUseYn(selectedProcess.use_yn); + setEditingId(target.id); + setFormProcessCode(target.process_code); + setFormProcessName(target.process_name); + setFormProcessType(target.process_type); + setFormStandardTime(target.standard_time ?? ""); + setFormWorkerCount(target.worker_count ?? ""); + setFormUseYn(target.use_yn); setFormOpen(true); }; diff --git a/frontend/app/(main)/COMPANY_29/equipment/info/page.tsx b/frontend/app/(main)/COMPANY_29/equipment/info/page.tsx index c2f3c0ca..17d81598 100644 --- a/frontend/app/(main)/COMPANY_29/equipment/info/page.tsx +++ b/frontend/app/(main)/COMPANY_29/equipment/info/page.tsx @@ -147,17 +147,17 @@ export default function EquipmentInfoPage() { const colProps: Record> = { equipment_code: { width: "w-[110px]" }, equipment_name: { minWidth: "min-w-[130px]", truncate: true, render: (v) => v || "-" }, - equipment_type: { width: "w-[90px]", render: (v) => v || "-" }, + equipment_type: { width: "w-[90px]", render: (v) => resolve("equipment_type", v) || v || "-" }, manufacturer: { width: "w-[100px]", render: (v) => v || "-" }, installation_location: { width: "w-[100px]", render: (v) => v || "-" }, - operation_status: { width: "w-[80px]", render: (v) => v || "-" }, + operation_status: { width: "w-[80px]", render: (v) => resolve("operation_status", v) || v || "-" }, }; return ts.visibleColumns.map((col) => ({ key: col.key, label: col.label, ...colProps[col.key], })); - }, [ts.visibleColumns]); + }, [ts.visibleColumns, catOptions]); // 설비 조회 const fetchEquipments = useCallback(async () => { @@ -170,11 +170,7 @@ export default function EquipmentInfoPage() { autoFilter: true, }); const raw = res.data?.data?.data || res.data?.data?.rows || []; - setEquipments(raw.map((r: any) => ({ - ...r, - equipment_type: resolve("equipment_type", r.equipment_type), - operation_status: resolve("operation_status", r.operation_status), - }))); + setEquipments(raw); setEquipCount(res.data?.data?.total || raw.length); } catch { toast.error("설비 목록 조회 실패"); } finally { setEquipLoading(false); } }, [searchFilters, catOptions]); @@ -437,9 +433,9 @@ export default function EquipmentInfoPage() { const handleExcelDownload = async () => { if (equipments.length === 0) return; await exportToExcel(equipments.map((e) => ({ - 설비코드: e.equipment_code, 설비명: e.equipment_name, 설비유형: e.equipment_type, + 설비코드: e.equipment_code, 설비명: e.equipment_name, 설비유형: resolve("equipment_type", e.equipment_type), 제조사: e.manufacturer, 모델명: e.model_name, 설치장소: e.installation_location, - 도입일자: e.introduction_date, 가동상태: e.operation_status, + 도입일자: e.introduction_date, 가동상태: resolve("operation_status", e.operation_status), })), "설비정보.xlsx", "설비"); toast.success("다운로드 완료"); }; diff --git a/frontend/app/(main)/COMPANY_29/master-data/company/page.tsx b/frontend/app/(main)/COMPANY_29/master-data/company/page.tsx index a607b7ea..4cf60273 100644 --- a/frontend/app/(main)/COMPANY_29/master-data/company/page.tsx +++ b/frontend/app/(main)/COMPANY_29/master-data/company/page.tsx @@ -563,10 +563,6 @@ export default function CompanyPage() { {/* 기본 정보 그리드 (2열) */}
-
- - -
@@ -470,7 +500,7 @@ export default function MoldInfoPage() {

{mold.mold_code}

{mold.mold_name}

{mold.mold_type && ( - {mold.mold_type} + {resolveMoldType(mold.mold_type)} )}
@@ -531,10 +561,7 @@ export default function MoldInfoPage() { 전체 - 사출금형 - 프레스금형 - 다이캐스팅 - 단조금형 + {moldTypeCatOptions.map((o) => {o.label})}
@@ -546,10 +573,7 @@ export default function MoldInfoPage() { 전체 - 사용중 - 점검중 - 수리중 - 폐기 + {operationStatusCatOptions.map((o) => {o.label})} @@ -670,13 +694,13 @@ export default function MoldInfoPage() {

{selectedMold.mold_name}

{selectedMold.mold_type && ( - {selectedMold.mold_type} + {resolveMoldType(selectedMold.mold_type)} )} {selectedMold.category && ( {selectedMold.category} )} - - {STATUS_MAP[selectedMold.operation_status]?.label || selectedMold.operation_status || "-"} + + {resolveOpStatus(selectedMold.operation_status) || "-"}
@@ -811,15 +835,15 @@ export default function MoldInfoPage() { {serials.map((s: any) => { - const ss = SERIAL_STATUS_MAP[s.status] || { label: s.status || "-", variant: "secondary" as const }; - const maxShot = detail?.shot_count || 0; + const ssLabel = resolveOpStatus(s.status); + const maxShot = selectedMold?.shot_count || 0; const curShot = s.current_shot_count || 0; const pct = maxShot > 0 ? Math.min(Math.round((curShot / maxShot) * 100), 100) : 0; return ( {s.serial_number} - {ss.label} + {ssLabel} {maxShot > 0 ? ( @@ -1043,10 +1067,7 @@ export default function MoldInfoPage() { - 사출금형 - 프레스금형 - 다이캐스팅 - 단조금형 + {moldTypeCatOptions.map((o) => {o.label})} @@ -1117,10 +1138,7 @@ export default function MoldInfoPage() { - 사용중 - 점검중 - 수리중 - 폐기 + {operationStatusCatOptions.map((o) => {o.label})} @@ -1175,10 +1193,7 @@ export default function MoldInfoPage() { - 사용중 - 보관중 - 수리중 - 폐기 + {operationStatusCatOptions.map((o) => {o.label})} diff --git a/frontend/app/(main)/COMPANY_29/production/bom/page.tsx b/frontend/app/(main)/COMPANY_29/production/bom/page.tsx index ef441cfc..8c7b582b 100644 --- a/frontend/app/(main)/COMPANY_29/production/bom/page.tsx +++ b/frontend/app/(main)/COMPANY_29/production/bom/page.tsx @@ -1530,53 +1530,6 @@ export default function BomManagementPage() { ) : (
- {/* 상세 카드 */} -
-
-

BOM 상세정보

- -
- {detailLoading ? ( -
- -
- ) : bomHeader ? ( -
-
- 품목코드 - {bomHeader.item_code || bomHeader.item_number || "-"} -
-
- 품명 - {bomHeader.item_name || "-"} -
-
- BOM 유형 - {BOM_TYPE_OPTIONS.find((o) => o.code === bomHeader.bom_type)?.label || bomHeader.bom_type || "-"} -
-
- 버전 - {bomHeader.version || "-"} -
-
- 기준수량 - {bomHeader.base_qty || "1"} {bomHeader.unit || ""} -
-
- 상태 - {renderStatusBadge(bomHeader.status)} -
-
- 메모 - {bomHeader.remark || "-"} -
-
- ) : null} -
- {/* 하단 탭: 트리뷰 / 버전 / 이력 */}
{ diff --git a/frontend/app/(main)/COMPANY_29/production/process-info/ItemRoutingTab.tsx b/frontend/app/(main)/COMPANY_29/production/process-info/ItemRoutingTab.tsx index e50e27d5..4eefd66c 100644 --- a/frontend/app/(main)/COMPANY_29/production/process-info/ItemRoutingTab.tsx +++ b/frontend/app/(main)/COMPANY_29/production/process-info/ItemRoutingTab.tsx @@ -92,6 +92,7 @@ export function ItemRoutingTab() { const [formWorkType, setFormWorkType] = useState("내부"); const [formStandardTime, setFormStandardTime] = useState(""); const [formOutsource, setFormOutsource] = useState(""); + const [subcontractorOptions, setSubcontractorOptions] = useState<{ code: string; name: string }[]>([]); const [detailSubmitting, setDetailSubmitting] = useState(false); const [registerDialogOpen, setRegisterDialogOpen] = useState(false); @@ -107,6 +108,19 @@ export function ItemRoutingTab() { return () => window.clearTimeout(t); }, [searchInput]); + // 외주사 목록 로드 + useEffect(() => { + (async () => { + try { + const res = await apiClient.post("/table-management/tables/subcontractor_mng/data", { + page: 1, size: 500, autoFilter: true, + }); + const rows = res.data?.data?.data || res.data?.data?.rows || []; + setSubcontractorOptions(rows.map((r: any) => ({ code: r.subcontractor_code || r.id, name: r.subcontractor_name || "" }))); + } catch { /* skip */ } + })(); + }, []); + useEffect(() => { const t = window.setTimeout(() => setRegisterSearchDebounced(registerSearch.trim()), 300); return () => window.clearTimeout(t); @@ -469,9 +483,9 @@ export function ItemRoutingTab() { details.map((d) => ({ ...d, process_display: d.process_name || d.process_code, - outsource_display: d.outsource_supplier || "—", + outsource_display: subcontractorOptions.find((s) => s.code === d.outsource_supplier)?.name || d.outsource_supplier || "—", })), - [details], + [details, subcontractorOptions], ); return ( @@ -896,12 +910,14 @@ export function ItemRoutingTab() { {showOutsourceField && (
- setFormOutsource(e.target.value)} - placeholder="외주 업체명" - className="h-9" - /> +
)}
diff --git a/frontend/app/(main)/COMPANY_29/production/process-info/ProcessMasterTab.tsx b/frontend/app/(main)/COMPANY_29/production/process-info/ProcessMasterTab.tsx index cfbee962..207fa3ad 100644 --- a/frontend/app/(main)/COMPANY_29/production/process-info/ProcessMasterTab.tsx +++ b/frontend/app/(main)/COMPANY_29/production/process-info/ProcessMasterTab.tsx @@ -221,18 +221,28 @@ export function ProcessMasterTab() { }; const openEdit = () => { - if (!selectedProcess) { - toast.message("수정할 공정을 좌측 목록에서 선택해주세요"); + if (selectedIds.size === 0) { + toast.message("수정할 공정을 체크박스로 선택해주세요"); + return; + } + if (selectedIds.size > 1) { + toast.message("수정은 1건만 선택해주세요"); + return; + } + const targetId = Array.from(selectedIds)[0]; + const target = processes.find((p) => p.id === targetId); + if (!target) { + toast.error("선택한 공정을 찾을 수 없습니다"); return; } setFormMode("edit"); - setEditingId(selectedProcess.id); - setFormProcessCode(selectedProcess.process_code); - setFormProcessName(selectedProcess.process_name); - setFormProcessType(selectedProcess.process_type); - setFormStandardTime(selectedProcess.standard_time ?? ""); - setFormWorkerCount(selectedProcess.worker_count ?? ""); - setFormUseYn(selectedProcess.use_yn); + setEditingId(target.id); + setFormProcessCode(target.process_code); + setFormProcessName(target.process_name); + setFormProcessType(target.process_type); + setFormStandardTime(target.standard_time ?? ""); + setFormWorkerCount(target.worker_count ?? ""); + setFormUseYn(target.use_yn); setFormOpen(true); }; diff --git a/frontend/app/(main)/COMPANY_30/equipment/info/page.tsx b/frontend/app/(main)/COMPANY_30/equipment/info/page.tsx index f415be9c..5aeac1f7 100644 --- a/frontend/app/(main)/COMPANY_30/equipment/info/page.tsx +++ b/frontend/app/(main)/COMPANY_30/equipment/info/page.tsx @@ -145,17 +145,17 @@ export default function EquipmentInfoPage() { const colProps: Record> = { equipment_code: { width: "w-[110px]" }, equipment_name: { minWidth: "min-w-[130px]", truncate: true, render: (v) => v || "-" }, - equipment_type: { width: "w-[90px]", render: (v) => v || "-" }, + equipment_type: { width: "w-[90px]", render: (v) => resolve("equipment_type", v) || v || "-" }, manufacturer: { width: "w-[100px]", render: (v) => v || "-" }, installation_location: { width: "w-[100px]", render: (v) => v || "-" }, - operation_status: { width: "w-[80px]", render: (v) => v || "-" }, + operation_status: { width: "w-[80px]", render: (v) => resolve("operation_status", v) || v || "-" }, }; return ts.visibleColumns.map((col) => ({ key: col.key, label: col.label, ...colProps[col.key], })); - }, [ts.visibleColumns]); + }, [ts.visibleColumns, catOptions]); // 설비 조회 const fetchEquipments = useCallback(async () => { @@ -168,11 +168,7 @@ export default function EquipmentInfoPage() { autoFilter: true, }); const raw = res.data?.data?.data || res.data?.data?.rows || []; - setEquipments(raw.map((r: any) => ({ - ...r, - equipment_type: resolve("equipment_type", r.equipment_type), - operation_status: resolve("operation_status", r.operation_status), - }))); + setEquipments(raw); setEquipCount(res.data?.data?.total || raw.length); } catch { toast.error("설비 목록 조회 실패"); } finally { setEquipLoading(false); } }, [searchFilters, catOptions]); @@ -415,9 +411,9 @@ export default function EquipmentInfoPage() { const handleExcelDownload = async () => { if (equipments.length === 0) return; await exportToExcel(equipments.map((e) => ({ - 설비코드: e.equipment_code, 설비명: e.equipment_name, 설비유형: e.equipment_type, + 설비코드: e.equipment_code, 설비명: e.equipment_name, 설비유형: resolve("equipment_type", e.equipment_type), 제조사: e.manufacturer, 모델명: e.model_name, 설치장소: e.installation_location, - 도입일자: e.introduction_date, 가동상태: e.operation_status, + 도입일자: e.introduction_date, 가동상태: resolve("operation_status", e.operation_status), })), "설비정보.xlsx", "설비"); toast.success("다운로드 완료"); }; diff --git a/frontend/app/(main)/COMPANY_30/master-data/company/page.tsx b/frontend/app/(main)/COMPANY_30/master-data/company/page.tsx index 0e4de0cd..347ea2ea 100644 --- a/frontend/app/(main)/COMPANY_30/master-data/company/page.tsx +++ b/frontend/app/(main)/COMPANY_30/master-data/company/page.tsx @@ -563,10 +563,6 @@ export default function CompanyPage() { {/* 기본 정보 그리드 (2열) */}
-
- - -
@@ -470,7 +500,7 @@ export default function MoldInfoPage() {

{mold.mold_code}

{mold.mold_name}

{mold.mold_type && ( - {mold.mold_type} + {resolveMoldType(mold.mold_type)} )}
@@ -531,10 +561,7 @@ export default function MoldInfoPage() { 전체 - 사출금형 - 프레스금형 - 다이캐스팅 - 단조금형 + {moldTypeCatOptions.map((o) => {o.label})}
@@ -546,10 +573,7 @@ export default function MoldInfoPage() { 전체 - 사용중 - 점검중 - 수리중 - 폐기 + {operationStatusCatOptions.map((o) => {o.label})} @@ -670,13 +694,13 @@ export default function MoldInfoPage() {

{selectedMold.mold_name}

{selectedMold.mold_type && ( - {selectedMold.mold_type} + {resolveMoldType(selectedMold.mold_type)} )} {selectedMold.category && ( {selectedMold.category} )} - - {STATUS_MAP[selectedMold.operation_status]?.label || selectedMold.operation_status || "-"} + + {resolveOpStatus(selectedMold.operation_status) || "-"}
@@ -811,15 +835,15 @@ export default function MoldInfoPage() { {serials.map((s: any) => { - const ss = SERIAL_STATUS_MAP[s.status] || { label: s.status || "-", variant: "secondary" as const }; - const maxShot = detail?.shot_count || 0; + const ssLabel = resolveOpStatus(s.status); + const maxShot = selectedMold?.shot_count || 0; const curShot = s.current_shot_count || 0; const pct = maxShot > 0 ? Math.min(Math.round((curShot / maxShot) * 100), 100) : 0; return ( {s.serial_number} - {ss.label} + {ssLabel} {maxShot > 0 ? ( @@ -1043,10 +1067,7 @@ export default function MoldInfoPage() { - 사출금형 - 프레스금형 - 다이캐스팅 - 단조금형 + {moldTypeCatOptions.map((o) => {o.label})} @@ -1117,10 +1138,7 @@ export default function MoldInfoPage() { - 사용중 - 점검중 - 수리중 - 폐기 + {operationStatusCatOptions.map((o) => {o.label})} @@ -1175,10 +1193,7 @@ export default function MoldInfoPage() { - 사용중 - 보관중 - 수리중 - 폐기 + {operationStatusCatOptions.map((o) => {o.label})} diff --git a/frontend/app/(main)/COMPANY_30/production/bom/page.tsx b/frontend/app/(main)/COMPANY_30/production/bom/page.tsx index 9b2e32c8..81356855 100644 --- a/frontend/app/(main)/COMPANY_30/production/bom/page.tsx +++ b/frontend/app/(main)/COMPANY_30/production/bom/page.tsx @@ -1530,53 +1530,6 @@ export default function BomManagementPage() { ) : (
- {/* 상세 카드 */} -
-
-

BOM 상세정보

- -
- {detailLoading ? ( -
- -
- ) : bomHeader ? ( -
-
- 품목코드 - {bomHeader.item_code || bomHeader.item_number || "-"} -
-
- 품명 - {bomHeader.item_name || "-"} -
-
- BOM 유형 - {BOM_TYPE_OPTIONS.find((o) => o.code === bomHeader.bom_type)?.label || bomHeader.bom_type || "-"} -
-
- 버전 - {bomHeader.version || "-"} -
-
- 기준수량 - {bomHeader.base_qty || "1"} {bomHeader.unit || ""} -
-
- 상태 - {renderStatusBadge(bomHeader.status)} -
-
- 메모 - {bomHeader.remark || "-"} -
-
- ) : null} -
- {/* 하단 탭: 트리뷰 / 버전 / 이력 */}
{ diff --git a/frontend/app/(main)/COMPANY_30/production/process-info/ItemRoutingTab.tsx b/frontend/app/(main)/COMPANY_30/production/process-info/ItemRoutingTab.tsx index e50e27d5..4eefd66c 100644 --- a/frontend/app/(main)/COMPANY_30/production/process-info/ItemRoutingTab.tsx +++ b/frontend/app/(main)/COMPANY_30/production/process-info/ItemRoutingTab.tsx @@ -92,6 +92,7 @@ export function ItemRoutingTab() { const [formWorkType, setFormWorkType] = useState("내부"); const [formStandardTime, setFormStandardTime] = useState(""); const [formOutsource, setFormOutsource] = useState(""); + const [subcontractorOptions, setSubcontractorOptions] = useState<{ code: string; name: string }[]>([]); const [detailSubmitting, setDetailSubmitting] = useState(false); const [registerDialogOpen, setRegisterDialogOpen] = useState(false); @@ -107,6 +108,19 @@ export function ItemRoutingTab() { return () => window.clearTimeout(t); }, [searchInput]); + // 외주사 목록 로드 + useEffect(() => { + (async () => { + try { + const res = await apiClient.post("/table-management/tables/subcontractor_mng/data", { + page: 1, size: 500, autoFilter: true, + }); + const rows = res.data?.data?.data || res.data?.data?.rows || []; + setSubcontractorOptions(rows.map((r: any) => ({ code: r.subcontractor_code || r.id, name: r.subcontractor_name || "" }))); + } catch { /* skip */ } + })(); + }, []); + useEffect(() => { const t = window.setTimeout(() => setRegisterSearchDebounced(registerSearch.trim()), 300); return () => window.clearTimeout(t); @@ -469,9 +483,9 @@ export function ItemRoutingTab() { details.map((d) => ({ ...d, process_display: d.process_name || d.process_code, - outsource_display: d.outsource_supplier || "—", + outsource_display: subcontractorOptions.find((s) => s.code === d.outsource_supplier)?.name || d.outsource_supplier || "—", })), - [details], + [details, subcontractorOptions], ); return ( @@ -896,12 +910,14 @@ export function ItemRoutingTab() { {showOutsourceField && (
- setFormOutsource(e.target.value)} - placeholder="외주 업체명" - className="h-9" - /> +
)}
diff --git a/frontend/app/(main)/COMPANY_30/production/process-info/ProcessMasterTab.tsx b/frontend/app/(main)/COMPANY_30/production/process-info/ProcessMasterTab.tsx index cfbee962..207fa3ad 100644 --- a/frontend/app/(main)/COMPANY_30/production/process-info/ProcessMasterTab.tsx +++ b/frontend/app/(main)/COMPANY_30/production/process-info/ProcessMasterTab.tsx @@ -221,18 +221,28 @@ export function ProcessMasterTab() { }; const openEdit = () => { - if (!selectedProcess) { - toast.message("수정할 공정을 좌측 목록에서 선택해주세요"); + if (selectedIds.size === 0) { + toast.message("수정할 공정을 체크박스로 선택해주세요"); + return; + } + if (selectedIds.size > 1) { + toast.message("수정은 1건만 선택해주세요"); + return; + } + const targetId = Array.from(selectedIds)[0]; + const target = processes.find((p) => p.id === targetId); + if (!target) { + toast.error("선택한 공정을 찾을 수 없습니다"); return; } setFormMode("edit"); - setEditingId(selectedProcess.id); - setFormProcessCode(selectedProcess.process_code); - setFormProcessName(selectedProcess.process_name); - setFormProcessType(selectedProcess.process_type); - setFormStandardTime(selectedProcess.standard_time ?? ""); - setFormWorkerCount(selectedProcess.worker_count ?? ""); - setFormUseYn(selectedProcess.use_yn); + setEditingId(target.id); + setFormProcessCode(target.process_code); + setFormProcessName(target.process_name); + setFormProcessType(target.process_type); + setFormStandardTime(target.standard_time ?? ""); + setFormWorkerCount(target.worker_count ?? ""); + setFormUseYn(target.use_yn); setFormOpen(true); }; diff --git a/frontend/app/(main)/COMPANY_7/equipment/info/page.tsx b/frontend/app/(main)/COMPANY_7/equipment/info/page.tsx index c2f3c0ca..17d81598 100644 --- a/frontend/app/(main)/COMPANY_7/equipment/info/page.tsx +++ b/frontend/app/(main)/COMPANY_7/equipment/info/page.tsx @@ -147,17 +147,17 @@ export default function EquipmentInfoPage() { const colProps: Record> = { equipment_code: { width: "w-[110px]" }, equipment_name: { minWidth: "min-w-[130px]", truncate: true, render: (v) => v || "-" }, - equipment_type: { width: "w-[90px]", render: (v) => v || "-" }, + equipment_type: { width: "w-[90px]", render: (v) => resolve("equipment_type", v) || v || "-" }, manufacturer: { width: "w-[100px]", render: (v) => v || "-" }, installation_location: { width: "w-[100px]", render: (v) => v || "-" }, - operation_status: { width: "w-[80px]", render: (v) => v || "-" }, + operation_status: { width: "w-[80px]", render: (v) => resolve("operation_status", v) || v || "-" }, }; return ts.visibleColumns.map((col) => ({ key: col.key, label: col.label, ...colProps[col.key], })); - }, [ts.visibleColumns]); + }, [ts.visibleColumns, catOptions]); // 설비 조회 const fetchEquipments = useCallback(async () => { @@ -170,11 +170,7 @@ export default function EquipmentInfoPage() { autoFilter: true, }); const raw = res.data?.data?.data || res.data?.data?.rows || []; - setEquipments(raw.map((r: any) => ({ - ...r, - equipment_type: resolve("equipment_type", r.equipment_type), - operation_status: resolve("operation_status", r.operation_status), - }))); + setEquipments(raw); setEquipCount(res.data?.data?.total || raw.length); } catch { toast.error("설비 목록 조회 실패"); } finally { setEquipLoading(false); } }, [searchFilters, catOptions]); @@ -437,9 +433,9 @@ export default function EquipmentInfoPage() { const handleExcelDownload = async () => { if (equipments.length === 0) return; await exportToExcel(equipments.map((e) => ({ - 설비코드: e.equipment_code, 설비명: e.equipment_name, 설비유형: e.equipment_type, + 설비코드: e.equipment_code, 설비명: e.equipment_name, 설비유형: resolve("equipment_type", e.equipment_type), 제조사: e.manufacturer, 모델명: e.model_name, 설치장소: e.installation_location, - 도입일자: e.introduction_date, 가동상태: e.operation_status, + 도입일자: e.introduction_date, 가동상태: resolve("operation_status", e.operation_status), })), "설비정보.xlsx", "설비"); toast.success("다운로드 완료"); }; diff --git a/frontend/app/(main)/COMPANY_7/master-data/company/page.tsx b/frontend/app/(main)/COMPANY_7/master-data/company/page.tsx index a607b7ea..4cf60273 100644 --- a/frontend/app/(main)/COMPANY_7/master-data/company/page.tsx +++ b/frontend/app/(main)/COMPANY_7/master-data/company/page.tsx @@ -563,10 +563,6 @@ export default function CompanyPage() { {/* 기본 정보 그리드 (2열) */}
-
- - -
@@ -470,7 +500,7 @@ export default function MoldInfoPage() {

{mold.mold_code}

{mold.mold_name}

{mold.mold_type && ( - {mold.mold_type} + {resolveMoldType(mold.mold_type)} )}
@@ -531,10 +561,7 @@ export default function MoldInfoPage() { 전체 - 사출금형 - 프레스금형 - 다이캐스팅 - 단조금형 + {moldTypeCatOptions.map((o) => {o.label})}
@@ -546,10 +573,7 @@ export default function MoldInfoPage() { 전체 - 사용중 - 점검중 - 수리중 - 폐기 + {operationStatusCatOptions.map((o) => {o.label})} @@ -670,13 +694,13 @@ export default function MoldInfoPage() {

{selectedMold.mold_name}

{selectedMold.mold_type && ( - {selectedMold.mold_type} + {resolveMoldType(selectedMold.mold_type)} )} {selectedMold.category && ( {selectedMold.category} )} - - {STATUS_MAP[selectedMold.operation_status]?.label || selectedMold.operation_status || "-"} + + {resolveOpStatus(selectedMold.operation_status) || "-"}
@@ -811,15 +835,15 @@ export default function MoldInfoPage() { {serials.map((s: any) => { - const ss = SERIAL_STATUS_MAP[s.status] || { label: s.status || "-", variant: "secondary" as const }; - const maxShot = detail?.shot_count || 0; + const ssLabel = resolveOpStatus(s.status); + const maxShot = selectedMold?.shot_count || 0; const curShot = s.current_shot_count || 0; const pct = maxShot > 0 ? Math.min(Math.round((curShot / maxShot) * 100), 100) : 0; return ( {s.serial_number} - {ss.label} + {ssLabel} {maxShot > 0 ? ( @@ -1043,10 +1067,7 @@ export default function MoldInfoPage() { - 사출금형 - 프레스금형 - 다이캐스팅 - 단조금형 + {moldTypeCatOptions.map((o) => {o.label})} @@ -1117,10 +1138,7 @@ export default function MoldInfoPage() { - 사용중 - 점검중 - 수리중 - 폐기 + {operationStatusCatOptions.map((o) => {o.label})} @@ -1175,10 +1193,7 @@ export default function MoldInfoPage() { - 사용중 - 보관중 - 수리중 - 폐기 + {operationStatusCatOptions.map((o) => {o.label})} diff --git a/frontend/app/(main)/COMPANY_7/production/bom/page.tsx b/frontend/app/(main)/COMPANY_7/production/bom/page.tsx index ef441cfc..8c7b582b 100644 --- a/frontend/app/(main)/COMPANY_7/production/bom/page.tsx +++ b/frontend/app/(main)/COMPANY_7/production/bom/page.tsx @@ -1530,53 +1530,6 @@ export default function BomManagementPage() { ) : (
- {/* 상세 카드 */} -
-
-

BOM 상세정보

- -
- {detailLoading ? ( -
- -
- ) : bomHeader ? ( -
-
- 품목코드 - {bomHeader.item_code || bomHeader.item_number || "-"} -
-
- 품명 - {bomHeader.item_name || "-"} -
-
- BOM 유형 - {BOM_TYPE_OPTIONS.find((o) => o.code === bomHeader.bom_type)?.label || bomHeader.bom_type || "-"} -
-
- 버전 - {bomHeader.version || "-"} -
-
- 기준수량 - {bomHeader.base_qty || "1"} {bomHeader.unit || ""} -
-
- 상태 - {renderStatusBadge(bomHeader.status)} -
-
- 메모 - {bomHeader.remark || "-"} -
-
- ) : null} -
- {/* 하단 탭: 트리뷰 / 버전 / 이력 */}
{ diff --git a/frontend/app/(main)/COMPANY_7/production/process-info/ItemRoutingTab.tsx b/frontend/app/(main)/COMPANY_7/production/process-info/ItemRoutingTab.tsx index e50e27d5..4eefd66c 100644 --- a/frontend/app/(main)/COMPANY_7/production/process-info/ItemRoutingTab.tsx +++ b/frontend/app/(main)/COMPANY_7/production/process-info/ItemRoutingTab.tsx @@ -92,6 +92,7 @@ export function ItemRoutingTab() { const [formWorkType, setFormWorkType] = useState("내부"); const [formStandardTime, setFormStandardTime] = useState(""); const [formOutsource, setFormOutsource] = useState(""); + const [subcontractorOptions, setSubcontractorOptions] = useState<{ code: string; name: string }[]>([]); const [detailSubmitting, setDetailSubmitting] = useState(false); const [registerDialogOpen, setRegisterDialogOpen] = useState(false); @@ -107,6 +108,19 @@ export function ItemRoutingTab() { return () => window.clearTimeout(t); }, [searchInput]); + // 외주사 목록 로드 + useEffect(() => { + (async () => { + try { + const res = await apiClient.post("/table-management/tables/subcontractor_mng/data", { + page: 1, size: 500, autoFilter: true, + }); + const rows = res.data?.data?.data || res.data?.data?.rows || []; + setSubcontractorOptions(rows.map((r: any) => ({ code: r.subcontractor_code || r.id, name: r.subcontractor_name || "" }))); + } catch { /* skip */ } + })(); + }, []); + useEffect(() => { const t = window.setTimeout(() => setRegisterSearchDebounced(registerSearch.trim()), 300); return () => window.clearTimeout(t); @@ -469,9 +483,9 @@ export function ItemRoutingTab() { details.map((d) => ({ ...d, process_display: d.process_name || d.process_code, - outsource_display: d.outsource_supplier || "—", + outsource_display: subcontractorOptions.find((s) => s.code === d.outsource_supplier)?.name || d.outsource_supplier || "—", })), - [details], + [details, subcontractorOptions], ); return ( @@ -896,12 +910,14 @@ export function ItemRoutingTab() { {showOutsourceField && (
- setFormOutsource(e.target.value)} - placeholder="외주 업체명" - className="h-9" - /> +
)}
diff --git a/frontend/app/(main)/COMPANY_7/production/process-info/ProcessMasterTab.tsx b/frontend/app/(main)/COMPANY_7/production/process-info/ProcessMasterTab.tsx index cfbee962..207fa3ad 100644 --- a/frontend/app/(main)/COMPANY_7/production/process-info/ProcessMasterTab.tsx +++ b/frontend/app/(main)/COMPANY_7/production/process-info/ProcessMasterTab.tsx @@ -221,18 +221,28 @@ export function ProcessMasterTab() { }; const openEdit = () => { - if (!selectedProcess) { - toast.message("수정할 공정을 좌측 목록에서 선택해주세요"); + if (selectedIds.size === 0) { + toast.message("수정할 공정을 체크박스로 선택해주세요"); + return; + } + if (selectedIds.size > 1) { + toast.message("수정은 1건만 선택해주세요"); + return; + } + const targetId = Array.from(selectedIds)[0]; + const target = processes.find((p) => p.id === targetId); + if (!target) { + toast.error("선택한 공정을 찾을 수 없습니다"); return; } setFormMode("edit"); - setEditingId(selectedProcess.id); - setFormProcessCode(selectedProcess.process_code); - setFormProcessName(selectedProcess.process_name); - setFormProcessType(selectedProcess.process_type); - setFormStandardTime(selectedProcess.standard_time ?? ""); - setFormWorkerCount(selectedProcess.worker_count ?? ""); - setFormUseYn(selectedProcess.use_yn); + setEditingId(target.id); + setFormProcessCode(target.process_code); + setFormProcessName(target.process_name); + setFormProcessType(target.process_type); + setFormStandardTime(target.standard_time ?? ""); + setFormWorkerCount(target.worker_count ?? ""); + setFormUseYn(target.use_yn); setFormOpen(true); }; diff --git a/frontend/app/(main)/COMPANY_8/equipment/info/page.tsx b/frontend/app/(main)/COMPANY_8/equipment/info/page.tsx index c2f3c0ca..17d81598 100644 --- a/frontend/app/(main)/COMPANY_8/equipment/info/page.tsx +++ b/frontend/app/(main)/COMPANY_8/equipment/info/page.tsx @@ -147,17 +147,17 @@ export default function EquipmentInfoPage() { const colProps: Record> = { equipment_code: { width: "w-[110px]" }, equipment_name: { minWidth: "min-w-[130px]", truncate: true, render: (v) => v || "-" }, - equipment_type: { width: "w-[90px]", render: (v) => v || "-" }, + equipment_type: { width: "w-[90px]", render: (v) => resolve("equipment_type", v) || v || "-" }, manufacturer: { width: "w-[100px]", render: (v) => v || "-" }, installation_location: { width: "w-[100px]", render: (v) => v || "-" }, - operation_status: { width: "w-[80px]", render: (v) => v || "-" }, + operation_status: { width: "w-[80px]", render: (v) => resolve("operation_status", v) || v || "-" }, }; return ts.visibleColumns.map((col) => ({ key: col.key, label: col.label, ...colProps[col.key], })); - }, [ts.visibleColumns]); + }, [ts.visibleColumns, catOptions]); // 설비 조회 const fetchEquipments = useCallback(async () => { @@ -170,11 +170,7 @@ export default function EquipmentInfoPage() { autoFilter: true, }); const raw = res.data?.data?.data || res.data?.data?.rows || []; - setEquipments(raw.map((r: any) => ({ - ...r, - equipment_type: resolve("equipment_type", r.equipment_type), - operation_status: resolve("operation_status", r.operation_status), - }))); + setEquipments(raw); setEquipCount(res.data?.data?.total || raw.length); } catch { toast.error("설비 목록 조회 실패"); } finally { setEquipLoading(false); } }, [searchFilters, catOptions]); @@ -437,9 +433,9 @@ export default function EquipmentInfoPage() { const handleExcelDownload = async () => { if (equipments.length === 0) return; await exportToExcel(equipments.map((e) => ({ - 설비코드: e.equipment_code, 설비명: e.equipment_name, 설비유형: e.equipment_type, + 설비코드: e.equipment_code, 설비명: e.equipment_name, 설비유형: resolve("equipment_type", e.equipment_type), 제조사: e.manufacturer, 모델명: e.model_name, 설치장소: e.installation_location, - 도입일자: e.introduction_date, 가동상태: e.operation_status, + 도입일자: e.introduction_date, 가동상태: resolve("operation_status", e.operation_status), })), "설비정보.xlsx", "설비"); toast.success("다운로드 완료"); }; diff --git a/frontend/app/(main)/COMPANY_8/master-data/company/page.tsx b/frontend/app/(main)/COMPANY_8/master-data/company/page.tsx index a607b7ea..4cf60273 100644 --- a/frontend/app/(main)/COMPANY_8/master-data/company/page.tsx +++ b/frontend/app/(main)/COMPANY_8/master-data/company/page.tsx @@ -563,10 +563,6 @@ export default function CompanyPage() { {/* 기본 정보 그리드 (2열) */}
-
- - -
- ) : numberingParts.some(p => p.isManual) ? ( - // 파트별 세그먼트 렌더링 (수동 입력 파트 있음) + ) : ( + // 자동 채번값 표시 + 사용자 직접 수정 가능 + setFormData(prev => ({ ...prev, [field.key]: e.target.value }))} + onFocus={(e) => e.target.select()} + placeholder="자동 채번 (직접 입력 가능)" + className="h-9" + /> + ) + /* 기존 세그먼트 UI 비활성화 — 대진산업은 직접 입력 허용 + numberingParts.some(p => p.isManual) ? (
{numberingParts.map((part, idx) => { const isFirst = idx === 0; @@ -852,7 +878,6 @@ export default function ItemInfoPage() { })}
) : ( - // 전체 auto: 읽기전용 표시 ) + */ ) : ["selling_price", "standard_price"].includes(field.key) ? ( + {ConfirmDialogComponent}
); } diff --git a/frontend/app/(main)/COMPANY_8/mold/info/page.tsx b/frontend/app/(main)/COMPANY_8/mold/info/page.tsx index 126c803d..ac3b2f9d 100644 --- a/frontend/app/(main)/COMPANY_8/mold/info/page.tsx +++ b/frontend/app/(main)/COMPANY_8/mold/info/page.tsx @@ -72,6 +72,36 @@ export default function MoldInfoPage() { const [selectedMoldCode, setSelectedMoldCode] = useState(null); const [viewMode, setViewMode] = useState<"list" | "grid">("list"); + // ─── 카테고리 옵션 (금형유형, 운영상태) ─── + const [moldTypeCatOptions, setMoldTypeCatOptions] = useState<{ code: string; label: string }[]>([]); + const [operationStatusCatOptions, setOperationStatusCatOptions] = useState<{ code: string; label: string }[]>([]); + + useEffect(() => { + const flatten = (arr: any[]): { code: string; label: string }[] => { + const result: { code: string; label: string }[] = []; + for (const v of arr) { result.push({ code: v.valueCode, label: v.valueLabel }); if (v.children?.length) result.push(...flatten(v.children)); } + return result; + }; + (async () => { + try { + const [typeRes, statusRes] = await Promise.all([ + apiClient.get("/table-categories/mold_mng/mold_type/values"), + apiClient.get("/table-categories/mold_mng/operation_status/values"), + ]); + if (typeRes.data?.success) setMoldTypeCatOptions(flatten(typeRes.data.data || [])); + if (statusRes.data?.success) setOperationStatusCatOptions(flatten(statusRes.data.data || [])); + } catch { /* skip */ } + })(); + }, []); + + const resolveMoldType = (code: string) => moldTypeCatOptions.find((o) => o.code === code)?.label || code; + const resolveOpStatus = (code: string) => { + const catLabel = operationStatusCatOptions.find((o) => o.code === code)?.label; + if (catLabel) return catLabel; + const legacyMap: Record = { ACTIVE: "사용중", INACTIVE: "미사용", REPAIR: "수리중", DISPOSED: "폐기", IN_USE: "사용중" }; + return legacyMap[code] || code; + }; + // ─── 검색 필터 ─── const [filterCode, setFilterCode] = useState(""); const [filterName, setFilterName] = useState(""); @@ -426,7 +456,7 @@ export default function MoldInfoPage() { // ─── 카드 렌더링 ─── const renderCard = (mold: any) => { const pct = calcLifePct(mold); - const st = STATUS_MAP[mold.operation_status] || { label: mold.operation_status || "-", variant: "secondary" as const }; + const stLabel = resolveOpStatus(mold.operation_status); const isSelected = selectedMoldCode === mold.mold_code; return ( @@ -460,7 +490,7 @@ export default function MoldInfoPage() { )}
- {st.label} + {stLabel}
@@ -470,7 +500,7 @@ export default function MoldInfoPage() {

{mold.mold_code}

{mold.mold_name}

{mold.mold_type && ( - {mold.mold_type} + {resolveMoldType(mold.mold_type)} )} @@ -531,10 +561,7 @@ export default function MoldInfoPage() { 전체 - 사출금형 - 프레스금형 - 다이캐스팅 - 단조금형 + {moldTypeCatOptions.map((o) => {o.label})} @@ -546,10 +573,7 @@ export default function MoldInfoPage() { 전체 - 사용중 - 점검중 - 수리중 - 폐기 + {operationStatusCatOptions.map((o) => {o.label})} @@ -670,13 +694,13 @@ export default function MoldInfoPage() {

{selectedMold.mold_name}

{selectedMold.mold_type && ( - {selectedMold.mold_type} + {resolveMoldType(selectedMold.mold_type)} )} {selectedMold.category && ( {selectedMold.category} )} - - {STATUS_MAP[selectedMold.operation_status]?.label || selectedMold.operation_status || "-"} + + {resolveOpStatus(selectedMold.operation_status) || "-"}
@@ -811,15 +835,15 @@ export default function MoldInfoPage() { {serials.map((s: any) => { - const ss = SERIAL_STATUS_MAP[s.status] || { label: s.status || "-", variant: "secondary" as const }; - const maxShot = detail?.shot_count || 0; + const ssLabel = resolveOpStatus(s.status); + const maxShot = selectedMold?.shot_count || 0; const curShot = s.current_shot_count || 0; const pct = maxShot > 0 ? Math.min(Math.round((curShot / maxShot) * 100), 100) : 0; return ( {s.serial_number} - {ss.label} + {ssLabel} {maxShot > 0 ? ( @@ -1043,10 +1067,7 @@ export default function MoldInfoPage() { - 사출금형 - 프레스금형 - 다이캐스팅 - 단조금형 + {moldTypeCatOptions.map((o) => {o.label})} @@ -1117,10 +1138,7 @@ export default function MoldInfoPage() { - 사용중 - 점검중 - 수리중 - 폐기 + {operationStatusCatOptions.map((o) => {o.label})} @@ -1175,10 +1193,7 @@ export default function MoldInfoPage() { - 사용중 - 보관중 - 수리중 - 폐기 + {operationStatusCatOptions.map((o) => {o.label})} diff --git a/frontend/app/(main)/COMPANY_8/outsourcing/subcontractor/page.tsx b/frontend/app/(main)/COMPANY_8/outsourcing/subcontractor/page.tsx index 2a5684b0..4a86ef27 100644 --- a/frontend/app/(main)/COMPANY_8/outsourcing/subcontractor/page.tsx +++ b/frontend/app/(main)/COMPANY_8/outsourcing/subcontractor/page.tsx @@ -1096,6 +1096,33 @@ export default function SubcontractorManagementPage() { /> {formErrors.business_number &&

{formErrors.business_number}

} +
+ + setSubcontractorForm((p) => ({ ...p, representative: e.target.value }))} + placeholder="대표이름" + className="h-9 text-sm" + /> +
+
+ + handleFormChange("phone", e.target.value)} + placeholder="02-0000-0000" + className="h-9 text-sm" + /> +
+
+ + handleFormChange("fax", e.target.value)} + placeholder="02-0000-0000" + className="h-9 text-sm" + /> +
) : (
- {/* 상세 카드 */} -
-
-

BOM 상세정보

- -
- {detailLoading ? ( -
- -
- ) : bomHeader ? ( -
-
- 품목코드 - {bomHeader.item_code || bomHeader.item_number || "-"} -
-
- 품명 - {bomHeader.item_name || "-"} -
-
- BOM 유형 - {BOM_TYPE_OPTIONS.find((o) => o.code === bomHeader.bom_type)?.label || bomHeader.bom_type || "-"} -
-
- 버전 - {bomHeader.version || "-"} -
-
- 기준수량 - {bomHeader.base_qty || "1"} {bomHeader.unit || ""} -
-
- 상태 - {renderStatusBadge(bomHeader.status)} -
-
- 메모 - {bomHeader.remark || "-"} -
-
- ) : null} -
- {/* 하단 탭: 트리뷰 / 버전 / 이력 */}
{ diff --git a/frontend/app/(main)/COMPANY_8/production/process-info/ItemRoutingTab.tsx b/frontend/app/(main)/COMPANY_8/production/process-info/ItemRoutingTab.tsx index e50e27d5..4eefd66c 100644 --- a/frontend/app/(main)/COMPANY_8/production/process-info/ItemRoutingTab.tsx +++ b/frontend/app/(main)/COMPANY_8/production/process-info/ItemRoutingTab.tsx @@ -92,6 +92,7 @@ export function ItemRoutingTab() { const [formWorkType, setFormWorkType] = useState("내부"); const [formStandardTime, setFormStandardTime] = useState(""); const [formOutsource, setFormOutsource] = useState(""); + const [subcontractorOptions, setSubcontractorOptions] = useState<{ code: string; name: string }[]>([]); const [detailSubmitting, setDetailSubmitting] = useState(false); const [registerDialogOpen, setRegisterDialogOpen] = useState(false); @@ -107,6 +108,19 @@ export function ItemRoutingTab() { return () => window.clearTimeout(t); }, [searchInput]); + // 외주사 목록 로드 + useEffect(() => { + (async () => { + try { + const res = await apiClient.post("/table-management/tables/subcontractor_mng/data", { + page: 1, size: 500, autoFilter: true, + }); + const rows = res.data?.data?.data || res.data?.data?.rows || []; + setSubcontractorOptions(rows.map((r: any) => ({ code: r.subcontractor_code || r.id, name: r.subcontractor_name || "" }))); + } catch { /* skip */ } + })(); + }, []); + useEffect(() => { const t = window.setTimeout(() => setRegisterSearchDebounced(registerSearch.trim()), 300); return () => window.clearTimeout(t); @@ -469,9 +483,9 @@ export function ItemRoutingTab() { details.map((d) => ({ ...d, process_display: d.process_name || d.process_code, - outsource_display: d.outsource_supplier || "—", + outsource_display: subcontractorOptions.find((s) => s.code === d.outsource_supplier)?.name || d.outsource_supplier || "—", })), - [details], + [details, subcontractorOptions], ); return ( @@ -896,12 +910,14 @@ export function ItemRoutingTab() { {showOutsourceField && (
- setFormOutsource(e.target.value)} - placeholder="외주 업체명" - className="h-9" - /> +
)}
diff --git a/frontend/app/(main)/COMPANY_8/production/process-info/ProcessMasterTab.tsx b/frontend/app/(main)/COMPANY_8/production/process-info/ProcessMasterTab.tsx index cfbee962..ab75ae0d 100644 --- a/frontend/app/(main)/COMPANY_8/production/process-info/ProcessMasterTab.tsx +++ b/frontend/app/(main)/COMPANY_8/production/process-info/ProcessMasterTab.tsx @@ -221,18 +221,29 @@ export function ProcessMasterTab() { }; const openEdit = () => { - if (!selectedProcess) { - toast.message("수정할 공정을 좌측 목록에서 선택해주세요"); + // 체크박스 기준: 1개만 체크된 경우 수정 + if (selectedIds.size === 0) { + toast.message("수정할 공정을 체크박스로 선택해주세요"); + return; + } + if (selectedIds.size > 1) { + toast.message("수정은 1건만 선택해주세요"); + return; + } + const targetId = Array.from(selectedIds)[0]; + const target = processes.find((p) => p.id === targetId); + if (!target) { + toast.error("선택한 공정을 찾을 수 없습니다"); return; } setFormMode("edit"); - setEditingId(selectedProcess.id); - setFormProcessCode(selectedProcess.process_code); - setFormProcessName(selectedProcess.process_name); - setFormProcessType(selectedProcess.process_type); - setFormStandardTime(selectedProcess.standard_time ?? ""); - setFormWorkerCount(selectedProcess.worker_count ?? ""); - setFormUseYn(selectedProcess.use_yn); + setEditingId(target.id); + setFormProcessCode(target.process_code); + setFormProcessName(target.process_name); + setFormProcessType(target.process_type); + setFormStandardTime(target.standard_time ?? ""); + setFormWorkerCount(target.worker_count ?? ""); + setFormUseYn(target.use_yn); setFormOpen(true); }; diff --git a/frontend/app/(main)/COMPANY_8/purchase/supplier/page.tsx b/frontend/app/(main)/COMPANY_8/purchase/supplier/page.tsx index 964e7e09..355a67e5 100644 --- a/frontend/app/(main)/COMPANY_8/purchase/supplier/page.tsx +++ b/frontend/app/(main)/COMPANY_8/purchase/supplier/page.tsx @@ -1896,6 +1896,33 @@ export default function SupplierManagementPage() { /> {formErrors.business_number &&

{formErrors.business_number}

}
+
+ + setSupplierForm((p) => ({ ...p, representative_name: e.target.value }))} + placeholder="대표이름" + className="h-9" + /> +
+
+ + handleFormChange("phone", e.target.value)} + placeholder="02-0000-0000" + className="h-9" + /> +
+
+ + handleFormChange("fax_number", e.target.value)} + placeholder="02-0000-0000" + className="h-9" + /> +
{formErrors.business_number &&

{formErrors.business_number}

}
+
+ + setCustomerForm((p) => ({ ...p, representative_name: e.target.value }))} + placeholder="대표이름" + className="h-9" + /> +
+
+ + handleFormChange("phone", e.target.value)} + placeholder="02-0000-0000" + className="h-9" + /> +
+
+ + handleFormChange("fax", e.target.value)} + placeholder="02-0000-0000" + className="h-9" + /> +
+
+ + handleFormChange("email", e.target.value)} + placeholder="example@email.com" + className={cn("h-9", formErrors.email && "border-destructive")} + /> + {formErrors.email &&

{formErrors.email}

} +
> = { equipment_code: { width: "w-[110px]" }, equipment_name: { minWidth: "min-w-[130px]", truncate: true, render: (v) => v || "-" }, - equipment_type: { width: "w-[90px]", render: (v) => v || "-" }, + equipment_type: { width: "w-[90px]", render: (v) => resolve("equipment_type", v) || v || "-" }, manufacturer: { width: "w-[100px]", render: (v) => v || "-" }, installation_location: { width: "w-[100px]", render: (v) => v || "-" }, - operation_status: { width: "w-[80px]", render: (v) => v || "-" }, + operation_status: { width: "w-[80px]", render: (v) => resolve("operation_status", v) || v || "-" }, }; return ts.visibleColumns.map((col) => ({ key: col.key, label: col.label, ...colProps[col.key], })); - }, [ts.visibleColumns]); + }, [ts.visibleColumns, catOptions]); // 설비 조회 const fetchEquipments = useCallback(async () => { @@ -170,11 +170,7 @@ export default function EquipmentInfoPage() { autoFilter: true, }); const raw = res.data?.data?.data || res.data?.data?.rows || []; - setEquipments(raw.map((r: any) => ({ - ...r, - equipment_type: resolve("equipment_type", r.equipment_type), - operation_status: resolve("operation_status", r.operation_status), - }))); + setEquipments(raw); setEquipCount(res.data?.data?.total || raw.length); } catch { toast.error("설비 목록 조회 실패"); } finally { setEquipLoading(false); } }, [searchFilters, catOptions]); @@ -437,9 +433,9 @@ export default function EquipmentInfoPage() { const handleExcelDownload = async () => { if (equipments.length === 0) return; await exportToExcel(equipments.map((e) => ({ - 설비코드: e.equipment_code, 설비명: e.equipment_name, 설비유형: e.equipment_type, + 설비코드: e.equipment_code, 설비명: e.equipment_name, 설비유형: resolve("equipment_type", e.equipment_type), 제조사: e.manufacturer, 모델명: e.model_name, 설치장소: e.installation_location, - 도입일자: e.introduction_date, 가동상태: e.operation_status, + 도입일자: e.introduction_date, 가동상태: resolve("operation_status", e.operation_status), })), "설비정보.xlsx", "설비"); toast.success("다운로드 완료"); }; diff --git a/frontend/app/(main)/COMPANY_9/master-data/company/page.tsx b/frontend/app/(main)/COMPANY_9/master-data/company/page.tsx index 0e4de0cd..347ea2ea 100644 --- a/frontend/app/(main)/COMPANY_9/master-data/company/page.tsx +++ b/frontend/app/(main)/COMPANY_9/master-data/company/page.tsx @@ -563,10 +563,6 @@ export default function CompanyPage() { {/* 기본 정보 그리드 (2열) */}
-
- - -
@@ -470,7 +500,7 @@ export default function MoldInfoPage() {

{mold.mold_code}

{mold.mold_name}

{mold.mold_type && ( - {mold.mold_type} + {resolveMoldType(mold.mold_type)} )}
@@ -531,10 +561,7 @@ export default function MoldInfoPage() { 전체 - 사출금형 - 프레스금형 - 다이캐스팅 - 단조금형 + {moldTypeCatOptions.map((o) => {o.label})}
@@ -546,10 +573,7 @@ export default function MoldInfoPage() { 전체 - 사용중 - 점검중 - 수리중 - 폐기 + {operationStatusCatOptions.map((o) => {o.label})}
@@ -670,13 +694,13 @@ export default function MoldInfoPage() {

{selectedMold.mold_name}

{selectedMold.mold_type && ( - {selectedMold.mold_type} + {resolveMoldType(selectedMold.mold_type)} )} {selectedMold.category && ( {selectedMold.category} )} - - {STATUS_MAP[selectedMold.operation_status]?.label || selectedMold.operation_status || "-"} + + {resolveOpStatus(selectedMold.operation_status) || "-"}
@@ -811,15 +835,15 @@ export default function MoldInfoPage() { {serials.map((s: any) => { - const ss = SERIAL_STATUS_MAP[s.status] || { label: s.status || "-", variant: "secondary" as const }; - const maxShot = detail?.shot_count || 0; + const ssLabel = resolveOpStatus(s.status); + const maxShot = selectedMold?.shot_count || 0; const curShot = s.current_shot_count || 0; const pct = maxShot > 0 ? Math.min(Math.round((curShot / maxShot) * 100), 100) : 0; return ( {s.serial_number} - {ss.label} + {ssLabel} {maxShot > 0 ? ( @@ -1043,10 +1067,7 @@ export default function MoldInfoPage() { - 사출금형 - 프레스금형 - 다이캐스팅 - 단조금형 + {moldTypeCatOptions.map((o) => {o.label})} @@ -1117,10 +1138,7 @@ export default function MoldInfoPage() { - 사용중 - 점검중 - 수리중 - 폐기 + {operationStatusCatOptions.map((o) => {o.label})} @@ -1175,10 +1193,7 @@ export default function MoldInfoPage() { - 사용중 - 보관중 - 수리중 - 폐기 + {operationStatusCatOptions.map((o) => {o.label})} diff --git a/frontend/app/(main)/COMPANY_9/production/bom/page.tsx b/frontend/app/(main)/COMPANY_9/production/bom/page.tsx index 9b2e32c8..81356855 100644 --- a/frontend/app/(main)/COMPANY_9/production/bom/page.tsx +++ b/frontend/app/(main)/COMPANY_9/production/bom/page.tsx @@ -1530,53 +1530,6 @@ export default function BomManagementPage() { ) : (
- {/* 상세 카드 */} -
-
-

BOM 상세정보

- -
- {detailLoading ? ( -
- -
- ) : bomHeader ? ( -
-
- 품목코드 - {bomHeader.item_code || bomHeader.item_number || "-"} -
-
- 품명 - {bomHeader.item_name || "-"} -
-
- BOM 유형 - {BOM_TYPE_OPTIONS.find((o) => o.code === bomHeader.bom_type)?.label || bomHeader.bom_type || "-"} -
-
- 버전 - {bomHeader.version || "-"} -
-
- 기준수량 - {bomHeader.base_qty || "1"} {bomHeader.unit || ""} -
-
- 상태 - {renderStatusBadge(bomHeader.status)} -
-
- 메모 - {bomHeader.remark || "-"} -
-
- ) : null} -
- {/* 하단 탭: 트리뷰 / 버전 / 이력 */}
{ diff --git a/frontend/app/(main)/COMPANY_9/production/process-info/ItemRoutingTab.tsx b/frontend/app/(main)/COMPANY_9/production/process-info/ItemRoutingTab.tsx index e50e27d5..4eefd66c 100644 --- a/frontend/app/(main)/COMPANY_9/production/process-info/ItemRoutingTab.tsx +++ b/frontend/app/(main)/COMPANY_9/production/process-info/ItemRoutingTab.tsx @@ -92,6 +92,7 @@ export function ItemRoutingTab() { const [formWorkType, setFormWorkType] = useState("내부"); const [formStandardTime, setFormStandardTime] = useState(""); const [formOutsource, setFormOutsource] = useState(""); + const [subcontractorOptions, setSubcontractorOptions] = useState<{ code: string; name: string }[]>([]); const [detailSubmitting, setDetailSubmitting] = useState(false); const [registerDialogOpen, setRegisterDialogOpen] = useState(false); @@ -107,6 +108,19 @@ export function ItemRoutingTab() { return () => window.clearTimeout(t); }, [searchInput]); + // 외주사 목록 로드 + useEffect(() => { + (async () => { + try { + const res = await apiClient.post("/table-management/tables/subcontractor_mng/data", { + page: 1, size: 500, autoFilter: true, + }); + const rows = res.data?.data?.data || res.data?.data?.rows || []; + setSubcontractorOptions(rows.map((r: any) => ({ code: r.subcontractor_code || r.id, name: r.subcontractor_name || "" }))); + } catch { /* skip */ } + })(); + }, []); + useEffect(() => { const t = window.setTimeout(() => setRegisterSearchDebounced(registerSearch.trim()), 300); return () => window.clearTimeout(t); @@ -469,9 +483,9 @@ export function ItemRoutingTab() { details.map((d) => ({ ...d, process_display: d.process_name || d.process_code, - outsource_display: d.outsource_supplier || "—", + outsource_display: subcontractorOptions.find((s) => s.code === d.outsource_supplier)?.name || d.outsource_supplier || "—", })), - [details], + [details, subcontractorOptions], ); return ( @@ -896,12 +910,14 @@ export function ItemRoutingTab() { {showOutsourceField && (
- setFormOutsource(e.target.value)} - placeholder="외주 업체명" - className="h-9" - /> +
)}
diff --git a/frontend/app/(main)/COMPANY_9/production/process-info/ProcessMasterTab.tsx b/frontend/app/(main)/COMPANY_9/production/process-info/ProcessMasterTab.tsx index cfbee962..207fa3ad 100644 --- a/frontend/app/(main)/COMPANY_9/production/process-info/ProcessMasterTab.tsx +++ b/frontend/app/(main)/COMPANY_9/production/process-info/ProcessMasterTab.tsx @@ -221,18 +221,28 @@ export function ProcessMasterTab() { }; const openEdit = () => { - if (!selectedProcess) { - toast.message("수정할 공정을 좌측 목록에서 선택해주세요"); + if (selectedIds.size === 0) { + toast.message("수정할 공정을 체크박스로 선택해주세요"); + return; + } + if (selectedIds.size > 1) { + toast.message("수정은 1건만 선택해주세요"); + return; + } + const targetId = Array.from(selectedIds)[0]; + const target = processes.find((p) => p.id === targetId); + if (!target) { + toast.error("선택한 공정을 찾을 수 없습니다"); return; } setFormMode("edit"); - setEditingId(selectedProcess.id); - setFormProcessCode(selectedProcess.process_code); - setFormProcessName(selectedProcess.process_name); - setFormProcessType(selectedProcess.process_type); - setFormStandardTime(selectedProcess.standard_time ?? ""); - setFormWorkerCount(selectedProcess.worker_count ?? ""); - setFormUseYn(selectedProcess.use_yn); + setEditingId(target.id); + setFormProcessCode(target.process_code); + setFormProcessName(target.process_name); + setFormProcessType(target.process_type); + setFormStandardTime(target.standard_time ?? ""); + setFormWorkerCount(target.worker_count ?? ""); + setFormUseYn(target.use_yn); setFormOpen(true); }; diff --git a/frontend/components/common/EDataTable.tsx b/frontend/components/common/EDataTable.tsx index 1b16a772..c1a47615 100644 --- a/frontend/components/common/EDataTable.tsx +++ b/frontend/components/common/EDataTable.tsx @@ -715,16 +715,17 @@ export function EDataTable = any>({ const id = getRowId(row, rowKey); const isSelected = selectedId != null && String(selectedId) === String(id); const isChecked = checkedIds.includes(id); - const highlighted = isSelected || isChecked; return ( { onSelect?.(id);