diff options
Diffstat (limited to 'components/data-table/data-table-preset.tsx')
| -rw-r--r-- | components/data-table/data-table-preset.tsx | 373 |
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 |
