@fayz-ai/plugin-financial 0.1.7 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +2 -0
  2. package/dist/FinancialContext.d.ts +2 -0
  3. package/dist/FinancialContext.d.ts.map +1 -1
  4. package/dist/FinancialPage.d.ts.map +1 -1
  5. package/dist/data/index.d.ts +1 -0
  6. package/dist/data/index.d.ts.map +1 -1
  7. package/dist/data/mock.d.ts +15 -0
  8. package/dist/data/mock.d.ts.map +1 -1
  9. package/dist/data/supabase.d.ts.map +1 -1
  10. package/dist/data/tables.d.ts +11 -0
  11. package/dist/data/tables.d.ts.map +1 -0
  12. package/dist/data/types.d.ts +2 -1
  13. package/dist/data/types.d.ts.map +1 -1
  14. package/dist/index.cjs +1594 -252
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.ts +13 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1456 -131
  19. package/dist/index.js.map +1 -1
  20. package/dist/locales/en.d.ts.map +1 -1
  21. package/dist/locales/pt-BR.d.ts.map +1 -1
  22. package/dist/migrations/index.d.ts +16 -0
  23. package/dist/migrations/index.d.ts.map +1 -0
  24. package/dist/quick-add.d.ts +13 -0
  25. package/dist/quick-add.d.ts.map +1 -0
  26. package/dist/registries.d.ts.map +1 -1
  27. package/dist/store.d.ts +3 -1
  28. package/dist/store.d.ts.map +1 -1
  29. package/dist/types.d.ts +42 -0
  30. package/dist/types.d.ts.map +1 -1
  31. package/dist/views/CardsView.d.ts.map +1 -1
  32. package/dist/views/InvoiceListView.d.ts.map +1 -1
  33. package/dist/views/QuickTransactionForm.d.ts +16 -0
  34. package/dist/views/QuickTransactionForm.d.ts.map +1 -0
  35. package/dist/views/SummaryView.d.ts +5 -4
  36. package/dist/views/SummaryView.d.ts.map +1 -1
  37. package/dist/views/dashboardWidgets.d.ts.map +1 -1
  38. package/package.json +7 -4
  39. package/src/FinancialContext.tsx +2 -0
  40. package/src/FinancialPage.tsx +86 -7
  41. package/src/README.md +5 -5
  42. package/src/contract.test.ts +1 -1
  43. package/src/data/capability.test.ts +45 -0
  44. package/src/data/index.ts +1 -0
  45. package/src/data/mock.ts +50 -10
  46. package/src/data/supabase.ts +95 -49
  47. package/src/data/tables.ts +12 -0
  48. package/src/data/types.ts +2 -1
  49. package/src/index.ts +24 -4
  50. package/src/locales/en.ts +75 -0
  51. package/src/locales/pt-BR.ts +75 -0
  52. package/src/migrations/000_plg_rename.sql +30 -0
  53. package/src/migrations/001_financial_base.sql +30 -30
  54. package/src/migrations/002_chart_of_accounts.sql +10 -10
  55. package/src/migrations/003_card_brands.sql +4 -4
  56. package/src/migrations/004_order_to_cash.sql +28 -28
  57. package/src/migrations/005_seed_defaults.sql +6 -6
  58. package/src/migrations/006_rls_policies.sql +5 -5
  59. package/src/migrations/{006_extract_fee_amount.sql → 006b_extract_fee_amount.sql} +4 -4
  60. package/src/migrations/007_reconciliation.sql +9 -9
  61. package/src/migrations/007b_movement_payment_method_type.sql +6 -0
  62. package/src/migrations/008_split_payment_movements.sql +5 -5
  63. package/src/migrations/index.ts +544 -0
  64. package/src/quick-add.ts +58 -0
  65. package/src/registries.ts +8 -7
  66. package/src/store.ts +88 -0
  67. package/src/types.ts +45 -0
  68. package/src/views/CardsView.tsx +186 -50
  69. package/src/views/InvoiceListView.tsx +60 -3
  70. package/src/views/QuickTransactionForm.tsx +537 -0
  71. package/src/views/SummaryView.tsx +197 -6
  72. package/src/views/dashboardWidgets.tsx +251 -5
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
- import React14, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
1
+ import * as React17 from 'react';
2
+ import React17__default, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
2
3
  import { createSafeDataProvider, registerTranslations, useTranslation, getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core';
3
- import { toast, defineKpiWidget, defineChartWidget, defineCustomWidget, defineTableWidget, DataTable, PageTransition, ModulePage, Button, DashboardCanvas, SubpageHeader, DatePicker, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, CurrencyInput, useSaveBar, SearchSelect, ListView, KpiCard, ChartWidget, Card, CardHeader, CardTitle, CardContent, TableWidget } from '@fayz-ai/ui';
4
+ import { toast, defineKpiWidget, defineChartWidget, defineCustomWidget, defineTableWidget, DataTable, PageTransition, ModulePage, Button, Modal, ModalContent, DatePicker, DashboardCanvas, SubpageHeader, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, CurrencyInput, useSaveBar, SearchSelect, ListView, KpiCard, ChartWidget, Card, CardHeader, CardTitle, CardContent, TableWidget, useDashboardNavigate, GoldCard } from '@fayz-ai/ui';
4
5
  import { createPluginContext, PluginSettingsPanel, dedup, useModuleNavigation, ModuleActionBar, SettingsGroup, ToggleRow, formatCurrency, parseViewId, PersonLink } from '@fayz-ai/saas';
6
+ import { Wallet, Plus, DollarSign, Landmark, CreditCard, BookOpen, Check, ChevronRight, X, CalendarDays, Paperclip, Camera, Tag, Repeat, CalendarClock, RefreshCw, ArrowDownCircle, ArrowUpCircle, Sparkles, Link2, ArrowRightLeft, Receipt, ChevronDown, Clock, CheckCheck, Calculator, StickyNote, Play, Square, ChevronUp, MoreVertical, Ban, LayoutList, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, Pencil, FileText, User, Calendar, Hash, ExternalLink, Building2, ArrowLeftRight, ShoppingCart, Car, Home, Utensils, Zap, Film, HeartPulse, Dumbbell, Music, ShoppingBag, CircleDollarSign, Search, Trash2, FileCheck, QrCode, Banknote, ArrowUpRight, ArrowDownRight, AlertTriangle, EyeOff, Eye } from 'lucide-react';
5
7
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
- import { Wallet, DollarSign, Landmark, CreditCard, BookOpen, Check, ChevronRight, RefreshCw, ArrowDownCircle, ArrowUpCircle, Sparkles, Link2, ArrowRightLeft, Receipt, ChevronDown, Clock, CheckCheck, X, Calculator, StickyNote, Play, Square, ChevronUp, MoreVertical, Ban, LayoutList, Plus, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, Pencil, FileText, User, Calendar, Hash, CalendarDays, ExternalLink, Building2, Search, Tag, Trash2, FileCheck, QrCode, Banknote, ArrowUpRight, ArrowDownRight, AlertTriangle } from 'lucide-react';
7
8
  import { createPortal } from 'react-dom';
8
9
  import { createStore as createStore$1 } from 'zustand/vanilla';
9
10
 
@@ -14,7 +15,628 @@ var useFinancialConfig = ctx.useConfig;
14
15
  var useFinancialProvider = ctx.useProvider;
15
16
  var useFinancialStore = ctx.useStore;
16
17
  function SummaryView() {
17
- return /* @__PURE__ */ jsx(DashboardCanvas, { surface: "plugin-home", domain: "financial", showHeader: false, className: "space-y-6" });
18
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
19
+ /* @__PURE__ */ jsx("div", { className: "md:hidden", children: /* @__PURE__ */ jsx(MobileSummary, {}) }),
20
+ /* @__PURE__ */ jsx(
21
+ DashboardCanvas,
22
+ {
23
+ surface: "plugin-home",
24
+ domain: "financial",
25
+ showHeader: false,
26
+ className: "hidden space-y-6 md:block"
27
+ }
28
+ )
29
+ ] });
30
+ }
31
+ function categoryIcon(label) {
32
+ const s = label.toLowerCase();
33
+ if (/(mercado|super|padaria|extra|p[aã]o|hortifr)/.test(s)) return ShoppingCart;
34
+ if (/(uber|99|posto|shell|gasol|combust|carro)/.test(s)) return Car;
35
+ if (/(aluguel|casa|condom|im[oó]vel)/.test(s)) return Home;
36
+ if (/(ifood|restaurante|outback|lanche|food|bar\b)/.test(s)) return Utensils;
37
+ if (/(luz|energia|enel|internet|vivo|conta de|[aá]gua)/.test(s)) return Zap;
38
+ if (/(cinema|netflix|cinemark|filme|disney|prime)/.test(s)) return Film;
39
+ if (/(farm|drogasil|sa[uú]de|plano|hospital|m[eé]dic)/.test(s)) return HeartPulse;
40
+ if (/(academia|smartfit|gym|treino)/.test(s)) return Dumbbell;
41
+ if (/(spotify|music|deezer)/.test(s)) return Music;
42
+ if (/(amazon|shopping|loja|magaz|americanas|mercado livre)/.test(s)) return ShoppingBag;
43
+ if (/(fatura|cart[aã]o|nubank|banco|boleto)/.test(s)) return Landmark;
44
+ if (/(sal[aá]rio|freel|pix|reembolso|receb|renda|d[ií]vidend)/.test(s)) return Wallet;
45
+ return CircleDollarSign;
46
+ }
47
+ function useDateLabel() {
48
+ const t2 = useTranslation();
49
+ const { currency } = useFinancialConfig();
50
+ return useMemo(() => {
51
+ const todayStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
52
+ const y = /* @__PURE__ */ new Date();
53
+ y.setDate(y.getDate() - 1);
54
+ const yesterdayStr = y.toISOString().slice(0, 10);
55
+ return (dateStr) => {
56
+ if (dateStr === todayStr) return t2("financial.summary.today");
57
+ if (dateStr === yesterdayStr) return t2("financial.summary.yesterday");
58
+ try {
59
+ return (/* @__PURE__ */ new Date(`${dateStr}T00:00:00`)).toLocaleDateString(currency.locale, {
60
+ weekday: "short",
61
+ day: "2-digit",
62
+ month: "short"
63
+ });
64
+ } catch {
65
+ return dateStr;
66
+ }
67
+ };
68
+ }, [t2, currency.locale]);
69
+ }
70
+ function MobileSummary() {
71
+ const t2 = useTranslation();
72
+ const { currency } = useFinancialConfig();
73
+ const summary = useFinancialStore((s) => s.summary);
74
+ const statement = useFinancialStore((s) => s.statement);
75
+ const bankAccounts = useFinancialStore((s) => s.bankAccounts);
76
+ const fetchSummary = useFinancialStore((s) => s.fetchSummary);
77
+ const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
78
+ const fetchStatement = useFinancialStore((s) => s.fetchStatement);
79
+ const dateLabel = useDateLabel();
80
+ useEffect(() => {
81
+ void fetchSummary();
82
+ void fetchBankAccounts();
83
+ const to = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
84
+ const from = /* @__PURE__ */ new Date();
85
+ from.setDate(from.getDate() - 90);
86
+ void fetchStatement({ dateRange: { from: from.toISOString().slice(0, 10), to } });
87
+ }, []);
88
+ const fmt = (n) => formatCurrency(n, currency);
89
+ const inflow = summary?.monthlyInflow ?? 0;
90
+ const outflow = summary?.monthlyOutflow ?? 0;
91
+ const accountName = useMemo(() => {
92
+ const map = new Map(bankAccounts.map((a) => [a.id, a.name]));
93
+ return (id) => id ? map.get(id) ?? "" : "";
94
+ }, [bankAccounts]);
95
+ const groups = useMemo(() => {
96
+ const entries = (statement?.entries ?? []).filter((e) => e.entryKind === "movement");
97
+ const byDate = /* @__PURE__ */ new Map();
98
+ for (const e of entries) {
99
+ const d = e.movement.paymentDate ?? e.movement.dueDate;
100
+ const bucket = byDate.get(d);
101
+ if (bucket) bucket.push(e);
102
+ else byDate.set(d, [e]);
103
+ }
104
+ return [...byDate.entries()].sort((a, b) => b[0].localeCompare(a[0]));
105
+ }, [statement]);
106
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
107
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
108
+ /* @__PURE__ */ jsxs("div", { className: "col-span-2 rounded-xl border bg-card p-4", children: [
109
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.summary.totalBalance") }),
110
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-2xl font-semibold", children: fmt(summary?.totalBalance ?? 0) })
111
+ ] }),
112
+ /* @__PURE__ */ jsxs("div", { className: "rounded-xl border bg-card p-3", children: [
113
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("financial.summary.income") }),
114
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-base font-semibold text-success", children: fmt(inflow) })
115
+ ] }),
116
+ /* @__PURE__ */ jsxs("div", { className: "rounded-xl border bg-card p-3", children: [
117
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("financial.summary.expenses") }),
118
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-base font-semibold text-destructive", children: fmt(outflow) })
119
+ ] })
120
+ ] }),
121
+ groups.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-xl border border-dashed py-12 text-center text-sm text-muted-foreground", children: t2("financial.summary.noTransactions") }) : /* @__PURE__ */ jsx("div", { className: "space-y-4", children: groups.map(([date, rows]) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
122
+ /* @__PURE__ */ jsx("p", { className: "px-1 text-xs font-medium uppercase tracking-wide text-muted-foreground", children: dateLabel(date) }),
123
+ /* @__PURE__ */ jsx("div", { className: "divide-y overflow-hidden rounded-xl border bg-card", children: rows.map((e) => {
124
+ const isCredit = e.movement.direction === "credit";
125
+ const title = e.invoice?.contactName || e.movement.notes || t2("financial.summary.transactionsTitle");
126
+ const Icon = categoryIcon(title);
127
+ const category = isCredit ? t2("financial.summary.income") : t2("financial.summary.expenses");
128
+ const acct = accountName(e.movement.bankAccountId);
129
+ const subtitle = acct ? `${category} \xB7 ${acct}` : category;
130
+ const receiptUrl = e.invoice?.metadata?.receiptUrl;
131
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-3 py-2.5", children: [
132
+ /* @__PURE__ */ jsx(
133
+ "span",
134
+ {
135
+ className: `flex h-9 w-9 shrink-0 items-center justify-center rounded-full ${isCredit ? "bg-success-soft text-success-soft-foreground" : "bg-destructive-soft text-destructive-soft-foreground"}`,
136
+ children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" })
137
+ }
138
+ ),
139
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
140
+ /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1.5 truncate text-sm font-medium", children: [
141
+ title,
142
+ receiptUrl && /* @__PURE__ */ jsx(Paperclip, { className: "h-3 w-3 shrink-0 text-muted-foreground", "aria-label": t2("financial.quickTx.receiptAttached") })
143
+ ] }),
144
+ /* @__PURE__ */ jsx("p", { className: "truncate text-xs text-muted-foreground", children: subtitle })
145
+ ] }),
146
+ receiptUrl && /* @__PURE__ */ jsx(
147
+ "img",
148
+ {
149
+ src: receiptUrl,
150
+ alt: t2("financial.quickTx.receiptAttached"),
151
+ className: "h-9 w-9 shrink-0 rounded-md border object-cover"
152
+ }
153
+ ),
154
+ /* @__PURE__ */ jsxs("span", { className: `shrink-0 text-sm font-semibold ${isCredit ? "text-success" : "text-destructive"}`, children: [
155
+ isCredit ? "+" : "-",
156
+ fmt(e.net)
157
+ ] })
158
+ ] }, e.movement.id);
159
+ }) })
160
+ ] }, date)) })
161
+ ] });
162
+ }
163
+ function typeTheme(type) {
164
+ switch (type) {
165
+ case "income":
166
+ return { header: "bg-success text-success-foreground", ring: "ring-success", icon: ArrowDownCircle };
167
+ case "transfer":
168
+ return { header: "bg-muted text-foreground", ring: "ring-ring", icon: ArrowLeftRight };
169
+ case "expense":
170
+ default:
171
+ return { header: "bg-destructive text-destructive-foreground", ring: "ring-destructive", icon: ArrowUpCircle };
172
+ }
173
+ }
174
+ function ChipSelect({ icon: Icon, label, value, options, onChange, emptyLabel }) {
175
+ const [open, setOpen] = useState(false);
176
+ const ref = useRef(null);
177
+ useEffect(() => {
178
+ if (!open) return;
179
+ function onDoc(e) {
180
+ if (ref.current && !ref.current.contains(e.target)) setOpen(false);
181
+ }
182
+ document.addEventListener("mousedown", onDoc);
183
+ return () => document.removeEventListener("mousedown", onDoc);
184
+ }, [open]);
185
+ const selected = options.find((o) => o.id === value);
186
+ return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
187
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: label }),
188
+ /* @__PURE__ */ jsxs(
189
+ "button",
190
+ {
191
+ type: "button",
192
+ onClick: () => setOpen((v) => !v),
193
+ className: "mt-1 flex w-full items-center gap-2 rounded-input border border-input bg-card px-3 py-2.5 text-left text-sm shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] transition-colors hover:bg-muted/30",
194
+ children: [
195
+ /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
196
+ /* @__PURE__ */ jsx("span", { className: `flex-1 truncate ${selected ? "" : "text-muted-foreground"}`, children: selected?.name ?? emptyLabel }),
197
+ /* @__PURE__ */ jsx(ChevronDown, { className: `h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform ${open ? "rotate-180" : ""}` })
198
+ ]
199
+ }
200
+ ),
201
+ open && /* @__PURE__ */ jsx("div", { className: "fayz-glass-surface absolute bottom-full left-0 right-0 z-50 mb-1 max-h-56 overflow-auto rounded-lg border bg-popover py-1 shadow-lg", children: options.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-3 py-4 text-center text-xs text-muted-foreground", children: emptyLabel }) : /* @__PURE__ */ jsxs(Fragment, { children: [
202
+ value != null && /* @__PURE__ */ jsxs(
203
+ "button",
204
+ {
205
+ onClick: () => {
206
+ onChange(void 0);
207
+ setOpen(false);
208
+ },
209
+ className: "flex w-full items-center gap-2 px-3 py-2.5 text-left text-xs text-muted-foreground hover:bg-muted/50",
210
+ children: [
211
+ /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" }),
212
+ " ",
213
+ emptyLabel
214
+ ]
215
+ }
216
+ ),
217
+ options.map((o) => /* @__PURE__ */ jsxs(
218
+ "button",
219
+ {
220
+ onClick: () => {
221
+ onChange(o.id);
222
+ setOpen(false);
223
+ },
224
+ className: `flex w-full items-center gap-2 px-3 py-2.5 text-left text-sm hover:bg-muted/50 ${o.id === value ? "bg-muted/30" : ""}`,
225
+ children: [
226
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: o.name }),
227
+ o.hint && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: o.hint }),
228
+ o.id === value && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5 text-primary" })
229
+ ]
230
+ },
231
+ o.id
232
+ ))
233
+ ] }) })
234
+ ] });
235
+ }
236
+ function TogglePill({ icon: Icon, label, checked, onChange, accent }) {
237
+ return /* @__PURE__ */ jsxs(
238
+ "button",
239
+ {
240
+ type: "button",
241
+ onClick: () => onChange(!checked),
242
+ className: `flex items-center gap-2 rounded-full border px-3 py-2 text-xs font-medium transition-colors ${checked ? `border-transparent ${accent ?? "bg-primary text-primary-foreground"}` : "border-input bg-card text-muted-foreground hover:bg-muted/40"}`,
243
+ children: [
244
+ /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" }),
245
+ label
246
+ ]
247
+ }
248
+ );
249
+ }
250
+ function QuickTransactionForm({ open, onOpenChange, defaultType = "expense", defaultAccountId, focusReceipt = false }) {
251
+ const t2 = useTranslation();
252
+ const { currency } = useFinancialConfig();
253
+ const provider = useFinancialProvider();
254
+ const createQuickTransaction = useFinancialStore((s) => s.createQuickTransaction);
255
+ const [type, setType] = useState(defaultType);
256
+ const [amount, setAmount] = useState(0);
257
+ const [amountStr, setAmountStr] = useState("");
258
+ const [paid, setPaid] = useState(true);
259
+ const [date, setDate] = useState(() => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
260
+ const [description, setDescription] = useState("");
261
+ const [categoryId, setCategoryId] = useState();
262
+ const [accountId, setAccountId] = useState();
263
+ const [toAccountId, setToAccountId] = useState();
264
+ const [recurring, setRecurring] = useState(false);
265
+ const [saving, setSaving] = useState(false);
266
+ const [receiptUrl, setReceiptUrl] = useState();
267
+ const fileInputRef = useRef(null);
268
+ const [accounts, setAccounts] = useState([]);
269
+ const [categories, setCategories] = useState([]);
270
+ useEffect(() => {
271
+ if (!open) return;
272
+ setType(defaultType);
273
+ setAmount(0);
274
+ setAmountStr("");
275
+ setPaid(true);
276
+ setDate((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
277
+ setDescription("");
278
+ setCategoryId(void 0);
279
+ setAccountId(defaultAccountId);
280
+ setToAccountId(void 0);
281
+ setRecurring(false);
282
+ setReceiptUrl(void 0);
283
+ }, [open, defaultType, defaultAccountId]);
284
+ useEffect(() => {
285
+ if (open && focusReceipt) {
286
+ const id = window.setTimeout(() => fileInputRef.current?.click(), 250);
287
+ return () => window.clearTimeout(id);
288
+ }
289
+ }, [open, focusReceipt]);
290
+ function onPickReceipt(e) {
291
+ const file = e.target.files?.[0];
292
+ e.target.value = "";
293
+ if (!file) return;
294
+ if (!file.type.startsWith("image/")) {
295
+ toast.error(t2("financial.quickTx.receiptInvalid"));
296
+ return;
297
+ }
298
+ const reader = new FileReader();
299
+ reader.onload = () => {
300
+ setReceiptUrl(typeof reader.result === "string" ? reader.result : void 0);
301
+ };
302
+ reader.onerror = () => toast.error(t2("financial.quickTx.receiptInvalid"));
303
+ reader.readAsDataURL(file);
304
+ }
305
+ useEffect(() => {
306
+ if (!open) return;
307
+ let cancelled = false;
308
+ void (async () => {
309
+ const [accs, coa] = await Promise.all([
310
+ provider.getBankAccounts(),
311
+ provider.getChartOfAccounts().catch(() => [])
312
+ ]);
313
+ if (cancelled) return;
314
+ setAccounts(accs);
315
+ setCategories(coa);
316
+ setAccountId((prev) => prev ?? accs[0]?.id);
317
+ })();
318
+ return () => {
319
+ cancelled = true;
320
+ };
321
+ }, [open, provider]);
322
+ const theme = typeTheme(type);
323
+ const HeaderIcon = theme.icon;
324
+ const accountOptions = useMemo(
325
+ () => accounts.map((a) => ({ id: a.id, name: a.name, hint: a.accountType === "credit_card" ? t2("financial.quickTx.card") : void 0 })),
326
+ [accounts, t2]
327
+ );
328
+ const categoryOptions = useMemo(
329
+ () => categories.filter((c) => c.isActive).map((c) => ({ id: c.id, name: c.name, hint: c.code })),
330
+ [categories]
331
+ );
332
+ function onAmountChange(raw) {
333
+ const cleaned = raw.replace(/[^\d.,]/g, "").replace(",", ".");
334
+ setAmountStr(raw.replace(/[^\d.,]/g, ""));
335
+ const n = Number.parseFloat(cleaned);
336
+ setAmount(Number.isFinite(n) ? n : 0);
337
+ }
338
+ const canSave = amount > 0 && !saving && (type === "transfer" ? !!accountId && !!toAccountId && accountId !== toAccountId : true);
339
+ async function handleSave() {
340
+ if (!canSave) {
341
+ if (type === "transfer" && accountId && accountId === toAccountId) {
342
+ toast.error(t2("financial.transfer.sameAccount"));
343
+ }
344
+ return;
345
+ }
346
+ setSaving(true);
347
+ try {
348
+ await createQuickTransaction({
349
+ type,
350
+ amount,
351
+ date,
352
+ paid: type === "transfer" ? true : paid,
353
+ description: description || void 0,
354
+ categoryId: type === "transfer" ? void 0 : categoryId,
355
+ bankAccountId: accountId,
356
+ toAccountId: type === "transfer" ? toAccountId : void 0,
357
+ recurring: type === "transfer" ? false : recurring,
358
+ receiptUrl: type === "transfer" ? void 0 : receiptUrl
359
+ });
360
+ onOpenChange(false);
361
+ } catch {
362
+ } finally {
363
+ setSaving(false);
364
+ }
365
+ }
366
+ const TYPES = [
367
+ { id: "expense", label: t2("financial.quickTx.expense") },
368
+ { id: "income", label: t2("financial.quickTx.income") },
369
+ { id: "transfer", label: t2("financial.quickTx.transfer") }
370
+ ];
371
+ return /* @__PURE__ */ jsx(Modal, { open, onOpenChange, children: /* @__PURE__ */ jsxs(ModalContent, { size: "md", noPadding: true, hideClose: true, className: "overflow-hidden", children: [
372
+ /* @__PURE__ */ jsxs("div", { className: `${theme.header} shrink-0 px-5 pb-5 pt-5`, children: [
373
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
374
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
375
+ /* @__PURE__ */ jsx(HeaderIcon, { className: "h-5 w-5" }),
376
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold", children: t2("financial.quickTx.title") })
377
+ ] }),
378
+ /* @__PURE__ */ jsx(
379
+ "button",
380
+ {
381
+ onClick: () => onOpenChange(false),
382
+ className: "flex h-7 w-7 items-center justify-center rounded-full opacity-80 transition-colors hover:bg-black/10 hover:opacity-100",
383
+ "aria-label": t2("financial.quickTx.close"),
384
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
385
+ }
386
+ )
387
+ ] }),
388
+ /* @__PURE__ */ jsx("div", { className: "mt-4 flex gap-1 rounded-full bg-black/15 p-1", children: TYPES.map((ty) => /* @__PURE__ */ jsx(
389
+ "button",
390
+ {
391
+ onClick: () => setType(ty.id),
392
+ className: `flex-1 rounded-full px-2 py-1.5 text-xs font-medium transition-colors ${type === ty.id ? "bg-white/90 text-foreground shadow-sm" : "opacity-90 hover:bg-white/10"}`,
393
+ children: ty.label
394
+ },
395
+ ty.id
396
+ )) }),
397
+ /* @__PURE__ */ jsxs("div", { className: "mt-5 flex items-baseline justify-center gap-2", children: [
398
+ /* @__PURE__ */ jsx("span", { className: "text-xl font-medium opacity-90", children: currency.symbol }),
399
+ /* @__PURE__ */ jsx(
400
+ "input",
401
+ {
402
+ type: "text",
403
+ inputMode: "decimal",
404
+ autoFocus: true,
405
+ value: amountStr,
406
+ onChange: (e) => onAmountChange(e.target.value),
407
+ onKeyDown: (e) => {
408
+ if (e.key === "Enter") void handleSave();
409
+ },
410
+ placeholder: "0,00",
411
+ className: "w-full max-w-[220px] bg-transparent text-center text-4xl font-bold tracking-tight outline-none placeholder:opacity-40"
412
+ }
413
+ )
414
+ ] })
415
+ ] }),
416
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-4 overflow-y-auto px-5 py-5", children: [
417
+ type !== "transfer" && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded-input border border-input bg-card px-3 py-2.5 shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)]", children: [
418
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: type === "income" ? t2("financial.quickTx.received") : t2("financial.quickTx.paid") }),
419
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
420
+ /* @__PURE__ */ jsx(
421
+ "button",
422
+ {
423
+ onClick: () => setPaid(true),
424
+ className: `rounded-full px-3 py-1 text-xs font-medium transition-colors ${paid ? "bg-success text-success-foreground" : "bg-muted text-muted-foreground"}`,
425
+ children: t2("financial.quickTx.yes")
426
+ }
427
+ ),
428
+ /* @__PURE__ */ jsx(
429
+ "button",
430
+ {
431
+ onClick: () => setPaid(false),
432
+ className: `rounded-full px-3 py-1 text-xs font-medium transition-colors ${!paid ? "bg-warning text-warning-foreground" : "bg-muted text-muted-foreground"}`,
433
+ children: t2("financial.quickTx.no")
434
+ }
435
+ )
436
+ ] })
437
+ ] }),
438
+ /* @__PURE__ */ jsxs("div", { children: [
439
+ /* @__PURE__ */ jsxs("span", { className: "mb-1 flex items-center gap-1.5 text-xs font-medium text-muted-foreground", children: [
440
+ /* @__PURE__ */ jsx(CalendarDays, { className: "h-3.5 w-3.5" }),
441
+ " ",
442
+ t2("financial.invoiceForm.date")
443
+ ] }),
444
+ /* @__PURE__ */ jsx(DatePicker, { value: date, onChange: setDate })
445
+ ] }),
446
+ /* @__PURE__ */ jsxs("div", { children: [
447
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.description") }),
448
+ /* @__PURE__ */ jsx(
449
+ "input",
450
+ {
451
+ type: "text",
452
+ value: description,
453
+ onChange: (e) => setDescription(e.target.value),
454
+ onKeyDown: (e) => {
455
+ if (e.key === "Enter") void handleSave();
456
+ },
457
+ placeholder: t2("financial.quickTx.descriptionPlaceholder"),
458
+ className: "mt-1 w-full rounded-input border border-input bg-card px-3 py-2.5 text-sm shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] outline-none focus:ring-2 focus:ring-ring"
459
+ }
460
+ )
461
+ ] }),
462
+ type !== "transfer" && /* @__PURE__ */ jsxs("div", { children: [
463
+ /* @__PURE__ */ jsxs("span", { className: "mb-1 flex items-center gap-1.5 text-xs font-medium text-muted-foreground", children: [
464
+ /* @__PURE__ */ jsx(Paperclip, { className: "h-3.5 w-3.5" }),
465
+ " ",
466
+ t2("financial.quickTx.receipt")
467
+ ] }),
468
+ /* @__PURE__ */ jsx(
469
+ "input",
470
+ {
471
+ ref: fileInputRef,
472
+ type: "file",
473
+ accept: "image/*",
474
+ capture: "environment",
475
+ className: "hidden",
476
+ onChange: onPickReceipt
477
+ }
478
+ ),
479
+ receiptUrl ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 rounded-input border border-input bg-card p-2 shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)]", children: [
480
+ /* @__PURE__ */ jsx(
481
+ "img",
482
+ {
483
+ src: receiptUrl,
484
+ alt: t2("financial.quickTx.receipt"),
485
+ className: "h-16 w-16 shrink-0 rounded-md border object-cover"
486
+ }
487
+ ),
488
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
489
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", children: t2("financial.quickTx.receiptAttached") }),
490
+ /* @__PURE__ */ jsx(
491
+ "button",
492
+ {
493
+ type: "button",
494
+ onClick: () => fileInputRef.current?.click(),
495
+ className: "text-xs text-primary hover:underline",
496
+ children: t2("financial.quickTx.receiptReplace")
497
+ }
498
+ )
499
+ ] }),
500
+ /* @__PURE__ */ jsx(
501
+ "button",
502
+ {
503
+ type: "button",
504
+ onClick: () => setReceiptUrl(void 0),
505
+ "aria-label": t2("financial.quickTx.receiptRemove"),
506
+ className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted",
507
+ children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
508
+ }
509
+ )
510
+ ] }) : /* @__PURE__ */ jsxs(
511
+ "button",
512
+ {
513
+ type: "button",
514
+ onClick: () => fileInputRef.current?.click(),
515
+ className: `flex w-full items-center justify-center gap-2 rounded-input border border-dashed px-3 py-3 text-sm font-medium transition-colors hover:bg-muted/30 ${focusReceipt ? "border-primary text-primary ring-1 ring-primary/30" : "border-input text-muted-foreground"}`,
516
+ children: [
517
+ /* @__PURE__ */ jsx(Camera, { className: "h-4 w-4" }),
518
+ " ",
519
+ t2("financial.quickTx.receiptCapture")
520
+ ]
521
+ }
522
+ )
523
+ ] }),
524
+ type === "transfer" ? /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-3", children: [
525
+ /* @__PURE__ */ jsx(
526
+ ChipSelect,
527
+ {
528
+ icon: Wallet,
529
+ label: t2("financial.quickTx.fromAccount"),
530
+ value: accountId,
531
+ options: accountOptions,
532
+ onChange: setAccountId,
533
+ emptyLabel: t2("financial.quickTx.selectAccount")
534
+ }
535
+ ),
536
+ /* @__PURE__ */ jsx(
537
+ ChipSelect,
538
+ {
539
+ icon: Wallet,
540
+ label: t2("financial.quickTx.toAccount"),
541
+ value: toAccountId,
542
+ options: accountOptions.filter((o) => o.id !== accountId),
543
+ onChange: setToAccountId,
544
+ emptyLabel: t2("financial.quickTx.selectAccount")
545
+ }
546
+ )
547
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-3", children: [
548
+ /* @__PURE__ */ jsx(
549
+ ChipSelect,
550
+ {
551
+ icon: Tag,
552
+ label: t2("financial.quickTx.category"),
553
+ value: categoryId,
554
+ options: categoryOptions,
555
+ onChange: setCategoryId,
556
+ emptyLabel: t2("financial.quickTx.selectCategory")
557
+ }
558
+ ),
559
+ /* @__PURE__ */ jsx(
560
+ ChipSelect,
561
+ {
562
+ icon: Wallet,
563
+ label: t2("financial.quickTx.account"),
564
+ value: accountId,
565
+ options: accountOptions,
566
+ onChange: setAccountId,
567
+ emptyLabel: t2("financial.quickTx.selectAccount")
568
+ }
569
+ )
570
+ ] }),
571
+ type !== "transfer" && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
572
+ /* @__PURE__ */ jsx(
573
+ TogglePill,
574
+ {
575
+ icon: Repeat,
576
+ label: t2("financial.quickTx.recurring"),
577
+ checked: recurring,
578
+ onChange: setRecurring
579
+ }
580
+ ),
581
+ /* @__PURE__ */ jsxs(
582
+ "button",
583
+ {
584
+ type: "button",
585
+ onClick: () => fileInputRef.current?.click(),
586
+ title: t2("financial.quickTx.attachment"),
587
+ className: `flex items-center gap-2 rounded-full border px-3 py-2 text-xs font-medium transition-colors ${receiptUrl ? "border-transparent bg-primary text-primary-foreground" : "border-input bg-card text-muted-foreground hover:bg-muted/40"}`,
588
+ children: [
589
+ /* @__PURE__ */ jsx(Paperclip, { className: "h-3.5 w-3.5" }),
590
+ " ",
591
+ t2("financial.quickTx.attachment"),
592
+ receiptUrl && /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" })
593
+ ]
594
+ }
595
+ )
596
+ ] })
597
+ ] }),
598
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-3 border-t px-5 py-3", children: [
599
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: amount > 0 ? formatCurrency(amount, currency) : t2("financial.quickTx.enterAmount") }),
600
+ /* @__PURE__ */ jsxs(
601
+ Button,
602
+ {
603
+ className: "ml-auto",
604
+ onClick: () => void handleSave(),
605
+ disabled: !canSave,
606
+ children: [
607
+ /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }),
608
+ saving ? t2("financial.quickTx.saving") : t2("financial.quickTx.save")
609
+ ]
610
+ }
611
+ )
612
+ ] })
613
+ ] }) });
614
+ }
615
+ var pending = null;
616
+ var listeners = /* @__PURE__ */ new Set();
617
+ function emit() {
618
+ for (const l of listeners) l();
619
+ }
620
+ function openQuickAdd(type = "expense", receipt = false) {
621
+ pending = { type, receipt };
622
+ emit();
623
+ }
624
+ function consumeQuickAdd() {
625
+ const req = pending;
626
+ pending = null;
627
+ return req;
628
+ }
629
+ function subscribe(cb) {
630
+ listeners.add(cb);
631
+ return () => {
632
+ listeners.delete(cb);
633
+ };
634
+ }
635
+ function getSnapshot() {
636
+ return pending;
637
+ }
638
+ function usePendingQuickAdd() {
639
+ return React17.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
18
640
  }
19
641
  var STATUS_OPTIONS = [
20
642
  { value: "open", labelKey: "financial.invoice.statusOpen", color: "bg-info-soft text-info-soft-foreground", icon: CircleDashed },
@@ -87,19 +709,41 @@ function useInvoiceColumns(currency) {
87
709
  function InvoiceListView({ direction, onNew, onEdit }) {
88
710
  const t2 = useTranslation();
89
711
  const { currency } = useFinancialConfig();
712
+ const provider = useFinancialProvider();
90
713
  const invoices = useFinancialStore((s) => s.invoices);
91
714
  useFinancialStore((s) => s.invoicesTotal);
92
715
  const invoicesLoading = useFinancialStore((s) => s.invoicesLoading);
93
716
  const fetchInvoices = useFinancialStore((s) => s.fetchInvoices);
94
717
  const [search, setSearch] = useState("");
95
718
  const [statusFilter, setStatusFilter] = useState([]);
719
+ const [accountId, setAccountId] = useState("");
720
+ const [costCenterId, setCostCenterId] = useState("");
721
+ const [accounts, setAccounts] = useState([]);
722
+ const [costCenters, setCostCenters] = useState([]);
723
+ useEffect(() => {
724
+ let active = true;
725
+ Promise.all([provider.getChartOfAccounts(), provider.getCostCenters()]).then(([accountRows, costCenterRows]) => {
726
+ if (!active) return;
727
+ setAccounts(accountRows.filter((account) => account.nodeType === "leaf"));
728
+ setCostCenters(costCenterRows);
729
+ }).catch(() => {
730
+ if (!active) return;
731
+ setAccounts([]);
732
+ setCostCenters([]);
733
+ });
734
+ return () => {
735
+ active = false;
736
+ };
737
+ }, [provider]);
96
738
  useEffect(() => {
97
739
  fetchInvoices({
98
740
  direction,
99
741
  status: statusFilter.length > 0 ? statusFilter : void 0,
100
- search: search || void 0
742
+ search: search || void 0,
743
+ accountId: accountId || void 0,
744
+ costCenterId: costCenterId || void 0
101
745
  });
102
- }, [direction, statusFilter, search]);
746
+ }, [direction, statusFilter, search, accountId, costCenterId]);
103
747
  const filtered = invoices.filter((inv) => inv.direction === direction);
104
748
  const listTitle = direction === "debit" ? t2("financial.invoice.accountsPayable") : t2("financial.invoice.accountsReceivable");
105
749
  const columns = useInvoiceColumns(currency);
@@ -129,6 +773,46 @@ function InvoiceListView({ direction, onNew, onEdit }) {
129
773
  subtitle: t2("financial.invoice.invoices", { count: filtered.length })
130
774
  }
131
775
  ),
776
+ (accounts.length > 0 || costCenters.length > 0) && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-end gap-3 rounded-lg border bg-muted/20 px-4 py-3", children: [
777
+ accounts.length > 0 && /* @__PURE__ */ jsxs("label", { className: "flex min-w-[220px] flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
778
+ t2("financial.invoice.filterAccount"),
779
+ /* @__PURE__ */ jsxs(
780
+ "select",
781
+ {
782
+ className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
783
+ value: accountId,
784
+ onChange: (event) => setAccountId(event.target.value),
785
+ children: [
786
+ /* @__PURE__ */ jsx("option", { value: "", children: t2("financial.invoice.filterAllAccounts") }),
787
+ accounts.map((account) => /* @__PURE__ */ jsxs("option", { value: account.id, children: [
788
+ account.code,
789
+ " - ",
790
+ account.name
791
+ ] }, account.id))
792
+ ]
793
+ }
794
+ )
795
+ ] }),
796
+ costCenters.length > 0 && /* @__PURE__ */ jsxs("label", { className: "flex min-w-[220px] flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
797
+ t2("financial.invoice.filterCostCenter"),
798
+ /* @__PURE__ */ jsxs(
799
+ "select",
800
+ {
801
+ className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
802
+ value: costCenterId,
803
+ onChange: (event) => setCostCenterId(event.target.value),
804
+ children: [
805
+ /* @__PURE__ */ jsx("option", { value: "", children: t2("financial.invoice.filterAllCostCenters") }),
806
+ costCenters.map((costCenter) => /* @__PURE__ */ jsxs("option", { value: costCenter.id, children: [
807
+ costCenter.code,
808
+ " - ",
809
+ costCenter.name
810
+ ] }, costCenter.id))
811
+ ]
812
+ }
813
+ )
814
+ ] })
815
+ ] }),
132
816
  /* @__PURE__ */ jsx(
133
817
  ListView,
134
818
  {
@@ -2701,59 +3385,141 @@ function CommissionsView() {
2701
3385
  /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center rounded-lg border-2 border-dashed border-muted p-12", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.commissions.comingSoon") }) })
2702
3386
  ] });
2703
3387
  }
3388
+ function cardMath(acc) {
3389
+ const limit = acc.creditLimit ?? 0;
3390
+ const used = Math.max(0, -acc.currentBalance);
3391
+ const available = Math.max(0, limit - used);
3392
+ const pct = limit > 0 ? Math.min(100, used / limit * 100) : 0;
3393
+ return { limit, used, available, pct };
3394
+ }
2704
3395
  function CardsView() {
2705
3396
  const t2 = useTranslation();
2706
- const { currency, labels } = useFinancialConfig();
2707
- const cardTransactions = useFinancialStore((s) => s.cardTransactions);
2708
- const cardsLoading = useFinancialStore((s) => s.cardsLoading);
2709
- const fetchCardTransactions = useFinancialStore((s) => s.fetchCardTransactions);
3397
+ const { currency } = useFinancialConfig();
3398
+ const bankAccounts = useFinancialStore((s) => s.bankAccounts);
3399
+ const bankAccountsLoading = useFinancialStore((s) => s.bankAccountsLoading);
3400
+ const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
3401
+ const [tab, setTab] = useState("open");
3402
+ const [addOpen, setAddOpen] = useState(false);
3403
+ const [addCardId, setAddCardId] = useState();
2710
3404
  useEffect(() => {
2711
- fetchCardTransactions();
3405
+ fetchBankAccounts();
2712
3406
  }, []);
2713
- const byBrand = {};
2714
- for (const tx of cardTransactions) {
2715
- const brand = tx.cardBrand || "Other";
2716
- if (!byBrand[brand]) byBrand[brand] = [];
2717
- byBrand[brand].push(tx);
3407
+ const cards = useMemo(
3408
+ () => bankAccounts.filter((a) => a.accountType === "credit_card"),
3409
+ [bankAccounts]
3410
+ );
3411
+ const openCards = useMemo(() => cards.filter((c) => c.isActive), [cards]);
3412
+ const closedCards = useMemo(() => cards.filter((c) => !c.isActive), [cards]);
3413
+ const visibleCards = tab === "open" ? openCards : closedCards;
3414
+ const summary = useMemo(() => {
3415
+ let totalAvailable = 0;
3416
+ let totalBills = 0;
3417
+ for (const c of openCards) {
3418
+ const { available, used } = cardMath(c);
3419
+ totalAvailable += available;
3420
+ totalBills += used;
3421
+ }
3422
+ return { totalAvailable, totalBills };
3423
+ }, [openCards]);
3424
+ function openAddExpense(cardId) {
3425
+ setAddCardId(cardId);
3426
+ setAddOpen(true);
2718
3427
  }
2719
3428
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
2720
3429
  /* @__PURE__ */ jsx(SubpageHeader, { title: t2("financial.cards.title"), subtitle: t2("financial.cards.subtitle") }),
2721
- cardsLoading ? /* @__PURE__ */ jsx("div", { className: "text-center py-12 text-sm text-muted-foreground", children: "Loading..." }) : cardTransactions.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center rounded-lg border-2 border-dashed border-muted", children: [
2722
- /* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-lg bg-indigo-50 dark:bg-indigo-500/20 mb-2", children: /* @__PURE__ */ jsx(CreditCard, { className: "h-5 w-5 text-indigo-500" }) }),
2723
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.cards.noTransactions") }),
2724
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: t2("financial.cards.transactionsAppear") })
2725
- ] }) : Object.entries(byBrand).map(([brand, txs]) => /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card shadow-sm", children: [
2726
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-4 py-3 border-b", children: [
2727
- /* @__PURE__ */ jsx(CreditCard, { className: "h-4 w-4 text-indigo-500" }),
2728
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: brand }),
2729
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
2730
- "(",
2731
- txs.length,
2732
- " transactions)"
3430
+ bankAccountsLoading && cards.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-center py-12 text-sm text-muted-foreground", children: "Loading..." }) : cards.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center rounded-2xl border-2 border-dashed border-muted", children: [
3431
+ /* @__PURE__ */ jsx("div", { className: "flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-br from-amber-300 to-yellow-600 mb-3 text-white shadow-sm", children: /* @__PURE__ */ jsx(CreditCard, { className: "h-6 w-6" }) }),
3432
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t2("financial.cards.noCards") }),
3433
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5 max-w-xs", children: t2("financial.cards.noCardsHint") })
3434
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3435
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-px overflow-hidden rounded-2xl border bg-border shadow-sm", children: [
3436
+ /* @__PURE__ */ jsxs("div", { className: "bg-card px-4 py-3", children: [
3437
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.cards.availableLimit") }),
3438
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-lg font-bold tabular-nums text-success", children: formatCurrency(summary.totalAvailable, currency) })
3439
+ ] }),
3440
+ /* @__PURE__ */ jsxs("div", { className: "bg-card px-4 py-3", children: [
3441
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.cards.allBillsValue") }),
3442
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-lg font-bold tabular-nums", children: formatCurrency(summary.totalBills, currency) })
2733
3443
  ] })
2734
3444
  ] }),
2735
- /* @__PURE__ */ jsx("div", { className: "divide-y", children: txs.map((tx) => /* @__PURE__ */ jsxs("div", { className: "px-4 py-3 flex items-center justify-between", children: [
2736
- /* @__PURE__ */ jsxs("div", { children: [
2737
- /* @__PURE__ */ jsxs("p", { className: "text-sm font-medium", children: [
2738
- "Installment ",
2739
- tx.currentInstallment,
2740
- "/",
2741
- tx.installmentCount
3445
+ closedCards.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex gap-1 rounded-full bg-muted p-1 w-full max-w-xs", children: ["open", "closed"].map((k) => /* @__PURE__ */ jsx(
3446
+ "button",
3447
+ {
3448
+ onClick: () => setTab(k),
3449
+ className: `flex-1 rounded-full px-3 py-1.5 text-xs font-medium transition-colors ${tab === k ? "bg-card text-foreground shadow-sm" : "text-muted-foreground hover:text-foreground"}`,
3450
+ children: k === "open" ? t2("financial.cards.tabOpen") : t2("financial.cards.tabClosed")
3451
+ },
3452
+ k
3453
+ )) }),
3454
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3", children: visibleCards.map((card) => {
3455
+ const { limit, used, available, pct } = cardMath(card);
3456
+ const over = pct >= 90;
3457
+ const brand = `${card.bankName ?? ""} ${card.name ?? ""}`.toLowerCase();
3458
+ const headerGrad = brand.includes("nubank") || brand.includes("nu ") ? "from-purple-500 via-purple-600 to-indigo-700" : brand.includes("ita") ? "from-orange-400 via-orange-500 to-amber-600" : brand.includes("inter") ? "from-orange-500 via-orange-600 to-orange-700" : "from-indigo-500 via-indigo-600 to-violet-700";
3459
+ const barGrad = brand.includes("nubank") || brand.includes("nu ") ? "from-purple-500 to-indigo-600" : brand.includes("ita") ? "from-orange-400 to-amber-500" : brand.includes("inter") ? "from-orange-500 to-orange-600" : "from-indigo-500 to-violet-600";
3460
+ return /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-2xl border bg-card shadow-sm", children: [
3461
+ /* @__PURE__ */ jsxs("div", { className: `bg-gradient-to-br ${headerGrad} px-4 py-4 text-white`, children: [
3462
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
3463
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3464
+ /* @__PURE__ */ jsx(CreditCard, { className: "h-5 w-5" }),
3465
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: card.name })
3466
+ ] }),
3467
+ card.bankName && /* @__PURE__ */ jsx("span", { className: "text-[11px] font-medium uppercase tracking-wide opacity-80", children: card.bankName })
3468
+ ] }),
3469
+ /* @__PURE__ */ jsxs("div", { className: "mt-4", children: [
3470
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] uppercase tracking-wide opacity-80", children: t2("financial.cards.currentBill") }),
3471
+ /* @__PURE__ */ jsx("p", { className: "text-2xl font-bold tabular-nums leading-tight", children: formatCurrency(used, currency) })
3472
+ ] })
2742
3473
  ] }),
