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