@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.cjs CHANGED
@@ -1,17 +1,33 @@
1
1
  'use strict';
2
2
 
3
- var React14 = require('react');
3
+ var React17 = require('react');
4
4
  var core = require('@fayz-ai/core');
5
5
  var ui = require('@fayz-ai/ui');
6
6
  var saas = require('@fayz-ai/saas');
7
- var jsxRuntime = require('react/jsx-runtime');
8
7
  var lucideReact = require('lucide-react');
8
+ var jsxRuntime = require('react/jsx-runtime');
9
9
  var reactDom = require('react-dom');
10
10
  var vanilla = require('zustand/vanilla');
11
11
 
12
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
+ function _interopNamespace(e) {
13
+ if (e && e.__esModule) return e;
14
+ var n = Object.create(null);
15
+ if (e) {
16
+ Object.keys(e).forEach(function (k) {
17
+ if (k !== 'default') {
18
+ var d = Object.getOwnPropertyDescriptor(e, k);
19
+ Object.defineProperty(n, k, d.get ? d : {
20
+ enumerable: true,
21
+ get: function () { return e[k]; }
22
+ });
23
+ }
24
+ });
25
+ }
26
+ n.default = e;
27
+ return Object.freeze(n);
28
+ }
13
29
 
14
- var React14__default = /*#__PURE__*/_interopDefault(React14);
30
+ var React17__namespace = /*#__PURE__*/_interopNamespace(React17);
15
31
 
16
32
  // src/index.ts
17
33
  var ctx = saas.createPluginContext("FinancialPage");
@@ -20,7 +36,628 @@ var useFinancialConfig = ctx.useConfig;
20
36
  var useFinancialProvider = ctx.useProvider;
21
37
  var useFinancialStore = ctx.useStore;
