ERP-node/frontend/components/screen/table-options/GroupingPanel.tsx

188 lines
7.2 KiB
TypeScript
Raw Normal View History

import React, { useState } from "react";
import { useTableOptions } from "@/contexts/TableOptionsContext";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
import { ScrollArea } from "@/components/ui/scroll-area";
import { ArrowRight, GripVertical, X } from "lucide-react";
interface Props {
isOpen: boolean;
onClose: () => void;
}
export const GroupingPanel: React.FC<Props> = ({ isOpen, onClose }) => {
const { getTable, selectedTableId } = useTableOptions();
const table = selectedTableId ? getTable(selectedTableId) : undefined;
const [selectedColumns, setSelectedColumns] = useState<string[]>([]);
const [draggedIndex, setDraggedIndex] = useState<number | null>(null);
const toggleColumn = (columnName: string) => {
if (selectedColumns.includes(columnName)) {
setSelectedColumns(selectedColumns.filter((c) => c !== columnName));
} else {
setSelectedColumns([...selectedColumns, columnName]);
}
};
const removeColumn = (columnName: string) => {
setSelectedColumns(selectedColumns.filter((c) => c !== columnName));
};
const moveColumn = (fromIndex: number, toIndex: number) => {
const newColumns = [...selectedColumns];
const [movedItem] = newColumns.splice(fromIndex, 1);
newColumns.splice(toIndex, 0, movedItem);
setSelectedColumns(newColumns);
};
const handleDragStart = (index: number) => {
setDraggedIndex(index);
};
const handleDragOver = (e: React.DragEvent, index: number) => {
e.preventDefault();
if (draggedIndex === null || draggedIndex === index) return;
moveColumn(draggedIndex, index);
setDraggedIndex(index);
};
const handleDragEnd = () => {
setDraggedIndex(null);
};
const applyGrouping = () => {
table?.onGroupChange(selectedColumns);
onClose();
};
const clearGrouping = () => {
setSelectedColumns([]);
table?.onGroupChange([]);
};
return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="max-w-[95vw] sm:max-w-xl">
<DialogHeader>
<DialogTitle className="text-base sm:text-lg"> </DialogTitle>
<DialogDescription className="text-xs sm:text-sm"> </DialogDescription>
</DialogHeader>
<div className="space-y-3 sm:space-y-4">
{/* 선택된 컬럼 (드래그 가능) */}
{selectedColumns.length > 0 && (
<div>
<div className="mb-2 flex items-center justify-between">
<div className="text-xs font-medium sm:text-sm"> ({selectedColumns.length})</div>
<Button variant="ghost" size="sm" onClick={clearGrouping} className="h-7 text-xs">
</Button>
</div>
<div className="max-h-[40vh] space-y-2 overflow-y-auto pr-1">
{selectedColumns.map((colName, index) => {
const col = table?.columns.find((c) => c.columnName === colName);
if (!col) return null;
return (
<div
key={colName}
draggable
onDragStart={() => handleDragStart(index)}
onDragOver={(e) => handleDragOver(e, index)}
onDragEnd={handleDragEnd}
className="bg-primary/5 hover:bg-primary/10 flex cursor-move items-center gap-2 rounded-lg border p-2 transition-colors sm:p-3"
>
<GripVertical className="text-muted-foreground h-4 w-4 flex-shrink-0" />
<div className="bg-primary text-primary-foreground flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full text-xs sm:h-6 sm:w-6">
{index + 1}
</div>
<div className="min-w-0 flex-1">
<div className="truncate text-xs font-medium sm:text-sm">{col.columnLabel}</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={() => removeColumn(colName)}
className="h-6 w-6 flex-shrink-0 p-0"
>
<X className="h-3 w-3" />
</Button>
</div>
);
})}
</div>
{/* 그룹화 순서 미리보기 */}
<div className="bg-muted/30 mt-2 rounded-lg border p-2">
<div className="flex flex-wrap items-center gap-2 text-xs">
{selectedColumns.map((colName, index) => {
const col = table?.columns.find((c) => c.columnName === colName);
return (
<React.Fragment key={colName}>
<span className="font-medium">{col?.columnLabel}</span>
{index < selectedColumns.length - 1 && <ArrowRight className="text-muted-foreground h-3 w-3" />}
</React.Fragment>
);
})}
</div>
</div>
</div>
)}
{/* 사용 가능한 컬럼 */}
<div>
<div className="mb-2 text-xs font-medium sm:text-sm"> </div>
<ScrollArea className={selectedColumns.length > 0 ? "h-[280px] sm:h-[320px]" : "h-[400px] sm:h-[450px]"}>
<div className="space-y-2 pr-4">
{table?.columns
.filter((col) => !selectedColumns.includes(col.columnName))
.map((col) => {
return (
<div
key={col.columnName}
className="bg-background hover:bg-muted/50 flex cursor-pointer items-center gap-3 rounded-lg border p-2 transition-colors sm:p-3"
onClick={() => toggleColumn(col.columnName)}
>
<Checkbox
checked={false}
onCheckedChange={() => toggleColumn(col.columnName)}
className="flex-shrink-0"
/>
<div className="min-w-0 flex-1">
<div className="truncate text-xs font-medium sm:text-sm">{col.columnLabel}</div>
<div className="text-muted-foreground truncate text-[10px] sm:text-xs">{col.columnName}</div>
</div>
</div>
);
})}
</div>
</ScrollArea>
</div>
</div>
<DialogFooter className="gap-2 sm:gap-0">
<Button variant="outline" onClick={onClose} className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm">
</Button>
<Button onClick={applyGrouping} className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm">
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};