123123
This commit is contained in:
parent
ba8a2fec2b
commit
c63eaf8434
|
|
@ -174,6 +174,8 @@ export function CompanySwitcher({ onClose, isOpen = false }: CompanySwitcherProp
|
||||||
? "bg-accent/50 font-semibold"
|
? "bg-accent/50 font-semibold"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
|
role="button"
|
||||||
|
aria-label={`${company.company_name} ${company.company_code}`}
|
||||||
onClick={() => handleCompanySwitch(company.company_code)}
|
onClick={() => handleCompanySwitch(company.company_code)}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
|
|
|
||||||
|
|
@ -359,10 +359,20 @@ const RealtimePreviewDynamicComponent: React.FC<RealtimePreviewProps> = ({
|
||||||
return `${actualHeight}px`;
|
return `${actualHeight}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 런타임 모드: ResponsiveGridRenderer가 ratio 기반으로 래퍼 높이를 설정하므로,
|
// 런타임 모드에서 컴포넌트 타입별 높이 처리
|
||||||
// 안쪽 컴포넌트는 "100%"로 래퍼를 채워야 비율이 정확하게 맞음
|
|
||||||
if (!isDesignMode) {
|
if (!isDesignMode) {
|
||||||
const compType = (component as any).componentType || component.componentConfig?.type || "";
|
const compType = (component as any).componentType || component.componentConfig?.type || "";
|
||||||
|
// 테이블: 부모 flex 컨테이너가 높이 관리 (flex: 1)
|
||||||
|
const flexGrowTypes = [
|
||||||
|
"table-list", "v2-table-list",
|
||||||
|
"split-panel-layout", "split-panel-layout2",
|
||||||
|
"v2-split-panel-layout", "screen-split-panel",
|
||||||
|
"v2-tab-container", "tab-container",
|
||||||
|
"tabs-widget", "v2-tabs-widget",
|
||||||
|
];
|
||||||
|
if (flexGrowTypes.some(t => compType === t)) {
|
||||||
|
return "100%";
|
||||||
|
}
|
||||||
const autoHeightTypes = [
|
const autoHeightTypes = [
|
||||||
"table-search-widget", "v2-table-search-widget",
|
"table-search-widget", "v2-table-search-widget",
|
||||||
"flow-widget",
|
"flow-widget",
|
||||||
|
|
@ -370,11 +380,9 @@ const RealtimePreviewDynamicComponent: React.FC<RealtimePreviewProps> = ({
|
||||||
if (autoHeightTypes.some(t => compType === t || compType.includes(t))) {
|
if (autoHeightTypes.some(t => compType === t || compType.includes(t))) {
|
||||||
return "auto";
|
return "auto";
|
||||||
}
|
}
|
||||||
// 나머지 모든 타입: 래퍼의 비율 스케일링을 따르도록 100%
|
|
||||||
return "100%";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 디자인 모드: 고정 픽셀 사용 (캔버스 내 절대 좌표 배치)
|
// 1순위: size.height가 있으면 우선 사용
|
||||||
if (size?.height && size.height > 0) {
|
if (size?.height && size.height > 0) {
|
||||||
if (component.componentConfig?.type === "table-list") {
|
if (component.componentConfig?.type === "table-list") {
|
||||||
return `${Math.max(size.height, 200)}px`;
|
return `${Math.max(size.height, 200)}px`;
|
||||||
|
|
@ -382,14 +390,17 @@ const RealtimePreviewDynamicComponent: React.FC<RealtimePreviewProps> = ({
|
||||||
return `${size.height}px`;
|
return `${size.height}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2순위: componentStyle.height (컴포넌트 정의에서 온 기본 스타일)
|
||||||
if (componentStyle?.height) {
|
if (componentStyle?.height) {
|
||||||
return typeof componentStyle.height === "number" ? `${componentStyle.height}px` : componentStyle.height;
|
return typeof componentStyle.height === "number" ? `${componentStyle.height}px` : componentStyle.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3순위: 기본값
|
||||||
if (component.componentConfig?.type === "table-list") {
|
if (component.componentConfig?.type === "table-list") {
|
||||||
return "200px";
|
return "200px";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 기본 높이
|
||||||
return "10px";
|
return "10px";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -592,10 +603,10 @@ const RealtimePreviewDynamicComponent: React.FC<RealtimePreviewProps> = ({
|
||||||
isResizing ? "none" :
|
isResizing ? "none" :
|
||||||
isOnSplitPanel ? (isDraggingSplitPanel ? "none" : "left 0.15s ease-out, width 0.15s ease-out") : undefined,
|
isOnSplitPanel ? (isDraggingSplitPanel ? "none" : "left 0.15s ease-out, width 0.15s ease-out") : undefined,
|
||||||
} : {
|
} : {
|
||||||
// 런타임 모드: CSS scale 기반 - 캔버스 픽셀 크기 그대로 사용, 부모가 scale()로 축소
|
// 런타임 모드: 부모(ResponsiveGridRenderer)가 위치/너비 관리
|
||||||
...safeComponentStyle,
|
...safeComponentStyle,
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: displayHeight,
|
||||||
position: "relative" as const,
|
position: "relative" as const,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,8 @@ function getComponentTypeId(component: ComponentData): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CSS transform scale 기반 렌더링.
|
* 디자이너 절대좌표를 캔버스 대비 비율로 변환하여 렌더링.
|
||||||
* 디자이너와 동일하게 캔버스 해상도(px)로 레이아웃 후 CSS scale로 축소/확대.
|
* 화면이 줄어들면 비율에 맞게 축소, 늘어나면 확대.
|
||||||
* 텍스트, 패딩, 버튼 등 모든 요소가 균일하게 스케일링되어 WYSIWYG 보장.
|
|
||||||
*/
|
*/
|
||||||
function ProportionalRenderer({
|
function ProportionalRenderer({
|
||||||
components,
|
components,
|
||||||
|
|
@ -48,7 +47,7 @@ function ProportionalRenderer({
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const topLevel = components.filter((c) => !c.parentId);
|
const topLevel = components.filter((c) => !c.parentId);
|
||||||
const scale = containerW > 0 ? containerW / canvasWidth : 1;
|
const ratio = containerW > 0 ? containerW / canvasWidth : 1;
|
||||||
|
|
||||||
const maxBottom = topLevel.reduce((max, c) => {
|
const maxBottom = topLevel.reduce((max, c) => {
|
||||||
const bottom = c.position.y + (c.size?.height || 40);
|
const bottom = c.position.y + (c.size?.height || 40);
|
||||||
|
|
@ -59,20 +58,11 @@ function ProportionalRenderer({
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
data-screen-runtime="true"
|
data-screen-runtime="true"
|
||||||
className="bg-background w-full overflow-hidden"
|
className="bg-background relative w-full overflow-x-hidden"
|
||||||
style={{ height: containerW > 0 ? `${maxBottom * scale}px` : "200px" }}
|
style={{ minHeight: containerW > 0 ? `${maxBottom * ratio}px` : "200px" }}
|
||||||
>
|
>
|
||||||
{containerW > 0 && (
|
{containerW > 0 &&
|
||||||
<div
|
topLevel.map((component) => {
|
||||||
style={{
|
|
||||||
width: `${canvasWidth}px`,
|
|
||||||
height: `${maxBottom}px`,
|
|
||||||
transform: `scale(${scale})`,
|
|
||||||
transformOrigin: "top left",
|
|
||||||
position: "relative",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{topLevel.map((component) => {
|
|
||||||
const typeId = getComponentTypeId(component);
|
const typeId = getComponentTypeId(component);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -81,10 +71,10 @@ function ProportionalRenderer({
|
||||||
data-component-type={typeId}
|
data-component-type={typeId}
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
left: `${component.position.x}px`,
|
left: `${(component.position.x / canvasWidth) * 100}%`,
|
||||||
top: `${component.position.y}px`,
|
top: `${component.position.y * ratio}px`,
|
||||||
width: `${component.size?.width || 100}px`,
|
width: `${((component.size?.width || 100) / canvasWidth) * 100}%`,
|
||||||
height: `${component.size?.height || 40}px`,
|
height: `${(component.size?.height || 40) * ratio}px`,
|
||||||
zIndex: component.position.z || 1,
|
zIndex: component.position.z || 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -93,8 +83,6 @@ function ProportionalRenderer({
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1410,7 +1410,7 @@ export const ButtonPrimaryComponent: React.FC<ButtonPrimaryComponentProps> = ({
|
||||||
const buttonElementStyle: React.CSSProperties = {
|
const buttonElementStyle: React.CSSProperties = {
|
||||||
width: buttonWidth,
|
width: buttonWidth,
|
||||||
height: buttonHeight,
|
height: buttonHeight,
|
||||||
minHeight: undefined, // 비율 스케일링 시 래퍼 높이를 정확히 따르도록 제거
|
minHeight: "32px", // 🔧 최소 높이를 32px로 줄임
|
||||||
// 커스텀 테두리 스타일 (StyleEditor 설정 우선, shorthand 사용 안 함)
|
// 커스텀 테두리 스타일 (StyleEditor 설정 우선, shorthand 사용 안 함)
|
||||||
borderWidth: style?.borderWidth || "0",
|
borderWidth: style?.borderWidth || "0",
|
||||||
borderStyle: (style?.borderStyle as React.CSSProperties["borderStyle"]) || (style?.borderWidth ? "solid" : "none"),
|
borderStyle: (style?.borderStyle as React.CSSProperties["borderStyle"]) || (style?.borderWidth ? "solid" : "none"),
|
||||||
|
|
|
||||||
|
|
@ -309,8 +309,11 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
const [leftFilters, setLeftFilters] = useState<TableFilter[]>([]);
|
const [leftFilters, setLeftFilters] = useState<TableFilter[]>([]);
|
||||||
const [leftGrouping, setLeftGrouping] = useState<string[]>([]);
|
const [leftGrouping, setLeftGrouping] = useState<string[]>([]);
|
||||||
const [leftColumnVisibility, setLeftColumnVisibility] = useState<ColumnVisibility[]>([]);
|
const [leftColumnVisibility, setLeftColumnVisibility] = useState<ColumnVisibility[]>([]);
|
||||||
const [leftColumnOrder, setLeftColumnOrder] = useState<string[]>([]); // 🔧 컬럼 순서
|
const [leftColumnOrder, setLeftColumnOrder] = useState<string[]>([]);
|
||||||
const [leftGroupSumConfig, setLeftGroupSumConfig] = useState<GroupSumConfig | null>(null); // 🆕 그룹별 합산 설정
|
const [leftGroupSumConfig, setLeftGroupSumConfig] = useState<GroupSumConfig | null>(null);
|
||||||
|
// 좌측 패널 컬럼 헤더 드래그
|
||||||
|
const [leftDraggedColumnIndex, setLeftDraggedColumnIndex] = useState<number | null>(null);
|
||||||
|
const [leftDropTargetColumnIndex, setLeftDropTargetColumnIndex] = useState<number | null>(null);
|
||||||
const [rightFilters, setRightFilters] = useState<TableFilter[]>([]);
|
const [rightFilters, setRightFilters] = useState<TableFilter[]>([]);
|
||||||
const [rightGrouping, setRightGrouping] = useState<string[]>([]);
|
const [rightGrouping, setRightGrouping] = useState<string[]>([]);
|
||||||
const [rightColumnVisibility, setRightColumnVisibility] = useState<ColumnVisibility[]>([]);
|
const [rightColumnVisibility, setRightColumnVisibility] = useState<ColumnVisibility[]>([]);
|
||||||
|
|
@ -2520,6 +2523,47 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
}
|
}
|
||||||
}, [selectedLeftItem, customLeftSelectedData, componentConfig, companyCode, toast, loadLeftData]);
|
}, [selectedLeftItem, customLeftSelectedData, componentConfig, companyCode, toast, loadLeftData]);
|
||||||
|
|
||||||
|
// 좌측 패널 컬럼 헤더 드래그
|
||||||
|
const handleLeftColumnDragStart = useCallback(
|
||||||
|
(e: React.DragEvent, columnIndex: number) => {
|
||||||
|
setLeftDraggedColumnIndex(columnIndex);
|
||||||
|
e.dataTransfer.effectAllowed = "move";
|
||||||
|
e.dataTransfer.setData("text/plain", `left-col-${columnIndex}`);
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
const handleLeftColumnDragOver = useCallback((e: React.DragEvent, columnIndex: number) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.dataTransfer.dropEffect = "move";
|
||||||
|
setLeftDropTargetColumnIndex(columnIndex);
|
||||||
|
}, []);
|
||||||
|
const handleLeftColumnDragEnd = useCallback(() => {
|
||||||
|
setLeftDraggedColumnIndex(null);
|
||||||
|
setLeftDropTargetColumnIndex(null);
|
||||||
|
}, []);
|
||||||
|
const handleLeftColumnDrop = useCallback(
|
||||||
|
(e: React.DragEvent, targetIndex: number) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const fromIdx = leftDraggedColumnIndex;
|
||||||
|
if (fromIdx === null || fromIdx === targetIndex) {
|
||||||
|
handleLeftColumnDragEnd();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const leftColumns = componentConfig.leftPanel?.columns || [];
|
||||||
|
const colNames = leftColumns
|
||||||
|
.filter((c: any) => typeof c === "string" || c.name || c.columnName)
|
||||||
|
.map((c: any) => typeof c === "string" ? c : c.name || c.columnName);
|
||||||
|
if (colNames.length > 0 && fromIdx >= 0 && fromIdx < colNames.length && targetIndex >= 0 && targetIndex < colNames.length) {
|
||||||
|
const reordered = [...colNames];
|
||||||
|
const [removed] = reordered.splice(fromIdx, 1);
|
||||||
|
reordered.splice(targetIndex, 0, removed);
|
||||||
|
setLeftColumnOrder(reordered);
|
||||||
|
}
|
||||||
|
handleLeftColumnDragEnd();
|
||||||
|
},
|
||||||
|
[leftDraggedColumnIndex, componentConfig, handleLeftColumnDragEnd],
|
||||||
|
);
|
||||||
|
|
||||||
// 우측 패널 컬럼 헤더 드래그 (디자인 모드에서 컬럼 순서 변경)
|
// 우측 패널 컬럼 헤더 드래그 (디자인 모드에서 컬럼 순서 변경)
|
||||||
const handleRightColumnDragStart = useCallback(
|
const handleRightColumnDragStart = useCallback(
|
||||||
(e: React.DragEvent, columnIndex: number, source: "main" | number) => {
|
(e: React.DragEvent, columnIndex: number, source: "main" | number) => {
|
||||||
|
|
@ -3568,6 +3612,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
(componentConfig.leftPanel?.showEdit !== false) ||
|
(componentConfig.leftPanel?.showEdit !== false) ||
|
||||||
(componentConfig.leftPanel?.showDelete !== false)
|
(componentConfig.leftPanel?.showDelete !== false)
|
||||||
);
|
);
|
||||||
|
const canDragLeftGroupedColumns = !isDesignMode && columnsToShow.length > 1;
|
||||||
if (groupedLeftData.length > 0) {
|
if (groupedLeftData.length > 0) {
|
||||||
return (
|
return (
|
||||||
<div className="overflow-auto">
|
<div className="overflow-auto">
|
||||||
|
|
@ -3579,18 +3624,33 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
<table className="divide-y divide-border table-fixed" style={{ width: leftTotalColWidth > 100 ? `${leftTotalColWidth}%` : '100%' }}>
|
<table className="divide-y divide-border table-fixed" style={{ width: leftTotalColWidth > 100 ? `${leftTotalColWidth}%` : '100%' }}>
|
||||||
<thead className="bg-muted">
|
<thead className="bg-muted">
|
||||||
<tr>
|
<tr>
|
||||||
{columnsToShow.map((col, idx) => (
|
{columnsToShow.map((col, idx) => {
|
||||||
|
const isDropTarget = canDragLeftGroupedColumns && leftDropTargetColumnIndex === idx;
|
||||||
|
const isDragging = canDragLeftGroupedColumns && leftDraggedColumnIndex === idx;
|
||||||
|
return (
|
||||||
<th
|
<th
|
||||||
key={idx}
|
key={idx}
|
||||||
className="px-3 py-[7px] text-left text-[9px] font-bold tracking-[0.04em] text-muted-foreground uppercase whitespace-nowrap"
|
className={cn(
|
||||||
|
"group/th text-muted-foreground relative px-3 py-[7px] text-left text-[9px] font-bold uppercase tracking-[0.04em] whitespace-nowrap",
|
||||||
|
isDropTarget && "border-l-[3px] border-l-primary bg-primary/5",
|
||||||
|
canDragLeftGroupedColumns && "cursor-grab active:cursor-grabbing",
|
||||||
|
isDragging && "opacity-50",
|
||||||
|
)}
|
||||||
style={{
|
style={{
|
||||||
width: col.width && col.width <= 100 ? `${col.width}%` : "auto",
|
width: col.width && col.width <= 100 ? `${col.width}%` : "auto",
|
||||||
textAlign: col.align || "left",
|
textAlign: col.align || "left",
|
||||||
}}
|
}}
|
||||||
|
draggable={canDragLeftGroupedColumns}
|
||||||
|
onDragStart={(e) => canDragLeftGroupedColumns && handleLeftColumnDragStart(e, idx)}
|
||||||
|
onDragOver={(e) => canDragLeftGroupedColumns && handleLeftColumnDragOver(e, idx)}
|
||||||
|
onDragEnd={handleLeftColumnDragEnd}
|
||||||
|
onDrop={(e) => canDragLeftGroupedColumns && handleLeftColumnDrop(e, idx)}
|
||||||
>
|
>
|
||||||
|
{canDragLeftGroupedColumns && <GripVertical className="text-muted-foreground/40 absolute top-1/2 left-0.5 h-3 w-3 -translate-y-1/2 opacity-0 transition-opacity group-hover/th:opacity-100" />}
|
||||||
{col.label}
|
{col.label}
|
||||||
</th>
|
</th>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
{hasGroupedLeftActions && (
|
{hasGroupedLeftActions && (
|
||||||
<th className="bg-muted sticky right-0 z-10 px-3 py-[7px] text-right text-[9px] font-bold tracking-[0.04em] text-muted-foreground uppercase whitespace-nowrap" style={{ width: "80px" }}>
|
<th className="bg-muted sticky right-0 z-10 px-3 py-[7px] text-right text-[9px] font-bold tracking-[0.04em] text-muted-foreground uppercase whitespace-nowrap" style={{ width: "80px" }}>
|
||||||
</th>
|
</th>
|
||||||
|
|
@ -3671,23 +3731,39 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
(componentConfig.leftPanel?.showEdit !== false) ||
|
(componentConfig.leftPanel?.showEdit !== false) ||
|
||||||
(componentConfig.leftPanel?.showDelete !== false)
|
(componentConfig.leftPanel?.showDelete !== false)
|
||||||
);
|
);
|
||||||
|
const canDragLeftColumns = !isDesignMode && columnsToShow.length > 1;
|
||||||
return (
|
return (
|
||||||
<div className="overflow-auto">
|
<div className="overflow-auto">
|
||||||
<table className="divide-y divide-border table-fixed" style={{ width: leftTotalColWidth > 100 ? `${leftTotalColWidth}%` : '100%' }}>
|
<table className="divide-y divide-border table-fixed" style={{ width: leftTotalColWidth > 100 ? `${leftTotalColWidth}%` : '100%' }}>
|
||||||
<thead className="sticky top-0 z-10 bg-muted">
|
<thead className="sticky top-0 z-10 bg-muted">
|
||||||
<tr>
|
<tr>
|
||||||
{columnsToShow.map((col, idx) => (
|
{columnsToShow.map((col, idx) => {
|
||||||
|
const isDropTarget = canDragLeftColumns && leftDropTargetColumnIndex === idx;
|
||||||
|
const isDragging = canDragLeftColumns && leftDraggedColumnIndex === idx;
|
||||||
|
return (
|
||||||
<th
|
<th
|
||||||
key={idx}
|
key={idx}
|
||||||
className="px-3 py-[7px] text-left text-[9px] font-bold tracking-[0.04em] text-muted-foreground uppercase whitespace-nowrap"
|
className={cn(
|
||||||
|
"group/th text-muted-foreground relative px-3 py-[7px] text-left text-[9px] font-bold uppercase tracking-[0.04em] whitespace-nowrap",
|
||||||
|
isDropTarget && "border-l-[3px] border-l-primary bg-primary/5",
|
||||||
|
canDragLeftColumns && "cursor-grab active:cursor-grabbing",
|
||||||
|
isDragging && "opacity-50",
|
||||||
|
)}
|
||||||
style={{
|
style={{
|
||||||
width: col.width && col.width <= 100 ? `${col.width}%` : "auto",
|
width: col.width && col.width <= 100 ? `${col.width}%` : "auto",
|
||||||
textAlign: col.align || "left",
|
textAlign: col.align || "left",
|
||||||
}}
|
}}
|
||||||
|
draggable={canDragLeftColumns}
|
||||||
|
onDragStart={(e) => canDragLeftColumns && handleLeftColumnDragStart(e, idx)}
|
||||||
|
onDragOver={(e) => canDragLeftColumns && handleLeftColumnDragOver(e, idx)}
|
||||||
|
onDragEnd={handleLeftColumnDragEnd}
|
||||||
|
onDrop={(e) => canDragLeftColumns && handleLeftColumnDrop(e, idx)}
|
||||||
>
|
>
|
||||||
|
{canDragLeftColumns && <GripVertical className="text-muted-foreground/40 absolute top-1/2 left-0.5 h-3 w-3 -translate-y-1/2 opacity-0 transition-opacity group-hover/th:opacity-100" />}
|
||||||
{col.label}
|
{col.label}
|
||||||
</th>
|
</th>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
{hasLeftTableActions && (
|
{hasLeftTableActions && (
|
||||||
<th className="bg-muted sticky right-0 z-10 px-3 py-[7px] text-right text-[9px] font-bold tracking-[0.04em] text-muted-foreground uppercase whitespace-nowrap" style={{ width: "80px" }}>
|
<th className="bg-muted sticky right-0 z-10 px-3 py-[7px] text-right text-[9px] font-bold tracking-[0.04em] text-muted-foreground uppercase whitespace-nowrap" style={{ width: "80px" }}>
|
||||||
</th>
|
</th>
|
||||||
|
|
@ -4161,9 +4237,11 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
// 테이블 모드로 표시 (행 클릭 시 상세 정보 펼치기)
|
// 테이블 모드로 표시 (행 클릭 시 상세 정보 펼치기)
|
||||||
if (currentTabConfig?.displayMode === "table") {
|
if (currentTabConfig?.displayMode === "table") {
|
||||||
const hasTabActions = currentTabConfig?.showEdit || currentTabConfig?.showDelete;
|
const hasTabActions = currentTabConfig?.showEdit || currentTabConfig?.showDelete;
|
||||||
// showInSummary가 false가 아닌 것만 메인 테이블에 표시
|
|
||||||
const tabSummaryColumns = tabColumns.filter((col: any) => col.showInSummary !== false);
|
|
||||||
const tabIndex = activeTabIndex - 1;
|
const tabIndex = activeTabIndex - 1;
|
||||||
|
let tabSummaryColumns = tabColumns.filter((col: any) => col.showInSummary !== false);
|
||||||
|
if (!isDesignMode) {
|
||||||
|
tabSummaryColumns = applyRuntimeOrder(tabSummaryColumns, tabIndex);
|
||||||
|
}
|
||||||
const canDragTabColumns = tabSummaryColumns.length > 0;
|
const canDragTabColumns = tabSummaryColumns.length > 0;
|
||||||
return (
|
return (
|
||||||
<div className="h-full overflow-auto">
|
<div className="h-full overflow-auto">
|
||||||
|
|
@ -4188,7 +4266,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
onDragEnd={handleRightColumnDragEnd}
|
onDragEnd={handleRightColumnDragEnd}
|
||||||
onDrop={(e) => canDragTabColumns && handleRightColumnDrop(e, idx, tabIndex)}
|
onDrop={(e) => canDragTabColumns && handleRightColumnDrop(e, idx, tabIndex)}
|
||||||
>
|
>
|
||||||
{canDragTabColumns && <GripVertical className="text-muted-foreground/30 group-hover/th:text-muted-foreground/60 absolute top-1/2 left-0.5 h-3 w-3 -translate-y-1/2 transition-opacity" />}
|
{canDragTabColumns && <GripVertical className="text-muted-foreground/40 absolute top-1/2 left-0.5 h-3 w-3 -translate-y-1/2 opacity-0 transition-opacity group-hover/th:opacity-100" />}
|
||||||
{col.label || col.name}
|
{col.label || col.name}
|
||||||
</th>
|
</th>
|
||||||
);
|
);
|
||||||
|
|
@ -4298,9 +4376,11 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
// 리스트 모드도 테이블형으로 통일 (행 클릭 시 상세 정보 표시)
|
// 리스트 모드도 테이블형으로 통일 (행 클릭 시 상세 정보 표시)
|
||||||
{
|
{
|
||||||
const hasTabActions = currentTabConfig?.showEdit || currentTabConfig?.showDelete;
|
const hasTabActions = currentTabConfig?.showEdit || currentTabConfig?.showDelete;
|
||||||
// showInSummary가 false가 아닌 것만 메인 테이블에 표시
|
|
||||||
const listSummaryColumns = tabColumns.filter((col: any) => col.showInSummary !== false);
|
|
||||||
const listTabIndex = activeTabIndex - 1;
|
const listTabIndex = activeTabIndex - 1;
|
||||||
|
let listSummaryColumns = tabColumns.filter((col: any) => col.showInSummary !== false);
|
||||||
|
if (!isDesignMode) {
|
||||||
|
listSummaryColumns = applyRuntimeOrder(listSummaryColumns, listTabIndex);
|
||||||
|
}
|
||||||
const canDragListTabColumns = listSummaryColumns.length > 0;
|
const canDragListTabColumns = listSummaryColumns.length > 0;
|
||||||
return (
|
return (
|
||||||
<div className="h-full overflow-auto">
|
<div className="h-full overflow-auto">
|
||||||
|
|
@ -4325,7 +4405,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
onDragEnd={handleRightColumnDragEnd}
|
onDragEnd={handleRightColumnDragEnd}
|
||||||
onDrop={(e) => canDragListTabColumns && handleRightColumnDrop(e, idx, listTabIndex)}
|
onDrop={(e) => canDragListTabColumns && handleRightColumnDrop(e, idx, listTabIndex)}
|
||||||
>
|
>
|
||||||
{canDragListTabColumns && <GripVertical className="text-muted-foreground/30 group-hover/th:text-muted-foreground/60 absolute top-1/2 left-0.5 h-3 w-3 -translate-y-1/2 transition-opacity" />}
|
{canDragListTabColumns && <GripVertical className="text-muted-foreground/40 absolute top-1/2 left-0.5 h-3 w-3 -translate-y-1/2 opacity-0 transition-opacity group-hover/th:opacity-100" />}
|
||||||
{col.label || col.name}
|
{col.label || col.name}
|
||||||
</th>
|
</th>
|
||||||
);
|
);
|
||||||
|
|
@ -4752,7 +4832,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
|
||||||
onDragEnd={handleRightColumnDragEnd}
|
onDragEnd={handleRightColumnDragEnd}
|
||||||
onDrop={(e) => isDraggable && handleRightColumnDrop(e, configColIndex, "main")}
|
onDrop={(e) => isDraggable && handleRightColumnDrop(e, configColIndex, "main")}
|
||||||
>
|
>
|
||||||
{isDraggable && <GripVertical className="text-muted-foreground/30 group-hover/th:text-muted-foreground/60 absolute top-1/2 left-0.5 h-3 w-3 -translate-y-1/2 transition-opacity" />}
|
{isDraggable && <GripVertical className="text-muted-foreground/40 absolute top-1/2 left-0.5 h-3 w-3 -translate-y-1/2 opacity-0 transition-opacity group-hover/th:opacity-100" />}
|
||||||
{col.label}
|
{col.label}
|
||||||
</th>
|
</th>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue