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:
parent
1a6d78df43
commit
21c0c2b95c
|
|
@ -5083,8 +5083,8 @@ export class ScreenManagementService {
|
||||||
let layout: { layout_data: any } | null = null;
|
let layout: { layout_data: any } | null = null;
|
||||||
|
|
||||||
// 🆕 기본 레이어(layer_id=1)를 우선 로드
|
// 🆕 기본 레이어(layer_id=1)를 우선 로드
|
||||||
// SUPER_ADMIN인 경우: 화면의 회사 코드로 레이아웃 조회
|
// SUPER_ADMIN이거나 companyCode가 "*"인 경우: 화면의 회사 코드로 레이아웃 조회
|
||||||
if (isSuperAdmin) {
|
if (isSuperAdmin || companyCode === "*") {
|
||||||
// 1. 화면 정의의 회사 코드 + 기본 레이어
|
// 1. 화면 정의의 회사 코드 + 기본 레이어
|
||||||
layout = await queryOne<{ layout_data: any }>(
|
layout = await queryOne<{ layout_data: any }>(
|
||||||
`SELECT layout_data FROM screen_layouts_v2
|
`SELECT layout_data FROM screen_layouts_v2
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,25 @@ function ScreenViewPage() {
|
||||||
} else {
|
} else {
|
||||||
// V1 레이아웃 또는 빈 레이아웃
|
// V1 레이아웃 또는 빈 레이아웃
|
||||||
const layoutData = await screenApi.getLayout(screenId);
|
const layoutData = await screenApi.getLayout(screenId);
|
||||||
|
if (layoutData?.components?.length > 0) {
|
||||||
setLayout(layoutData);
|
setLayout(layoutData);
|
||||||
|
} else {
|
||||||
|
console.warn("[ScreenViewPage] getLayout 실패, getLayerLayout(1) fallback:", screenId);
|
||||||
|
const baseLayerData = await screenApi.getLayerLayout(screenId, 1);
|
||||||
|
if (baseLayerData && isValidV2Layout(baseLayerData)) {
|
||||||
|
const converted = convertV2ToLegacy(baseLayerData);
|
||||||
|
if (converted) {
|
||||||
|
setLayout({
|
||||||
|
...converted,
|
||||||
|
screenResolution: baseLayerData.screenResolution || converted.screenResolution,
|
||||||
|
} as LayoutData);
|
||||||
|
} else {
|
||||||
|
setLayout(layoutData);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setLayout(layoutData);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (layoutError) {
|
} catch (layoutError) {
|
||||||
console.warn("레이아웃 로드 실패, 빈 레이아웃 사용:", layoutError);
|
console.warn("레이아웃 로드 실패, 빈 레이아웃 사용:", layoutError);
|
||||||
|
|
|
||||||
|
|
@ -413,9 +413,28 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
||||||
|
|
||||||
// V2 없으면 기존 API fallback
|
// V2 없으면 기존 API fallback
|
||||||
if (!layoutData) {
|
if (!layoutData) {
|
||||||
|
console.warn("[EditModal] V2 레이아웃 없음, getLayout fallback 시도:", screenId);
|
||||||
layoutData = await screenApi.getLayout(screenId);
|
layoutData = await screenApi.getLayout(screenId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getLayout도 실패하면 기본 레이어(layer_id=1) 직접 로드
|
||||||
|
if (!layoutData || !layoutData.components || layoutData.components.length === 0) {
|
||||||
|
console.warn("[EditModal] getLayout도 실패, getLayerLayout(1) 최종 fallback:", screenId);
|
||||||
|
try {
|
||||||
|
const baseLayerData = await screenApi.getLayerLayout(screenId, 1);
|
||||||
|
if (baseLayerData && isValidV2Layout(baseLayerData)) {
|
||||||
|
layoutData = convertV2ToLegacy(baseLayerData);
|
||||||
|
if (layoutData) {
|
||||||
|
layoutData.screenResolution = baseLayerData.screenResolution || layoutData.screenResolution;
|
||||||
|
}
|
||||||
|
} else if (baseLayerData?.components) {
|
||||||
|
layoutData = baseLayerData;
|
||||||
|
}
|
||||||
|
} catch (fallbackErr) {
|
||||||
|
console.error("[EditModal] getLayerLayout(1) fallback 실패:", fallbackErr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (screenInfo && layoutData) {
|
if (screenInfo && layoutData) {
|
||||||
const components = layoutData.components || [];
|
const components = layoutData.components || [];
|
||||||
|
|
||||||
|
|
@ -1440,7 +1459,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
||||||
</div>
|
</div>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<div className="flex flex-1 items-center justify-center overflow-y-auto [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-gray-300 [&::-webkit-scrollbar-track]:bg-transparent">
|
<div className="flex flex-1 justify-center overflow-y-auto [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-gray-300 [&::-webkit-scrollbar-track]:bg-transparent">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="flex h-full items-center justify-center">
|
<div className="flex h-full items-center justify-center">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
|
|
@ -1455,7 +1474,7 @@ export const EditModal: React.FC<EditModalProps> = ({ className }) => {
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
data-screen-runtime="true"
|
data-screen-runtime="true"
|
||||||
className="relative bg-white"
|
className="relative m-auto bg-white"
|
||||||
style={{
|
style={{
|
||||||
width: screenDimensions?.width || 800,
|
width: screenDimensions?.width || 800,
|
||||||
// 조건부 레이어가 활성화되면 높이 자동 확장
|
// 조건부 레이어가 활성화되면 높이 자동 확장
|
||||||
|
|
|
||||||
|
|
@ -2191,10 +2191,11 @@ export const InteractiveScreenViewer: React.FC<InteractiveScreenViewerProps> = (
|
||||||
|
|
||||||
// 라벨 표시 여부 계산
|
// 라벨 표시 여부 계산
|
||||||
const shouldShowLabel =
|
const shouldShowLabel =
|
||||||
!hideLabel && // hideLabel이 true면 라벨 숨김
|
!hideLabel &&
|
||||||
(component.style?.labelDisplay ?? true) &&
|
(component.style?.labelDisplay ?? true) !== false &&
|
||||||
|
component.style?.labelDisplay !== "false" &&
|
||||||
(component.label || component.style?.labelText) &&
|
(component.label || component.style?.labelText) &&
|
||||||
!templateTypes.includes(component.type); // 템플릿 컴포넌트는 라벨 표시 안함
|
!templateTypes.includes(component.type);
|
||||||
|
|
||||||
const labelText = component.style?.labelText || component.label || "";
|
const labelText = component.style?.labelText || component.label || "";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1109,7 +1109,7 @@ export const InteractiveScreenViewerDynamic: React.FC<InteractiveScreenViewerPro
|
||||||
type === "v2-input" || type === "v2-select" || type === "v2-date" ||
|
type === "v2-input" || type === "v2-select" || type === "v2-date" ||
|
||||||
compType === "v2-input" || compType === "v2-select" || compType === "v2-date";
|
compType === "v2-input" || compType === "v2-select" || compType === "v2-date";
|
||||||
const hasVisibleLabel = isV2InputComponent &&
|
const hasVisibleLabel = isV2InputComponent &&
|
||||||
style?.labelDisplay !== false &&
|
style?.labelDisplay !== false && style?.labelDisplay !== "false" &&
|
||||||
(style?.labelText || (component as any).label);
|
(style?.labelText || (component as any).label);
|
||||||
|
|
||||||
// 라벨 위치에 따라 오프셋 계산 (좌/우 배치 시 세로 오프셋 불필요)
|
// 라벨 위치에 따라 오프셋 계산 (좌/우 배치 시 세로 오프셋 불필요)
|
||||||
|
|
|
||||||
|
|
@ -700,7 +700,7 @@ export const V2Date = forwardRef<HTMLDivElement, V2DateProps>((props, ref) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const showLabel = label && style?.labelDisplay !== false;
|
const showLabel = label && style?.labelDisplay !== false && style?.labelDisplay !== "false";
|
||||||
const componentWidth = size?.width || style?.width;
|
const componentWidth = size?.width || style?.width;
|
||||||
const componentHeight = size?.height || style?.height;
|
const componentHeight = size?.height || style?.height;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -962,7 +962,7 @@ export const V2Input = forwardRef<HTMLDivElement, V2InputProps>((props, ref) =>
|
||||||
};
|
};
|
||||||
|
|
||||||
const actualLabel = label || style?.labelText;
|
const actualLabel = label || style?.labelText;
|
||||||
const showLabel = actualLabel && style?.labelDisplay === true;
|
const showLabel = actualLabel && style?.labelDisplay !== false && style?.labelDisplay !== "false";
|
||||||
const componentWidth = size?.width || style?.width;
|
const componentWidth = size?.width || style?.width;
|
||||||
const componentHeight = size?.height || style?.height;
|
const componentHeight = size?.height || style?.height;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1135,7 +1135,7 @@ export const V2Select = forwardRef<HTMLDivElement, V2SelectProps>(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const showLabel = label && style?.labelDisplay !== false;
|
const showLabel = label && style?.labelDisplay !== false && style?.labelDisplay !== "false";
|
||||||
const componentWidth = size?.width || style?.width;
|
const componentWidth = size?.width || style?.width;
|
||||||
const componentHeight = size?.height || style?.height;
|
const componentHeight = size?.height || style?.height;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import { TableListConfig, ColumnConfig } from "./types";
|
||||||
import { entityJoinApi } from "@/lib/api/entityJoin";
|
import { entityJoinApi } from "@/lib/api/entityJoin";
|
||||||
import { tableTypeApi } from "@/lib/api/screen";
|
import { tableTypeApi } from "@/lib/api/screen";
|
||||||
import { tableManagementApi } from "@/lib/api/tableManagement";
|
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 { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
|
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
|
||||||
import { cn } from "@/lib/utils";
|
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 className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -1240,3 +1268,97 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
||||||
</div>
|
</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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ import {
|
||||||
Database,
|
Database,
|
||||||
Table2,
|
Table2,
|
||||||
Link2,
|
Link2,
|
||||||
|
GripVertical,
|
||||||
|
Pencil,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command";
|
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 className="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -1484,3 +1514,97 @@ export const TableListConfigPanel: React.FC<TableListConfigPanelProps> = ({
|
||||||
</div>
|
</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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -3173,16 +3173,16 @@ export class ButtonActionExecutor {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. 화면 설명 가져오기
|
// 1. 화면 정보 가져오기 (제목/설명이 미설정 시 화면명에서 가져옴)
|
||||||
let description = config.modalDescription || "";
|
let screenInfo: any = null;
|
||||||
if (!description) {
|
if (!config.modalTitle || !config.modalDescription) {
|
||||||
try {
|
try {
|
||||||
const screenInfo = await screenApi.getScreen(config.targetScreenId);
|
screenInfo = await screenApi.getScreen(config.targetScreenId);
|
||||||
description = screenInfo?.description || "";
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("화면 설명을 가져오지 못했습니다:", error);
|
console.warn("화면 정보를 가져오지 못했습니다:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let description = config.modalDescription || screenInfo?.description || "";
|
||||||
|
|
||||||
// 2. 데이터 소스 및 선택된 데이터 수집
|
// 2. 데이터 소스 및 선택된 데이터 수집
|
||||||
let selectedData: any[] = [];
|
let selectedData: any[] = [];
|
||||||
|
|
@ -3288,7 +3288,7 @@ export class ButtonActionExecutor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 동적 모달 제목 생성
|
// 3. 동적 모달 제목 생성
|
||||||
let finalTitle = config.modalTitle || "화면";
|
let finalTitle = config.modalTitle || screenInfo?.screenName || "데이터 등록";
|
||||||
|
|
||||||
// 블록 기반 제목 처리
|
// 블록 기반 제목 처리
|
||||||
if (config.modalTitleBlocks?.length) {
|
if (config.modalTitleBlocks?.length) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue