feat: refactor dashboard and account pages to utilize new layout components, enhancing structure and loading states
This commit is contained in:
192
components/categories/category-edit-dialog.tsx
Normal file
192
components/categories/category-edit-dialog.tsx
Normal file
@@ -0,0 +1,192 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Plus, X } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { Category } from "@/lib/types";
|
||||
import { categoryColors } from "./constants";
|
||||
|
||||
interface CategoryFormData {
|
||||
name: string;
|
||||
color: string;
|
||||
keywords: string[];
|
||||
parentId: string | null;
|
||||
}
|
||||
|
||||
interface CategoryEditDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
editingCategory: Category | null;
|
||||
formData: CategoryFormData;
|
||||
onFormDataChange: (data: CategoryFormData) => void;
|
||||
parentCategories: Category[];
|
||||
onSave: () => void;
|
||||
}
|
||||
|
||||
export function CategoryEditDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
editingCategory,
|
||||
formData,
|
||||
onFormDataChange,
|
||||
parentCategories,
|
||||
onSave,
|
||||
}: CategoryEditDialogProps) {
|
||||
const [newKeyword, setNewKeyword] = useState("");
|
||||
|
||||
const addKeyword = () => {
|
||||
if (
|
||||
newKeyword.trim() &&
|
||||
!formData.keywords.includes(newKeyword.trim().toLowerCase())
|
||||
) {
|
||||
onFormDataChange({
|
||||
...formData,
|
||||
keywords: [...formData.keywords, newKeyword.trim().toLowerCase()],
|
||||
});
|
||||
setNewKeyword("");
|
||||
}
|
||||
};
|
||||
|
||||
const removeKeyword = (keyword: string) => {
|
||||
onFormDataChange({
|
||||
...formData,
|
||||
keywords: formData.keywords.filter((k) => k !== keyword),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
{editingCategory ? "Modifier la catégorie" : "Nouvelle catégorie"}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4">
|
||||
{/* Catégorie parente */}
|
||||
<div className="space-y-2">
|
||||
<Label>Catégorie parente</Label>
|
||||
<Select
|
||||
value={formData.parentId || "none"}
|
||||
onValueChange={(value) =>
|
||||
onFormDataChange({
|
||||
...formData,
|
||||
parentId: value === "none" ? null : value,
|
||||
})
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Aucune (catégorie principale)" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="none">
|
||||
Aucune (catégorie principale)
|
||||
</SelectItem>
|
||||
{parentCategories
|
||||
.filter((p) => p.id !== editingCategory?.id)
|
||||
.map((parent) => (
|
||||
<SelectItem key={parent.id} value={parent.id}>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className="w-3 h-3 rounded-full"
|
||||
style={{ backgroundColor: parent.color }}
|
||||
/>
|
||||
{parent.name}
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Nom */}
|
||||
<div className="space-y-2">
|
||||
<Label>Nom</Label>
|
||||
<Input
|
||||
value={formData.name}
|
||||
onChange={(e) =>
|
||||
onFormDataChange({ ...formData, name: e.target.value })
|
||||
}
|
||||
placeholder="Ex: Alimentation"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Couleur */}
|
||||
<div className="space-y-2">
|
||||
<Label>Couleur</Label>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{categoryColors.map((color) => (
|
||||
<button
|
||||
key={color}
|
||||
onClick={() => onFormDataChange({ ...formData, color })}
|
||||
className={cn(
|
||||
"w-8 h-8 rounded-full transition-transform",
|
||||
formData.color === color &&
|
||||
"ring-2 ring-offset-2 ring-primary scale-110"
|
||||
)}
|
||||
style={{ backgroundColor: color }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mots-clés */}
|
||||
<div className="space-y-2">
|
||||
<Label>Mots-clés pour la catégorisation automatique</Label>
|
||||
<div className="flex gap-2">
|
||||
<Input
|
||||
value={newKeyword}
|
||||
onChange={(e) => setNewKeyword(e.target.value)}
|
||||
placeholder="Ajouter un mot-clé"
|
||||
onKeyDown={(e) =>
|
||||
e.key === "Enter" && (e.preventDefault(), addKeyword())
|
||||
}
|
||||
/>
|
||||
<Button type="button" onClick={addKeyword} size="icon">
|
||||
<Plus className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1 mt-2 max-h-32 overflow-y-auto">
|
||||
{formData.keywords.map((keyword) => (
|
||||
<Badge key={keyword} variant="secondary" className="gap-1">
|
||||
{keyword}
|
||||
<button onClick={() => removeKeyword(keyword)}>
|
||||
<X className="w-3 h-3" />
|
||||
</button>
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
||||
Annuler
|
||||
</Button>
|
||||
<Button onClick={onSave} disabled={!formData.name.trim()}>
|
||||
{editingCategory ? "Enregistrer" : "Créer"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user