22
38
  function SummaryView() {
23
- return /* @__PURE__ */ jsxRuntime.jsx(ui.DashboardCanvas, { surface: "plugin-home", domain: "financial", showHeader: false, className: "space-y-6" });
39
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
40
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "md:hidden", children: /* @__PURE__ */ jsxRuntime.jsx(MobileSummary, {}) }),
41
+ /* @__PURE__ */ jsxRuntime.jsx(
42
+ ui.DashboardCanvas,
43
+ {
44
+ surface: "plugin-home",
45
+ domain: "financial",
46
+ showHeader: false,
47
+ className: "hidden space-y-6 md:block"
48
+ }
49
+ )
50
+ ] });
51
+ }
52
+ function categoryIcon(label) {
53
+ const s = label.toLowerCase();
54
+ if (/(mercado|super|padaria|extra|p[aã]o|hortifr)/.test(s)) return lucideReact.ShoppingCart;
55
+ if (/(uber|99|posto|shell|gasol|combust|carro)/.test(s)) return lucideReact.Car;
56
+ if (/(aluguel|casa|condom|im[oó]vel)/.test(s)) return lucideReact.Home;
57
+ if (/(ifood|restaurante|outback|lanche|food|bar\b)/.test(s)) return lucideReact.Utensils;
58
+ if (/(luz|energia|enel|internet|vivo|conta de|[aá]gua)/.test(s)) return lucideReact.Zap;
59
+ if (/(cinema|netflix|cinemark|filme|disney|prime)/.test(s)) return lucideReact.Film;
60
+ if (/(farm|drogasil|sa[uú]de|plano|hospital|m[eé]dic)/.test(s)) return lucideReact.HeartPulse;
61
+ if (/(academia|smartfit|gym|treino)/.test(s)) return lucideReact.Dumbbell;
62
+ if (/(spotify|music|deezer)/.test(s)) return lucideReact.Music;
63
+ if (/(amazon|shopping|loja|magaz|americanas|mercado livre)/.test(s)) return lucideReact.ShoppingBag;
64
+ if (/(fatura|cart[aã]o|nubank|banco|boleto)/.test(s)) return lucideReact.Landmark;
65
+ if (/(sal[aá]rio|freel|pix|reembolso|receb|renda|d[ií]vidend)/.test(s)) return lucideReact.Wallet;
66
+ return lucideReact.CircleDollarSign;
67
+ }
68
+ function useDateLabel() {
69
+ const t2 = core.useTranslation();
70
+ const { currency } = useFinancialConfig();
71
+ return React17.useMemo(() => {
72
+ const todayStr = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
73
+ const y = /* @__PURE__ */ new Date();
74
+ y.setDate(y.getDate() - 1);
75
+ const yesterdayStr = y.toISOString().slice(0, 10);
76
+ return (dateStr) => {
77
+ if (dateStr === todayStr) return t2("financial.summary.today");
78
+ if (dateStr === yesterdayStr) return t2("financial.summary.yesterday");
79
+ try {
80
+ return (/* @__PURE__ */ new Date(`${dateStr}T00:00:00`)).toLocaleDateString(currency.locale, {
81
+ weekday: "short",
82
+ day: "2-digit",
83
+ month: "short"
84
+ });
85
+ } catch {
86
+ return dateStr;
87
+ }
88
+ };
89
+ }, [t2, currency.locale]);
90
+ }
91
+ function MobileSummary() {
92
+ const t2 = core.useTranslation();
93
+ const { currency } = useFinancialConfig();
94
+ const summary = useFinancialStore((s) => s.summary);
95
+ const statement = useFinancialStore((s) => s.statement);
96
+ const bankAccounts = useFinancialStore((s) => s.bankAccounts);
97
+ const fetchSummary = useFinancialStore((s) => s.fetchSummary);
98
+ const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
99
+ const fetchStatement = useFinancialStore((s) => s.fetchStatement);
100
+ const dateLabel = useDateLabel();
101
+ React17.useEffect(() => {
102
+ void fetchSummary();
103
+ void fetchBankAccounts();
104
+ const to = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
105
+ const from = /* @__PURE__ */ new Date();
106
+ from.setDate(from.getDate() - 90);
107
+ void fetchStatement({ dateRange: { from: from.toISOString().slice(0, 10), to } });
108
+ }, []);
109
+ const fmt = (n) => saas.formatCurrency(n, currency);
110
+ const inflow = summary?.monthlyInflow ?? 0;
111
+ const outflow = summary?.monthlyOutflow ?? 0;
112
+ const accountName = React17.useMemo(() => {
113
+ const map = new Map(bankAccounts.map((a) => [a.id, a.name]));
114
+ return (id) => id ? map.get(id) ?? "" : "";
115
+ }, [bankAccounts]);
116
+ const groups = React17.useMemo(() => {
117
+ const entries = (statement?.entries ?? []).filter((e) => e.entryKind === "movement");
118
+ const byDate = /* @__PURE__ */ new Map();
119
+ for (const e of entries) {
120
+ const d = e.movement.paymentDate ?? e.movement.dueDate;
121
+ const bucket = byDate.get(d);
122
+ if (bucket) bucket.push(e);
123
+ else byDate.set(d, [e]);
124
+ }
125
+ return [...byDate.entries()].sort((a, b) => b[0].localeCompare(a[0]));
126
+ }, [statement]);
127
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
128
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
129
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "col-span-2 rounded-xl border bg-card p-4", children: [
130
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.summary.totalBalance") }),
131
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-2xl font-semibold", children: fmt(summary?.totalBalance ?? 0) })
132
+ ] }),
133
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-xl border bg-card p-3", children: [
134
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("financial.summary.income") }),
135
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-base font-semibold text-success", children: fmt(inflow) })
136
+ ] }),
137
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-xl border bg-card p-3", children: [
138
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("financial.summary.expenses") }),
139
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-base font-semibold text-destructive", children: fmt(outflow) })
140
+ ] })
141
+ ] }),
142
+ groups.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-xl border border-dashed py-12 text-center text-sm text-muted-foreground", children: t2("financial.summary.noTransactions") }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: groups.map(([date, rows]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
143
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-1 text-xs font-medium uppercase tracking-wide text-muted-foreground", children: dateLabel(date) }),
144
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y overflow-hidden rounded-xl border bg-card", children: rows.map((e) => {
145
+ const isCredit = e.movement.direction === "credit";
146
+ const title = e.invoice?.contactName || e.movement.notes || t2("financial.summary.transactionsTitle");
147
+ const Icon = categoryIcon(title);
148
+ const category = isCredit ? t2("financial.summary.income") : t2("financial.summary.expenses");
149
+ const acct = accountName(e.movement.bankAccountId);
150
+ const subtitle = acct ? `${category} \xB7 ${acct}` : category;
151
+ const receiptUrl = e.invoice?.metadata?.receiptUrl;
152
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-3 py-2.5", children: [
153
+ /* @__PURE__ */ jsxRuntime.jsx(
154
+ "span",
155
+ {
156
+ 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"}`,
157
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4" })
158
+ }
159
+ ),
160
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
161
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center gap-1.5 truncate text-sm font-medium", children: [
162
+ title,
163
+ receiptUrl && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Paperclip, { className: "h-3 w-3 shrink-0 text-muted-foreground", "aria-label": t2("financial.quickTx.receiptAttached") })
164
+ ] }),
165
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-xs text-muted-foreground", children: subtitle })
166
+ ] }),
167
+ receiptUrl && /* @__PURE__ */ jsxRuntime.jsx(
168
+ "img",
169
+ {
170
+ src: receiptUrl,
171
+ alt: t2("financial.quickTx.receiptAttached"),
172
+ className: "h-9 w-9 shrink-0 rounded-md border object-cover"
173
+ }
174
+ ),
175
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: `shrink-0 text-sm font-semibold ${isCredit ? "text-success" : "text-destructive"}`, children: [
176
+ isCredit ? "+" : "-",
177
+ fmt(e.net)
178
+ ] })
179
+ ] }, e.movement.id);
180
+ }) })
181
+ ] }, date)) })
182
+ ] });
183
+ }
184
+ function typeTheme(type) {
185
+ switch (type) {
186
+ case "income":
187
+ return { header: "bg-success text-success-foreground", ring: "ring-success", icon: lucideReact.ArrowDownCircle };
188
+ case "transfer":
189
+ return { header: "bg-muted text-foreground", ring: "ring-ring", icon: lucideReact.ArrowLeftRight };
190
+ case "expense":
191
+ default:
192
+ return { header: "bg-destructive text-destructive-foreground", ring: "ring-destructive", icon: lucideReact.ArrowUpCircle };
193
+ }
194
+ }
195
+ function ChipSelect({ icon: Icon, label, value, options, onChange, emptyLabel }) {
196
+ const [open, setOpen] = React17.useState(false);
197
+ const ref = React17.useRef(null);
198
+ React17.useEffect(() => {
199
+ if (!open) return;
200
+ function onDoc(e) {
201
+ if (ref.current && !ref.current.contains(e.target)) setOpen(false);
202
+ }
203
+ document.addEventListener("mousedown", onDoc);
204
+ return () => document.removeEventListener("mousedown", onDoc);
205
+ }, [open]);
206
+ const selected = options.find((o) => o.id === value);
207
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref, className: "relative", children: [
208
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-muted-foreground", children: label }),
209
+ /* @__PURE__ */ jsxRuntime.jsxs(
210
+ "button",
211
+ {
212
+ type: "button",
213
+ onClick: () => setOpen((v) => !v),
214
+ 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",
215
+ children: [
216
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
217
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: `flex-1 truncate ${selected ? "" : "text-muted-foreground"}`, children: selected?.name ?? emptyLabel }),
218
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: `h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform ${open ? "rotate-180" : ""}` })
219
+ ]
220
+ }
221
+ ),
222
+ open && /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx("p", { className: "px-3 py-4 text-center text-xs text-muted-foreground", children: emptyLabel }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
223
+ value != null && /* @__PURE__ */ jsxRuntime.jsxs(
224
+ "button",
225
+ {
226
+ onClick: () => {
227
+ onChange(void 0);
228
+ setOpen(false);
229
+ },
230
+ className: "flex w-full items-center gap-2 px-3 py-2.5 text-left text-xs text-muted-foreground hover:bg-muted/50",
231
+ children: [
232
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-3.5 w-3.5" }),
233
+ " ",
234
+ emptyLabel
235
+ ]
236
+ }
237
+ ),
238
+ options.map((o) => /* @__PURE__ */ jsxRuntime.jsxs(
239
+ "button",
240
+ {
241
+ onClick: () => {
242
+ onChange(o.id);
243
+ setOpen(false);
244
+ },
245
+ 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" : ""}`,
246
+ children: [
247
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1 truncate", children: o.name }),
248
+ o.hint && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-muted-foreground", children: o.hint }),
249
+ o.id === value && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-3.5 w-3.5 text-primary" })
250
+ ]
251
+ },
252
+ o.id
253
+ ))
254
+ ] }) })
255
+ ] });
256
+ }
257
+ function TogglePill({ icon: Icon, label, checked, onChange, accent }) {
258
+ return /* @__PURE__ */ jsxRuntime.jsxs(
259
+ "button",
260
+ {
261
+ type: "button",
262
+ onClick: () => onChange(!checked),
263
+ 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"}`,
264
+ children: [
265
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-3.5 w-3.5" }),
266
+ label
267
+ ]
268
+ }
269
+ );
270
+ }
271
+ function QuickTransactionForm({ open, onOpenChange, defaultType = "expense", defaultAccountId, focusReceipt = false }) {
272
+ const t2 = core.useTranslation();
273
+ const { currency } = useFinancialConfig();
274
+ const provider = useFinancialProvider();
275
+ const createQuickTransaction = useFinancialStore((s) => s.createQuickTransaction);
276
+ const [type, setType] = React17.useState(defaultType);
277
+ const [amount, setAmount] = React17.useState(0);
278
+ const [amountStr, setAmountStr] = React17.useState("");
279
+ const [paid, setPaid] = React17.useState(true);
280
+ const [date, setDate] = React17.useState(() => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
281
+ const [description, setDescription] = React17.useState("");
282
+ const [categoryId, setCategoryId] = React17.useState();
283
+ const [accountId, setAccountId] = React17.useState();
284
+ const [toAccountId, setToAccountId] = React17.useState();
285
+ const [recurring, setRecurring] = React17.useState(false);
286
+ const [saving, setSaving] = React17.useState(false);
287
+ const [receiptUrl, setReceiptUrl] = React17.useState();
288
+ const fileInputRef = React17.useRef(null);
289
+ const [accounts, setAccounts] = React17.useState([]);
290
+ const [categories, setCategories] = React17.useState([]);
291
+ React17.useEffect(() => {
292
+ if (!open) return;
293
+ setType(defaultType);
294
+ setAmount(0);
295
+ setAmountStr("");
296
+ setPaid(true);
297
+ setDate((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
298
+ setDescription("");
299
+ setCategoryId(void 0);
300
+ setAccountId(defaultAccountId);
301
+ setToAccountId(void 0);
302
+ setRecurring(false);
303
+ setReceiptUrl(void 0);
304
+ }, [open, defaultType, defaultAccountId]);
305
+ React17.useEffect(() => {
306
+ if (open && focusReceipt) {
307
+ const id = window.setTimeout(() => fileInputRef.current?.click(), 250);
308
+ return () => window.clearTimeout(id);
309
+ }
310
+ }, [open, focusReceipt]);
311
+ function onPickReceipt(e) {
312
+ const file = e.target.files?.[0];
313
+ e.target.value = "";
314
+ if (!file) return;
315
+ if (!file.type.startsWith("image/")) {
316
+ ui.toast.error(t2("financial.quickTx.receiptInvalid"));
317
+ return;
318
+ }
319
+ const reader = new FileReader();
320
+ reader.onload = () => {
321
+ setReceiptUrl(typeof reader.result === "string" ? reader.result : void 0);
322
+ };
323
+ reader.onerror = () => ui.toast.error(t2("financial.quickTx.receiptInvalid"));
324
+ reader.readAsDataURL(file);
325
+ }
326
+ React17.useEffect(() => {
327
+ if (!open) return;
328
+ let cancelled = false;
329
+ void (async () => {
330
+ const [accs, coa] = await Promise.all([
331
+ provider.getBankAccounts(),
332
+ provider.getChartOfAccounts().catch(() => [])
333
+ ]);
334
+ if (cancelled) return;
335
+ setAccounts(accs);
336
+ setCategories(coa);
337
+ setAccountId((prev) => prev ?? accs[0]?.id);
338
+ })();
339
+ return () => {
340
+ cancelled = true;
341
+ };
342
+ }, [open, provider]);
343
+ const theme = typeTheme(type);
344
+ const HeaderIcon = theme.icon;
345
+ const accountOptions = React17.useMemo(
346
+ () => accounts.map((a) => ({ id: a.id, name: a.name, hint: a.accountType === "credit_card" ? t2("financial.quickTx.card") : void 0 })),
347
+ [accounts, t2]
348
+ );
349
+ const categoryOptions = React17.useMemo(
350
+ () => categories.filter((c) => c.isActive).map((c) => ({ id: c.id, name: c.name, hint: c.code })),
351
+ [categories]
352
+ );
353
+ function onAmountChange(raw) {
354
+ const cleaned = raw.replace(/[^\d.,]/g, "").replace(",", ".");
355
+ setAmountStr(raw.replace(/[^\d.,]/g, ""));
356
+ const n = Number.parseFloat(cleaned);
357
+ setAmount(Number.isFinite(n) ? n : 0);
358
+ }
359
+ const canSave = amount > 0 && !saving && (type === "transfer" ? !!accountId && !!toAccountId && accountId !== toAccountId : true);
360
+ async function handleSave() {
361
+ if (!canSave) {
362
+ if (type === "transfer" && accountId && accountId === toAccountId) {
363
+ ui.toast.error(t2("financial.transfer.sameAccount"));
364
+ }
365
+ return;
366
+ }
367
+ setSaving(true);
368
+ try {
369
+ await createQuickTransaction({
370
+ type,
371
+ amount,
372
+ date,
373
+ paid: type === "transfer" ? true : paid,
374
+ description: description || void 0,
375
+ categoryId: type === "transfer" ? void 0 : categoryId,
376
+ bankAccountId: accountId,
377
+ toAccountId: type === "transfer" ? toAccountId : void 0,
378
+ recurring: type === "transfer" ? false : recurring,
379
+ receiptUrl: type === "transfer" ? void 0 : receiptUrl
380
+ });
381
+ onOpenChange(false);
382
+ } catch {
383
+ } finally {
384
+ setSaving(false);
385
+ }
386
+ }
387
+ const TYPES = [
388
+ { id: "expense", label: t2("financial.quickTx.expense") },
389
+ { id: "income", label: t2("financial.quickTx.income") },
390
+ { id: "transfer", label: t2("financial.quickTx.transfer") }
391
+ ];
392
+ return /* @__PURE__ */ jsxRuntime.jsx(ui.Modal, { open, onOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.ModalContent, { size: "md", noPadding: true, hideClose: true, className: "overflow-hidden", children: [
393
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${theme.header} shrink-0 px-5 pb-5 pt-5`, children: [
394
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
395
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
396
+ /* @__PURE__ */ jsxRuntime.jsx(HeaderIcon, { className: "h-5 w-5" }),
397
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold", children: t2("financial.quickTx.title") })
398
+ ] }),
399
+ /* @__PURE__ */ jsxRuntime.jsx(
400
+ "button",
401
+ {
402
+ onClick: () => onOpenChange(false),
403
+ className: "flex h-7 w-7 items-center justify-center rounded-full opacity-80 transition-colors hover:bg-black/10 hover:opacity-100",
404
+ "aria-label": t2("financial.quickTx.close"),
405
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" })
406
+ }
407
+ )
408
+ ] }),
409
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 flex gap-1 rounded-full bg-black/15 p-1", children: TYPES.map((ty) => /* @__PURE__ */ jsxRuntime.jsx(
410
+ "button",
411
+ {
412
+ onClick: () => setType(ty.id),
413
+ 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"}`,
414
+ children: ty.label
415
+ },
416
+ ty.id
417
+ )) }),
418
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-5 flex items-baseline justify-center gap-2", children: [
419
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xl font-medium opacity-90", children: currency.symbol }),
420
+ /* @__PURE__ */ jsxRuntime.jsx(
421
+ "input",
422
+ {
423
+ type: "text",
424
+ inputMode: "decimal",
425
+ autoFocus: true,
426
+ value: amountStr,
427
+ onChange: (e) => onAmountChange(e.target.value),
428
+ onKeyDown: (e) => {
429
+ if (e.key === "Enter") void handleSave();
430
+ },
431
+ placeholder: "0,00",
432
+ className: "w-full max-w-[220px] bg-transparent text-center text-4xl font-bold tracking-tight outline-none placeholder:opacity-40"
433
+ }
434
+ )
435
+ ] })
436
+ ] }),
437
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 space-y-4 overflow-y-auto px-5 py-5", children: [
438
+ type !== "transfer" && /* @__PURE__ */ jsxRuntime.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: [
439
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: type === "income" ? t2("financial.quickTx.received") : t2("financial.quickTx.paid") }),
440
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-1", children: [
441
+ /* @__PURE__ */ jsxRuntime.jsx(
442
+ "button",
443
+ {
444
+ onClick: () => setPaid(true),
445
+ className: `rounded-full px-3 py-1 text-xs font-medium transition-colors ${paid ? "bg-success text-success-foreground" : "bg-muted text-muted-foreground"}`,
446
+ children: t2("financial.quickTx.yes")
447
+ }
448
+ ),
449
+ /* @__PURE__ */ jsxRuntime.jsx(
450
+ "button",
451
+ {
452
+ onClick: () => setPaid(false),
453
+ className: `rounded-full px-3 py-1 text-xs font-medium transition-colors ${!paid ? "bg-warning text-warning-foreground" : "bg-muted text-muted-foreground"}`,
454
+ children: t2("financial.quickTx.no")
455
+ }
456
+ )
457
+ ] })
458
+ ] }),
459
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
460
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mb-1 flex items-center gap-1.5 text-xs font-medium text-muted-foreground", children: [
461
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarDays, { className: "h-3.5 w-3.5" }),
462
+ " ",
463
+ t2("financial.invoiceForm.date")
464
+ ] }),
465
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DatePicker, { value: date, onChange: setDate })
466
+ ] }),
467
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
468
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.description") }),
469
+ /* @__PURE__ */ jsxRuntime.jsx(
470
+ "input",
471
+ {
472
+ type: "text",
473
+ value: description,
474
+ onChange: (e) => setDescription(e.target.value),
475
+ onKeyDown: (e) => {
476
+ if (e.key === "Enter") void handleSave();
477
+ },
478
+ placeholder: t2("financial.quickTx.descriptionPlaceholder"),
479
+ 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"
480
+ }
481
+ )
482
+ ] }),
483
+ type !== "transfer" && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
484
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "mb-1 flex items-center gap-1.5 text-xs font-medium text-muted-foreground", children: [
485
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Paperclip, { className: "h-3.5 w-3.5" }),
486
+ " ",
487
+ t2("financial.quickTx.receipt")
488
+ ] }),
489
+ /* @__PURE__ */ jsxRuntime.jsx(
490
+ "input",
491
+ {
492
+ ref: fileInputRef,
493
+ type: "file",
494
+ accept: "image/*",
495
+ capture: "environment",
496
+ className: "hidden",
497
+ onChange: onPickReceipt
498
+ }
499
+ ),
500
+ receiptUrl ? /* @__PURE__ */ jsxRuntime.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: [
501
+ /* @__PURE__ */ jsxRuntime.jsx(
502
+ "img",
503
+ {
504
+ src: receiptUrl,
505
+ alt: t2("financial.quickTx.receipt"),
506
+ className: "h-16 w-16 shrink-0 rounded-md border object-cover"
507
+ }
508
+ ),
509
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [
510
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-medium", children: t2("financial.quickTx.receiptAttached") }),
511
+ /* @__PURE__ */ jsxRuntime.jsx(
512
+ "button",
513
+ {
514
+ type: "button",
515
+ onClick: () => fileInputRef.current?.click(),
516
+ className: "text-xs text-primary hover:underline",
517
+ children: t2("financial.quickTx.receiptReplace")
518
+ }
519
+ )
520
+ ] }),
521
+ /* @__PURE__ */ jsxRuntime.jsx(
522
+ "button",
523
+ {
524
+ type: "button",
525
+ onClick: () => setReceiptUrl(void 0),
526
+ "aria-label": t2("financial.quickTx.receiptRemove"),
527
+ className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-muted-foreground transition-colors hover:bg-muted",
528
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" })
529
+ }
530
+ )
531
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
532
+ "button",
533
+ {
534
+ type: "button",
535
+ onClick: () => fileInputRef.current?.click(),
536
+ 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"}`,
537
+ children: [
538
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Camera, { className: "h-4 w-4" }),
539
+ " ",
540
+ t2("financial.quickTx.receiptCapture")
541
+ ]
542
+ }
543
+ )
544
+ ] }),
545
+ type === "transfer" ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-3", children: [
546
+ /* @__PURE__ */ jsxRuntime.jsx(
547
+ ChipSelect,
548
+ {
549
+ icon: lucideReact.Wallet,
550
+ label: t2("financial.quickTx.fromAccount"),
551
+ value: accountId,
552
+ options: accountOptions,
553
+ onChange: setAccountId,
554
+ emptyLabel: t2("financial.quickTx.selectAccount")
555
+ }
556
+ ),
557
+ /* @__PURE__ */ jsxRuntime.jsx(
558
+ ChipSelect,
559
+ {
560
+ icon: lucideReact.Wallet,
561
+ label: t2("financial.quickTx.toAccount"),
562
+ value: toAccountId,
563
+ options: accountOptions.filter((o) => o.id !== accountId),
564
+ onChange: setToAccountId,
565
+ emptyLabel: t2("financial.quickTx.selectAccount")
566
+ }
567
+ )
568
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-3", children: [
569
+ /* @__PURE__ */ jsxRuntime.jsx(
570
+ ChipSelect,
571
+ {
572
+ icon: lucideReact.Tag,
573
+ label: t2("financial.quickTx.category"),
574
+ value: categoryId,
575
+ options: categoryOptions,
576
+ onChange: setCategoryId,
577
+ emptyLabel: t2("financial.quickTx.selectCategory")
578
+ }
579
+ ),
580
+ /* @__PURE__ */ jsxRuntime.jsx(
581
+ ChipSelect,
582
+ {
583
+ icon: lucideReact.Wallet,
584
+ label: t2("financial.quickTx.account"),
585
+ value: accountId,
586
+ options: accountOptions,
587
+ onChange: setAccountId,
588
+ emptyLabel: t2("financial.quickTx.selectAccount")
589
+ }
590
+ )
591
+ ] }),
592
+ type !== "transfer" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-2", children: [
593
+ /* @__PURE__ */ jsxRuntime.jsx(
594
+ TogglePill,
595
+ {
596
+ icon: lucideReact.Repeat,
597
+ label: t2("financial.quickTx.recurring"),
598
+ checked: recurring,
599
+ onChange: setRecurring
600
+ }
601
+ ),
602
+ /* @__PURE__ */ jsxRuntime.jsxs(
603
+ "button",
604
+ {
605
+ type: "button",
606
+ onClick: () => fileInputRef.current?.click(),
607
+ title: t2("financial.quickTx.attachment"),
608
+ 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"}`,
609
+ children: [
610
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Paperclip, { className: "h-3.5 w-3.5" }),
611
+ " ",
612
+ t2("financial.quickTx.attachment"),
613
+ receiptUrl && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-3.5 w-3.5" })
614
+ ]
615
+ }
616
+ )
617
+ ] })
618
+ ] }),
619
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex shrink-0 items-center gap-3 border-t px-5 py-3", children: [
620
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: amount > 0 ? saas.formatCurrency(amount, currency) : t2("financial.quickTx.enterAmount") }),
621
+ /* @__PURE__ */ jsxRuntime.jsxs(
622
+ ui.Button,
623
+ {
624
+ className: "ml-auto",
625
+ onClick: () => void handleSave(),
626
+ disabled: !canSave,
627
+ children: [
628
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4" }),
629
+ saving ? t2("financial.quickTx.saving") : t2("financial.quickTx.save")
630
+ ]
631
+ }
632
+ )
633
+ ] })
634
+ ] }) });
635
+ }
636
+ var pending = null;
637
+ var listeners = /* @__PURE__ */ new Set();
638
+ function emit() {
639
+ for (const l of listeners) l();
640
+ }
641
+ function openQuickAdd(type = "expense", receipt = false) {
642
+ pending = { type, receipt };
643
+ emit();
644
+ }
645
+ function consumeQuickAdd() {
646
+ const req = pending;
647
+ pending = null;
648
+ return req;
649
+ }
650
+ function subscribe(cb) {
651
+ listeners.add(cb);
652
+ return () => {
653
+ listeners.delete(cb);
654
+ };
655
+ }
656
+ function getSnapshot() {
657
+ return pending;
658
+ }
659
+ function usePendingQuickAdd() {
660
+ return React17__namespace.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
24
661
  }