2743
- /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
2744
- "Due: ",
2745
- tx.expectedDate
3474
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3 px-4 py-3", children: [
3475
+ /* @__PURE__ */ jsxs("div", { children: [
3476
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-[11px] text-muted-foreground", children: [
3477
+ /* @__PURE__ */ jsx("span", { children: t2("financial.cards.usedOfLimit", { percent: String(Math.round(pct)) }) }),
3478
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: formatCurrency(limit, currency) })
3479
+ ] }),
3480
+ /* @__PURE__ */ jsx("div", { className: "mt-1 h-2 overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx(
3481
+ "div",
3482
+ {
3483
+ className: `h-full rounded-full ${over ? "bg-destructive" : `bg-gradient-to-r ${barGrad}`}`,
3484
+ style: { width: `${pct}%` }
3485
+ }
3486
+ ) })
3487
+ ] }),
3488
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
3489
+ /* @__PURE__ */ jsxs("div", { children: [
3490
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("financial.cards.availableLimit") }),
3491
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold tabular-nums", children: formatCurrency(available, currency) })
3492
+ ] }),
3493
+ card.dueDay != null && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 text-right text-xs text-muted-foreground", children: [
3494
+ /* @__PURE__ */ jsx(CalendarClock, { className: "h-3.5 w-3.5" }),
3495
+ /* @__PURE__ */ jsx("span", { children: t2("financial.cards.dueOn", { day: String(card.dueDay) }) })
3496
+ ] })
3497
+ ] }),
3498
+ /* @__PURE__ */ jsxs(
3499
+ "button",
3500
+ {
3501
+ type: "button",
3502
+ onClick: () => openAddExpense(card.id),
3503
+ className: "flex w-full items-center justify-center gap-1.5 rounded-input border border-input bg-card px-3 py-2 text-xs font-medium text-foreground transition-colors hover:bg-muted/40",
3504
+ children: [
3505
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
3506
+ t2("financial.cards.addExpense")
3507
+ ]
3508
+ }
3509
+ )
2746
3510
  ] })
2747
- ] }),
2748
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
2749
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: formatCurrency(tx.installmentAmount, currency) }),
2750
- tx.status === "received" ? /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-success/15 px-2 py-0.5 text-[10px] font-medium text-success dark:bg-success/20", children: [
2751
- /* @__PURE__ */ jsx(Check, { className: "h-2.5 w-2.5" }),
2752
- " Received"
2753
- ] }) : /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-warning/15 px-2 py-0.5 text-[10px] font-medium text-warning dark:bg-warning/20", children: "Pending" })
2754
- ] })
2755
- ] }, tx.id)) })
2756
- ] }, brand))
3511
+ ] }, card.id);
3512
+ }) })
3513
+ ] }),
3514
+ /* @__PURE__ */ jsx(
3515
+ QuickTransactionForm,
3516
+ {
3517
+ open: addOpen,
3518
+ onOpenChange: setAddOpen,
3519
+ defaultType: "expense",
3520
+ defaultAccountId: addCardId
3521
+ }
3522
+ )
2757
3523
  ] });
2758
3524
  }
2759
3525
  function ReconciliationView() {
@@ -3086,11 +3852,40 @@ function FinancialPage({ config, provider, store, registries }) {
3086
3852
  return false;
3087
3853
  }
3088
3854
  });
3855
+ useEffect(() => {
3856
+ if (onboardingComplete) return;
3857
+ let cancelled = false;
3858
+ void provider.getMovements({ pageSize: 1 }).then((res) => {
3859
+ if (!cancelled && res.total > 0) setOnboardingComplete(true);
3860
+ }).catch(() => {
3861
+ });
3862
+ return () => {
3863
+ cancelled = true;
3864
+ };
3865
+ }, [onboardingComplete, provider]);
3089
3866
  const t2 = useTranslation();
3090
3867
  const intent = parseIntent(view);
3091
3868
  const isSettings = view === "settings";
3092
3869
  const nav = buildNav(config, view, navigate, t2);
3870
+ const [quickAddOpen, setQuickAddOpen] = useState(false);
3871
+ const [quickAddType, setQuickAddType] = useState("expense");
3872
+ const [quickAddReceipt, setQuickAddReceipt] = useState(false);
3873
+ const openQuickAdd2 = useCallback((type = "expense") => {
3874
+ setQuickAddType(type);
3875
+ setQuickAddReceipt(false);
3876
+ setQuickAddOpen(true);
3877
+ }, []);
3878
+ const pendingQuickAdd = usePendingQuickAdd();
3879
+ useEffect(() => {
3880
+ if (!pendingQuickAdd) return;
3881
+ const req = consumeQuickAdd();
3882
+ if (!req) return;
3883
+ setQuickAddType(req.type);
3884
+ setQuickAddReceipt(req.receipt);
3885
+ setQuickAddOpen(true);
3886
+ }, [pendingQuickAdd]);
3093
3887
  const quickActions = useMemo(() => {
3888
+ if (config.quickAdd) return [];
3094
3889
  const actions = [];
3095
3890
  if (config.modules.payables) {
3096
3891
  actions.push({
@@ -3120,7 +3915,7 @@ function FinancialPage({ config, provider, store, registries }) {
3120
3915
  });
3121
3916
  }
3122
3917
  return actions;
3123
- }, [config.modules]);
3918
+ }, [config.modules, config.quickAdd]);
3124
3919
  function handleOnboardingComplete() {
3125
3920
  setOnboardingComplete(true);
3126
3921
  try {
@@ -3164,7 +3959,7 @@ function FinancialPage({ config, provider, store, registries }) {
3164
3959
  return /* @__PURE__ */ jsx(SummaryView, {});
3165
3960
  }
3166
3961
  }
3167
- return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store, children: /* @__PURE__ */ jsx(
3962
+ return /* @__PURE__ */ jsx(FinancialContextProvider, { config, provider, store, children: /* @__PURE__ */ jsxs(
3168
3963
  ModulePage,
3169
3964
  {
3170
3965
  title: config.labels.pageTitle,
@@ -3173,15 +3968,44 @@ function FinancialPage({ config, provider, store, registries }) {
3173
3968
  showHeader: intent.view === "summary" || view === "summary",
3174
3969
  viewKey: view,
3175
3970
  direction,
3176
- headerAction: /* @__PURE__ */ jsx(
3177
- ModuleActionBar,
3178
- {
3179
- quickActions,
3180
- settingsPath: registries && registries.length > 0 ? "/settings/financial" : void 0,
3181
- settingsLabel: t2("financial.settingsPage.title")
3182
- }
3183
- ),
3184
- children: renderView()
3971
+ headerAction: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3972
+ config.quickAdd && // One-primary-action rule (FAY-1247): quick-add is the single primary
3973
+ // add affordance in B2C mode. The receipt entry point lives inside the
3974
+ // sheet's "Fotografar ou anexar recibo" attachment step, so no separate
3975
+ // "Enviar recibo" button is needed here.
3976
+ /* @__PURE__ */ jsxs(
3977
+ Button,
3978
+ {
3979
+ size: "sm",
3980
+ className: "hidden md:inline-flex",
3981
+ onClick: () => openQuickAdd2("expense"),
3982
+ children: [
3983
+ /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
3984
+ t2("financial.quickTx.newTransaction")
3985
+ ]
3986
+ }
3987
+ ),
3988
+ /* @__PURE__ */ jsx(
3989
+ ModuleActionBar,
3990
+ {
3991
+ quickActions,
3992
+ settingsPath: registries && registries.length > 0 ? "/settings/financial" : void 0,
3993
+ settingsLabel: t2("financial.settingsPage.title")
3994
+ }
3995
+ )
3996
+ ] }),
3997
+ children: [
3998
+ renderView(),
3999
+ /* @__PURE__ */ jsx(
4000
+ QuickTransactionForm,
4001
+ {
4002
+ open: quickAddOpen,
4003
+ onOpenChange: setQuickAddOpen,
4004
+ defaultType: quickAddType,
4005
+ focusReceipt: quickAddReceipt
4006
+ }
4007
+ )
4008
+ ]
3185
4009
  }
3186
4010
  ) });
3187
4011
  }
@@ -3328,6 +4152,202 @@ function RecentTransactions() {
3328
4152
  ];
3329
4153
  return /* @__PURE__ */ jsx(TableWidget, { title: t2("financial.summary.recentTransactions"), icon: "CircleDollarSign", columns, data: invoices.slice(0, 5), emptyMessage: t2("financial.summary.noTransactions") });
3330
4154
  }
