엔티티컬럼 표시설정 수정 #297
|
|
@ -52,3 +52,4 @@ export default router;
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -48,3 +48,4 @@ export default router;
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -64,3 +64,4 @@ export default router;
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -52,3 +52,4 @@ export default router;
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -186,8 +186,13 @@ export class EntityJoinService {
|
|||
}
|
||||
}
|
||||
|
||||
// 별칭 컬럼명 생성 (writer -> writer_name)
|
||||
const aliasColumn = `${column.column_name}_name`;
|
||||
// 🎯 별칭 컬럼명 생성 - 사용자가 선택한 displayColumns 기반으로 동적 생성
|
||||
// 단일 컬럼: manager + user_name → manager_user_name
|
||||
// 여러 컬럼: 첫 번째 컬럼 기준 (나머지는 개별 alias로 처리됨)
|
||||
const firstDisplayColumn = displayColumns[0] || "name";
|
||||
const aliasColumn = `${column.column_name}_${firstDisplayColumn}`;
|
||||
|
||||
logger.info(`🔧 별칭 컬럼명 생성: ${column.column_name} + ${firstDisplayColumn} → ${aliasColumn}`);
|
||||
|
||||
const joinConfig: EntityJoinConfig = {
|
||||
sourceTable: tableName,
|
||||
|
|
|
|||
|
|
@ -584,3 +584,4 @@ const result = await executeNodeFlow(flowId, {
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -357,3 +357,4 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -343,3 +343,4 @@ const getComponentValue = (componentId: string) => {
|
|||
3. **조건부 저장**: 특정 조건 만족 시에만 저장
|
||||
4. **연쇄 저장**: 한 번의 클릭으로 여러 테이블에 저장
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ export default function TableManagementPage() {
|
|||
const [createTableModalOpen, setCreateTableModalOpen] = useState(false);
|
||||
const [addColumnModalOpen, setAddColumnModalOpen] = useState(false);
|
||||
const [ddlLogViewerOpen, setDdlLogViewerOpen] = useState(false);
|
||||
|
||||
|
||||
// 테이블 복제 관련 상태
|
||||
const [duplicateModalMode, setDuplicateModalMode] = useState<"create" | "duplicate">("create");
|
||||
const [duplicateSourceTable, setDuplicateSourceTable] = useState<string | null>(null);
|
||||
|
|
@ -109,7 +109,7 @@ export default function TableManagementPage() {
|
|||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||
const [tableToDelete, setTableToDelete] = useState<string>("");
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
|
||||
|
||||
// 선택된 테이블 목록 (체크박스)
|
||||
const [selectedTableIds, setSelectedTableIds] = useState<Set<string>>(new Set());
|
||||
|
||||
|
|
@ -459,11 +459,39 @@ export default function TableManagementPage() {
|
|||
if (!selectedTable) return;
|
||||
|
||||
try {
|
||||
// 🎯 Entity 타입인 경우 detailSettings에 엔티티 설정을 JSON으로 포함
|
||||
let finalDetailSettings = column.detailSettings || "";
|
||||
|
||||
if (column.inputType === "entity" && column.referenceTable) {
|
||||
// 기존 detailSettings를 파싱하거나 새로 생성
|
||||
let existingSettings: Record<string, unknown> = {};
|
||||
if (typeof column.detailSettings === "string" && column.detailSettings.trim().startsWith("{")) {
|
||||
try {
|
||||
existingSettings = JSON.parse(column.detailSettings);
|
||||
} catch {
|
||||
existingSettings = {};
|
||||
}
|
||||
}
|
||||
|
||||
// 엔티티 설정 추가
|
||||
const entitySettings = {
|
||||
...existingSettings,
|
||||
entityTable: column.referenceTable,
|
||||
entityCodeColumn: column.referenceColumn || "id",
|
||||
entityLabelColumn: column.displayColumn || "name",
|
||||
placeholder: (existingSettings.placeholder as string) || "항목을 선택하세요",
|
||||
searchable: existingSettings.searchable ?? true,
|
||||
};
|
||||
|
||||
finalDetailSettings = JSON.stringify(entitySettings);
|
||||
console.log("🔧 Entity 설정 JSON 생성:", entitySettings);
|
||||
}
|
||||
|
||||
const columnSetting = {
|
||||
columnName: column.columnName, // 실제 DB 컬럼명 (변경 불가)
|
||||
columnLabel: column.displayName, // 사용자가 입력한 표시명
|
||||
inputType: column.inputType || "text",
|
||||
detailSettings: column.detailSettings || "",
|
||||
detailSettings: finalDetailSettings,
|
||||
codeCategory: column.codeCategory || "",
|
||||
codeValue: column.codeValue || "",
|
||||
referenceTable: column.referenceTable || "",
|
||||
|
|
@ -487,7 +515,7 @@ export default function TableManagementPage() {
|
|||
|
||||
if (response.data.success) {
|
||||
console.log("✅ 컬럼 설정 저장 성공");
|
||||
|
||||
|
||||
// 🆕 Category 타입인 경우 컬럼 매핑 처리
|
||||
console.log("🔍 카테고리 조건 체크:", {
|
||||
isCategory: column.inputType === "category",
|
||||
|
|
@ -547,7 +575,7 @@ export default function TableManagementPage() {
|
|||
} else if (successCount > 0 && failCount > 0) {
|
||||
toast.warning(`컬럼 설정 저장 성공. ${successCount}개 메뉴 매핑 성공, ${failCount}개 실패.`);
|
||||
} else if (failCount > 0) {
|
||||
toast.error(`컬럼 설정 저장 성공. 메뉴 매핑 생성 실패.`);
|
||||
toast.error("컬럼 설정 저장 성공. 메뉴 매핑 생성 실패.");
|
||||
}
|
||||
} else {
|
||||
toast.success("컬럼 설정이 저장되었습니다. (메뉴 매핑 없음)");
|
||||
|
|
@ -680,9 +708,7 @@ export default function TableManagementPage() {
|
|||
console.log("📊 전체 매핑 결과:", { totalSuccessCount, totalFailCount });
|
||||
|
||||
if (totalSuccessCount > 0) {
|
||||
toast.success(
|
||||
`테이블 설정 및 ${totalSuccessCount}개 카테고리 메뉴 매핑이 저장되었습니다.`
|
||||
);
|
||||
toast.success(`테이블 설정 및 ${totalSuccessCount}개 카테고리 메뉴 매핑이 저장되었습니다.`);
|
||||
} else if (totalFailCount > 0) {
|
||||
toast.warning(`테이블 설정은 저장되었으나 ${totalFailCount}개 메뉴 매핑 생성 실패.`);
|
||||
} else {
|
||||
|
|
@ -1000,14 +1026,15 @@ export default function TableManagementPage() {
|
|||
.filter(
|
||||
(table) =>
|
||||
table.tableName.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
(table.displayName && table.displayName.toLowerCase().includes(searchTerm.toLowerCase())),
|
||||
(table.displayName &&
|
||||
table.displayName.toLowerCase().includes(searchTerm.toLowerCase())),
|
||||
)
|
||||
.every((table) => selectedTableIds.has(table.tableName))
|
||||
}
|
||||
onCheckedChange={handleSelectAll}
|
||||
aria-label="전체 선택"
|
||||
/>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
<span className="text-muted-foreground text-sm">
|
||||
{selectedTableIds.size > 0 && `${selectedTableIds.size}개 선택됨`}
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -1047,9 +1074,9 @@ export default function TableManagementPage() {
|
|||
<div
|
||||
key={table.tableName}
|
||||
className={`bg-card rounded-lg p-4 shadow-sm transition-all ${
|
||||
selectedTable === table.tableName
|
||||
? "shadow-md bg-muted/30"
|
||||
: "hover:shadow-lg hover:bg-muted/20"
|
||||
selectedTable === table.tableName
|
||||
? "bg-muted/30 shadow-md"
|
||||
: "hover:bg-muted/20 hover:shadow-lg"
|
||||
}`}
|
||||
style={
|
||||
selectedTable === table.tableName
|
||||
|
|
@ -1068,10 +1095,7 @@ export default function TableManagementPage() {
|
|||
onClick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className="flex-1 cursor-pointer"
|
||||
onClick={() => handleTableSelect(table.tableName)}
|
||||
>
|
||||
<div className="flex-1 cursor-pointer" onClick={() => handleTableSelect(table.tableName)}>
|
||||
<h4 className="text-sm font-semibold">{table.displayName || table.tableName}</h4>
|
||||
<p className="text-muted-foreground mt-1 text-xs">
|
||||
{table.description || getTextFromUI(TABLE_MANAGEMENT_KEYS.TABLE_DESCRIPTION, "설명 없음")}
|
||||
|
|
@ -1147,7 +1171,10 @@ export default function TableManagementPage() {
|
|||
) : (
|
||||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
{/* 컬럼 헤더 (고정) */}
|
||||
<div className="text-foreground grid h-12 flex-shrink-0 items-center border-b px-6 py-3 text-sm font-semibold" style={{ gridTemplateColumns: "160px 200px 250px 1fr" }}>
|
||||
<div
|
||||
className="text-foreground grid h-12 flex-shrink-0 items-center border-b px-6 py-3 text-sm font-semibold"
|
||||
style={{ gridTemplateColumns: "160px 200px 250px 1fr" }}
|
||||
>
|
||||
<div className="pr-4">컬럼명</div>
|
||||
<div className="px-4">라벨</div>
|
||||
<div className="pr-6">입력 타입</div>
|
||||
|
|
@ -1171,7 +1198,7 @@ export default function TableManagementPage() {
|
|||
className="bg-background hover:bg-muted/50 grid min-h-16 items-start border-b px-6 py-3 transition-colors"
|
||||
style={{ gridTemplateColumns: "160px 200px 250px 1fr" }}
|
||||
>
|
||||
<div className="pr-4 pt-1">
|
||||
<div className="pt-1 pr-4">
|
||||
<div className="font-mono text-sm">{column.columnName}</div>
|
||||
</div>
|
||||
<div className="px-4">
|
||||
|
|
@ -1226,9 +1253,9 @@ export default function TableManagementPage() {
|
|||
<label className="text-muted-foreground mb-1 block text-xs">
|
||||
적용할 메뉴 (2레벨)
|
||||
</label>
|
||||
<div className="border rounded-lg p-3 space-y-2 max-h-48 overflow-y-auto">
|
||||
<div className="max-h-48 space-y-2 overflow-y-auto rounded-lg border p-3">
|
||||
{secondLevelMenus.length === 0 ? (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
<p className="text-muted-foreground text-xs">
|
||||
2레벨 메뉴가 없습니다. 메뉴를 선택하지 않으면 모든 메뉴에서 사용 가능합니다.
|
||||
</p>
|
||||
) : (
|
||||
|
|
@ -1236,7 +1263,7 @@ export default function TableManagementPage() {
|
|||
// menuObjid를 숫자로 변환하여 비교
|
||||
const menuObjidNum = Number(menu.menuObjid);
|
||||
const isChecked = (column.categoryMenus || []).includes(menuObjidNum);
|
||||
|
||||
|
||||
return (
|
||||
<div key={menu.menuObjid} className="flex items-center gap-2">
|
||||
<input
|
||||
|
|
@ -1253,15 +1280,15 @@ export default function TableManagementPage() {
|
|||
prev.map((col) =>
|
||||
col.columnName === column.columnName
|
||||
? { ...col, categoryMenus: newMenus }
|
||||
: col
|
||||
)
|
||||
: col,
|
||||
),
|
||||
);
|
||||
}}
|
||||
className="h-4 w-4 rounded border-gray-300 text-primary focus:ring-2 focus:ring-ring"
|
||||
className="text-primary focus:ring-ring h-4 w-4 rounded border-gray-300 focus:ring-2"
|
||||
/>
|
||||
<label
|
||||
htmlFor={`category-menu-${column.columnName}-${menu.menuObjid}`}
|
||||
className="text-xs cursor-pointer flex-1"
|
||||
className="flex-1 cursor-pointer text-xs"
|
||||
>
|
||||
{menu.parentMenuName} → {menu.menuName}
|
||||
</label>
|
||||
|
|
@ -1282,9 +1309,7 @@ export default function TableManagementPage() {
|
|||
<>
|
||||
{/* 참조 테이블 */}
|
||||
<div className="w-48">
|
||||
<label className="text-muted-foreground mb-1 block text-xs">
|
||||
참조 테이블
|
||||
</label>
|
||||
<label className="text-muted-foreground mb-1 block text-xs">참조 테이블</label>
|
||||
<Select
|
||||
value={column.referenceTable || "none"}
|
||||
onValueChange={(value) =>
|
||||
|
|
@ -1296,15 +1321,10 @@ export default function TableManagementPage() {
|
|||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{referenceTableOptions.map((option, index) => (
|
||||
<SelectItem
|
||||
key={`entity-${option.value}-${index}`}
|
||||
value={option.value}
|
||||
>
|
||||
<SelectItem key={`entity-${option.value}-${index}`} value={option.value}>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{option.label}</span>
|
||||
<span className="text-muted-foreground text-xs">
|
||||
{option.value}
|
||||
</span>
|
||||
<span className="text-muted-foreground text-xs">{option.value}</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
|
|
@ -1315,9 +1335,7 @@ export default function TableManagementPage() {
|
|||
{/* 조인 컬럼 */}
|
||||
{column.referenceTable && column.referenceTable !== "none" && (
|
||||
<div className="w-48">
|
||||
<label className="text-muted-foreground mb-1 block text-xs">
|
||||
조인 컬럼
|
||||
</label>
|
||||
<label className="text-muted-foreground mb-1 block text-xs">조인 컬럼</label>
|
||||
<Select
|
||||
value={column.referenceColumn || "none"}
|
||||
onValueChange={(value) =>
|
||||
|
|
@ -1361,9 +1379,7 @@ export default function TableManagementPage() {
|
|||
column.referenceColumn &&
|
||||
column.referenceColumn !== "none" && (
|
||||
<div className="w-48">
|
||||
<label className="text-muted-foreground mb-1 block text-xs">
|
||||
표시 컬럼
|
||||
</label>
|
||||
<label className="text-muted-foreground mb-1 block text-xs">표시 컬럼</label>
|
||||
<Select
|
||||
value={column.displayColumn || "none"}
|
||||
onValueChange={(value) =>
|
||||
|
|
@ -1408,7 +1424,7 @@ export default function TableManagementPage() {
|
|||
column.referenceColumn !== "none" &&
|
||||
column.displayColumn &&
|
||||
column.displayColumn !== "none" && (
|
||||
<div className="bg-primary/10 text-primary flex items-center gap-1 rounded px-2 py-1 text-xs w-48">
|
||||
<div className="bg-primary/10 text-primary flex w-48 items-center gap-1 rounded px-2 py-1 text-xs">
|
||||
<span>✓</span>
|
||||
<span className="truncate">설정 완료</span>
|
||||
</div>
|
||||
|
|
@ -1460,9 +1476,10 @@ export default function TableManagementPage() {
|
|||
setDuplicateSourceTable(null);
|
||||
}}
|
||||
onSuccess={async (result) => {
|
||||
const message = duplicateModalMode === "duplicate"
|
||||
? "테이블이 성공적으로 복제되었습니다!"
|
||||
: "테이블이 성공적으로 생성되었습니다!";
|
||||
const message =
|
||||
duplicateModalMode === "duplicate"
|
||||
? "테이블이 성공적으로 복제되었습니다!"
|
||||
: "테이블이 성공적으로 생성되었습니다!";
|
||||
toast.success(message);
|
||||
// 테이블 목록 새로고침
|
||||
await loadTables();
|
||||
|
|
@ -1516,13 +1533,10 @@ export default function TableManagementPage() {
|
|||
{selectedTableIds.size > 0 ? (
|
||||
<>
|
||||
선택된 <strong>{selectedTableIds.size}개</strong>의 테이블을 삭제하시겠습니까?
|
||||
<br />
|
||||
이 작업은 되돌릴 수 없습니다.
|
||||
<br />이 작업은 되돌릴 수 없습니다.
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
정말로 테이블을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.
|
||||
</>
|
||||
<>정말로 테이블을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.</>
|
||||
)}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
|
@ -1600,4 +1614,3 @@ export default function TableManagementPage() {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -137,3 +137,4 @@ export const useActiveTabOptional = () => {
|
|||
return useContext(ActiveTabContext);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -194,3 +194,4 @@ export function applyAutoFillToFormData(
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4104,13 +4104,14 @@ export const TableListComponent: React.FC<TableListComponentProps> = ({
|
|||
// 선택된 컬럼들의 값을 구분자로 조합
|
||||
const values = displayColumns
|
||||
.map((colName: string) => {
|
||||
// 1. 먼저 직접 컬럼명으로 시도 (기본 테이블 컬럼인 경우)
|
||||
let cellValue = rowData[colName];
|
||||
// 🎯 백엔드 alias 규칙: ${sourceColumn}_${displayColumn}
|
||||
// 예: manager 컬럼에서 user_name 선택 시 → manager_user_name
|
||||
const joinedKey = `${column.columnName}_${colName}`;
|
||||
let cellValue = rowData[joinedKey];
|
||||
|
||||
// 2. 없으면 ${sourceColumn}_${colName} 형식으로 시도 (조인 테이블 컬럼인 경우)
|
||||
// fallback: 직접 컬럼명으로 시도 (기본 테이블 컬럼인 경우)
|
||||
if (cellValue === null || cellValue === undefined) {
|
||||
const joinedKey = `${column.columnName}_${colName}`;
|
||||
cellValue = rowData[joinedKey];
|
||||
cellValue = rowData[colName];
|
||||
}
|
||||
|
||||
if (cellValue === null || cellValue === undefined) return "";
|
||||
|
|
|
|||
|
|
@ -1686,3 +1686,4 @@ const 출고등록_설정: ScreenSplitPanel = {
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -533,3 +533,4 @@ const { data: config } = await getScreenSplitPanel(screenId);
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -520,3 +520,4 @@ function ScreenViewPage() {
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue