mirror of
https://github.com/Zproger/Aurum.git
synced 2026-10-13 11:06:40 +07:00
feat: add category management page (create/edit/delete)
The backend already had full category CRUD but nothing in the UI exposed it — categories could only be viewed inside charts. Adds a Categories nav item and page with icon/color pickers, grouped by expense/income, backed by new create/update/delete mutations. Also expands icons.tsx's ICON_MAP, which was missing 14 icon keys already in use by real category data and silently falling back to a wallet glyph everywhere they render. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ const ru = {
|
||||
"nav.roi": "Доходность",
|
||||
"nav.transactions": "Транзакции",
|
||||
"nav.accounts": "Счета",
|
||||
"nav.categories": "Категории",
|
||||
"nav.cashFlow": "Движение денег",
|
||||
"nav.reports": "Отчёты",
|
||||
"nav.budget": "Бюджет",
|
||||
@@ -300,6 +301,24 @@ const ru = {
|
||||
"account.form.typeLabel": "Тип",
|
||||
"account.form.saveError": "Не удалось сохранить счёт. Проверьте данные и попробуйте снова.",
|
||||
|
||||
"category.expenseSectionTitle": "Категории расходов",
|
||||
"category.incomeSectionTitle": "Категории доходов",
|
||||
"category.empty": "Категорий пока нет.",
|
||||
"category.defaultBadge": "стандартная",
|
||||
"category.defaultCannotDelete": "Стандартные категории нельзя удалить",
|
||||
"category.confirmDelete": "Удалить категорию «{{name}}»?\n\nЕё транзакции останутся без категории.",
|
||||
"category.kind.expense": "Расход",
|
||||
"category.kind.income": "Доход",
|
||||
"category.form.newTitle": "Новая категория",
|
||||
"category.form.editTitle": "Изменить категорию",
|
||||
"category.form.nameLabel": "Название",
|
||||
"category.form.namePlaceholder": "Продукты, транспорт, подписки…",
|
||||
"category.form.kindLabel": "Тип",
|
||||
"category.form.iconLabel": "Иконка",
|
||||
"category.form.colorLabel": "Цвет",
|
||||
"category.form.customColorLabel": "Свой цвет",
|
||||
"category.form.saveError": "Не удалось сохранить категорию. Проверьте данные и попробуйте снова.",
|
||||
|
||||
"cashFlow.income": "Доход",
|
||||
"cashFlow.expense": "Расход",
|
||||
"cashFlow.net": "Баланс",
|
||||
@@ -344,6 +363,7 @@ const en: Record<keyof typeof ru, string> = {
|
||||
"nav.roi": "Returns",
|
||||
"nav.transactions": "Transactions",
|
||||
"nav.accounts": "Accounts",
|
||||
"nav.categories": "Categories",
|
||||
"nav.cashFlow": "Cash Flow",
|
||||
"nav.reports": "Reports",
|
||||
"nav.budget": "Budget",
|
||||
@@ -634,6 +654,24 @@ const en: Record<keyof typeof ru, string> = {
|
||||
"account.form.typeLabel": "Type",
|
||||
"account.form.saveError": "Failed to save the account. Check the data and try again.",
|
||||
|
||||
"category.expenseSectionTitle": "Expense categories",
|
||||
"category.incomeSectionTitle": "Income categories",
|
||||
"category.empty": "No categories yet.",
|
||||
"category.defaultBadge": "default",
|
||||
"category.defaultCannotDelete": "Default categories cannot be deleted",
|
||||
"category.confirmDelete": "Delete the category “{{name}}”?\n\nIts transactions will be left uncategorized.",
|
||||
"category.kind.expense": "Expense",
|
||||
"category.kind.income": "Income",
|
||||
"category.form.newTitle": "New category",
|
||||
"category.form.editTitle": "Edit category",
|
||||
"category.form.nameLabel": "Name",
|
||||
"category.form.namePlaceholder": "Groceries, transport, subscriptions…",
|
||||
"category.form.kindLabel": "Type",
|
||||
"category.form.iconLabel": "Icon",
|
||||
"category.form.colorLabel": "Color",
|
||||
"category.form.customColorLabel": "Custom color",
|
||||
"category.form.saveError": "Failed to save the category. Check the data and try again.",
|
||||
|
||||
"cashFlow.income": "Income",
|
||||
"cashFlow.expense": "Expense",
|
||||
"cashFlow.net": "Net",
|
||||
|
||||
@@ -5,25 +5,41 @@ import {
|
||||
Building2,
|
||||
Car,
|
||||
Clapperboard,
|
||||
Code,
|
||||
Dumbbell,
|
||||
Gamepad2,
|
||||
Gem,
|
||||
Gift,
|
||||
GraduationCap,
|
||||
HeartPulse,
|
||||
Home,
|
||||
MoreHorizontal,
|
||||
Package,
|
||||
Plane,
|
||||
PlusCircle,
|
||||
Receipt,
|
||||
Repeat,
|
||||
Shapes,
|
||||
Shirt,
|
||||
ShoppingBag,
|
||||
ShoppingBasket,
|
||||
Smartphone,
|
||||
Sofa,
|
||||
Sparkles,
|
||||
TrendingUp,
|
||||
Truck,
|
||||
Utensils,
|
||||
Video,
|
||||
Wallet,
|
||||
ZapOff,
|
||||
type LucideIcon,
|
||||
} from "lucide-react";
|
||||
|
||||
// Maps the backend's plain-string icon keys (Category.icon) to a concrete
|
||||
// lucide component. New categories fall back to a generic wallet glyph so an
|
||||
// unmapped icon key never breaks rendering.
|
||||
// unmapped icon key never breaks rendering. Keep this in sync with whatever
|
||||
// icon keys actually exist on Category rows — an icon used by a real
|
||||
// category but missing here silently renders as the Wallet fallback.
|
||||
const ICON_MAP: Record<string, LucideIcon> = {
|
||||
home: Home,
|
||||
"shopping-basket": ShoppingBasket,
|
||||
@@ -44,9 +60,29 @@ const ICON_MAP: Record<string, LucideIcon> = {
|
||||
"building-2": Building2,
|
||||
package: Package,
|
||||
gem: Gem,
|
||||
code: Code,
|
||||
dumbbell: Dumbbell,
|
||||
"gamepad-2": Gamepad2,
|
||||
"graduation-cap": GraduationCap,
|
||||
plane: Plane,
|
||||
receipt: Receipt,
|
||||
shapes: Shapes,
|
||||
shirt: Shirt,
|
||||
smartphone: Smartphone,
|
||||
sofa: Sofa,
|
||||
sparkles: Sparkles,
|
||||
truck: Truck,
|
||||
// lucide-react dropped brand/trademarked glyphs (no "Youtube" icon) — a
|
||||
// generic video-camera icon is the closest available stand-in.
|
||||
youtube: Video,
|
||||
"zap-off": ZapOff,
|
||||
};
|
||||
|
||||
export function getCategoryIcon(icon: string | null | undefined): LucideIcon {
|
||||
if (!icon) return Wallet;
|
||||
return ICON_MAP[icon] ?? Wallet;
|
||||
}
|
||||
|
||||
// The full set of icon keys a category can be assigned, for the icon picker
|
||||
// in the category form — kept in sync with ICON_MAP above.
|
||||
export const CATEGORY_ICON_OPTIONS = Object.keys(ICON_MAP);
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
PieChart,
|
||||
Repeat,
|
||||
Settings,
|
||||
Tags,
|
||||
Target,
|
||||
TrendingUp,
|
||||
type LucideIcon,
|
||||
@@ -29,6 +30,7 @@ export const NAV_ITEMS: NavItem[] = [
|
||||
{ labelKey: "nav.roi", to: "/roi", icon: Calculator },
|
||||
{ labelKey: "nav.transactions", to: "/transactions", icon: ArrowLeftRight },
|
||||
{ labelKey: "nav.accounts", to: "/accounts", icon: Layers },
|
||||
{ labelKey: "nav.categories", to: "/categories", icon: Tags },
|
||||
{ labelKey: "nav.cashFlow", to: "/cash-flow", icon: Activity },
|
||||
{ labelKey: "nav.reports", to: "/reports", icon: PieChart },
|
||||
{ labelKey: "nav.budget", to: "/budget", icon: Target },
|
||||
|
||||
Reference in New Issue
Block a user