4155
+ function useDueLabel() {
4156
+ const t2 = useTranslation();
4157
+ return (dueDate) => {
4158
+ const today2 = /* @__PURE__ */ new Date();
4159
+ today2.setHours(0, 0, 0, 0);
4160
+ const due = /* @__PURE__ */ new Date(`${dueDate}T00:00:00`);
4161
+ const days = Math.round((due.getTime() - today2.getTime()) / 864e5);
4162
+ if (days < 0) return t2("financial.home.overdueDays", { count: String(Math.abs(days)) });
4163
+ if (days === 0) return t2("financial.home.dueToday");
4164
+ if (days === 1) return t2("financial.home.dueTomorrow");
4165
+ return t2("financial.home.dueInDays", { count: String(days) });
4166
+ };
4167
+ }
4168
+ function BalanceHero() {
4169
+ const t2 = useTranslation();
4170
+ const { currency } = useFinancialConfig();
4171
+ const summary = useFinancialStore((s) => s.summary);
4172
+ const navigate = useDashboardNavigate();
4173
+ useEnsureSummary();
4174
+ const [hidden, setHidden] = React17__default.useState(false);
4175
+ const money = (v) => hidden ? "\u2022\u2022\u2022\u2022\u2022\u2022" : formatCurrency(v ?? 0, currency);
4176
+ const inflow = summary?.monthlyInflow ?? 0;
4177
+ const outflow = summary?.monthlyOutflow ?? 0;
4178
+ return /* @__PURE__ */ jsxs(GoldCard, { branded: true, className: "px-5 py-4 text-center md:px-8 md:py-5", children: [
4179
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-wide opacity-75", children: t2("financial.home.balance") }),
4180
+ /* @__PURE__ */ jsxs("div", { className: "mt-0.5 flex items-center justify-center gap-2.5", children: [
4181
+ /* @__PURE__ */ jsx("span", { className: "text-3xl font-bold tracking-tight tabular-nums md:text-4xl", children: money(summary?.totalBalance) }),
4182
+ /* @__PURE__ */ jsx(
4183
+ "button",
4184
+ {
4185
+ type: "button",
4186
+ onClick: () => setHidden((h) => !h),
4187
+ "aria-label": hidden ? t2("financial.home.showBalance") : t2("financial.home.hideBalance"),
4188
+ className: "rounded-full p-1.5 text-primary-foreground/80 transition-colors hover:bg-white/10",
4189
+ children: hidden ? /* @__PURE__ */ jsx(EyeOff, { className: "h-5 w-5" }) : /* @__PURE__ */ jsx(Eye, { className: "h-5 w-5" })
4190
+ }
4191
+ )
4192
+ ] }),
4193
+ /* @__PURE__ */ jsxs("div", { className: "mx-auto mt-3 grid max-w-lg grid-cols-2 gap-3", children: [
4194
+ /* @__PURE__ */ jsxs(
4195
+ "button",
4196
+ {
4197
+ type: "button",
4198
+ onClick: () => navigate("/financial/receivables-list"),
4199
+ "aria-label": t2("financial.home.inflow"),
4200
+ className: "flex cursor-pointer items-center gap-2 rounded-2xl bg-white/10 px-3 py-2 text-left transition-colors hover:bg-white/20",
4201
+ children: [
4202
+ /* @__PURE__ */ jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-emerald-500/90", children: /* @__PURE__ */ jsx(ArrowUpRight, { className: "h-4 w-4 text-white" }) }),
4203
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
4204
+ /* @__PURE__ */ jsx("span", { className: "block text-[11px] uppercase tracking-wide opacity-70", children: t2("financial.home.inflow") }),
4205
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-semibold tabular-nums", children: money(inflow) })
4206
+ ] })
4207
+ ]
4208
+ }
4209
+ ),
4210
+ /* @__PURE__ */ jsxs(
4211
+ "button",
4212
+ {
4213
+ type: "button",
4214
+ onClick: () => navigate("/financial/payables-list"),
4215
+ "aria-label": t2("financial.home.outflow"),
4216
+ className: "flex cursor-pointer items-center gap-2 rounded-2xl bg-white/10 px-3 py-2 text-left transition-colors hover:bg-white/20",
4217
+ children: [
4218
+ /* @__PURE__ */ jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-rose-500/90", children: /* @__PURE__ */ jsx(ArrowDownRight, { className: "h-4 w-4 text-white" }) }),
4219
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
4220
+ /* @__PURE__ */ jsx("span", { className: "block text-[11px] uppercase tracking-wide opacity-70", children: t2("financial.home.outflow") }),
4221
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-sm font-semibold tabular-nums", children: money(outflow) })
4222
+ ] })
4223
+ ]
4224
+ }
4225
+ )
4226
+ ] })
4227
+ ] });
4228
+ }
4229
+ var SPEND_CATEGORIES = [
4230
+ { key: "subscriptions", keywords: ["netflix", "spotify", "prime video", "disney", "hbo", " max", "youtube", "apple", "assinatura", "subscription"] },
4231
+ { key: "groceries", keywords: ["supermercado", "mercado", "padaria", "hortifruti", "feira", "a\xE7ougue", "grocery", "market"] },
4232
+ { key: "transport", keywords: ["uber", "99", "taxi", "posto", "shell", "ipiranga", "combust\xEDvel", "gasolina", "metr\xF4", "\xF4nibus", "transport", "fuel"] },
4233
+ { key: "dining", keywords: ["ifood", "rappi", "restaurante", "outback", "lanchonete", "pizzaria", "caf\xE9", "cafeteria", "mcdonald", "burger", "food"] },
4234
+ { key: "home", keywords: ["aluguel", "condom\xEDnio", "luz", "enel", "\xE1gua", "sabesp", "internet", "vivo", "claro", "g\xE1s", "iptu", "rent", "utilit"] },
4235
+ { key: "health", keywords: ["farm\xE1cia", "farmacia", "drogasil", "drogaria", "academia", "smartfit", "cl\xEDnica", "hospital", "m\xE9dico", "sa\xFAde", "gym", "fitness", "health", "pharmac"] },
4236
+ { key: "leisure", keywords: ["cinema", "cinemark", "ingresso", "show", "teatro", "viagem", "hotel", "steam", "leisure"] },
4237
+ { key: "shopping", keywords: ["amazon", "magalu", "shopee", "aliexpress", "loja", "shopping", "zara", "renner", "store"] }
4238
+ ];
4239
+ function categorizeSpend(notes) {
4240
+ const s = (notes ?? "").toLowerCase();
4241
+ for (const c of SPEND_CATEGORIES) if (c.keywords.some((k) => s.includes(k))) return c.key;
4242
+ return "other";
4243
+ }
4244
+ function SpendByCategoryChart() {
4245
+ const t2 = useTranslation();
4246
+ const provider = useFinancialProvider();
4247
+ const [rows, setRows] = React17__default.useState([]);
4248
+ useEffect(() => {
4249
+ let alive = true;
4250
+ void (async () => {
4251
+ const res = await provider.getMovements({ status: "paid", direction: "debit" });
4252
+ if (!alive) return;
4253
+ const totals = /* @__PURE__ */ new Map();
4254
+ for (const m of res.data) {
4255
+ if (m.movementKind === "transfer") continue;
4256
+ const key = categorizeSpend(m.notes);
4257
+ totals.set(key, (totals.get(key) ?? 0) + (m.paidAmount || m.amount));
4258
+ }
4259
+ setRows(
4260
+ [...totals.entries()].sort((a, b) => b[1] - a[1]).map(([key, value]) => ({ name: t2(`financial.home.category.${key}`), value: Math.round(value * 100) / 100 }))
4261
+ );
4262
+ })();
4263
+ return () => {
4264
+ alive = false;
4265
+ };
4266
+ }, [provider]);
4267
+ if (rows.length === 0) {
4268
+ return /* @__PURE__ */ jsxs(Card, { children: [
4269
+ /* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { children: t2("financial.home.spendByCategory") }) }),
4270
+ /* @__PURE__ */ jsx(CardContent, { className: "flex h-[200px] items-center justify-center text-sm text-muted-foreground", children: t2("financial.home.noSpend") })
4271
+ ] });
4272
+ }
4273
+ return /* @__PURE__ */ jsx(
4274
+ ChartWidget,
4275
+ {
4276
+ type: "pie",
4277
+ title: t2("financial.home.spendByCategory"),
4278
+ icon: "PieChart",
4279
+ categoryKey: "name",
4280
+ data: rows,
4281
+ series: [{ dataKey: "value", label: t2("financial.home.spendByCategory") }]
4282
+ }
4283
+ );
4284
+ }
4285
+ function UpcomingBills() {
4286
+ const t2 = useTranslation();
4287
+ const { currency } = useFinancialConfig();
4288
+ const provider = useFinancialProvider();
4289
+ const dueLabel = useDueLabel();
4290
+ const [bills, setBills] = React17__default.useState([]);
4291
+ useEffect(() => {
4292
+ let alive = true;
4293
+ void (async () => {
4294
+ const pending2 = await provider.getMovements({ status: "pending" });
4295
+ if (!alive) return;
4296
+ setBills(
4297
+ pending2.data.filter((m) => m.direction === "debit" && m.movementKind === "bill").sort((a, b) => a.dueDate.localeCompare(b.dueDate))
4298
+ );
4299
+ })();
4300
+ return () => {
4301
+ alive = false;
4302
+ };
4303
+ }, [provider]);
4304
+ return /* @__PURE__ */ jsxs(GoldCard, { className: "p-0 overflow-hidden", children: [
4305
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-4 pt-4", children: /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold text-foreground", children: t2("financial.home.upcomingBills") }) }),
4306
+ /* @__PURE__ */ jsx("div", { className: "mt-3", children: bills.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-4 py-6 text-center text-xs text-muted-foreground", children: t2("financial.home.noBills") }) : bills.map((bill, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3.5" + (i > 0 ? " border-t border-border" : ""), children: [
4307
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
4308
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium text-foreground", children: bill.notes ?? t2("financial.home.bill") }),
4309
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: dueLabel(bill.dueDate) })
4310
+ ] }),
4311
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold tabular-nums text-foreground", children: formatCurrency(bill.amount - bill.paidAmount, currency) })
4312
+ ] }, bill.id)) })
4313
+ ] });
4314
+ }
4315
+ function CreditCards() {
4316
+ const t2 = useTranslation();
4317
+ const { currency } = useFinancialConfig();
4318
+ const bankAccounts = useFinancialStore((s) => s.bankAccounts);
4319
+ const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
4320
+ useEffect(() => {
4321
+ void fetchBankAccounts();
4322
+ }, []);
4323
+ const cards = bankAccounts.filter((a) => a.accountType === "credit_card");
4324
+ return /* @__PURE__ */ jsxs(GoldCard, { className: "bg-transparent border-0 p-0 shadow-none", children: [
4325
+ /* @__PURE__ */ jsx("h2", { className: "mb-3 text-sm font-semibold text-foreground", children: t2("financial.home.cards") }),
4326
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
4327
+ cards.map((card) => {
4328
+ const fatura = Math.abs(Math.min(0, card.currentBalance));
4329
+ const limite = Math.max(0, (card.creditLimit ?? 0) - fatura);
4330
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-2xl bg-gradient-to-br from-violet-600 to-indigo-700 p-4 text-white shadow-md", children: [
4331
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
4332
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: card.bankName ?? card.name }),
4333
+ /* @__PURE__ */ jsx(CreditCard, { className: "h-5 w-5 opacity-80" })
4334
+ ] }),
4335
+ /* @__PURE__ */ jsx("p", { className: "mt-6 text-[11px] uppercase tracking-wide opacity-70", children: t2("financial.home.currentBill") }),
4336
+ /* @__PURE__ */ jsx("p", { className: "text-xl font-bold tabular-nums", children: formatCurrency(fatura, currency) }),
4337
+ /* @__PURE__ */ jsxs("p", { className: "mt-2 text-[11px] opacity-70", children: [
4338
+ t2("financial.home.availableLimit"),
4339
+ " ",
4340
+ formatCurrency(limite, currency)
4341
+ ] })
4342
+ ] }, card.id);
4343
+ }),
4344
+ cards.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center rounded-2xl border border-dashed border-border py-6 text-sm font-medium text-muted-foreground", children: [
4345
+ "+ ",
4346
+ t2("financial.home.addCard")
4347
+ ] }) : null
4348
+ ] })
4349
+ ] });
4350
+ }
3331
4351
  function createFinancialDashboardWidgets(ctx2) {
3332
4352
  const withCtx = (Inner) => {
3333
4353
  const Wrapped = () => /* @__PURE__ */ jsx(FinancialContextProvider, { config: ctx2.config, provider: ctx2.provider, store: ctx2.store, children: /* @__PURE__ */ jsx(Inner, {}) });
@@ -3344,7 +4364,19 @@ function createFinancialDashboardWidgets(ctx2) {
3344
4364
  defineChartWidget({ id: "financial.chart.cash-flow", title: "financial.summary.cashFlow", domain: "financial", span: 2, defaultOrder: 10, surfaces: ["plugin-home"], component: withCtx(CashFlowChart) }),
3345
4365
  defineCustomWidget({ id: "financial.panel.breakdown", title: "financial.summary.cashFlowBreakdown", domain: "financial", span: 1, defaultOrder: 11, surfaces: ["plugin-home"], component: withCtx(CashFlowBreakdown) }),
3346
4366
  defineCustomWidget({ id: "financial.panel.overdue", title: "financial.summary.overdueTitle", domain: "financial", span: 1, defaultOrder: 12, surfaces: ["plugin-home"], component: withCtx(OverdueAlerts) }),
3347
- defineTableWidget({ id: "financial.table.recent", title: "financial.summary.recentTransactions", domain: "financial", span: 4, defaultOrder: 20, surfaces: ["plugin-home"], component: withCtx(RecentTransactions) })
4367
+ defineTableWidget({ id: "financial.table.recent", title: "financial.summary.recentTransactions", domain: "financial", span: 4, defaultOrder: 20, surfaces: ["plugin-home"], component: withCtx(RecentTransactions) }),
4368
+ // FINANCE-HOME surface (B2C phone-first dashboard) — a compact analytical
4369
+ // layout: hero band → charts row (spend-by-category + cash-flow) → bills +
4370
+ // cards. This is a dedicated consumer surface (norman-ai's money screen), NOT
4371
+ // the universal B2B 'home': a full-bleed balance hero and credit-card tiles
4372
+ // are personal-finance chrome that would leak into every app that merely
4373
+ // enables plugin-financial (e.g. the beauty-saas Painel). Apps that want them
4374
+ // opt in explicitly by rendering <DashboardCanvas surface="finance-home">.
4375
+ defineCustomWidget({ id: "financial.hero.balance", title: "financial.home.balance", domain: "financial", span: 4, defaultOrder: 0, surfaces: ["finance-home"], component: withCtx(BalanceHero) }),
4376
+ defineChartWidget({ id: "financial.chart.spend-by-category", title: "financial.home.spendByCategory", domain: "financial", span: 2, defaultOrder: 1, surfaces: ["finance-home"], component: withCtx(SpendByCategoryChart) }),
4377
+ defineChartWidget({ id: "financial.chart.cash-flow-home", title: "financial.summary.cashFlow", domain: "financial", span: 2, defaultOrder: 2, surfaces: ["finance-home"], component: withCtx(CashFlowChart) }),
4378
+ defineCustomWidget({ id: "financial.list.upcoming-bills", title: "financial.home.upcomingBills", domain: "financial", span: 2, defaultOrder: 3, surfaces: ["finance-home"], component: withCtx(UpcomingBills) }),
4379
+ defineCustomWidget({ id: "financial.cards", title: "financial.home.cards", domain: "financial", span: 2, defaultOrder: 4, surfaces: ["finance-home"], component: withCtx(CreditCards) })
3348
4380
  ];
3349
4381
  }
3350
4382
 
@@ -3377,9 +4409,10 @@ function paginate(items, page, pageSize) {
3377
4409
  const start = (p - 1) * ps;
3378
4410
  return { data: items.slice(start, start + ps), total: items.length };
3379
4411
  }
3380
- function createStore(seedPaymentMethodTypes) {
4412
+ function createStore(options) {
3381
4413
  const tenantId = "mock-tenant";
3382
- const defaultPMTypes = ([
4414
+ const seed = options?.seed;
4415
+ const defaultPMTypes = (options?.paymentMethodTypes ?? [
3383
4416
  { name: "Cash", transactionType: "cash" },
3384
4417
  { name: "Credit Card", transactionType: "credit_card" },
3385
4418
  { name: "Debit Card", transactionType: "debit_card" },
@@ -3417,19 +4450,23 @@ function createStore(seedPaymentMethodTypes) {
3417
4450
  updatedAt: now()
3418
4451
  };
3419
4452
  return {
3420
- invoices: [],
3421
- invoiceItems: [],
3422
- movements: [],
3423
- bankAccounts: [cashAccount, bankAccount],
4453
+ // Seeded data (when provided) makes the module render as if the user already
4454
+ // has accounts + transactions — a populated Summary/Statements instead of the
4455
+ // empty onboarding state. Seed arrays are copied so the caller's data is not mutated.
4456
+ invoices: seed?.invoices ? [...seed.invoices] : [],
4457
+ invoiceItems: seed?.invoiceItems ? [...seed.invoiceItems] : [],
4458
+ movements: seed?.movements ? [...seed.movements] : [],
4459
+ bankAccounts: seed?.bankAccounts ? [...seed.bankAccounts] : [cashAccount, bankAccount],
3424
4460
  cashSessions: [],
3425
- paymentMethods: [],
4461
+ paymentMethods: seed?.paymentMethods ? [...seed.paymentMethods] : [],
3426
4462
  paymentMethodTypes: defaultPMTypes,
3427
4463
  chartOfAccounts: [],
4464
+ costCenters: [],
3428
4465
  cardTransactions: []
3429
4466
  };
3430
4467
  }
3431
4468
  function createMockFinancialProvider(options) {
3432
- const store = createStore();
4469
+ const store = createStore(options);
3433
4470
  const tenantId = "mock-tenant";
3434
4471
  function recalcInvoiceStatus(invoice) {
3435
4472
  if (invoice.paidAmount >= invoice.totalAmount) {
@@ -3450,6 +4487,14 @@ function createMockFinancialProvider(options) {
3450
4487
  if (query.direction) results = results.filter((i) => i.direction === query.direction);
3451
4488
  if (query.status) results = results.filter((i) => matchesStatus(i.status, query.status));
3452
4489
  if (query.contactId) results = results.filter((i) => i.contactId === query.contactId);
4490
+ if (query.accountId) {
4491
+ const invoiceIds = new Set(store.invoiceItems.filter((item) => item.accountId === query.accountId).map((item) => item.invoiceId));
4492
+ results = results.filter((invoice) => invoiceIds.has(invoice.id));
4493
+ }
4494
+ if (query.costCenterId) {
4495
+ const invoiceIds = new Set(store.invoiceItems.filter((item) => item.costCenterId === query.costCenterId).map((item) => item.invoiceId));
4496
+ results = results.filter((invoice) => invoiceIds.has(invoice.id));
4497
+ }
3453
4498
  if (query.dateRange) results = results.filter((i) => matchesDateRange(i.invoiceDate, query.dateRange));
3454
4499
  if (query.search) {
3455
4500
  const s = query.search.toLowerCase();
@@ -3772,6 +4817,9 @@ function createMockFinancialProvider(options) {
3772
4817
  async getChartOfAccounts() {
3773
4818
  return store.chartOfAccounts.filter((n) => n.isActive);
3774
4819
  },
4820
+ async getCostCenters() {
4821
+ return store.costCenters.filter((c) => c.isActive);
4822
+ },
3775
4823
  // --- Card Transactions ---
3776
4824
  async getCardTransactions(dateRange) {
3777
4825
  let results = [...store.cardTransactions];
@@ -3859,7 +4907,9 @@ function createMockFinancialProvider(options) {
3859
4907
  (m) => m.direction === "debit" && m.movementKind === "bill" && ["pending", "partial"].includes(m.status)
3860
4908
  );
3861
4909
  const todayStr = today();
3862
- const monthStart = todayStr.slice(0, 7) + "-01";
4910
+ const monthAgo = /* @__PURE__ */ new Date();
4911
+ monthAgo.setDate(monthAgo.getDate() - 30);
4912
+ const monthStart = monthAgo.toISOString().slice(0, 10);
3863
4913
  const monthEnd = todayStr;
3864
4914
  const monthMovements = store.movements.filter(
3865
4915
  (m) => m.movementKind === "payment" && matchesDateRange(m.paymentDate, dateRange ?? { from: monthStart, to: monthEnd })
@@ -3966,6 +5016,18 @@ function createMockFinancialProvider(options) {
3966
5016
  return provider;
3967
5017
  }
3968
5018
 
5019
+ // src/data/tables.ts
5020
+ var T = {
5021
+ movements: "plg_financial_movements",
5022
+ chartOfAccounts: "plg_financial_chart_of_accounts",
5023
+ bankAccounts: "plg_financial_bank_accounts",
5024
+ cardBrands: "plg_financial_card_brands",
5025
+ costCenters: "plg_financial_cost_centers",
5026
+ cashRegisterSessions: "plg_financial_cash_register_sessions",
5027
+ paymentMethods: "plg_financial_payment_methods",
5028
+ paymentMethodTypes: "plg_financial_payment_method_types"
5029
+ };
5030
+
3969
5031
  // src/data/supabase.ts
3970
5032
  function getTenantId() {
3971
5033
  return getActiveTenantId();
@@ -3988,7 +5050,7 @@ function camelToSnake(obj) {
3988
5050
  function getClients() {
3989
5051
  const supabase = getSupabaseClientOptional();
3990
5052
  if (!supabase) throw new Error("Supabase not initialized");
3991
- return { core: supabase.schema("saas_core"), pub: supabase };
5053
+ return { core: supabase, pub: supabase };
3992
5054
  }
3993
5055
  function mapStageToInvoiceStatus(stage) {
3994
5056
  switch (stage) {
@@ -4015,11 +5077,11 @@ async function sweepOverdue() {
4015
5077
  try {
4016
5078
  const { core, pub } = getClients();
4017
5079
  const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4018
- const { data: overdueMovs } = await pub.from("financial_movements").update({ status: "overdue" }).in("status", ["pending", "partial"]).lt("due_date", today2).select("invoice_id");
5080
+ const { data: overdueMovs } = await pub.from(T.movements).update({ status: "overdue" }).in("status", ["pending", "partial"]).lt("due_date", today2).select("invoice_id");
4019
5081
  if (overdueMovs && overdueMovs.length > 0) {
4020
5082
  const invoiceIds = [...new Set(overdueMovs.map((m) => m.invoice_id).filter(Boolean))];
4021
5083
  for (const invId of invoiceIds) {
4022
- const { data: allMovs } = await pub.from("financial_movements").select("status").eq("invoice_id", invId).eq("movement_kind", "bill");
5084
+ const { data: allMovs } = await pub.from(T.movements).select("status").eq("invoice_id", invId).eq("movement_kind", "bill");
4023
5085
  const movs = allMovs ?? [];
4024
5086
  const hasOverdue = movs.some((m) => m.status === "overdue");
4025
5087
  const allPaid = movs.every((m) => m.status === "paid");
@@ -4043,9 +5105,21 @@ async function fetchInvoiceBalances(pub, ids) {
4043
5105
  function round22(n) {
4044
5106
  return Math.round(n * 100) / 100;
4045
5107
  }
5108
+ async function findOrderIdsByItemMetadata(core, filters) {
5109
+ if (!filters.accountId && !filters.costCenterId) return void 0;
5110
+ let qb = core.from("order_items").select("order_id");
5111
+ if (filters.accountId) qb = qb.filter("metadata->>accountId", "eq", filters.accountId);
5112
+ if (filters.costCenterId) qb = qb.filter("metadata->>costCenterId", "eq", filters.costCenterId);
5113
+ const { data } = await qb;
5114
+ const ids = [];
5115
+ for (const row of data ?? []) {
5116
+ if (typeof row.order_id === "string" && row.order_id.length > 0) ids.push(row.order_id);
5117
+ }
5118
+ return Array.from(new Set(ids));
5119
+ }
4046
5120
  async function getMethodFeePct(pub, paymentMethodId) {
4047
5121
  if (!paymentMethodId) return 0;
4048
- const { data } = await pub.from("payment_methods").select("discount_value").eq("id", paymentMethodId).single();
5122
+ const { data } = await pub.from(T.paymentMethods).select("discount_value").eq("id", paymentMethodId).single();
4049
5123
  return Number(data?.discount_value) || 0;
4050
5124
  }
4051
5125
  function genId() {
@@ -4070,6 +5144,14 @@ function createSupabaseFinancialProvider() {
4070
5144
  const statuses = Array.isArray(query.status) ? query.status : [query.status];
4071
5145
  qb = qb.in("status", statuses);
4072
5146
  }
5147
+ const itemScopedOrderIds = await findOrderIdsByItemMetadata(core, {
5148
+ accountId: query.accountId,
5149
+ costCenterId: query.costCenterId
5150
+ });
5151
+ if (itemScopedOrderIds) {
5152
+ if (itemScopedOrderIds.length === 0) return { data: [], total: 0 };
5153
+ qb = qb.in("id", itemScopedOrderIds);
5154
+ }
4073
5155
  if (query.contactId) qb = qb.eq("party_id", query.contactId);
4074
5156
  if (query.search) qb = qb.ilike("notes", `%${query.search}%`);
4075
5157
  if (query.dateRange) qb = qb.gte("created_at", query.dateRange.from).lte("created_at", query.dateRange.to);
@@ -4109,7 +5191,7 @@ function createSupabaseFinancialProvider() {
4109
5191
  const o = snakeToCamel(data);
4110
5192
  const meta = data.metadata ?? {};
4111
5193
  const balance = (await fetchInvoiceBalances(pub, [id])).get(id);
4112
- const { data: movs } = await pub.from("financial_movements").select("paid_amount").eq("invoice_id", id).eq("movement_kind", "bill");
5194
+ const { data: movs } = await pub.from(T.movements).select("paid_amount").eq("invoice_id", id).eq("movement_kind", "bill");
4113
5195
  return {
4114
5196
  id: o.id,
4115
5197
  direction: o.direction ?? (o.kind === "invoice_payable" ? "debit" : "credit"),
@@ -4145,6 +5227,8 @@ function createSupabaseFinancialProvider() {
4145
5227
  discount: i.discount ?? 0,
4146
5228
  surcharge: 0,
4147
5229
  referenceId: i.productId ?? i.serviceId,
5230
+ accountId: i.metadata?.accountId,
5231
+ costCenterId: i.metadata?.costCenterId,
4148
5232
  createdAt: i.createdAt
4149
5233
  };
4150
5234
  });
@@ -4184,12 +5268,17 @@ function createSupabaseFinancialProvider() {
4184
5268
  discount: item.discount ?? 0,
4185
5269
  total: item.quantity * item.unitPrice - (item.discount ?? 0) + (item.surcharge ?? 0),
4186
5270
  sort_order: i,
4187
- metadata: { itemKind: item.itemKind, referenceId: item.referenceId }
5271
+ metadata: {
5272
+ itemKind: item.itemKind,
5273
+ referenceId: item.referenceId,
5274
+ accountId: item.accountId,
5275
+ costCenterId: item.costCenterId
5276
+ }
4188
5277
  }))
4189
5278
  );
4190
5279
  }
4191
5280
  if (order && input.installments.length > 0) {
4192
- await pub.from("financial_movements").insert(
5281
+ await pub.from(T.movements).insert(
4193
5282
  input.installments.map((inst, i) => ({
4194
5283
  tenant_id: tenantId,
4195
5284
  invoice_id: order.id,
@@ -4231,12 +5320,12 @@ function createSupabaseFinancialProvider() {
4231
5320
  async cancelInvoice(id) {
4232
5321
  const { core, pub } = getClients();
4233
5322
  await core.from("orders").update({ status: "cancelled" }).eq("id", id);
4234
- await pub.from("financial_movements").update({ status: "cancelled" }).eq("invoice_id", id).eq("status", "pending");
5323
+ await pub.from(T.movements).update({ status: "cancelled" }).eq("invoice_id", id).eq("status", "pending");
4235
5324
  },
4236
5325
  // --- Movements (public.financial_movements) ---
4237
5326
  async getMovements(query) {
4238
5327
  const { pub } = getClients();
4239
- let qb = pub.from("financial_movements").select("*", { count: "exact" });
5328
+ let qb = pub.from(T.movements).select("*", { count: "exact" });
4240
5329
  if (query.direction) qb = qb.eq("direction", query.direction);
4241
5330
  if (query.status) {
4242
5331
  const statuses = Array.isArray(query.status) ? query.status : [query.status];
@@ -4253,7 +5342,7 @@ function createSupabaseFinancialProvider() {
4253
5342
  },
4254
5343
  async payMovement(input) {
4255
5344
  const { core, pub } = getClients();
4256
- const { data: mov } = await pub.from("financial_movements").select("*").eq("id", input.movementId).single();
5345
+ const { data: mov } = await pub.from(T.movements).select("*").eq("id", input.movementId).single();
4257
5346
  if (!mov) throw new Error("Movement not found");
4258
5347
  const tenantId = mov.tenant_id ?? getTenantId();
4259
5348
  const feePct = mov.direction === "credit" ? await getMethodFeePct(pub, input.paymentMethodId) : 0;
@@ -4278,17 +5367,17 @@ function createSupabaseFinancialProvider() {
4278
5367
  card_installments: input.cardInstallments ?? null,
4279
5368
  notes: input.notes ?? null
4280
5369
  };
4281
- let { data, error } = await pub.from("financial_movements").insert(paymentRow).select().single();
5370
+ let { data, error } = await pub.from(T.movements).insert(paymentRow).select().single();
4282
5371
  if (error && /fee_amount/i.test(error.message ?? "")) {
4283
5372
  const { fee_amount, ...rest } = paymentRow;
4284
- ({ data, error } = await pub.from("financial_movements").insert(rest).select().single());
5373
+ ({ data, error } = await pub.from(T.movements).insert(rest).select().single());
4285
5374
  }
4286
5375
  if (error) throw error;
4287
5376
  const newPaid = round22((mov.paid_amount ?? 0) + input.amount);
4288
5377
  const billStatus = newPaid >= mov.amount ? "paid" : "partial";
4289
- await pub.from("financial_movements").update({ paid_amount: newPaid, status: billStatus, payment_date: null, bank_account_id: null }).eq("id", mov.id);
5378
+ await pub.from(T.movements).update({ paid_amount: newPaid, status: billStatus, payment_date: null, bank_account_id: null }).eq("id", mov.id);
4290
5379
  if (mov.invoice_id) {
4291
- const { data: allMovs } = await pub.from("financial_movements").select("status, amount, paid_amount").eq("invoice_id", mov.invoice_id).eq("movement_kind", "bill");
5380
+ const { data: allMovs } = await pub.from(T.movements).select("status, amount, paid_amount").eq("invoice_id", mov.invoice_id).eq("movement_kind", "bill");
4292
5381
  const movs = allMovs ?? [];
4293
5382
  const allPaid = movs.length > 0 && movs.every((m) => m.status === "paid");
4294
5383
  const anyPaid = movs.some((m) => m.status === "paid" || m.status === "partial");
@@ -4305,7 +5394,7 @@ function createSupabaseFinancialProvider() {
4305
5394
  },
4306
5395
  async cancelMovement(id) {
4307
5396
  const { pub } = getClients();
4308
- await pub.from("financial_movements").update({ status: "cancelled" }).eq("id", id);
5397
+ await pub.from(T.movements).update({ status: "cancelled" }).eq("id", id);
4309
5398
  },
4310
5399
  // --- Transfers (between accounts) ---
4311
5400
  async createTransfer(input) {
@@ -4341,10 +5430,10 @@ function createSupabaseFinancialProvider() {
4341
5430
  metadata: { transferId, transferRole: "in", counterAccountId: input.fromAccountId }
4342
5431
  }
4343
5432
  ];
4344
- let { data, error } = await pub.from("financial_movements").insert(rowsToInsert).select("id, direction");
5433
+ let { data, error } = await pub.from(T.movements).insert(rowsToInsert).select("id, direction");
4345
5434
  if (error && /fee_amount/i.test(error.message ?? "")) {
4346
5435
  const stripped = rowsToInsert.map(({ fee_amount, ...rest }) => rest);
4347
- ({ data, error } = await pub.from("financial_movements").insert(stripped).select("id, direction"));
5436
+ ({ data, error } = await pub.from(T.movements).insert(stripped).select("id, direction"));
4348
5437
  }
4349
5438
  if (error) throw error;
4350
5439
  const rows = data ?? [];
@@ -4355,13 +5444,13 @@ function createSupabaseFinancialProvider() {
4355
5444
  // --- Bank Accounts ---
4356
5445
  async getBankAccounts() {
4357
5446
  const { pub } = getClients();
4358
- const { data } = await pub.from("bank_accounts").select("*").eq("is_active", true).order("name");
5447
+ const { data } = await pub.from(T.bankAccounts).select("*").eq("is_active", true).order("name");
4359
5448
  return (data ?? []).map((r) => snakeToCamel(r));
4360
5449
  },
4361
5450
  async createBankAccount(input) {
4362
5451
  const { pub } = getClients();
4363
5452
  const tenantId = getTenantId();
4364
- const { data } = await pub.from("bank_accounts").insert({
5453
+ const { data } = await pub.from(T.bankAccounts).insert({
4365
5454
  ...camelToSnake(input),
4366
5455
  tenant_id: tenantId,
4367
5456
  current_balance: input.initialBalance ?? 0
@@ -4373,26 +5462,26 @@ function createSupabaseFinancialProvider() {
4373
5462
  const row = camelToSnake(partial);
4374
5463
  delete row.id;
4375
5464
  delete row.tenant_id;
4376
- const { data } = await pub.from("bank_accounts").update(row).eq("id", id).select().single();
5465
+ const { data } = await pub.from(T.bankAccounts).update(row).eq("id", id).select().single();
4377
5466
  return snakeToCamel(data);
4378
5467
  },
4379
5468
  // --- Cash Sessions ---
4380
5469
  async getCashSessions(bankAccountId) {
4381
5470
  const { pub } = getClients();
4382
- let qb = pub.from("cash_register_sessions").select("*");
5471
+ let qb = pub.from(T.cashRegisterSessions).select("*");
4383
5472
  if (bankAccountId) qb = qb.eq("bank_account_id", bankAccountId);
4384
5473
  const { data } = await qb.order("opened_at", { ascending: false });
4385
5474
  return (data ?? []).map((r) => snakeToCamel(r));
4386
5475
  },
4387
5476
  async getOpenSession(bankAccountId) {
4388
5477
  const { pub } = getClients();
4389
- const { data } = await pub.from("cash_register_sessions").select("*").eq("bank_account_id", bankAccountId).eq("status", "open").single();
5478
+ const { data } = await pub.from(T.cashRegisterSessions).select("*").eq("bank_account_id", bankAccountId).eq("status", "open").single();
4390
5479
  return data ? snakeToCamel(data) : null;
4391
5480
  },
4392
5481
  async openCashSession(input) {
4393
5482
  const { pub } = getClients();
4394
5483
  const tenantId = getTenantId();
4395
- const { data } = await pub.from("cash_register_sessions").insert({
5484
+ const { data } = await pub.from(T.cashRegisterSessions).insert({
4396
5485
  tenant_id: tenantId,
4397
5486
  bank_account_id: input.bankAccountId,
4398
5487
  status: "open",
@@ -4405,7 +5494,7 @@ function createSupabaseFinancialProvider() {
4405
5494
  },
4406
5495
  async closeCashSession(input) {
4407
5496
  const { pub } = getClients();
4408
- const { data } = await pub.from("cash_register_sessions").update({
5497
+ const { data } = await pub.from(T.cashRegisterSessions).update({
4409
5498
  status: "closed",
4410
5499
  closing_balance: input.closingBalance,
4411
5500
  closed_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -4417,8 +5506,8 @@ function createSupabaseFinancialProvider() {
4417
5506
  },
4418
5507
  async getCashSessionSummary(sessionId) {
4419
5508
  const { pub } = getClients();
4420
- const session = (await pub.from("cash_register_sessions").select("*").eq("id", sessionId).single()).data;
4421
- const { data: movs } = await pub.from("financial_movements").select("direction, paid_amount").eq("cash_session_id", sessionId).eq("status", "paid");
5509
+ const session = (await pub.from(T.cashRegisterSessions).select("*").eq("id", sessionId).single()).data;
5510
+ const { data: movs } = await pub.from(T.movements).select("direction, paid_amount").eq("cash_session_id", sessionId).eq("status", "paid");
4422
5511
  const movements = movs ?? [];
4423
5512
  return {
4424
5513
  session: snakeToCamel(session),
@@ -4430,18 +5519,23 @@ function createSupabaseFinancialProvider() {
4430
5519
  // --- Payment Methods ---
4431
5520
  async getPaymentMethods() {
4432
5521
  const { pub } = getClients();
4433
- const { data } = await pub.from("payment_methods").select("*").eq("is_active", true).order("name");
5522
+ const { data } = await pub.from(T.paymentMethods).select("*").eq("is_active", true).order("name");
4434
5523
  return (data ?? []).map((r) => snakeToCamel(r));
4435
5524
  },
4436
5525
  async getPaymentMethodTypes() {
4437
5526
  const { pub } = getClients();
4438
- const { data } = await pub.from("payment_method_types").select("*").eq("is_active", true).order("name");
5527
+ const { data } = await pub.from(T.paymentMethodTypes).select("*").eq("is_active", true).order("name");
4439
5528
  return (data ?? []).map((r) => snakeToCamel(r));
4440
5529
  },
4441
5530
  // --- Chart of Accounts ---
4442
5531
  async getChartOfAccounts() {
4443
5532
  const { pub } = getClients();
4444
- const { data } = await pub.from("chart_of_accounts").select("*").eq("is_active", true).order("code");
5533
+ const { data } = await pub.from(T.chartOfAccounts).select("*").eq("is_active", true).order("code");
5534
+ return (data ?? []).map((r) => snakeToCamel(r));
5535
+ },
5536
+ async getCostCenters() {
5537
+ const { pub } = getClients();
5538
+ const { data } = await pub.from(T.costCenters).select("*").eq("is_active", true).order("code");
4445
5539
  return (data ?? []).map((r) => snakeToCamel(r));
4446
5540
  },
4447
5541
  // --- Card Transactions ---
@@ -4453,7 +5547,7 @@ function createSupabaseFinancialProvider() {
4453
5547
  async getStatement(query) {
4454
5548
  const { core, pub } = getClients();
4455
5549
  const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4456
- const { data: acctRows } = await pub.from("bank_accounts").select("id, name, initial_balance, is_active");
5550
+ const { data: acctRows } = await pub.from(T.bankAccounts).select("id, name, initial_balance, is_active");
4457
5551
  const allAccounts = acctRows ?? [];
4458
5552
  const consolidated = !query.bankAccountId;
4459
5553
  const scopeAccounts = consolidated ? allAccounts.filter((a) => a.is_active) : allAccounts.filter((a) => a.id === query.bankAccountId);
@@ -4465,10 +5559,10 @@ function createSupabaseFinancialProvider() {
4465
5559
  }
4466
5560
  const scopeFilter = (qb) => consolidated ? qb : qb.in("bank_account_id", accountIds);
4467
5561
  const netOf = (r) => r.direction === "credit" ? (Number(r.paid_amount) || 0) - (Number(r.fee_amount) || 0) : -(Number(r.paid_amount) || 0);
4468
- const { data: priorRows } = await scopeFilter(pub.from("financial_movements").select("*").neq("status", "cancelled").lt("payment_date", query.dateRange.from));
5562
+ const { data: priorRows } = await scopeFilter(pub.from(T.movements).select("*").neq("status", "cancelled").lt("payment_date", query.dateRange.from));
4469
5563
  const priorNet = (priorRows ?? []).reduce((s, r) => s + netOf(r), 0);
4470
5564
  const openingBalance = round22(initialSum + priorNet);
4471
- const { data: movs } = await scopeFilter(pub.from("financial_movements").select("*").neq("status", "cancelled").gte("payment_date", query.dateRange.from).lte("payment_date", query.dateRange.to).order("payment_date").order("created_at"));
5565
+ const { data: movs } = await scopeFilter(pub.from(T.movements).select("*").neq("status", "cancelled").gte("payment_date", query.dateRange.from).lte("payment_date", query.dateRange.to).order("payment_date").order("created_at"));
4472
5566
  const invoiceIds = [...new Set((movs ?? []).map((r) => r.invoice_id).filter(Boolean))];
4473
5567
  const invoiceById = /* @__PURE__ */ new Map();
4474
5568
  if (invoiceIds.length) {
@@ -4510,7 +5604,7 @@ function createSupabaseFinancialProvider() {
4510
5604
  });
4511
5605
  const matchedIds = [...new Set(entries.map((e) => e.movement.matchedMovementId).filter(Boolean))];
4512
5606
  if (matchedIds.length) {
4513
- const { data: matched } = await pub.from("financial_movements").select("id, invoice_id, notes").in("id", matchedIds);
5607
+ const { data: matched } = await pub.from(T.movements).select("id, invoice_id, notes").in("id", matchedIds);
4514
5608
  const matchedById = new Map((matched ?? []).map((m) => [m.id, m]));
4515
5609
  const matchedInvoiceIds = [...new Set((matched ?? []).map((m) => m.invoice_id).filter(Boolean))];
4516
5610
  const invById = /* @__PURE__ */ new Map();
@@ -4532,7 +5626,7 @@ function createSupabaseFinancialProvider() {
4532
5626
  };
4533
5627
  }
4534
5628
  }
4535
- const { data: todayRows } = await scopeFilter(pub.from("financial_movements").select("*").neq("status", "cancelled").lte("payment_date", today2));
5629
+ const { data: todayRows } = await scopeFilter(pub.from(T.movements).select("*").neq("status", "cancelled").lte("payment_date", today2));
4536
5630
  const accountCurrentBalance = round22(initialSum + (todayRows ?? []).reduce((s, r) => s + netOf(r), 0));
4537
5631
  return {
4538
5632
  entries,
@@ -4550,9 +5644,9 @@ function createSupabaseFinancialProvider() {
4550
5644
  async getSummary(dateRange) {
4551
5645
  sweepOverdue();
4552
5646
  const { pub } = getClients();
4553
- const { data: accounts } = await pub.from("bank_accounts").select("current_balance").eq("is_active", true);
5647
+ const { data: accounts } = await pub.from(T.bankAccounts).select("current_balance").eq("is_active", true);
4554
5648
  const totalBalance = (accounts ?? []).reduce((s, a) => s + (a.current_balance ?? 0), 0);
4555
- const { data: movs } = await pub.from("financial_movements").select("direction, movement_kind, amount, paid_amount, status, due_date, payment_date");
5649
+ const { data: movs } = await pub.from(T.movements).select("direction, movement_kind, amount, paid_amount, status, due_date, payment_date");
4556
5650
  const allMovs = movs ?? [];
4557
5651
  const unpaid = allMovs.filter((m) => ["pending", "partial", "overdue"].includes(m.status));
4558
5652
  const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -4560,7 +5654,9 @@ function createSupabaseFinancialProvider() {
4560
5654
  const unpaidDebit = unpaid.filter((m) => m.direction === "debit");
4561
5655
  const overdueCredit = unpaidCredit.filter((m) => m.status === "overdue" || m.due_date < today2);
4562
5656
  const overdueDebit = unpaidDebit.filter((m) => m.status === "overdue" || m.due_date < today2);
4563
- const monthStart = today2.slice(0, 7) + "-01";
5657
+ const monthAgo = /* @__PURE__ */ new Date();
5658
+ monthAgo.setDate(monthAgo.getDate() - 30);
5659
+ const monthStart = monthAgo.toISOString().slice(0, 10);
4564
5660
  const monthPaid = allMovs.filter((m) => m.movement_kind === "payment" && m.payment_date >= monthStart);
4565
5661
  return {
4566
5662
  totalBalance,
@@ -4583,7 +5679,7 @@ function createSupabaseFinancialProvider() {
4583
5679
  const tenantId = getTenantId();
4584
5680
  let imported = 0, duplicates = 0;
4585
5681
  for (const line of input.lines) {
4586
- const { error } = await pub.from("financial_movements").insert({
5682
+ const { error } = await pub.from(T.movements).insert({
4587
5683
  tenant_id: tenantId,
4588
5684
  direction: line.direction,
4589
5685
  movement_kind: "payment",
@@ -4606,7 +5702,7 @@ function createSupabaseFinancialProvider() {
4606
5702
  },
4607
5703
  async getUnreconciled(query) {
4608
5704
  const { pub } = getClients();
4609
- let qb = pub.from("financial_movements").select("*").not("external_source", "is", null).is("reconciled_at", null);
5705
+ let qb = pub.from(T.movements).select("*").not("external_source", "is", null).is("reconciled_at", null);
4610
5706
  if (query?.bankAccountId) qb = qb.eq("bank_account_id", query.bankAccountId);
4611
5707
  if (query?.dateRange) qb = qb.gte("payment_date", query.dateRange.from).lte("payment_date", query.dateRange.to);
4612
5708
  const { data } = await qb.order("payment_date", { ascending: false });
@@ -4614,12 +5710,12 @@ function createSupabaseFinancialProvider() {
4614
5710
  },
4615
5711
  async suggestReconciliation(bankMovementId) {
4616
5712
  const { core, pub } = getClients();
4617
- const { data: line } = await pub.from("financial_movements").select("*").eq("id", bankMovementId).single();
5713
+ const { data: line } = await pub.from(T.movements).select("*").eq("id", bankMovementId).single();
4618
5714
  if (!line) return [];
4619
5715
  const amount = Number(line.amount) || 0;
4620
5716
  const lineDate = line.payment_date ?? line.due_date;
4621
5717
  const tol = Math.max(0.01, round22(amount * 0.02));
4622
- const { data } = await pub.from("financial_movements").select("*").is("external_source", null).is("reconciled_at", null).eq("direction", line.direction).gte("amount", round22(amount - tol)).lte("amount", round22(amount + tol)).neq("id", bankMovementId).limit(25);
5718
+ const { data } = await pub.from(T.movements).select("*").is("external_source", null).is("reconciled_at", null).eq("direction", line.direction).gte("amount", round22(amount - tol)).lte("amount", round22(amount + tol)).neq("id", bankMovementId).limit(25);
4623
5719
  const candidates = (data ?? []).map((r) => {
4624
5720
  const amtDiff = Math.abs((Number(r.amount) || 0) - amount);
4625
5721
  const amtScore = amount > 0 ? Math.max(0, 1 - amtDiff / amount) : 0;
@@ -4645,20 +5741,20 @@ function createSupabaseFinancialProvider() {
4645
5741
  async reconcileMovement(input) {
4646
5742
  const { pub } = getClients();
4647
5743
  const now2 = (/* @__PURE__ */ new Date()).toISOString();
4648
- await pub.from("financial_movements").update({
5744
+ await pub.from(T.movements).update({
4649
5745
  reconciled_at: now2,
4650
5746
  matched_movement_id: input.matchedMovementId ?? null
4651
5747
  }).eq("id", input.bankMovementId);
4652
5748
  if (input.matchedMovementId) {
4653
- await pub.from("financial_movements").update({ reconciled_at: now2 }).eq("id", input.matchedMovementId);
5749
+ await pub.from(T.movements).update({ reconciled_at: now2 }).eq("id", input.matchedMovementId);
4654
5750
  }
4655
5751
  },
4656
5752
  async unreconcileMovement(bankMovementId) {
4657
5753
  const { pub } = getClients();
4658
- const { data: line } = await pub.from("financial_movements").select("matched_movement_id").eq("id", bankMovementId).single();
4659
- await pub.from("financial_movements").update({ reconciled_at: null, matched_movement_id: null }).eq("id", bankMovementId);
5754
+ const { data: line } = await pub.from(T.movements).select("matched_movement_id").eq("id", bankMovementId).single();
5755
+ await pub.from(T.movements).update({ reconciled_at: null, matched_movement_id: null }).eq("id", bankMovementId);
4660
5756
  if (line?.matched_movement_id) {
4661
- await pub.from("financial_movements").update({ reconciled_at: null }).eq("id", line.matched_movement_id);
5757
+ await pub.from(T.movements).update({ reconciled_at: null }).eq("id", line.matched_movement_id);
4662
5758
  }
4663
5759
  }
4664
5760
  };
@@ -4751,6 +5847,82 @@ function createFinancialStore(provider) {
4751
5847
  throw err;
4752
5848
  }
4753
5849
  },
5850
+ async createQuickTransaction(input) {
5851
+ try {
5852
+ if (input.type === "transfer") {
5853
+ if (!input.bankAccountId || !input.toAccountId) {
5854
+ throw new Error("Select source and destination accounts");
5855
+ }
5856
+ await provider.createTransfer({
5857
+ fromAccountId: input.bankAccountId,
5858
+ toAccountId: input.toAccountId,
5859
+ amount: input.amount,
5860
+ date: input.date,
5861
+ notes: input.description?.trim() || void 0
5862
+ });
5863
+ } else {
5864
+ const direction = input.type === "income" ? "credit" : "debit";
5865
+ const desc = input.description?.trim() || (input.type === "income" ? "Receita" : "Despesa");
5866
+ const invoiceMeta = {};
5867
+ if (input.recurring) invoiceMeta.recurring = true;
5868
+ if (input.receiptUrl) invoiceMeta.receiptUrl = input.receiptUrl;
5869
+ const invoice = await provider.createInvoice({
5870
+ direction,
5871
+ invoiceDate: input.date,
5872
+ contactName: desc,
5873
+ metadata: Object.keys(invoiceMeta).length > 0 ? invoiceMeta : void 0,
5874
+ items: [{
5875
+ itemKind: "other",
5876
+ description: desc,
5877
+ quantity: 1,
5878
+ unitPrice: input.amount,
5879
+ accountId: input.categoryId
5880
+ }],
5881
+ installments: [{
5882
+ direction,
5883
+ movementKind: "bill",
5884
+ amount: input.amount,
5885
+ dueDate: input.date,
5886
+ installmentNumber: 1,
5887
+ bankAccountId: input.bankAccountId,
5888
+ notes: desc
5889
+ }]
5890
+ });
5891
+ if (input.paid) {
5892
+ const movements = await provider.getMovements({ direction, status: "pending" });
5893
+ const bill = movements.data.find((m) => m.invoiceId === invoice.id && m.movementKind === "bill");
5894
+ if (bill) {
5895
+ await provider.payMovement({
5896
+ movementId: bill.id,
5897
+ amount: input.amount,
5898
+ paymentDate: input.date,
5899
+ bankAccountId: input.bankAccountId,
5900
+ notes: desc
5901
+ });
5902
+ }
5903
+ }
5904
+ }
5905
+ const invoiceQuery = get().invoiceQuery;
5906
+ const statementQuery = get().statementQuery;
5907
+ const [summary, bankAccounts, invoicesRes, statement] = await Promise.all([
5908
+ provider.getSummary(),
5909
+ provider.getBankAccounts(),
5910
+ provider.getInvoices(invoiceQuery),
5911
+ statementQuery ? provider.getStatement(statementQuery) : Promise.resolve(get().statement)
5912
+ ]);
5913
+ set({
5914
+ summary,
5915
+ bankAccounts,
5916
+ invoices: invoicesRes.data,
5917
+ invoicesTotal: invoicesRes.total,
5918
+ statement
5919
+ });
5920
+ toast.success("Transaction saved");
5921
+ } catch (err) {
5922
+ toast.error("Failed to save transaction", { description: err?.message });
5923
+ throw err;
5924
+ }
5925
+ },
4754
5926
  async payMovement(input) {
4755
5927
  try {
4756
5928
  await provider.payMovement(input);
@@ -4830,7 +6002,7 @@ var paymentMethodTypeEntity = {
4830
6002
  { key: "transactionType", label: "Transaction Type", type: "select", options: ["cash", "credit_card", "debit_card", "pix", "bank_transfer", "check", "voucher"], showInTable: true, required: true },
4831
6003
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
4832
6004
  ],
4833
- data: { table: "payment_method_types", tenantScoped: true }
6005
+ data: { table: T.paymentMethodTypes, tenantScoped: true }
4834
6006
  };
4835
6007
  var paymentMethodEntity = {
4836
6008
  name: "Payment Method",
@@ -4842,14 +6014,14 @@ var paymentMethodEntity = {
4842
6014
  { key: "name", label: "Name", type: "text", required: true, showInTable: true },
4843
6015
  // FK to the seeded payment_method_types catalog — load real ids at runtime
4844
6016
  // (a static enum select would write the string into the uuid column).
4845
- { key: "paymentMethodTypeId", label: "Type", type: "relation", relation: { table: "payment_method_types", labelField: "name" }, showInTable: true, required: true },
6017
+ { key: "paymentMethodTypeId", label: "Type", type: "relation", relation: { table: T.paymentMethodTypes, labelField: "name" }, showInTable: true, required: true },
4846
6018
  { key: "discountValue", label: "Discount %", type: "number", showInTable: true },
4847
6019
  { key: "interestValue", label: "Interest %", type: "number", showInTable: true },
4848
6020
  { key: "minInstallments", label: "Min Installments", type: "number", defaultValue: 1 },
4849
6021
  { key: "maxInstallments", label: "Max Installments", type: "number", defaultValue: 1, showInTable: true },
4850
6022
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
4851
6023
  ],
4852
- data: { table: "payment_methods", tenantScoped: true }
6024
+ data: { table: T.paymentMethods, tenantScoped: true }
4853
6025
  };
4854
6026
  var chartOfAccountsEntity = {
4855
6027
  name: "Ledger Account",
@@ -4864,7 +6036,7 @@ var chartOfAccountsEntity = {
4864
6036
  { key: "parentId", label: "Parent", type: "text", showInTable: false },
4865
6037
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
4866
6038
  ],
4867
- data: { table: "chart_of_accounts", tenantScoped: true }
6039
+ data: { table: T.chartOfAccounts, tenantScoped: true }
4868
6040
  };
4869
6041
  var costCenterEntity = {
4870
6042
  name: "Cost Center",
@@ -4877,7 +6049,7 @@ var costCenterEntity = {
4877
6049
  { key: "name", label: "Name", type: "text", required: true, showInTable: true },
4878
6050
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
4879
6051
  ],
4880
- data: { table: "cost_centers", tenantScoped: true }
6052
+ data: { table: T.costCenters, tenantScoped: true }
4881
6053
  };
4882
6054
  var bankAccountEntity = {
4883
6055
  name: "Bank Account",
@@ -4902,7 +6074,7 @@ var bankAccountEntity = {
4902
6074
  { key: "dueDay", label: "Due Day", type: "number" },
4903
6075
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
4904
6076
  ],
4905
- data: { table: "bank_accounts", tenantScoped: true }
6077
+ data: { table: T.bankAccounts, tenantScoped: true }
4906
6078
  };
4907
6079
  var cardBrandEntity = {
4908
6080
  name: "Card Brand",
@@ -4914,7 +6086,7 @@ var cardBrandEntity = {
4914
6086
  { key: "name", label: "Name", type: "text", required: true, showInTable: true },
4915
6087
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
4916
6088
  ],
4917
- data: { table: "card_brands", tenantScoped: true }
6089
+ data: { table: T.cardBrands, tenantScoped: true }
4918
6090
  };
4919
6091
  var commissionRuleEntity = {
4920
6092
  name: "Commission Rule",
@@ -5077,8 +6249,18 @@ var financialRegistries = [
5077
6249
 
5078
6250
  // src/locales/en.ts
5079
6251
  var en = {
6252
+ "financial.cards.addExpense": "Add expense",
6253
+ "financial.cards.allBillsValue": "All bills value",
6254
+ "financial.cards.availableLimit": "Available limit",
6255
+ "financial.cards.currentBill": "Current bill",
6256
+ "financial.cards.dueOn": "Due on the {{day}}",
5080
6257
  "financial.cards.installment": "Installment {{current}}/{{total}}",
6258
+ "financial.cards.noCards": "No cards yet",
6259
+ "financial.cards.noCardsHint": "Add a credit card in settings to track your bills here",
5081
6260
  "financial.cards.noTransactions": "No card transactions",
6261
+ "financial.cards.tabClosed": "Closed",
6262
+ "financial.cards.tabOpen": "Open",
6263
+ "financial.cards.usedOfLimit": "{{percent}}% of limit used",
5082
6264
  "financial.cards.pending": "Pending",
5083
6265
  "financial.cards.received": "Received",
5084
6266
  "financial.cards.subtitle": "Credit and debit card transactions",
@@ -5205,6 +6387,10 @@ var en = {
5205
6387
  "financial.nav.rules": "Rules",
5206
6388
  "financial.nav.statements": "Statements",
5207
6389
  "financial.nav.summary": "Summary",
6390
+ "financial.invoice.filterAccount": "Chart account",
6391
+ "financial.invoice.filterAllAccounts": "All accounts",
6392
+ "financial.invoice.filterCostCenter": "Cost center",
6393
+ "financial.invoice.filterAllCostCenters": "All cost centers",
5208
6394
  "financial.reconciliation.title": "Reconciliation",
5209
6395
  "financial.reconciliation.subtitle": "Match imported bank lines to your movements",
5210
6396
  "financial.reconciliation.pending": "{{count}} to reconcile",
@@ -5319,6 +6505,36 @@ var en = {
5319
6505
  "financial.summary.totalBalance": "Total Balance",
5320
6506
  "financial.summary.transactionsAppear": "Transactions will appear here once recorded",
5321
6507
  "financial.summary.viewAll": "View all",
6508
+ "financial.summary.today": "Today",
6509
+ "financial.summary.yesterday": "Yesterday",
6510
+ "financial.summary.transactionsTitle": "Transactions",
6511
+ "financial.home.balance": "Balance",
6512
+ "financial.home.inflow": "Income",
6513
+ "financial.home.outflow": "Expenses",
6514
+ "financial.home.showBalance": "Show balance",
6515
+ "financial.home.hideBalance": "Hide balance",
6516
+ "financial.home.upcomingBills": "Upcoming bills",
6517
+ "financial.home.noBills": "No upcoming bills.",
6518
+ "financial.home.dueToday": "Due today",
6519
+ "financial.home.dueTomorrow": "Due tomorrow",
6520
+ "financial.home.dueInDays": "Due in {{count}} days",
6521
+ "financial.home.overdueDays": "Overdue by {{count}} days",
6522
+ "financial.home.cards": "Cards",
6523
+ "financial.home.currentBill": "Current bill",
6524
+ "financial.home.availableLimit": "Available limit",
6525
+ "financial.home.addCard": "New card",
6526
+ "financial.home.bill": "Bill",
6527
+ "financial.home.spendByCategory": "Spending by category",
6528
+ "financial.home.noSpend": "No spending this month.",
6529
+ "financial.home.category.groceries": "Groceries",
6530
+ "financial.home.category.transport": "Transport",
6531
+ "financial.home.category.subscriptions": "Subscriptions",
6532
+ "financial.home.category.dining": "Dining",
6533
+ "financial.home.category.home": "Home",
6534
+ "financial.home.category.health": "Health",
6535
+ "financial.home.category.leisure": "Leisure",
6536
+ "financial.home.category.shopping": "Shopping",
6537
+ "financial.home.category.other": "Other",
5322
6538
  "financial.quickActions.newPayable": "New Payable",
5323
6539
  "financial.quickActions.newPayableDesc": "Record an expense or bill to pay",
5324
6540
  "financial.quickActions.newReceivable": "New Receivable",
@@ -5480,13 +6696,54 @@ var en = {
5480
6696
  "financial.transfer.created": "Transfer recorded",
5481
6697
  "financial.transfer.failed": "Failed to record transfer",
5482
6698
  "financial.transfer.sameAccount": "Source and destination must differ",
5483
- "financial.transfer.invalidAmount": "Amount must be greater than zero"
6699
+ "financial.transfer.invalidAmount": "Amount must be greater than zero",
6700
+ "financial.quickTx.title": "New transaction",
6701
+ "financial.quickTx.close": "Close",
6702
+ "financial.quickTx.newTransaction": "New transaction",
6703
+ "financial.quickTx.expense": "Expense",
6704
+ "financial.quickTx.income": "Income",
6705
+ "financial.quickTx.transfer": "Transfer",
6706
+ "financial.quickTx.paid": "Paid",
6707
+ "financial.quickTx.received": "Received",
6708
+ "financial.quickTx.yes": "Yes",
6709
+ "financial.quickTx.no": "No",
6710
+ "financial.quickTx.category": "Category",
6711
+ "financial.quickTx.selectCategory": "No category",
6712
+ "financial.quickTx.account": "Account",
6713
+ "financial.quickTx.selectAccount": "Select account",
6714
+ "financial.quickTx.fromAccount": "From account",
6715
+ "financial.quickTx.toAccount": "To account",
6716
+ "financial.quickTx.card": "Card",
6717
+ "financial.quickTx.recurring": "Recurring",
6718
+ "financial.quickTx.attachment": "Attachment",
6719
+ "financial.quickTx.attachmentSoon": "Attachments coming soon",
6720
+ "financial.quickTx.receipt": "Receipt",
6721
+ "financial.quickTx.receiptCapture": "Snap or attach a receipt",
6722
+ "financial.quickTx.receiptAttached": "Receipt attached",
6723
+ "financial.quickTx.receiptReplace": "Replace",
6724
+ "financial.quickTx.receiptRemove": "Remove receipt",
6725
+ "financial.quickTx.receiptInvalid": "Please choose an image file",
6726
+ "financial.quickTx.sendReceipt": "Send receipt",
6727
+ "financial.quickTx.descriptionPlaceholder": "e.g. Groceries, Salary\u2026",
6728
+ "financial.quickTx.enterAmount": "Enter an amount",
6729
+ "financial.quickTx.save": "Save",
6730
+ "financial.quickTx.saving": "Saving\u2026"
5484
6731
  };
5485
6732
 
5486
6733
  // src/locales/pt-BR.ts
5487
6734
  var ptBR = {
6735
+ "financial.cards.addExpense": "Adicionar despesa",
6736
+ "financial.cards.allBillsValue": "Total das faturas",
6737
+ "financial.cards.availableLimit": "Limite dispon\xEDvel",
6738
+ "financial.cards.currentBill": "Fatura atual",
6739
+ "financial.cards.dueOn": "Vence dia {{day}}",
5488
6740
  "financial.cards.installment": "Parcela {{current}}/{{total}}",
6741
+ "financial.cards.noCards": "Nenhum cart\xE3o cadastrado",
6742
+ "financial.cards.noCardsHint": "Adicione um cart\xE3o de cr\xE9dito nas configura\xE7\xF5es para acompanhar suas faturas aqui",
5489
6743
  "financial.cards.noTransactions": "Nenhuma transa\xE7\xE3o com cart\xE3o",
6744
+ "financial.cards.tabClosed": "Fechados",
6745
+ "financial.cards.tabOpen": "Abertos",
6746
+ "financial.cards.usedOfLimit": "{{percent}}% do limite usado",
5490
6747
  "financial.cards.pending": "Pendente",
5491
6748
  "financial.cards.received": "Recebido",
5492
6749
  "financial.cards.subtitle": "Transa\xE7\xF5es de cart\xE3o de cr\xE9dito e d\xE9bito",
@@ -5613,6 +6870,10 @@ var ptBR = {
5613
6870
  "financial.nav.rules": "Regras",
5614
6871
  "financial.nav.statements": "Extratos",
5615
6872
  "financial.nav.summary": "Resumo",
6873
+ "financial.invoice.filterAccount": "Plano de contas",
6874
+ "financial.invoice.filterAllAccounts": "Todos os planos",
6875
+ "financial.invoice.filterCostCenter": "Centro de custo",
6876
+ "financial.invoice.filterAllCostCenters": "Todos os centros",
5616
6877
  "financial.reconciliation.title": "Concilia\xE7\xE3o",
5617
6878
  "financial.reconciliation.subtitle": "Concilie as linhas importadas do banco com seus lan\xE7amentos",
5618
6879
  "financial.reconciliation.pending": "{{count}} para conciliar",
@@ -5727,6 +6988,36 @@ var ptBR = {
5727
6988
  "financial.summary.totalBalance": "Saldo Total",
5728
6989
  "financial.summary.transactionsAppear": "As transa\xE7\xF5es aparecer\xE3o aqui ap\xF3s serem registradas",
5729
6990
  "financial.summary.viewAll": "Ver todos",
6991
+ "financial.summary.today": "Hoje",
6992
+ "financial.summary.yesterday": "Ontem",
6993
+ "financial.summary.transactionsTitle": "Transa\xE7\xF5es",
6994
+ "financial.home.balance": "Saldo",
6995
+ "financial.home.inflow": "Entradas",
6996
+ "financial.home.outflow": "Sa\xEDdas",
6997
+ "financial.home.showBalance": "Mostrar saldo",
6998
+ "financial.home.hideBalance": "Ocultar saldo",
6999
+ "financial.home.upcomingBills": "Pr\xF3ximas contas",
7000
+ "financial.home.noBills": "Nenhuma conta a vencer.",
7001
+ "financial.home.dueToday": "Vence hoje",
7002
+ "financial.home.dueTomorrow": "Vence amanh\xE3",
7003
+ "financial.home.dueInDays": "Vence em {{count}} dias",
7004
+ "financial.home.overdueDays": "Venceu h\xE1 {{count}} dias",
7005
+ "financial.home.cards": "Cart\xF5es",
7006
+ "financial.home.currentBill": "Fatura atual",
7007
+ "financial.home.availableLimit": "Limite dispon\xEDvel",
7008
+ "financial.home.addCard": "Novo cart\xE3o",
7009
+ "financial.home.bill": "Conta",
7010
+ "financial.home.spendByCategory": "Gastos por categoria",
7011
+ "financial.home.noSpend": "Sem gastos neste m\xEAs.",
7012
+ "financial.home.category.groceries": "Mercado",
7013
+ "financial.home.category.transport": "Transporte",
7014
+ "financial.home.category.subscriptions": "Assinaturas",
7015
+ "financial.home.category.dining": "Alimenta\xE7\xE3o",
7016
+ "financial.home.category.home": "Casa",
7017
+ "financial.home.category.health": "Sa\xFAde",
7018
+ "financial.home.category.leisure": "Lazer",
7019
+ "financial.home.category.shopping": "Compras",
7020
+ "financial.home.category.other": "Outros",
5730
7021
  "financial.quickActions.newPayable": "Nova Conta a Pagar",
5731
7022
  "financial.quickActions.newPayableDesc": "Registrar uma despesa ou conta a pagar",
5732
7023
  "financial.quickActions.newReceivable": "Nova Conta a Receber",
@@ -5888,7 +7179,38 @@ var ptBR = {
5888
7179
  "financial.transfer.created": "Transfer\xEAncia registrada",
5889
7180
  "financial.transfer.failed": "Falha ao registrar transfer\xEAncia",
5890
7181
  "financial.transfer.sameAccount": "Origem e destino devem ser diferentes",
5891
- "financial.transfer.invalidAmount": "O valor deve ser maior que zero"
7182
+ "financial.transfer.invalidAmount": "O valor deve ser maior que zero",
7183
+ "financial.quickTx.title": "Nova transa\xE7\xE3o",
7184
+ "financial.quickTx.close": "Fechar",
7185
+ "financial.quickTx.newTransaction": "Nova transa\xE7\xE3o",
7186
+ "financial.quickTx.expense": "Despesa",
7187
+ "financial.quickTx.income": "Receita",
7188
+ "financial.quickTx.transfer": "Transfer\xEAncia",
7189
+ "financial.quickTx.paid": "Pago",
7190
+ "financial.quickTx.received": "Recebido",
7191
+ "financial.quickTx.yes": "Sim",
7192
+ "financial.quickTx.no": "N\xE3o",
7193
+ "financial.quickTx.category": "Categoria",
7194
+ "financial.quickTx.selectCategory": "Sem categoria",
7195
+ "financial.quickTx.account": "Conta",
7196
+ "financial.quickTx.selectAccount": "Selecione a conta",
7197
+ "financial.quickTx.fromAccount": "Conta de origem",
7198
+ "financial.quickTx.toAccount": "Conta de destino",
7199
+ "financial.quickTx.card": "Cart\xE3o",
7200
+ "financial.quickTx.recurring": "Recorrente",
7201
+ "financial.quickTx.attachment": "Anexo",
7202
+ "financial.quickTx.attachmentSoon": "Anexos em breve",
7203
+ "financial.quickTx.receipt": "Recibo",
7204
+ "financial.quickTx.receiptCapture": "Fotografar ou anexar recibo",
7205
+ "financial.quickTx.receiptAttached": "Recibo anexado",
7206
+ "financial.quickTx.receiptReplace": "Substituir",
7207
+ "financial.quickTx.receiptRemove": "Remover recibo",
7208
+ "financial.quickTx.receiptInvalid": "Escolha um arquivo de imagem",
7209
+ "financial.quickTx.sendReceipt": "Enviar recibo",
7210
+ "financial.quickTx.descriptionPlaceholder": "ex.: Mercado, Sal\xE1rio\u2026",
7211
+ "financial.quickTx.enterAmount": "Informe um valor",
7212
+ "financial.quickTx.save": "Salvar",
7213
+ "financial.quickTx.saving": "Salvando\u2026"
5892
7214
  };
5893
7215
 
5894
7216
  // src/locales/index.ts
@@ -6038,6 +7360,8 @@ function resolveConfig(options) {
6038
7360
  // Opt-in: only shown when an app explicitly enables reconciliation (bank connector present).
6039
7361
  reconciliation: options?.modules?.reconciliation === true
6040
7362
  },
7363
+ // Opt-in: only render the plugin's own quick-add buttons/FAB when the host asks.
7364
+ quickAdd: options?.quickAdd === true,
6041
7365
  labels: { ...DEFAULT_LABELS, ...options?.labels },
6042
7366
  currency: { ...DEFAULT_CURRENCY, ...options?.currency },
6043
7367
  itemTypes: options?.itemTypes ?? DEFAULT_ITEM_TYPES,
@@ -6058,9 +7382,10 @@ function createFinancialPlugin(options) {
6058
7382
  const provider = options?.dataProvider ?? createSafeFinancialProvider();
6059
7383
  const store = createFinancialStore(provider);
6060
7384
  const dashboardWidgets = createFinancialDashboardWidgets({ config, provider, store });
6061
- const PersonStatementBound = (props) => React14.createElement(PersonStatementWidget, { person: props.item, provider, config });
7385
+ const registries = [...financialRegistries, ...options?.settingsRegistries ?? []];
7386
+ const PersonStatementBound = (props) => React17__default.createElement(PersonStatementWidget, { person: props.item, provider, config });
6062
7387
  PersonStatementBound.displayName = "FinancialPersonStatement";
6063
- const PageComponent = () => React14.createElement(FinancialPage, { config, provider, store, registries: financialRegistries });
7388
+ const PageComponent = () => React17__default.createElement(FinancialPage, { config, provider, store, registries });
6064
7389
  return {
6065
7390
  id: "financial",
6066
7391
  name: config.labels.pageTitle,
@@ -6165,18 +7490,18 @@ function createFinancialPlugin(options) {
6165
7490
  ]
6166
7491
  }
6167
7492
  ],
6168
- registries: financialRegistries,
7493
+ registries,
6169
7494
  settings: [
6170
7495
  {
6171
7496
  id: "financial",
6172
7497
  label: "Financial",
6173
7498
  icon: "DollarSign",
6174
7499
  component: (() => {
6175
- const FinancialSettingsTab = () => React14.createElement(PluginSettingsPanel, {
7500
+ const FinancialSettingsTab = () => React17__default.createElement(PluginSettingsPanel, {
6176
7501
  title: "Financial Settings",
6177
7502
  subtitle: "Payment methods, accounts, and configurations",
6178
- generalSettings: React14.createElement(FinancialGeneralSettings),
6179
- registries: financialRegistries,
7503
+ generalSettings: React17__default.createElement(FinancialGeneralSettings),
7504
+ registries,
6180
7505
  hostPluginId: "financial",
6181
7506
  routeBase: "/settings/financial"
6182
7507
  });
@@ -6191,6 +7516,6 @@ function createFinancialPlugin(options) {
6191
7516
  };
6192
7517
  }
6193
7518
 
6194
- export { createFinancialPlugin, createSafeFinancialProvider };
7519
+ export { createFinancialPlugin, createMockFinancialProvider, createSafeFinancialProvider, openQuickAdd };
6195
7520
  //# sourceMappingURL=index.js.map
6196
7521
  //# sourceMappingURL=index.js.map