25
662
  var STATUS_OPTIONS = [
26
663
  { value: "open", labelKey: "financial.invoice.statusOpen", color: "bg-info-soft text-info-soft-foreground", icon: lucideReact.CircleDashed },
@@ -41,7 +678,7 @@ function StatusBadge({ status }) {
41
678
  }
42
679
  function useInvoiceColumns(currency) {
43
680
  const t2 = core.useTranslation();
44
- return React14.useMemo(() => [
681
+ return React17.useMemo(() => [
45
682
  {
46
683
  accessorKey: "fiscalNumber",
47
684
  header: "#",
@@ -93,27 +730,49 @@ function useInvoiceColumns(currency) {
93
730
  function InvoiceListView({ direction, onNew, onEdit }) {
94
731
  const t2 = core.useTranslation();
95
732
  const { currency } = useFinancialConfig();
733
+ const provider = useFinancialProvider();
96
734
  const invoices = useFinancialStore((s) => s.invoices);
97
735
  useFinancialStore((s) => s.invoicesTotal);
98
736
  const invoicesLoading = useFinancialStore((s) => s.invoicesLoading);
99
737
  const fetchInvoices = useFinancialStore((s) => s.fetchInvoices);
100
- const [search, setSearch] = React14.useState("");
101
- const [statusFilter, setStatusFilter] = React14.useState([]);
102
- React14.useEffect(() => {
738
+ const [search, setSearch] = React17.useState("");
739
+ const [statusFilter, setStatusFilter] = React17.useState([]);
740
+ const [accountId, setAccountId] = React17.useState("");
741
+ const [costCenterId, setCostCenterId] = React17.useState("");
742
+ const [accounts, setAccounts] = React17.useState([]);
743
+ const [costCenters, setCostCenters] = React17.useState([]);
744
+ React17.useEffect(() => {
745
+ let active = true;
746
+ Promise.all([provider.getChartOfAccounts(), provider.getCostCenters()]).then(([accountRows, costCenterRows]) => {
747
+ if (!active) return;
748
+ setAccounts(accountRows.filter((account) => account.nodeType === "leaf"));
749
+ setCostCenters(costCenterRows);
750
+ }).catch(() => {
751
+ if (!active) return;
752
+ setAccounts([]);
753
+ setCostCenters([]);
754
+ });
755
+ return () => {
756
+ active = false;
757
+ };
758
+ }, [provider]);
759
+ React17.useEffect(() => {
103
760
  fetchInvoices({
104
761
  direction,
105
762
  status: statusFilter.length > 0 ? statusFilter : void 0,
106
- search: search || void 0
763
+ search: search || void 0,
764
+ accountId: accountId || void 0,
765
+ costCenterId: costCenterId || void 0
107
766
  });
108
- }, [direction, statusFilter, search]);
767
+ }, [direction, statusFilter, search, accountId, costCenterId]);
109
768
  const filtered = invoices.filter((inv) => inv.direction === direction);
110
769
  const listTitle = direction === "debit" ? t2("financial.invoice.accountsPayable") : t2("financial.invoice.accountsReceivable");
111
770
  const columns = useInvoiceColumns(currency);
112
- const tags = React14.useMemo(
771
+ const tags = React17.useMemo(
113
772
  () => STATUS_OPTIONS.filter((o) => o.value !== "cancelled").map((o) => ({ value: o.value, label: t2(o.labelKey) })),
114
773
  [t2]
115
774
  );
116
- const summary = React14.useMemo(() => {
775
+ const summary = React17.useMemo(() => {
117
776
  let totalAmount = 0;
118
777
  let paidAmount = 0;
119
778
  let openAmount = 0;
@@ -135,6 +794,46 @@ function InvoiceListView({ direction, onNew, onEdit }) {
135
794
  subtitle: t2("financial.invoice.invoices", { count: filtered.length })
136
795
  }
137
796
  ),
797
+ (accounts.length > 0 || costCenters.length > 0) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-end gap-3 rounded-lg border bg-muted/20 px-4 py-3", children: [
798
+ accounts.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex min-w-[220px] flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
799
+ t2("financial.invoice.filterAccount"),
800
+ /* @__PURE__ */ jsxRuntime.jsxs(
801
+ "select",
802
+ {
803
+ className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
804
+ value: accountId,
805
+ onChange: (event) => setAccountId(event.target.value),
806
+ children: [
807
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: t2("financial.invoice.filterAllAccounts") }),
808
+ accounts.map((account) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: account.id, children: [
809
+ account.code,
810
+ " - ",
811
+ account.name
812
+ ] }, account.id))
813
+ ]
814
+ }
815
+ )
816
+ ] }),
817
+ costCenters.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex min-w-[220px] flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
818
+ t2("financial.invoice.filterCostCenter"),
819
+ /* @__PURE__ */ jsxRuntime.jsxs(
820
+ "select",
821
+ {
822
+ className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
823
+ value: costCenterId,
824
+ onChange: (event) => setCostCenterId(event.target.value),
825
+ children: [
826
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: t2("financial.invoice.filterAllCostCenters") }),
827
+ costCenters.map((costCenter) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: costCenter.id, children: [
828
+ costCenter.code,
829
+ " - ",
830
+ costCenter.name
831
+ ] }, costCenter.id))
832
+ ]
833
+ }
834
+ )
835
+ ] })
836
+ ] }),
138
837
  /* @__PURE__ */ jsxRuntime.jsx(
139
838
  ui.ListView,
140
839
  {
@@ -177,10 +876,10 @@ function InvoiceListView({ direction, onNew, onEdit }) {
177
876
  }
178
877
  function LocationSelector({ locations, value, onChange }) {
179
878
  const t2 = core.useTranslation();
180
- const [open, setOpen] = React14.useState(false);
181
- const [search, setSearch] = React14.useState("");
182
- const ref = React14.useRef(null);
183
- React14.useEffect(() => {
879
+ const [open, setOpen] = React17.useState(false);
880
+ const [search, setSearch] = React17.useState("");
881
+ const ref = React17.useRef(null);
882
+ React17.useEffect(() => {
184
883
  function handler(e) {
185
884
  if (ref.current && !ref.current.contains(e.target)) setOpen(false);
186
885
  }
@@ -260,7 +959,7 @@ function ItemRow({ item, index, itemTypes, currency, expanded, onToggle, onUpdat
260
959
  const total = item.quantity * item.unitPrice - (item.discount ?? 0) + (item.surcharge ?? 0);
261
960
  const kindLabel = itemTypes.find((tp) => tp.value === item.itemKind)?.label ?? item.itemKind;
262
961
  const hasAdjustments = (item.discount ?? 0) > 0 || (item.surcharge ?? 0) > 0;
263
- const [showAdjustments, setShowAdjustments] = React14.useState(hasAdjustments);
962
+ const [showAdjustments, setShowAdjustments] = React17.useState(hasAdjustments);
264
963
  function handleFieldKeyDown(e) {
265
964
  if (e.key === "Enter" && !e.shiftKey) {
266
965
  e.preventDefault();
@@ -496,25 +1195,25 @@ function InvoiceFormView({ direction, editId, onSaved }) {
496
1195
  const createInvoice = useFinancialStore((s) => s.createInvoice);
497
1196
  const hasLocations = locations.length > 0;
498
1197
  const isEdit = !!editId;
499
- const [loaded, setLoaded] = React14.useState(!editId);
500
- const [loading, setLoading] = React14.useState(!!editId);
501
- const [contactName, setContactName] = React14.useState("");
502
- const [contactId, setContactId] = React14.useState();
503
- const [unitId, setUnitId] = React14.useState(locations[0]?.id ?? "");
504
- const [invoiceDate, setInvoiceDate] = React14.useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
505
- const [fiscalNumber, setFiscalNumber] = React14.useState("");
506
- const [observations, setObservations] = React14.useState("");
507
- const [items, setItems] = React14.useState([]);
508
- const [expandedItemId, setExpandedItemId] = React14.useState(null);
509
- const [addingItem, setAddingItem] = React14.useState(false);
510
- const [installments, setInstallments] = React14.useState([]);
511
- const [saving, setSaving] = React14.useState(false);
512
- const [menuOpen, setMenuOpen] = React14.useState(false);
513
- const [confirmCancel, setConfirmCancel] = React14.useState(false);
514
- const [cancelling, setCancelling] = React14.useState(false);
515
- const menuRef = React14.useRef(null);
1198
+ const [loaded, setLoaded] = React17.useState(!editId);
1199
+ const [loading, setLoading] = React17.useState(!!editId);
1200
+ const [contactName, setContactName] = React17.useState("");
1201
+ const [contactId, setContactId] = React17.useState();
1202
+ const [unitId, setUnitId] = React17.useState(locations[0]?.id ?? "");
1203
+ const [invoiceDate, setInvoiceDate] = React17.useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
1204
+ const [fiscalNumber, setFiscalNumber] = React17.useState("");
1205
+ const [observations, setObservations] = React17.useState("");
1206
+ const [items, setItems] = React17.useState([]);
1207
+ const [expandedItemId, setExpandedItemId] = React17.useState(null);
1208
+ const [addingItem, setAddingItem] = React17.useState(false);
1209
+ const [installments, setInstallments] = React17.useState([]);
1210
+ const [saving, setSaving] = React17.useState(false);
1211
+ const [menuOpen, setMenuOpen] = React17.useState(false);
1212
+ const [confirmCancel, setConfirmCancel] = React17.useState(false);
1213
+ const [cancelling, setCancelling] = React17.useState(false);
1214
+ const menuRef = React17.useRef(null);
516
1215
  const cancelInvoice = useFinancialStore((s) => s.cancelInvoice);
517
- React14.useEffect(() => {
1216
+ React17.useEffect(() => {
518
1217
  if (!menuOpen) return;
519
1218
  function handleClick(e) {
520
1219
  if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false);
@@ -533,7 +1232,7 @@ function InvoiceFormView({ direction, editId, onSaved }) {
533
1232
  setCancelling(false);
534
1233
  }
535
1234
  }
536
- React14.useEffect(() => {
1235
+ React17.useEffect(() => {
537
1236
  if (!editId || loaded) return;
538
1237
  setLoading(true);
539
1238
  (async () => {
@@ -570,8 +1269,8 @@ function InvoiceFormView({ direction, editId, onSaved }) {
570
1269
  const totalAmount = items.reduce((sum, it) => {
571
1270
  return sum + it.quantity * it.unitPrice - (it.discount ?? 0) + (it.surcharge ?? 0);
572
1271
  }, 0);
573
- const prevItemCount = React14.useRef(items.length);
574
- React14.useEffect(() => {
1272
+ const prevItemCount = React17.useRef(items.length);
1273
+ React17.useEffect(() => {
575
1274
  if (items.length > 0 && prevItemCount.current === 0 && installments.length === 0 && !isEdit) {
576
1275
  const total = items.reduce((sum, it) => sum + it.quantity * it.unitPrice - (it.discount ?? 0) + (it.surcharge ?? 0), 0);
577
1276
  if (total > 0) {
@@ -580,7 +1279,7 @@ function InvoiceFormView({ direction, editId, onSaved }) {
580
1279
  }
581
1280
  prevItemCount.current = items.length;
582
1281
  }, [items.length]);
583
- React14.useEffect(() => {
1282
+ React17.useEffect(() => {
584
1283
  if (installments.length > 0 && totalAmount > 0 && !isEdit) {
585
1284
  const count = installments.length;
586
1285
  const perInstallment = Math.floor(totalAmount * 100 / count) / 100;
@@ -692,8 +1391,8 @@ function InvoiceFormView({ direction, editId, onSaved }) {
692
1391
  items: items.map(({ _id, ...rest }) => rest),
693
1392
  installments: installments.map(({ _id, ...rest }) => rest)
694
1393
  };
695
- const snapshot = React14.useRef(null);
696
- React14.useEffect(() => {
1394
+ const snapshot = React17.useRef(null);
1395
+ React17.useEffect(() => {
697
1396
  if (loaded && snapshot.current === null) snapshot.current = JSON.stringify(fields);
698
1397
  }, [loaded]);
699
1398
  const dirty = snapshot.current !== null && JSON.stringify(fields) !== snapshot.current;
@@ -1003,40 +1702,40 @@ function PaymentModal({ movement, onClose, onPaid }) {
1003
1702
  const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
1004
1703
  const fetchPaymentMethods = useFinancialStore((s) => s.fetchPaymentMethods);
1005
1704
  const remaining = movement.amount - movement.paidAmount;
1006
- const [amount, setAmount] = React14.useState(remaining);
1007
- const [paymentDate, setPaymentDate] = React14.useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
1008
- const [selectedTypeId, setSelectedTypeId] = React14.useState("");
1009
- const [paymentMethodId, setPaymentMethodId] = React14.useState("");
1010
- const [bankAccountId, setBankAccountId] = React14.useState("");
1011
- const [cardBrand, setCardBrand] = React14.useState("");
1012
- const [cardInstallments, setCardInstallments] = React14.useState(1);
1013
- const [notes, setNotes] = React14.useState("");
1014
- const [saving, setSaving] = React14.useState(false);
1015
- const [visible, setVisible] = React14.useState(false);
1016
- const [expandMethodPicker, setExpandMethodPicker] = React14.useState(true);
1017
- React14.useEffect(() => {
1705
+ const [amount, setAmount] = React17.useState(remaining);
1706
+ const [paymentDate, setPaymentDate] = React17.useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
1707
+ const [selectedTypeId, setSelectedTypeId] = React17.useState("");
1708
+ const [paymentMethodId, setPaymentMethodId] = React17.useState("");
1709
+ const [bankAccountId, setBankAccountId] = React17.useState("");
1710
+ const [cardBrand, setCardBrand] = React17.useState("");
1711
+ const [cardInstallments, setCardInstallments] = React17.useState(1);
1712
+ const [notes, setNotes] = React17.useState("");
1713
+ const [saving, setSaving] = React17.useState(false);
1714
+ const [visible, setVisible] = React17.useState(false);
1715
+ const [expandMethodPicker, setExpandMethodPicker] = React17.useState(true);
1716
+ React17.useEffect(() => {
1018
1717
  fetchBankAccounts();
1019
1718
  fetchPaymentMethods();
1020
1719
  requestAnimationFrame(() => setVisible(true));
1021
1720
  }, []);
1022
- const selectedType = React14.useMemo(
1721
+ const selectedType = React17.useMemo(
1023
1722
  () => paymentMethodTypes.find((tp) => tp.id === selectedTypeId),
1024
1723
  [paymentMethodTypes, selectedTypeId]
1025
1724
  );
1026
1725
  const txType = selectedType?.transactionType ?? "";
1027
- const filteredMethods = React14.useMemo(
1726
+ const filteredMethods = React17.useMemo(
1028
1727
  () => paymentMethods.filter((m) => m.paymentMethodTypeId === selectedTypeId),
1029
1728
  [paymentMethods, selectedTypeId]
1030
1729
  );
1031
1730
  const needsCard = txType === "credit_card" || txType === "debit_card";
1032
1731
  const needsCardInstallments = txType === "credit_card";
1033
- const eligibleAccounts = React14.useMemo(() => {
1732
+ const eligibleAccounts = React17.useMemo(() => {
1034
1733
  const allowed = selectedType?.allowedAccountTypes && selectedType.allowedAccountTypes.length ? selectedType.allowedAccountTypes : ACCOUNT_TYPES_FOR_TX[txType];
1035
1734
  if (!allowed || !allowed.length) return bankAccounts;
1036
1735
  const filtered = bankAccounts.filter((a) => allowed.includes(a.accountType));
1037
1736
  return filtered.length ? filtered : bankAccounts;
1038
1737
  }, [bankAccounts, selectedType, txType]);
1039
- React14.useEffect(() => {
1738
+ React17.useEffect(() => {
1040
1739
  if (selectedTypeId && !bankAccountId && eligibleAccounts.length) {
1041
1740
  setBankAccountId(eligibleAccounts[0].id);
1042
1741
  }
@@ -1280,19 +1979,19 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
1280
1979
  const bankAccounts = useFinancialStore((s) => s.bankAccounts);
1281
1980
  const fetchPaymentMethods = useFinancialStore((s) => s.fetchPaymentMethods);
1282
1981
  const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
1283
- const [detailsLoaded, setDetailsLoaded] = React14.useState(false);
1284
- const [detailsLoading, setDetailsLoading] = React14.useState(false);
1285
- const [invoice, setInvoice] = React14.useState(null);
1286
- const [items, setItems] = React14.useState([]);
1287
- const [movements, setMovements] = React14.useState([]);
1288
- const [payments, setPayments] = React14.useState([]);
1289
- const [loading, setLoading] = React14.useState(true);
1290
- const [payingMovement, setPayingMovement] = React14.useState(null);
1291
- const [expandedMovId, setExpandedMovId] = React14.useState(null);
1292
- const [menuOpen, setMenuOpen] = React14.useState(false);
1293
- const [confirmCancel, setConfirmCancel] = React14.useState(false);
1294
- const [cancelling, setCancelling] = React14.useState(false);
1295
- const menuRef = React14.useRef(null);
1982
+ const [detailsLoaded, setDetailsLoaded] = React17.useState(false);
1983
+ const [detailsLoading, setDetailsLoading] = React17.useState(false);
1984
+ const [invoice, setInvoice] = React17.useState(null);
1985
+ const [items, setItems] = React17.useState([]);
1986
+ const [movements, setMovements] = React17.useState([]);
1987
+ const [payments, setPayments] = React17.useState([]);
1988
+ const [loading, setLoading] = React17.useState(true);
1989
+ const [payingMovement, setPayingMovement] = React17.useState(null);
1990
+ const [expandedMovId, setExpandedMovId] = React17.useState(null);
1991
+ const [menuOpen, setMenuOpen] = React17.useState(false);
1992
+ const [confirmCancel, setConfirmCancel] = React17.useState(false);
1993
+ const [cancelling, setCancelling] = React17.useState(false);
1994
+ const menuRef = React17.useRef(null);
1296
1995
  const cancelInvoice = useFinancialStore((s) => s.cancelInvoice);
1297
1996
  async function loadData() {
1298
1997
  setLoading(true);
@@ -1311,10 +2010,10 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
1311
2010
  );
1312
2011
  setLoading(false);
1313
2012
  }
1314
- React14.useEffect(() => {
2013
+ React17.useEffect(() => {
1315
2014
  loadData();
1316
2015
  }, [invoiceId]);
1317
- React14.useEffect(() => {
2016
+ React17.useEffect(() => {
1318
2017
  if (!menuOpen) return;
1319
2018
  function handleClick(e) {
1320
2019
  if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false);
@@ -1915,16 +2614,16 @@ function CashOpeningPanel({
1915
2614
  currency,
1916
2615
  onOpen
1917
2616
  }) {
1918
- const [selectedAccountId, setSelectedAccountId] = React14.useState(cashAccounts[0]?.id ?? "");
1919
- const [mode, setMode] = React14.useState("quick");
1920
- const [quickBalance, setQuickBalance] = React14.useState(0);
1921
- const [counts, setCounts] = React14.useState({});
1922
- const [notes, setNotes] = React14.useState("");
1923
- const [showNotes, setShowNotes] = React14.useState(false);
1924
- const [confirming, setConfirming] = React14.useState(false);
1925
- const [submitting, setSubmitting] = React14.useState(false);
2617
+ const [selectedAccountId, setSelectedAccountId] = React17.useState(cashAccounts[0]?.id ?? "");
2618
+ const [mode, setMode] = React17.useState("quick");
2619
+ const [quickBalance, setQuickBalance] = React17.useState(0);
2620
+ const [counts, setCounts] = React17.useState({});
2621
+ const [notes, setNotes] = React17.useState("");
2622
+ const [showNotes, setShowNotes] = React17.useState(false);
2623
+ const [confirming, setConfirming] = React17.useState(false);
2624
+ const [submitting, setSubmitting] = React17.useState(false);
1926
2625
  const t2 = core.useTranslation();
1927
- const countedTotal = React14.useMemo(() => {
2626
+ const countedTotal = React17.useMemo(() => {
1928
2627
  return BRL_DENOMINATIONS.reduce((sum, d) => sum + (counts[d.value] ?? 0) * d.value, 0);
1929
2628
  }, [counts]);
1930
2629
  const effectiveBalance = mode === "count" ? countedTotal : quickBalance;
@@ -2100,13 +2799,13 @@ function CashClosingPanel({
2100
2799
  currency,
2101
2800
  onClose
2102
2801
  }) {
2103
- const [mode, setMode] = React14.useState("quick");
2104
- const [quickBalance, setQuickBalance] = React14.useState(0);
2105
- const [counts, setCounts] = React14.useState({});
2106
- const [notes, setNotes] = React14.useState("");
2107
- const [submitting, setSubmitting] = React14.useState(false);
2802
+ const [mode, setMode] = React17.useState("quick");
2803
+ const [quickBalance, setQuickBalance] = React17.useState(0);
2804
+ const [counts, setCounts] = React17.useState({});
2805
+ const [notes, setNotes] = React17.useState("");
2806
+ const [submitting, setSubmitting] = React17.useState(false);
2108
2807
  const t2 = core.useTranslation();
2109
- const countedTotal = React14.useMemo(() => {
2808
+ const countedTotal = React17.useMemo(() => {
2110
2809
  return BRL_DENOMINATIONS.reduce((sum, d) => sum + (counts[d.value] ?? 0) * d.value, 0);
2111
2810
  }, [counts]);
2112
2811
  const effectiveBalance = mode === "count" ? countedTotal : quickBalance;
@@ -2211,8 +2910,8 @@ function CashRegistersView() {
2211
2910
  const fetchCashSessions = useFinancialStore((s) => s.fetchCashSessions);
2212
2911
  const openCashSession = useFinancialStore((s) => s.openCashSession);
2213
2912
  const closeCashSession = useFinancialStore((s) => s.closeCashSession);
2214
- const [expandedClosing, setExpandedClosing] = React14.useState(null);
2215
- React14.useEffect(() => {
2913
+ const [expandedClosing, setExpandedClosing] = React17.useState(null);
2914
+ React17.useEffect(() => {
2216
2915
  fetchBankAccounts();
2217
2916
  fetchCashSessions();
2218
2917
  }, []);
@@ -2316,7 +3015,7 @@ function OpenAnotherCollapsible({
2316
3015
  onOpen
2317
3016
  }) {
2318
3017
  const t2 = core.useTranslation();
2319
- const [expanded, setExpanded] = React14.useState(false);
3018
+ const [expanded, setExpanded] = React17.useState(false);
2320
3019
  const availableAccounts = cashAccounts.filter((a) => !openSessions.some((s) => s.bankAccountId === a.id));
2321
3020
  if (availableAccounts.length === 0) return null;
2322
3021
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border bg-card shadow-sm", children: [
@@ -2343,14 +3042,14 @@ function TransferModal({ onClose, onDone, defaultFromId }) {
2343
3042
  const bankAccounts = useFinancialStore((s) => s.bankAccounts);
2344
3043
  const createTransfer = useFinancialStore((s) => s.createTransfer);
2345
3044
  const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
2346
- const [fromAccountId, setFromAccountId] = React14.useState(defaultFromId ?? "");
2347
- const [toAccountId, setToAccountId] = React14.useState("");
2348
- const [amount, setAmount] = React14.useState(0);
2349
- const [date, setDate] = React14.useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
2350
- const [notes, setNotes] = React14.useState("");
2351
- const [saving, setSaving] = React14.useState(false);
2352
- const [visible, setVisible] = React14.useState(false);
2353
- React14.useEffect(() => {
3045
+ const [fromAccountId, setFromAccountId] = React17.useState(defaultFromId ?? "");
3046
+ const [toAccountId, setToAccountId] = React17.useState("");
3047
+ const [amount, setAmount] = React17.useState(0);
3048
+ const [date, setDate] = React17.useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
3049
+ const [notes, setNotes] = React17.useState("");
3050
+ const [saving, setSaving] = React17.useState(false);
3051
+ const [visible, setVisible] = React17.useState(false);
3052
+ React17.useEffect(() => {
2354
3053
  fetchBankAccounts();
2355
3054
  requestAnimationFrame(() => setVisible(true));
2356
3055
  }, []);
@@ -2493,19 +3192,19 @@ function StatementsView({ onNavigate }) {
2493
3192
  const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
2494
3193
  const fetchPaymentMethods = useFinancialStore((s) => s.fetchPaymentMethods);
2495
3194
  const fetchStatement = useFinancialStore((s) => s.fetchStatement);
2496
- const [accountId, setAccountId] = React14.useState("");
2497
- const [dateFrom, setDateFrom] = React14.useState(() => {
3195
+ const [accountId, setAccountId] = React17.useState("");
3196
+ const [dateFrom, setDateFrom] = React17.useState(() => {
2498
3197
  const d = /* @__PURE__ */ new Date();
2499
3198
  d.setMonth(d.getMonth() - 3);
2500
3199
  return d.toISOString().slice(0, 10);
2501
3200
  });
2502
- const [dateTo, setDateTo] = React14.useState(() => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
2503
- const [showTransfer, setShowTransfer] = React14.useState(false);
2504
- React14.useEffect(() => {
3201
+ const [dateTo, setDateTo] = React17.useState(() => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
3202
+ const [showTransfer, setShowTransfer] = React17.useState(false);
3203
+ React17.useEffect(() => {
2505
3204
  fetchBankAccounts();
2506
3205
  fetchPaymentMethods();
2507
3206
  }, []);
2508
- const methodName = React14.useMemo(() => {
3207
+ const methodName = React17.useMemo(() => {
2509
3208
  const byId = new Map(paymentMethods.map((m) => [m.id, m.name]));
2510
3209
  const typeById = new Map(paymentMethodTypes.map((tp) => [tp.id, tp.name]));
2511
3210
  return (mov) => {
@@ -2520,7 +3219,7 @@ function StatementsView({ onNavigate }) {
2520
3219
  const base = e.movement.direction === "credit" ? "receivables" : "payables";
2521
3220
  onNavigate(`${base}-detail:${e.movement.invoiceId}`);
2522
3221
  }
2523
- React14.useEffect(() => {
3222
+ React17.useEffect(() => {
2524
3223
  if (accountId) {
2525
3224
  fetchStatement({
2526
3225
  bankAccountId: accountId === ALL ? void 0 : accountId,
@@ -2528,11 +3227,11 @@ function StatementsView({ onNavigate }) {
2528
3227
  });
2529
3228
  }
2530
3229
  }, [accountId, dateFrom, dateTo]);
2531
- React14.useEffect(() => {
3230
+ React17.useEffect(() => {
2532
3231
  if (!accountId) setAccountId(ALL);
2533
3232
  }, []);
2534
3233
  const fmt = (n) => saas.formatCurrency(n, currency);
2535
- const columns = React14.useMemo(() => [
3234
+ const columns = React17.useMemo(() => [
2536
3235
  {
2537
3236
  id: "date",
2538
3237
  header: t2("financial.statements.columnDate"),
@@ -2707,59 +3406,141 @@ function CommissionsView() {
2707
3406
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center rounded-lg border-2 border-dashed border-muted p-12", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.commissions.comingSoon") }) })
2708
3407
  ] });
2709
3408
  }
3409
+ function cardMath(acc) {
3410
+ const limit = acc.creditLimit ?? 0;
3411
+ const used = Math.max(0, -acc.currentBalance);
3412
+ const available = Math.max(0, limit - used);
3413
+ const pct = limit > 0 ? Math.min(100, used / limit * 100) : 0;
3414
+ return { limit, used, available, pct };
3415
+ }
2710
3416
  function CardsView() {
2711
3417
  const t2 = core.useTranslation();
2712
- const { currency, labels } = useFinancialConfig();
2713
- const cardTransactions = useFinancialStore((s) => s.cardTransactions);
2714
- const cardsLoading = useFinancialStore((s) => s.cardsLoading);
2715
- const fetchCardTransactions = useFinancialStore((s) => s.fetchCardTransactions);
2716
- React14.useEffect(() => {
2717
- fetchCardTransactions();
3418
+ const { currency } = useFinancialConfig();
3419
+ const bankAccounts = useFinancialStore((s) => s.bankAccounts);
3420
+ const bankAccountsLoading = useFinancialStore((s) => s.bankAccountsLoading);
3421
+ const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
3422
+ const [tab, setTab] = React17.useState("open");
3423
+ const [addOpen, setAddOpen] = React17.useState(false);
3424
+ const [addCardId, setAddCardId] = React17.useState();
3425
+ React17.useEffect(() => {
3426
+ fetchBankAccounts();
2718
3427
  }, []);
2719
- const byBrand = {};
2720
- for (const tx of cardTransactions) {
2721
- const brand = tx.cardBrand || "Other";
2722
- if (!byBrand[brand]) byBrand[brand] = [];
2723
- byBrand[brand].push(tx);
3428
+ const cards = React17.useMemo(
3429
+ () => bankAccounts.filter((a) => a.accountType === "credit_card"),
3430
+ [bankAccounts]
3431
+ );
3432
+ const openCards = React17.useMemo(() => cards.filter((c) => c.isActive), [cards]);
3433
+ const closedCards = React17.useMemo(() => cards.filter((c) => !c.isActive), [cards]);
3434
+ const visibleCards = tab === "open" ? openCards : closedCards;
3435
+ const summary = React17.useMemo(() => {
3436
+ let totalAvailable = 0;
3437
+ let totalBills = 0;
3438
+ for (const c of openCards) {
3439
+ const { available, used } = cardMath(c);
3440
+ totalAvailable += available;
3441
+ totalBills += used;
3442
+ }
3443
+ return { totalAvailable, totalBills };
3444
+ }, [openCards]);
3445
+ function openAddExpense(cardId) {
3446
+ setAddCardId(cardId);
3447
+ setAddOpen(true);
2724
3448
  }
2725
3449
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
2726
3450
  /* @__PURE__ */ jsxRuntime.jsx(ui.SubpageHeader, { title: t2("financial.cards.title"), subtitle: t2("financial.cards.subtitle") }),
2727
- cardsLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-12 text-sm text-muted-foreground", children: "Loading..." }) : cardTransactions.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center rounded-lg border-2 border-dashed border-muted", children: [
2728
- /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "h-5 w-5 text-indigo-500" }) }),
2729
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.cards.noTransactions") }),
2730
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: t2("financial.cards.transactionsAppear") })
2731
- ] }) : Object.entries(byBrand).map(([brand, txs]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border bg-card shadow-sm", children: [
2732
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-4 py-3 border-b", children: [
2733
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "h-4 w-4 text-indigo-500" }),
2734
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold", children: brand }),
2735
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground", children: [
2736
- "(",
2737
- txs.length,
2738
- " transactions)"
3451
+ bankAccountsLoading && cards.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-12 text-sm text-muted-foreground", children: "Loading..." }) : cards.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center rounded-2xl border-2 border-dashed border-muted", children: [
3452
+ /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "h-6 w-6" }) }),
3453
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: t2("financial.cards.noCards") }),
3454
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mt-0.5 max-w-xs", children: t2("financial.cards.noCardsHint") })
3455
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3456
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-px overflow-hidden rounded-2xl border bg-border shadow-sm", children: [
3457
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-card px-4 py-3", children: [
3458
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.cards.availableLimit") }),
3459
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-lg font-bold tabular-nums text-success", children: saas.formatCurrency(summary.totalAvailable, currency) })
3460
+ ] }),
3461
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-card px-4 py-3", children: [
3462
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.cards.allBillsValue") }),
3463
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-lg font-bold tabular-nums", children: saas.formatCurrency(summary.totalBills, currency) })
2739
3464
  ] })
2740
3465
  ] }),
2741
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y", children: txs.map((tx) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-4 py-3 flex items-center justify-between", children: [
2742
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2743
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm font-medium", children: [
2744
- "Installment ",
2745
- tx.currentInstallment,
2746
- "/",
2747
- tx.installmentCount
3466
+ closedCards.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-1 rounded-full bg-muted p-1 w-full max-w-xs", children: ["open", "closed"].map((k) => /* @__PURE__ */ jsxRuntime.jsx(
3467
+ "button",
3468
+ {
3469
+ onClick: () => setTab(k),
3470
+ 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"}`,
3471
+ children: k === "open" ? t2("financial.cards.tabOpen") : t2("financial.cards.tabClosed")
3472
+ },
3473
+ k
3474
+ )) }),
3475
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3", children: visibleCards.map((card) => {
3476
+ const { limit, used, available, pct } = cardMath(card);
3477
+ const over = pct >= 90;
3478
+ const brand = `${card.bankName ?? ""} ${card.name ?? ""}`.toLowerCase();
3479
+ 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";
3480
+ 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";
3481
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "overflow-hidden rounded-2xl border bg-card shadow-sm", children: [
3482
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `bg-gradient-to-br ${headerGrad} px-4 py-4 text-white`, children: [
3483
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between", children: [
3484
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
3485
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "h-5 w-5" }),
3486
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold", children: card.name })
3487
+ ] }),
3488
+ card.bankName && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] font-medium uppercase tracking-wide opacity-80", children: card.bankName })
3489
+ ] }),
3490
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4", children: [
3491
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] uppercase tracking-wide opacity-80", children: t2("financial.cards.currentBill") }),
3492
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-2xl font-bold tabular-nums leading-tight", children: saas.formatCurrency(used, currency) })
3493
+ ] })
2748
3494
  ] }),
2749
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground", children: [
2750
- "Due: ",
2751
- tx.expectedDate
3495
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3 px-4 py-3", children: [
3496
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3497
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-[11px] text-muted-foreground", children: [
3498
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: t2("financial.cards.usedOfLimit", { percent: String(Math.round(pct)) }) }),
3499
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tabular-nums", children: saas.formatCurrency(limit, currency) })
3500
+ ] }),
3501
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-1 h-2 overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsxRuntime.jsx(
3502
+ "div",
3503
+ {
3504
+ className: `h-full rounded-full ${over ? "bg-destructive" : `bg-gradient-to-r ${barGrad}`}`,
3505
+ style: { width: `${pct}%` }
3506
+ }
3507
+ ) })
3508
+ ] }),
3509
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
3510
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3511
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] text-muted-foreground", children: t2("financial.cards.availableLimit") }),
3512
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold tabular-nums", children: saas.formatCurrency(available, currency) })
3513
+ ] }),
3514
+ card.dueDay != null && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 text-right text-xs text-muted-foreground", children: [
3515
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CalendarClock, { className: "h-3.5 w-3.5" }),
3516
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: t2("financial.cards.dueOn", { day: String(card.dueDay) }) })
3517
+ ] })
3518
+ ] }),
3519
+ /* @__PURE__ */ jsxRuntime.jsxs(
3520
+ "button",
3521
+ {
3522
+ type: "button",
3523
+ onClick: () => openAddExpense(card.id),
3524
+ 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",
3525
+ children: [
3526
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3.5 w-3.5" }),
3527
+ t2("financial.cards.addExpense")
3528
+ ]
3529
+ }
3530
+ )
2752
3531
  ] })
2753
- ] }),
2754
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
2755
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium", children: saas.formatCurrency(tx.installmentAmount, currency) }),
2756
- tx.status === "received" ? /* @__PURE__ */ jsxRuntime.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: [
2757
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-2.5 w-2.5" }),
2758
- " Received"
2759
- ] }) : /* @__PURE__ */ jsxRuntime.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" })
2760
- ] })
2761
- ] }, tx.id)) })
2762
- ] }, brand))
3532
+ ] }, card.id);
3533
+ }) })
3534
+ ] }),
3535
+ /* @__PURE__ */ jsxRuntime.jsx(
3536
+ QuickTransactionForm,
3537
+ {
3538
+ open: addOpen,
3539
+ onOpenChange: setAddOpen,
3540
+ defaultType: "expense",
3541
+ defaultAccountId: addCardId
3542
+ }
3543
+ )
2763
3544
  ] });
