441 lines
15 KiB
TypeScript
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,
|
|
};
|
|
}
|