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 207fa3ad..deb8a99c 100644
--- a/frontend/app/(main)/COMPANY_10/production/process-info/ProcessMasterTab.tsx
+++ b/frontend/app/(main)/COMPANY_10/production/process-info/ProcessMasterTab.tsx
@@ -47,6 +47,7 @@ import {
} from "@/components/ui/table";
import { Checkbox } from "@/components/ui/checkbox";
import { EDataTable, EDataTableColumn } from "@/components/common/EDataTable";
+import { SmartSelect } from "@/components/common/SmartSelect";
import {
getProcessList,
createProcess,
@@ -511,23 +512,17 @@ export function ProcessMasterTab() {
-
+ />
- {showShortcutHint && (
-
- 탭 전환:
- {TAB_META.map((t) => (
-
-
- Alt+{t.shortcut}
-
- {t.shortLabel}
-
- ))}
-
- )}
- {TAB_META.map(({ value, label, icon: Icon, shortcut }) => (
+ {TAB_META.map(({ value, label, icon: Icon }) => (
{label}
@@ -168,34 +128,6 @@ export default function ProcessInfoPage() {
- {/* 탭 설명 배너 */}
-
-
-
-
- {activeMeta.shortLabel}
-
- {activeMeta.detailDesc}
-
-
- {activeMeta.actions.map((action, i) => {
- const ActionIcon = ACTION_ICONS[i % ACTION_ICONS.length];
- return (
-
-
- {action}
-
- );
- })}
-
-
-
-
{/* 탭 컨텐츠 */}
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 207fa3ad..deb8a99c 100644
--- a/frontend/app/(main)/COMPANY_16/production/process-info/ProcessMasterTab.tsx
+++ b/frontend/app/(main)/COMPANY_16/production/process-info/ProcessMasterTab.tsx
@@ -47,6 +47,7 @@ import {
} from "@/components/ui/table";
import { Checkbox } from "@/components/ui/checkbox";
import { EDataTable, EDataTableColumn } from "@/components/common/EDataTable";
+import { SmartSelect } from "@/components/common/SmartSelect";
import {
getProcessList,
createProcess,
@@ -511,23 +512,17 @@ export function ProcessMasterTab() {
-
+ />
("process");
- const [showShortcutHint, setShowShortcutHint] = useState(false);
const activeMeta = TAB_META.find((t) => t.value === activeTab)!;
@@ -85,19 +80,6 @@ export default function ProcessInfoPage() {
setActiveTab(value as TabValue);
}, []);
- useEffect(() => {
- const handleKeyDown = (e: KeyboardEvent) => {
- if (!e.altKey) return;
- const tabByShortcut = TAB_META.find((t) => t.shortcut === e.key);
- if (tabByShortcut) {
- e.preventDefault();
- setActiveTab(tabByShortcut.value);
- }
- };
- window.addEventListener("keydown", handleKeyDown);
- return () => window.removeEventListener("keydown", handleKeyDown);
- }, []);
-
return (
{/* 페이지 헤더 */}
@@ -120,30 +102,8 @@ export default function ProcessInfoPage() {
ts.setOpen(true)} title="테이블 설정">
- setShowShortcutHint((v) => !v)}
- aria-label="키보드 단축키 보기"
- >
-
- 단축키
-
- {showShortcutHint && (
-
- 탭 전환:
- {TAB_META.map((t) => (
-
-
- Alt+{t.shortcut}
-
- {t.shortLabel}
-
- ))}
-
- )}
- {TAB_META.map(({ value, label, icon: Icon, shortcut }) => (
+ {TAB_META.map(({ value, label, icon: Icon }) => (
{label}
@@ -168,34 +128,6 @@ export default function ProcessInfoPage() {
- {/* 탭 설명 배너 */}
-
-
-
-
- {activeMeta.shortLabel}
-
- {activeMeta.detailDesc}
-
-
- {activeMeta.actions.map((action, i) => {
- const ActionIcon = ACTION_ICONS[i % ACTION_ICONS.length];
- return (
-
-
- {action}
-
- );
- })}
-
-
-
-
{/* 탭 컨텐츠 */}
diff --git a/frontend/app/(main)/COMPANY_16/quality/item-inspection/page.tsx b/frontend/app/(main)/COMPANY_16/quality/item-inspection/page.tsx
index 15118ffc..80d61947 100644
--- a/frontend/app/(main)/COMPANY_16/quality/item-inspection/page.tsx
+++ b/frontend/app/(main)/COMPANY_16/quality/item-inspection/page.tsx
@@ -98,6 +98,11 @@ export default function ItemInspectionInfoPage() {
const [inspectionRows, setInspectionRows] = useState>({});
const [collapsedTypes, setCollapsedTypes] = useState>({});
+ // 복사 모달: 편집 가능한 기준 데이터 상태 (등록/수정 폼과 평행 구조)
+ const [copyForm, setCopyForm] = useState>({});
+ const [copyInspectionRows, setCopyInspectionRows] = useState>({});
+ const [copyCollapsedTypes, setCopyCollapsedTypes] = useState>({});
+
// 기본 라우팅 공정 목록 (적용공정 Select용)
const [processOptions, setProcessOptions] = useState<{ code: string; name: string }[]>([]);
@@ -294,11 +299,62 @@ export default function ItemInspectionInfoPage() {
setCopyTotal(resData?.total || resData?.totalCount || rows.length);
} catch { /* skip */ } finally { setCopySearchLoading(false); }
};
- const openCopyModal = () => {
+ const openCopyModal = async () => {
if (!selectedItemCode) { toast.error("복사 기준 품목을 먼저 선택해주세요"); return; }
const srcGroup = groupedData.find(g => g.item_code === selectedItemCode);
if (!srcGroup || srcGroup.rows.length === 0) { toast.error("선택한 품목에 복사할 검사정보가 없어요"); return; }
setCopySearchKeyword(""); setCopyPage(1); setCopyCheckedIds([]);
+
+ // 기준 품목 데이터를 편집용 상태로 복제 (openEdit과 동일한 변환 로직)
+ const baseRow = srcGroup.rows[0];
+ try {
+ const res = await apiClient.post(`/table-management/tables/${TABLE_NAME}/data`, {
+ page: 1, size: 0,
+ dataFilter: { enabled: true, filters: [{ columnName: "item_code", operator: "equals", value: selectedItemCode }] },
+ autoFilter: true,
+ });
+ const allRows = res.data?.data?.data || res.data?.data?.rows || [];
+ const rowMap: Record = {};
+ const typeFlags: Record = {};
+ for (const r of allRows) {
+ const inspType = r.inspection_type || "";
+ const matched = INSPECTION_TYPES.find(t =>
+ t.matchLabels.some(ml => inspType.includes(ml)) ||
+ inspTypeCatOptions.some(cat => inspType.includes(cat.code) && t.matchLabels.some(ml => cat.label.includes(ml)))
+ );
+ const typeKey = matched?.key || "";
+ if (!typeKey) continue;
+ typeFlags[typeKey] = true;
+ if (!rowMap[typeKey]) rowMap[typeKey] = [];
+ const mCode = r.inspection_method || "";
+ const mLabel = inspMethodCatOptions.find(o => o.code === mCode)?.label || mCode;
+ const inspOpt = inspOptions.find(o => o.code === r.inspection_standard_id);
+ const jcCode = inspOpt?.judgment_criteria || "";
+ const jcLabel = judgmentCatOptions.find(c => c.code === jcCode)?.label || jcCode;
+ const unitCode = inspOpt?.unit || "";
+ const unitLabel = inspUnitCatOptions.find(c => c.code === unitCode)?.label || unitCode;
+ rowMap[typeKey].push({
+ id: crypto.randomUUID(), // 복사본은 새 id 부여 (원본과 분리)
+ inspection_standard_id: r.inspection_standard_id || "",
+ inspection_detail: r.inspection_item_name || r.inspection_standard || "",
+ inspection_method: mLabel,
+ apply_process: "",
+ acceptance_criteria: r.pass_criteria || "",
+ is_required: r.is_required === "true" || r.is_required === true,
+ judgment_criteria: jcLabel,
+ selection_options: inspOpt?.selection_options || "",
+ unit: unitLabel,
+ });
+ }
+ setCopyInspectionRows(rowMap);
+ setCopyForm({ ...baseRow, ...typeFlags });
+ setCopyCollapsedTypes({});
+ } catch {
+ setCopyInspectionRows({});
+ setCopyForm({ ...baseRow });
+ setCopyCollapsedTypes({});
+ }
+
setCopyModalOpen(true);
searchCopyTargets(1);
};
@@ -309,10 +365,18 @@ export default function ItemInspectionInfoPage() {
const handleCopy = async () => {
if (!selectedItemCode) { toast.error("복사 기준 품목이 없어요"); return; }
if (copyCheckedIds.length === 0) { toast.error("붙여넣을 품목을 선택해주세요"); return; }
- const sourceGroup = groupedData.find(g => g.item_code === selectedItemCode);
- if (!sourceGroup || sourceGroup.rows.length === 0) { toast.error("복사할 검사정보가 없어요"); return; }
+
+ // 편집된 rows를 평탄화 (선택된 검사유형의 rows만)
+ const enabledTypes = INSPECTION_TYPES.filter(t => !!copyForm[t.key]);
+ const flatRows: Array<{ row: InspectionRow; typeLabel: string }> = [];
+ for (const t of enabledTypes) {
+ const rows = copyInspectionRows[t.key] || [];
+ for (const r of rows) flatRows.push({ row: r, typeLabel: t.label });
+ }
+ if (flatRows.length === 0) { toast.error("복사할 검사항목이 없어요"); return; }
+
const ok = await confirm(
- `선택한 ${copyCheckedIds.length}개 품목에 검사정보를 복사할까요?`,
+ `선택한 ${copyCheckedIds.length}개 품목에 편집된 검사정보(${flatRows.length}개 행)를 복사할까요?`,
{ description: "대상 품목의 기존 검사정보는 삭제 후 교체됩니다.", variant: "info", confirmText: "복사" }
);
if (!ok) return;
@@ -333,13 +397,19 @@ export default function ItemInspectionInfoPage() {
if (existing.length > 0) {
await apiClient.delete(`/table-management/tables/${TABLE_NAME}/delete`, { data: existing.map((r: any) => ({ id: r.id })) });
}
- for (const r of sourceGroup.rows) {
- const { id: _id, created_at: _c, updated_at: _u, ...rest } = r;
+ for (const { row: r, typeLabel } of flatRows) {
await apiClient.post(`/table-management/tables/${TABLE_NAME}/add`, {
- ...rest,
id: crypto.randomUUID(),
item_code: targetCode,
item_name: targetName,
+ inspection_type: typeLabel,
+ inspection_standard_id: r.inspection_standard_id || "",
+ inspection_item_name: r.inspection_detail || "",
+ inspection_method: r.inspection_method || "",
+ pass_criteria: r.acceptance_criteria || "",
+ is_required: r.is_required ? "true" : "false",
+ is_active: copyForm.is_active || "사용",
+ manager: copyForm.manager || "",
});
}
setCopyProgress({ current: i + 1, total: copyCheckedIds.length });
@@ -525,6 +595,46 @@ export default function ItemInspectionInfoPage() {
};
const toggleCollapse = (typeKey: string) => { setCollapsedTypes(prev => ({ ...prev, [typeKey]: !prev[typeKey] })); };
+ /* ═══════════════════ 복사 모달용 검사항목 행 관리 (등록 폼과 평행) ═══════════════════ */
+ const addCopyInspRow = (typeKey: string) => {
+ setCopyInspectionRows(prev => ({
+ ...prev,
+ [typeKey]: [...(prev[typeKey] || []), { id: crypto.randomUUID(), inspection_standard_id: "", inspection_detail: "", inspection_method: "", apply_process: "", acceptance_criteria: "", is_required: false }],
+ }));
+ };
+ const removeCopyInspRow = (typeKey: string, rowId: string) => {
+ setCopyInspectionRows(prev => ({ ...prev, [typeKey]: (prev[typeKey] || []).filter(r => r.id !== rowId) }));
+ };
+ const updateCopyInspRow = (typeKey: string, rowId: string, field: string, value: any) => {
+ setCopyInspectionRows(prev => ({
+ ...prev,
+ [typeKey]: (prev[typeKey] || []).map(r => {
+ if (r.id !== rowId) return r;
+ if (field === "inspection_standard_id") {
+ const opt = inspOptions.find(o => o.code === value);
+ const methodCode = opt?.method || "";
+ const methodLabel = inspMethodCatOptions.find(o => o.code === methodCode)?.label || methodCode;
+ const jcCode = opt?.judgment_criteria || "";
+ const jcLabel = judgmentCatOptions.find(c => c.code === jcCode)?.label || jcCode;
+ const unitCode = opt?.unit || "";
+ const unitLabel = inspUnitCatOptions.find(c => c.code === unitCode)?.label || unitCode;
+ return {
+ ...r,
+ inspection_standard_id: value,
+ inspection_detail: opt?.detail || "",
+ inspection_method: methodLabel,
+ judgment_criteria: jcLabel,
+ selection_options: opt?.selection_options || "",
+ unit: unitLabel,
+ acceptance_criteria: "",
+ };
+ }
+ return { ...r, [field]: value };
+ }),
+ }));
+ };
+ const toggleCopyCollapse = (typeKey: string) => { setCopyCollapsedTypes(prev => ({ ...prev, [typeKey]: !prev[typeKey] })); };
+
const handleSave = async () => {
if (!form.item_code) { toast.error("품목코드는 필수예요"); return; }
setSaving(true);
@@ -1285,20 +1395,20 @@ export default function ItemInspectionInfoPage() {
- {/* ═══════════════════ 복사 모달 ═══════════════════ */}
+ {/* ═══════════════════ 복사 모달 (2단 분할: 좌 대상 / 우 편집) ═══════════════════ */}