Kraft102's picture
fix: sql.js Docker/Alpine compatibility layer for PatternMemory and FailureMemory
5a81b95
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;