Enhance backend controllers, frontend pages, and V2 components
- Fix department, receiving, shippingOrder, shippingPlan controllers - Update admin pages (company management, disk usage) - Improve sales/logistics pages (order, shipping, outbound, receiving) - Enhance V2 components (file-upload, split-panel-layout, table-list) - Add SmartSelect common component - Update DataGrid, FullscreenDialog common components - Add gitignore rules for personal pipeline tools Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,7 @@ import { formatNumber as centralFormatNumber } from "@/lib/formatting";
|
||||
import { useToast } from "@/hooks/use-toast";
|
||||
import { tableTypeApi } from "@/lib/api/screen";
|
||||
import { apiClient, getFullImageUrl } from "@/lib/api/client";
|
||||
import { codeCache } from "@/lib/caching/codeCache";
|
||||
import { getFilePreviewUrl } from "@/lib/api/file";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -402,6 +403,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
const [isLoadingRight, setIsLoadingRight] = useState(false);
|
||||
const [rightTableColumns, setRightTableColumns] = useState<any[]>([]); // 우측 테이블 컬럼 정보
|
||||
const [columnInputTypes, setColumnInputTypes] = useState<Record<string, string>>({});
|
||||
const [columnCodeCategories, setColumnCodeCategories] = useState<Record<string, string>>({}); // columnName → codeCategory
|
||||
const [expandedItems, setExpandedItems] = useState<Set<any>>(new Set()); // 펼쳐진 항목들
|
||||
|
||||
// 🆕 페이징 상태
|
||||
@@ -1124,6 +1126,29 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
return <SplitPanelCellImage value={String(value)} />;
|
||||
}
|
||||
|
||||
// code 타입: code_value → code_name 변환
|
||||
if (colInputType === "code") {
|
||||
const codeCategory = columnCodeCategories[columnName];
|
||||
if (codeCategory && value) {
|
||||
try {
|
||||
const syncResult = codeCache.getCodeSync(codeCategory);
|
||||
if (syncResult && Array.isArray(syncResult)) {
|
||||
const foundCode = syncResult.find(
|
||||
(item: any) => String(item.code_value).toUpperCase() === String(value).toUpperCase(),
|
||||
);
|
||||
if (foundCode) {
|
||||
return foundCode.code_name;
|
||||
}
|
||||
} else {
|
||||
// 캐시 미스: 비동기 로딩 트리거
|
||||
codeCache.getCodeAsync(codeCategory).catch(() => {});
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 🆕 날짜 포맷 적용
|
||||
if (format?.type === "date" || format?.dateFormat) {
|
||||
return formatDateValue(value, format?.dateFormat || "YYYY-MM-DD");
|
||||
@@ -1156,20 +1181,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
|
||||
if (mapping && mapping[strValue]) {
|
||||
const categoryData = mapping[strValue];
|
||||
const displayLabel = categoryData.label || strValue;
|
||||
const displayColor = categoryData.color || "#64748b";
|
||||
|
||||
return (
|
||||
<Badge
|
||||
style={{
|
||||
backgroundColor: displayColor,
|
||||
borderColor: displayColor,
|
||||
}}
|
||||
className="text-white"
|
||||
>
|
||||
{displayLabel}
|
||||
</Badge>
|
||||
);
|
||||
return categoryData.label || strValue;
|
||||
}
|
||||
|
||||
// 전역 폴백: 컬럼명으로 매핑을 못 찾았을 때, 전체 매핑에서 값 검색
|
||||
@@ -1178,19 +1190,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
const m = categoryMappings[key];
|
||||
if (m && m[strValue]) {
|
||||
const categoryData = m[strValue];
|
||||
const displayLabel = categoryData.label || strValue;
|
||||
const displayColor = categoryData.color || "#64748b";
|
||||
return (
|
||||
<Badge
|
||||
style={{
|
||||
backgroundColor: displayColor,
|
||||
borderColor: displayColor,
|
||||
}}
|
||||
className="text-white"
|
||||
>
|
||||
{displayLabel}
|
||||
</Badge>
|
||||
);
|
||||
return categoryData.label || strValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1216,7 +1216,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
// 일반 값
|
||||
return String(value);
|
||||
},
|
||||
[formatDateValue, formatNumberValue, columnInputTypes],
|
||||
[formatDateValue, formatNumberValue, columnInputTypes, columnCodeCategories],
|
||||
);
|
||||
|
||||
// 🆕 패널 config의 columns에서 additionalJoinColumns 추출하는 헬퍼
|
||||
@@ -2218,6 +2218,43 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
loadLeftColumnLabels();
|
||||
}, [componentConfig.leftPanel?.tableName, isDesignMode]);
|
||||
|
||||
// 왼쪽 테이블 inputTypes + codeCategory 로드
|
||||
useEffect(() => {
|
||||
const loadLeftColumnInputTypes = async () => {
|
||||
const leftTableName = componentConfig.leftPanel?.tableName;
|
||||
if (!leftTableName || isDesignMode) return;
|
||||
|
||||
try {
|
||||
const columnsResponse = await tableTypeApi.getColumns(leftTableName);
|
||||
const inputTypes: Record<string, string> = {};
|
||||
const codeCategories: Record<string, string> = {};
|
||||
columnsResponse.forEach((col: any) => {
|
||||
const colName = col.columnName || col.column_name;
|
||||
if (colName) {
|
||||
inputTypes[colName] = col.inputType || "text";
|
||||
if (col.codeCategory) {
|
||||
codeCategories[colName] = col.codeCategory;
|
||||
}
|
||||
}
|
||||
});
|
||||
setColumnInputTypes((prev) => ({ ...prev, ...inputTypes }));
|
||||
setColumnCodeCategories((prev) => ({ ...prev, ...codeCategories }));
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
loadLeftColumnInputTypes();
|
||||
}, [componentConfig.leftPanel?.tableName, isDesignMode]);
|
||||
|
||||
// codeCategory 프리로딩 (캐시 미스 방지)
|
||||
useEffect(() => {
|
||||
const categories = Object.values(columnCodeCategories).filter(Boolean);
|
||||
if (categories.length > 0) {
|
||||
codeCache.preloadCodes([...new Set(categories)]).catch(() => {});
|
||||
}
|
||||
}, [columnCodeCategories]);
|
||||
|
||||
// 우측 테이블 컬럼 정보 로드
|
||||
useEffect(() => {
|
||||
const loadRightTableColumns = async () => {
|
||||
@@ -2247,20 +2284,25 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
});
|
||||
|
||||
const inputTypes: Record<string, string> = {};
|
||||
const codeCategories: Record<string, string> = {};
|
||||
for (const tbl of tablesToLoad) {
|
||||
try {
|
||||
const inputTypesResponse = await tableTypeApi.getColumnInputTypes(tbl);
|
||||
inputTypesResponse.forEach((col: any) => {
|
||||
const tblColumnsResponse = await tableTypeApi.getColumns(tbl);
|
||||
tblColumnsResponse.forEach((col: any) => {
|
||||
const colName = col.columnName || col.column_name;
|
||||
if (colName) {
|
||||
inputTypes[colName] = col.inputType || "text";
|
||||
if (col.codeCategory) {
|
||||
codeCategories[colName] = col.codeCategory;
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
setColumnInputTypes(inputTypes);
|
||||
setColumnInputTypes((prev) => ({ ...prev, ...inputTypes }));
|
||||
setColumnCodeCategories((prev) => ({ ...prev, ...codeCategories }));
|
||||
} catch (error) {
|
||||
console.error("우측 테이블 컬럼 정보 로드 실패:", error);
|
||||
}
|
||||
@@ -2304,12 +2346,18 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
|
||||
if (response.data.success && response.data.data) {
|
||||
const valueMap: Record<string, { label: string; color?: string }> = {};
|
||||
response.data.data.forEach((item: any) => {
|
||||
valueMap[item.value_code || item.valueCode] = {
|
||||
label: item.value_label || item.valueLabel,
|
||||
color: item.color,
|
||||
};
|
||||
});
|
||||
const flattenCategories = (items: any[]) => {
|
||||
items.forEach((item: any) => {
|
||||
valueMap[item.value_code || item.valueCode] = {
|
||||
label: item.value_label || item.valueLabel,
|
||||
color: item.color,
|
||||
};
|
||||
if (item.children && item.children.length > 0) {
|
||||
flattenCategories(item.children);
|
||||
}
|
||||
});
|
||||
};
|
||||
flattenCategories(response.data.data);
|
||||
|
||||
// 조인된 테이블은 "테이블명.컬럼명" 형태로도 저장
|
||||
const mappingKey = tableName === leftTableName ? columnName : `${tableName}.${columnName}`;
|
||||
@@ -2391,19 +2439,24 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||
|
||||
if (response.data.success && response.data.data) {
|
||||
const valueMap: Record<string, { label: string; color?: string }> = {};
|
||||
response.data.data.forEach((item: any) => {
|
||||
valueMap[item.value_code || item.valueCode] = {
|
||||
label: item.value_label || item.valueLabel,
|
||||
color: item.color,
|
||||
};
|
||||
});
|
||||
const flattenCategories = (items: any[]) => {
|
||||
items.forEach((item: any) => {
|
||||
valueMap[item.value_code || item.valueCode] = {
|
||||
label: item.value_label || item.valueLabel,
|
||||
color: item.color,
|
||||
};
|
||||
if (item.children && item.children.length > 0) {
|
||||
flattenCategories(item.children);
|
||||
}
|
||||
});
|
||||
};
|
||||
flattenCategories(response.data.data);
|
||||
|
||||
// 조인된 테이블의 경우 "테이블명.컬럼명" 형태로 저장
|
||||
const mappingKey = tableName === rightTableName ? columnName : `${tableName}.${columnName}`;
|
||||
mappings[mappingKey] = valueMap;
|
||||
|
||||
// 🆕 컬럼명만으로도 접근할 수 있도록 추가 저장 (모든 테이블)
|
||||
// 기존 매핑이 있으면 병합, 없으면 새로 생성
|
||||
// 컬럼명만으로도 접근할 수 있도록 추가 저장 (모든 테이블)
|
||||
mappings[columnName] = { ...(mappings[columnName] || {}), ...valueMap };
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user