@fayz-ai/plugin-financial 0.1.7 → 0.2.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 (50) hide show
  1. package/dist/FinancialContext.d.ts +2 -0
  2. package/dist/FinancialContext.d.ts.map +1 -1
  3. package/dist/FinancialPage.d.ts.map +1 -1
  4. package/dist/data/index.d.ts +1 -0
  5. package/dist/data/index.d.ts.map +1 -1
  6. package/dist/data/mock.d.ts +15 -0
  7. package/dist/data/mock.d.ts.map +1 -1
  8. package/dist/data/supabase.d.ts.map +1 -1
  9. package/dist/data/types.d.ts +2 -1
  10. package/dist/data/types.d.ts.map +1 -1
  11. package/dist/index.cjs +1531 -201
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.ts +13 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1393 -80
  16. package/dist/index.js.map +1 -1
  17. package/dist/locales/en.d.ts.map +1 -1
  18. package/dist/locales/pt-BR.d.ts.map +1 -1
  19. package/dist/quick-add.d.ts +13 -0
  20. package/dist/quick-add.d.ts.map +1 -0
  21. package/dist/store.d.ts +3 -1
  22. package/dist/store.d.ts.map +1 -1
  23. package/dist/types.d.ts +42 -0
  24. package/dist/types.d.ts.map +1 -1
  25. package/dist/views/CardsView.d.ts.map +1 -1
  26. package/dist/views/InvoiceListView.d.ts.map +1 -1
  27. package/dist/views/QuickTransactionForm.d.ts +16 -0
  28. package/dist/views/QuickTransactionForm.d.ts.map +1 -0
  29. package/dist/views/SummaryView.d.ts +5 -4
  30. package/dist/views/SummaryView.d.ts.map +1 -1
  31. package/dist/views/dashboardWidgets.d.ts.map +1 -1
  32. package/package.json +4 -7
  33. package/src/FinancialContext.tsx +2 -0
  34. package/src/FinancialPage.tsx +86 -7
  35. package/src/data/capability.test.ts +45 -0
  36. package/src/data/index.ts +1 -0
  37. package/src/data/mock.ts +50 -10
  38. package/src/data/supabase.ts +46 -3
  39. package/src/data/types.ts +2 -1
  40. package/src/index.ts +24 -4
  41. package/src/locales/en.ts +75 -0
  42. package/src/locales/pt-BR.ts +75 -0
  43. package/src/quick-add.ts +58 -0
  44. package/src/store.ts +88 -0
  45. package/src/types.ts +45 -0
  46. package/src/views/CardsView.tsx +186 -50
  47. package/src/views/InvoiceListView.tsx +60 -3
  48. package/src/views/QuickTransactionForm.tsx +537 -0
  49. package/src/views/SummaryView.tsx +197 -6
  50. 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 })
@@ -4043,6 +5093,18 @@ async function fetchInvoiceBalances(pub, ids) {
4043
5093
  function round22(n) {
4044
5094
  return Math.round(n * 100) / 100;
4045
5095
  }
5096
+ async function findOrderIdsByItemMetadata(core, filters) {
5097
+ if (!filters.accountId && !filters.costCenterId) return void 0;
5098
+ let qb = core.from("order_items").select("order_id");
5099
+ if (filters.accountId) qb = qb.filter("metadata->>accountId", "eq", filters.accountId);
5100
+ if (filters.costCenterId) qb = qb.filter("metadata->>costCenterId", "eq", filters.costCenterId);
5101
+ const { data } = await qb;
5102
+ const ids = [];
5103
+ for (const row of data ?? []) {
5104
+ if (typeof row.order_id === "string" && row.order_id.length > 0) ids.push(row.order_id);
5105
+ }
5106
+ return Array.from(new Set(ids));
5107
+ }
4046
5108
  async function getMethodFeePct(pub, paymentMethodId) {
4047
5109
  if (!paymentMethodId) return 0;
4048
5110
  const { data } = await pub.from("payment_methods").select("discount_value").eq("id", paymentMethodId).single();
@@ -4070,6 +5132,14 @@ function createSupabaseFinancialProvider() {
4070
5132
  const statuses = Array.isArray(query.status) ? query.status : [query.status];
4071
5133
  qb = qb.in("status", statuses);
4072
5134
  }
5135
+ const itemScopedOrderIds = await findOrderIdsByItemMetadata(core, {
5136
+ accountId: query.accountId,
5137
+ costCenterId: query.costCenterId
5138
+ });
5139
+ if (itemScopedOrderIds) {
5140
+ if (itemScopedOrderIds.length === 0) return { data: [], total: 0 };
5141
+ qb = qb.in("id", itemScopedOrderIds);
5142
+ }
4073
5143
  if (query.contactId) qb = qb.eq("party_id", query.contactId);
4074
5144
  if (query.search) qb = qb.ilike("notes", `%${query.search}%`);
4075
5145
  if (query.dateRange) qb = qb.gte("created_at", query.dateRange.from).lte("created_at", query.dateRange.to);
@@ -4145,6 +5215,8 @@ function createSupabaseFinancialProvider() {
4145
5215
  discount: i.discount ?? 0,
4146
5216
  surcharge: 0,
4147
5217
  referenceId: i.productId ?? i.serviceId,
5218
+ accountId: i.metadata?.accountId,
5219
+ costCenterId: i.metadata?.costCenterId,
4148
5220
  createdAt: i.createdAt
4149
5221
  };
4150
5222
  });
