import React, { useEffect, useState } from "react"; import { Check, Search } from "lucide-react"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./dialog"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, } from "./accordion"; import { Input } from "./input"; import { Switch } from "./switch"; import { Button } from "./button"; import { cn } from "@/lib/utils"; // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- export interface CategoryChild { id: string; name: string; icon?: React.ReactNode; } export interface CategoryItem { id: string; name: string; icon?: React.ReactNode; subCategories?: CategoryChild[]; } export interface CategoryEditDialogProps { open: boolean; onOpenChange: (open: boolean) => void; /** Transaction description shown as subtitle in the dialog header */ transactionDescription?: string; /** Currently assigned category ID — pre-selects and auto-expands the matching parent */ currentCategoryId?: string; /** Full category tree to display */ categories: CategoryItem[]; /** Called on Save — receives the chosen category ID and the "apply to future" toggle state */ onSave: (categoryId: string, applyToFuture: boolean) => void; } // --------------------------------------------------------------------------- // Internal sub-components // --------------------------------------------------------------------------- function ItemIcon({ icon, active, }: { icon?: React.ReactNode; active: boolean; }) { if (!icon) return null; return ( {icon} ); } function CategoryRow({ name, icon, isSelected, indent = false, onClick, }: { name: string; icon?: React.ReactNode; isSelected: boolean; indent?: boolean; onClick: () => void; }) { return ( ); } // --------------------------------------------------------------------------- // Component // --------------------------------------------------------------------------- export function CategoryEditDialog({ open, onOpenChange, transactionDescription, currentCategoryId, categories, onSave, }: CategoryEditDialogProps) { const [search, setSearch] = useState(""); const [selected, setSelected] = useState(currentCategoryId ?? ""); const [applyToFuture, setApplyToFuture] = useState(false); const [openItem, setOpenItem] = useState(""); useEffect(() => { if (!open) return; setSelected(currentCategoryId ?? ""); setSearch(""); setApplyToFuture(false); const parent = categories.find( (c) => c.id === currentCategoryId || c.subCategories?.some((s) => s.id === currentCategoryId), ); setOpenItem(parent?.id ?? ""); }, [open, currentCategoryId, categories]); const q = search.toLowerCase(); const filtered = categories.filter( (c) => !q || c.name.toLowerCase().includes(q) || c.subCategories?.some((s) => s.name.toLowerCase().includes(q)), ); return ( Change Category {transactionDescription && (

{transactionDescription}

)}
{/* Search */}
setSearch(e.target.value)} className="h-8 pl-8 text-body-small" />
{/* Category list */}
{filtered.length === 0 ? (

No categories found

) : q ? ( // Search mode — flat list, no animation filtered.map((cat) => { const hasChildren = (cat.subCategories?.length ?? 0) > 0; const matchingChildren = cat.subCategories?.filter((s) => s.name.toLowerCase().includes(q), ); return (
{(!hasChildren || cat.name.toLowerCase().includes(q)) && (hasChildren ? ( // Non-selectable group header
{cat.name}
) : ( setSelected(cat.id)} /> ))} {matchingChildren?.map((sub) => ( setSelected(sub.id)} /> ))}
); }) ) : ( // Browse mode — accordion with animated expand/collapse setOpenItem(v[0] ?? "")} > {filtered.map((cat) => { if ((cat.subCategories?.length ?? 0) > 0) { return ( {cat.name} {cat.subCategories!.map((sub) => ( setSelected(sub.id)} /> ))} ); } return (
setSelected(cat.id)} />
); })}
)}
{/* Apply to future toggle */}
{/* Actions */}
); }