ERP-node/frontend/components/common/TableOptionsModal.tsx

324 lines
11 KiB
TypeScript
Raw Permalink Normal View History

2025-11-05 16:36:32 +09:00
"use client";
import React, { useState, useEffect } from "react";
import {
2025-12-05 10:46:10 +09:00
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from "@/components/ui/dialog";
2025-11-05 16:36:32 +09:00
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { Input } from "@/components/ui/input";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { GripVertical, Eye, EyeOff } from "lucide-react";
interface ColumnConfig {
columnName: string;
label: string;
visible: boolean;
width?: number;
frozen?: boolean;
}
interface TableOptionsModalProps {
isOpen: boolean;
onClose: () => void;
columns: ColumnConfig[];
onSave: (config: {
columns: ColumnConfig[];
showGridLines: boolean;
viewMode: "table" | "card" | "grouped-card";
}) => void;
tableName: string;
userId?: string;
}
export function TableOptionsModal({
isOpen,
onClose,
columns: initialColumns,
onSave,
tableName,
userId = "guest",
}: TableOptionsModalProps) {
const [columns, setColumns] = useState<ColumnConfig[]>(initialColumns);
const [showGridLines, setShowGridLines] = useState(true);
const [viewMode, setViewMode] = useState<"table" | "card" | "grouped-card">("table");
const [draggedIndex, setDraggedIndex] = useState<number | null>(null);
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
// localStorage에서 설정 불러오기
useEffect(() => {
if (isOpen) {
const storageKey = `table_options_${tableName}_${userId}`;
const savedConfig = localStorage.getItem(storageKey);
if (savedConfig) {
try {
const parsed = JSON.parse(savedConfig);
setColumns(parsed.columns || initialColumns);
setShowGridLines(parsed.showGridLines ?? true);
setViewMode(parsed.viewMode || "table");
} catch (error) {
console.error("설정 불러오기 실패:", error);
}
} else {
setColumns(initialColumns);
}
}
}, [isOpen, tableName, userId, initialColumns]);
// 컬럼 표시/숨기기 토글
const toggleColumnVisibility = (index: number) => {
const newColumns = [...columns];
newColumns[index].visible = !newColumns[index].visible;
setColumns(newColumns);
};
// 컬럼 틀고정 토글
const toggleColumnFrozen = (index: number) => {
const newColumns = [...columns];
newColumns[index].frozen = !newColumns[index].frozen;
setColumns(newColumns);
};
// 컬럼 너비 변경
const updateColumnWidth = (index: number, width: number) => {
const newColumns = [...columns];
newColumns[index].width = width;
setColumns(newColumns);
};
// 드래그 앤 드롭 핸들러
const handleDragStart = (index: number) => {
setDraggedIndex(index);
};
const handleDragOver = (e: React.DragEvent, index: number) => {
e.preventDefault();
setDragOverIndex(index);
};
const handleDrop = (e: React.DragEvent, dropIndex: number) => {
e.preventDefault();
if (draggedIndex === null || draggedIndex === dropIndex) {
setDraggedIndex(null);
setDragOverIndex(null);
return;
}
const newColumns = [...columns];
const [draggedColumn] = newColumns.splice(draggedIndex, 1);
newColumns.splice(dropIndex, 0, draggedColumn);
setColumns(newColumns);
setDraggedIndex(null);
setDragOverIndex(null);
};
const handleDragEnd = () => {
setDraggedIndex(null);
setDragOverIndex(null);
};
// 저장
const handleSave = () => {
const config = {
columns,
showGridLines,
viewMode,
};
// localStorage에 저장
const storageKey = `table_options_${tableName}_${userId}`;
localStorage.setItem(storageKey, JSON.stringify(config));
onSave(config);
onClose();
};
// 초기화
const handleReset = () => {
setColumns(initialColumns);
setShowGridLines(true);
setViewMode("table");
};
return (
2025-12-05 10:46:10 +09:00
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent className="max-h-[90vh] sm:max-w-[700px]">
<DialogHeader>
<DialogTitle className="text-base sm:text-lg"> </DialogTitle>
<DialogDescription className="text-xs sm:text-sm">
/, , .
</DialogDescription>
</DialogHeader>
2025-11-05 16:36:32 +09:00
<Tabs defaultValue="columns" className="flex flex-col flex-1 overflow-hidden">
<TabsList className="grid w-full grid-cols-3 flex-shrink-0">
2025-11-05 16:36:32 +09:00
<TabsTrigger value="columns" className="text-xs sm:text-sm"> </TabsTrigger>
<TabsTrigger value="display" className="text-xs sm:text-sm"> </TabsTrigger>
<TabsTrigger value="view" className="text-xs sm:text-sm"> </TabsTrigger>
</TabsList>
{/* 컬럼 설정 탭 */}
<TabsContent value="columns" className="flex-1 overflow-y-auto space-y-3 sm:space-y-4 mt-4">
2025-11-05 16:36:32 +09:00
<div className="text-xs sm:text-sm text-muted-foreground mb-2">
, / .
</div>
<div className="space-y-2">
2025-11-05 16:36:32 +09:00
{columns.map((column, index) => (
<div
key={column.columnName}
draggable
onDragStart={() => handleDragStart(index)}
onDragOver={(e) => handleDragOver(e, index)}
onDrop={(e) => handleDrop(e, index)}
onDragEnd={handleDragEnd}
className={`flex items-center gap-2 p-2 sm:p-3 border rounded-md bg-card hover:bg-accent/50 transition-colors cursor-move ${
dragOverIndex === index ? "border-primary" : "border-border"
}`}
>
{/* 드래그 핸들 */}
<GripVertical className="h-4 w-4 text-muted-foreground flex-shrink-0" />
{/* 컬럼명 */}
<div className="flex-1 min-w-0">
<div className="text-xs sm:text-sm font-medium truncate">
{column.label}
</div>
<div className="text-[10px] sm:text-xs text-muted-foreground truncate">
{column.columnName}
</div>
</div>
{/* 너비 설정 */}
<div className="flex items-center gap-1 sm:gap-2">
<Label className="text-[10px] sm:text-xs whitespace-nowrap">:</Label>
<Input
type="number"
value={column.width || 150}
onChange={(e) => updateColumnWidth(index, parseInt(e.target.value) || 150)}
className="h-7 w-16 sm:h-8 sm:w-20 text-xs"
min={50}
max={500}
/>
</div>
{/* 틀고정 */}
<Button
variant={column.frozen ? "default" : "outline"}
size="sm"
onClick={() => toggleColumnFrozen(index)}
className="h-7 px-2 text-[10px] sm:h-8 sm:px-3 sm:text-xs"
>
{column.frozen ? "고정됨" : "고정"}
</Button>
{/* 표시/숨기기 */}
<Button
variant="ghost"
size="icon"
onClick={() => toggleColumnVisibility(index)}
className="h-7 w-7 sm:h-8 sm:w-8 flex-shrink-0"
>
{column.visible ? (
<Eye className="h-4 w-4 text-primary" />
) : (
<EyeOff className="h-4 w-4 text-muted-foreground" />
)}
</Button>
</div>
))}
</div>
</TabsContent>
{/* 표시 설정 탭 */}
<TabsContent value="display" className="flex-1 overflow-y-auto space-y-3 sm:space-y-4 mt-4">
2025-11-05 16:36:32 +09:00
<div className="flex items-center justify-between p-3 sm:p-4 border rounded-md bg-card">
<div className="space-y-0.5">
<Label className="text-xs sm:text-sm font-medium"> </Label>
<p className="text-[10px] sm:text-xs text-muted-foreground">
</p>
</div>
<Switch
checked={showGridLines}
onCheckedChange={setShowGridLines}
/>
</div>
</TabsContent>
{/* 보기 모드 탭 */}
<TabsContent value="view" className="flex-1 overflow-y-auto space-y-3 sm:space-y-4 mt-4">
2025-11-05 16:36:32 +09:00
<div className="grid gap-3">
<Button
variant={viewMode === "table" ? "default" : "outline"}
onClick={() => setViewMode("table")}
className="h-auto flex-col items-start p-3 sm:p-4 text-left"
>
<div className="text-sm sm:text-base font-semibold"></div>
<div className="text-xs text-muted-foreground mt-1">
/
</div>
</Button>
<Button
variant={viewMode === "card" ? "default" : "outline"}
onClick={() => setViewMode("card")}
className="h-auto flex-col items-start p-3 sm:p-4 text-left"
>
<div className="text-sm sm:text-base font-semibold"></div>
<div className="text-xs text-muted-foreground mt-1">
( )
</div>
</Button>
<Button
variant={viewMode === "grouped-card" ? "default" : "outline"}
onClick={() => setViewMode("grouped-card")}
className="h-auto flex-col items-start p-3 sm:p-4 text-left"
>
<div className="text-sm sm:text-base font-semibold"> </div>
<div className="text-xs text-muted-foreground mt-1">
</div>
</Button>
</div>
</TabsContent>
</Tabs>
2025-12-05 10:46:10 +09:00
<DialogFooter className="gap-2 sm:gap-0 mt-4">
2025-11-05 16:36:32 +09:00
<Button
variant="outline"
onClick={handleReset}
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
>
</Button>
<Button
variant="outline"
onClick={onClose}
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
>
</Button>
<Button
onClick={handleSave}
className="h-8 flex-1 text-xs sm:h-10 sm:flex-none sm:text-sm"
>
</Button>
2025-12-05 10:46:10 +09:00
</DialogFooter>
</DialogContent>
</Dialog>
2025-11-05 16:36:32 +09:00
);
}