This commit is contained in:
DDD1542
2026-03-17 21:50:37 +09:00
parent cfd7ee9fce
commit b293d184bb
8 changed files with 219 additions and 141 deletions

View File

@@ -21,7 +21,7 @@ import {
Move,
FileSpreadsheet,
List,
LayoutPanelRight,
PanelRight,
} from "lucide-react";
import { dataApi } from "@/lib/api/data";
import { entityJoinApi } from "@/lib/api/entityJoin";
@@ -3524,7 +3524,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
{columnsToShow.map((col, idx) => (
<th
key={idx}
className="px-3 py-2 text-left text-xs font-medium tracking-wider text-muted-foreground uppercase whitespace-nowrap"
className="px-3 py-[7px] text-left text-[9px] font-bold tracking-[0.04em] text-muted-foreground uppercase whitespace-nowrap"
style={{
width: col.width && col.width <= 100 ? `${col.width}%` : "auto",
textAlign: col.align || "left",
@@ -3534,7 +3534,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
</th>
))}
{hasGroupedLeftActions && (
<th className="bg-muted sticky right-0 z-10 px-3 py-2 text-right text-xs font-medium tracking-wider 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>
)}
</tr>
@@ -3621,7 +3621,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
{columnsToShow.map((col, idx) => (
<th
key={idx}
className="px-3 py-2 text-left text-xs font-medium tracking-wider text-muted-foreground uppercase whitespace-nowrap"
className="px-3 py-[7px] text-left text-[9px] font-bold tracking-[0.04em] text-muted-foreground uppercase whitespace-nowrap"
style={{
width: col.width && col.width <= 100 ? `${col.width}%` : "auto",
textAlign: col.align || "left",
@@ -3631,7 +3631,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
</th>
))}
{hasLeftTableActions && (
<th className="bg-muted sticky right-0 z-10 px-3 py-2 text-right text-xs font-medium tracking-wider 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>
)}
</tr>
@@ -3972,17 +3972,17 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
>
<div className="flex w-full items-center justify-between gap-2">
<div className="flex min-w-0 flex-1 items-center gap-2">
<LayoutPanelRight className="h-4 w-4 shrink-0 text-muted-foreground" />
<PanelRight className="h-4 w-4 shrink-0 text-muted-foreground" />
{/* 탭이 없으면 제목만, 있으면 탭으로 전환 (2px primary 밑줄 인디케이터) */}
{(componentConfig.rightPanel?.additionalTabs?.length || 0) > 0 ? (
<div className="flex items-center gap-0">
<button
onClick={() => handleTabChange(0)}
className={cn(
"px-3 py-1 text-sm font-medium transition-colors",
"px-3.5 py-2 text-[10px] font-semibold transition-all -mb-px",
activeTabIndex === 0
? "text-primary border-b-2 border-primary font-semibold bg-primary/5"
: "text-foreground/70 hover:text-foreground hover:bg-muted/30"
? "text-primary border-b-2 border-primary"
: "text-muted-foreground border-b-2 border-transparent hover:text-foreground"
)}
>
{componentConfig.rightPanel?.title || "기본"}
@@ -3992,10 +3992,10 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
key={tab.tabId || `tab-${index}`}
onClick={() => handleTabChange(index + 1)}
className={cn(
"px-3 py-1 text-sm font-medium transition-colors",
"px-3.5 py-2 text-[10px] font-semibold transition-all -mb-px",
activeTabIndex === index + 1
? "text-primary border-b-2 border-primary font-semibold bg-primary/5"
: "text-foreground/70 hover:text-foreground hover:bg-muted/30"
? "text-primary border-b-2 border-primary"
: "text-muted-foreground border-b-2 border-transparent hover:text-foreground"
)}
>
{tab.label || `${index + 1}`}
@@ -4120,7 +4120,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
<th
key={col.name}
className={cn(
"text-muted-foreground px-3 py-2 text-left text-xs font-semibold",
"text-muted-foreground px-3 py-[7px] text-left text-[9px] font-bold uppercase tracking-[0.04em]",
isDropTarget && "border-l-[3px] border-l-primary bg-primary/5",
canDragTabColumns && "cursor-grab active:cursor-grabbing",
isDragging && "opacity-50",
@@ -4136,7 +4136,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
);
})}
{hasTabActions && (
<th className="text-muted-foreground px-3 py-2 text-right text-xs font-semibold"></th>
<th className="text-muted-foreground px-3 py-[7px] text-right text-[9px] font-bold uppercase tracking-[0.04em]"></th>
)}
</tr>
</thead>
@@ -4157,13 +4157,13 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
<React.Fragment key={tabItemId}>
<tr
className={cn(
"cursor-pointer border-b border-border/40 transition-colors",
isTabExpanded ? "bg-primary/5" : idx % 2 === 1 ? "bg-muted/10 hover:bg-muted/30" : "hover:bg-muted/30",
"group/action cursor-pointer border-b border-border/50 transition-[background] duration-75",
isTabExpanded ? "bg-primary/5" : idx % 2 === 1 ? "bg-muted/50 hover:bg-accent" : "hover:bg-accent",
)}
onClick={() => toggleRightItemExpansion(`tab_${activeTabIndex}_${tabItemId}`)}
>
{tabSummaryColumns.map((col: any) => (
<td key={col.name} className="px-3 py-2 text-xs" style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
<td key={col.name} className="px-3 py-2 text-[11px]" style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{col.type === "progress"
? renderProgressCell(col, item, selectedLeftItem)
: formatCellValue(
@@ -4256,7 +4256,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
<th
key={col.name}
className={cn(
"text-muted-foreground px-3 py-2 text-left text-xs font-semibold",
"text-muted-foreground px-3 py-[7px] text-left text-[9px] font-bold uppercase tracking-[0.04em]",
isDropTarget && "border-l-[3px] border-l-primary bg-primary/5",
canDragListTabColumns && "cursor-grab active:cursor-grabbing",
isDragging && "opacity-50",
@@ -4272,7 +4272,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
);
})}
{hasTabActions && (
<th className="text-muted-foreground px-3 py-2 text-right text-xs font-semibold"></th>
<th className="text-muted-foreground px-3 py-[7px] text-right text-[9px] font-bold uppercase tracking-[0.04em]"></th>
)}
</tr>
</thead>
@@ -4292,13 +4292,13 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
<React.Fragment key={tabItemId}>
<tr
className={cn(
"cursor-pointer border-b border-border/40 transition-colors",
isTabExpanded ? "bg-primary/5" : idx % 2 === 1 ? "bg-muted/10 hover:bg-muted/30" : "hover:bg-muted/30",
"group/action cursor-pointer border-b border-border/50 transition-[background] duration-75",
isTabExpanded ? "bg-primary/5" : idx % 2 === 1 ? "bg-muted/50 hover:bg-accent" : "hover:bg-accent",
)}
onClick={() => toggleRightItemExpansion(`tab_${activeTabIndex}_${tabItemId}`)}
>
{listSummaryColumns.map((col: any) => (
<td key={col.name} className="px-3 py-2 text-xs" style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
<td key={col.name} className="px-3 py-2 text-[11px]" style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{col.type === "progress"
? renderProgressCell(col, item, selectedLeftItem)
: formatCellValue(
@@ -4670,7 +4670,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
<th
key={idx}
className={cn(
"text-muted-foreground px-3 py-2 text-left text-xs font-semibold whitespace-nowrap",
"text-muted-foreground 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",
isDraggable && "cursor-grab active:cursor-grabbing",
isDragging && "opacity-50",
@@ -4689,26 +4689,29 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
</th>
);
})}
{/* 수정 또는 삭제 버튼이 하나라도 활성화되어 있을 때만 작업 컬럼 표시 */}
{!isDesignMode &&
((componentConfig.rightPanel?.editButton?.enabled ?? true) ||
(componentConfig.rightPanel?.deleteButton?.enabled ?? true)) && (
<th className="text-muted-foreground px-3 py-2 text-right text-xs font-semibold" style={{ width: '80px' }}>
{(() => {
const rightEditVisible = (componentConfig.rightPanel?.showEdit ?? componentConfig.rightPanel?.editButton?.enabled) !== false;
const rightDeleteVisible = (componentConfig.rightPanel?.showDelete ?? componentConfig.rightPanel?.deleteButton?.enabled) !== false;
return !isDesignMode && (rightEditVisible || rightDeleteVisible) ? (
<th className="bg-background text-muted-foreground sticky right-0 z-10 px-3 py-[7px] text-right text-[9px] font-bold uppercase tracking-[0.04em]" style={{ width: '80px' }}>
</th>
)}
) : null;
})()}
</tr>
</thead>
<tbody>
{filteredData.map((item, idx) => {
const itemId = item.id || item.ID || idx;
const rightEditVisible = (componentConfig.rightPanel?.showEdit ?? componentConfig.rightPanel?.editButton?.enabled) !== false;
const rightDeleteVisible = (componentConfig.rightPanel?.showDelete ?? componentConfig.rightPanel?.deleteButton?.enabled) !== false;
return (
<tr key={itemId} className={cn("group/action border-b border-border/40 transition-colors hover:bg-muted/30", idx % 2 === 1 && "bg-muted/10")}>
<tr key={itemId} className={cn("group/action border-b border-border/50 transition-[background] duration-75 hover:bg-accent", idx % 2 === 1 && "bg-muted/50")}>
{columnsToShow.map((col, colIdx) => (
<td
key={colIdx}
className="px-3 py-2 text-xs"
className="px-3 py-2 text-[11px]"
style={{ textAlign: col.align || "left", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}
>
{col.type === "progress"
@@ -4722,12 +4725,10 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
</td>
))}
{/* 수정 또는 삭제 버튼이 하나라도 활성화되어 있을 때만 작업 셀 표시 */}
{!isDesignMode &&
((componentConfig.rightPanel?.editButton?.enabled ?? true) ||
(componentConfig.rightPanel?.deleteButton?.enabled ?? true)) && (
<td className="px-3 py-2 text-right text-sm whitespace-nowrap group/action">
{!isDesignMode && (rightEditVisible || rightDeleteVisible) && (
<td className="bg-card sticky right-0 z-10 px-3 py-2 text-right text-sm whitespace-nowrap group-hover/action:bg-accent">
<div className="flex justify-end gap-1 opacity-0 transition-opacity group-hover/action:opacity-100">
{(componentConfig.rightPanel?.editButton?.enabled ?? true) && (
{rightEditVisible && (
<Button
variant={
componentConfig.rightPanel?.editButton?.buttonVariant || "outline"
@@ -4743,7 +4744,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
{componentConfig.rightPanel?.editButton?.buttonLabel || "수정"}
</Button>
)}
{(componentConfig.rightPanel?.deleteButton?.enabled ?? true) && (
{rightDeleteVisible && (
<button
onClick={(e) => {
e.stopPropagation();
@@ -4801,8 +4802,8 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
return sum + w;
}, 0);
const hasEditButton = !isDesignMode && (componentConfig.rightPanel?.editButton?.enabled ?? true);
const hasDeleteButton = !isDesignMode && (componentConfig.rightPanel?.deleteButton?.enabled ?? true);
const hasEditButton = !isDesignMode && (componentConfig.rightPanel?.showEdit ?? componentConfig.rightPanel?.editButton?.enabled) !== false;
const hasDeleteButton = !isDesignMode && (componentConfig.rightPanel?.showDelete ?? componentConfig.rightPanel?.deleteButton?.enabled) !== false;
const hasActions = hasEditButton || hasDeleteButton;
return filteredData.length > 0 ? (
@@ -4814,14 +4815,14 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
{columnsToDisplay.map((col) => (
<th
key={col.name}
className="text-muted-foreground px-3 py-2 text-left text-xs font-semibold whitespace-nowrap"
className="text-muted-foreground px-3 py-[7px] text-left text-[9px] font-bold uppercase tracking-[0.04em] whitespace-nowrap"
style={{ width: col.width && col.width <= 100 ? `${col.width}%` : "auto" }}
>
{col.label}
</th>
))}
{hasActions && (
<th className="text-muted-foreground px-3 py-2 text-right text-xs font-semibold" style={{ width: '80px' }}></th>
<th className="bg-background text-muted-foreground sticky right-0 z-10 px-3 py-[7px] text-right text-[9px] font-bold uppercase tracking-[0.04em]" style={{ width: '80px' }}></th>
)}
</tr>
</thead>
@@ -4849,13 +4850,13 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
<React.Fragment key={itemId}>
<tr
className={cn(
"group/action cursor-pointer border-b border-border/40 transition-colors",
isExpanded ? "bg-primary/5" : idx % 2 === 1 ? "bg-muted/10 hover:bg-muted/30" : "hover:bg-muted/30",
"group/action cursor-pointer border-b border-border/50 transition-[background] duration-75",
isExpanded ? "bg-primary/5" : idx % 2 === 1 ? "bg-muted/50 hover:bg-accent" : "hover:bg-accent",
)}
onClick={() => toggleRightItemExpansion(itemId)}
>
{columnsToDisplay.map((col) => (
<td key={col.name} className="px-3 py-2 text-xs" style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
<td key={col.name} className="px-3 py-2 text-[11px]" style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
{formatCellValue(
col.name,
getEntityJoinValue(item, col.name),
@@ -4865,7 +4866,7 @@ export const SplitPanelLayoutComponent: React.FC<SplitPanelLayoutComponentProps>
</td>
))}
{hasActions && (
<td className="px-3 py-2 text-right">
<td className="bg-card sticky right-0 z-10 px-3 py-2 text-right group-hover/action:bg-accent">
<div className="flex items-center justify-end gap-1 opacity-0 transition-opacity group-hover/action:opacity-100">
{hasEditButton && (
<Button size="sm" variant="ghost" className="h-7 px-2 text-xs"