feat: replace category selection with CategoryCombobox in RuleCreateDialog and TransactionTable for improved user experience

This commit is contained in:
Julien Froidefond
2025-11-29 17:26:20 +01:00
parent 9e576f2b0e
commit 0ce50d1477
3 changed files with 283 additions and 130 deletions

View File

@@ -13,15 +13,8 @@ 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 {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
import { CategoryIcon } from "@/components/ui/category-icon";
import { CategoryCombobox } from "@/components/ui/category-combobox";
import { Tag, AlertCircle, CheckCircle2 } from "lucide-react";
import type { Category, Transaction } from "@/lib/types";
@@ -54,7 +47,7 @@ export function RuleCreateDialog({
onSave,
}: RuleCreateDialogProps) {
const [keyword, setKeyword] = useState("");
const [categoryId, setCategoryId] = useState<string>("");
const [categoryId, setCategoryId] = useState<string | null>(null);
const [applyToExisting, setApplyToExisting] = useState(true);
const [isLoading, setIsLoading] = useState(false);
@@ -62,28 +55,11 @@ export function RuleCreateDialog({
useEffect(() => {
if (group) {
setKeyword(group.suggestedKeyword);
setCategoryId("");
setCategoryId(null);
setApplyToExisting(true);
}
}, [group]);
// Organize categories by parent
const { parentCategories, childrenByParent } = useMemo(() => {
const parents = categories.filter((c) => c.parentId === null);
const children: Record<string, Category[]> = {};
categories
.filter((c) => c.parentId !== null)
.forEach((child) => {
if (!children[child.parentId!]) {
children[child.parentId!] = [];
}
children[child.parentId!].push(child);
});
return { parentCategories: parents, childrenByParent: children };
}, [categories]);
// Check if keyword already exists in any category
const existingCategory = useMemo(() => {
if (!keyword) return null;
@@ -166,42 +142,16 @@ export function RuleCreateDialog({
)}
</div>
{/* Category select */}
{/* Category select with search */}
<div className="space-y-2">
<Label htmlFor="category">Catégorie</Label>
<Select value={categoryId} onValueChange={setCategoryId}>
<SelectTrigger>
<SelectValue placeholder="Sélectionner une catégorie" />
</SelectTrigger>
<SelectContent>
{parentCategories.map((parent) => (
<div key={parent.id}>
<SelectItem value={parent.id} className="font-medium">
<div className="flex items-center gap-2">
<CategoryIcon
icon={parent.icon}
color={parent.color}
size={16}
/>
<span>{parent.name}</span>
</div>
</SelectItem>
{childrenByParent[parent.id]?.map((child) => (
<SelectItem key={child.id} value={child.id}>
<div className="flex items-center gap-2 pl-4">
<CategoryIcon
icon={child.icon}
color={child.color}
size={16}
/>
<span>{child.name}</span>
</div>
</SelectItem>
))}
</div>
))}
</SelectContent>
</Select>
<Label>Catégorie</Label>
<CategoryCombobox
categories={categories}
value={categoryId}
onChange={setCategoryId}
placeholder="Sélectionner une catégorie..."
width="w-full"
/>
{selectedCategory && (
<div className="flex items-center gap-2 flex-wrap">
<span className="text-xs text-muted-foreground">