summaryrefslogtreecommitdiff
path: root/components/data-table/data-table-preset.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'components/data-table/data-table-preset.tsx')
-rw-r--r--components/data-table/data-table-preset.tsx373
1 files changed, 373 insertions, 0 deletions
diff --git a/components/data-table/data-table-preset.tsx b/components/data-table/data-table-preset.tsx
new file mode 100644
index 00000000..007bde2f
--- /dev/null
+++ b/components/data-table/data-table-preset.tsx
@@ -0,0 +1,373 @@
+// components/data-table/table-preset-manager.tsx
+import React, { useState } from 'react'
+import { Button } from '@/components/ui/button'
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+} from '@/components/ui/dropdown-menu'
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/ui/dialog'
+import { Input } from '@/components/ui/input'
+import { Label } from '@/components/ui/label'
+import { Checkbox } from '@/components/ui/checkbox'
+import {
+ Settings,
+ Save,
+ Star,
+ Edit,
+ Trash2,
+ Plus,
+ Check,
+ Loader2,
+ ChevronDown,
+ Bookmark
+} from 'lucide-react'
+import { TableSettings } from '@/db/schema'
+
+interface TablePreset {
+ id: string
+ name: string
+ isDefault: boolean
+ isActive: boolean
+ settings: TableSettings
+}
+
+interface TablePresetManagerProps<TData = any> {
+ presets: TablePreset[]
+ activePresetId: string | null
+ currentSettings: TableSettings<TData>
+ hasUnsavedChanges: boolean
+ isLoading: boolean
+ onCreatePreset: (name: string, settings: TableSettings<TData>, isDefault: boolean) => Promise<boolean>
+ onUpdatePreset: (presetId: string, settings: TableSettings<TData>) => Promise<void>
+ onDeletePreset: (presetId: string) => Promise<boolean>
+ onApplyPreset: (presetId: string) => Promise<boolean>
+ onSetDefaultPreset: (presetId: string) => Promise<void>
+ onRenamePreset: (presetId: string, newName: string) => Promise<boolean>
+}
+
+export function TablePresetManager<TData = any>({
+ presets,
+ activePresetId,
+ currentSettings,
+ hasUnsavedChanges,
+ isLoading,
+ onCreatePreset,
+ onUpdatePreset,
+ onDeletePreset,
+ onApplyPreset,
+ onSetDefaultPreset,
+ onRenamePreset,
+}: TablePresetManagerProps<TData>) {
+ const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false)
+ const [isRenameDialogOpen, setIsRenameDialogOpen] = useState(false)
+ const [newPresetName, setNewPresetName] = useState('')
+ const [isDefaultPreset, setIsDefaultPreset] = useState(false)
+ const [renamingPresetId, setRenamingPresetId] = useState<string | null>(null)
+ const [processingPresetId, setProcessingPresetId] = useState<string | null>(null)
+
+ const activePreset = presets.find(p => p.id === activePresetId)
+
+ const handleCreatePreset = async () => {
+ if (!newPresetName.trim()) return
+
+ const success = await onCreatePreset(newPresetName, currentSettings, isDefaultPreset)
+ if (success) {
+ setIsCreateDialogOpen(false)
+ setNewPresetName('')
+ setIsDefaultPreset(false)
+ }
+ }
+
+ const handleRenamePreset = async () => {
+ if (renamingPresetId && newPresetName.trim()) {
+ const success = await onRenamePreset(renamingPresetId, newPresetName)
+ if (success) {
+ setIsRenameDialogOpen(false)
+ setNewPresetName('')
+ setRenamingPresetId(null)
+ }
+ }
+ }
+
+ const openRenameDialog = (preset: TablePreset) => {
+ setRenamingPresetId(preset.id)
+ setNewPresetName(preset.name)
+ setIsRenameDialogOpen(true)
+ }
+
+ const handleUpdateCurrentPreset = async () => {
+ if (activePresetId) {
+ await onUpdatePreset(activePresetId, currentSettings)
+ }
+ }
+
+ const handleApplyPreset = async (presetId: string) => {
+ setProcessingPresetId(presetId)
+ await onApplyPreset(presetId)
+ setProcessingPresetId(null)
+ }
+
+ if (isLoading) {
+ return (
+ <Button variant="outline" size="sm" className="h-8" disabled>
+ <Loader2 size={16} className="mr-2 animate-spin" />
+ 로딩 중...
+ </Button>
+ )
+ }
+
+ return (
+ <>
+ <DropdownMenu>
+ <DropdownMenuTrigger asChild>
+ <Button variant="outline" size="sm" className="h-8">
+ <Settings size={16} className="mr-2" />
+ {/* <span className="hidden sm:inline">맞춤 설정</span> */}
+ {activePreset ? (
+ <div className="flex items-center ml-2">
+ <span className="font-medium text-sm">{activePreset.name}</span>
+ {hasUnsavedChanges && <span className="ml-1 text-amber-500">*</span>}
+ {activePreset.isDefault && <Star size={12} className="ml-1 text-yellow-500" />}
+ </div>
+ ) : null}
+ <ChevronDown size={14} className="ml-1" />
+ </Button>
+ </DropdownMenuTrigger>
+ <DropdownMenuContent className="w-72">
+ <DropdownMenuLabel className="flex items-center">
+ <Bookmark size={16} className="mr-2" />
+ 테이블 맞춤 설정
+ </DropdownMenuLabel>
+ <DropdownMenuSeparator />
+
+ {/* 활성 맞춤 설정 표시 */}
+ {activePreset && (
+ <>
+ <div className="px-2 py-2 bg-muted/50 rounded-sm mx-1">
+ <div className="text-xs text-muted-foreground">현재 활성</div>
+ <div className="flex items-center justify-between">
+ <span className="font-medium text-sm">{activePreset.name}</span>
+ <div className="flex items-center gap-1">
+ {activePreset.isDefault && <Star size={12} className="text-yellow-500" />}
+ {hasUnsavedChanges && <span className="text-amber-500 text-xs">수정됨</span>}
+ </div>
+ </div>
+ </div>
+ <DropdownMenuSeparator />
+ </>
+ )}
+
+ {/* 빠른 액션 */}
+ <DropdownMenuItem onClick={() => setIsCreateDialogOpen(true)}>
+ <Plus size={14} className="mr-2" />
+ 현재 설정으로 새 맞춤 설정 만들기
+ </DropdownMenuItem>
+
+ {activePresetId && (
+ <DropdownMenuItem onClick={handleUpdateCurrentPreset}>
+ <Save size={14} className="mr-2" />
+ {hasUnsavedChanges ? '변경 내용 저장' : '현재 설정 업데이트'}
+ </DropdownMenuItem>
+ )}
+
+ <DropdownMenuSeparator />
+
+ {/* 맞춤 설정 목록 */}
+ <DropdownMenuLabel className="text-xs text-muted-foreground">
+ 저장된 맞춤 설정
+ </DropdownMenuLabel>
+
+ {presets.length === 0 ? (
+ <div className="px-2 py-2 text-center text-sm text-muted-foreground">
+ 저장된 프리셋이 없습니다
+ </div>
+ ) : (
+ presets.map((preset) => (
+ <DropdownMenuSub key={preset.id}>
+ <DropdownMenuSubTrigger>
+ <div className="flex items-center w-full">
+ {preset.id === activePresetId ? (
+ <Check size={14} className="mr-2" />
+ ) : (
+ <div className="w-[14px] mr-2" />
+ )}
+ <span className={preset.id === activePresetId ? "font-medium" : ""}>
+ {preset.name}
+ </span>
+ {preset.isDefault && <Star size={12} className="ml-1 text-yellow-500" />}
+ </div>
+ </DropdownMenuSubTrigger>
+ <DropdownMenuSubContent className="w-48">
+ <DropdownMenuItem onClick={() => handleApplyPreset(preset.id)}>
+ {processingPresetId === preset.id ? (
+ <Loader2 size={14} className="mr-2 animate-spin" />
+ ) : (
+ <Check size={14} className="mr-2" />
+ )}
+ 적용
+ </DropdownMenuItem>
+ <DropdownMenuItem onClick={() => openRenameDialog(preset)}>
+ <Edit size={14} className="mr-2" />
+ 이름 변경
+ </DropdownMenuItem>
+ <DropdownMenuItem onClick={() => onSetDefaultPreset(preset.id)}>
+ <Star size={14} className="mr-2" />
+ {preset.isDefault ? '기본 해제' : '기본으로 설정'}
+ </DropdownMenuItem>
+ <DropdownMenuSeparator />
+ {presets.length > 1 && (
+ <DropdownMenuItem
+ onClick={() => onDeletePreset(preset.id)}
+ className="text-red-600 focus:text-red-600"
+ >
+ <Trash2 size={14} className="mr-2" />
+ 삭제
+ </DropdownMenuItem>
+ )}
+ </DropdownMenuSubContent>
+ </DropdownMenuSub>
+ ))
+ )}
+ </DropdownMenuContent>
+ </DropdownMenu>
+
+ {/* Create Dialog */}
+ <Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
+ <DialogContent className="sm:max-w-[425px]">
+ <DialogHeader>
+ <DialogTitle>새 맞춤 설정 저장</DialogTitle>
+ <DialogDescription>
+ 현재 테이블 설정을 새로운 프리셋으로 저장합니다.
+ </DialogDescription>
+ </DialogHeader>
+ <div className="grid gap-4 py-4">
+ <div className="grid grid-cols-4 items-center gap-4">
+ <Label htmlFor="presetName" className="text-right">
+ 이름
+ </Label>
+ <div className="col-span-3">
+ <Input
+ id="presetName"
+ value={newPresetName}
+ onChange={(e) => setNewPresetName(e.target.value)}
+ placeholder="맞춤 설정 이름을 입력하세요"
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ handleCreatePreset()
+ }
+ }}
+ />
+ </div>
+ </div>
+ <div className="grid grid-cols-4 items-center gap-4">
+ <Label className="text-right">옵션</Label>
+ <div className="col-span-3">
+ <div className="flex items-center space-x-2">
+ <Checkbox
+ id="isDefault"
+ checked={isDefaultPreset}
+ onCheckedChange={(checked) => setIsDefaultPreset(checked as boolean)}
+ />
+ <Label htmlFor="isDefault" className="text-sm font-normal">
+ 기본 프리셋으로 설정
+ </Label>
+ </div>
+ <p className="text-xs text-muted-foreground mt-1">
+ 기본 프리셋은 테이블 로드 시 자동으로 적용됩니다.
+ </p>
+ </div>
+ </div>
+ </div>
+ <DialogFooter>
+ <Button
+ type="button"
+ variant="outline"
+ onClick={() => {
+ setIsCreateDialogOpen(false)
+ setNewPresetName('')
+ setIsDefaultPreset(false)
+ }}
+ >
+ 취소
+ </Button>
+ <Button
+ type="button"
+ onClick={handleCreatePreset}
+ disabled={!newPresetName.trim()}
+ >
+ <Save size={14} className="mr-2" />
+ 저장
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+
+ {/* Rename Dialog */}
+ <Dialog open={isRenameDialogOpen} onOpenChange={setIsRenameDialogOpen}>
+ <DialogContent className="sm:max-w-[425px]">
+ <DialogHeader>
+ <DialogTitle>맞춤 설정 이름 변경</DialogTitle>
+ <DialogDescription>
+ '{presets.find(p => p.id === renamingPresetId)?.name}' 프리셋의 이름을 변경합니다.
+ </DialogDescription>
+ </DialogHeader>
+ <div className="grid gap-4 py-4">
+ <div className="grid grid-cols-4 items-center gap-4">
+ <Label htmlFor="renamePresetName" className="text-right">
+ 새 이름
+ </Label>
+ <div className="col-span-3">
+ <Input
+ id="renamePresetName"
+ value={newPresetName}
+ onChange={(e) => setNewPresetName(e.target.value)}
+ placeholder="새 맞춤 설정 이름을 입력하세요"
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ handleRenamePreset()
+ }
+ }}
+ />
+ </div>
+ </div>
+ </div>
+ <DialogFooter>
+ <Button
+ type="button"
+ variant="outline"
+ onClick={() => {
+ setIsRenameDialogOpen(false)
+ setNewPresetName('')
+ setRenamingPresetId(null)
+ }}
+ >
+ 취소
+ </Button>
+ <Button
+ type="button"
+ onClick={handleRenamePreset}
+ disabled={!newPresetName.trim()}
+ >
+ <Edit size={14} className="mr-2" />
+ 변경
+ </Button>
+ </DialogFooter>
+ </DialogContent>
+ </Dialog>
+ </>
+ )
+} \ No newline at end of file