feat: Add progress bar functionality to SplitPanelLayoutComponent and configuration options
- Implemented a new progress bar rendering function in the SplitPanelLayoutComponent to visually represent the ratio of child to parent values. - Enhanced the SortableColumnRow component to support progress column configuration, allowing users to set current and maximum values through a popover interface. - Updated the AdditionalTabConfigPanel to include options for adding progress columns, improving user experience in managing data visualization. These changes significantly enhance the functionality and usability of the split panel layout by providing visual progress indicators and configuration options for users.
This commit is contained in:
@@ -28,10 +28,10 @@ import { CSS } from "@dnd-kit/utilities";
|
||||
|
||||
// 드래그 가능한 컬럼 아이템
|
||||
function SortableColumnRow({
|
||||
id, col, index, isNumeric, isEntityJoin, onLabelChange, onWidthChange, onFormatChange, onRemove, onShowInSummaryChange, onShowInDetailChange,
|
||||
id, col, index, isNumeric, isEntityJoin, onLabelChange, onWidthChange, onFormatChange, onRemove, onShowInSummaryChange, onShowInDetailChange, onProgressChange, availableChildColumns, availableParentColumns,
|
||||
}: {
|
||||
id: string;
|
||||
col: { name: string; label: string; width?: number; format?: any; showInSummary?: boolean; showInDetail?: boolean };
|
||||
col: { name: string; label: string; width?: number; format?: any; showInSummary?: boolean; showInDetail?: boolean; type?: string; numerator?: string; denominator?: string };
|
||||
index: number;
|
||||
isNumeric: boolean;
|
||||
isEntityJoin?: boolean;
|
||||
@@ -41,6 +41,9 @@ function SortableColumnRow({
|
||||
onRemove: () => void;
|
||||
onShowInSummaryChange?: (checked: boolean) => void;
|
||||
onShowInDetailChange?: (checked: boolean) => void;
|
||||
onProgressChange?: (updates: { numerator?: string; denominator?: string }) => void;
|
||||
availableChildColumns?: Array<{ columnName: string; columnLabel: string }>;
|
||||
availableParentColumns?: Array<{ columnName: string; columnLabel: string }>;
|
||||
}) {
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });
|
||||
const style = { transform: CSS.Transform.toString(transform), transition };
|
||||
@@ -53,12 +56,44 @@ function SortableColumnRow({
|
||||
"flex items-center gap-1.5 rounded-md border bg-card px-2 py-1.5",
|
||||
isDragging && "z-50 opacity-50 shadow-md",
|
||||
isEntityJoin && "border-blue-200 bg-blue-50/30",
|
||||
col.type === "progress" && "border-emerald-200 bg-emerald-50/30",
|
||||
)}
|
||||
>
|
||||
<div {...attributes} {...listeners} className="text-muted-foreground hover:text-foreground cursor-grab touch-none">
|
||||
<GripVertical className="h-3 w-3" />
|
||||
</div>
|
||||
{isEntityJoin ? (
|
||||
{col.type === "progress" ? (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<button className="shrink-0 cursor-pointer rounded bg-emerald-100 px-1 text-[9px] font-medium text-emerald-700 hover:bg-emerald-200" title="클릭하여 설정 변경">BAR</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-56 space-y-2 p-3" align="start">
|
||||
<p className="text-xs font-medium">프로그레스 설정</p>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[10px]">현재값 (자식 컬럼)</Label>
|
||||
<Select value={col.numerator || ""} onValueChange={(v) => onProgressChange?.({ numerator: v })}>
|
||||
<SelectTrigger className="h-7 text-[10px]"><SelectValue placeholder="컬럼 선택" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{(availableChildColumns || []).map((c) => (
|
||||
<SelectItem key={c.columnName} value={c.columnName} className="text-xs">{c.columnLabel || c.columnName}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[10px]">최대값 (부모 컬럼)</Label>
|
||||
<Select value={col.denominator || ""} onValueChange={(v) => onProgressChange?.({ denominator: v })}>
|
||||
<SelectTrigger className="h-7 text-[10px]"><SelectValue placeholder="컬럼 선택" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
{(availableParentColumns || []).map((c) => (
|
||||
<SelectItem key={c.columnName} value={c.columnName} className="text-xs">{c.columnLabel || c.columnName}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
) : isEntityJoin ? (
|
||||
<Link2 className="h-3 w-3 shrink-0 text-blue-500" title="Entity 조인 컬럼" />
|
||||
) : (
|
||||
<span className="text-muted-foreground w-5 shrink-0 text-center text-[10px] font-medium">#{index + 1}</span>
|
||||
@@ -656,6 +691,13 @@ const AdditionalTabConfigPanel: React.FC<AdditionalTabConfigPanelProps> = ({
|
||||
newColumns[index] = { ...newColumns[index], showInDetail: checked };
|
||||
updateTab({ columns: newColumns });
|
||||
}}
|
||||
onProgressChange={(updates) => {
|
||||
const newColumns = [...selectedColumns];
|
||||
newColumns[index] = { ...newColumns[index], ...updates };
|
||||
updateTab({ columns: newColumns });
|
||||
}}
|
||||
availableChildColumns={tabColumns.map((c) => ({ columnName: c.columnName, columnLabel: c.columnLabel || c.columnName }))}
|
||||
availableParentColumns={leftTableColumns.map((c) => ({ columnName: c.columnName, columnLabel: c.columnLabel || c.columnName }))}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -685,6 +727,104 @@ const AdditionalTabConfigPanel: React.FC<AdditionalTabConfigPanelProps> = ({
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 프로그레스 컬럼 추가 */}
|
||||
{tab.tableName && (
|
||||
<div className="border-border/60 my-2 border-t pt-2">
|
||||
<details className="group">
|
||||
<summary className="flex cursor-pointer list-none items-center gap-2 select-none">
|
||||
<ChevronRight className="h-3 w-3 shrink-0 text-emerald-500 transition-transform group-open:rotate-90" />
|
||||
<span className="text-[10px] font-medium text-emerald-600">프로그레스 컬럼 추가</span>
|
||||
</summary>
|
||||
<div className="mt-2 space-y-2 rounded-md border border-emerald-200 bg-emerald-50/50 p-2">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[10px]">라벨</Label>
|
||||
<Input
|
||||
id={`tab-${tabIndex}-progress-label`}
|
||||
placeholder="예: 샷수 현황"
|
||||
className="h-7 text-xs"
|
||||
defaultValue=""
|
||||
/>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[10px]">현재값 (자식 컬럼)</Label>
|
||||
<Select
|
||||
onValueChange={(v) => {
|
||||
const el = document.getElementById(`tab-${tabIndex}-progress-numerator`) as HTMLInputElement;
|
||||
if (el) el.value = v;
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-7 text-[10px]">
|
||||
<SelectValue placeholder="컬럼 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{tabColumns.map((col) => (
|
||||
<SelectItem key={col.columnName} value={col.columnName} className="text-xs">
|
||||
{col.columnLabel || col.columnName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<input type="hidden" id={`tab-${tabIndex}-progress-numerator`} />
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<Label className="text-[10px]">최대값 (부모 컬럼)</Label>
|
||||
<Select
|
||||
onValueChange={(v) => {
|
||||
const el = document.getElementById(`tab-${tabIndex}-progress-denominator`) as HTMLInputElement;
|
||||
if (el) el.value = v;
|
||||
}}
|
||||
>
|
||||
<SelectTrigger className="h-7 text-[10px]">
|
||||
<SelectValue placeholder="컬럼 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{leftTableColumns.map((col) => (
|
||||
<SelectItem key={col.columnName} value={col.columnName} className="text-xs">
|
||||
{col.columnLabel || col.columnName}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<input type="hidden" id={`tab-${tabIndex}-progress-denominator`} />
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-7 w-full text-xs text-emerald-700 border-emerald-300 hover:bg-emerald-100"
|
||||
onClick={() => {
|
||||
const labelEl = document.getElementById(`tab-${tabIndex}-progress-label`) as HTMLInputElement;
|
||||
const numEl = document.getElementById(`tab-${tabIndex}-progress-numerator`) as HTMLInputElement;
|
||||
const denEl = document.getElementById(`tab-${tabIndex}-progress-denominator`) as HTMLInputElement;
|
||||
const label = labelEl?.value || "프로그레스";
|
||||
const numerator = numEl?.value;
|
||||
const denominator = denEl?.value;
|
||||
if (!numerator || !denominator) return;
|
||||
updateTab({
|
||||
columns: [
|
||||
...selectedColumns,
|
||||
{
|
||||
name: `progress_${numerator}_${denominator}`,
|
||||
label,
|
||||
width: 200,
|
||||
type: "progress",
|
||||
numerator,
|
||||
denominator,
|
||||
} as any,
|
||||
],
|
||||
});
|
||||
if (labelEl) labelEl.value = "";
|
||||
}}
|
||||
>
|
||||
추가
|
||||
</Button>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Entity 조인 컬럼 - 아코디언 (접기/펼치기) */}
|
||||
{(() => {
|
||||
const joinData = tab.tableName ? entityJoinColumnsMap?.[tab.tableName] : null;
|
||||
|
||||
Reference in New Issue
Block a user