@@ -4184,7 +5256,12 @@ function createSupabaseFinancialProvider() {
4184
5256
  discount: item.discount ?? 0,
4185
5257
  total: item.quantity * item.unitPrice - (item.discount ?? 0) + (item.surcharge ?? 0),
4186
5258
  sort_order: i,
4187
- metadata: { itemKind: item.itemKind, referenceId: item.referenceId }
5259
+ metadata: {
5260
+ itemKind: item.itemKind,
5261
+ referenceId: item.referenceId,
5262
+ accountId: item.accountId,
5263
+ costCenterId: item.costCenterId
5264
+ }
4188
5265
  }))
4189
5266
  );
4190
5267
  }
@@ -4444,6 +5521,11 @@ function createSupabaseFinancialProvider() {
4444
5521
  const { data } = await pub.from("chart_of_accounts").select("*").eq("is_active", true).order("code");
4445
5522
  return (data ?? []).map((r) => snakeToCamel(r));
4446
5523
  },
5524
+ async getCostCenters() {
5525
+ const { pub } = getClients();
5526
+ const { data } = await pub.from("cost_centers").select("*").eq("is_active", true).order("code");
5527
+ return (data ?? []).map((r) => snakeToCamel(r));
5528
+ },
4447
5529
  // --- Card Transactions ---
