feat: implement hierarchical category management with parent-child relationships and enhance category creation dialog

This commit is contained in:
Julien Froidefond
2025-11-27 10:29:59 +01:00
parent d7374e4129
commit 7314cb6716
9 changed files with 1048 additions and 260 deletions

View File

@@ -3,6 +3,7 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
import { CheckCircle2, Circle } from "lucide-react"
import { CategoryIcon } from "@/components/ui/category-icon"
import type { BankingData } from "@/lib/types"
import { cn } from "@/lib/utils"
@@ -86,9 +87,10 @@ export function RecentTransactions({ data }: RecentTransactionsProps) {
{category && (
<Badge
variant="secondary"
className="text-xs"
className="text-xs gap-1"
style={{ backgroundColor: `${category.color}20`, color: category.color }}
>
<CategoryIcon icon={category.icon} color={category.color} size={12} />
{category.name}
</Badge>
)}

View File

@@ -0,0 +1,143 @@
"use client"
import {
ShoppingCart,
Utensils,
Croissant,
Fuel,
Train,
Car,
SquareParking,
Bike,
Plane,
Home,
Zap,
Droplet,
Hammer,
Sofa,
Pill,
Stethoscope,
Hospital,
Glasses,
Dumbbell,
Sparkles,
Tv,
Music,
Film,
Gamepad,
Book,
Ticket,
Shirt,
Smartphone,
Package,
Wifi,
Repeat,
Landmark,
Shield,
HeartPulse,
Receipt,
PiggyBank,
Banknote,
Wallet,
HandCoins,
Undo,
Coins,
Bed,
Luggage,
GraduationCap,
Baby,
PawPrint,
Wrench,
HeartHandshake,
Gift,
Cigarette,
ArrowRightLeft,
HelpCircle,
Tag,
Folder,
Key,
Refrigerator,
type LucideIcon,
} from "lucide-react"
// Map icon names to Lucide components
const iconMap: Record<string, LucideIcon> = {
"shopping-cart": ShoppingCart,
"utensils": Utensils,
"croissant": Croissant,
"fuel": Fuel,
"train": Train,
"car": Car,
"car-taxi": Car, // Using Car as fallback for car-taxi
"car-key": Key, // Using Key as fallback
"parking": SquareParking,
"bike": Bike,
"plane": Plane,
"home": Home,
"zap": Zap,
"droplet": Droplet,
"hammer": Hammer,
"sofa": Sofa,
"refrigerator": Refrigerator,
"pill": Pill,
"stethoscope": Stethoscope,
"hospital": Hospital,
"glasses": Glasses,
"dumbbell": Dumbbell,
"sparkles": Sparkles,
"tv": Tv,
"music": Music,
"film": Film,
"gamepad": Gamepad,
"book": Book,
"ticket": Ticket,
"shirt": Shirt,
"smartphone": Smartphone,
"package": Package,
"wifi": Wifi,
"repeat": Repeat,
"landmark": Landmark,
"shield": Shield,
"heart-pulse": HeartPulse,
"receipt": Receipt,
"piggy-bank": PiggyBank,
"banknote": Banknote,
"wallet": Wallet,
"hand-coins": HandCoins,
"undo": Undo,
"coins": Coins,
"bed": Bed,
"luggage": Luggage,
"graduation-cap": GraduationCap,
"baby": Baby,
"paw-print": PawPrint,
"wrench": Wrench,
"heart-handshake": HeartHandshake,
"gift": Gift,
"cigarette": Cigarette,
"arrow-right-left": ArrowRightLeft,
"help-circle": HelpCircle,
"tag": Tag,
"folder": Folder,
}
// Get all available icon names
export const availableIcons = Object.keys(iconMap)
// Get the icon component by name
export function getIconComponent(iconName: string): LucideIcon {
return iconMap[iconName] || Tag
}
interface CategoryIconProps {
icon: string
color?: string
className?: string
size?: number
}
export function CategoryIcon({ icon, color, className, size = 20 }: CategoryIconProps) {
const IconComponent = getIconComponent(icon)
return <IconComponent className={className} style={{ color }} size={size} />
}