2764
3545
  }
2765
3546
  function ReconciliationView() {
@@ -2767,14 +3548,14 @@ function ReconciliationView() {
2767
3548
  const { currency } = useFinancialConfig();
2768
3549
  const provider = useFinancialProvider();
2769
3550
  const fmt = (n) => saas.formatCurrency(n, currency);
2770
- const [lines, setLines] = React14.useState([]);
2771
- const [loading, setLoading] = React14.useState(true);
2772
- const [selectedId, setSelectedId] = React14.useState(null);
2773
- const [candidates, setCandidates] = React14.useState([]);
2774
- const [candidatesLoading, setCandidatesLoading] = React14.useState(false);
2775
- const [busy, setBusy] = React14.useState(false);
3551
+ const [lines, setLines] = React17.useState([]);
3552
+ const [loading, setLoading] = React17.useState(true);
3553
+ const [selectedId, setSelectedId] = React17.useState(null);
3554
+ const [candidates, setCandidates] = React17.useState([]);
3555
+ const [candidatesLoading, setCandidatesLoading] = React17.useState(false);
3556
+ const [busy, setBusy] = React17.useState(false);
2776
3557
  const supported = typeof provider.getUnreconciled === "function";
2777
- const load = React14.useCallback(async () => {
3558
+ const load = React17.useCallback(async () => {
2778
3559
  if (!provider.getUnreconciled) return;
2779
3560
  setLoading(true);
2780
3561
  try {
@@ -2785,10 +3566,10 @@ function ReconciliationView() {
2785
3566
  setLoading(false);
2786
3567
  }
2787
3568
  }, [provider]);
2788
- React14.useEffect(() => {
3569
+ React17.useEffect(() => {
2789
3570
  void load();
2790
3571
  }, [load]);
2791
- React14.useEffect(() => {
3572
+ React17.useEffect(() => {
2792
3573
  if (!selectedId || !provider.suggestReconciliation) {
2793
3574
  setCandidates([]);
2794
3575
  return;
@@ -2940,7 +3721,7 @@ var STEPS = [
2940
3721
  ];
2941
3722
  function FinancialOnboarding({ onComplete }) {
2942
3723
  const t2 = core.useTranslation();
2943
- const [step, setStep] = React14.useState(0);
3724
+ const [step, setStep] = React17.useState(0);
2944
3725
  const current = STEPS[step];
2945
3726
  const isLast = step === STEPS.length - 1;
2946
3727
  const Icon = current.icon;
@@ -3085,18 +3866,47 @@ function FinancialPage({ config, provider, store, registries }) {
3085
3866
  cards: 0,
3086
3867
  settings: 1
3087
3868
  });
3088
- const [onboardingComplete, setOnboardingComplete] = React14.useState(() => {
3869
+ const [onboardingComplete, setOnboardingComplete] = React17.useState(() => {
3089
3870
  try {
3090
3871
  return localStorage.getItem("saas-core:financial-onboarded") === "true";
3091
3872
  } catch {
3092
3873
  return false;
3093
3874
  }
3094
3875
  });
3876
+ React17.useEffect(() => {
3877
+ if (onboardingComplete) return;
3878
+ let cancelled = false;
3879
+ void provider.getMovements({ pageSize: 1 }).then((res) => {
3880
+ if (!cancelled && res.total > 0) setOnboardingComplete(true);
3881
+ }).catch(() => {
3882
+ });
3883
+ return () => {
3884
+ cancelled = true;
3885
+ };
3886
+ }, [onboardingComplete, provider]);
3095
3887
  const t2 = core.useTranslation();
3096
3888
  const intent = parseIntent(view);
3097
3889
  const isSettings = view === "settings";
3098
3890
  const nav = buildNav(config, view, navigate, t2);
3099
- const quickActions = React14.useMemo(() => {
3891
+ const [quickAddOpen, setQuickAddOpen] = React17.useState(false);
3892
+ const [quickAddType, setQuickAddType] = React17.useState("expense");
3893
+ const [quickAddReceipt, setQuickAddReceipt] = React17.useState(false);
3894
+ const openQuickAdd2 = React17.useCallback((type = "expense") => {
3895
+ setQuickAddType(type);
3896
+ setQuickAddReceipt(false);
3897
+ setQuickAddOpen(true);
3898
+ }, []);
3899
+ const pendingQuickAdd = usePendingQuickAdd();
3900
+ React17.useEffect(() => {
3901
+ if (!pendingQuickAdd) return;
3902
+ const req = consumeQuickAdd();
3903
+ if (!req) return;
3904
+ setQuickAddType(req.type);
3905
+ setQuickAddReceipt(req.receipt);
3906
+ setQuickAddOpen(true);
3907
+ }, [pendingQuickAdd]);
3908
+ const quickActions = React17.useMemo(() => {
3909
+ if (config.quickAdd) return [];
3100
3910
  const actions = [];
3101
3911
  if (config.modules.payables) {
3102
3912
  actions.push({
@@ -3126,7 +3936,7 @@ function FinancialPage({ config, provider, store, registries }) {
3126
3936
  });
3127
3937
  }
3128
3938
  return actions;
3129
- }, [config.modules]);
3939
+ }, [config.modules, config.quickAdd]);
3130
3940
  function handleOnboardingComplete() {
3131
3941
  setOnboardingComplete(true);
3132
3942
  try {
@@ -3170,7 +3980,7 @@ function FinancialPage({ config, provider, store, registries }) {
3170
3980
  return /* @__PURE__ */ jsxRuntime.jsx(SummaryView, {});
3171
3981
  }
3172
3982
  }
3173
- return /* @__PURE__ */ jsxRuntime.jsx(FinancialContextProvider, { config, provider, store, children: /* @__PURE__ */ jsxRuntime.jsx(
3983
+ return /* @__PURE__ */ jsxRuntime.jsx(FinancialContextProvider, { config, provider, store, children: /* @__PURE__ */ jsxRuntime.jsxs(
3174
3984
  ui.ModulePage,
3175
3985
  {
3176
3986
  title: config.labels.pageTitle,
@@ -3179,21 +3989,50 @@ function FinancialPage({ config, provider, store, registries }) {
3179
3989
  showHeader: intent.view === "summary" || view === "summary",
3180
3990
  viewKey: view,
3181
3991
  direction,
3182
- headerAction: /* @__PURE__ */ jsxRuntime.jsx(
3183
- saas.ModuleActionBar,
3184
- {
3185
- quickActions,
3186
- settingsPath: registries && registries.length > 0 ? "/settings/financial" : void 0,
3187
- settingsLabel: t2("financial.settingsPage.title")
3188
- }
3189
- ),
3190
- children: renderView()
3992
+ headerAction: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
3993
+ config.quickAdd && // One-primary-action rule (FAY-1247): quick-add is the single primary
3994
+ // add affordance in B2C mode. The receipt entry point lives inside the
3995
+ // sheet's "Fotografar ou anexar recibo" attachment step, so no separate
3996
+ // "Enviar recibo" button is needed here.
3997
+ /* @__PURE__ */ jsxRuntime.jsxs(
3998
+ ui.Button,
3999
+ {
4000
+ size: "sm",
4001
+ className: "hidden md:inline-flex",
4002
+ onClick: () => openQuickAdd2("expense"),
4003
+ children: [
4004
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-4 w-4" }),
4005
+ t2("financial.quickTx.newTransaction")
4006
+ ]
4007
+ }
4008
+ ),
4009
+ /* @__PURE__ */ jsxRuntime.jsx(
4010
+ saas.ModuleActionBar,
4011
+ {
4012
+ quickActions,
4013
+ settingsPath: registries && registries.length > 0 ? "/settings/financial" : void 0,
4014
+ settingsLabel: t2("financial.settingsPage.title")
4015
+ }
4016
+ )
4017
+ ] }),
4018
+ children: [
4019
+ renderView(),
4020
+ /* @__PURE__ */ jsxRuntime.jsx(
4021
+ QuickTransactionForm,
4022
+ {
4023
+ open: quickAddOpen,
4024
+ onOpenChange: setQuickAddOpen,
4025
+ defaultType: quickAddType,
4026
+ focusReceipt: quickAddReceipt
4027
+ }
4028
+ )
4029
+ ]
3191
4030
  }
3192
4031
  ) });
3193
4032
  }
3194
4033
  function useEnsureSummary() {
3195
4034
  const fetchSummary = useFinancialStore((s) => s.fetchSummary);
3196
- React14.useEffect(() => {
4035
+ React17.useEffect(() => {
3197
4036
  void fetchSummary();
3198
4037
  }, []);
3199
4038
  }
@@ -3317,7 +4156,7 @@ function RecentTransactions() {
3317
4156
  const { currency } = useFinancialConfig();
3318
4157
  const invoices = useFinancialStore((s) => s.invoices);
3319
4158
  const fetchInvoices = useFinancialStore((s) => s.fetchInvoices);
3320
- React14.useEffect(() => {
4159
+ React17.useEffect(() => {
3321
4160
  void fetchInvoices({ pageSize: 5 });
3322
4161
  }, []);
3323
4162
  const columns = [
@@ -3334,6 +4173,202 @@ function RecentTransactions() {
3334
4173
  ];
3335
4174
  return /* @__PURE__ */ jsxRuntime.jsx(ui.TableWidget, { title: t2("financial.summary.recentTransactions"), icon: "CircleDollarSign", columns, data: invoices.slice(0, 5), emptyMessage: t2("financial.summary.noTransactions") });
3336
4175
  }
4176
+ function useDueLabel() {
4177
+ const t2 = core.useTranslation();
4178
+ return (dueDate) => {
4179
+ const today2 = /* @__PURE__ */ new Date();
4180
+ today2.setHours(0, 0, 0, 0);
4181
+ const due = /* @__PURE__ */ new Date(`${dueDate}T00:00:00`);
4182
+ const days = Math.round((due.getTime() - today2.getTime()) / 864e5);
4183
+ if (days < 0) return t2("financial.home.overdueDays", { count: String(Math.abs(days)) });
4184
+ if (days === 0) return t2("financial.home.dueToday");
4185
+ if (days === 1) return t2("financial.home.dueTomorrow");
4186
+ return t2("financial.home.dueInDays", { count: String(days) });
4187
+ };
4188
+ }
4189
+ function BalanceHero() {
4190
+ const t2 = core.useTranslation();
4191
+ const { currency } = useFinancialConfig();
4192
+ const summary = useFinancialStore((s) => s.summary);
4193
+ const navigate = ui.useDashboardNavigate();
4194
+ useEnsureSummary();
4195
+ const [hidden, setHidden] = React17__namespace.default.useState(false);
4196
+ const money = (v) => hidden ? "\u2022\u2022\u2022\u2022\u2022\u2022" : saas.formatCurrency(v ?? 0, currency);
4197
+ const inflow = summary?.monthlyInflow ?? 0;
4198
+ const outflow = summary?.monthlyOutflow ?? 0;
4199
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.GoldCard, { branded: true, className: "px-5 py-4 text-center md:px-8 md:py-5", children: [
4200
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium uppercase tracking-wide opacity-75", children: t2("financial.home.balance") }),
4201
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-0.5 flex items-center justify-center gap-2.5", children: [
4202
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-3xl font-bold tracking-tight tabular-nums md:text-4xl", children: money(summary?.totalBalance) }),
4203
+ /* @__PURE__ */ jsxRuntime.jsx(
4204
+ "button",
4205
+ {
4206
+ type: "button",
4207
+ onClick: () => setHidden((h) => !h),
4208
+ "aria-label": hidden ? t2("financial.home.showBalance") : t2("financial.home.hideBalance"),
4209
+ className: "rounded-full p-1.5 text-primary-foreground/80 transition-colors hover:bg-white/10",
4210
+ children: hidden ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.EyeOff, { className: "h-5 w-5" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Eye, { className: "h-5 w-5" })
4211
+ }
4212
+ )
4213
+ ] }),
4214
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto mt-3 grid max-w-lg grid-cols-2 gap-3", children: [
4215
+ /* @__PURE__ */ jsxRuntime.jsxs(
4216
+ "button",
4217
+ {
4218
+ type: "button",
4219
+ onClick: () => navigate("/financial/receivables-list"),
4220
+ "aria-label": t2("financial.home.inflow"),
4221
+ 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",
4222
+ children: [
4223
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-emerald-500/90", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUpRight, { className: "h-4 w-4 text-white" }) }),
4224
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0", children: [
4225
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-[11px] uppercase tracking-wide opacity-70", children: t2("financial.home.inflow") }),
4226
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block truncate text-sm font-semibold tabular-nums", children: money(inflow) })
4227
+ ] })
4228
+ ]
4229
+ }
4230
+ ),
4231
+ /* @__PURE__ */ jsxRuntime.jsxs(
4232
+ "button",
4233
+ {
4234
+ type: "button",
4235
+ onClick: () => navigate("/financial/payables-list"),
4236
+ "aria-label": t2("financial.home.outflow"),
4237
+ 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",
4238
+ children: [
4239
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-rose-500/90", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowDownRight, { className: "h-4 w-4 text-white" }) }),
4240
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "min-w-0", children: [
4241
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-[11px] uppercase tracking-wide opacity-70", children: t2("financial.home.outflow") }),
4242
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block truncate text-sm font-semibold tabular-nums", children: money(outflow) })
4243
+ ] })
4244
+ ]
4245
+ }
4246
+ )
4247
+ ] })
4248
+ ] });
4249
+ }
4250
+ var SPEND_CATEGORIES = [
4251
+ { key: "subscriptions", keywords: ["netflix", "spotify", "prime video", "disney", "hbo", " max", "youtube", "apple", "assinatura", "subscription"] },
4252
+ { key: "groceries", keywords: ["supermercado", "mercado", "padaria", "hortifruti", "feira", "a\xE7ougue", "grocery", "market"] },
4253
+ { key: "transport", keywords: ["uber", "99", "taxi", "posto", "shell", "ipiranga", "combust\xEDvel", "gasolina", "metr\xF4", "\xF4nibus", "transport", "fuel"] },
4254
+ { key: "dining", keywords: ["ifood", "rappi", "restaurante", "outback", "lanchonete", "pizzaria", "caf\xE9", "cafeteria", "mcdonald", "burger", "food"] },
4255
+ { key: "home", keywords: ["aluguel", "condom\xEDnio", "luz", "enel", "\xE1gua", "sabesp", "internet", "vivo", "claro", "g\xE1s", "iptu", "rent", "utilit"] },
4256
+ { key: "health", keywords: ["farm\xE1cia", "farmacia", "drogasil", "drogaria", "academia", "smartfit", "cl\xEDnica", "hospital", "m\xE9dico", "sa\xFAde", "gym", "fitness", "health", "pharmac"] },
4257
+ { key: "leisure", keywords: ["cinema", "cinemark", "ingresso", "show", "teatro", "viagem", "hotel", "steam", "leisure"] },
4258
+ { key: "shopping", keywords: ["amazon", "magalu", "shopee", "aliexpress", "loja", "shopping", "zara", "renner", "store"] }
4259
+ ];
4260
+ function categorizeSpend(notes) {
4261
+ const s = (notes ?? "").toLowerCase();
4262
+ for (const c of SPEND_CATEGORIES) if (c.keywords.some((k) => s.includes(k))) return c.key;
4263
+ return "other";
4264
+ }
4265
+ function SpendByCategoryChart() {
4266
+ const t2 = core.useTranslation();
4267
+ const provider = useFinancialProvider();
4268
+ const [rows, setRows] = React17__namespace.default.useState([]);
4269
+ React17.useEffect(() => {
4270
+ let alive = true;
4271
+ void (async () => {
4272
+ const res = await provider.getMovements({ status: "paid", direction: "debit" });
4273
+ if (!alive) return;
4274
+ const totals = /* @__PURE__ */ new Map();
4275
+ for (const m of res.data) {
4276
+ if (m.movementKind === "transfer") continue;
4277
+ const key = categorizeSpend(m.notes);
4278
+ totals.set(key, (totals.get(key) ?? 0) + (m.paidAmount || m.amount));
4279
+ }
4280
+ setRows(
4281
+ [...totals.entries()].sort((a, b) => b[1] - a[1]).map(([key, value]) => ({ name: t2(`financial.home.category.${key}`), value: Math.round(value * 100) / 100 }))
4282
+ );
4283
+ })();
4284
+ return () => {
4285
+ alive = false;
4286
+ };
4287
+ }, [provider]);
4288
+ if (rows.length === 0) {
4289
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Card, { children: [
4290
+ /* @__PURE__ */ jsxRuntime.jsx(ui.CardHeader, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.CardTitle, { children: t2("financial.home.spendByCategory") }) }),
4291
+ /* @__PURE__ */ jsxRuntime.jsx(ui.CardContent, { className: "flex h-[200px] items-center justify-center text-sm text-muted-foreground", children: t2("financial.home.noSpend") })
4292
+ ] });
4293
+ }
4294
+ return /* @__PURE__ */ jsxRuntime.jsx(
4295
+ ui.ChartWidget,
4296
+ {
4297
+ type: "pie",
4298
+ title: t2("financial.home.spendByCategory"),
4299
+ icon: "PieChart",
4300
+ categoryKey: "name",
4301
+ data: rows,
4302
+ series: [{ dataKey: "value", label: t2("financial.home.spendByCategory") }]
4303
+ }
4304
+ );
4305
+ }
4306
+ function UpcomingBills() {
4307
+ const t2 = core.useTranslation();
4308
+ const { currency } = useFinancialConfig();
4309
+ const provider = useFinancialProvider();
4310
+ const dueLabel = useDueLabel();
4311
+ const [bills, setBills] = React17__namespace.default.useState([]);
4312
+ React17.useEffect(() => {
4313
+ let alive = true;
4314
+ void (async () => {
4315
+ const pending2 = await provider.getMovements({ status: "pending" });
4316
+ if (!alive) return;
4317
+ setBills(
4318
+ pending2.data.filter((m) => m.direction === "debit" && m.movementKind === "bill").sort((a, b) => a.dueDate.localeCompare(b.dueDate))
4319
+ );
4320
+ })();
4321
+ return () => {
4322
+ alive = false;
4323
+ };
4324
+ }, [provider]);
4325
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.GoldCard, { className: "p-0 overflow-hidden", children: [
4326
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between px-4 pt-4", children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold text-foreground", children: t2("financial.home.upcomingBills") }) }),
4327
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3", children: bills.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 py-6 text-center text-xs text-muted-foreground", children: t2("financial.home.noBills") }) : bills.map((bill, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-4 py-3.5" + (i > 0 ? " border-t border-border" : ""), children: [
4328
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0", children: [
4329
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "truncate text-sm font-medium text-foreground", children: bill.notes ?? t2("financial.home.bill") }),
4330
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: dueLabel(bill.dueDate) })
4331
+ ] }),
4332
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-sm font-semibold tabular-nums text-foreground", children: saas.formatCurrency(bill.amount - bill.paidAmount, currency) })
4333
+ ] }, bill.id)) })
4334
+ ] });
4335
+ }
4336
+ function CreditCards() {
4337
+ const t2 = core.useTranslation();
4338
+ const { currency } = useFinancialConfig();
4339
+ const bankAccounts = useFinancialStore((s) => s.bankAccounts);
4340
+ const fetchBankAccounts = useFinancialStore((s) => s.fetchBankAccounts);
4341
+ React17.useEffect(() => {
4342
+ void fetchBankAccounts();
4343
+ }, []);
4344
+ const cards = bankAccounts.filter((a) => a.accountType === "credit_card");
4345
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.GoldCard, { className: "bg-transparent border-0 p-0 shadow-none", children: [
4346
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "mb-3 text-sm font-semibold text-foreground", children: t2("financial.home.cards") }),
4347
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
4348
+ cards.map((card) => {
4349
+ const fatura = Math.abs(Math.min(0, card.currentBalance));
4350
+ const limite = Math.max(0, (card.creditLimit ?? 0) - fatura);
4351
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-2xl bg-gradient-to-br from-violet-600 to-indigo-700 p-4 text-white shadow-md", children: [
4352
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
4353
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold", children: card.bankName ?? card.name }),
4354
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CreditCard, { className: "h-5 w-5 opacity-80" })
4355
+ ] }),
4356
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-6 text-[11px] uppercase tracking-wide opacity-70", children: t2("financial.home.currentBill") }),
4357
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xl font-bold tabular-nums", children: saas.formatCurrency(fatura, currency) }),
4358
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mt-2 text-[11px] opacity-70", children: [
4359
+ t2("financial.home.availableLimit"),
4360
+ " ",
4361
+ saas.formatCurrency(limite, currency)
4362
+ ] })
4363
+ ] }, card.id);
4364
+ }),
4365
+ cards.length === 0 ? /* @__PURE__ */ jsxRuntime.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: [
4366
+ "+ ",
4367
+ t2("financial.home.addCard")
4368
+ ] }) : null
4369
+ ] })
4370
+ ] });
4371
+ }
3337
4372
  function createFinancialDashboardWidgets(ctx2) {
3338
4373
  const withCtx = (Inner) => {
3339
4374
  const Wrapped = () => /* @__PURE__ */ jsxRuntime.jsx(FinancialContextProvider, { config: ctx2.config, provider: ctx2.provider, store: ctx2.store, children: /* @__PURE__ */ jsxRuntime.jsx(Inner, {}) });
@@ -3350,7 +4385,19 @@ function createFinancialDashboardWidgets(ctx2) {
3350
4385
  ui.defineChartWidget({ id: "financial.chart.cash-flow", title: "financial.summary.cashFlow", domain: "financial", span: 2, defaultOrder: 10, surfaces: ["plugin-home"], component: withCtx(CashFlowChart) }),
3351
4386
  ui.defineCustomWidget({ id: "financial.panel.breakdown", title: "financial.summary.cashFlowBreakdown", domain: "financial", span: 1, defaultOrder: 11, surfaces: ["plugin-home"], component: withCtx(CashFlowBreakdown) }),
3352
4387
  ui.defineCustomWidget({ id: "financial.panel.overdue", title: "financial.summary.overdueTitle", domain: "financial", span: 1, defaultOrder: 12, surfaces: ["plugin-home"], component: withCtx(OverdueAlerts) }),
3353
- ui.defineTableWidget({ id: "financial.table.recent", title: "financial.summary.recentTransactions", domain: "financial", span: 4, defaultOrder: 20, surfaces: ["plugin-home"], component: withCtx(RecentTransactions) })
4388
+ ui.defineTableWidget({ id: "financial.table.recent", title: "financial.summary.recentTransactions", domain: "financial", span: 4, defaultOrder: 20, surfaces: ["plugin-home"], component: withCtx(RecentTransactions) }),
4389
+ // FINANCE-HOME surface (B2C phone-first dashboard) — a compact analytical
4390
+ // layout: hero band → charts row (spend-by-category + cash-flow) → bills +
4391
+ // cards. This is a dedicated consumer surface (norman-ai's money screen), NOT
4392
+ // the universal B2B 'home': a full-bleed balance hero and credit-card tiles
4393
+ // are personal-finance chrome that would leak into every app that merely
4394
+ // enables plugin-financial (e.g. the beauty-saas Painel). Apps that want them
4395
+ // opt in explicitly by rendering <DashboardCanvas surface="finance-home">.
4396
+ ui.defineCustomWidget({ id: "financial.hero.balance", title: "financial.home.balance", domain: "financial", span: 4, defaultOrder: 0, surfaces: ["finance-home"], component: withCtx(BalanceHero) }),
4397
+ ui.defineChartWidget({ id: "financial.chart.spend-by-category", title: "financial.home.spendByCategory", domain: "financial", span: 2, defaultOrder: 1, surfaces: ["finance-home"], component: withCtx(SpendByCategoryChart) }),
4398
+ ui.defineChartWidget({ id: "financial.chart.cash-flow-home", title: "financial.summary.cashFlow", domain: "financial", span: 2, defaultOrder: 2, surfaces: ["finance-home"], component: withCtx(CashFlowChart) }),
4399
+ ui.defineCustomWidget({ id: "financial.list.upcoming-bills", title: "financial.home.upcomingBills", domain: "financial", span: 2, defaultOrder: 3, surfaces: ["finance-home"], component: withCtx(UpcomingBills) }),
4400
+ ui.defineCustomWidget({ id: "financial.cards", title: "financial.home.cards", domain: "financial", span: 2, defaultOrder: 4, surfaces: ["finance-home"], component: withCtx(CreditCards) })
3354
4401
  ];
3355
4402
  }
3356
4403
 
@@ -3383,9 +4430,10 @@ function paginate(items, page, pageSize) {
3383
4430
  const start = (p - 1) * ps;
3384
4431
  return { data: items.slice(start, start + ps), total: items.length };
3385
4432
  }
3386
- function createStore(seedPaymentMethodTypes) {
4433
+ function createStore(options) {
3387
4434
  const tenantId = "mock-tenant";
3388
- const defaultPMTypes = ([
4435
+ const seed = options?.seed;
4436
+ const defaultPMTypes = (options?.paymentMethodTypes ?? [
3389
4437
  { name: "Cash", transactionType: "cash" },
3390
4438
  { name: "Credit Card", transactionType: "credit_card" },
3391
4439
  { name: "Debit Card", transactionType: "debit_card" },
@@ -3423,19 +4471,23 @@ function createStore(seedPaymentMethodTypes) {
3423
4471
  updatedAt: now()
3424
4472
  };
3425
4473
  return {
3426
- invoices: [],
3427
- invoiceItems: [],
3428
- movements: [],
3429
- bankAccounts: [cashAccount, bankAccount],
4474
+ // Seeded data (when provided) makes the module render as if the user already
4475
+ // has accounts + transactions — a populated Summary/Statements instead of the
4476
+ // empty onboarding state. Seed arrays are copied so the caller's data is not mutated.
4477
+ invoices: seed?.invoices ? [...seed.invoices] : [],
4478
+ invoiceItems: seed?.invoiceItems ? [...seed.invoiceItems] : [],
4479
+ movements: seed?.movements ? [...seed.movements] : [],
4480
+ bankAccounts: seed?.bankAccounts ? [...seed.bankAccounts] : [cashAccount, bankAccount],
3430
4481
  cashSessions: [],
3431
- paymentMethods: [],
4482
+ paymentMethods: seed?.paymentMethods ? [...seed.paymentMethods] : [],
3432
4483
  paymentMethodTypes: defaultPMTypes,
3433
4484
  chartOfAccounts: [],
4485
+ costCenters: [],
3434
4486
  cardTransactions: []
3435
4487
  };
3436
4488
  }
3437
4489
  function createMockFinancialProvider(options) {
3438
- const store = createStore();
4490
+ const store = createStore(options);
3439
4491
  const tenantId = "mock-tenant";
3440
4492
  function recalcInvoiceStatus(invoice) {
3441
4493
  if (invoice.paidAmount >= invoice.totalAmount) {
@@ -3456,6 +4508,14 @@ function createMockFinancialProvider(options) {
3456
4508
  if (query.direction) results = results.filter((i) => i.direction === query.direction);
3457
4509
  if (query.status) results = results.filter((i) => matchesStatus(i.status, query.status));
3458
4510
  if (query.contactId) results = results.filter((i) => i.contactId === query.contactId);
4511
+ if (query.accountId) {
4512
+ const invoiceIds = new Set(store.invoiceItems.filter((item) => item.accountId === query.accountId).map((item) => item.invoiceId));
4513
+ results = results.filter((invoice) => invoiceIds.has(invoice.id));
4514
+ }
4515
+ if (query.costCenterId) {
4516
+ const invoiceIds = new Set(store.invoiceItems.filter((item) => item.costCenterId === query.costCenterId).map((item) => item.invoiceId));
4517
+ results = results.filter((invoice) => invoiceIds.has(invoice.id));
4518
+ }
3459
4519
  if (query.dateRange) results = results.filter((i) => matchesDateRange(i.invoiceDate, query.dateRange));
3460
4520
  if (query.search) {
3461
4521
  const s = query.search.toLowerCase();
@@ -3778,6 +4838,9 @@ function createMockFinancialProvider(options) {
3778
4838
  async getChartOfAccounts() {
3779
4839
  return store.chartOfAccounts.filter((n) => n.isActive);
3780
4840
  },
4841
+ async getCostCenters() {
4842
+ return store.costCenters.filter((c) => c.isActive);
4843
+ },
3781
4844
  // --- Card Transactions ---
3782
4845
  async getCardTransactions(dateRange) {
3783
4846
  let results = [...store.cardTransactions];
@@ -3865,7 +4928,9 @@ function createMockFinancialProvider(options) {
3865
4928
  (m) => m.direction === "debit" && m.movementKind === "bill" && ["pending", "partial"].includes(m.status)
3866
4929
  );
3867
4930
  const todayStr = today();
3868
- const monthStart = todayStr.slice(0, 7) + "-01";
4931
+ const monthAgo = /* @__PURE__ */ new Date();
4932
+ monthAgo.setDate(monthAgo.getDate() - 30);
4933
+ const monthStart = monthAgo.toISOString().slice(0, 10);
3869
4934
  const monthEnd = todayStr;
3870
4935
  const monthMovements = store.movements.filter(
3871
4936
  (m) => m.movementKind === "payment" && matchesDateRange(m.paymentDate, dateRange ?? { from: monthStart, to: monthEnd })
@@ -4049,6 +5114,18 @@ async function fetchInvoiceBalances(pub, ids) {
4049
5114
  function round22(n) {
4050
5115
  return Math.round(n * 100) / 100;
4051
5116
  }
5117
+ async function findOrderIdsByItemMetadata(core, filters) {
5118
+ if (!filters.accountId && !filters.costCenterId) return void 0;
5119
+ let qb = core.from("order_items").select("order_id");
5120
+ if (filters.accountId) qb = qb.filter("metadata->>accountId", "eq", filters.accountId);
5121
+ if (filters.costCenterId) qb = qb.filter("metadata->>costCenterId", "eq", filters.costCenterId);
5122
+ const { data } = await qb;
5123
+ const ids = [];
5124
+ for (const row of data ?? []) {
5125
+ if (typeof row.order_id === "string" && row.order_id.length > 0) ids.push(row.order_id);
5126
+ }
5127
+ return Array.from(new Set(ids));
5128
+ }
4052
5129
  async function getMethodFeePct(pub, paymentMethodId) {
4053
5130
  if (!paymentMethodId) return 0;
4054
5131
  const { data } = await pub.from("payment_methods").select("discount_value").eq("id", paymentMethodId).single();
@@ -4076,6 +5153,14 @@ function createSupabaseFinancialProvider() {
4076
5153
  const statuses = Array.isArray(query.status) ? query.status : [query.status];
4077
5154
  qb = qb.in("status", statuses);
4078
5155
  }
5156
+ const itemScopedOrderIds = await findOrderIdsByItemMetadata(core, {
5157
+ accountId: query.accountId,
5158
+ costCenterId: query.costCenterId
5159
+ });
5160
+ if (itemScopedOrderIds) {
5161
+ if (itemScopedOrderIds.length === 0) return { data: [], total: 0 };
5162
+ qb = qb.in("id", itemScopedOrderIds);
5163
+ }
4079
5164
  if (query.contactId) qb = qb.eq("party_id", query.contactId);
4080
5165
  if (query.search) qb = qb.ilike("notes", `%${query.search}%`);
4081
5166
  if (query.dateRange) qb = qb.gte("created_at", query.dateRange.from).lte("created_at", query.dateRange.to);
@@ -4151,6 +5236,8 @@ function createSupabaseFinancialProvider() {
4151
5236
  discount: i.discount ?? 0,
4152
5237
  surcharge: 0,
4153
5238
  referenceId: i.productId ?? i.serviceId,
5239
+ accountId: i.metadata?.accountId,
5240
+ costCenterId: i.metadata?.costCenterId,
4154
5241
  createdAt: i.createdAt
4155
5242
  };
4156
5243
  });
@@ -4190,7 +5277,12 @@ function createSupabaseFinancialProvider() {
4190
5277
  discount: item.discount ?? 0,
4191
5278
  total: item.quantity * item.unitPrice - (item.discount ?? 0) + (item.surcharge ?? 0),
4192
5279
  sort_order: i,
4193
- metadata: { itemKind: item.itemKind, referenceId: item.referenceId }
5280
+ metadata: {
5281
+ itemKind: item.itemKind,
5282
+ referenceId: item.referenceId,
5283
+ accountId: item.accountId,
5284
+ costCenterId: item.costCenterId
5285
+ }
4194
5286
  }))
4195
5287
  );
4196
5288
  }
@@ -4450,6 +5542,11 @@ function createSupabaseFinancialProvider() {
4450
5542
  const { data } = await pub.from("chart_of_accounts").select("*").eq("is_active", true).order("code");
4451
5543
  return (data ?? []).map((r) => snakeToCamel(r));
4452
5544
  },
5545
+ async getCostCenters() {
5546
+ const { pub } = getClients();
5547
+ const { data } = await pub.from("cost_centers").select("*").eq("is_active", true).order("code");
5548
+ return (data ?? []).map((r) => snakeToCamel(r));
5549
+ },
4453
5550
  // --- Card Transactions ---
4454
5551
  async getCardTransactions(dateRange) {
4455
5552
  getClients();
@@ -4566,7 +5663,9 @@ function createSupabaseFinancialProvider() {
4566
5663
  const unpaidDebit = unpaid.filter((m) => m.direction === "debit");
4567
5664
  const overdueCredit = unpaidCredit.filter((m) => m.status === "overdue" || m.due_date < today2);
4568
5665
  const overdueDebit = unpaidDebit.filter((m) => m.status === "overdue" || m.due_date < today2);
4569
- const monthStart = today2.slice(0, 7) + "-01";
5666
+ const monthAgo = /* @__PURE__ */ new Date();
5667
+ monthAgo.setDate(monthAgo.getDate() - 30);
5668
+ const monthStart = monthAgo.toISOString().slice(0, 10);
4570
5669
  const monthPaid = allMovs.filter((m) => m.movement_kind === "payment" && m.payment_date >= monthStart);
4571
5670
  return {
4572
5671
  totalBalance,
@@ -4757,6 +5856,82 @@ function createFinancialStore(provider) {
4757
5856
  throw err;
4758
5857
  }
4759
5858
  },
5859
+ async createQuickTransaction(input) {
5860
+ try {
5861
+ if (input.type === "transfer") {
5862
+ if (!input.bankAccountId || !input.toAccountId) {
5863
+ throw new Error("Select source and destination accounts");
5864
+ }
5865
+ await provider.createTransfer({
5866
+ fromAccountId: input.bankAccountId,
5867
+ toAccountId: input.toAccountId,
5868
+ amount: input.amount,
5869
+ date: input.date,
5870
+ notes: input.description?.trim() || void 0
5871
+ });
5872
+ } else {
5873
+ const direction = input.type === "income" ? "credit" : "debit";
5874
+ const desc = input.description?.trim() || (input.type === "income" ? "Receita" : "Despesa");
5875
+ const invoiceMeta = {};
5876
+ if (input.recurring) invoiceMeta.recurring = true;
5877
+ if (input.receiptUrl) invoiceMeta.receiptUrl = input.receiptUrl;
5878
+ const invoice = await provider.createInvoice({
5879
+ direction,
5880
+ invoiceDate: input.date,
5881
+ contactName: desc,
5882
+ metadata: Object.keys(invoiceMeta).length > 0 ? invoiceMeta : void 0,
5883
+ items: [{
5884
+ itemKind: "other",
5885
+ description: desc,
5886
+ quantity: 1,
5887
+ unitPrice: input.amount,
5888
+ accountId: input.categoryId
5889
+ }],
5890
+ installments: [{
5891
+ direction,
5892
+ movementKind: "bill",
5893
+ amount: input.amount,
5894
+ dueDate: input.date,
5895
+ installmentNumber: 1,
5896
+ bankAccountId: input.bankAccountId,
5897
+ notes: desc
5898
+ }]
5899
+ });
5900
+ if (input.paid) {
5901
+ const movements = await provider.getMovements({ direction, status: "pending" });
5902
+ const bill = movements.data.find((m) => m.invoiceId === invoice.id && m.movementKind === "bill");
5903
+ if (bill) {
5904
+ await provider.payMovement({
5905
+ movementId: bill.id,
5906
+ amount: input.amount,
5907
+ paymentDate: input.date,
5908
+ bankAccountId: input.bankAccountId,
5909
+ notes: desc
5910
+ });
5911
+ }
5912
+ }
5913
+ }
5914
+ const invoiceQuery = get().invoiceQuery;
5915
+ const statementQuery = get().statementQuery;
5916
+ const [summary, bankAccounts, invoicesRes, statement] = await Promise.all([
5917
+ provider.getSummary(),
5918
+ provider.getBankAccounts(),
5919
+ provider.getInvoices(invoiceQuery),
5920
+ statementQuery ? provider.getStatement(statementQuery) : Promise.resolve(get().statement)
5921
+ ]);
5922
+ set({
5923
+ summary,
5924
+ bankAccounts,
5925
+ invoices: invoicesRes.data,
5926
+ invoicesTotal: invoicesRes.total,
5927
+ statement
5928
+ });
5929
+ ui.toast.success("Transaction saved");
5930
+ } catch (err) {
5931
+ ui.toast.error("Failed to save transaction", { description: err?.message });
5932
+ throw err;
5933
+ }
5934
+ },
4760
5935
  async payMovement(input) {
4761
5936
  try {
4762
5937
  await provider.payMovement(input);
@@ -5083,8 +6258,18 @@ var financialRegistries = [
5083
6258
 
5084
6259
  // src/locales/en.ts
5085
6260
  var en = {
6261
+ "financial.cards.addExpense": "Add expense",
6262
+ "financial.cards.allBillsValue": "All bills value",
6263
+ "financial.cards.availableLimit": "Available limit",
6264
+ "financial.cards.currentBill": "Current bill",
6265
+ "financial.cards.dueOn": "Due on the {{day}}",
5086
6266
  "financial.cards.installment": "Installment {{current}}/{{total}}",
6267
+ "financial.cards.noCards": "No cards yet",
6268
+ "financial.cards.noCardsHint": "Add a credit card in settings to track your bills here",
5087
6269
  "financial.cards.noTransactions": "No card transactions",
6270
+ "financial.cards.tabClosed": "Closed",
6271
+ "financial.cards.tabOpen": "Open",
6272
+ "financial.cards.usedOfLimit": "{{percent}}% of limit used",
5088
6273
  "financial.cards.pending": "Pending",
5089
6274
  "financial.cards.received": "Received",
5090
6275
  "financial.cards.subtitle": "Credit and debit card transactions",
@@ -5211,6 +6396,10 @@ var en = {
5211
6396
  "financial.nav.rules": "Rules",
5212
6397
  "financial.nav.statements": "Statements",
5213
6398
  "financial.nav.summary": "Summary",
6399
+ "financial.invoice.filterAccount": "Chart account",
6400
+ "financial.invoice.filterAllAccounts": "All accounts",
6401
+ "financial.invoice.filterCostCenter": "Cost center",
6402
+ "financial.invoice.filterAllCostCenters": "All cost centers",
5214
6403
  "financial.reconciliation.title": "Reconciliation",
5215
6404
  "financial.reconciliation.subtitle": "Match imported bank lines to your movements",
5216
6405
  "financial.reconciliation.pending": "{{count}} to reconcile",
@@ -5325,6 +6514,36 @@ var en = {
5325
6514
  "financial.summary.totalBalance": "Total Balance",
5326
6515
  "financial.summary.transactionsAppear": "Transactions will appear here once recorded",
5327
6516
  "financial.summary.viewAll": "View all",
6517
+ "financial.summary.today": "Today",
6518
+ "financial.summary.yesterday": "Yesterday",
6519
+ "financial.summary.transactionsTitle": "Transactions",
6520
+ "financial.home.balance": "Balance",
6521
+ "financial.home.inflow": "Income",
6522
+ "financial.home.outflow": "Expenses",
6523
+ "financial.home.showBalance": "Show balance",
6524
+ "financial.home.hideBalance": "Hide balance",
6525
+ "financial.home.upcomingBills": "Upcoming bills",
6526
+ "financial.home.noBills": "No upcoming bills.",
6527
+ "financial.home.dueToday": "Due today",
6528
+ "financial.home.dueTomorrow": "Due tomorrow",
6529
+ "financial.home.dueInDays": "Due in {{count}} days",
6530
+ "financial.home.overdueDays": "Overdue by {{count}} days",
6531
+ "financial.home.cards": "Cards",
6532
+ "financial.home.currentBill": "Current bill",
6533
+ "financial.home.availableLimit": "Available limit",
6534
+ "financial.home.addCard": "New card",
6535
+ "financial.home.bill": "Bill",
6536
+ "financial.home.spendByCategory": "Spending by category",
6537
+ "financial.home.noSpend": "No spending this month.",
6538
+ "financial.home.category.groceries": "Groceries",
6539
+ "financial.home.category.transport": "Transport",
6540
+ "financial.home.category.subscriptions": "Subscriptions",
6541
+ "financial.home.category.dining": "Dining",
6542
+ "financial.home.category.home": "Home",
6543
+ "financial.home.category.health": "Health",
6544
+ "financial.home.category.leisure": "Leisure",
6545
+ "financial.home.category.shopping": "Shopping",
6546
+ "financial.home.category.other": "Other",
5328
6547
  "financial.quickActions.newPayable": "New Payable",
5329
6548
  "financial.quickActions.newPayableDesc": "Record an expense or bill to pay",
5330
6549
  "financial.quickActions.newReceivable": "New Receivable",
@@ -5486,13 +6705,54 @@ var en = {
5486
6705
  "financial.transfer.created": "Transfer recorded",
5487
6706
  "financial.transfer.failed": "Failed to record transfer",
5488
6707
  "financial.transfer.sameAccount": "Source and destination must differ",
5489
- "financial.transfer.invalidAmount": "Amount must be greater than zero"
6708
+ "financial.transfer.invalidAmount": "Amount must be greater than zero",
6709
+ "financial.quickTx.title": "New transaction",
6710
+ "financial.quickTx.close": "Close",
6711
+ "financial.quickTx.newTransaction": "New transaction",
6712
+ "financial.quickTx.expense": "Expense",
6713
+ "financial.quickTx.income": "Income",
6714
+ "financial.quickTx.transfer": "Transfer",
6715
+ "financial.quickTx.paid": "Paid",
6716
+ "financial.quickTx.received": "Received",
6717
+ "financial.quickTx.yes": "Yes",
6718
+ "financial.quickTx.no": "No",
6719
+ "financial.quickTx.category": "Category",
6720
+ "financial.quickTx.selectCategory": "No category",
6721
+ "financial.quickTx.account": "Account",
6722
+ "financial.quickTx.selectAccount": "Select account",
6723
+ "financial.quickTx.fromAccount": "From account",
6724
+ "financial.quickTx.toAccount": "To account",
6725
+ "financial.quickTx.card": "Card",
6726
+ "financial.quickTx.recurring": "Recurring",
6727
+ "financial.quickTx.attachment": "Attachment",
6728
+ "financial.quickTx.attachmentSoon": "Attachments coming soon",
6729
+ "financial.quickTx.receipt": "Receipt",
6730
+ "financial.quickTx.receiptCapture": "Snap or attach a receipt",
6731
+ "financial.quickTx.receiptAttached": "Receipt attached",
6732
+ "financial.quickTx.receiptReplace": "Replace",
6733
+ "financial.quickTx.receiptRemove": "Remove receipt",
6734
+ "financial.quickTx.receiptInvalid": "Please choose an image file",
6735
+ "financial.quickTx.sendReceipt": "Send receipt",
6736
+ "financial.quickTx.descriptionPlaceholder": "e.g. Groceries, Salary\u2026",
6737
+ "financial.quickTx.enterAmount": "Enter an amount",
6738
+ "financial.quickTx.save": "Save",
6739
+ "financial.quickTx.saving": "Saving\u2026"
5490
6740
  };
5491
6741
 
5492
6742
  // src/locales/pt-BR.ts
5493
6743
  var ptBR = {
6744
+ "financial.cards.addExpense": "Adicionar despesa",
6745
+ "financial.cards.allBillsValue": "Total das faturas",
6746
+ "financial.cards.availableLimit": "Limite dispon\xEDvel",
6747
+ "financial.cards.currentBill": "Fatura atual",
6748
+ "financial.cards.dueOn": "Vence dia {{day}}",
5494
6749
  "financial.cards.installment": "Parcela {{current}}/{{total}}",
6750
+ "financial.cards.noCards": "Nenhum cart\xE3o cadastrado",
6751
+ "financial.cards.noCardsHint": "Adicione um cart\xE3o de cr\xE9dito nas configura\xE7\xF5es para acompanhar suas faturas aqui",
5495
6752
  "financial.cards.noTransactions": "Nenhuma transa\xE7\xE3o com cart\xE3o",
6753
+ "financial.cards.tabClosed": "Fechados",
6754
+ "financial.cards.tabOpen": "Abertos",
6755
+ "financial.cards.usedOfLimit": "{{percent}}% do limite usado",
5496
6756
  "financial.cards.pending": "Pendente",
5497
6757
  "financial.cards.received": "Recebido",
5498
6758
  "financial.cards.subtitle": "Transa\xE7\xF5es de cart\xE3o de cr\xE9dito e d\xE9bito",
@@ -5619,6 +6879,10 @@ var ptBR = {
5619
6879
  "financial.nav.rules": "Regras",
5620
6880
  "financial.nav.statements": "Extratos",
5621
6881
  "financial.nav.summary": "Resumo",
6882
+ "financial.invoice.filterAccount": "Plano de contas",
6883
+ "financial.invoice.filterAllAccounts": "Todos os planos",
6884
+ "financial.invoice.filterCostCenter": "Centro de custo",
6885
+ "financial.invoice.filterAllCostCenters": "Todos os centros",
5622
6886
  "financial.reconciliation.title": "Concilia\xE7\xE3o",
5623
6887
  "financial.reconciliation.subtitle": "Concilie as linhas importadas do banco com seus lan\xE7amentos",
5624
6888
  "financial.reconciliation.pending": "{{count}} para conciliar",
@@ -5733,6 +6997,36 @@ var ptBR = {
5733
6997
  "financial.summary.totalBalance": "Saldo Total",
5734
6998
  "financial.summary.transactionsAppear": "As transa\xE7\xF5es aparecer\xE3o aqui ap\xF3s serem registradas",
5735
6999
  "financial.summary.viewAll": "Ver todos",
7000
+ "financial.summary.today": "Hoje",
7001
+ "financial.summary.yesterday": "Ontem",
7002
+ "financial.summary.transactionsTitle": "Transa\xE7\xF5es",
7003
+ "financial.home.balance": "Saldo",
7004
+ "financial.home.inflow": "Entradas",
7005
+ "financial.home.outflow": "Sa\xEDdas",
7006
+ "financial.home.showBalance": "Mostrar saldo",
7007
+ "financial.home.hideBalance": "Ocultar saldo",
7008
+ "financial.home.upcomingBills": "Pr\xF3ximas contas",
7009
+ "financial.home.noBills": "Nenhuma conta a vencer.",
7010
+ "financial.home.dueToday": "Vence hoje",
7011
+ "financial.home.dueTomorrow": "Vence amanh\xE3",
7012
+ "financial.home.dueInDays": "Vence em {{count}} dias",
7013
+ "financial.home.overdueDays": "Venceu h\xE1 {{count}} dias",
7014
+ "financial.home.cards": "Cart\xF5es",
7015
+ "financial.home.currentBill": "Fatura atual",
7016
+ "financial.home.availableLimit": "Limite dispon\xEDvel",
7017
+ "financial.home.addCard": "Novo cart\xE3o",
7018
+ "financial.home.bill": "Conta",
7019
+ "financial.home.spendByCategory": "Gastos por categoria",
7020
+ "financial.home.noSpend": "Sem gastos neste m\xEAs.",
7021
+ "financial.home.category.groceries": "Mercado",
7022
+ "financial.home.category.transport": "Transporte",
7023
+ "financial.home.category.subscriptions": "Assinaturas",
7024
+ "financial.home.category.dining": "Alimenta\xE7\xE3o",
7025
+ "financial.home.category.home": "Casa",
7026
+ "financial.home.category.health": "Sa\xFAde",
7027
+ "financial.home.category.leisure": "Lazer",
7028
+ "financial.home.category.shopping": "Compras",
7029
+ "financial.home.category.other": "Outros",
5736
7030
  "financial.quickActions.newPayable": "Nova Conta a Pagar",
5737
7031
  "financial.quickActions.newPayableDesc": "Registrar uma despesa ou conta a pagar",
5738
7032
  "financial.quickActions.newReceivable": "Nova Conta a Receber",
@@ -5894,7 +7188,38 @@ var ptBR = {
5894
7188
  "financial.transfer.created": "Transfer\xEAncia registrada",
5895
7189
  "financial.transfer.failed": "Falha ao registrar transfer\xEAncia",
5896
7190
  "financial.transfer.sameAccount": "Origem e destino devem ser diferentes",
5897
- "financial.transfer.invalidAmount": "O valor deve ser maior que zero"
7191
+ "financial.transfer.invalidAmount": "O valor deve ser maior que zero",
7192
+ "financial.quickTx.title": "Nova transa\xE7\xE3o",
7193
+ "financial.quickTx.close": "Fechar",
7194
+ "financial.quickTx.newTransaction": "Nova transa\xE7\xE3o",
7195
+ "financial.quickTx.expense": "Despesa",
7196
+ "financial.quickTx.income": "Receita",
7197
+ "financial.quickTx.transfer": "Transfer\xEAncia",
7198
+ "financial.quickTx.paid": "Pago",
7199
+ "financial.quickTx.received": "Recebido",
7200
+ "financial.quickTx.yes": "Sim",
7201
+ "financial.quickTx.no": "N\xE3o",
7202
+ "financial.quickTx.category": "Categoria",
7203
+ "financial.quickTx.selectCategory": "Sem categoria",
7204
+ "financial.quickTx.account": "Conta",
7205
+ "financial.quickTx.selectAccount": "Selecione a conta",
7206
+ "financial.quickTx.fromAccount": "Conta de origem",
7207
+ "financial.quickTx.toAccount": "Conta de destino",
7208
+ "financial.quickTx.card": "Cart\xE3o",
7209
+ "financial.quickTx.recurring": "Recorrente",
7210
+ "financial.quickTx.attachment": "Anexo",
7211
+ "financial.quickTx.attachmentSoon": "Anexos em breve",
7212
+ "financial.quickTx.receipt": "Recibo",
7213
+ "financial.quickTx.receiptCapture": "Fotografar ou anexar recibo",
7214
+ "financial.quickTx.receiptAttached": "Recibo anexado",
7215
+ "financial.quickTx.receiptReplace": "Substituir",
7216
+ "financial.quickTx.receiptRemove": "Remover recibo",
7217
+ "financial.quickTx.receiptInvalid": "Escolha um arquivo de imagem",
7218
+ "financial.quickTx.sendReceipt": "Enviar recibo",
7219
+ "financial.quickTx.descriptionPlaceholder": "ex.: Mercado, Sal\xE1rio\u2026",
7220
+ "financial.quickTx.enterAmount": "Informe um valor",
7221
+ "financial.quickTx.save": "Salvar",
7222
+ "financial.quickTx.saving": "Salvando\u2026"
5898
7223
  };
5899
7224
 
5900
7225
  // src/locales/index.ts
@@ -5912,9 +7237,9 @@ var STATUS_STYLE = {
5912
7237
  function PersonStatementWidget({ person, provider, config }) {
5913
7238
  const t2 = core.useTranslation();
5914
7239
  const currency = config.currency;
5915
- const [invoices, setInvoices] = React14.useState([]);
5916
- const [loading, setLoading] = React14.useState(true);
5917
- React14.useEffect(() => {
7240
+ const [invoices, setInvoices] = React17.useState([]);
7241
+ const [loading, setLoading] = React17.useState(true);
7242
+ React17.useEffect(() => {
5918
7243
  let alive = true;
5919
7244
  if (!person?.id) {
5920
7245
  setLoading(false);
@@ -5934,7 +7259,7 @@ function PersonStatementWidget({ person, provider, config }) {
5934
7259
  };
5935
7260
  }, [person?.id, provider]);
5936
7261
  const fmt = (n) => saas.formatCurrency(n, currency);
5937
- const totals = React14.useMemo(() => {
7262
+ const totals = React17.useMemo(() => {
5938
7263
  const active = invoices.filter((i) => i.status !== "cancelled");
5939
7264
  const billed = active.reduce((s, i) => s + (i.totalAmount ?? 0), 0);
5940
7265
  const paid = active.reduce((s, i) => s + (i.paidAmount ?? 0), 0);
@@ -5944,7 +7269,7 @@ function PersonStatementWidget({ person, provider, config }) {
5944
7269
  const base = inv.direction === "credit" ? "receivables" : "payables";
5945
7270
  window.location.hash = `/financial/${base}/detail/${inv.id}`;
5946
7271
  };
5947
- const columns = React14.useMemo(() => [
7272
+ const columns = React17.useMemo(() => [
5948
7273
  {
5949
7274
  id: "date",
5950
7275
  header: t2("financial.invoice.columnDate"),
@@ -6044,6 +7369,8 @@ function resolveConfig(options) {
6044
7369
  // Opt-in: only shown when an app explicitly enables reconciliation (bank connector present).
6045
7370
  reconciliation: options?.modules?.reconciliation === true
6046
7371
  },
7372
+ // Opt-in: only render the plugin's own quick-add buttons/FAB when the host asks.
7373
+ quickAdd: options?.quickAdd === true,
6047
7374
  labels: { ...DEFAULT_LABELS, ...options?.labels },
6048
7375
  currency: { ...DEFAULT_CURRENCY, ...options?.currency },
6049
7376
  itemTypes: options?.itemTypes ?? DEFAULT_ITEM_TYPES,
@@ -6064,9 +7391,10 @@ function createFinancialPlugin(options) {
6064
7391
  const provider = options?.dataProvider ?? createSafeFinancialProvider();
6065
7392
  const store = createFinancialStore(provider);
6066
7393
  const dashboardWidgets = createFinancialDashboardWidgets({ config, provider, store });
6067
- const PersonStatementBound = (props) => React14__default.default.createElement(PersonStatementWidget, { person: props.item, provider, config });
7394
+ const registries = [...financialRegistries, ...options?.settingsRegistries ?? []];
7395
+ const PersonStatementBound = (props) => React17__namespace.default.createElement(PersonStatementWidget, { person: props.item, provider, config });
6068
7396
  PersonStatementBound.displayName = "FinancialPersonStatement";
6069
- const PageComponent = () => React14__default.default.createElement(FinancialPage, { config, provider, store, registries: financialRegistries });
7397
+ const PageComponent = () => React17__namespace.default.createElement(FinancialPage, { config, provider, store, registries });
6070
7398
  return {
6071
7399
  id: "financial",
6072
7400
  name: config.labels.pageTitle,
@@ -6171,18 +7499,18 @@ function createFinancialPlugin(options) {
6171
7499
  ]
6172
7500
  }
6173
7501
  ],
6174
- registries: financialRegistries,
7502
+ registries,
6175
7503
  settings: [
6176
7504
  {
6177
7505
  id: "financial",
6178
7506
  label: "Financial",
6179
7507
  icon: "DollarSign",
6180
7508
  component: (() => {
6181
- const FinancialSettingsTab = () => React14__default.default.createElement(saas.PluginSettingsPanel, {
7509
+ const FinancialSettingsTab = () => React17__namespace.default.createElement(saas.PluginSettingsPanel, {
6182
7510
  title: "Financial Settings",
6183
7511
  subtitle: "Payment methods, accounts, and configurations",
6184
- generalSettings: React14__default.default.createElement(FinancialGeneralSettings),
6185
- registries: financialRegistries,
7512
+ generalSettings: React17__namespace.default.createElement(FinancialGeneralSettings),
7513
+ registries,
6186
7514
  hostPluginId: "financial",
6187
7515
  routeBase: "/settings/financial"
6188
7516
  });
@@ -6198,6 +7526,8 @@ function createFinancialPlugin(options) {
6198
7526
  }
6199
7527
 
6200
7528
  exports.createFinancialPlugin = createFinancialPlugin;
7529
+ exports.createMockFinancialProvider = createMockFinancialProvider;
6201
7530
  exports.createSafeFinancialProvider = createSafeFinancialProvider;
7531
+ exports.openQuickAdd = openQuickAdd;
6202
7532
  //# sourceMappingURL=index.cjs.map
6203
7533
  //# sourceMappingURL=index.cjs.map