4448
5530
  async getCardTransactions(dateRange) {
4449
5531
  getClients();
@@ -4560,7 +5642,9 @@ function createSupabaseFinancialProvider() {
4560
5642
  const unpaidDebit = unpaid.filter((m) => m.direction === "debit");
4561
5643
  const overdueCredit = unpaidCredit.filter((m) => m.status === "overdue" || m.due_date < today2);
4562
5644
  const overdueDebit = unpaidDebit.filter((m) => m.status === "overdue" || m.due_date < today2);
4563
- const monthStart = today2.slice(0, 7) + "-01";
5645
+ const monthAgo = /* @__PURE__ */ new Date();
5646
+ monthAgo.setDate(monthAgo.getDate() - 30);
5647
+ const monthStart = monthAgo.toISOString().slice(0, 10);
4564
5648
  const monthPaid = allMovs.filter((m) => m.movement_kind === "payment" && m.payment_date >= monthStart);
4565
5649
  return {
4566
5650
  totalBalance,
@@ -4751,6 +5835,82 @@ function createFinancialStore(provider) {
4751
5835
  throw err;
4752
5836
  }
4753
5837
  },
5838
+ async createQuickTransaction(input) {
5839
+ try {
5840
+ if (input.type === "transfer") {
5841
+ if (!input.bankAccountId || !input.toAccountId) {
5842
+ throw new Error("Select source and destination accounts");
5843
+ }
5844
+ await provider.createTransfer({
5845
+ fromAccountId: input.bankAccountId,
5846
+ toAccountId: input.toAccountId,
5847
+ amount: input.amount,
5848
+ date: input.date,
5849
+ notes: input.description?.trim() || void 0
5850
+ });
5851
+ } else {
5852
+ const direction = input.type === "income" ? "credit" : "debit";
5853
+ const desc = input.description?.trim() || (input.type === "income" ? "Receita" : "Despesa");
5854
+ const invoiceMeta = {};
5855
+ if (input.recurring) invoiceMeta.recurring = true;
5856
+ if (input.receiptUrl) invoiceMeta.receiptUrl = input.receiptUrl;
5857
+ const invoice = await provider.createInvoice({
5858
+ direction,
5859
+ invoiceDate: input.date,
5860
+ contactName: desc,
5861
+ metadata: Object.keys(invoiceMeta).length > 0 ? invoiceMeta : void 0,
5862
+ items: [{
5863
+ itemKind: "other",
5864
+ description: desc,
5865
+ quantity: 1,
5866
+ unitPrice: input.amount,
5867
+ accountId: input.categoryId
5868
+ }],
5869
+ installments: [{
5870
+ direction,
5871
+ movementKind: "bill",
5872
+ amount: input.amount,
5873
+ dueDate: input.date,
5874
+ installmentNumber: 1,
5875
+ bankAccountId: input.bankAccountId,
5876
+ notes: desc
5877
+ }]
5878
+ });
5879
+ if (input.paid) {
5880
+ const movements = await provider.getMovements({ direction, status: "pending" });
5881
+ const bill = movements.data.find((m) => m.invoiceId === invoice.id && m.movementKind === "bill");
5882
+ if (bill) {
5883
+ await provider.payMovement({
5884
+ movementId: bill.id,
5885
+ amount: input.amount,
5886
+ paymentDate: input.date,
5887
+ bankAccountId: input.bankAccountId,
5888
+ notes: desc
5889
+ });
5890
+ }
5891
+ }
5892
+ }
5893
+ const invoiceQuery = get().invoiceQuery;
5894
+ const statementQuery = get().statementQuery;
5895
+ const [summary, bankAccounts, invoicesRes, statement] = await Promise.all([
5896
+ provider.getSummary(),
5897
+ provider.getBankAccounts(),
5898
+ provider.getInvoices(invoiceQuery),
5899
+ statementQuery ? provider.getStatement(statementQuery) : Promise.resolve(get().statement)
5900
+ ]);
5901
+ set({
5902
+ summary,
5903
+ bankAccounts,
5904
+ invoices: invoicesRes.data,
5905
+ invoicesTotal: invoicesRes.total,
5906
+ statement
5907
+ });
5908
+ toast.success("Transaction saved");
5909
+ } catch (err) {
5910
+ toast.error("Failed to save transaction", { description: err?.message });
5911
+ throw err;
5912
+ }
5913
+ },
4754
5914
  async payMovement(input) {
4755
5915
  try {
4756
5916
  await provider.payMovement(input);
@@ -5077,8 +6237,18 @@ var financialRegistries = [
5077
6237
 
5078
6238
  // src/locales/en.ts
5079
6239
  var en = {
6240
+ "financial.cards.addExpense": "Add expense",
6241
+ "financial.cards.allBillsValue": "All bills value",
6242
+ "financial.cards.availableLimit": "Available limit",
6243
+ "financial.cards.currentBill": "Current bill",
6244
+ "financial.cards.dueOn": "Due on the {{day}}",
5080
6245
  "financial.cards.installment": "Installment {{current}}/{{total}}",
6246
+ "financial.cards.noCards": "No cards yet",
6247
+ "financial.cards.noCardsHint": "Add a credit card in settings to track your bills here",
5081
6248
  "financial.cards.noTransactions": "No card transactions",
6249
+ "financial.cards.tabClosed": "Closed",
6250
+ "financial.cards.tabOpen": "Open",
6251
+ "financial.cards.usedOfLimit": "{{percent}}% of limit used",
5082
6252
  "financial.cards.pending": "Pending",
5083
6253
  "financial.cards.received": "Received",
5084
6254
  "financial.cards.subtitle": "Credit and debit card transactions",
@@ -5205,6 +6375,10 @@ var en = {
5205
6375
  "financial.nav.rules": "Rules",
5206
6376
  "financial.nav.statements": "Statements",
5207
6377
  "financial.nav.summary": "Summary",
6378
+ "financial.invoice.filterAccount": "Chart account",
6379
+ "financial.invoice.filterAllAccounts": "All accounts",
6380
+ "financial.invoice.filterCostCenter": "Cost center",
6381
+ "financial.invoice.filterAllCostCenters": "All cost centers",
5208
6382
  "financial.reconciliation.title": "Reconciliation",
5209
6383
  "financial.reconciliation.subtitle": "Match imported bank lines to your movements",
5210
6384
  "financial.reconciliation.pending": "{{count}} to reconcile",
@@ -5319,6 +6493,36 @@ var en = {
5319
6493
  "financial.summary.totalBalance": "Total Balance",
5320
6494
  "financial.summary.transactionsAppear": "Transactions will appear here once recorded",
5321
6495
  "financial.summary.viewAll": "View all",
6496
+ "financial.summary.today": "Today",
6497
+ "financial.summary.yesterday": "Yesterday",
6498
+ "financial.summary.transactionsTitle": "Transactions",
6499
+ "financial.home.balance": "Balance",
6500
+ "financial.home.inflow": "Income",
6501
+ "financial.home.outflow": "Expenses",
6502
+ "financial.home.showBalance": "Show balance",
6503
+ "financial.home.hideBalance": "Hide balance",
6504
+ "financial.home.upcomingBills": "Upcoming bills",
6505
+ "financial.home.noBills": "No upcoming bills.",
6506
+ "financial.home.dueToday": "Due today",
6507
+ "financial.home.dueTomorrow": "Due tomorrow",
6508
+ "financial.home.dueInDays": "Due in {{count}} days",
6509
+ "financial.home.overdueDays": "Overdue by {{count}} days",
6510
+ "financial.home.cards": "Cards",
6511
+ "financial.home.currentBill": "Current bill",
6512
+ "financial.home.availableLimit": "Available limit",
6513
+ "financial.home.addCard": "New card",
6514
+ "financial.home.bill": "Bill",
6515
+ "financial.home.spendByCategory": "Spending by category",
6516
+ "financial.home.noSpend": "No spending this month.",
6517
+ "financial.home.category.groceries": "Groceries",
6518
+ "financial.home.category.transport": "Transport",
6519
+ "financial.home.category.subscriptions": "Subscriptions",
6520
+ "financial.home.category.dining": "Dining",
6521
+ "financial.home.category.home": "Home",
6522
+ "financial.home.category.health": "Health",
6523
+ "financial.home.category.leisure": "Leisure",
6524
+ "financial.home.category.shopping": "Shopping",
6525
+ "financial.home.category.other": "Other",
5322
6526
  "financial.quickActions.newPayable": "New Payable",
5323
6527
  "financial.quickActions.newPayableDesc": "Record an expense or bill to pay",
5324
6528
  "financial.quickActions.newReceivable": "New Receivable",
@@ -5480,13 +6684,54 @@ var en = {
5480
6684
  "financial.transfer.created": "Transfer recorded",
5481
6685
  "financial.transfer.failed": "Failed to record transfer",
5482
6686
  "financial.transfer.sameAccount": "Source and destination must differ",
5483
- "financial.transfer.invalidAmount": "Amount must be greater than zero"
6687
+ "financial.transfer.invalidAmount": "Amount must be greater than zero",
6688
+ "financial.quickTx.title": "New transaction",
6689
+ "financial.quickTx.close": "Close",
6690
+ "financial.quickTx.newTransaction": "New transaction",
6691
+ "financial.quickTx.expense": "Expense",
6692
+ "financial.quickTx.income": "Income",
6693
+ "financial.quickTx.transfer": "Transfer",
6694
+ "financial.quickTx.paid": "Paid",
6695
+ "financial.quickTx.received": "Received",
6696
+ "financial.quickTx.yes": "Yes",
6697
+ "financial.quickTx.no": "No",
6698
+ "financial.quickTx.category": "Category",
6699
+ "financial.quickTx.selectCategory": "No category",
6700
+ "financial.quickTx.account": "Account",
6701
+ "financial.quickTx.selectAccount": "Select account",
6702
+ "financial.quickTx.fromAccount": "From account",
6703
+ "financial.quickTx.toAccount": "To account",
6704
+ "financial.quickTx.card": "Card",
6705
+ "financial.quickTx.recurring": "Recurring",
6706
+ "financial.quickTx.attachment": "Attachment",
6707
+ "financial.quickTx.attachmentSoon": "Attachments coming soon",
6708
+ "financial.quickTx.receipt": "Receipt",
6709
+ "financial.quickTx.receiptCapture": "Snap or attach a receipt",
6710
+ "financial.quickTx.receiptAttached": "Receipt attached",
6711
+ "financial.quickTx.receiptReplace": "Replace",
6712
+ "financial.quickTx.receiptRemove": "Remove receipt",
6713
+ "financial.quickTx.receiptInvalid": "Please choose an image file",
6714
+ "financial.quickTx.sendReceipt": "Send receipt",
6715
+ "financial.quickTx.descriptionPlaceholder": "e.g. Groceries, Salary\u2026",
6716
+ "financial.quickTx.enterAmount": "Enter an amount",
6717
+ "financial.quickTx.save": "Save",
6718
+ "financial.quickTx.saving": "Saving\u2026"
5484
6719
  };
5485
6720
 
5486
6721
  // src/locales/pt-BR.ts
5487
6722
  var ptBR = {
6723
+ "financial.cards.addExpense": "Adicionar despesa",
6724
+ "financial.cards.allBillsValue": "Total das faturas",
6725
+ "financial.cards.availableLimit": "Limite dispon\xEDvel",
6726
+ "financial.cards.currentBill": "Fatura atual",
6727
+ "financial.cards.dueOn": "Vence dia {{day}}",
5488
6728
  "financial.cards.installment": "Parcela {{current}}/{{total}}",
6729
+ "financial.cards.noCards": "Nenhum cart\xE3o cadastrado",
6730
+ "financial.cards.noCardsHint": "Adicione um cart\xE3o de cr\xE9dito nas configura\xE7\xF5es para acompanhar suas faturas aqui",
5489
6731
  "financial.cards.noTransactions": "Nenhuma transa\xE7\xE3o com cart\xE3o",
6732
+ "financial.cards.tabClosed": "Fechados",
6733
+ "financial.cards.tabOpen": "Abertos",
6734
+ "financial.cards.usedOfLimit": "{{percent}}% do limite usado",
5490
6735
  "financial.cards.pending": "Pendente",
5491
6736
  "financial.cards.received": "Recebido",
5492
6737
  "financial.cards.subtitle": "Transa\xE7\xF5es de cart\xE3o de cr\xE9dito e d\xE9bito",
@@ -5613,6 +6858,10 @@ var ptBR = {
5613
6858
  "financial.nav.rules": "Regras",
5614
6859
  "financial.nav.statements": "Extratos",
5615
6860
  "financial.nav.summary": "Resumo",
6861
+ "financial.invoice.filterAccount": "Plano de contas",
6862
+ "financial.invoice.filterAllAccounts": "Todos os planos",
6863
+ "financial.invoice.filterCostCenter": "Centro de custo",
6864
+ "financial.invoice.filterAllCostCenters": "Todos os centros",
5616
6865
  "financial.reconciliation.title": "Concilia\xE7\xE3o",
5617
6866
  "financial.reconciliation.subtitle": "Concilie as linhas importadas do banco com seus lan\xE7amentos",
5618
6867
  "financial.reconciliation.pending": "{{count}} para conciliar",
@@ -5727,6 +6976,36 @@ var ptBR = {
5727
6976
  "financial.summary.totalBalance": "Saldo Total",
5728
6977
  "financial.summary.transactionsAppear": "As transa\xE7\xF5es aparecer\xE3o aqui ap\xF3s serem registradas",
5729
6978
  "financial.summary.viewAll": "Ver todos",
6979
+ "financial.summary.today": "Hoje",
6980
+ "financial.summary.yesterday": "Ontem",
6981
+ "financial.summary.transactionsTitle": "Transa\xE7\xF5es",
6982
+ "financial.home.balance": "Saldo",
6983
+ "financial.home.inflow": "Entradas",
6984
+ "financial.home.outflow": "Sa\xEDdas",
6985
+ "financial.home.showBalance": "Mostrar saldo",
6986
+ "financial.home.hideBalance": "Ocultar saldo",
6987
+ "financial.home.upcomingBills": "Pr\xF3ximas contas",
6988
+ "financial.home.noBills": "Nenhuma conta a vencer.",
6989
+ "financial.home.dueToday": "Vence hoje",
6990
+ "financial.home.dueTomorrow": "Vence amanh\xE3",
6991
+ "financial.home.dueInDays": "Vence em {{count}} dias",
6992
+ "financial.home.overdueDays": "Venceu h\xE1 {{count}} dias",
6993
+ "financial.home.cards": "Cart\xF5es",
6994
+ "financial.home.currentBill": "Fatura atual",
6995
+ "financial.home.availableLimit": "Limite dispon\xEDvel",
6996
+ "financial.home.addCard": "Novo cart\xE3o",
6997
+ "financial.home.bill": "Conta",
6998
+ "financial.home.spendByCategory": "Gastos por categoria",
6999
+ "financial.home.noSpend": "Sem gastos neste m\xEAs.",
7000
+ "financial.home.category.groceries": "Mercado",
7001
+ "financial.home.category.transport": "Transporte",
7002
+ "financial.home.category.subscriptions": "Assinaturas",
7003
+ "financial.home.category.dining": "Alimenta\xE7\xE3o",
7004
+ "financial.home.category.home": "Casa",
7005
+ "financial.home.category.health": "Sa\xFAde",
7006
+ "financial.home.category.leisure": "Lazer",
7007
+ "financial.home.category.shopping": "Compras",
7008
+ "financial.home.category.other": "Outros",
5730
7009
  "financial.quickActions.newPayable": "Nova Conta a Pagar",
5731
7010
  "financial.quickActions.newPayableDesc": "Registrar uma despesa ou conta a pagar",
5732
7011
  "financial.quickActions.newReceivable": "Nova Conta a Receber",
@@ -5888,7 +7167,38 @@ var ptBR = {
5888
7167
  "financial.transfer.created": "Transfer\xEAncia registrada",
5889
7168
  "financial.transfer.failed": "Falha ao registrar transfer\xEAncia",
5890
7169
  "financial.transfer.sameAccount": "Origem e destino devem ser diferentes",
5891
- "financial.transfer.invalidAmount": "O valor deve ser maior que zero"
7170
+ "financial.transfer.invalidAmount": "O valor deve ser maior que zero",
7171
+ "financial.quickTx.title": "Nova transa\xE7\xE3o",
7172
+ "financial.quickTx.close": "Fechar",
7173
+ "financial.quickTx.newTransaction": "Nova transa\xE7\xE3o",
7174
+ "financial.quickTx.expense": "Despesa",
7175
+ "financial.quickTx.income": "Receita",
7176
+ "financial.quickTx.transfer": "Transfer\xEAncia",
7177
+ "financial.quickTx.paid": "Pago",
7178
+ "financial.quickTx.received": "Recebido",
7179
+ "financial.quickTx.yes": "Sim",
7180
+ "financial.quickTx.no": "N\xE3o",
7181
+ "financial.quickTx.category": "Categoria",
7182
+ "financial.quickTx.selectCategory": "Sem categoria",
7183
+ "financial.quickTx.account": "Conta",
7184
+ "financial.quickTx.selectAccount": "Selecione a conta",
7185
+ "financial.quickTx.fromAccount": "Conta de origem",
7186
+ "financial.quickTx.toAccount": "Conta de destino",
7187
+ "financial.quickTx.card": "Cart\xE3o",
7188
+ "financial.quickTx.recurring": "Recorrente",
7189
+ "financial.quickTx.attachment": "Anexo",
7190
+ "financial.quickTx.attachmentSoon": "Anexos em breve",
7191
+ "financial.quickTx.receipt": "Recibo",
7192
+ "financial.quickTx.receiptCapture": "Fotografar ou anexar recibo",
7193
+ "financial.quickTx.receiptAttached": "Recibo anexado",
7194
+ "financial.quickTx.receiptReplace": "Substituir",
7195
+ "financial.quickTx.receiptRemove": "Remover recibo",
7196
+ "financial.quickTx.receiptInvalid": "Escolha um arquivo de imagem",
7197
+ "financial.quickTx.sendReceipt": "Enviar recibo",
7198
+ "financial.quickTx.descriptionPlaceholder": "ex.: Mercado, Sal\xE1rio\u2026",
7199
+ "financial.quickTx.enterAmount": "Informe um valor",
7200
+ "financial.quickTx.save": "Salvar",
7201
+ "financial.quickTx.saving": "Salvando\u2026"
5892
7202
  };
5893
7203
 
5894
7204
  // src/locales/index.ts
@@ -6038,6 +7348,8 @@ function resolveConfig(options) {
6038
7348
  // Opt-in: only shown when an app explicitly enables reconciliation (bank connector present).
6039
7349
  reconciliation: options?.modules?.reconciliation === true
6040
7350
  },
7351
+ // Opt-in: only render the plugin's own quick-add buttons/FAB when the host asks.
7352
+ quickAdd: options?.quickAdd === true,
6041
7353
  labels: { ...DEFAULT_LABELS, ...options?.labels },
6042
7354
  currency: { ...DEFAULT_CURRENCY, ...options?.currency },
6043
7355
  itemTypes: options?.itemTypes ?? DEFAULT_ITEM_TYPES,
@@ -6058,9 +7370,10 @@ function createFinancialPlugin(options) {
6058
7370
  const provider = options?.dataProvider ?? createSafeFinancialProvider();
6059
7371
  const store = createFinancialStore(provider);
6060
7372
  const dashboardWidgets = createFinancialDashboardWidgets({ config, provider, store });
6061
- const PersonStatementBound = (props) => React14.createElement(PersonStatementWidget, { person: props.item, provider, config });
7373
+ const registries = [...financialRegistries, ...options?.settingsRegistries ?? []];
7374
+ const PersonStatementBound = (props) => React17__default.createElement(PersonStatementWidget, { person: props.item, provider, config });
6062
7375
  PersonStatementBound.displayName = "FinancialPersonStatement";
6063
- const PageComponent = () => React14.createElement(FinancialPage, { config, provider, store, registries: financialRegistries });
7376
+ const PageComponent = () => React17__default.createElement(FinancialPage, { config, provider, store, registries });
6064
7377
  return {
6065
7378
  id: "financial",
6066
7379
  name: config.labels.pageTitle,
@@ -6165,18 +7478,18 @@ function createFinancialPlugin(options) {
6165
7478
  ]
6166
7479
  }
6167
7480
  ],
6168
- registries: financialRegistries,
7481
+ registries,
6169
7482
  settings: [
6170
7483
  {
6171
7484
  id: "financial",
6172
7485
  label: "Financial",
6173
7486
  icon: "DollarSign",
6174
7487
  component: (() => {
6175
- const FinancialSettingsTab = () => React14.createElement(PluginSettingsPanel, {
7488
+ const FinancialSettingsTab = () => React17__default.createElement(PluginSettingsPanel, {
6176
7489
  title: "Financial Settings",
6177
7490
  subtitle: "Payment methods, accounts, and configurations",
6178
- generalSettings: React14.createElement(FinancialGeneralSettings),
6179
- registries: financialRegistries,
7491
+ generalSettings: React17__default.createElement(FinancialGeneralSettings),
7492
+ registries,
6180
7493
  hostPluginId: "financial",
6181
7494
  routeBase: "/settings/financial"
6182
7495
  });
@@ -6191,6 +7504,6 @@ function createFinancialPlugin(options) {
6191
7504
  };
6192
7505
  }
6193
7506
 
6194
- export { createFinancialPlugin, createSafeFinancialProvider };
7507
+ export { createFinancialPlugin, createMockFinancialProvider, createSafeFinancialProvider, openQuickAdd };
6195
7508
  //# sourceMappingURL=index.js.map
6196
7509
  //# sourceMappingURL=index.js.map