ERP-node/frontend/lib/registry/components/v2-item-routing/hooks/useItemRouting.ts

441 lines
15 KiB
TypeScript

"use client";
import { useState, useCallback, useMemo, useRef } from "react";
import { apiClient } from "@/lib/api/client";
import { ItemRoutingConfig, ItemData, RoutingVersionData, RoutingDetailData, ColumnDef } from "../types";
import { defaultConfig } from "../config";
const API_BASE = "/process-work-standard";
/** 표시 컬럼 목록에서 기본(item_name, item_code) 외 추가 컬럼만 추출 */
function getExtraColumnNames(columns?: ColumnDef[]): string {
if (!columns || columns.length === 0) return "";
return columns
.map((c) => c.name)
.filter((n) => n && n !== "item_name" && n !== "item_code")
.join(",");
}
export function useItemRouting(configPartial: Partial<ItemRoutingConfig>) {
const configKey = useMemo(
() => JSON.stringify(configPartial),
[configPartial]
);
const config: ItemRoutingConfig = useMemo(() => ({
...defaultConfig,
...configPartial,
dataSource: { ...defaultConfig.dataSource, ...configPartial?.dataSource },
modals: { ...defaultConfig.modals, ...configPartial?.modals },
processColumns: configPartial?.processColumns?.length
? configPartial.processColumns
: defaultConfig.processColumns,
}), [configKey]);
const configRef = useRef(config);
configRef.current = config;
const [items, setItems] = useState<ItemData[]>([]);
const [allItems, setAllItems] = useState<ItemData[]>([]);
const [versions, setVersions] = useState<RoutingVersionData[]>([]);
const [details, setDetails] = useState<RoutingDetailData[]>([]);
const [loading, setLoading] = useState(false);
const [selectedItemCode, setSelectedItemCode] = useState<string | null>(null);
const [selectedItemName, setSelectedItemName] = useState<string | null>(null);
const [selectedVersionId, setSelectedVersionId] = useState<string | null>(null);
const isRegisteredMode = config.itemListMode === "registered";
/** API 기본 파라미터 생성 */
const buildBaseParams = useCallback((search?: string, columns?: ColumnDef[]) => {
const ds = configRef.current.dataSource;
const extra = getExtraColumnNames(columns);
const filters = configRef.current.itemFilterConditions;
const params: Record<string, string> = {
tableName: ds.itemTable,
nameColumn: ds.itemNameColumn,
codeColumn: ds.itemCodeColumn,
routingTable: ds.routingVersionTable,
routingFkColumn: ds.routingVersionFkColumn,
};
if (search) params.search = search;
if (extra) params.extraColumns = extra;
if (filters && filters.length > 0) {
params.filterConditions = JSON.stringify(filters);
}
return new URLSearchParams(params);
}, []);
// ────────────────────────────────────────
// 품목 목록 조회 (all 모드)
// ────────────────────────────────────────
const fetchItems = useCallback(
async (search?: string) => {
try {
setLoading(true);
const cols = configRef.current.itemDisplayColumns;
const params = buildBaseParams(search, cols);
const res = await apiClient.get(`${API_BASE}/items?${params}`);
if (res.data?.success) {
const data = res.data.data || [];
if (configRef.current.itemListMode !== "registered") {
setItems(data);
}
return data;
}
} catch (err) {
console.error("품목 조회 실패", err);
} finally {
setLoading(false);
}
return [];
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[configKey, buildBaseParams]
);
// ────────────────────────────────────────
// 등록 품목 조회 (registered 모드)
// ────────────────────────────────────────
const fetchRegisteredItems = useCallback(
async (search?: string) => {
const screenCode = configRef.current.screenCode;
if (!screenCode) {
console.warn("screenCode가 설정되지 않았습니다");
setItems([]);
return;
}
try {
setLoading(true);
const ds = configRef.current.dataSource;
const cols = configRef.current.itemDisplayColumns;
const extra = getExtraColumnNames(cols);
const params = new URLSearchParams({
tableName: ds.itemTable,
nameColumn: ds.itemNameColumn,
codeColumn: ds.itemCodeColumn,
routingTable: ds.routingVersionTable,
routingFkColumn: ds.routingVersionFkColumn,
...(search ? { search } : {}),
...(extra ? { extraColumns: extra } : {}),
});
const res = await apiClient.get(
`${API_BASE}/registered-items/${encodeURIComponent(screenCode)}?${params}`
);
if (res.data?.success) {
setItems(res.data.data || []);
}
} catch (err) {
console.error("등록 품목 조회 실패", err);
} finally {
setLoading(false);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[configKey]
);
// ────────────────────────────────────────
// 전체 품목 조회 (등록 팝업용 - 필터+추가컬럼 적용)
// ────────────────────────────────────────
const fetchAllItems = useCallback(
async (search?: string) => {
try {
const cols = configRef.current.modalDisplayColumns;
const params = buildBaseParams(search, cols);
const res = await apiClient.get(`${API_BASE}/items?${params}`);
if (res.data?.success) {
setAllItems(res.data.data || []);
}
} catch (err) {
console.error("전체 품목 조회 실패", err);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[configKey, buildBaseParams]
);
// ────────────────────────────────────────
// 품목 등록/제거 (registered 모드)
// ────────────────────────────────────────
const registerItem = useCallback(
async (itemId: string, itemCode: string) => {
const screenCode = configRef.current.screenCode;
if (!screenCode) return false;
try {
const res = await apiClient.post(`${API_BASE}/registered-items`, {
screenCode,
itemId,
itemCode,
});
if (res.data?.success) {
await fetchRegisteredItems();
return true;
}
} catch (err) {
console.error("품목 등록 실패", err);
}
return false;
},
[fetchRegisteredItems]
);
const registerItemsBatch = useCallback(
async (itemList: { itemId: string; itemCode: string }[]) => {
const screenCode = configRef.current.screenCode;
if (!screenCode) return false;
try {
const res = await apiClient.post(`${API_BASE}/registered-items/batch`, {
screenCode,
items: itemList,
});
if (res.data?.success) {
await fetchRegisteredItems();
return true;
}
} catch (err) {
console.error("품목 일괄 등록 실패", err);
}
return false;
},
[fetchRegisteredItems]
);
const unregisterItem = useCallback(
async (registeredId: string) => {
try {
const res = await apiClient.delete(`${API_BASE}/registered-items/${registeredId}`);
if (res.data?.success) {
if (selectedItemCode) {
const removedItem = items.find((i) => i.registered_id === registeredId);
if (removedItem) {
const removedCode = removedItem.item_code || removedItem[configRef.current.dataSource.itemCodeColumn];
if (selectedItemCode === removedCode) {
setSelectedItemCode(null);
setSelectedItemName(null);
setSelectedVersionId(null);
setVersions([]);
setDetails([]);
}
}
}
await fetchRegisteredItems();
return true;
}
} catch (err) {
console.error("등록 품목 제거 실패", err);
}
return false;
},
[selectedItemCode, items, fetchRegisteredItems]
);
// ────────────────────────────────────────
// 라우팅 버전/공정 관련 (기존 동일)
// ────────────────────────────────────────
const fetchVersions = useCallback(
async (itemCode: string) => {
try {
const ds = configRef.current.dataSource;
const params = new URLSearchParams({
routingVersionTable: ds.routingVersionTable,
routingDetailTable: ds.routingDetailTable,
routingFkColumn: ds.routingVersionFkColumn,
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) {
const routingData = res.data.data || [];
setVersions(routingData);
return routingData;
}
} catch (err) {
console.error("라우팅 버전 조회 실패", err);
}
return [];
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[configKey]
);
const fetchDetails = useCallback(
async (versionId: string) => {
try {
setLoading(true);
const ds = configRef.current.dataSource;
const searchConditions = {
[ds.routingDetailFkColumn]: { value: versionId, operator: "equals" },
};
const params = new URLSearchParams({
page: "1",
size: "1000",
search: JSON.stringify(searchConditions),
sortBy: "seq_no",
sortOrder: "ASC",
enableEntityJoin: "true",
});
const res = await apiClient.get(
`/table-management/tables/${ds.routingDetailTable}/data-with-joins?${params}`
);
if (res.data?.success) {
const result = res.data.data;
setDetails(Array.isArray(result) ? result : result?.data || []);
}
} catch (err) {
console.error("공정 상세 조회 실패", err);
} finally {
setLoading(false);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[configKey]
);
const selectItem = useCallback(
async (itemCode: string, itemName: string) => {
setSelectedItemCode(itemCode);
setSelectedItemName(itemName);
setSelectedVersionId(null);
setDetails([]);
const versionList = await fetchVersions(itemCode);
if (versionList.length > 0) {
const defaultVersion = versionList.find((v: RoutingVersionData) => v.is_default);
const targetVersion = defaultVersion || (configRef.current.autoSelectFirstVersion ? versionList[0] : null);
if (targetVersion) {
setSelectedVersionId(targetVersion.id);
await fetchDetails(targetVersion.id);
}
}
},
[fetchVersions, fetchDetails]
);
const selectVersion = useCallback(
async (versionId: string) => {
setSelectedVersionId(versionId);
await fetchDetails(versionId);
},
[fetchDetails]
);
const refreshVersions = useCallback(async () => {
if (selectedItemCode) {
const versionList = await fetchVersions(selectedItemCode);
if (selectedVersionId) {
await fetchDetails(selectedVersionId);
} else if (versionList.length > 0) {
const lastVersion = versionList[versionList.length - 1];
setSelectedVersionId(lastVersion.id);
await fetchDetails(lastVersion.id);
}
}
}, [selectedItemCode, selectedVersionId, fetchVersions, fetchDetails]);
const refreshDetails = useCallback(async () => {
if (selectedVersionId) {
await fetchDetails(selectedVersionId);
}
}, [selectedVersionId, fetchDetails]);
const deleteDetail = useCallback(
async (detailId: string) => {
try {
const ds = configRef.current.dataSource;
const res = await apiClient.delete(
`/table-management/tables/${ds.routingDetailTable}/delete`,
{ data: [{ id: detailId }] }
);
if (res.data?.success) { await refreshDetails(); return true; }
} catch (err) { console.error("공정 삭제 실패", err); }
return false;
},
[refreshDetails]
);
const deleteVersion = useCallback(
async (versionId: string) => {
try {
const ds = configRef.current.dataSource;
const res = await apiClient.delete(
`/table-management/tables/${ds.routingVersionTable}/delete`,
{ data: [{ id: versionId }] }
);
if (res.data?.success) {
if (selectedVersionId === versionId) { setSelectedVersionId(null); setDetails([]); }
await refreshVersions();
return true;
}
} catch (err) { console.error("버전 삭제 실패", err); }
return false;
},
[selectedVersionId, refreshVersions]
);
const setDefaultVersion = useCallback(
async (versionId: string) => {
try {
const ds = configRef.current.dataSource;
const res = await apiClient.put(`${API_BASE}/versions/${versionId}/set-default`, {
routingVersionTable: ds.routingVersionTable,
routingFkColumn: ds.routingVersionFkColumn,
});
if (res.data?.success) {
if (selectedItemCode) await fetchVersions(selectedItemCode);
return true;
}
} catch (err) { console.error("기본 버전 설정 실패", err); }
return false;
},
[selectedItemCode, fetchVersions]
);
const unsetDefaultVersion = useCallback(
async (versionId: string) => {
try {
const ds = configRef.current.dataSource;
const res = await apiClient.put(`${API_BASE}/versions/${versionId}/unset-default`, {
routingVersionTable: ds.routingVersionTable,
});
if (res.data?.success) {
if (selectedItemCode) await fetchVersions(selectedItemCode);
return true;
}
} catch (err) { console.error("기본 버전 해제 실패", err); }
return false;
},
[selectedItemCode, fetchVersions]
);
return {
config,
items,
allItems,
versions,
details,
loading,
selectedItemCode,
selectedItemName,
selectedVersionId,
isRegisteredMode,
fetchItems,
fetchRegisteredItems,
fetchAllItems,
registerItem,
registerItemsBatch,
unregisterItem,
selectItem,
selectVersion,
refreshVersions,
refreshDetails,
deleteDetail,
deleteVersion,
setDefaultVersion,
unsetDefaultVersion,
};
}