Add bulk actions, multi-currency, expenses, templates, and reports

Schema (migration 0001):
- clients: add currency column (default USD)
- invoices: add currency column (default USD)
- New expenses table: amount, currency, category, billable, reimbursable,
  client/invoice/business relations, notes
- New invoice_templates table: name, type (notes|terms), content, isDefault

API:
- invoices: add bulkUpdateStatus and bulkDelete procedures (ownership-safe)
- invoices: currency field threaded through create/update schemas
- clients: currency field added to create/update schemas
- New expenses router: full CRUD with authorization
- New invoiceTemplates router: full CRUD, isDefault management per type
- Root router: wire in expenses and invoiceTemplates

Currency (src/lib/currency.ts):
- Shared formatCurrency(amount, currency) utility replacing hardcoded USD
- SUPPORTED_CURRENCIES list (17 currencies)
- Invoice form: currency selector in Config card, auto-fills from client
- Client form: currency selector in Billing Information card

Bulk actions (invoices list):
- Checkbox column with select-all support
- Selection toolbar: Mark as Sent/Paid/Draft dropdown, Delete (N) button
- DataTable: new selectionActions prop renders toolbar when rows selected

Notes templates:
- Invoice form: Notes card with textarea in Details tab
- Template dropdown button appears when templates exist
- /dashboard/invoices/templates: full CRUD page for notes and terms templates

New pages:
- /dashboard/expenses: expense list with summary cards, add/edit dialog
- /dashboard/reports: KPI cards, 12-month revenue area chart, top clients
  bar chart, status breakdown, recent activity
- Navigation: Expenses and Reports added to Main section

https://claude.ai/code/session_012sqEgNQpx676isepeoX4Mi
This commit is contained in:
Claude
2026-04-05 02:34:06 +00:00
parent ba14526fc5
commit e6b79ce2c2
19 changed files with 3233 additions and 214 deletions
+30
View File
@@ -0,0 +1,30 @@
export const SUPPORTED_CURRENCIES = [
{ code: "USD", label: "USD US Dollar" },
{ code: "EUR", label: "EUR Euro" },
{ code: "GBP", label: "GBP British Pound" },
{ code: "CAD", label: "CAD Canadian Dollar" },
{ code: "AUD", label: "AUD Australian Dollar" },
{ code: "NZD", label: "NZD New Zealand Dollar" },
{ code: "CHF", label: "CHF Swiss Franc" },
{ code: "JPY", label: "JPY Japanese Yen" },
{ code: "SGD", label: "SGD Singapore Dollar" },
{ code: "HKD", label: "HKD Hong Kong Dollar" },
{ code: "SEK", label: "SEK Swedish Krona" },
{ code: "NOK", label: "NOK Norwegian Krone" },
{ code: "DKK", label: "DKK Danish Krone" },
{ code: "MXN", label: "MXN Mexican Peso" },
{ code: "BRL", label: "BRL Brazilian Real" },
{ code: "INR", label: "INR Indian Rupee" },
{ code: "ZAR", label: "ZAR South African Rand" },
] as const;
export type CurrencyCode = (typeof SUPPORTED_CURRENCIES)[number]["code"];
export function formatCurrency(amount: number, currency = "USD"): string {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency,
minimumFractionDigits: 2,
maximumFractionDigits: 2,
}).format(amount);
}
+4
View File
@@ -4,6 +4,8 @@ import {
Users,
FileText,
Building,
Receipt,
BarChart2,
} from "lucide-react";
export interface NavLink {
@@ -25,6 +27,8 @@ export const navigationConfig: NavSection[] = [
{ name: "Clients", href: "/dashboard/clients", icon: Users },
{ name: "Businesses", href: "/dashboard/businesses", icon: Building },
{ name: "Invoices", href: "/dashboard/invoices", icon: FileText },
{ name: "Expenses", href: "/dashboard/expenses", icon: Receipt },
{ name: "Reports", href: "/dashboard/reports", icon: BarChart2 },
],
},
{