fix: Enhance layout loading logic in screen management

- Updated the ScreenManagementService to allow SUPER_ADMIN or users with companyCode as "*" to load layouts based on the screen's company code.
- Improved layout loading in ScreenViewPage and EditModal components by implementing fallback mechanisms to ensure a valid layout is always set.
- Added console warnings for better debugging when layout loading fails, enhancing error visibility and user experience.
- Refactored label display logic in various components to ensure consistent behavior across input types.
This commit is contained in:
DDD1542
2026-02-27 14:00:06 +09:00
parent 1a6d78df43
commit 21c0c2b95c
11 changed files with 304 additions and 20 deletions

View File

@@ -10,7 +10,7 @@ import { TableListConfig, ColumnConfig } from "./types";
import { entityJoinApi } from "@/lib/api/entityJoin";
import { tableTypeApi } from "@/lib/api/screen";
import { tableManagementApi } from "@/lib/api/tableManagement";
import { Plus, Trash2, ArrowUp, ArrowDown, ChevronsUpDown, Check, Lock, Unlock, Database, Table2, Link2 } from "lucide-react";
import { Plus, Trash2, ArrowUp, ArrowDown, ChevronsUpDown, Check, Lock, Unlock, Database, Table2, Link2, GripVertical, Pencil } from "lucide-react";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
import { cn } from "@/lib/utils";
@@ -1213,6 +1213,34 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
</>
)}
{/* 선택된 컬럼 순서 변경 */}
{config.columns && config.columns.length > 0 && (
<div className="space-y-3">
<div>
<h3 className="text-sm font-semibold"> / </h3>
<p className="text-muted-foreground text-[10px]">
</p>
</div>
<hr className="border-border" />
<div className="space-y-1">
{[...(config.columns || [])]
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
.map((column, idx) => (
<SelectedColumnItem
key={column.columnName}
column={column}
index={idx}
total={config.columns?.length || 0}
onMove={(direction) => moveColumn(column.columnName, direction)}
onRemove={() => removeColumn(column.columnName)}
onUpdate={(updates) => updateColumn(column.columnName, updates)}
/>
))}
</div>
</div>
)}
{/* 🆕 데이터 필터링 설정 */}
<div className="space-y-3">
<div>
@@ -1240,3 +1268,97 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
</div>
);
};
/**
* 선택된 컬럼 항목 컴포넌트
* 순서 이동, 삭제, 표시명 수정 기능 제공
*/
const SelectedColumnItem: React.FC<{
column: ColumnConfig;
index: number;
total: number;
onMove: (direction: "up" | "down") => void;
onRemove: () => void;
onUpdate: (updates: Partial<ColumnConfig>) => void;
}> = ({ column, index, total, onMove, onRemove, onUpdate }) => {
const [isEditing, setIsEditing] = useState(false);
const [editValue, setEditValue] = useState(column.displayName || column.columnName);
const handleSave = () => {
const trimmed = editValue.trim();
if (trimmed && trimmed !== column.displayName) {
onUpdate({ displayName: trimmed });
}
setIsEditing(false);
};
return (
<div className="hover:bg-muted/50 group flex items-center gap-1 rounded px-1.5 py-1">
<GripVertical className="text-muted-foreground/40 h-3 w-3 flex-shrink-0" />
<span className="text-muted-foreground min-w-[18px] text-[10px]">{index + 1}</span>
{isEditing ? (
<Input
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
onBlur={handleSave}
onKeyDown={(e) => {
if (e.key === "Enter") handleSave();
if (e.key === "Escape") {
setEditValue(column.displayName || column.columnName);
setIsEditing(false);
}
}}
className="h-5 flex-1 px-1 text-xs"
autoFocus
/>
) : (
<button
type="button"
className="flex flex-1 items-center gap-1 truncate text-left text-xs"
onClick={() => {
setEditValue(column.displayName || column.columnName);
setIsEditing(true);
}}
title="클릭하여 표시명 수정"
>
<span className="truncate">{column.displayName || column.columnName}</span>
{column.isEntityJoin && (
<Link2 className="h-2.5 w-2.5 flex-shrink-0 text-blue-500" />
)}
<Pencil className="text-muted-foreground/0 group-hover:text-muted-foreground/60 ml-auto h-2.5 w-2.5 flex-shrink-0 transition-colors" />
</button>
)}
<div className="flex flex-shrink-0 items-center gap-0.5">
<button
type="button"
className="hover:bg-muted disabled:opacity-30 rounded p-0.5 transition-colors"
onClick={() => onMove("up")}
disabled={index === 0}
title="위로 이동"
>
<ArrowUp className="h-3 w-3" />
</button>
<button
type="button"
className="hover:bg-muted disabled:opacity-30 rounded p-0.5 transition-colors"
onClick={() => onMove("down")}
disabled={index === total - 1}
title="아래로 이동"
>
<ArrowDown className="h-3 w-3" />
</button>
<button
type="button"
className="text-muted-foreground hover:text-destructive hover:bg-destructive/10 rounded p-0.5 transition-colors"
onClick={onRemove}
title="컬럼 제거"
>
<Trash2 className="h-3 w-3" />
</button>
</div>
</div>
);
};

View File

