Spaces:
Paused
Paused
| import { useState } from 'react'; | |
| import { | |
| Bookmark, Plus, Trash2, Check, Edit2, ChevronDown, | |
| Shield, BarChart3, Layers, Star | |
| } from 'lucide-react'; | |
| import { Button } from '@/components/ui/button'; | |
| import { Input } from '@/components/ui/input'; | |
| import { | |
| DropdownMenu, | |
| DropdownMenuContent, | |
| DropdownMenuItem, | |
| DropdownMenuSeparator, | |
| DropdownMenuTrigger, | |
| } from '@/components/ui/dropdown-menu'; | |
| import { | |
| Dialog, | |
| DialogContent, | |
| DialogDescription, | |
| DialogFooter, | |
| DialogHeader, | |
| DialogTitle, | |
| } from '@/components/ui/dialog'; | |
| import { DashboardPreset, useDashboardPresets } from '@/hooks/useDashboardPresets'; | |
| import { DashboardWidget } from '@/pages/Dashboard'; | |
| import { toast } from '@/hooks/use-toast'; | |
| import { cn } from '@/lib/utils'; | |
| interface PresetManagerProps { | |
| currentWidgets: DashboardWidget[]; | |
| onLoadPreset: (widgets: DashboardWidget[]) => void; | |
| } | |
| const presetIcons: Record<string, any> = { | |
| 'default': Layers, | |
| 'soc-analyst': Shield, | |
| 'executive': BarChart3, | |
| 'minimal': Star, | |
| }; | |
| const PresetManager = ({ currentWidgets, onLoadPreset }: PresetManagerProps) => { | |
| const { | |
| presets, | |
| activePreset, | |
| createPreset, | |
| updatePreset, | |
| deletePreset, | |
| loadPreset, | |
| setActivePreset | |
| } = useDashboardPresets(); | |
| const [saveDialogOpen, setSaveDialogOpen] = useState(false); | |
| const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); | |
| const [presetToDelete, setPresetToDelete] = useState<DashboardPreset | null>(null); | |
| const [newPresetName, setNewPresetName] = useState(''); | |
| const [newPresetDescription, setNewPresetDescription] = useState(''); | |
| const handleLoadPreset = (presetId: string) => { | |
| const widgets = loadPreset(presetId); | |
| if (widgets) { | |
| onLoadPreset(widgets); | |
| const preset = presets.find(p => p.id === presetId); | |
| toast({ | |
| title: "Preset indlæst", | |
| description: `"${preset?.name}" er nu aktivt` | |
| }); | |
| } | |
| }; | |
| const handleSavePreset = () => { | |
| if (!newPresetName.trim()) { | |
| toast({ | |
| title: "Navn påkrævet", | |
| description: "Indtast et navn til preset", | |
| variant: "destructive" | |
| }); | |
| return; | |
| } | |
| createPreset(newPresetName.trim(), currentWidgets, newPresetDescription.trim() || undefined); | |
| toast({ | |
| title: "Preset gemt", | |
| description: `"${newPresetName}" er oprettet` | |
| }); | |
| setNewPresetName(''); | |
| setNewPresetDescription(''); | |
| setSaveDialogOpen(false); | |
| }; | |
| const handleUpdateActivePreset = () => { | |
| if (activePreset && !activePreset.isBuiltIn) { | |
| updatePreset(activePreset.id, currentWidgets); | |
| toast({ | |
| title: "Preset opdateret", | |
| description: `"${activePreset.name}" er opdateret` | |
| }); | |
| } | |
| }; | |
| const handleDeletePreset = () => { | |
| if (presetToDelete && !presetToDelete.isBuiltIn) { | |
| deletePreset(presetToDelete.id); | |
| toast({ | |
| title: "Preset slettet", | |
| description: `"${presetToDelete.name}" er slettet` | |
| }); | |
| setPresetToDelete(null); | |
| setDeleteDialogOpen(false); | |
| } | |
| }; | |
| const confirmDeletePreset = (preset: DashboardPreset) => { | |
| setPresetToDelete(preset); | |
| setDeleteDialogOpen(true); | |
| }; | |
| const userPresets = presets.filter(p => !p.isBuiltIn); | |
| const builtInPresets = presets.filter(p => p.isBuiltIn); | |
| return ( | |
| <> | |
| <DropdownMenu> | |
| <DropdownMenuTrigger asChild> | |
| <Button variant="outline" size="sm" className="gap-2"> | |
| <Bookmark className="w-4 h-4" /> | |
| {activePreset ? activePreset.name : 'Presets'} | |
| <ChevronDown className="w-3 h-3 opacity-50" /> | |
| </Button> | |
| </DropdownMenuTrigger> | |
| <DropdownMenuContent align="start" className="w-64 bg-card border-border"> | |
| <div className="px-2 py-1.5 text-xs font-medium text-muted-foreground"> | |
| Standard presets | |
| </div> | |
| {builtInPresets.map((preset) => { | |
| const Icon = presetIcons[preset.id] || Bookmark; | |
| return ( | |
| <DropdownMenuItem | |
| key={preset.id} | |
| onClick={() => handleLoadPreset(preset.id)} | |
| className="flex items-center justify-between cursor-pointer" | |
| > | |
| <div className="flex items-center gap-2"> | |
| <Icon className="w-4 h-4 text-primary" /> | |
| <div> | |
| <div className="font-medium">{preset.name}</div> | |
| {preset.description && ( | |
| <div className="text-xs text-muted-foreground">{preset.description}</div> | |
| )} | |
| </div> | |
| </div> | |
| {activePreset?.id === preset.id && ( | |
| <Check className="w-4 h-4 text-primary" /> | |
| )} | |
| </DropdownMenuItem> | |
| ); | |
| })} | |
| {userPresets.length > 0 && ( | |
| <> | |
| <DropdownMenuSeparator /> | |
| <div className="px-2 py-1.5 text-xs font-medium text-muted-foreground"> | |
| Mine presets | |
| </div> | |
| {userPresets.map((preset) => ( | |
| <DropdownMenuItem | |
| key={preset.id} | |
| className="flex items-center justify-between cursor-pointer group" | |
| > | |
| <div | |
| className="flex items-center gap-2 flex-1" | |
| onClick={() => handleLoadPreset(preset.id)} | |
| > | |
| <Bookmark className="w-4 h-4 text-accent" /> | |
| <div> | |
| <div className="font-medium">{preset.name}</div> | |
| {preset.description && ( | |
| <div className="text-xs text-muted-foreground">{preset.description}</div> | |
| )} | |
| </div> | |
| </div> | |
| <div className="flex items-center gap-1"> | |
| {activePreset?.id === preset.id && ( | |
| <Check className="w-4 h-4 text-primary" /> | |
| )} | |
| <Button | |
| variant="ghost" | |
| size="sm" | |
| className="h-6 w-6 p-0 opacity-0 group-hover:opacity-100" | |
| onClick={(e) => { | |
| e.stopPropagation(); | |
| confirmDeletePreset(preset); | |
| }} | |
| > | |
| <Trash2 className="w-3 h-3 text-destructive" /> | |
| </Button> | |
| </div> | |
| </DropdownMenuItem> | |
| ))} | |
| </> | |
| )} | |
| <DropdownMenuSeparator /> | |
| <DropdownMenuItem | |
| onClick={() => setSaveDialogOpen(true)} | |
| className="cursor-pointer" | |
| > | |
| <Plus className="w-4 h-4 mr-2" /> | |
| Gem som nyt preset | |
| </DropdownMenuItem> | |
| {activePreset && !activePreset.isBuiltIn && ( | |
| <DropdownMenuItem | |
| onClick={handleUpdateActivePreset} | |
| className="cursor-pointer" | |
| > | |
| <Edit2 className="w-4 h-4 mr-2" /> | |
| Opdater "{activePreset.name}" | |
| </DropdownMenuItem> | |
| )} | |
| </DropdownMenuContent> | |
| </DropdownMenu> | |
| {/* Save Dialog */} | |
| <Dialog open={saveDialogOpen} onOpenChange={setSaveDialogOpen}> | |
| <DialogContent className="bg-card border-border"> | |
| <DialogHeader> | |
| <DialogTitle className="text-primary font-display">Gem nyt preset</DialogTitle> | |
| <DialogDescription> | |
| Gem dit nuværende dashboard layout som et genbrugeligt preset | |
| </DialogDescription> | |
| </DialogHeader> | |
| <div className="space-y-4 py-4"> | |
| <div className="space-y-2"> | |
| <label className="text-sm font-medium">Navn</label> | |
| <Input | |
| placeholder="Mit dashboard" | |
| value={newPresetName} | |
| onChange={(e) => setNewPresetName(e.target.value)} | |
| className="bg-secondary/30 border-border" | |
| /> | |
| </div> | |
| <div className="space-y-2"> | |
| <label className="text-sm font-medium">Beskrivelse (valgfri)</label> | |
| <Input | |
| placeholder="Kort beskrivelse af preset" | |
| value={newPresetDescription} | |
| onChange={(e) => setNewPresetDescription(e.target.value)} | |
| className="bg-secondary/30 border-border" | |
| /> | |
| </div> | |
| <div className="text-sm text-muted-foreground"> | |
| {currentWidgets.length} widgets vil blive gemt | |
| </div> | |
| </div> | |
| <DialogFooter> | |
| <Button variant="outline" onClick={() => setSaveDialogOpen(false)}> | |
| Annuller | |
| </Button> | |
| <Button variant="cyber" onClick={handleSavePreset}> | |
| Gem preset | |
| </Button> | |
| </DialogFooter> | |
| </DialogContent> | |
| </Dialog> | |
| {/* Delete Confirmation Dialog */} | |
| <Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}> | |
| <DialogContent className="bg-card border-border"> | |
| <DialogHeader> | |
| <DialogTitle className="text-destructive font-display">Slet preset</DialogTitle> | |
| <DialogDescription> | |
| Er du sikker på at du vil slette "{presetToDelete?.name}"? Denne handling kan ikke fortrydes. | |
| </DialogDescription> | |
| </DialogHeader> | |
| <DialogFooter> | |
| <Button variant="outline" onClick={() => setDeleteDialogOpen(false)}> | |
| Annuller | |
| </Button> | |
| <Button variant="destructive" onClick={handleDeletePreset}> | |
| Slet preset | |
| </Button> | |
| </DialogFooter> | |
| </DialogContent> | |
| </Dialog> | |
| </> | |
| ); | |
| }; | |
| export default PresetManager; | |