Merge remote-tracking branch 'origin/main' into ksh
This commit is contained in:
commit
c365f06ed7
|
|
@ -52,7 +52,8 @@ export function EntitySearchInputComponent({
|
|||
// 연쇄관계 설정 추출 (webTypeConfig 또는 component.componentConfig에서)
|
||||
const config = component?.componentConfig || {};
|
||||
const effectiveCascadingRelationCode = cascadingRelationCode || config.cascadingRelationCode;
|
||||
const effectiveParentFieldId = parentFieldId || config.parentFieldId;
|
||||
// cascadingParentField: ConfigPanel에서 저장되는 필드명
|
||||
const effectiveParentFieldId = parentFieldId || config.cascadingParentField || config.parentFieldId;
|
||||
const effectiveCascadingRole = config.cascadingRole; // "parent" | "child" | undefined
|
||||
|
||||
// 부모 역할이면 연쇄관계 로직 적용 안함 (자식만 부모 값에 따라 필터링됨)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { Search, Loader2 } from "lucide-react";
|
|||
import { useEntitySearch } from "../entity-search-input/useEntitySearch";
|
||||
import { ItemSelectionModalProps, ModalFilterConfig } from "./types";
|
||||
import { apiClient } from "@/lib/api/client";
|
||||
import { getCategoryLabelsByCodes } from "@/lib/api/tableCategoryValue";
|
||||
|
||||
export function ItemSelectionModal({
|
||||
open,
|
||||
|
|
@ -99,13 +100,44 @@ export function ItemSelectionModal({
|
|||
}
|
||||
}
|
||||
|
||||
// 정렬 후 옵션으로 변환
|
||||
// 🆕 CATEGORY_ 코드가 있는지 확인하고 라벨 조회
|
||||
const allCodes = new Set<string>();
|
||||
for (const val of uniqueValues) {
|
||||
// 콤마로 구분된 다중 값도 처리
|
||||
const codes = val.split(",").map(c => c.trim());
|
||||
codes.forEach(code => {
|
||||
if (code.startsWith("CATEGORY_")) {
|
||||
allCodes.add(code);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// CATEGORY_ 코드가 있으면 라벨 조회
|
||||
let labelMap: Record<string, string> = {};
|
||||
if (allCodes.size > 0) {
|
||||
try {
|
||||
const labelResponse = await getCategoryLabelsByCodes(Array.from(allCodes));
|
||||
if (labelResponse.success && labelResponse.data) {
|
||||
labelMap = labelResponse.data;
|
||||
}
|
||||
} catch (labelError) {
|
||||
console.error("카테고리 라벨 조회 실패:", labelError);
|
||||
}
|
||||
}
|
||||
|
||||
// 정렬 후 옵션으로 변환 (라벨 적용)
|
||||
const options = Array.from(uniqueValues)
|
||||
.sort()
|
||||
.map((val) => ({
|
||||
value: val,
|
||||
label: val,
|
||||
}));
|
||||
.map((val) => {
|
||||
// 콤마로 구분된 다중 값 처리
|
||||
if (val.includes(",")) {
|
||||
const codes = val.split(",").map(c => c.trim());
|
||||
const labels = codes.map(code => labelMap[code] || code);
|
||||
return { value: val, label: labels.join(", ") };
|
||||
}
|
||||
// 단일 값
|
||||
return { value: val, label: labelMap[val] || val };
|
||||
});
|
||||
|
||||
setCategoryOptions((prev) => ({
|
||||
...prev,
|
||||
|
|
|
|||
|
|
@ -605,7 +605,7 @@ export const RackStructureComponent: React.FC<RackStructureComponentProps> = ({
|
|||
location_type: context?.locationType || "선반",
|
||||
status: context?.status || "사용",
|
||||
// 추가 필드 (테이블 컬럼명과 동일)
|
||||
warehouse_id: context?.warehouseCode,
|
||||
warehouse_code: context?.warehouseCode,
|
||||
warehouse_name: context?.warehouseName,
|
||||
floor: context?.floor,
|
||||
zone: context?.zone,
|
||||
|
|
@ -623,6 +623,18 @@ export const RackStructureComponent: React.FC<RackStructureComponentProps> = ({
|
|||
|
||||
setPreviewData(locations);
|
||||
setIsPreviewGenerated(true);
|
||||
|
||||
console.log("🏗️ [RackStructure] 생성된 위치 데이터:", {
|
||||
locationsCount: locations.length,
|
||||
firstLocation: locations[0],
|
||||
context: {
|
||||
warehouseCode: context?.warehouseCode,
|
||||
warehouseName: context?.warehouseName,
|
||||
floor: context?.floor,
|
||||
zone: context?.zone,
|
||||
},
|
||||
});
|
||||
|
||||
onChange?.(locations);
|
||||
}, [conditions, context, generateLocationCode, onChange, missingFields, hasRowOverlap, duplicateErrors, existingLocations, rowOverlapErrors]);
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export interface GeneratedLocation {
|
|||
location_type?: string; // 위치 유형
|
||||
status?: string; // 사용 여부
|
||||
// 추가 필드 (상위 폼에서 매핑된 값)
|
||||
warehouse_id?: string; // 창고 ID/코드
|
||||
warehouse_code?: string; // 창고 코드 (DB 컬럼명과 동일)
|
||||
warehouse_name?: string; // 창고명
|
||||
floor?: string; // 층
|
||||
zone?: string; // 구역
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { WebType } from "@/types/common";
|
|||
import { tableTypeApi } from "@/lib/api/screen";
|
||||
import { entityJoinApi } from "@/lib/api/entityJoin";
|
||||
import { codeCache } from "@/lib/caching/codeCache";
|
||||
import { getCategoryLabelsByCodes } from "@/lib/api/tableCategoryValue";
|
||||
import { useEntityJoinOptimization } from "@/lib/hooks/useEntityJoinOptimization";
|
||||
import { getFullImageUrl } from "@/lib/api/client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
|
@ -471,6 +472,7 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
}
|
||||
|
||||
// 2. 헤더 필터 적용 (joinColumnMapping 사용 안 함 - 직접 컬럼명 사용)
|
||||
// 🆕 다중 값 지원: 셀 값이 "A,B,C" 형태일 때, 필터에서 "A"를 선택하면 해당 행도 표시
|
||||
if (Object.keys(headerFilters).length > 0) {
|
||||
result = result.filter((row) => {
|
||||
return Object.entries(headerFilters).every(([columnName, values]) => {
|
||||
|
|
@ -480,7 +482,16 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
const cellValue = row[columnName] ?? row[columnName.toLowerCase()] ?? row[columnName.toUpperCase()];
|
||||
const cellStr = cellValue !== null && cellValue !== undefined ? String(cellValue) : "";
|
||||
|
||||
return values.has(cellStr);
|
||||
// 정확히 일치하는 경우
|
||||
if (values.has(cellStr)) return true;
|
||||
|
||||
// 다중 값인 경우: 콤마로 분리해서 하나라도 포함되면 true
|
||||
if (cellStr.includes(",")) {
|
||||
const cellValues = cellStr.split(",").map(v => v.trim());
|
||||
return cellValues.some(v => values.has(v));
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -2248,12 +2259,18 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
// 🆕 편집 모드 진입 placeholder (실제 구현은 visibleColumns 정의 후)
|
||||
const startEditingRef = useRef<() => void>(() => {});
|
||||
|
||||
// 🆕 카테고리 라벨 매핑 (API에서 가져온 것)
|
||||
const [categoryLabelCache, setCategoryLabelCache] = useState<Record<string, string>>({});
|
||||
|
||||
// 🆕 각 컬럼의 고유값 목록 계산 (라벨 포함)
|
||||
const columnUniqueValues = useMemo(() => {
|
||||
const result: Record<string, Array<{ value: string; label: string }>> = {};
|
||||
|
||||
if (data.length === 0) return result;
|
||||
|
||||
// 🆕 전체 데이터에서 개별 값 -> 라벨 매핑 테이블 구축 (다중 값 처리용)
|
||||
const globalLabelMap: Record<string, Map<string, string>> = {};
|
||||
|
||||
(tableConfig.columns || []).forEach((column: { columnName: string }) => {
|
||||
if (column.columnName === "__checkbox__") return;
|
||||
|
||||
|
|
@ -2265,23 +2282,70 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
`${column.columnName}_value_label`, // 예: division_value_label
|
||||
];
|
||||
const valuesMap = new Map<string, string>(); // value -> label
|
||||
const singleValueLabelMap = new Map<string, string>(); // 개별 값 -> 라벨 (다중값 처리용)
|
||||
|
||||
// 1차: 모든 데이터에서 개별 값 -> 라벨 매핑 수집 (단일값 + 다중값 모두)
|
||||
data.forEach((row) => {
|
||||
const val = row[mappedColumnName];
|
||||
if (val !== null && val !== undefined && val !== "") {
|
||||
const valueStr = String(val);
|
||||
// 라벨 컬럼 후보들 중 값이 있는 것 사용, 없으면 원본 값 사용
|
||||
let label = valueStr;
|
||||
|
||||
// 라벨 컬럼에서 라벨 찾기
|
||||
let labelStr = "";
|
||||
for (const labelCol of labelColumnCandidates) {
|
||||
if (row[labelCol] && row[labelCol] !== "") {
|
||||
label = String(row[labelCol]);
|
||||
labelStr = String(row[labelCol]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
valuesMap.set(valueStr, label);
|
||||
|
||||
// 단일 값인 경우
|
||||
if (!valueStr.includes(",")) {
|
||||
if (labelStr) {
|
||||
singleValueLabelMap.set(valueStr, labelStr);
|
||||
}
|
||||
} else {
|
||||
// 다중 값인 경우: 값과 라벨을 각각 분리해서 매핑
|
||||
const individualValues = valueStr.split(",").map(v => v.trim());
|
||||
const individualLabels = labelStr ? labelStr.split(",").map(l => l.trim()) : [];
|
||||
|
||||
// 값과 라벨 개수가 같으면 1:1 매핑
|
||||
if (individualValues.length === individualLabels.length) {
|
||||
individualValues.forEach((v, idx) => {
|
||||
if (individualLabels[idx] && !singleValueLabelMap.has(v)) {
|
||||
singleValueLabelMap.set(v, individualLabels[idx]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 2차: 모든 값 처리 (다중 값 포함) - 필터 목록용
|
||||
data.forEach((row) => {
|
||||
const val = row[mappedColumnName];
|
||||
if (val !== null && val !== undefined && val !== "") {
|
||||
const valueStr = String(val);
|
||||
|
||||
// 콤마로 구분된 다중 값인지 확인
|
||||
if (valueStr.includes(",")) {
|
||||
// 다중 값: 각각 분리해서 개별 라벨 찾기
|
||||
const individualValues = valueStr.split(",").map(v => v.trim());
|
||||
// 🆕 singleValueLabelMap → categoryLabelCache 순으로 라벨 찾기
|
||||
const individualLabels = individualValues.map(v =>
|
||||
singleValueLabelMap.get(v) || categoryLabelCache[v] || v
|
||||
);
|
||||
valuesMap.set(valueStr, individualLabels.join(", "));
|
||||
} else {
|
||||
// 단일 값: 매핑에서 찾거나 캐시에서 찾거나 원본 사용
|
||||
const label = singleValueLabelMap.get(valueStr) || categoryLabelCache[valueStr] || valueStr;
|
||||
valuesMap.set(valueStr, label);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
globalLabelMap[column.columnName] = singleValueLabelMap;
|
||||
|
||||
// value-label 쌍으로 저장하고 라벨 기준 정렬
|
||||
result[column.columnName] = Array.from(valuesMap.entries())
|
||||
.map(([value, label]) => ({ value, label }))
|
||||
|
|
@ -2289,7 +2353,44 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
});
|
||||
|
||||
return result;
|
||||
}, [data, tableConfig.columns, joinColumnMapping]);
|
||||
}, [data, tableConfig.columns, joinColumnMapping, categoryLabelCache]);
|
||||
|
||||
// 🆕 라벨을 못 찾은 CATEGORY_ 코드들을 API로 조회
|
||||
useEffect(() => {
|
||||
const unlabeledCodes = new Set<string>();
|
||||
|
||||
// columnUniqueValues에서 라벨이 코드 그대로인 항목 찾기
|
||||
Object.values(columnUniqueValues).forEach(items => {
|
||||
items.forEach(item => {
|
||||
// 라벨에 CATEGORY_가 포함되어 있으면 라벨을 못 찾은 것
|
||||
if (item.label.includes("CATEGORY_")) {
|
||||
// 콤마로 분리해서 개별 코드 추출
|
||||
const codes = item.label.split(",").map(c => c.trim());
|
||||
codes.forEach(code => {
|
||||
if (code.startsWith("CATEGORY_") && !categoryLabelCache[code]) {
|
||||
unlabeledCodes.add(code);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (unlabeledCodes.size === 0) return;
|
||||
|
||||
// API로 라벨 조회
|
||||
const fetchLabels = async () => {
|
||||
try {
|
||||
const response = await getCategoryLabelsByCodes(Array.from(unlabeledCodes));
|
||||
if (response.success && response.data) {
|
||||
setCategoryLabelCache(prev => ({ ...prev, ...response.data }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("카테고리 라벨 조회 실패:", error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchLabels();
|
||||
}, [columnUniqueValues, categoryLabelCache]);
|
||||
|
||||
// 🆕 헤더 필터 토글
|
||||
const toggleHeaderFilter = useCallback((columnName: string, value: string) => {
|
||||
|
|
|
|||
|
|
@ -1375,17 +1375,17 @@ export class ButtonActionExecutor {
|
|||
|
||||
// 저장 전 중복 체크
|
||||
const firstLocation = locations[0];
|
||||
const warehouseId = firstLocation.warehouse_id || firstLocation.warehouseCode;
|
||||
const warehouseCode = firstLocation.warehouse_code || firstLocation.warehouse_id || firstLocation.warehouseCode;
|
||||
const floor = firstLocation.floor;
|
||||
const zone = firstLocation.zone;
|
||||
|
||||
if (warehouseId && floor && zone) {
|
||||
console.log("🔍 [handleRackStructureBatchSave] 기존 데이터 중복 체크:", { warehouseId, floor, zone });
|
||||
if (warehouseCode && floor && zone) {
|
||||
console.log("🔍 [handleRackStructureBatchSave] 기존 데이터 중복 체크:", { warehouseCode, floor, zone });
|
||||
|
||||
try {
|
||||
const existingResponse = await DynamicFormApi.getTableData(tableName, {
|
||||
filters: {
|
||||
warehouse_id: warehouseId,
|
||||
warehouse_code: warehouseCode,
|
||||
floor: floor,
|
||||
zone: zone,
|
||||
},
|
||||
|
|
@ -1435,8 +1435,8 @@ export class ButtonActionExecutor {
|
|||
location_name: loc.location_name || loc.locationName,
|
||||
row_num: loc.row_num || String(loc.rowNum),
|
||||
level_num: loc.level_num || String(loc.levelNum),
|
||||
// 창고 정보 (렉 구조 컴포넌트에서 전달)
|
||||
warehouse_id: loc.warehouse_id || loc.warehouseCode,
|
||||
// 창고 정보 (렉 구조 컴포넌트에서 전달) - DB 컬럼명은 warehouse_code
|
||||
warehouse_code: loc.warehouse_code || loc.warehouse_id || loc.warehouseCode,
|
||||
warehouse_name: loc.warehouse_name || loc.warehouseName,
|
||||
// 위치 정보 (렉 구조 컴포넌트에서 전달)
|
||||
floor: loc.floor,
|
||||
|
|
|
|||
Loading…
Reference in New Issue