@@ -22,6 +22,8 @@ import {
Database,
Table2,
Link2,
GripVertical,
Pencil,
} from "lucide-react";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
@@ -1458,6 +1460,34 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
</>
)}
{/* 선택된 컬럼 순서 변경 */}
{config.columns && config.columns.length > 0 && (
<div className="space-y-3">
<div>
<h3 className="text-sm font-semibold"> / </h3>
<p className="text-muted-foreground text-[10px]">
</p>
</div>
<hr className="border-border" />
<div className="space-y-1">
{[...(config.columns || [])]
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
.map((column, idx) => (
<SelectedColumnItem
key={column.columnName}
column={column}
index={idx}
total={config.columns?.length || 0}
onMove={(direction) => moveColumn(column.columnName, direction)}
onRemove={() => removeColumn(column.columnName)}
onUpdate={(updates) => updateColumn(column.columnName, updates)}
/>
))}
</div>
</div>
)}
{/* 🆕 데이터 필터링 설정 */}
<div className="space-y-3">
<div>
@@ -1484,3 +1514,97 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
</div>
);
};
/**
* 선택된 컬럼 항목 컴포넌트
* 순서 이동, 삭제, 표시명 수정 기능 제공
*/
const SelectedColumnItem: React.FC<{
column: ColumnConfig;
index: number;
total: number;
onMove: (direction: "up" | "down") => void;
onRemove: () => void;
onUpdate: (updates: Partial<ColumnConfig>) => void;
}> = ({ column, index, total, onMove, onRemove, onUpdate }) => {
const [isEditing, setIsEditing] = useState(false);
const [editValue, setEditValue] = useState(column.displayName || column.columnName);
const handleSave = () => {
const trimmed = editValue.trim();
if (trimmed && trimmed !== column.displayName) {
onUpdate({ displayName: trimmed });
}
setIsEditing(false);
};
return (
<div className="hover:bg-muted/50 group flex items-center gap-1 rounded px-1.5 py-1">
<GripVertical className="text-muted-foreground/40 h-3 w-3 flex-shrink-0" />
<span className="text-muted-foreground min-w-[18px] text-[10px]">{index + 1}</span>
{isEditing ? (
<Input
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
onBlur={handleSave}
onKeyDown={(e) => {
if (e.key === "Enter") handleSave();
if (e.key === "Escape") {
setEditValue(column.displayName || column.columnName);
setIsEditing(false);
}
}}
className="h-5 flex-1 px-1 text-xs"
autoFocus
/>
) : (
<button
type="button"
className="flex flex-1 items-center gap-1 truncate text-left text-xs"
onClick={() => {
setEditValue(column.displayName || column.columnName);
setIsEditing(true);
}}
title="클릭하여 표시명 수정"
>
<span className="truncate">{column.displayName || column.columnName}</span>
{column.isEntityJoin && (
<Link2 className="h-2.5 w-2.5 flex-shrink-0 text-blue-500" />
)}
<Pencil className="text-muted-foreground/0 group-hover:text-muted-foreground/60 ml-auto h-2.5 w-2.5 flex-shrink-0 transition-colors" />
</button>
)}
<div className="flex flex-shrink-0 items-center gap-0.5">
<button
type="button"
className="hover:bg-muted disabled:opacity-30 rounded p-0.5 transition-colors"
onClick={() => onMove("up")}
disabled={index === 0}
title="위로 이동"
>
<ArrowUp className="h-3 w-3" />
</button>
<button
type="button"
className="hover:bg-muted disabled:opacity-30 rounded p-0.5 transition-colors"
onClick={() => onMove("down")}
disabled={index === total - 1}
title="아래로 이동"
>
<ArrowDown className="h-3 w-3" />
</button>
<button
type="button"
className="text-muted-foreground hover:text-destructive hover:bg-destructive/10 rounded p-0.5 transition-colors"
onClick={onRemove}
title="컬럼 제거"
>
<Trash2 className="h-3 w-3" />
</button>
</div>
</div>
);
};

View File

@@ -3173,16 +3173,16 @@ export class ButtonActionExecutor {
return false;
}
// 1. 화면 설명 가져오기
let description = config.modalDescription || "";
if (!description) {
// 1. 화면 정보 가져오기 (제목/설명이 미설정 시 화면명에서 가져옴)
let screenInfo: any = null;
if (!config.modalTitle || !config.modalDescription) {
try {
const screenInfo = await screenApi.getScreen(config.targetScreenId);
description = screenInfo?.description || "";
screenInfo = await screenApi.getScreen(config.targetScreenId);
} catch (error) {
console.warn("화면 설명을 가져오지 못했습니다:", error);
console.warn("화면 정보를 가져오지 못했습니다:", error);
}
}
let description = config.modalDescription || screenInfo?.description || "";
// 2. 데이터 소스 및 선택된 데이터 수집
let selectedData: any[] = [];
@@ -3288,7 +3288,7 @@ export class ButtonActionExecutor {
}
// 3. 동적 모달 제목 생성
let finalTitle = config.modalTitle || "화면";
let finalTitle = config.modalTitle || screenInfo?.screenName || "데이터 등록";
// 블록 기반 제목 처리
if (config.modalTitleBlocks?.length) {