diff --git a/frontend/src/components/QuickAddPicker.tsx b/frontend/src/components/QuickAddPicker.tsx new file mode 100644 index 0000000..c5b018a --- /dev/null +++ b/frontend/src/components/QuickAddPicker.tsx @@ -0,0 +1,114 @@ +import { useState, useEffect } from 'react' +import { useTranslation } from 'react-i18next' +import { Link } from 'react-router-dom' +import { Button } from '@/components/ui/button' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu' +import { Zap } from 'lucide-react' +import { quickAdd as quickAddApi, categories as categoriesApi, budgetItems, type QuickAddItem } from '@/lib/api' + +interface Props { + budgetId: string + onItemAdded: () => void +} + +export function QuickAddPicker({ budgetId, onItemAdded }: Props) { + const { t } = useTranslation() + const [items, setItems] = useState([]) + const [loadingItems, setLoadingItems] = useState(true) + const [creatingId, setCreatingId] = useState(null) + const [open, setOpen] = useState(false) + + useEffect(() => { + quickAddApi + .list() + .then((data) => setItems(data ?? [])) + .catch(() => setItems([])) + .finally(() => setLoadingItems(false)) + }, []) + + const handleSelect = async (item: QuickAddItem) => { + if (creatingId) return + setCreatingId(item.id) + try { + // Find or create a matching category for the quick-add item + const allCategories = await categoriesApi.list() + let categoryId: string + + const existing = allCategories.find( + (c) => c.name.toLowerCase() === item.name.toLowerCase() + ) + + if (existing) { + categoryId = existing.id + } else { + const created = await categoriesApi.create({ + name: item.name, + type: 'variable_expense', + icon: item.icon, + }) + categoryId = created.id + } + + await budgetItems.create(budgetId, { + category_id: categoryId, + item_tier: 'one_off', + }) + + setOpen(false) + onItemAdded() + } finally { + setCreatingId(null) + } + } + + return ( + + + + + + {loadingItems ? ( +
{t('common.loading')}
+ ) : items.length === 0 ? ( +
+

{t('quickAdd.emptyPicker')}

+ setOpen(false)} + > + {t('quickAdd.goToLibrary')} + +
+ ) : ( + items.map((item) => ( + { + e.preventDefault() + handleSelect(item) + }} + className="flex items-center gap-2 cursor-pointer" + > + {creatingId === item.id ? ( + + ) : ( + {item.icon} + )} + {item.name} + + )) + )} +
+
+ ) +} diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index 9fcc562..2cd923b 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -14,6 +14,7 @@ import { EmptyState } from '@/components/EmptyState' import { useBudgets } from '@/hooks/useBudgets' import { useAuth } from '@/hooks/useAuth' import { budgetItems as budgetItemsApi } from '@/lib/api' +import { QuickAddPicker } from '@/components/QuickAddPicker' import { FolderOpen } from 'lucide-react' import { palette } from '@/lib/palette' @@ -98,6 +99,12 @@ export function DashboardPage() { + {current && ( + selectBudget(current.id)} + /> + )} {showCreate && (