@fayz-ai/plugin-financial 0.9.0 → 0.9.1
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 +3 -3
- package/dist/components/PersonStatementWidget.d.ts +1 -1
- package/dist/components/PersonStatementWidget.d.ts.map +1 -1
- package/dist/{FinancialContext.d.ts → context.d.ts} +4 -2
- package/dist/context.d.ts.map +1 -0
- package/dist/data/registries.d.ts.map +1 -0
- package/dist/index.d.ts +10 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1208 -1368
- package/dist/index.js.map +1 -1
- package/dist/lib/message-events.d.ts +12 -0
- package/dist/lib/message-events.d.ts.map +1 -0
- package/dist/{quick-add.d.ts → lib/quick-add.d.ts} +1 -1
- package/dist/lib/quick-add.d.ts.map +1 -0
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/{FinancialPage.d.ts → views/FinancialPage.d.ts} +3 -3
- package/dist/views/FinancialPage.d.ts.map +1 -0
- package/dist/views/InvoiceDetailView.d.ts.map +1 -1
- package/dist/views/InvoiceFormView.d.ts.map +1 -1
- package/dist/views/InvoiceListView.d.ts.map +1 -1
- package/dist/views/PayablesView.d.ts +1 -1
- package/dist/views/PayablesView.d.ts.map +1 -1
- package/dist/views/ReceivablesView.d.ts +1 -1
- package/dist/views/ReceivablesView.d.ts.map +1 -1
- package/dist/views/StatementsView.d.ts.map +1 -1
- package/dist/views/dashboardWidgets.d.ts +1 -1
- package/dist/views/dashboardWidgets.d.ts.map +1 -1
- package/package.json +12 -14
- package/dist/FinancialContext.d.ts.map +0 -1
- package/dist/FinancialPage.d.ts.map +0 -1
- package/dist/contract.test.d.ts +0 -2
- package/dist/contract.test.d.ts.map +0 -1
- package/dist/quick-add.d.ts.map +0 -1
- package/dist/registries.d.ts.map +0 -1
- package/src/FinancialContext.tsx +0 -59
- package/src/FinancialPage.tsx +0 -413
- package/src/README.md +0 -155
- package/src/components/FinancialGeneralSettings.tsx +0 -39
- package/src/components/FinancialOnboarding.tsx +0 -87
- package/src/components/FinancialSettings.tsx +0 -31
- package/src/components/PaymentModal.tsx +0 -369
- package/src/components/PersonStatementWidget.tsx +0 -133
- package/src/components/TransferModal.tsx +0 -150
- package/src/contract.test.ts +0 -58
- package/src/data/capability.test.ts +0 -135
- package/src/data/index.ts +0 -3
- package/src/data/mock.ts +0 -842
- package/src/data/supabase.ts +0 -968
- package/src/data/tables.ts +0 -13
- package/src/data/types.ts +0 -86
- package/src/index.ts +0 -487
- package/src/lib/tenant.ts +0 -3
- package/src/locales/en.ts +0 -493
- package/src/locales/index.ts +0 -7
- package/src/locales/pt-BR.ts +0 -493
- package/src/migrations/000_plg_rename.sql +0 -30
- package/src/migrations/001_financial_base.sql +0 -109
- package/src/migrations/002_chart_of_accounts.sql +0 -28
- package/src/migrations/003_card_brands.sql +0 -12
- package/src/migrations/004_order_to_cash.sql +0 -178
- package/src/migrations/005_seed_defaults.sql +0 -38
- package/src/migrations/006_rls_policies.sql +0 -37
- package/src/migrations/006b_extract_fee_amount.sql +0 -20
- package/src/migrations/007_reconciliation.sql +0 -32
- package/src/migrations/007b_movement_payment_method_type.sql +0 -6
- package/src/migrations/008_split_payment_movements.sql +0 -29
- package/src/migrations/009_agent_rpcs.sql +0 -165
- package/src/migrations/010_invoice_internal.sql +0 -133
- package/src/migrations/011_nfe.sql +0 -58
- package/src/migrations/index.ts +0 -923
- package/src/quick-add.ts +0 -58
- package/src/registries.ts +0 -308
- package/src/store.ts +0 -329
- package/src/types.ts +0 -592
- package/src/views/CardsView.tsx +0 -213
- package/src/views/CashRegistersView.tsx +0 -597
- package/src/views/CommissionsView.tsx +0 -18
- package/src/views/InvoiceDetailView.tsx +0 -616
- package/src/views/InvoiceFormView.tsx +0 -945
- package/src/views/InvoiceListView.tsx +0 -323
- package/src/views/NotasFiscaisView.tsx +0 -209
- package/src/views/PayablesView.tsx +0 -48
- package/src/views/QuickTransactionForm.tsx +0 -548
- package/src/views/ReceivablesView.tsx +0 -48
- package/src/views/ReconciliationView.tsx +0 -211
- package/src/views/StatementsView.tsx +0 -335
- package/src/views/SummaryView.tsx +0 -203
- package/src/views/dashboardWidgets.tsx +0 -437
- package/src/views/navReferrer.ts +0 -24
- /package/dist/{registries.d.ts → data/registries.d.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import * as React18 from 'react';
|
|
2
2
|
import React18__default, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
|
|
3
|
-
import { createSafeDataProvider, registerTranslations, useTranslation, getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core';
|
|
4
|
-
import { toast, defineKpiWidget, defineChartWidget, defineCustomWidget, defineTableWidget, DataTable, PageTransition, ModulePage, Button, Modal, ModalContent, DatePicker, DashboardCanvas, SubpageHeader, Skeleton, Input, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, CurrencyInput, useSaveBar, SearchSelect, ListView, KpiCard, ChartWidget,
|
|
5
|
-
import { createPluginContext, PluginSettingsPanel, dedup, useModuleNavigation, usePermissionOptional, useModuleNavAccess, PermissionGate, ModuleActionBar, useTenantPluginSettings, SettingsGroup, ToggleRow, formatCurrency, parseViewId, EntitlementGate, UpgradePrompt, useLimitGuard, invalidateLimit, PersonLink } from '@fayz-ai/
|
|
6
|
-
import { Wallet, Plus, DollarSign, Landmark, CreditCard, BookOpen, Check, ChevronRight, X, CalendarDays, Paperclip, Camera, Tag, Repeat, ChevronDown, FileText, FileCheck2, ExternalLink, Send, Ban, CalendarClock, RefreshCw, ArrowDownCircle, ArrowUpCircle, Sparkles, Link2, ArrowRightLeft, Receipt, Clock, ArrowLeftRight, CheckCheck, Calculator, StickyNote, Play, Square, ChevronUp, MoreVertical,
|
|
3
|
+
import { createSafeDataProvider, registerTranslations, registerMessageDefaults, useTranslation, getSupabaseClientOptional, getActiveTenantId, exportCSV } from '@fayz-ai/core';
|
|
4
|
+
import { toast, defineKpiWidget, defineChartWidget, defineCustomWidget, defineTableWidget, DataTable, PageTransition, ModulePage, Button, Modal, ModalContent, DatePicker, DashboardCanvas, SubpageHeader, Skeleton, Card, FilterChip, Input, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, CurrencyInput, useSaveBar, SearchSelect, ListView, KpiCard, ChartWidget, CardHeader, CardTitle, CardContent, TableWidget, useDashboardNavigate, GoldCard } from '@fayz-ai/ui';
|
|
5
|
+
import { createPluginContext, PluginSettingsPanel, dedup, useModuleNavigation, usePermissionOptional, useModuleNavAccess, PermissionGate, ModuleActionBar, useTenantPluginSettings, SettingsGroup, ToggleRow, formatCurrency, parseViewId, EntitlementGate, UpgradePrompt, useLimitGuard, invalidateLimit, PersonLink, OrderFormSkeleton, OrderFormBlock, OrderFormView, OrderFormItemsCard, OrderFormItemRow, OrderFormAddItemStep, OrderDetailSkeleton, OrderDetailBlock, OrderDetailView } from '@fayz-ai/admin';
|
|
6
|
+
import { Wallet, Plus, DollarSign, Landmark, CreditCard, BookOpen, Check, ChevronRight, X, CalendarDays, Paperclip, Camera, Tag, Repeat, ChevronDown, FileText, FileCheck2, ExternalLink, Send, Ban, CalendarClock, RefreshCw, ArrowDownCircle, ArrowUpCircle, Sparkles, Link2, ArrowRightLeft, Search, Download, Receipt, Clock, ArrowLeftRight, CheckCheck, Calculator, StickyNote, Play, Square, ChevronUp, MoreVertical, CircleCheckBig, CircleDashed, CircleAlert, CircleEllipsis, Pencil, LayoutList, Layers, Building2, ShoppingCart, Car, Home, Utensils, Zap, Film, HeartPulse, Dumbbell, Music, ShoppingBag, CircleDollarSign, FileCheck, QrCode, Banknote, ArrowUpRight, ArrowDownRight, AlertTriangle, EyeOff, Eye } from 'lucide-react';
|
|
7
7
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
8
8
|
import { createPortal } from 'react-dom';
|
|
9
9
|
import { createStore as createStore$1 } from 'zustand/vanilla';
|
|
@@ -789,7 +789,6 @@ function InvoiceListView({ direction, onNew, onEdit }) {
|
|
|
789
789
|
});
|
|
790
790
|
}, [direction, statusFilter, search, accountId, costCenterId, dateRange]);
|
|
791
791
|
const filtered = invoices.filter((inv) => inv.direction === direction);
|
|
792
|
-
const listTitle = direction === "debit" ? t2("financial.invoice.accountsPayable") : t2("financial.invoice.accountsReceivable");
|
|
793
792
|
const columns = useInvoiceColumns(currency);
|
|
794
793
|
const tags = useMemo(
|
|
795
794
|
() => STATUS_OPTIONS.filter((o) => o.value !== "cancelled").map((o) => ({ value: o.value, label: t2(o.labelKey) })),
|
|
@@ -809,114 +808,85 @@ function InvoiceListView({ direction, onNew, onEdit }) {
|
|
|
809
808
|
}, [filtered]);
|
|
810
809
|
const receivedLabel = direction === "debit" ? t2("financial.invoice.summaryPaid") : t2("financial.invoice.summaryReceived");
|
|
811
810
|
const pendingLabel = direction === "debit" ? t2("financial.invoice.summaryPayable") : t2("financial.invoice.summaryReceivable");
|
|
812
|
-
|
|
811
|
+
const exportRows = () => {
|
|
812
|
+
exportCSV(
|
|
813
|
+
[
|
|
814
|
+
{ key: "fiscalNumber", label: "#" },
|
|
815
|
+
{ key: "invoiceDate", label: t2("financial.invoice.columnDate") },
|
|
816
|
+
{ key: "contactName", label: t2("financial.invoice.columnDescription") },
|
|
817
|
+
{ key: "totalAmount", label: t2("financial.invoice.columnAmount"), format: (v) => formatCurrency(Number(v) || 0, currency) },
|
|
818
|
+
{ key: "paidAmount", label: t2("financial.invoice.columnPaid"), format: (v) => formatCurrency(Number(v) || 0, currency) },
|
|
819
|
+
{ key: "status", label: t2("financial.invoice.columnStatus"), format: (v) => {
|
|
820
|
+
const opt = STATUS_OPTIONS.find((o) => o.value === v);
|
|
821
|
+
return opt ? t2(opt.labelKey) : String(v ?? "");
|
|
822
|
+
} },
|
|
823
|
+
{ key: "totalInstallments", label: t2("financial.invoice.columnInst") }
|
|
824
|
+
],
|
|
825
|
+
filtered,
|
|
826
|
+
direction === "debit" ? "contas-a-pagar" : "contas-a-receber"
|
|
827
|
+
);
|
|
828
|
+
};
|
|
829
|
+
const listFilters = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
813
830
|
/* @__PURE__ */ jsx(
|
|
814
|
-
|
|
831
|
+
FilterChip,
|
|
815
832
|
{
|
|
816
|
-
|
|
817
|
-
|
|
833
|
+
icon: CalendarDays,
|
|
834
|
+
label: t2("financial.invoice.filterPeriod"),
|
|
835
|
+
allLabel: t2("financial.invoice.filterAllPeriods"),
|
|
836
|
+
value: datePreset === "all" ? void 0 : datePreset,
|
|
837
|
+
onChange: (next) => applyPreset(next ?? "all"),
|
|
838
|
+
options: [
|
|
839
|
+
{ value: "thisMonth", label: t2("financial.invoice.filterThisMonth") },
|
|
840
|
+
{ value: "lastMonth", label: t2("financial.invoice.filterLastMonth") },
|
|
841
|
+
{ value: "last30", label: t2("financial.invoice.filterLast30") },
|
|
842
|
+
{ value: "thisYear", label: t2("financial.invoice.filterThisYear") },
|
|
843
|
+
{ value: "custom", label: t2("financial.invoice.filterCustom") }
|
|
844
|
+
]
|
|
818
845
|
}
|
|
819
846
|
),
|
|
820
|
-
/* @__PURE__ */ jsxs(
|
|
821
|
-
/* @__PURE__ */
|
|
822
|
-
|
|
823
|
-
/* @__PURE__ */ jsxs(
|
|
824
|
-
"select",
|
|
825
|
-
{
|
|
826
|
-
className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
|
|
827
|
-
value: datePreset,
|
|
828
|
-
onChange: (event) => applyPreset(event.target.value),
|
|
829
|
-
children: [
|
|
830
|
-
/* @__PURE__ */ jsx("option", { value: "all", children: t2("financial.invoice.filterAllPeriods") }),
|
|
831
|
-
/* @__PURE__ */ jsx("option", { value: "thisMonth", children: t2("financial.invoice.filterThisMonth") }),
|
|
832
|
-
/* @__PURE__ */ jsx("option", { value: "lastMonth", children: t2("financial.invoice.filterLastMonth") }),
|
|
833
|
-
/* @__PURE__ */ jsx("option", { value: "last30", children: t2("financial.invoice.filterLast30") }),
|
|
834
|
-
/* @__PURE__ */ jsx("option", { value: "thisYear", children: t2("financial.invoice.filterThisYear") }),
|
|
835
|
-
/* @__PURE__ */ jsx("option", { value: "custom", children: t2("financial.invoice.filterCustom") })
|
|
836
|
-
]
|
|
837
|
-
}
|
|
838
|
-
)
|
|
839
|
-
] }),
|
|
840
|
-
/* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
|
|
841
|
-
t2("financial.invoice.filterFrom"),
|
|
842
|
-
/* @__PURE__ */ jsx(
|
|
843
|
-
"input",
|
|
844
|
-
{
|
|
845
|
-
type: "date",
|
|
846
|
-
className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
|
|
847
|
-
value: dateFrom,
|
|
848
|
-
max: dateTo || void 0,
|
|
849
|
-
onChange: (event) => {
|
|
850
|
-
setDateFrom(event.target.value);
|
|
851
|
-
setDatePreset("custom");
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
)
|
|
855
|
-
] }),
|
|
856
|
-
/* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
|
|
857
|
-
t2("financial.invoice.filterTo"),
|
|
858
|
-
/* @__PURE__ */ jsx(
|
|
859
|
-
"input",
|
|
860
|
-
{
|
|
861
|
-
type: "date",
|
|
862
|
-
className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
|
|
863
|
-
value: dateTo,
|
|
864
|
-
min: dateFrom || void 0,
|
|
865
|
-
onChange: (event) => {
|
|
866
|
-
setDateTo(event.target.value);
|
|
867
|
-
setDatePreset("custom");
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
)
|
|
871
|
-
] }),
|
|
872
|
-
(dateFrom || dateTo) && /* @__PURE__ */ jsx(
|
|
873
|
-
"button",
|
|
874
|
-
{
|
|
875
|
-
type: "button",
|
|
876
|
-
className: "h-9 rounded-md px-2 text-xs font-medium text-muted-foreground hover:text-foreground",
|
|
877
|
-
onClick: () => applyPreset("all"),
|
|
878
|
-
children: t2("financial.invoice.filterClear")
|
|
879
|
-
}
|
|
880
|
-
),
|
|
881
|
-
accounts.length > 0 && /* @__PURE__ */ jsxs("label", { className: "flex min-w-[220px] flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
|
|
882
|
-
t2("financial.invoice.filterAccount"),
|
|
883
|
-
/* @__PURE__ */ jsxs(
|
|
884
|
-
"select",
|
|
885
|
-
{
|
|
886
|
-
className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
|
|
887
|
-
value: accountId,
|
|
888
|
-
onChange: (event) => setAccountId(event.target.value),
|
|
889
|
-
children: [
|
|
890
|
-
/* @__PURE__ */ jsx("option", { value: "", children: t2("financial.invoice.filterAllAccounts") }),
|
|
891
|
-
accounts.map((account) => /* @__PURE__ */ jsxs("option", { value: account.id, children: [
|
|
892
|
-
account.code,
|
|
893
|
-
" - ",
|
|
894
|
-
account.name
|
|
895
|
-
] }, account.id))
|
|
896
|
-
]
|
|
897
|
-
}
|
|
898
|
-
)
|
|
899
|
-
] }),
|
|
900
|
-
costCenters.length > 0 && /* @__PURE__ */ jsxs("label", { className: "flex min-w-[220px] flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
|
|
901
|
-
t2("financial.invoice.filterCostCenter"),
|
|
902
|
-
/* @__PURE__ */ jsxs(
|
|
903
|
-
"select",
|
|
904
|
-
{
|
|
905
|
-
className: "h-9 rounded-md border bg-background px-3 text-sm text-foreground",
|
|
906
|
-
value: costCenterId,
|
|
907
|
-
onChange: (event) => setCostCenterId(event.target.value),
|
|
908
|
-
children: [
|
|
909
|
-
/* @__PURE__ */ jsx("option", { value: "", children: t2("financial.invoice.filterAllCostCenters") }),
|
|
910
|
-
costCenters.map((costCenter) => /* @__PURE__ */ jsxs("option", { value: costCenter.id, children: [
|
|
911
|
-
costCenter.code,
|
|
912
|
-
" - ",
|
|
913
|
-
costCenter.name
|
|
914
|
-
] }, costCenter.id))
|
|
915
|
-
]
|
|
916
|
-
}
|
|
917
|
-
)
|
|
918
|
-
] })
|
|
847
|
+
datePreset === "custom" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
848
|
+
/* @__PURE__ */ jsx(DatePicker, { value: dateFrom, onChange: setDateFrom, variant: "chip" }),
|
|
849
|
+
/* @__PURE__ */ jsx(DatePicker, { value: dateTo, onChange: setDateTo, variant: "chip" })
|
|
919
850
|
] }),
|
|
851
|
+
(dateFrom || dateTo) && /* @__PURE__ */ jsx(
|
|
852
|
+
"button",
|
|
853
|
+
{
|
|
854
|
+
type: "button",
|
|
855
|
+
className: "h-9 rounded-md px-2 text-xs font-medium text-muted-foreground hover:text-foreground",
|
|
856
|
+
onClick: () => applyPreset("all"),
|
|
857
|
+
children: t2("financial.invoice.filterClear")
|
|
858
|
+
}
|
|
859
|
+
),
|
|
860
|
+
accounts.length > 0 && /* @__PURE__ */ jsx(
|
|
861
|
+
FilterChip,
|
|
862
|
+
{
|
|
863
|
+
icon: Landmark,
|
|
864
|
+
label: t2("financial.invoice.filterAccount"),
|
|
865
|
+
allLabel: t2("financial.invoice.filterAllAccounts"),
|
|
866
|
+
value: accountId || void 0,
|
|
867
|
+
onChange: (next) => setAccountId(next ?? ""),
|
|
868
|
+
options: accounts.map((account) => ({
|
|
869
|
+
value: account.id,
|
|
870
|
+
label: `${account.code} - ${account.name}`
|
|
871
|
+
}))
|
|
872
|
+
}
|
|
873
|
+
),
|
|
874
|
+
costCenters.length > 0 && /* @__PURE__ */ jsx(
|
|
875
|
+
FilterChip,
|
|
876
|
+
{
|
|
877
|
+
icon: Building2,
|
|
878
|
+
label: t2("financial.invoice.filterCostCenter"),
|
|
879
|
+
allLabel: t2("financial.invoice.filterAllCostCenters"),
|
|
880
|
+
value: costCenterId || void 0,
|
|
881
|
+
onChange: (next) => setCostCenterId(next ?? ""),
|
|
882
|
+
options: costCenters.map((costCenter) => ({
|
|
883
|
+
value: costCenter.id,
|
|
884
|
+
label: `${costCenter.code} - ${costCenter.name}`
|
|
885
|
+
}))
|
|
886
|
+
}
|
|
887
|
+
)
|
|
888
|
+
] });
|
|
889
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
920
890
|
/* @__PURE__ */ jsx(
|
|
921
891
|
ListView,
|
|
922
892
|
{
|
|
@@ -928,823 +898,37 @@ function InvoiceListView({ direction, onNew, onEdit }) {
|
|
|
928
898
|
onSearchChange: setSearch,
|
|
929
899
|
searchDebounce: 0,
|
|
930
900
|
tags,
|
|
901
|
+
allTagLabel: t2("crud.list.allFacet"),
|
|
931
902
|
activeTag: statusFilter.length === 1 ? statusFilter[0] : void 0,
|
|
932
903
|
onTagChange: (v) => setStatusFilter(v ? [v] : []),
|
|
904
|
+
filters: listFilters,
|
|
905
|
+
onExport: filtered.length > 0 ? exportRows : void 0,
|
|
906
|
+
exportLabel: t2("crud.export.action"),
|
|
933
907
|
newLabel: t2("financial.invoice.new"),
|
|
934
908
|
onNew: onNewGated,
|
|
935
909
|
onRowClick: (row) => onEdit?.(row.id),
|
|
936
910
|
emptyIcon: FileText,
|
|
937
911
|
emptyMessage: t2("financial.invoice.noInvoices"),
|
|
938
|
-
emptyActionLabel: onNewGated ? t2("financial.invoice.createFirst") : void 0,
|
|
939
|
-
onEmptyAction: onNewGated
|
|
940
|
-
}
|
|
941
|
-
),
|
|
942
|
-
filtered.length > 0 && !invoicesLoading && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-6 rounded-lg border bg-muted/30 px-5 py-3 text-sm", children: [
|
|
943
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
944
|
-
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t2("financial.invoice.summaryTotal") }),
|
|
945
|
-
/* @__PURE__ */ jsx("span", { className: "font-semibold", children: formatCurrency(summary.totalAmount, currency) })
|
|
946
|
-
] }),
|
|
947
|
-
/* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-border" }),
|
|
948
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
949
|
-
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: receivedLabel }),
|
|
950
|
-
/* @__PURE__ */ jsx("span", { className: "font-semibold text-success", children: formatCurrency(summary.paidAmount, currency) })
|
|
951
|
-
] }),
|
|
952
|
-
/* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-border" }),
|
|
953
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
954
|
-
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: pendingLabel }),
|
|
955
|
-
/* @__PURE__ */ jsx("span", { className: "font-semibold text-warning", children: formatCurrency(summary.openAmount, currency) })
|
|
956
|
-
] })
|
|
957
|
-
] })
|
|
958
|
-
] });
|
|
959
|
-
}
|
|
960
|
-
function LocationSelector({ locations, value, onChange }) {
|
|
961
|
-
const t2 = useTranslation();
|
|
962
|
-
const [open, setOpen] = useState(false);
|
|
963
|
-
const [search, setSearch] = useState("");
|
|
964
|
-
const ref = useRef(null);
|
|
965
|
-
useEffect(() => {
|
|
966
|
-
function handler(e) {
|
|
967
|
-
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
968
|
-
}
|
|
969
|
-
document.addEventListener("mousedown", handler);
|
|
970
|
-
return () => document.removeEventListener("mousedown", handler);
|
|
971
|
-
}, []);
|
|
972
|
-
const selected = locations.find((l) => l.id === value);
|
|
973
|
-
const filtered = locations.filter(
|
|
974
|
-
(l) => !search.trim() || l.name.toLowerCase().includes(search.toLowerCase())
|
|
975
|
-
);
|
|
976
|
-
return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
|
|
977
|
-
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.unit") }),
|
|
978
|
-
/* @__PURE__ */ jsxs(
|
|
979
|
-
"button",
|
|
980
|
-
{
|
|
981
|
-
type: "button",
|
|
982
|
-
onClick: () => setOpen(!open),
|
|
983
|
-
className: "w-full mt-1 flex items-center gap-2 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm text-left hover:bg-muted/30 transition-colors",
|
|
984
|
-
children: [
|
|
985
|
-
/* @__PURE__ */ jsx(Building2, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
|
|
986
|
-
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: selected?.name ?? t2("financial.invoiceForm.selectUnit") }),
|
|
987
|
-
/* @__PURE__ */ jsx(ChevronDown, { className: `h-3 w-3 text-muted-foreground transition-transform ${open ? "rotate-180" : ""}` })
|
|
988
|
-
]
|
|
989
|
-
}
|
|
990
|
-
),
|
|
991
|
-
open && /* @__PURE__ */ jsxs("div", { className: "fayz-glass-surface absolute top-full left-0 right-0 z-50 mt-1 rounded-lg border bg-popover shadow-lg overflow-hidden min-w-[260px]", children: [
|
|
992
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b", children: [
|
|
993
|
-
/* @__PURE__ */ jsx(Building2, { className: "h-4 w-4 text-muted-foreground" }),
|
|
994
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: t2("financial.invoiceForm.selectUnitTitle") }),
|
|
995
|
-
/* @__PURE__ */ jsx("button", { onClick: () => setOpen(false), className: "ml-auto text-muted-foreground hover:text-foreground", children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" }) })
|
|
996
|
-
] }),
|
|
997
|
-
/* @__PURE__ */ jsx("div", { className: "p-2 border-b", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5", children: [
|
|
998
|
-
/* @__PURE__ */ jsx(Search, { className: "h-3.5 w-3.5 text-muted-foreground" }),
|
|
999
|
-
/* @__PURE__ */ jsx(
|
|
1000
|
-
"input",
|
|
1001
|
-
{
|
|
1002
|
-
type: "text",
|
|
1003
|
-
autoFocus: true,
|
|
1004
|
-
value: search,
|
|
1005
|
-
onChange: (e) => setSearch(e.target.value),
|
|
1006
|
-
placeholder: t2("financial.invoiceForm.searchUnit"),
|
|
1007
|
-
className: "flex-1 bg-transparent text-sm outline-none"
|
|
1008
|
-
}
|
|
1009
|
-
)
|
|
1010
|
-
] }) }),
|
|
1011
|
-
/* @__PURE__ */ jsxs("div", { className: "max-h-56 overflow-auto py-1", children: [
|
|
1012
|
-
filtered.map((loc) => /* @__PURE__ */ jsxs(
|
|
1013
|
-
"button",
|
|
1014
|
-
{
|
|
1015
|
-
onClick: () => {
|
|
1016
|
-
onChange(loc.id);
|
|
1017
|
-
setOpen(false);
|
|
1018
|
-
setSearch("");
|
|
1019
|
-
},
|
|
1020
|
-
className: `w-full flex items-center gap-3 px-3 py-2.5 text-left hover:bg-muted/50 transition-colors ${loc.id === value ? "bg-muted/30" : ""}`,
|
|
1021
|
-
children: [
|
|
1022
|
-
/* @__PURE__ */ jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-muted shrink-0", children: /* @__PURE__ */ jsx(Building2, { className: "h-4 w-4 text-muted-foreground" }) }),
|
|
1023
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
1024
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: loc.name }),
|
|
1025
|
-
loc.isHQ && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: t2("financial.invoiceForm.headquarters") })
|
|
1026
|
-
] })
|
|
1027
|
-
]
|
|
1028
|
-
},
|
|
1029
|
-
loc.id
|
|
1030
|
-
)),
|
|
1031
|
-
filtered.length === 0 && /* @__PURE__ */ jsx("p", { className: "px-3 py-4 text-center text-xs text-muted-foreground", children: t2("financial.invoiceForm.noUnitsFound") })
|
|
1032
|
-
] })
|
|
1033
|
-
] })
|
|
1034
|
-
] });
|
|
1035
|
-
}
|
|
1036
|
-
var formId = 1;
|
|
1037
|
-
function fid() {
|
|
1038
|
-
return `f${formId++}`;
|
|
1039
|
-
}
|
|
1040
|
-
function ItemRow({ item, index, itemTypes, currency, expanded, onToggle, onUpdate, onRemove, entityLookups, showTypeSelector }) {
|
|
1041
|
-
const t2 = useTranslation();
|
|
1042
|
-
const total = item.quantity * item.unitPrice - (item.discount ?? 0) + (item.surcharge ?? 0);
|
|
1043
|
-
const kindLabel = itemTypes.find((tp) => tp.value === item.itemKind)?.label ?? item.itemKind;
|
|
1044
|
-
const hasAdjustments = (item.discount ?? 0) > 0 || (item.surcharge ?? 0) > 0;
|
|
1045
|
-
const [showAdjustments, setShowAdjustments] = useState(hasAdjustments);
|
|
1046
|
-
function handleFieldKeyDown(e) {
|
|
1047
|
-
if (e.key === "Enter" && !e.shiftKey) {
|
|
1048
|
-
e.preventDefault();
|
|
1049
|
-
onToggle();
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
return /* @__PURE__ */ jsxs("div", { className: "border-b last:border-0", children: [
|
|
1053
|
-
/* @__PURE__ */ jsxs(
|
|
1054
|
-
"div",
|
|
1055
|
-
{
|
|
1056
|
-
className: "flex items-center gap-2 px-4 py-3 cursor-pointer hover:bg-muted/30 transition-colors group",
|
|
1057
|
-
onClick: onToggle,
|
|
1058
|
-
children: [
|
|
1059
|
-
/* @__PURE__ */ jsx(ChevronDown, { className: `h-3 w-3 text-muted-foreground shrink-0 transition-transform ${expanded ? "rotate-0" : "-rotate-90"}` }),
|
|
1060
|
-
/* @__PURE__ */ jsxs("span", { className: "text-[10px] font-medium text-muted-foreground w-5 shrink-0", children: [
|
|
1061
|
-
index + 1,
|
|
1062
|
-
"."
|
|
1063
|
-
] }),
|
|
1064
|
-
/* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded px-1.5 py-0.5 text-[9px] font-medium bg-muted text-muted-foreground shrink-0", children: kindLabel }),
|
|
1065
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm truncate flex-1 min-w-0", children: item.description || /* @__PURE__ */ jsx("span", { className: "text-muted-foreground italic", children: t2("financial.invoiceForm.noDescription") }) }),
|
|
1066
|
-
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground shrink-0", children: [
|
|
1067
|
-
item.quantity,
|
|
1068
|
-
" x ",
|
|
1069
|
-
formatCurrency(item.unitPrice, currency)
|
|
1070
|
-
] }),
|
|
1071
|
-
hasAdjustments && /* @__PURE__ */ jsx(Tag, { className: "h-3 w-3 text-warning shrink-0" }),
|
|
1072
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold shrink-0 min-w-[80px] text-right", children: formatCurrency(total, currency) }),
|
|
1073
|
-
/* @__PURE__ */ jsx(
|
|
1074
|
-
"button",
|
|
1075
|
-
{
|
|
1076
|
-
onClick: (e) => {
|
|
1077
|
-
e.stopPropagation();
|
|
1078
|
-
onRemove();
|
|
1079
|
-
},
|
|
1080
|
-
className: "p-1 text-muted-foreground opacity-0 group-hover:opacity-100 hover:text-destructive transition-all shrink-0",
|
|
1081
|
-
children: /* @__PURE__ */ jsx(Trash2, { className: "h-3 w-3" })
|
|
1082
|
-
}
|
|
1083
|
-
)
|
|
1084
|
-
]
|
|
1085
|
-
}
|
|
1086
|
-
),
|
|
1087
|
-
expanded && /* @__PURE__ */ jsxs("div", { className: "px-4 pb-4 pt-1 ml-8 space-y-3 bg-muted/10", children: [
|
|
1088
|
-
showTypeSelector && /* @__PURE__ */ jsx("div", { className: "flex gap-1.5", children: itemTypes.map((tp) => /* @__PURE__ */ jsx(
|
|
1089
|
-
"button",
|
|
1090
|
-
{
|
|
1091
|
-
onClick: () => onUpdate({ itemKind: tp.value }),
|
|
1092
|
-
className: `rounded-full px-2.5 py-1 text-[10px] font-medium transition-colors ${item.itemKind === tp.value ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-muted/80"}`,
|
|
1093
|
-
children: tp.label
|
|
1094
|
-
},
|
|
1095
|
-
tp.value
|
|
1096
|
-
)) }),
|
|
1097
|
-
/* @__PURE__ */ jsxs("div", { className: "grid gap-3 grid-cols-12", children: [
|
|
1098
|
-
/* @__PURE__ */ jsx("div", { className: "col-span-6 sm:col-span-7", children: entityLookups[item.itemKind] ? /* @__PURE__ */ jsx(
|
|
1099
|
-
SearchSelect,
|
|
1100
|
-
{
|
|
1101
|
-
label: t2("financial.invoiceForm.item"),
|
|
1102
|
-
value: item.referenceId ?? "",
|
|
1103
|
-
displayValue: item.description,
|
|
1104
|
-
loadOnOpen: true,
|
|
1105
|
-
onChange: (id, opt) => {
|
|
1106
|
-
const price = opt?.data?.price;
|
|
1107
|
-
onUpdate({
|
|
1108
|
-
referenceId: id || void 0,
|
|
1109
|
-
description: opt?.label ?? "",
|
|
1110
|
-
...price != null ? { unitPrice: price } : {}
|
|
1111
|
-
});
|
|
1112
|
-
},
|
|
1113
|
-
onSearch: async (q) => {
|
|
1114
|
-
const results = await entityLookups[item.itemKind].search(q);
|
|
1115
|
-
return results.map((r) => ({
|
|
1116
|
-
id: r.id,
|
|
1117
|
-
label: r.label,
|
|
1118
|
-
subtitle: r.subtitle,
|
|
1119
|
-
group: r.group,
|
|
1120
|
-
data: r
|
|
1121
|
-
}));
|
|
1122
|
-
},
|
|
1123
|
-
placeholder: `Search ${itemTypes.find((tp) => tp.value === item.itemKind)?.label?.toLowerCase() ?? "item"}...`,
|
|
1124
|
-
renderOption: (opt) => /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
1125
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm truncate", children: opt.label }),
|
|
1126
|
-
opt.subtitle && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: opt.subtitle })
|
|
1127
|
-
] })
|
|
1128
|
-
}
|
|
1129
|
-
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1130
|
-
/* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium text-muted-foreground", children: t2("financial.invoiceForm.description") }),
|
|
1131
|
-
/* @__PURE__ */ jsx(
|
|
1132
|
-
"input",
|
|
1133
|
-
{
|
|
1134
|
-
type: "text",
|
|
1135
|
-
value: item.description,
|
|
1136
|
-
onChange: (e) => onUpdate({ description: e.target.value }),
|
|
1137
|
-
onKeyDown: handleFieldKeyDown,
|
|
1138
|
-
placeholder: t2("financial.invoiceForm.itemDescription"),
|
|
1139
|
-
autoFocus: true,
|
|
1140
|
-
className: "w-full mt-0.5 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
1141
|
-
}
|
|
1142
|
-
)
|
|
1143
|
-
] }) }),
|
|
1144
|
-
/* @__PURE__ */ jsxs("div", { className: "col-span-2 sm:col-span-1", children: [
|
|
1145
|
-
/* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium text-muted-foreground", children: t2("financial.invoiceForm.qty") }),
|
|
1146
|
-
/* @__PURE__ */ jsx(
|
|
1147
|
-
"input",
|
|
1148
|
-
{
|
|
1149
|
-
type: "number",
|
|
1150
|
-
min: 1,
|
|
1151
|
-
value: item.quantity,
|
|
1152
|
-
onChange: (e) => onUpdate({ quantity: Number(e.target.value) || 1 }),
|
|
1153
|
-
onKeyDown: handleFieldKeyDown,
|
|
1154
|
-
className: "w-full mt-0.5 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2 py-1.5 text-sm text-center focus:outline-none focus:ring-2 focus:ring-ring"
|
|
1155
|
-
}
|
|
1156
|
-
)
|
|
1157
|
-
] }),
|
|
1158
|
-
/* @__PURE__ */ jsx("div", { className: "col-span-4", onKeyDown: handleFieldKeyDown, children: /* @__PURE__ */ jsx(
|
|
1159
|
-
CurrencyInput,
|
|
1160
|
-
{
|
|
1161
|
-
label: t2("financial.invoiceForm.unitPrice"),
|
|
1162
|
-
value: item.unitPrice,
|
|
1163
|
-
onChange: (v) => onUpdate({ unitPrice: v }),
|
|
1164
|
-
symbol: currency.symbol,
|
|
1165
|
-
locale: currency.locale,
|
|
1166
|
-
currencyCode: currency.code
|
|
1167
|
-
}
|
|
1168
|
-
) })
|
|
1169
|
-
] }),
|
|
1170
|
-
!showAdjustments ? /* @__PURE__ */ jsxs(
|
|
1171
|
-
"button",
|
|
1172
|
-
{
|
|
1173
|
-
onClick: () => setShowAdjustments(true),
|
|
1174
|
-
className: "inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors",
|
|
1175
|
-
children: [
|
|
1176
|
-
/* @__PURE__ */ jsx(Plus, { className: "h-3 w-3" }),
|
|
1177
|
-
" ",
|
|
1178
|
-
t2("financial.invoiceForm.discountSurcharge")
|
|
1179
|
-
]
|
|
1180
|
-
}
|
|
1181
|
-
) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1182
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
|
|
1183
|
-
/* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium text-muted-foreground", children: t2("financial.invoiceForm.discount") }),
|
|
1184
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center mt-0.5 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] focus-within:ring-2 focus-within:ring-primary/20", children: [
|
|
1185
|
-
/* @__PURE__ */ jsx("span", { className: "pl-2 text-xs text-muted-foreground", children: currency.symbol }),
|
|
1186
|
-
/* @__PURE__ */ jsx(
|
|
1187
|
-
"input",
|
|
1188
|
-
{
|
|
1189
|
-
type: "number",
|
|
1190
|
-
min: 0,
|
|
1191
|
-
step: 0.01,
|
|
1192
|
-
value: item.discount ?? 0,
|
|
1193
|
-
onChange: (e) => onUpdate({ discount: Number(e.target.value) || 0 }),
|
|
1194
|
-
onKeyDown: handleFieldKeyDown,
|
|
1195
|
-
className: "flex-1 bg-transparent px-2 py-1 text-xs outline-none"
|
|
1196
|
-
}
|
|
1197
|
-
)
|
|
1198
|
-
] })
|
|
1199
|
-
] }),
|
|
1200
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
|
|
1201
|
-
/* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium text-muted-foreground", children: t2("financial.invoiceForm.surcharge") }),
|
|
1202
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center mt-0.5 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] focus-within:ring-2 focus-within:ring-primary/20", children: [
|
|
1203
|
-
/* @__PURE__ */ jsx("span", { className: "pl-2 text-xs text-muted-foreground", children: currency.symbol }),
|
|
1204
|
-
/* @__PURE__ */ jsx(
|
|
1205
|
-
"input",
|
|
1206
|
-
{
|
|
1207
|
-
type: "number",
|
|
1208
|
-
min: 0,
|
|
1209
|
-
step: 0.01,
|
|
1210
|
-
value: item.surcharge ?? 0,
|
|
1211
|
-
onChange: (e) => onUpdate({ surcharge: Number(e.target.value) || 0 }),
|
|
1212
|
-
onKeyDown: handleFieldKeyDown,
|
|
1213
|
-
className: "flex-1 bg-transparent px-2 py-1 text-xs outline-none"
|
|
1214
|
-
}
|
|
1215
|
-
)
|
|
1216
|
-
] })
|
|
1217
|
-
] }),
|
|
1218
|
-
/* @__PURE__ */ jsx(
|
|
1219
|
-
"button",
|
|
1220
|
-
{
|
|
1221
|
-
onClick: () => {
|
|
1222
|
-
onUpdate({ discount: 0, surcharge: 0 });
|
|
1223
|
-
setShowAdjustments(false);
|
|
1224
|
-
},
|
|
1225
|
-
className: "mt-3 p-1 text-muted-foreground hover:text-destructive transition-colors",
|
|
1226
|
-
title: t2("financial.invoiceForm.removeAdjustments"),
|
|
1227
|
-
children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
|
|
1228
|
-
}
|
|
1229
|
-
)
|
|
1230
|
-
] }),
|
|
1231
|
-
/* @__PURE__ */ jsx("div", { className: "flex justify-end pt-1", children: /* @__PURE__ */ jsxs(
|
|
1232
|
-
"button",
|
|
1233
|
-
{
|
|
1234
|
-
onClick: onToggle,
|
|
1235
|
-
className: "inline-flex items-center gap-1 rounded-md px-2.5 py-1 text-[11px] text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors",
|
|
1236
|
-
children: [
|
|
1237
|
-
/* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }),
|
|
1238
|
-
" ",
|
|
1239
|
-
t2("financial.invoiceForm.done")
|
|
1240
|
-
]
|
|
1241
|
-
}
|
|
1242
|
-
) })
|
|
1243
|
-
] })
|
|
1244
|
-
] });
|
|
1245
|
-
}
|
|
1246
|
-
function AddItemStep({ itemTypes, onSelect, onCancel }) {
|
|
1247
|
-
const t2 = useTranslation();
|
|
1248
|
-
return /* @__PURE__ */ jsxs("div", { className: "border-b px-4 py-3 bg-primary/5 animate-in fade-in-0 slide-in-from-top-1", children: [
|
|
1249
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-2", children: t2("financial.invoiceForm.selectItemType") }),
|
|
1250
|
-
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 flex-wrap", children: [
|
|
1251
|
-
itemTypes.map((t3) => /* @__PURE__ */ jsx(
|
|
1252
|
-
"button",
|
|
1253
|
-
{
|
|
1254
|
-
onClick: () => onSelect(t3.value),
|
|
1255
|
-
className: "inline-flex items-center gap-1.5 rounded-lg border-2 border-transparent bg-card px-4 py-2 text-sm font-medium shadow-sm hover:border-primary hover:shadow-md transition-all",
|
|
1256
|
-
children: t3.label
|
|
1257
|
-
},
|
|
1258
|
-
t3.value
|
|
1259
|
-
)),
|
|
1260
|
-
/* @__PURE__ */ jsx(
|
|
1261
|
-
"button",
|
|
1262
|
-
{
|
|
1263
|
-
onClick: onCancel,
|
|
1264
|
-
className: "inline-flex items-center rounded-lg px-3 py-2 text-xs text-muted-foreground hover:text-foreground transition-colors",
|
|
1265
|
-
children: t2("financial.invoiceForm.cancel")
|
|
1266
|
-
}
|
|
1267
|
-
)
|
|
1268
|
-
] })
|
|
1269
|
-
] });
|
|
1270
|
-
}
|
|
1271
|
-
function InvoiceFormView({ direction, editId, onSaved }) {
|
|
1272
|
-
const t2 = useTranslation();
|
|
1273
|
-
const config = useFinancialConfig();
|
|
1274
|
-
const { currency, itemTypes, locations } = config;
|
|
1275
|
-
const provider = useFinancialProvider();
|
|
1276
|
-
const hasItemLookups = itemTypes.some((tp) => config.entityLookups[tp.value]);
|
|
1277
|
-
const defaultItemKind = itemTypes.find((tp) => tp.value === "other")?.value ?? itemTypes.find((tp) => !config.entityLookups[tp.value])?.value ?? itemTypes[0]?.value ?? "other";
|
|
1278
|
-
const createInvoice = useFinancialStore((s) => s.createInvoice);
|
|
1279
|
-
const guardMovements = useLimitGuard("movements_month");
|
|
1280
|
-
const can = usePermissionOptional();
|
|
1281
|
-
const hasLocations = locations.length > 0;
|
|
1282
|
-
const isEdit = !!editId;
|
|
1283
|
-
const [loaded, setLoaded] = useState(!editId);
|
|
1284
|
-
const [loading, setLoading] = useState(!!editId);
|
|
1285
|
-
const [contactName, setContactName] = useState("");
|
|
1286
|
-
const [contactId, setContactId] = useState();
|
|
1287
|
-
const [unitId, setUnitId] = useState(locations[0]?.id ?? "");
|
|
1288
|
-
const [invoiceDate, setInvoiceDate] = useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
1289
|
-
const [fiscalNumber, setFiscalNumber] = useState("");
|
|
1290
|
-
const [observations, setObservations] = useState("");
|
|
1291
|
-
const [items, setItems] = useState([]);
|
|
1292
|
-
const [expandedItemId, setExpandedItemId] = useState(null);
|
|
1293
|
-
const [addingItem, setAddingItem] = useState(false);
|
|
1294
|
-
const [installments, setInstallments] = useState([]);
|
|
1295
|
-
const [saving, setSaving] = useState(false);
|
|
1296
|
-
const [menuOpen, setMenuOpen] = useState(false);
|
|
1297
|
-
const [confirmCancel, setConfirmCancel] = useState(false);
|
|
1298
|
-
const [cancelling, setCancelling] = useState(false);
|
|
1299
|
-
const menuRef = useRef(null);
|
|
1300
|
-
const cancelInvoice = useFinancialStore((s) => s.cancelInvoice);
|
|
1301
|
-
useEffect(() => {
|
|
1302
|
-
if (!menuOpen) return;
|
|
1303
|
-
function handleClick(e) {
|
|
1304
|
-
if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false);
|
|
1305
|
-
}
|
|
1306
|
-
document.addEventListener("mousedown", handleClick);
|
|
1307
|
-
return () => document.removeEventListener("mousedown", handleClick);
|
|
1308
|
-
}, [menuOpen]);
|
|
1309
|
-
async function handleCancelInvoice() {
|
|
1310
|
-
if (!editId) return;
|
|
1311
|
-
setCancelling(true);
|
|
1312
|
-
try {
|
|
1313
|
-
await cancelInvoice(editId);
|
|
1314
|
-
onSaved?.();
|
|
1315
|
-
} catch {
|
|
1316
|
-
} finally {
|
|
1317
|
-
setCancelling(false);
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
useEffect(() => {
|
|
1321
|
-
if (!editId || loaded) return;
|
|
1322
|
-
setLoading(true);
|
|
1323
|
-
(async () => {
|
|
1324
|
-
const invoice = await provider.getInvoiceById(editId);
|
|
1325
|
-
if (!invoice) return;
|
|
1326
|
-
setContactName(invoice.contactName ?? "");
|
|
1327
|
-
setContactId(invoice.contactId);
|
|
1328
|
-
setUnitId(invoice.unitId ?? locations[0]?.id ?? "");
|
|
1329
|
-
setInvoiceDate(invoice.invoiceDate);
|
|
1330
|
-
setFiscalNumber(invoice.fiscalNumber ?? "");
|
|
1331
|
-
setObservations(invoice.observations ?? "");
|
|
1332
|
-
const invoiceItems = await provider.getInvoiceItems(editId);
|
|
1333
|
-
setItems(invoiceItems.map((it) => ({
|
|
1334
|
-
_id: fid(),
|
|
1335
|
-
itemKind: it.itemKind,
|
|
1336
|
-
referenceId: it.referenceId,
|
|
1337
|
-
description: it.description,
|
|
1338
|
-
quantity: it.quantity,
|
|
1339
|
-
unitPrice: it.unitPrice,
|
|
1340
|
-
discount: it.discount,
|
|
1341
|
-
surcharge: it.surcharge
|
|
1342
|
-
})));
|
|
1343
|
-
const movements = await provider.getMovements({ direction: invoice.direction });
|
|
1344
|
-
const invoiceMovements = movements.data.filter((m) => m.invoiceId === editId && m.movementKind === "bill").sort((a, b) => (a.installmentNumber ?? 0) - (b.installmentNumber ?? 0));
|
|
1345
|
-
setInstallments(invoiceMovements.map((m) => ({
|
|
1346
|
-
_id: fid(),
|
|
1347
|
-
amount: m.amount,
|
|
1348
|
-
dueDate: m.dueDate
|
|
1349
|
-
})));
|
|
1350
|
-
setLoaded(true);
|
|
1351
|
-
setLoading(false);
|
|
1352
|
-
})();
|
|
1353
|
-
}, [editId]);
|
|
1354
|
-
const totalAmount = items.reduce((sum, it) => {
|
|
1355
|
-
return sum + it.quantity * it.unitPrice - (it.discount ?? 0) + (it.surcharge ?? 0);
|
|
1356
|
-
}, 0);
|
|
1357
|
-
const prevItemCount = useRef(items.length);
|
|
1358
|
-
useEffect(() => {
|
|
1359
|
-
if (items.length > 0 && prevItemCount.current === 0 && installments.length === 0 && !isEdit) {
|
|
1360
|
-
const total = items.reduce((sum, it) => sum + it.quantity * it.unitPrice - (it.discount ?? 0) + (it.surcharge ?? 0), 0);
|
|
1361
|
-
if (total > 0) {
|
|
1362
|
-
setInstallments([{ _id: fid(), amount: total, dueDate: invoiceDate }]);
|
|
1363
|
-
}
|
|
1364
|
-
}
|
|
1365
|
-
prevItemCount.current = items.length;
|
|
1366
|
-
}, [items.length]);
|
|
1367
|
-
useEffect(() => {
|
|
1368
|
-
if (installments.length > 0 && totalAmount > 0 && !isEdit) {
|
|
1369
|
-
const count = installments.length;
|
|
1370
|
-
const perInstallment = Math.floor(totalAmount * 100 / count) / 100;
|
|
1371
|
-
const remainder = Math.round((totalAmount - perInstallment * count) * 100) / 100;
|
|
1372
|
-
setInstallments((prev) => prev.map((inst, i) => ({
|
|
1373
|
-
...inst,
|
|
1374
|
-
amount: i === 0 ? perInstallment + remainder : perInstallment
|
|
1375
|
-
})));
|
|
1376
|
-
}
|
|
1377
|
-
}, [totalAmount]);
|
|
1378
|
-
function addItemOfKind(kind) {
|
|
1379
|
-
const newItem = {
|
|
1380
|
-
_id: fid(),
|
|
1381
|
-
itemKind: kind,
|
|
1382
|
-
description: "",
|
|
1383
|
-
quantity: 1,
|
|
1384
|
-
unitPrice: 0
|
|
1385
|
-
};
|
|
1386
|
-
setItems([...items, newItem]);
|
|
1387
|
-
setExpandedItemId(newItem._id);
|
|
1388
|
-
setAddingItem(false);
|
|
1389
|
-
}
|
|
1390
|
-
function startAddItem() {
|
|
1391
|
-
if (hasItemLookups) setAddingItem(true);
|
|
1392
|
-
else addItemOfKind(defaultItemKind);
|
|
1393
|
-
}
|
|
1394
|
-
function updateItem(id, data) {
|
|
1395
|
-
setItems(items.map((it) => it._id === id ? { ...it, ...data } : it));
|
|
1396
|
-
}
|
|
1397
|
-
function removeItem(id) {
|
|
1398
|
-
setItems(items.filter((it) => it._id !== id));
|
|
1399
|
-
}
|
|
1400
|
-
function generateInstallments(count) {
|
|
1401
|
-
if (count < 1 || totalAmount <= 0) return;
|
|
1402
|
-
const perInstallment = Math.floor(totalAmount * 100 / count) / 100;
|
|
1403
|
-
const remainder = Math.round((totalAmount - perInstallment * count) * 100) / 100;
|
|
1404
|
-
const base = new Date(invoiceDate);
|
|
1405
|
-
const inst = [];
|
|
1406
|
-
for (let i = 0; i < count; i++) {
|
|
1407
|
-
const due = new Date(base);
|
|
1408
|
-
due.setMonth(due.getMonth() + i + 1);
|
|
1409
|
-
inst.push({
|
|
1410
|
-
_id: fid(),
|
|
1411
|
-
amount: i === 0 ? perInstallment + remainder : perInstallment,
|
|
1412
|
-
dueDate: due.toISOString().slice(0, 10)
|
|
1413
|
-
});
|
|
1414
|
-
}
|
|
1415
|
-
setInstallments(inst);
|
|
1416
|
-
}
|
|
1417
|
-
async function handleSave() {
|
|
1418
|
-
if (!isEdit && !can("financial", "create")) return;
|
|
1419
|
-
if (items.length === 0) {
|
|
1420
|
-
toast.error(t2("common.formIncomplete"));
|
|
1421
|
-
return;
|
|
1422
|
-
}
|
|
1423
|
-
if (!isEdit && await guardMovements(Math.max(1, installments.length)) === "blocked") return;
|
|
1424
|
-
setSaving(true);
|
|
1425
|
-
try {
|
|
1426
|
-
const movInputs = installments.map((inst, i) => ({
|
|
1427
|
-
direction,
|
|
1428
|
-
movementKind: "bill",
|
|
1429
|
-
amount: inst.amount,
|
|
1430
|
-
dueDate: inst.dueDate,
|
|
1431
|
-
installmentNumber: i + 1
|
|
1432
|
-
}));
|
|
1433
|
-
if (movInputs.length === 0) {
|
|
1434
|
-
movInputs.push({
|
|
1435
|
-
direction,
|
|
1436
|
-
movementKind: "bill",
|
|
1437
|
-
amount: totalAmount,
|
|
1438
|
-
dueDate: invoiceDate,
|
|
1439
|
-
installmentNumber: 1
|
|
1440
|
-
});
|
|
1441
|
-
}
|
|
1442
|
-
if (isEdit && editId) {
|
|
1443
|
-
await provider.updateInvoice(editId, {
|
|
1444
|
-
invoiceDate,
|
|
1445
|
-
contactId,
|
|
1446
|
-
contactName: contactName || void 0,
|
|
1447
|
-
unitId: unitId || void 0,
|
|
1448
|
-
fiscalNumber: fiscalNumber || void 0,
|
|
1449
|
-
observations: observations || void 0,
|
|
1450
|
-
totalAmount
|
|
1451
|
-
});
|
|
1452
|
-
onSaved?.(editId);
|
|
1453
|
-
} else {
|
|
1454
|
-
const invoice = await createInvoice({
|
|
1455
|
-
direction,
|
|
1456
|
-
invoiceDate,
|
|
1457
|
-
contactId,
|
|
1458
|
-
contactName: contactName || void 0,
|
|
1459
|
-
unitId: unitId || void 0,
|
|
1460
|
-
fiscalNumber: fiscalNumber || void 0,
|
|
1461
|
-
observations: observations || void 0,
|
|
1462
|
-
items: items.map(({ _id, ...rest }) => rest),
|
|
1463
|
-
installments: movInputs
|
|
1464
|
-
});
|
|
1465
|
-
invalidateLimit("movements_month");
|
|
1466
|
-
onSaved?.(invoice.id);
|
|
1467
|
-
}
|
|
1468
|
-
} finally {
|
|
1469
|
-
setSaving(false);
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
const fields = {
|
|
1473
|
-
contactName,
|
|
1474
|
-
contactId,
|
|
1475
|
-
unitId,
|
|
1476
|
-
invoiceDate,
|
|
1477
|
-
fiscalNumber,
|
|
1478
|
-
observations,
|
|
1479
|
-
items: items.map(({ _id, ...rest }) => rest),
|
|
1480
|
-
installments: installments.map(({ _id, ...rest }) => rest)
|
|
1481
|
-
};
|
|
1482
|
-
const snapshot = useRef(null);
|
|
1483
|
-
useEffect(() => {
|
|
1484
|
-
if (loaded && snapshot.current === null) snapshot.current = JSON.stringify(fields);
|
|
1485
|
-
}, [loaded]);
|
|
1486
|
-
const dirty = snapshot.current !== null && JSON.stringify(fields) !== snapshot.current;
|
|
1487
|
-
useSaveBar({
|
|
1488
|
-
dirty,
|
|
1489
|
-
saving,
|
|
1490
|
-
onSave: () => {
|
|
1491
|
-
void handleSave();
|
|
1492
|
-
},
|
|
1493
|
-
onDiscard: () => onSaved?.(),
|
|
1494
|
-
saveLabel: t2("financial.invoiceForm.save")
|
|
1495
|
-
});
|
|
1496
|
-
const title = direction === "debit" ? t2("financial.invoice.accountsPayable") : t2("financial.invoice.accountsReceivable");
|
|
1497
|
-
const subtitle = isEdit ? t2("financial.invoiceForm.editing") : t2("financial.invoiceForm.creating");
|
|
1498
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
1499
|
-
/* @__PURE__ */ jsx(
|
|
1500
|
-
SubpageHeader,
|
|
1501
|
-
{
|
|
1502
|
-
title,
|
|
1503
|
-
subtitle,
|
|
1504
|
-
onBack: onSaved,
|
|
1505
|
-
parentLabel: direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables"),
|
|
1506
|
-
actions: isEdit ? /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsxs("div", { className: "relative", ref: menuRef, children: [
|
|
1507
|
-
/* @__PURE__ */ jsx(
|
|
1508
|
-
Button,
|
|
1509
|
-
{
|
|
1510
|
-
variant: "outline",
|
|
1511
|
-
size: "icon",
|
|
1512
|
-
onClick: () => setMenuOpen(!menuOpen),
|
|
1513
|
-
"aria-label": "More actions",
|
|
1514
|
-
children: /* @__PURE__ */ jsx(MoreVertical, { className: "h-4 w-4" })
|
|
1515
|
-
}
|
|
1516
|
-
),
|
|
1517
|
-
menuOpen && /* @__PURE__ */ jsx("div", { className: "fayz-glass-surface absolute right-0 top-full mt-1 w-44 rounded-lg border bg-popover shadow-md z-20 py-1", style: { animation: "field-slide-in 150ms ease-out" }, children: /* @__PURE__ */ jsxs(
|
|
1518
|
-
"button",
|
|
1519
|
-
{
|
|
1520
|
-
onClick: () => {
|
|
1521
|
-
setConfirmCancel(true);
|
|
1522
|
-
setMenuOpen(false);
|
|
1523
|
-
},
|
|
1524
|
-
className: "flex w-full items-center gap-2 px-3 py-2 text-xs text-destructive hover:bg-destructive/10 transition-colors",
|
|
1525
|
-
children: [
|
|
1526
|
-
/* @__PURE__ */ jsx(Ban, { className: "h-3.5 w-3.5" }),
|
|
1527
|
-
" ",
|
|
1528
|
-
t2("financial.invoiceForm.cancelInvoice")
|
|
1529
|
-
]
|
|
1530
|
-
}
|
|
1531
|
-
) })
|
|
1532
|
-
] }) }) : null
|
|
1533
|
-
}
|
|
1534
|
-
),
|
|
1535
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card shadow-sm p-5", children: /* @__PURE__ */ jsxs("div", { className: `grid gap-4 ${hasLocations ? "sm:grid-cols-12" : "sm:grid-cols-10"}`, children: [
|
|
1536
|
-
/* @__PURE__ */ jsx("div", { className: hasLocations ? "sm:col-span-4" : "sm:col-span-4", children: /* @__PURE__ */ jsx(
|
|
1537
|
-
SearchSelect,
|
|
1538
|
-
{
|
|
1539
|
-
label: direction === "debit" ? t2("financial.invoiceForm.payTo") : t2("financial.invoiceForm.receiveFrom"),
|
|
1540
|
-
required: true,
|
|
1541
|
-
value: contactId ?? "",
|
|
1542
|
-
displayValue: contactName,
|
|
1543
|
-
onChange: (id, opt) => {
|
|
1544
|
-
setContactId(id || void 0);
|
|
1545
|
-
setContactName(opt?.label ?? "");
|
|
1546
|
-
},
|
|
1547
|
-
onSearch: async (q) => {
|
|
1548
|
-
if (!config.contactLookup) return [];
|
|
1549
|
-
const results = await config.contactLookup.search(q);
|
|
1550
|
-
return results.map((r) => ({ id: r.id, label: r.label, subtitle: r.subtitle, group: r.group, data: r }));
|
|
1551
|
-
},
|
|
1552
|
-
placeholder: t2("financial.invoiceForm.searchType")
|
|
1553
|
-
}
|
|
1554
|
-
) }),
|
|
1555
|
-
hasLocations && /* @__PURE__ */ jsx("div", { className: "sm:col-span-3", children: /* @__PURE__ */ jsx(
|
|
1556
|
-
LocationSelector,
|
|
1557
|
-
{
|
|
1558
|
-
locations,
|
|
1559
|
-
value: unitId,
|
|
1560
|
-
onChange: setUnitId
|
|
1561
|
-
}
|
|
1562
|
-
) }),
|
|
1563
|
-
/* @__PURE__ */ jsxs("div", { className: hasLocations ? "sm:col-span-2" : "sm:col-span-3", children: [
|
|
1564
|
-
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.date") }),
|
|
1565
|
-
/* @__PURE__ */ jsx(DatePicker, { value: invoiceDate, onChange: setInvoiceDate, className: "mt-1" })
|
|
1566
|
-
] }),
|
|
1567
|
-
/* @__PURE__ */ jsxs("div", { className: hasLocations ? "sm:col-span-3" : "sm:col-span-3", children: [
|
|
1568
|
-
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.fiscalNumber") }),
|
|
1569
|
-
/* @__PURE__ */ jsx(
|
|
1570
|
-
"input",
|
|
1571
|
-
{
|
|
1572
|
-
type: "text",
|
|
1573
|
-
value: fiscalNumber,
|
|
1574
|
-
onChange: (e) => setFiscalNumber(e.target.value),
|
|
1575
|
-
placeholder: t2("financial.invoiceForm.fiscalNumber"),
|
|
1576
|
-
className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
1577
|
-
}
|
|
1578
|
-
)
|
|
1579
|
-
] })
|
|
1580
|
-
] }) }),
|
|
1581
|
-
loading ? /* @__PURE__ */ jsxs("div", { className: "grid gap-4 lg:grid-cols-3", children: [
|
|
1582
|
-
/* @__PURE__ */ jsxs("div", { className: "lg:col-span-2 rounded-lg border bg-card shadow-sm overflow-hidden", children: [
|
|
1583
|
-
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-4 py-3 border-b", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1584
|
-
/* @__PURE__ */ jsx(LayoutList, { className: "h-4 w-4 text-muted-foreground" }),
|
|
1585
|
-
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t2("financial.invoiceForm.items") })
|
|
1586
|
-
] }) }),
|
|
1587
|
-
/* @__PURE__ */ jsx("div", { className: "p-4 space-y-3", children: [1, 2].map((i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-2 py-3", children: [
|
|
1588
|
-
/* @__PURE__ */ jsx("div", { className: "h-4 w-4 rounded bg-muted animate-pulse" }),
|
|
1589
|
-
/* @__PURE__ */ jsx("div", { className: "h-4 w-8 rounded bg-muted animate-pulse" }),
|
|
1590
|
-
/* @__PURE__ */ jsx("div", { className: "h-4 w-16 rounded bg-muted animate-pulse" }),
|
|
1591
|
-
/* @__PURE__ */ jsx("div", { className: "h-4 flex-1 rounded bg-muted animate-pulse" }),
|
|
1592
|
-
/* @__PURE__ */ jsx("div", { className: "h-4 w-20 rounded bg-muted animate-pulse" }),
|
|
1593
|
-
/* @__PURE__ */ jsx("div", { className: "h-4 w-16 rounded bg-muted animate-pulse" })
|
|
1594
|
-
] }, i)) })
|
|
1595
|
-
] }),
|
|
1596
|
-
/* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card shadow-sm", children: [
|
|
1597
|
-
/* @__PURE__ */ jsx("div", { className: "px-5 py-3 border-b", children: /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t2("financial.invoiceForm.installments") }) }),
|
|
1598
|
-
/* @__PURE__ */ jsx("div", { className: "p-4 space-y-3", children: [1, 2].map((i) => /* @__PURE__ */ jsxs("div", { className: "rounded border p-2.5 space-y-2", children: [
|
|
1599
|
-
/* @__PURE__ */ jsxs("div", { className: "flex justify-between", children: [
|
|
1600
|
-
/* @__PURE__ */ jsx("div", { className: "h-3 w-6 rounded bg-muted animate-pulse" }),
|
|
1601
|
-
/* @__PURE__ */ jsx("div", { className: "h-4 w-16 rounded bg-muted animate-pulse" })
|
|
1602
|
-
] }),
|
|
1603
|
-
/* @__PURE__ */ jsx("div", { className: "h-7 rounded bg-muted animate-pulse" })
|
|
1604
|
-
] }, i)) })
|
|
1605
|
-
] })
|
|
1606
|
-
] }) : /* @__PURE__ */ jsxs("div", { className: "grid gap-4 lg:grid-cols-3", children: [
|
|
1607
|
-
/* @__PURE__ */ jsxs("div", { className: "lg:col-span-2 rounded-lg border bg-card shadow-sm overflow-hidden flex flex-col", children: [
|
|
1608
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 border-b", children: [
|
|
1609
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1610
|
-
/* @__PURE__ */ jsx(LayoutList, { className: "h-4 w-4 text-muted-foreground" }),
|
|
1611
|
-
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t2("financial.invoiceForm.items") })
|
|
1612
|
-
] }),
|
|
1613
|
-
/* @__PURE__ */ jsxs(
|
|
1614
|
-
"button",
|
|
1615
|
-
{
|
|
1616
|
-
onClick: startAddItem,
|
|
1617
|
-
className: "inline-flex items-center gap-1.5 rounded-lg bg-primary border border-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 shadow-button-primary active:shadow-button-inset transition-colors",
|
|
1618
|
-
children: [
|
|
1619
|
-
/* @__PURE__ */ jsx(Plus, { className: "h-3 w-3" }),
|
|
1620
|
-
" ",
|
|
1621
|
-
t2("financial.invoiceForm.addItem")
|
|
1622
|
-
]
|
|
1623
|
-
}
|
|
1624
|
-
)
|
|
1625
|
-
] }),
|
|
1626
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
|
|
1627
|
-
addingItem && /* @__PURE__ */ jsx(
|
|
1628
|
-
AddItemStep,
|
|
1629
|
-
{
|
|
1630
|
-
itemTypes,
|
|
1631
|
-
onSelect: addItemOfKind,
|
|
1632
|
-
onCancel: () => setAddingItem(false)
|
|
1633
|
-
}
|
|
1634
|
-
),
|
|
1635
|
-
items.length === 0 && !addingItem ? /* @__PURE__ */ jsxs("div", { className: "py-10 text-center", children: [
|
|
1636
|
-
/* @__PURE__ */ jsx(LayoutList, { className: "h-8 w-8 text-muted-foreground/30 mx-auto mb-2" }),
|
|
1637
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.invoiceForm.noItems") }),
|
|
1638
|
-
/* @__PURE__ */ jsx("button", { onClick: startAddItem, className: "text-xs text-primary hover:underline mt-1", children: t2("financial.invoiceForm.addFirstItem") })
|
|
1639
|
-
] }) : items.map((item, i) => /* @__PURE__ */ jsx(
|
|
1640
|
-
ItemRow,
|
|
1641
|
-
{
|
|
1642
|
-
item,
|
|
1643
|
-
index: i,
|
|
1644
|
-
itemTypes,
|
|
1645
|
-
currency,
|
|
1646
|
-
expanded: expandedItemId === item._id,
|
|
1647
|
-
onToggle: () => setExpandedItemId(expandedItemId === item._id ? null : item._id),
|
|
1648
|
-
onUpdate: (data) => updateItem(item._id, data),
|
|
1649
|
-
onRemove: () => {
|
|
1650
|
-
removeItem(item._id);
|
|
1651
|
-
if (expandedItemId === item._id) setExpandedItemId(null);
|
|
1652
|
-
},
|
|
1653
|
-
entityLookups: config.entityLookups,
|
|
1654
|
-
showTypeSelector: hasItemLookups
|
|
1655
|
-
},
|
|
1656
|
-
item._id
|
|
1657
|
-
))
|
|
1658
|
-
] }),
|
|
1659
|
-
items.length > 0 && /* @__PURE__ */ jsxs("div", { className: "px-4 py-3 border-t bg-muted/20 flex justify-between items-center mt-auto", children: [
|
|
1660
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t2("financial.invoiceForm.itemCount", { count: items.length, plural: items.length !== 1 ? "s" : "" }) }),
|
|
1661
|
-
/* @__PURE__ */ jsxs("div", { className: "text-right", children: [
|
|
1662
|
-
/* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
|
|
1663
|
-
t2("financial.invoiceForm.total"),
|
|
1664
|
-
" "
|
|
1665
|
-
] }),
|
|
1666
|
-
/* @__PURE__ */ jsx("span", { className: "text-lg font-bold", children: formatCurrency(totalAmount, currency) })
|
|
1667
|
-
] })
|
|
1668
|
-
] })
|
|
1669
|
-
] }),
|
|
1670
|
-
/* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card shadow-sm", children: [
|
|
1671
|
-
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-5 py-3 border-b", children: /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: "Installments" }) }),
|
|
1672
|
-
/* @__PURE__ */ jsxs("div", { className: "p-4 space-y-3", children: [
|
|
1673
|
-
/* @__PURE__ */ jsx("div", { className: "flex gap-1.5 flex-wrap", children: [1, 2, 3, 6, 12].map((n) => /* @__PURE__ */ jsxs(
|
|
1674
|
-
"button",
|
|
1675
|
-
{
|
|
1676
|
-
onClick: () => generateInstallments(n),
|
|
1677
|
-
disabled: totalAmount <= 0,
|
|
1678
|
-
className: `rounded-full px-2.5 py-1 text-[10px] font-medium transition-colors disabled:opacity-30 ${installments.length === n ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-muted/80"}`,
|
|
1679
|
-
children: [
|
|
1680
|
-
n,
|
|
1681
|
-
"x"
|
|
1682
|
-
]
|
|
1683
|
-
},
|
|
1684
|
-
n
|
|
1685
|
-
)) }),
|
|
1686
|
-
installments.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground py-4 text-center", children: totalAmount <= 0 ? t2("financial.invoiceForm.addItemsFirst") : t2("financial.invoiceForm.selectInstallments") }) : /* @__PURE__ */ jsx("div", { className: "space-y-2", children: installments.map((inst, i) => /* @__PURE__ */ jsxs("div", { className: "rounded border p-2.5 space-y-1.5", children: [
|
|
1687
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
1688
|
-
/* @__PURE__ */ jsxs("span", { className: "text-[10px] font-medium text-muted-foreground", children: [
|
|
1689
|
-
"#",
|
|
1690
|
-
i + 1
|
|
1691
|
-
] }),
|
|
1692
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: formatCurrency(inst.amount, currency) })
|
|
1693
|
-
] }),
|
|
1694
|
-
/* @__PURE__ */ jsx(
|
|
1695
|
-
DatePicker,
|
|
1696
|
-
{
|
|
1697
|
-
value: inst.dueDate,
|
|
1698
|
-
onChange: (v) => {
|
|
1699
|
-
const updated = [...installments];
|
|
1700
|
-
updated[i] = { ...inst, dueDate: v };
|
|
1701
|
-
setInstallments(updated);
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
)
|
|
1705
|
-
] }, inst._id)) })
|
|
1706
|
-
] })
|
|
1707
|
-
] })
|
|
1708
|
-
] }),
|
|
1709
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
1710
|
-
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.observations") }),
|
|
1711
|
-
/* @__PURE__ */ jsx(
|
|
1712
|
-
"textarea",
|
|
1713
|
-
{
|
|
1714
|
-
value: observations,
|
|
1715
|
-
onChange: (e) => setObservations(e.target.value),
|
|
1716
|
-
placeholder: t2("financial.invoiceForm.additionalNotes"),
|
|
1717
|
-
rows: 2,
|
|
1718
|
-
className: "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring resize-none"
|
|
1719
|
-
}
|
|
1720
|
-
)
|
|
1721
|
-
] }),
|
|
1722
|
-
confirmCancel && createPortal(
|
|
1723
|
-
/* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", onClick: () => setConfirmCancel(false), children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-sm rounded-modal border bg-card shadow-lg mx-4 p-5", onClick: (e) => e.stopPropagation(), style: { animation: "field-slide-in 200ms ease-out" }, children: [
|
|
1724
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-3", children: [
|
|
1725
|
-
/* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-destructive/10 shrink-0", children: /* @__PURE__ */ jsx(Ban, { className: "h-5 w-5 text-destructive" }) }),
|
|
1726
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
1727
|
-
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t2("financial.invoiceForm.cancelInvoice") }),
|
|
1728
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.invoiceForm.cancelConfirm") })
|
|
1729
|
-
] })
|
|
1730
|
-
] }),
|
|
1731
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mb-4", children: t2("financial.invoiceForm.cancelDesc") }),
|
|
1732
|
-
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
1733
|
-
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: () => setConfirmCancel(false), children: t2("financial.invoiceForm.keepInvoice") }),
|
|
1734
|
-
/* @__PURE__ */ jsxs(Button, { variant: "destructive", size: "sm", onClick: handleCancelInvoice, disabled: cancelling, children: [
|
|
1735
|
-
/* @__PURE__ */ jsx(Ban, { className: "h-3.5 w-3.5" }),
|
|
1736
|
-
cancelling ? t2("financial.invoiceForm.cancelling") : t2("financial.invoiceForm.yesCancel")
|
|
1737
|
-
] })
|
|
1738
|
-
] })
|
|
1739
|
-
] }) }),
|
|
1740
|
-
document.body
|
|
1741
|
-
),
|
|
1742
|
-
/* @__PURE__ */ jsx("style", { children: `
|
|
1743
|
-
@keyframes field-slide-in {
|
|
1744
|
-
from { opacity: 0; transform: translateY(8px); }
|
|
1745
|
-
to { opacity: 1; transform: translateY(0); }
|
|
1746
|
-
}
|
|
1747
|
-
` })
|
|
912
|
+
emptyActionLabel: onNewGated ? t2("financial.invoice.createFirst") : void 0,
|
|
913
|
+
onEmptyAction: onNewGated
|
|
914
|
+
}
|
|
915
|
+
),
|
|
916
|
+
filtered.length > 0 && !invoicesLoading && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-6 rounded-lg border bg-muted/30 px-5 py-3 text-sm", children: [
|
|
917
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
918
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t2("financial.invoice.summaryTotal") }),
|
|
919
|
+
/* @__PURE__ */ jsx("span", { className: "font-semibold", children: formatCurrency(summary.totalAmount, currency) })
|
|
920
|
+
] }),
|
|
921
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-border" }),
|
|
922
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
923
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: receivedLabel }),
|
|
924
|
+
/* @__PURE__ */ jsx("span", { className: "font-semibold text-success", children: formatCurrency(summary.paidAmount, currency) })
|
|
925
|
+
] }),
|
|
926
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-border" }),
|
|
927
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
928
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: pendingLabel }),
|
|
929
|
+
/* @__PURE__ */ jsx("span", { className: "font-semibold text-warning", children: formatCurrency(summary.openAmount, currency) })
|
|
930
|
+
] })
|
|
931
|
+
] })
|
|
1748
932
|
] });
|
|
1749
933
|
}
|
|
1750
934
|
var ACCOUNT_TYPES_FOR_TX = {
|
|
@@ -2022,39 +1206,634 @@ function PaymentModal({ movement, onClose, onPaid }) {
|
|
|
2022
1206
|
from { opacity: 0; transform: translateY(8px); }
|
|
2023
1207
|
to { opacity: 1; transform: translateY(0); }
|
|
2024
1208
|
}
|
|
2025
|
-
` }),
|
|
2026
|
-
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 px-5 py-4 border-t sticky bottom-0 bg-card", children: [
|
|
2027
|
-
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: handleClose, children: t2("financial.payment.cancel") }),
|
|
2028
|
-
/* @__PURE__ */ jsxs(
|
|
2029
|
-
Button,
|
|
2030
|
-
{
|
|
2031
|
-
variant: "default",
|
|
2032
|
-
size: "sm",
|
|
2033
|
-
onClick: handlePay,
|
|
2034
|
-
disabled: amount <= 0 || !selectedTypeId || !bankAccountId || saving,
|
|
2035
|
-
children: [
|
|
2036
|
-
/* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }),
|
|
2037
|
-
saving ? t2("financial.payment.processing") : t2("financial.payment.payAmount", { amount: formatCurrency(amount, currency) })
|
|
2038
|
-
]
|
|
2039
|
-
}
|
|
2040
|
-
)
|
|
2041
|
-
] })
|
|
2042
|
-
]
|
|
1209
|
+
` }),
|
|
1210
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 px-5 py-4 border-t sticky bottom-0 bg-card", children: [
|
|
1211
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: handleClose, children: t2("financial.payment.cancel") }),
|
|
1212
|
+
/* @__PURE__ */ jsxs(
|
|
1213
|
+
Button,
|
|
1214
|
+
{
|
|
1215
|
+
variant: "default",
|
|
1216
|
+
size: "sm",
|
|
1217
|
+
onClick: handlePay,
|
|
1218
|
+
disabled: amount <= 0 || !selectedTypeId || !bankAccountId || saving,
|
|
1219
|
+
children: [
|
|
1220
|
+
/* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }),
|
|
1221
|
+
saving ? t2("financial.payment.processing") : t2("financial.payment.payAmount", { amount: formatCurrency(amount, currency) })
|
|
1222
|
+
]
|
|
1223
|
+
}
|
|
1224
|
+
)
|
|
1225
|
+
] })
|
|
1226
|
+
]
|
|
1227
|
+
}
|
|
1228
|
+
)
|
|
1229
|
+
}
|
|
1230
|
+
),
|
|
1231
|
+
document.body
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
function LocationSelector({ locations, value, onChange }) {
|
|
1235
|
+
const t2 = useTranslation();
|
|
1236
|
+
const [open, setOpen] = useState(false);
|
|
1237
|
+
const [search, setSearch] = useState("");
|
|
1238
|
+
const ref = useRef(null);
|
|
1239
|
+
useEffect(() => {
|
|
1240
|
+
function handler(e) {
|
|
1241
|
+
if (ref.current && !ref.current.contains(e.target)) setOpen(false);
|
|
1242
|
+
}
|
|
1243
|
+
document.addEventListener("mousedown", handler);
|
|
1244
|
+
return () => document.removeEventListener("mousedown", handler);
|
|
1245
|
+
}, []);
|
|
1246
|
+
const selected = locations.find((l) => l.id === value);
|
|
1247
|
+
const filtered = locations.filter(
|
|
1248
|
+
(l) => !search.trim() || l.name.toLowerCase().includes(search.toLowerCase())
|
|
1249
|
+
);
|
|
1250
|
+
return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
|
|
1251
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.unit") }),
|
|
1252
|
+
/* @__PURE__ */ jsxs(
|
|
1253
|
+
"button",
|
|
1254
|
+
{
|
|
1255
|
+
type: "button",
|
|
1256
|
+
onClick: () => setOpen(!open),
|
|
1257
|
+
className: "w-full mt-1 flex items-center gap-2 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm text-left hover:bg-muted/30 transition-colors",
|
|
1258
|
+
children: [
|
|
1259
|
+
/* @__PURE__ */ jsx(Building2, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
|
|
1260
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: selected?.name ?? t2("financial.invoiceForm.selectUnit") }),
|
|
1261
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: `h-3 w-3 text-muted-foreground transition-transform ${open ? "rotate-180" : ""}` })
|
|
1262
|
+
]
|
|
1263
|
+
}
|
|
1264
|
+
),
|
|
1265
|
+
open && /* @__PURE__ */ jsxs("div", { className: "fayz-glass-surface absolute top-full left-0 right-0 z-50 mt-1 rounded-lg border bg-popover shadow-lg overflow-hidden min-w-[260px]", children: [
|
|
1266
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 border-b", children: [
|
|
1267
|
+
/* @__PURE__ */ jsx(Building2, { className: "h-4 w-4 text-muted-foreground" }),
|
|
1268
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold", children: t2("financial.invoiceForm.selectUnitTitle") }),
|
|
1269
|
+
/* @__PURE__ */ jsx("button", { onClick: () => setOpen(false), className: "ml-auto text-muted-foreground hover:text-foreground", children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" }) })
|
|
1270
|
+
] }),
|
|
1271
|
+
/* @__PURE__ */ jsx("div", { className: "p-2 border-b", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2.5 py-1.5", children: [
|
|
1272
|
+
/* @__PURE__ */ jsx(Search, { className: "h-3.5 w-3.5 text-muted-foreground" }),
|
|
1273
|
+
/* @__PURE__ */ jsx(
|
|
1274
|
+
"input",
|
|
1275
|
+
{
|
|
1276
|
+
type: "text",
|
|
1277
|
+
autoFocus: true,
|
|
1278
|
+
value: search,
|
|
1279
|
+
onChange: (e) => setSearch(e.target.value),
|
|
1280
|
+
placeholder: t2("financial.invoiceForm.searchUnit"),
|
|
1281
|
+
className: "flex-1 bg-transparent text-sm outline-none"
|
|
1282
|
+
}
|
|
1283
|
+
)
|
|
1284
|
+
] }) }),
|
|
1285
|
+
/* @__PURE__ */ jsxs("div", { className: "max-h-56 overflow-auto py-1", children: [
|
|
1286
|
+
filtered.map((loc) => /* @__PURE__ */ jsxs(
|
|
1287
|
+
"button",
|
|
1288
|
+
{
|
|
1289
|
+
onClick: () => {
|
|
1290
|
+
onChange(loc.id);
|
|
1291
|
+
setOpen(false);
|
|
1292
|
+
setSearch("");
|
|
1293
|
+
},
|
|
1294
|
+
className: `w-full flex items-center gap-3 px-3 py-2.5 text-left hover:bg-muted/50 transition-colors ${loc.id === value ? "bg-muted/30" : ""}`,
|
|
1295
|
+
children: [
|
|
1296
|
+
/* @__PURE__ */ jsx("div", { className: "flex h-8 w-8 items-center justify-center rounded-lg bg-muted shrink-0", children: /* @__PURE__ */ jsx(Building2, { className: "h-4 w-4 text-muted-foreground" }) }),
|
|
1297
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
1298
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium truncate", children: loc.name }),
|
|
1299
|
+
loc.isHQ && /* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: t2("financial.invoiceForm.headquarters") })
|
|
1300
|
+
] })
|
|
1301
|
+
]
|
|
1302
|
+
},
|
|
1303
|
+
loc.id
|
|
1304
|
+
)),
|
|
1305
|
+
filtered.length === 0 && /* @__PURE__ */ jsx("p", { className: "px-3 py-4 text-center text-xs text-muted-foreground", children: t2("financial.invoiceForm.noUnitsFound") })
|
|
1306
|
+
] })
|
|
1307
|
+
] })
|
|
1308
|
+
] });
|
|
1309
|
+
}
|
|
1310
|
+
var formId = 1;
|
|
1311
|
+
function fid() {
|
|
1312
|
+
return `f${formId++}`;
|
|
1313
|
+
}
|
|
1314
|
+
function InvoiceFormView({ direction, editId, onSaved }) {
|
|
1315
|
+
const t2 = useTranslation();
|
|
1316
|
+
const config = useFinancialConfig();
|
|
1317
|
+
const { currency, itemTypes, locations } = config;
|
|
1318
|
+
const provider = useFinancialProvider();
|
|
1319
|
+
const hasItemLookups = itemTypes.some((tp) => config.entityLookups[tp.value]);
|
|
1320
|
+
const defaultItemKind = itemTypes.find((tp) => tp.value === "other")?.value ?? itemTypes.find((tp) => !config.entityLookups[tp.value])?.value ?? itemTypes[0]?.value ?? "other";
|
|
1321
|
+
const createInvoice = useFinancialStore((s) => s.createInvoice);
|
|
1322
|
+
const guardMovements = useLimitGuard("movements_month");
|
|
1323
|
+
const can = usePermissionOptional();
|
|
1324
|
+
const hasLocations = locations.length > 0;
|
|
1325
|
+
const isEdit = !!editId;
|
|
1326
|
+
const [loaded, setLoaded] = useState(!editId);
|
|
1327
|
+
const [loading, setLoading] = useState(!!editId);
|
|
1328
|
+
const [contactName, setContactName] = useState("");
|
|
1329
|
+
const [contactId, setContactId] = useState();
|
|
1330
|
+
const [unitId, setUnitId] = useState(locations[0]?.id ?? "");
|
|
1331
|
+
const [invoiceDate, setInvoiceDate] = useState((/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
1332
|
+
const [fiscalNumber, setFiscalNumber] = useState("");
|
|
1333
|
+
const [observations, setObservations] = useState("");
|
|
1334
|
+
const [items, setItems] = useState([]);
|
|
1335
|
+
const [expandedItemId, setExpandedItemId] = useState(null);
|
|
1336
|
+
const [addingItem, setAddingItem] = useState(false);
|
|
1337
|
+
const [installments, setInstallments] = useState([]);
|
|
1338
|
+
const [savedMovements, setSavedMovements] = useState([]);
|
|
1339
|
+
const [payingMovement, setPayingMovement] = useState(null);
|
|
1340
|
+
const [saving, setSaving] = useState(false);
|
|
1341
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
1342
|
+
const [confirmCancel, setConfirmCancel] = useState(false);
|
|
1343
|
+
const [cancelling, setCancelling] = useState(false);
|
|
1344
|
+
const menuRef = useRef(null);
|
|
1345
|
+
const cancelInvoice = useFinancialStore((s) => s.cancelInvoice);
|
|
1346
|
+
useEffect(() => {
|
|
1347
|
+
if (!menuOpen) return;
|
|
1348
|
+
function handleClick(e) {
|
|
1349
|
+
if (menuRef.current && !menuRef.current.contains(e.target)) setMenuOpen(false);
|
|
1350
|
+
}
|
|
1351
|
+
document.addEventListener("mousedown", handleClick);
|
|
1352
|
+
return () => document.removeEventListener("mousedown", handleClick);
|
|
1353
|
+
}, [menuOpen]);
|
|
1354
|
+
async function handleCancelInvoice() {
|
|
1355
|
+
if (!editId) return;
|
|
1356
|
+
setCancelling(true);
|
|
1357
|
+
try {
|
|
1358
|
+
await cancelInvoice(editId);
|
|
1359
|
+
onSaved?.();
|
|
1360
|
+
} catch {
|
|
1361
|
+
} finally {
|
|
1362
|
+
setCancelling(false);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
useEffect(() => {
|
|
1366
|
+
if (!editId || loaded) return;
|
|
1367
|
+
setLoading(true);
|
|
1368
|
+
(async () => {
|
|
1369
|
+
const invoice = await provider.getInvoiceById(editId);
|
|
1370
|
+
if (!invoice) return;
|
|
1371
|
+
setContactName(invoice.contactName ?? "");
|
|
1372
|
+
setContactId(invoice.contactId);
|
|
1373
|
+
setUnitId(invoice.unitId ?? locations[0]?.id ?? "");
|
|
1374
|
+
setInvoiceDate(invoice.invoiceDate);
|
|
1375
|
+
setFiscalNumber(invoice.fiscalNumber ?? "");
|
|
1376
|
+
setObservations(invoice.observations ?? "");
|
|
1377
|
+
const invoiceItems = await provider.getInvoiceItems(editId);
|
|
1378
|
+
setItems(invoiceItems.map((it) => ({
|
|
1379
|
+
_id: fid(),
|
|
1380
|
+
itemKind: it.itemKind,
|
|
1381
|
+
referenceId: it.referenceId,
|
|
1382
|
+
description: it.description,
|
|
1383
|
+
quantity: it.quantity,
|
|
1384
|
+
unitPrice: it.unitPrice,
|
|
1385
|
+
discount: it.discount,
|
|
1386
|
+
surcharge: it.surcharge
|
|
1387
|
+
})));
|
|
1388
|
+
const movements = await provider.getMovements({ direction: invoice.direction });
|
|
1389
|
+
const invoiceMovements = movements.data.filter((m) => m.invoiceId === editId && m.movementKind === "bill").sort((a, b) => (a.installmentNumber ?? 0) - (b.installmentNumber ?? 0));
|
|
1390
|
+
setSavedMovements(invoiceMovements);
|
|
1391
|
+
setInstallments(invoiceMovements.map((m) => ({
|
|
1392
|
+
_id: fid(),
|
|
1393
|
+
amount: m.amount,
|
|
1394
|
+
dueDate: m.dueDate
|
|
1395
|
+
})));
|
|
1396
|
+
setLoaded(true);
|
|
1397
|
+
setLoading(false);
|
|
1398
|
+
})();
|
|
1399
|
+
}, [editId]);
|
|
1400
|
+
const totalAmount = items.reduce((sum, it) => {
|
|
1401
|
+
return sum + it.quantity * it.unitPrice - (it.discount ?? 0) + (it.surcharge ?? 0);
|
|
1402
|
+
}, 0);
|
|
1403
|
+
const prevItemCount = useRef(items.length);
|
|
1404
|
+
useEffect(() => {
|
|
1405
|
+
if (items.length > 0 && prevItemCount.current === 0 && installments.length === 0 && !isEdit) {
|
|
1406
|
+
const total = items.reduce((sum, it) => sum + it.quantity * it.unitPrice - (it.discount ?? 0) + (it.surcharge ?? 0), 0);
|
|
1407
|
+
if (total > 0) {
|
|
1408
|
+
setInstallments([{ _id: fid(), amount: total, dueDate: invoiceDate }]);
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
prevItemCount.current = items.length;
|
|
1412
|
+
}, [items.length]);
|
|
1413
|
+
useEffect(() => {
|
|
1414
|
+
if (installments.length > 0 && totalAmount > 0 && !isEdit) {
|
|
1415
|
+
const count = installments.length;
|
|
1416
|
+
const perInstallment = Math.floor(totalAmount * 100 / count) / 100;
|
|
1417
|
+
const remainder = Math.round((totalAmount - perInstallment * count) * 100) / 100;
|
|
1418
|
+
setInstallments((prev) => prev.map((inst, i) => ({
|
|
1419
|
+
...inst,
|
|
1420
|
+
amount: i === 0 ? perInstallment + remainder : perInstallment
|
|
1421
|
+
})));
|
|
1422
|
+
}
|
|
1423
|
+
}, [totalAmount]);
|
|
1424
|
+
function addItemOfKind(kind) {
|
|
1425
|
+
const newItem = {
|
|
1426
|
+
_id: fid(),
|
|
1427
|
+
itemKind: kind,
|
|
1428
|
+
description: "",
|
|
1429
|
+
quantity: 1,
|
|
1430
|
+
unitPrice: 0
|
|
1431
|
+
};
|
|
1432
|
+
setItems([...items, newItem]);
|
|
1433
|
+
setExpandedItemId(newItem._id);
|
|
1434
|
+
setAddingItem(false);
|
|
1435
|
+
}
|
|
1436
|
+
function startAddItem() {
|
|
1437
|
+
if (hasItemLookups) setAddingItem(true);
|
|
1438
|
+
else addItemOfKind(defaultItemKind);
|
|
1439
|
+
}
|
|
1440
|
+
function updateItem(id, data) {
|
|
1441
|
+
setItems(items.map((it) => it._id === id ? { ...it, ...data } : it));
|
|
1442
|
+
}
|
|
1443
|
+
function removeItem(id) {
|
|
1444
|
+
setItems(items.filter((it) => it._id !== id));
|
|
1445
|
+
}
|
|
1446
|
+
function generateInstallments(count) {
|
|
1447
|
+
if (count < 1 || totalAmount <= 0) return;
|
|
1448
|
+
const perInstallment = Math.floor(totalAmount * 100 / count) / 100;
|
|
1449
|
+
const remainder = Math.round((totalAmount - perInstallment * count) * 100) / 100;
|
|
1450
|
+
const base = new Date(invoiceDate);
|
|
1451
|
+
const inst = [];
|
|
1452
|
+
for (let i = 0; i < count; i++) {
|
|
1453
|
+
const due = new Date(base);
|
|
1454
|
+
due.setMonth(due.getMonth() + i + 1);
|
|
1455
|
+
inst.push({
|
|
1456
|
+
_id: fid(),
|
|
1457
|
+
amount: i === 0 ? perInstallment + remainder : perInstallment,
|
|
1458
|
+
dueDate: due.toISOString().slice(0, 10)
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
setInstallments(inst);
|
|
1462
|
+
}
|
|
1463
|
+
async function handleSave() {
|
|
1464
|
+
if (!isEdit && !can("financial", "create")) return;
|
|
1465
|
+
if (items.length === 0) {
|
|
1466
|
+
toast.error(t2("common.formIncomplete"));
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
if (!isEdit && await guardMovements(Math.max(1, installments.length)) === "blocked") return;
|
|
1470
|
+
setSaving(true);
|
|
1471
|
+
try {
|
|
1472
|
+
const movInputs = installments.map((inst, i) => ({
|
|
1473
|
+
direction,
|
|
1474
|
+
movementKind: "bill",
|
|
1475
|
+
amount: inst.amount,
|
|
1476
|
+
dueDate: inst.dueDate,
|
|
1477
|
+
installmentNumber: i + 1
|
|
1478
|
+
}));
|
|
1479
|
+
if (movInputs.length === 0) {
|
|
1480
|
+
movInputs.push({
|
|
1481
|
+
direction,
|
|
1482
|
+
movementKind: "bill",
|
|
1483
|
+
amount: totalAmount,
|
|
1484
|
+
dueDate: invoiceDate,
|
|
1485
|
+
installmentNumber: 1
|
|
1486
|
+
});
|
|
1487
|
+
}
|
|
1488
|
+
if (isEdit && editId) {
|
|
1489
|
+
await provider.updateInvoice(editId, {
|
|
1490
|
+
invoiceDate,
|
|
1491
|
+
contactId,
|
|
1492
|
+
contactName: contactName || void 0,
|
|
1493
|
+
unitId: unitId || void 0,
|
|
1494
|
+
fiscalNumber: fiscalNumber || void 0,
|
|
1495
|
+
observations: observations || void 0,
|
|
1496
|
+
totalAmount
|
|
1497
|
+
});
|
|
1498
|
+
onSaved?.(editId);
|
|
1499
|
+
} else {
|
|
1500
|
+
const invoice = await createInvoice({
|
|
1501
|
+
direction,
|
|
1502
|
+
invoiceDate,
|
|
1503
|
+
contactId,
|
|
1504
|
+
contactName: contactName || void 0,
|
|
1505
|
+
unitId: unitId || void 0,
|
|
1506
|
+
fiscalNumber: fiscalNumber || void 0,
|
|
1507
|
+
observations: observations || void 0,
|
|
1508
|
+
items: items.map(({ _id, ...rest }) => rest),
|
|
1509
|
+
installments: movInputs
|
|
1510
|
+
});
|
|
1511
|
+
invalidateLimit("movements_month");
|
|
1512
|
+
onSaved?.(invoice.id);
|
|
1513
|
+
}
|
|
1514
|
+
} finally {
|
|
1515
|
+
setSaving(false);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
const fields = {
|
|
1519
|
+
contactName,
|
|
1520
|
+
contactId,
|
|
1521
|
+
unitId,
|
|
1522
|
+
invoiceDate,
|
|
1523
|
+
fiscalNumber,
|
|
1524
|
+
observations,
|
|
1525
|
+
items: items.map(({ _id, ...rest }) => rest),
|
|
1526
|
+
installments: installments.map(({ _id, ...rest }) => rest)
|
|
1527
|
+
};
|
|
1528
|
+
const snapshot = useRef(null);
|
|
1529
|
+
useEffect(() => {
|
|
1530
|
+
if (loaded && snapshot.current === null) snapshot.current = JSON.stringify(fields);
|
|
1531
|
+
}, [loaded]);
|
|
1532
|
+
const dirty = snapshot.current !== null && JSON.stringify(fields) !== snapshot.current;
|
|
1533
|
+
useSaveBar({
|
|
1534
|
+
dirty,
|
|
1535
|
+
saving,
|
|
1536
|
+
onSave: () => {
|
|
1537
|
+
void handleSave();
|
|
1538
|
+
},
|
|
1539
|
+
onDiscard: () => onSaved?.(),
|
|
1540
|
+
saveLabel: t2("financial.invoiceForm.save")
|
|
1541
|
+
});
|
|
1542
|
+
const title = isEdit ? t2("financial.invoiceForm.editTitle") : t2("financial.invoiceForm.newTitle");
|
|
1543
|
+
const subtitle = isEdit ? t2("financial.invoiceForm.editing") : t2("financial.invoiceForm.creating");
|
|
1544
|
+
if (loading) return /* @__PURE__ */ jsx(OrderFormSkeleton, {});
|
|
1545
|
+
const money = (v) => formatCurrency(v, currency);
|
|
1546
|
+
const actions = isEdit ? /* @__PURE__ */ jsxs("div", { className: "relative", ref: menuRef, children: [
|
|
1547
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "icon", onClick: () => setMenuOpen(!menuOpen), "aria-label": "More actions", children: /* @__PURE__ */ jsx(MoreVertical, { className: "h-4 w-4" }) }),
|
|
1548
|
+
menuOpen && /* @__PURE__ */ jsx("div", { className: "fayz-glass-surface absolute right-0 top-full z-20 mt-1 w-44 rounded-lg border bg-popover py-1 shadow-md", style: { animation: "field-slide-in 150ms ease-out" }, children: /* @__PURE__ */ jsxs(
|
|
1549
|
+
"button",
|
|
1550
|
+
{
|
|
1551
|
+
onClick: () => {
|
|
1552
|
+
setConfirmCancel(true);
|
|
1553
|
+
setMenuOpen(false);
|
|
1554
|
+
},
|
|
1555
|
+
className: "flex w-full items-center gap-2 px-3 py-2 text-xs text-destructive transition-colors hover:bg-destructive/10",
|
|
1556
|
+
children: [
|
|
1557
|
+
/* @__PURE__ */ jsx(Ban, { className: "h-3.5 w-3.5" }),
|
|
1558
|
+
" ",
|
|
1559
|
+
t2("financial.invoiceForm.cancelInvoice")
|
|
1560
|
+
]
|
|
1561
|
+
}
|
|
1562
|
+
) })
|
|
1563
|
+
] }) : null;
|
|
1564
|
+
const paidSoFar = savedMovements.reduce((sum, m) => sum + (m.paidAmount ?? 0), 0);
|
|
1565
|
+
const hasPayments = paidSoFar > 0;
|
|
1566
|
+
const installmentsEditor = /* @__PURE__ */ jsx(OrderFormBlock, { title: t2("financial.invoiceForm.installments"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
1567
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: [1, 2, 3, 6, 12].map((n) => /* @__PURE__ */ jsxs(
|
|
1568
|
+
"button",
|
|
1569
|
+
{
|
|
1570
|
+
type: "button",
|
|
1571
|
+
onClick: () => generateInstallments(n),
|
|
1572
|
+
disabled: totalAmount <= 0,
|
|
1573
|
+
className: `rounded-full px-2.5 py-1 text-[10px] font-medium transition-colors disabled:opacity-30 ${installments.length === n ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-muted/80"}`,
|
|
1574
|
+
children: [
|
|
1575
|
+
n,
|
|
1576
|
+
"x"
|
|
1577
|
+
]
|
|
1578
|
+
},
|
|
1579
|
+
n
|
|
1580
|
+
)) }),
|
|
1581
|
+
installments.length === 0 ? /* @__PURE__ */ jsx("p", { className: "py-4 text-center text-xs text-muted-foreground", children: totalAmount <= 0 ? t2("financial.invoiceForm.addItemsFirst") : t2("financial.invoiceForm.selectInstallments") }) : /* @__PURE__ */ jsx("div", { className: "space-y-2", children: installments.map((inst, i) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 rounded border p-2.5", children: [
|
|
1582
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
1583
|
+
/* @__PURE__ */ jsxs("span", { className: "text-[10px] font-medium text-muted-foreground", children: [
|
|
1584
|
+
"#",
|
|
1585
|
+
i + 1
|
|
1586
|
+
] }),
|
|
1587
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-semibold tabular-nums", children: money(inst.amount) })
|
|
1588
|
+
] }),
|
|
1589
|
+
/* @__PURE__ */ jsx(
|
|
1590
|
+
DatePicker,
|
|
1591
|
+
{
|
|
1592
|
+
value: inst.dueDate,
|
|
1593
|
+
onChange: (v) => {
|
|
1594
|
+
const updated = [...installments];
|
|
1595
|
+
updated[i] = { ...inst, dueDate: v };
|
|
1596
|
+
setInstallments(updated);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
)
|
|
1600
|
+
] }, inst._id)) })
|
|
1601
|
+
] }) });
|
|
1602
|
+
const scheduleSection = isEdit ? /* @__PURE__ */ jsx(OrderFormBlock, { title: t2("financial.invoice.paymentSchedule"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
1603
|
+
hasPayments && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.invoiceForm.scheduleLocked") }),
|
|
1604
|
+
!hasPayments && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1.5", children: [1, 2, 3, 6, 12].map((n) => /* @__PURE__ */ jsxs(
|
|
1605
|
+
"button",
|
|
1606
|
+
{
|
|
1607
|
+
type: "button",
|
|
1608
|
+
onClick: () => generateInstallments(n),
|
|
1609
|
+
disabled: totalAmount <= 0,
|
|
1610
|
+
className: `rounded-full px-2.5 py-1 text-[10px] font-medium transition-colors disabled:opacity-30 ${installments.length === n ? "bg-primary text-primary-foreground" : "bg-muted text-muted-foreground hover:bg-muted/80"}`,
|
|
1611
|
+
children: [
|
|
1612
|
+
n,
|
|
1613
|
+
"x"
|
|
1614
|
+
]
|
|
1615
|
+
},
|
|
1616
|
+
n
|
|
1617
|
+
)) }),
|
|
1618
|
+
installments.length === 0 ? /* @__PURE__ */ jsx("p", { className: "py-4 text-center text-xs text-muted-foreground", children: totalAmount <= 0 ? t2("financial.invoiceForm.addItemsFirst") : t2("financial.invoiceForm.selectInstallments") }) : /* @__PURE__ */ jsx("div", { className: "divide-y rounded border", children: installments.map((inst, i) => {
|
|
1619
|
+
const saved = savedMovements[i];
|
|
1620
|
+
const isPaid = saved?.status === "paid";
|
|
1621
|
+
const isPartial = saved?.status === "partial";
|
|
1622
|
+
const StatusIcon = isPaid ? CircleCheckBig : CircleDashed;
|
|
1623
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3 px-3 py-2.5", children: [
|
|
1624
|
+
/* @__PURE__ */ jsxs("span", { className: "w-6 shrink-0 text-xs font-medium text-muted-foreground", children: [
|
|
1625
|
+
"#",
|
|
1626
|
+
i + 1
|
|
1627
|
+
] }),
|
|
1628
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm font-semibold tabular-nums", children: money(inst.amount) }),
|
|
1629
|
+
saved && /* @__PURE__ */ jsxs("span", { className: `inline-flex shrink-0 items-center gap-1 rounded-full px-1.5 py-0.5 text-[10px] font-medium ${isPaid ? "bg-success-soft text-success-soft-foreground" : isPartial ? "bg-warning-soft text-warning-soft-foreground" : "bg-muted text-muted-foreground"}`, children: [
|
|
1630
|
+
/* @__PURE__ */ jsx(StatusIcon, { className: "h-2.5 w-2.5" }),
|
|
1631
|
+
t2(isPaid ? "financial.invoice.statusPaid" : isPartial ? "financial.invoice.statusPartial" : "financial.invoice.statusPending")
|
|
1632
|
+
] }),
|
|
1633
|
+
/* @__PURE__ */ jsxs("div", { className: "ml-auto flex items-center gap-2", children: [
|
|
1634
|
+
!hasPayments ? /* @__PURE__ */ jsx(
|
|
1635
|
+
DatePicker,
|
|
1636
|
+
{
|
|
1637
|
+
value: inst.dueDate,
|
|
1638
|
+
variant: "chip",
|
|
1639
|
+
onChange: (v) => {
|
|
1640
|
+
const updated = [...installments];
|
|
1641
|
+
updated[i] = { ...inst, dueDate: v };
|
|
1642
|
+
setInstallments(updated);
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
) : /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: inst.dueDate }),
|
|
1646
|
+
saved && !isPaid && /* @__PURE__ */ jsxs(Button, { size: "sm", variant: "outline", onClick: () => setPayingMovement(saved), children: [
|
|
1647
|
+
/* @__PURE__ */ jsx(DollarSign, { className: "mr-1 h-3.5 w-3.5" }),
|
|
1648
|
+
t2("financial.invoice.pay")
|
|
1649
|
+
] })
|
|
1650
|
+
] })
|
|
1651
|
+
] }, inst._id);
|
|
1652
|
+
}) })
|
|
1653
|
+
] }) }) : null;
|
|
1654
|
+
const sideSections = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1655
|
+
/* @__PURE__ */ jsx(OrderFormBlock, { title: direction === "debit" ? t2("financial.invoiceForm.payTo") : t2("financial.invoiceForm.receiveFrom"), children: /* @__PURE__ */ jsx(
|
|
1656
|
+
SearchSelect,
|
|
1657
|
+
{
|
|
1658
|
+
required: true,
|
|
1659
|
+
value: contactId ?? "",
|
|
1660
|
+
displayValue: contactName,
|
|
1661
|
+
onChange: (id, opt) => {
|
|
1662
|
+
setContactId(id || void 0);
|
|
1663
|
+
setContactName(opt?.label ?? "");
|
|
1664
|
+
},
|
|
1665
|
+
onSearch: async (q) => {
|
|
1666
|
+
if (!config.contactLookup) return [];
|
|
1667
|
+
const results = await config.contactLookup.search(q);
|
|
1668
|
+
return results.map((r) => ({ id: r.id, label: r.label, subtitle: r.subtitle, group: r.group, data: r }));
|
|
1669
|
+
},
|
|
1670
|
+
placeholder: t2("financial.invoiceForm.searchType")
|
|
1671
|
+
}
|
|
1672
|
+
) }),
|
|
1673
|
+
/* @__PURE__ */ jsx(OrderFormBlock, { title: t2("financial.invoice.details"), children: /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
1674
|
+
hasLocations && /* @__PURE__ */ jsx(LocationSelector, { locations, value: unitId, onChange: setUnitId }),
|
|
1675
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1676
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.date") }),
|
|
1677
|
+
/* @__PURE__ */ jsx(DatePicker, { value: invoiceDate, onChange: setInvoiceDate, className: "mt-1" })
|
|
1678
|
+
] }),
|
|
1679
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1680
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("financial.invoiceForm.fiscalNumber") }),
|
|
1681
|
+
/* @__PURE__ */ jsx(
|
|
1682
|
+
"input",
|
|
1683
|
+
{
|
|
1684
|
+
type: "text",
|
|
1685
|
+
value: fiscalNumber,
|
|
1686
|
+
onChange: (e) => setFiscalNumber(e.target.value),
|
|
1687
|
+
placeholder: t2("financial.invoiceForm.fiscalNumber"),
|
|
1688
|
+
className: "mt-1 w-full rounded-input border border-input bg-card px-3 py-2 text-sm shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] focus:outline-none focus:ring-2 focus:ring-ring"
|
|
2043
1689
|
}
|
|
2044
1690
|
)
|
|
1691
|
+
] })
|
|
1692
|
+
] }) }),
|
|
1693
|
+
!isEdit && installmentsEditor,
|
|
1694
|
+
/* @__PURE__ */ jsx(OrderFormBlock, { title: t2("financial.invoiceForm.observations"), children: /* @__PURE__ */ jsx(
|
|
1695
|
+
"textarea",
|
|
1696
|
+
{
|
|
1697
|
+
value: observations,
|
|
1698
|
+
onChange: (e) => setObservations(e.target.value),
|
|
1699
|
+
placeholder: t2("financial.invoiceForm.additionalNotes"),
|
|
1700
|
+
rows: 2,
|
|
1701
|
+
className: "w-full resize-none rounded-input border border-input bg-card px-3 py-2 text-sm shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] focus:outline-none focus:ring-2 focus:ring-ring"
|
|
1702
|
+
}
|
|
1703
|
+
) })
|
|
1704
|
+
] });
|
|
1705
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1706
|
+
/* @__PURE__ */ jsxs(
|
|
1707
|
+
OrderFormView,
|
|
1708
|
+
{
|
|
1709
|
+
title,
|
|
1710
|
+
subtitle,
|
|
1711
|
+
parentLabel: direction === "debit" ? t2("financial.nav.payables") : t2("financial.nav.receivables"),
|
|
1712
|
+
onBack: onSaved,
|
|
1713
|
+
actions,
|
|
1714
|
+
sideSections,
|
|
1715
|
+
children: [
|
|
1716
|
+
/* @__PURE__ */ jsx(
|
|
1717
|
+
OrderFormItemsCard,
|
|
1718
|
+
{
|
|
1719
|
+
label: t2("financial.invoiceForm.items"),
|
|
1720
|
+
addLabel: t2("financial.invoiceForm.addItem"),
|
|
1721
|
+
onAdd: startAddItem,
|
|
1722
|
+
count: items.length,
|
|
1723
|
+
countLabel: t2("financial.invoiceForm.itemCount", { count: items.length, plural: items.length !== 1 ? "s" : "" }),
|
|
1724
|
+
totalLabel: t2("financial.invoiceForm.total"),
|
|
1725
|
+
total: money(totalAmount),
|
|
1726
|
+
emptyLabel: t2("financial.invoiceForm.noItems"),
|
|
1727
|
+
emptyActionLabel: t2("financial.invoiceForm.addFirstItem"),
|
|
1728
|
+
header: addingItem && /* @__PURE__ */ jsx(
|
|
1729
|
+
OrderFormAddItemStep,
|
|
1730
|
+
{
|
|
1731
|
+
itemTypes,
|
|
1732
|
+
onSelect: addItemOfKind,
|
|
1733
|
+
onCancel: () => setAddingItem(false),
|
|
1734
|
+
label: t2("financial.invoiceForm.selectItemType"),
|
|
1735
|
+
cancelLabel: t2("financial.invoiceForm.cancel")
|
|
1736
|
+
}
|
|
1737
|
+
),
|
|
1738
|
+
children: items.map((item, i) => /* @__PURE__ */ jsx(
|
|
1739
|
+
OrderFormItemRow,
|
|
1740
|
+
{
|
|
1741
|
+
index: i,
|
|
1742
|
+
itemKind: item.itemKind,
|
|
1743
|
+
description: item.description,
|
|
1744
|
+
quantity: item.quantity,
|
|
1745
|
+
unitPrice: item.unitPrice,
|
|
1746
|
+
discount: item.discount,
|
|
1747
|
+
surcharge: item.surcharge,
|
|
1748
|
+
referenceId: item.referenceId,
|
|
1749
|
+
itemTypes,
|
|
1750
|
+
currency,
|
|
1751
|
+
formatMoney: money,
|
|
1752
|
+
expanded: expandedItemId === item._id,
|
|
1753
|
+
onToggle: () => setExpandedItemId(expandedItemId === item._id ? null : item._id),
|
|
1754
|
+
onChange: (patch) => updateItem(item._id, patch),
|
|
1755
|
+
onRemove: () => {
|
|
1756
|
+
removeItem(item._id);
|
|
1757
|
+
if (expandedItemId === item._id) setExpandedItemId(null);
|
|
1758
|
+
},
|
|
1759
|
+
entityLookups: config.entityLookups,
|
|
1760
|
+
showTypeSelector: hasItemLookups,
|
|
1761
|
+
allowSurcharge: true,
|
|
1762
|
+
labels: {
|
|
1763
|
+
item: t2("financial.invoiceForm.item"),
|
|
1764
|
+
description: t2("financial.invoiceForm.description"),
|
|
1765
|
+
descriptionPlaceholder: t2("financial.invoiceForm.itemDescription"),
|
|
1766
|
+
noDescription: t2("financial.invoiceForm.noDescription"),
|
|
1767
|
+
qty: t2("financial.invoiceForm.qty"),
|
|
1768
|
+
unitPrice: t2("financial.invoiceForm.unitPrice"),
|
|
1769
|
+
discount: t2("financial.invoiceForm.discount"),
|
|
1770
|
+
surcharge: t2("financial.invoiceForm.surcharge"),
|
|
1771
|
+
adjustments: t2("financial.invoiceForm.discountSurcharge"),
|
|
1772
|
+
removeAdjustments: t2("financial.invoiceForm.removeAdjustments"),
|
|
1773
|
+
done: t2("financial.invoiceForm.done")
|
|
1774
|
+
}
|
|
1775
|
+
},
|
|
1776
|
+
item._id
|
|
1777
|
+
))
|
|
1778
|
+
}
|
|
1779
|
+
),
|
|
1780
|
+
scheduleSection
|
|
1781
|
+
]
|
|
2045
1782
|
}
|
|
2046
1783
|
),
|
|
2047
|
-
|
|
2048
|
-
|
|
1784
|
+
payingMovement && /* @__PURE__ */ jsx(
|
|
1785
|
+
PaymentModal,
|
|
1786
|
+
{
|
|
1787
|
+
movement: payingMovement,
|
|
1788
|
+
onClose: () => setPayingMovement(null),
|
|
1789
|
+
onPaid: () => {
|
|
1790
|
+
setPayingMovement(null);
|
|
1791
|
+
void (async () => {
|
|
1792
|
+
if (!editId) return;
|
|
1793
|
+
const movements = await provider.getMovements({ direction });
|
|
1794
|
+
setSavedMovements(
|
|
1795
|
+
movements.data.filter((m) => m.invoiceId === editId && m.movementKind === "bill").sort((a, b) => (a.installmentNumber ?? 0) - (b.installmentNumber ?? 0))
|
|
1796
|
+
);
|
|
1797
|
+
})();
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
),
|
|
1801
|
+
confirmCancel && createPortal(
|
|
1802
|
+
/* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", onClick: () => setConfirmCancel(false), children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-sm rounded-modal border bg-card shadow-lg mx-4 p-5", onClick: (e) => e.stopPropagation(), style: { animation: "field-slide-in 200ms ease-out" }, children: [
|
|
1803
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 mb-3", children: [
|
|
1804
|
+
/* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-full bg-destructive/10 shrink-0", children: /* @__PURE__ */ jsx(Ban, { className: "h-5 w-5 text-destructive" }) }),
|
|
1805
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1806
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t2("financial.invoiceForm.cancelInvoice") }),
|
|
1807
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t2("financial.invoiceForm.cancelConfirm") })
|
|
1808
|
+
] })
|
|
1809
|
+
] }),
|
|
1810
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mb-4", children: t2("financial.invoiceForm.cancelDesc") }),
|
|
1811
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
1812
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: () => setConfirmCancel(false), children: t2("financial.invoiceForm.keepInvoice") }),
|
|
1813
|
+
/* @__PURE__ */ jsxs(Button, { variant: "destructive", size: "sm", onClick: handleCancelInvoice, disabled: cancelling, children: [
|
|
1814
|
+
/* @__PURE__ */ jsx(Ban, { className: "h-3.5 w-3.5" }),
|
|
1815
|
+
cancelling ? t2("financial.invoiceForm.cancelling") : t2("financial.invoiceForm.yesCancel")
|
|
1816
|
+
] })
|
|
1817
|
+
] })
|
|
1818
|
+
] }) }),
|
|
1819
|
+
document.body
|
|
1820
|
+
),
|
|
1821
|
+
/* @__PURE__ */ jsx("style", { children: `
|
|
1822
|
+
@keyframes field-slide-in {
|
|
1823
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
1824
|
+
to { opacity: 1; transform: translateY(0); }
|
|
1825
|
+
}
|
|
1826
|
+
` })
|
|
1827
|
+
] });
|
|
2049
1828
|
}
|
|
2050
1829
|
var STATUS_COLORS = {
|
|
2051
|
-
open: { bg: "bg-info-soft text-info-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusOpen" },
|
|
2052
|
-
pending: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusPending" },
|
|
2053
|
-
partial: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleEllipsis, labelKey: "financial.invoice.statusPartial" },
|
|
2054
|
-
paid: { bg: "bg-success-soft text-success-soft-foreground", icon: CircleCheckBig, labelKey: "financial.invoice.statusPaid" },
|
|
2055
|
-
overdue: { bg: "bg-destructive-soft text-destructive-soft-foreground", icon: CircleAlert, labelKey: "financial.invoice.statusOverdue" },
|
|
2056
|
-
draft: { bg: "bg-muted text-muted-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusDraft" },
|
|
2057
|
-
cancelled: { bg: "bg-muted text-muted-foreground", icon: Ban, labelKey: "financial.invoice.statusCancelled" }
|
|
1830
|
+
open: { bg: "bg-info-soft text-info-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusOpen", tone: "info" },
|
|
1831
|
+
pending: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusPending", tone: "warning" },
|
|
1832
|
+
partial: { bg: "bg-warning-soft text-warning-soft-foreground", icon: CircleEllipsis, labelKey: "financial.invoice.statusPartial", tone: "warning" },
|
|
1833
|
+
paid: { bg: "bg-success-soft text-success-soft-foreground", icon: CircleCheckBig, labelKey: "financial.invoice.statusPaid", tone: "success" },
|
|
1834
|
+
overdue: { bg: "bg-destructive-soft text-destructive-soft-foreground", icon: CircleAlert, labelKey: "financial.invoice.statusOverdue", tone: "destructive" },
|
|
1835
|
+
draft: { bg: "bg-muted text-muted-foreground", icon: CircleDashed, labelKey: "financial.invoice.statusDraft", tone: "muted" },
|
|
1836
|
+
cancelled: { bg: "bg-muted text-muted-foreground", icon: Ban, labelKey: "financial.invoice.statusCancelled", tone: "muted" }
|
|
2058
1837
|
};
|
|
2059
1838
|
function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel }) {
|
|
2060
1839
|
const t2 = useTranslation();
|
|
@@ -2125,69 +1904,18 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
|
|
|
2125
1904
|
}
|
|
2126
1905
|
async function handleCancel() {
|
|
2127
1906
|
setCancelling(true);
|
|
2128
|
-
try {
|
|
2129
|
-
await cancelInvoice(invoiceId);
|
|
2130
|
-
await loadData();
|
|
2131
|
-
setConfirmCancel(false);
|
|
2132
|
-
setMenuOpen(false);
|
|
2133
|
-
} catch {
|
|
2134
|
-
} finally {
|
|
2135
|
-
setCancelling(false);
|
|
2136
|
-
}
|
|
2137
|
-
}
|
|
2138
|
-
if (loading) {
|
|
2139
|
-
return /* @__PURE__ */
|
|
2140
|
-
/* @__PURE__ */ jsx(SubpageHeader, { title: "", onBack, parentLabel }),
|
|
2141
|
-
/* @__PURE__ */ jsxs("div", { className: "rounded-xl border bg-card shadow-sm overflow-hidden", children: [
|
|
2142
|
-
/* @__PURE__ */ jsx("div", { className: "h-1 bg-muted animate-pulse" }),
|
|
2143
|
-
/* @__PURE__ */ jsxs("div", { className: "p-5 pb-4", children: [
|
|
2144
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
|
|
2145
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
2146
|
-
/* @__PURE__ */ jsx("div", { className: "h-10 w-10 rounded-lg bg-muted/40 animate-pulse" }),
|
|
2147
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2148
|
-
/* @__PURE__ */ jsx("div", { className: "h-3 w-20 rounded bg-muted/40 animate-pulse" }),
|
|
2149
|
-
/* @__PURE__ */ jsx("div", { className: "h-6 w-32 rounded bg-muted/40 animate-pulse" })
|
|
2150
|
-
] })
|
|
2151
|
-
] }),
|
|
2152
|
-
/* @__PURE__ */ jsx("div", { className: "h-6 w-20 rounded-full bg-muted/40 animate-pulse" })
|
|
2153
|
-
] }),
|
|
2154
|
-
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-3 mt-4 pt-4 border-t", children: [1, 2, 3, 4].map((i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2155
|
-
/* @__PURE__ */ jsx("div", { className: "h-3.5 w-3.5 rounded bg-muted/30 animate-pulse" }),
|
|
2156
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
|
|
2157
|
-
/* @__PURE__ */ jsx("div", { className: "h-2 w-10 rounded bg-muted/30 animate-pulse" }),
|
|
2158
|
-
/* @__PURE__ */ jsx("div", { className: "h-3 w-16 rounded bg-muted/40 animate-pulse" })
|
|
2159
|
-
] })
|
|
2160
|
-
] }, i)) })
|
|
2161
|
-
] }),
|
|
2162
|
-
/* @__PURE__ */ jsx("div", { className: "px-5 pt-3 pb-1.5", children: /* @__PURE__ */ jsx("div", { className: "h-3 w-12 rounded bg-muted/30 animate-pulse" }) }),
|
|
2163
|
-
/* @__PURE__ */ jsxs("div", { className: "px-5", children: [
|
|
2164
|
-
/* @__PURE__ */ jsxs("div", { className: "border-b py-2 flex justify-between", children: [
|
|
2165
|
-
/* @__PURE__ */ jsx("div", { className: "h-2 w-24 rounded bg-muted/20 animate-pulse" }),
|
|
2166
|
-
/* @__PURE__ */ jsx("div", { className: "h-2 w-16 rounded bg-muted/20 animate-pulse" })
|
|
2167
|
-
] }),
|
|
2168
|
-
[1, 2].map((i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between py-2.5 border-b last:border-0", children: [
|
|
2169
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2170
|
-
/* @__PURE__ */ jsx("div", { className: "h-3 w-4 rounded bg-muted/30 animate-pulse" }),
|
|
2171
|
-
/* @__PURE__ */ jsx("div", { className: "h-5 w-14 rounded bg-muted/30 animate-pulse" }),
|
|
2172
|
-
/* @__PURE__ */ jsx("div", { className: "h-3 w-28 rounded bg-muted/40 animate-pulse" })
|
|
2173
|
-
] }),
|
|
2174
|
-
/* @__PURE__ */ jsx("div", { className: "h-3 w-20 rounded bg-muted/40 animate-pulse" })
|
|
2175
|
-
] }, i))
|
|
2176
|
-
] }),
|
|
2177
|
-
/* @__PURE__ */ jsx("div", { className: "px-5 py-3 border-t bg-muted/10 flex justify-end", children: /* @__PURE__ */ jsx("div", { className: "h-4 w-32 rounded bg-muted/40 animate-pulse" }) })
|
|
2178
|
-
] }),
|
|
2179
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2180
|
-
/* @__PURE__ */ jsx("div", { className: "h-4 w-28 rounded bg-muted/30 animate-pulse" }),
|
|
2181
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card shadow-sm divide-y", children: [1].map((i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [
|
|
2182
|
-
/* @__PURE__ */ jsx("div", { className: "h-3 w-5 rounded bg-muted/30 animate-pulse" }),
|
|
2183
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-1.5", children: [
|
|
2184
|
-
/* @__PURE__ */ jsx("div", { className: "h-4 w-24 rounded bg-muted/40 animate-pulse" }),
|
|
2185
|
-
/* @__PURE__ */ jsx("div", { className: "h-2 w-32 rounded bg-muted/30 animate-pulse" })
|
|
2186
|
-
] }),
|
|
2187
|
-
/* @__PURE__ */ jsx("div", { className: "h-7 w-16 rounded-lg bg-muted/30 animate-pulse" })
|
|
2188
|
-
] }, i)) })
|
|
2189
|
-
] })
|
|
2190
|
-
] });
|
|
1907
|
+
try {
|
|
1908
|
+
await cancelInvoice(invoiceId);
|
|
1909
|
+
await loadData();
|
|
1910
|
+
setConfirmCancel(false);
|
|
1911
|
+
setMenuOpen(false);
|
|
1912
|
+
} catch {
|
|
1913
|
+
} finally {
|
|
1914
|
+
setCancelling(false);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
if (loading) {
|
|
1918
|
+
return /* @__PURE__ */ jsx(OrderDetailSkeleton, {});
|
|
2191
1919
|
}
|
|
2192
1920
|
if (!invoice) {
|
|
2193
1921
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
@@ -2196,335 +1924,235 @@ function InvoiceDetailView({ invoiceId, direction, onBack, onEdit, backLabel })
|
|
|
2196
1924
|
] });
|
|
2197
1925
|
}
|
|
2198
1926
|
const statusConfig = STATUS_COLORS[invoice.status] ?? STATUS_COLORS.pending;
|
|
2199
|
-
|
|
1927
|
+
statusConfig.icon;
|
|
2200
1928
|
const totalPaid = movements.reduce((sum, m) => sum + m.paidAmount, 0);
|
|
2201
1929
|
const totalRemaining = invoice.totalAmount - totalPaid;
|
|
2202
1930
|
const subtotal = items.reduce((sum, it) => sum + it.quantity * it.unitPrice, 0);
|
|
2203
1931
|
const totalDiscount = items.reduce((sum, it) => sum + it.discount, 0);
|
|
2204
1932
|
const totalSurcharge = items.reduce((sum, it) => sum + it.surcharge, 0);
|
|
2205
|
-
const
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
},
|
|
2219
|
-
{
|
|
2220
|
-
|
|
2221
|
-
header: () => /* @__PURE__ */ jsx("span", { className: "block text-right", children: t2("financial.invoice.columnQty") }),
|
|
2222
|
-
cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "block text-right text-xs tabular-nums", children: getValue() })
|
|
2223
|
-
},
|
|
2224
|
-
{
|
|
2225
|
-
accessorKey: "unitPrice",
|
|
2226
|
-
header: () => /* @__PURE__ */ jsx("span", { className: "block text-right", children: t2("financial.invoice.columnUnitPrice") }),
|
|
2227
|
-
cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "block text-right text-xs tabular-nums", children: formatCurrency(getValue(), currency) })
|
|
2228
|
-
},
|
|
2229
|
-
{
|
|
2230
|
-
accessorKey: "totalAmount",
|
|
2231
|
-
header: () => /* @__PURE__ */ jsx("span", { className: "block text-right", children: t2("financial.invoice.columnTotal") }),
|
|
2232
|
-
cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "block text-right font-medium tabular-nums", children: formatCurrency(getValue(), currency) })
|
|
2233
|
-
}
|
|
1933
|
+
const detailItems = items.map((it, i) => ({
|
|
1934
|
+
id: it.id ?? String(i),
|
|
1935
|
+
name: it.description,
|
|
1936
|
+
description: it.itemKind,
|
|
1937
|
+
quantity: it.quantity,
|
|
1938
|
+
unitPrice: formatCurrency(it.unitPrice, currency),
|
|
1939
|
+
total: formatCurrency(it.totalAmount, currency)
|
|
1940
|
+
}));
|
|
1941
|
+
const cancelled = invoice.status === "cancelled";
|
|
1942
|
+
const totalRows = [
|
|
1943
|
+
...totalDiscount > 0 || totalSurcharge > 0 ? [{ label: t2("financial.invoice.subtotal"), value: formatCurrency(subtotal, currency), muted: true }] : [],
|
|
1944
|
+
...totalDiscount > 0 ? [{ label: t2("financial.invoice.discount"), value: `-${formatCurrency(totalDiscount, currency)}`, muted: true }] : [],
|
|
1945
|
+
...totalSurcharge > 0 ? [{ label: t2("financial.invoice.surcharge"), value: `+${formatCurrency(totalSurcharge, currency)}`, muted: true }] : [],
|
|
1946
|
+
{ label: t2("financial.invoice.total"), value: formatCurrency(invoice.totalAmount, currency), strong: true },
|
|
1947
|
+
{ label: t2("financial.invoice.columnPaid"), value: formatCurrency(totalPaid, currency) },
|
|
1948
|
+
...totalRemaining > 0 ? [{ label: t2("financial.invoice.balance"), value: formatCurrency(totalRemaining, currency) }] : []
|
|
2234
1949
|
];
|
|
2235
|
-
|
|
1950
|
+
direction === "debit" ? t2("financial.invoice.accountsPayable") : t2("financial.invoice.accountsReceivable");
|
|
1951
|
+
const actions = /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
1952
|
+
!cancelled && invoice.status !== "paid" && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: onEdit, children: [
|
|
1953
|
+
/* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" }),
|
|
1954
|
+
" ",
|
|
1955
|
+
t2("financial.invoice.edit")
|
|
1956
|
+
] }),
|
|
1957
|
+
!cancelled && invoice.status !== "paid" && /* @__PURE__ */ jsxs("div", { className: "relative", ref: menuRef, children: [
|
|
1958
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", size: "icon", onClick: () => setMenuOpen(!menuOpen), "aria-label": "More actions", children: /* @__PURE__ */ jsx(MoreVertical, { className: "h-4 w-4" }) }),
|
|
1959
|
+
menuOpen && /* @__PURE__ */ jsx("div", { className: "fayz-glass-surface absolute right-0 top-full mt-1 w-44 rounded-lg border bg-popover shadow-md z-20 py-1", style: { animation: "field-slide-in 150ms ease-out" }, children: /* @__PURE__ */ jsxs(
|
|
1960
|
+
"button",
|
|
1961
|
+
{
|
|
1962
|
+
onClick: () => {
|
|
1963
|
+
setConfirmCancel(true);
|
|
1964
|
+
setMenuOpen(false);
|
|
1965
|
+
},
|
|
1966
|
+
className: "flex w-full items-center gap-2 px-3 py-2 text-xs text-destructive hover:bg-destructive/10 transition-colors",
|
|
1967
|
+
children: [
|
|
1968
|
+
/* @__PURE__ */ jsx(Ban, { className: "h-3.5 w-3.5" }),
|
|
1969
|
+
" ",
|
|
1970
|
+
t2("financial.invoice.cancelInvoice")
|
|
1971
|
+
]
|
|
1972
|
+
}
|
|
1973
|
+
) })
|
|
1974
|
+
] })
|
|
1975
|
+
] });
|
|
1976
|
+
const sideBlocks = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1977
|
+
invoice.bookingStartsAt && /* @__PURE__ */ jsx(OrderDetailBlock, { title: t2("financial.invoice.booking"), children: /* @__PURE__ */ jsxs(
|
|
1978
|
+
"button",
|
|
1979
|
+
{
|
|
1980
|
+
type: "button",
|
|
1981
|
+
onClick: () => config.onBookingClick?.(invoice.id),
|
|
1982
|
+
className: "inline-flex items-center gap-1 text-sm font-medium text-magic hover:underline",
|
|
1983
|
+
children: [
|
|
1984
|
+
/* @__PURE__ */ jsx(CalendarDays, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
1985
|
+
new Date(invoice.bookingStartsAt).toLocaleDateString(void 0, { weekday: "short", day: "2-digit", month: "short" }),
|
|
1986
|
+
" ",
|
|
1987
|
+
new Date(invoice.bookingStartsAt).toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" }),
|
|
1988
|
+
/* @__PURE__ */ jsx(ExternalLink, { className: "h-2.5 w-2.5" })
|
|
1989
|
+
]
|
|
1990
|
+
}
|
|
1991
|
+
) }),
|
|
1992
|
+
invoice.observations && /* @__PURE__ */ jsx(OrderDetailBlock, { title: t2("financial.invoice.notes"), children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: invoice.observations }) })
|
|
1993
|
+
] });
|
|
1994
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2236
1995
|
/* @__PURE__ */ jsx(
|
|
2237
|
-
|
|
1996
|
+
OrderDetailView,
|
|
2238
1997
|
{
|
|
2239
1998
|
title: invoice.fiscalNumber ? `#${invoice.fiscalNumber}` : invoice.contactName ?? invoice.invoiceDate,
|
|
1999
|
+
badges: [{ label: t2(statusConfig.labelKey), tone: statusConfig.tone }],
|
|
2000
|
+
subtitle: invoice.invoiceDate,
|
|
2240
2001
|
onBack,
|
|
2241
|
-
parentLabel,
|
|
2242
|
-
actions
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2002
|
+
backLabel: parentLabel,
|
|
2003
|
+
actions,
|
|
2004
|
+
stats: [
|
|
2005
|
+
{ id: "total", label: t2("financial.invoice.total"), value: formatCurrency(invoice.totalAmount, currency), icon: DollarSign },
|
|
2006
|
+
{ id: "items", label: t2("financial.invoice.items"), value: items.length, icon: LayoutList },
|
|
2007
|
+
{ id: "installments", label: t2("financial.invoice.installments"), value: `${invoice.totalInstallments}x`, icon: Layers },
|
|
2008
|
+
{ id: "date", label: t2("financial.invoice.date"), value: invoice.invoiceDate, icon: CalendarDays }
|
|
2009
|
+
],
|
|
2010
|
+
customer: { name: invoice.contactName, email: null },
|
|
2011
|
+
customerNode: invoice.contactName ? /* @__PURE__ */ jsx(PersonLink, { personId: invoice.contactId, name: invoice.contactName, size: "sm" }) : /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: "\u2014" }),
|
|
2012
|
+
items: detailItems,
|
|
2013
|
+
itemsLabel: t2("financial.invoice.items"),
|
|
2014
|
+
totals: totalRows,
|
|
2015
|
+
totalsLabel: t2("financial.invoice.payment"),
|
|
2016
|
+
mainSections: (
|
|
2017
|
+
// O cronograma no MESMO bloco que os itens e o pagamento — antes o título
|
|
2018
|
+
// ficava fora do cartão, e a seção lia como se fosse de outra tela.
|
|
2019
|
+
/* @__PURE__ */ jsx(OrderDetailBlock, { title: t2("financial.invoice.paymentSchedule"), children: /* @__PURE__ */ jsx("div", { className: "-mx-4 -mb-4", children: movements.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-4 py-4 text-center text-sm text-muted-foreground", children: t2("financial.invoice.noInstallments") }) : /* @__PURE__ */ jsx("div", { className: "divide-y border-t", children: movements.map((mov) => {
|
|
2020
|
+
const remaining = mov.amount - mov.paidAmount;
|
|
2021
|
+
const isPaid = mov.status === "paid";
|
|
2022
|
+
const isPartial = mov.status === "partial";
|
|
2023
|
+
const hasPaidDetails = isPaid || isPartial;
|
|
2024
|
+
const isExpanded = expandedMovId === mov.id;
|
|
2025
|
+
const movStatus = STATUS_COLORS[mov.status] ?? STATUS_COLORS.pending;
|
|
2026
|
+
const MovIcon = movStatus.icon;
|
|
2027
|
+
const instPayments = payments.filter((p) => (p.installmentNumber ?? null) === (mov.installmentNumber ?? null));
|
|
2028
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
2029
|
+
/* @__PURE__ */ jsxs(
|
|
2030
|
+
"div",
|
|
2031
|
+
{
|
|
2032
|
+
className: `flex items-center gap-3 px-4 py-3 ${hasPaidDetails ? "cursor-pointer hover:bg-muted/20 transition-colors" : ""}`,
|
|
2033
|
+
onClick: hasPaidDetails ? () => handleExpandMov(mov.id) : void 0,
|
|
2034
|
+
children: [
|
|
2035
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-muted-foreground w-6 shrink-0", children: [
|
|
2036
|
+
"#",
|
|
2037
|
+
mov.installmentNumber
|
|
2038
|
+
] }),
|
|
2039
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
2040
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2041
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium tabular-nums", children: formatCurrency(mov.amount, currency) }),
|
|
2042
|
+
/* @__PURE__ */ jsxs("span", { className: `inline-flex items-center gap-0.5 rounded-full px-1.5 py-0.5 text-[9px] font-medium capitalize ${movStatus.bg}`, children: [
|
|
2043
|
+
/* @__PURE__ */ jsx(MovIcon, { className: "h-2 w-2" }),
|
|
2044
|
+
" ",
|
|
2045
|
+
t2(movStatus.labelKey)
|
|
2046
|
+
] })
|
|
2047
|
+
] }),
|
|
2048
|
+
/* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
|
|
2049
|
+
t2("financial.invoice.due"),
|
|
2050
|
+
" ",
|
|
2051
|
+
mov.dueDate,
|
|
2052
|
+
mov.paidAmount > 0 && !isPaid && ` \xB7 ${t2("financial.invoice.paidLabel")} ${formatCurrency(mov.paidAmount, currency)}`,
|
|
2053
|
+
mov.paymentDate && ` \xB7 ${t2("financial.invoice.paidOn")} ${mov.paymentDate}`
|
|
2054
|
+
] })
|
|
2055
|
+
] }),
|
|
2056
|
+
!isPaid && !isPartial && invoice.status !== "cancelled" && /* @__PURE__ */ jsxs(
|
|
2057
|
+
"button",
|
|
2058
|
+
{
|
|
2059
|
+
onClick: (e) => {
|
|
2060
|
+
e.stopPropagation();
|
|
2061
|
+
setPayingMovement(mov);
|
|
2062
|
+
},
|
|
2063
|
+
className: "inline-flex items-center gap-1 rounded-lg bg-primary border border-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 shadow-button-primary active:shadow-button-inset transition-colors shrink-0",
|
|
2064
|
+
children: [
|
|
2065
|
+
/* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
|
|
2066
|
+
t2("financial.invoice.pay"),
|
|
2067
|
+
" ",
|
|
2068
|
+
remaining < mov.amount ? formatCurrency(remaining, currency) : ""
|
|
2069
|
+
]
|
|
2070
|
+
}
|
|
2071
|
+
),
|
|
2072
|
+
isPartial && invoice.status !== "cancelled" && /* @__PURE__ */ jsxs(
|
|
2073
|
+
"button",
|
|
2074
|
+
{
|
|
2075
|
+
onClick: (e) => {
|
|
2076
|
+
e.stopPropagation();
|
|
2077
|
+
setPayingMovement(mov);
|
|
2078
|
+
},
|
|
2079
|
+
className: "inline-flex items-center gap-1 rounded-lg border px-3 py-1.5 text-xs font-medium hover:bg-muted bg-card shadow-button active:shadow-button-inset transition-colors shrink-0",
|
|
2080
|
+
children: [
|
|
2081
|
+
/* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
|
|
2082
|
+
t2("financial.invoice.pay"),
|
|
2083
|
+
" ",
|
|
2084
|
+
formatCurrency(remaining, currency)
|
|
2085
|
+
]
|
|
2086
|
+
}
|
|
2087
|
+
),
|
|
2088
|
+
hasPaidDetails && /* @__PURE__ */ jsx(ChevronDown, { className: `h-3.5 w-3.5 text-muted-foreground shrink-0 transition-transform duration-200 ${isExpanded ? "rotate-180" : ""}` })
|
|
2089
|
+
]
|
|
2090
|
+
}
|
|
2091
|
+
),
|
|
2092
|
+
/* @__PURE__ */ jsx(
|
|
2093
|
+
"div",
|
|
2094
|
+
{
|
|
2095
|
+
className: "grid transition-all duration-200 ease-out",
|
|
2096
|
+
style: { gridTemplateRows: isExpanded ? "1fr" : "0fr" },
|
|
2097
|
+
children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-2.5 pl-14", children: detailsLoading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
2098
|
+
/* @__PURE__ */ jsx("div", { className: "h-3 w-20 rounded bg-muted/40 animate-pulse" }),
|
|
2099
|
+
/* @__PURE__ */ jsx("div", { className: "h-3 w-16 rounded bg-muted/30 animate-pulse" }),
|
|
2100
|
+
/* @__PURE__ */ jsx("div", { className: "h-3 w-24 rounded bg-muted/40 animate-pulse" })
|
|
2101
|
+
] }) : instPayments.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-[11px] text-muted-foreground", children: t2("financial.invoice.noDetails") }) : /* @__PURE__ */ jsx("div", { className: "space-y-1", children: instPayments.map((p) => {
|
|
2102
|
+
const pMethod = paymentMethods.find((m) => m.id === p.paymentMethodId);
|
|
2103
|
+
const pType = paymentMethodTypes.find((tp) => tp.id === (pMethod?.paymentMethodTypeId ?? p.paymentMethodTypeId));
|
|
2104
|
+
const pAccount = bankAccounts.find((a) => a.id === p.bankAccountId);
|
|
2105
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-1 gap-y-0.5 text-[11px] text-muted-foreground", children: [
|
|
2106
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-success", children: formatCurrency(p.paidAmount, currency) }),
|
|
2107
|
+
pType && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2108
|
+
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
2109
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: pType.name }),
|
|
2110
|
+
pMethod && /* @__PURE__ */ jsxs("span", { children: [
|
|
2111
|
+
"(",
|
|
2112
|
+
pMethod.name,
|
|
2113
|
+
")"
|
|
2114
|
+
] })
|
|
2115
|
+
] }),
|
|
2116
|
+
p.cardBrand && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2117
|
+
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
2118
|
+
/* @__PURE__ */ jsx(CreditCard, { className: "h-3 w-3 shrink-0" }),
|
|
2119
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: p.cardBrand }),
|
|
2120
|
+
p.cardInstallments && /* @__PURE__ */ jsxs("span", { children: [
|
|
2121
|
+
p.cardInstallments,
|
|
2122
|
+
"x"
|
|
2123
|
+
] })
|
|
2124
|
+
] }),
|
|
2125
|
+
pAccount && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2126
|
+
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
2127
|
+
/* @__PURE__ */ jsx(Building2, { className: "h-3 w-3 shrink-0" }),
|
|
2128
|
+
/* @__PURE__ */ jsx("span", { children: pAccount.name })
|
|
2129
|
+
] }),
|
|
2130
|
+
(p.feeAmount ?? 0) > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2131
|
+
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
2132
|
+
/* @__PURE__ */ jsxs("span", { className: "text-warning", children: [
|
|
2133
|
+
t2("financial.statements.columnFee"),
|
|
2134
|
+
" ",
|
|
2135
|
+
formatCurrency(p.feeAmount ?? 0, currency)
|
|
2136
|
+
] })
|
|
2137
|
+
] }),
|
|
2138
|
+
p.paymentDate && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2139
|
+
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
2140
|
+
/* @__PURE__ */ jsx("span", { children: p.paymentDate })
|
|
2141
|
+
] }),
|
|
2142
|
+
p.notes && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2143
|
+
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
2144
|
+
/* @__PURE__ */ jsx("span", { className: "italic truncate max-w-[200px]", children: p.notes })
|
|
2145
|
+
] })
|
|
2146
|
+
] }, p.id);
|
|
2147
|
+
}) }) }) })
|
|
2148
|
+
}
|
|
2149
|
+
)
|
|
2150
|
+
] }, mov.id);
|
|
2151
|
+
}) }) }) })
|
|
2152
|
+
),
|
|
2153
|
+
extraSections: sideBlocks
|
|
2267
2154
|
}
|
|
2268
2155
|
),
|
|
2269
|
-
/* @__PURE__ */ jsxs("div", { className: "rounded-xl border bg-card shadow-sm overflow-hidden", children: [
|
|
2270
|
-
/* @__PURE__ */ jsx("div", { className: "h-1 bg-primary" }),
|
|
2271
|
-
/* @__PURE__ */ jsxs("div", { className: "p-5 pb-4", children: [
|
|
2272
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-4", children: [
|
|
2273
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-start gap-3", children: [
|
|
2274
|
-
/* @__PURE__ */ jsx("div", { className: "flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10 shrink-0", children: /* @__PURE__ */ jsx(FileText, { className: "h-5 w-5 text-primary" }) }),
|
|
2275
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
2276
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wider", children: direction === "debit" ? t2("financial.invoice.accountsPayable") : t2("financial.invoice.accountsReceivable") }),
|
|
2277
|
-
/* @__PURE__ */ jsx("p", { className: `text-xl font-bold mt-0.5 ${invoice.status === "cancelled" ? "line-through text-muted-foreground" : ""}`, children: formatCurrency(invoice.totalAmount, currency) })
|
|
2278
|
-
] })
|
|
2279
|
-
] }),
|
|
2280
|
-
/* @__PURE__ */ jsxs("span", { className: `inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-[11px] font-semibold capitalize ${statusConfig.bg}`, children: [
|
|
2281
|
-
/* @__PURE__ */ jsx(StatusIcon, { className: "h-3 w-3" }),
|
|
2282
|
-
" ",
|
|
2283
|
-
t2(statusConfig.labelKey)
|
|
2284
|
-
] })
|
|
2285
|
-
] }),
|
|
2286
|
-
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-3 mt-4 pt-4 border-t", children: [
|
|
2287
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2288
|
-
/* @__PURE__ */ jsx(User, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
|
|
2289
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
2290
|
-
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: t2("financial.invoice.contact") }),
|
|
2291
|
-
invoice.contactName ? /* @__PURE__ */ jsx(PersonLink, { personId: invoice.contactId, name: invoice.contactName, size: "sm" }) : /* @__PURE__ */ jsx("p", { className: "text-xs font-medium truncate", children: "\u2014" })
|
|
2292
|
-
] })
|
|
2293
|
-
] }),
|
|
2294
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2295
|
-
/* @__PURE__ */ jsx(Calendar, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
|
|
2296
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
2297
|
-
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: t2("financial.invoice.date") }),
|
|
2298
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium", children: invoice.invoiceDate })
|
|
2299
|
-
] })
|
|
2300
|
-
] }),
|
|
2301
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2302
|
-
/* @__PURE__ */ jsx(Hash, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
|
|
2303
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
2304
|
-
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: t2("financial.invoice.installments") }),
|
|
2305
|
-
/* @__PURE__ */ jsxs("p", { className: "text-xs font-medium", children: [
|
|
2306
|
-
invoice.totalInstallments,
|
|
2307
|
-
"x"
|
|
2308
|
-
] })
|
|
2309
|
-
] })
|
|
2310
|
-
] }),
|
|
2311
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2312
|
-
/* @__PURE__ */ jsx(DollarSign, { className: "h-3.5 w-3.5 text-muted-foreground shrink-0" }),
|
|
2313
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
2314
|
-
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: t2("financial.invoice.columnPaid") }),
|
|
2315
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium", children: formatCurrency(totalPaid, currency) })
|
|
2316
|
-
] })
|
|
2317
|
-
] })
|
|
2318
|
-
] }),
|
|
2319
|
-
invoice.bookingStartsAt && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-3 pt-3 border-t", children: [
|
|
2320
|
-
/* @__PURE__ */ jsx(CalendarDays, { className: "h-3.5 w-3.5 text-magic shrink-0" }),
|
|
2321
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 min-w-0", children: [
|
|
2322
|
-
/* @__PURE__ */ jsx("p", { className: "text-[10px] text-muted-foreground", children: t2("financial.invoice.booking") }),
|
|
2323
|
-
/* @__PURE__ */ jsxs(
|
|
2324
|
-
"button",
|
|
2325
|
-
{
|
|
2326
|
-
type: "button",
|
|
2327
|
-
onClick: () => config.onBookingClick?.(invoice.id),
|
|
2328
|
-
className: "inline-flex items-center gap-1 text-xs font-medium text-magic hover:underline",
|
|
2329
|
-
children: [
|
|
2330
|
-
new Date(invoice.bookingStartsAt).toLocaleDateString(void 0, { weekday: "short", day: "2-digit", month: "short" }),
|
|
2331
|
-
" ",
|
|
2332
|
-
new Date(invoice.bookingStartsAt).toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" }),
|
|
2333
|
-
/* @__PURE__ */ jsx(ExternalLink, { className: "h-2.5 w-2.5" })
|
|
2334
|
-
]
|
|
2335
|
-
}
|
|
2336
|
-
)
|
|
2337
|
-
] })
|
|
2338
|
-
] })
|
|
2339
|
-
] }),
|
|
2340
|
-
items.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2341
|
-
/* @__PURE__ */ jsx("div", { className: "px-5 pt-3 pb-1.5", children: /* @__PURE__ */ jsx("h3", { className: "text-[11px] font-semibold text-muted-foreground uppercase tracking-wider", children: t2("financial.invoice.items") }) }),
|
|
2342
|
-
/* @__PURE__ */ jsx("div", { className: "px-5", children: /* @__PURE__ */ jsx(DataTable, { columns: itemColumns, data: items, variant: "flat", compact: true }) }),
|
|
2343
|
-
/* @__PURE__ */ jsx("div", { className: "px-5 py-3 mt-1 border-t bg-muted/10", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-end gap-0.5", children: [
|
|
2344
|
-
(totalDiscount > 0 || totalSurcharge > 0) && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-6 text-xs", children: [
|
|
2345
|
-
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t2("financial.invoice.subtotal") }),
|
|
2346
|
-
/* @__PURE__ */ jsx("span", { className: "tabular-nums w-24 text-right", children: formatCurrency(subtotal, currency) })
|
|
2347
|
-
] }),
|
|
2348
|
-
totalDiscount > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-6 text-xs text-success", children: [
|
|
2349
|
-
/* @__PURE__ */ jsx("span", { children: t2("financial.invoice.discount") }),
|
|
2350
|
-
/* @__PURE__ */ jsxs("span", { className: "tabular-nums w-24 text-right", children: [
|
|
2351
|
-
"-",
|
|
2352
|
-
formatCurrency(totalDiscount, currency)
|
|
2353
|
-
] })
|
|
2354
|
-
] }),
|
|
2355
|
-
totalSurcharge > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-6 text-xs text-warning", children: [
|
|
2356
|
-
/* @__PURE__ */ jsx("span", { children: t2("financial.invoice.surcharge") }),
|
|
2357
|
-
/* @__PURE__ */ jsxs("span", { className: "tabular-nums w-24 text-right", children: [
|
|
2358
|
-
"+",
|
|
2359
|
-
formatCurrency(totalSurcharge, currency)
|
|
2360
|
-
] })
|
|
2361
|
-
] }),
|
|
2362
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-6 text-sm font-bold pt-1", children: [
|
|
2363
|
-
/* @__PURE__ */ jsx("span", { children: t2("financial.invoice.total") }),
|
|
2364
|
-
/* @__PURE__ */ jsx("span", { className: `tabular-nums w-24 text-right ${invoice.status === "cancelled" ? "line-through text-muted-foreground" : ""}`, children: formatCurrency(invoice.totalAmount, currency) })
|
|
2365
|
-
] })
|
|
2366
|
-
] }) })
|
|
2367
|
-
] }),
|
|
2368
|
-
invoice.observations && /* @__PURE__ */ jsxs("div", { className: "px-5 py-3 border-t", children: [
|
|
2369
|
-
/* @__PURE__ */ jsx("p", { className: "text-[10px] font-medium text-muted-foreground uppercase tracking-wider mb-1", children: t2("financial.invoice.notes") }),
|
|
2370
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: invoice.observations })
|
|
2371
|
-
] })
|
|
2372
|
-
] }),
|
|
2373
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2374
|
-
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold text-muted-foreground", children: t2("financial.invoice.paymentSchedule") }),
|
|
2375
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card shadow-sm", children: movements.length === 0 ? /* @__PURE__ */ jsx("div", { className: "px-4 py-8 text-center text-sm text-muted-foreground", children: t2("financial.invoice.noInstallments") }) : /* @__PURE__ */ jsx("div", { className: "divide-y", children: movements.map((mov) => {
|
|
2376
|
-
const remaining = mov.amount - mov.paidAmount;
|
|
2377
|
-
const isPaid = mov.status === "paid";
|
|
2378
|
-
const isPartial = mov.status === "partial";
|
|
2379
|
-
const hasPaidDetails = isPaid || isPartial;
|
|
2380
|
-
const isExpanded = expandedMovId === mov.id;
|
|
2381
|
-
const movStatus = STATUS_COLORS[mov.status] ?? STATUS_COLORS.pending;
|
|
2382
|
-
const MovIcon = movStatus.icon;
|
|
2383
|
-
const instPayments = payments.filter((p) => (p.installmentNumber ?? null) === (mov.installmentNumber ?? null));
|
|
2384
|
-
return /* @__PURE__ */ jsxs("div", { children: [
|
|
2385
|
-
/* @__PURE__ */ jsxs(
|
|
2386
|
-
"div",
|
|
2387
|
-
{
|
|
2388
|
-
className: `flex items-center gap-3 px-4 py-3 ${hasPaidDetails ? "cursor-pointer hover:bg-muted/20 transition-colors" : ""}`,
|
|
2389
|
-
onClick: hasPaidDetails ? () => handleExpandMov(mov.id) : void 0,
|
|
2390
|
-
children: [
|
|
2391
|
-
/* @__PURE__ */ jsxs("span", { className: "text-xs font-medium text-muted-foreground w-6 shrink-0", children: [
|
|
2392
|
-
"#",
|
|
2393
|
-
mov.installmentNumber
|
|
2394
|
-
] }),
|
|
2395
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
2396
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2397
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium tabular-nums", children: formatCurrency(mov.amount, currency) }),
|
|
2398
|
-
/* @__PURE__ */ jsxs("span", { className: `inline-flex items-center gap-0.5 rounded-full px-1.5 py-0.5 text-[9px] font-medium capitalize ${movStatus.bg}`, children: [
|
|
2399
|
-
/* @__PURE__ */ jsx(MovIcon, { className: "h-2 w-2" }),
|
|
2400
|
-
" ",
|
|
2401
|
-
t2(movStatus.labelKey)
|
|
2402
|
-
] })
|
|
2403
|
-
] }),
|
|
2404
|
-
/* @__PURE__ */ jsxs("p", { className: "text-[10px] text-muted-foreground", children: [
|
|
2405
|
-
t2("financial.invoice.due"),
|
|
2406
|
-
" ",
|
|
2407
|
-
mov.dueDate,
|
|
2408
|
-
mov.paidAmount > 0 && !isPaid && ` \xB7 ${t2("financial.invoice.paidLabel")} ${formatCurrency(mov.paidAmount, currency)}`,
|
|
2409
|
-
mov.paymentDate && ` \xB7 ${t2("financial.invoice.paidOn")} ${mov.paymentDate}`
|
|
2410
|
-
] })
|
|
2411
|
-
] }),
|
|
2412
|
-
!isPaid && !isPartial && invoice.status !== "cancelled" && /* @__PURE__ */ jsxs(
|
|
2413
|
-
"button",
|
|
2414
|
-
{
|
|
2415
|
-
onClick: (e) => {
|
|
2416
|
-
e.stopPropagation();
|
|
2417
|
-
setPayingMovement(mov);
|
|
2418
|
-
},
|
|
2419
|
-
className: "inline-flex items-center gap-1 rounded-lg bg-primary border border-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 shadow-button-primary active:shadow-button-inset transition-colors shrink-0",
|
|
2420
|
-
children: [
|
|
2421
|
-
/* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
|
|
2422
|
-
t2("financial.invoice.pay"),
|
|
2423
|
-
" ",
|
|
2424
|
-
remaining < mov.amount ? formatCurrency(remaining, currency) : ""
|
|
2425
|
-
]
|
|
2426
|
-
}
|
|
2427
|
-
),
|
|
2428
|
-
isPartial && invoice.status !== "cancelled" && /* @__PURE__ */ jsxs(
|
|
2429
|
-
"button",
|
|
2430
|
-
{
|
|
2431
|
-
onClick: (e) => {
|
|
2432
|
-
e.stopPropagation();
|
|
2433
|
-
setPayingMovement(mov);
|
|
2434
|
-
},
|
|
2435
|
-
className: "inline-flex items-center gap-1 rounded-lg border px-3 py-1.5 text-xs font-medium hover:bg-muted bg-card shadow-button active:shadow-button-inset transition-colors shrink-0",
|
|
2436
|
-
children: [
|
|
2437
|
-
/* @__PURE__ */ jsx(DollarSign, { className: "h-3 w-3" }),
|
|
2438
|
-
t2("financial.invoice.pay"),
|
|
2439
|
-
" ",
|
|
2440
|
-
formatCurrency(remaining, currency)
|
|
2441
|
-
]
|
|
2442
|
-
}
|
|
2443
|
-
),
|
|
2444
|
-
hasPaidDetails && /* @__PURE__ */ jsx(ChevronDown, { className: `h-3.5 w-3.5 text-muted-foreground shrink-0 transition-transform duration-200 ${isExpanded ? "rotate-180" : ""}` })
|
|
2445
|
-
]
|
|
2446
|
-
}
|
|
2447
|
-
),
|
|
2448
|
-
/* @__PURE__ */ jsx(
|
|
2449
|
-
"div",
|
|
2450
|
-
{
|
|
2451
|
-
className: "grid transition-all duration-200 ease-out",
|
|
2452
|
-
style: { gridTemplateRows: isExpanded ? "1fr" : "0fr" },
|
|
2453
|
-
children: /* @__PURE__ */ jsx("div", { className: "overflow-hidden", children: /* @__PURE__ */ jsx("div", { className: "px-4 pb-2.5 pl-14", children: detailsLoading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
2454
|
-
/* @__PURE__ */ jsx("div", { className: "h-3 w-20 rounded bg-muted/40 animate-pulse" }),
|
|
2455
|
-
/* @__PURE__ */ jsx("div", { className: "h-3 w-16 rounded bg-muted/30 animate-pulse" }),
|
|
2456
|
-
/* @__PURE__ */ jsx("div", { className: "h-3 w-24 rounded bg-muted/40 animate-pulse" })
|
|
2457
|
-
] }) : instPayments.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-[11px] text-muted-foreground", children: t2("financial.invoice.noDetails") }) : /* @__PURE__ */ jsx("div", { className: "space-y-1", children: instPayments.map((p) => {
|
|
2458
|
-
const pMethod = paymentMethods.find((m) => m.id === p.paymentMethodId);
|
|
2459
|
-
const pType = paymentMethodTypes.find((tp) => tp.id === (pMethod?.paymentMethodTypeId ?? p.paymentMethodTypeId));
|
|
2460
|
-
const pAccount = bankAccounts.find((a) => a.id === p.bankAccountId);
|
|
2461
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-1 gap-y-0.5 text-[11px] text-muted-foreground", children: [
|
|
2462
|
-
/* @__PURE__ */ jsx("span", { className: "font-medium text-success", children: formatCurrency(p.paidAmount, currency) }),
|
|
2463
|
-
pType && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2464
|
-
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
2465
|
-
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: pType.name }),
|
|
2466
|
-
pMethod && /* @__PURE__ */ jsxs("span", { children: [
|
|
2467
|
-
"(",
|
|
2468
|
-
pMethod.name,
|
|
2469
|
-
")"
|
|
2470
|
-
] })
|
|
2471
|
-
] }),
|
|
2472
|
-
p.cardBrand && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2473
|
-
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
2474
|
-
/* @__PURE__ */ jsx(CreditCard, { className: "h-3 w-3 shrink-0" }),
|
|
2475
|
-
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: p.cardBrand }),
|
|
2476
|
-
p.cardInstallments && /* @__PURE__ */ jsxs("span", { children: [
|
|
2477
|
-
p.cardInstallments,
|
|
2478
|
-
"x"
|
|
2479
|
-
] })
|
|
2480
|
-
] }),
|
|
2481
|
-
pAccount && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2482
|
-
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
2483
|
-
/* @__PURE__ */ jsx(Building2, { className: "h-3 w-3 shrink-0" }),
|
|
2484
|
-
/* @__PURE__ */ jsx("span", { children: pAccount.name })
|
|
2485
|
-
] }),
|
|
2486
|
-
(p.feeAmount ?? 0) > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2487
|
-
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
2488
|
-
/* @__PURE__ */ jsxs("span", { className: "text-warning", children: [
|
|
2489
|
-
t2("financial.statements.columnFee"),
|
|
2490
|
-
" ",
|
|
2491
|
-
formatCurrency(p.feeAmount ?? 0, currency)
|
|
2492
|
-
] })
|
|
2493
|
-
] }),
|
|
2494
|
-
p.paymentDate && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2495
|
-
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
2496
|
-
/* @__PURE__ */ jsx("span", { children: p.paymentDate })
|
|
2497
|
-
] }),
|
|
2498
|
-
p.notes && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2499
|
-
/* @__PURE__ */ jsx("span", { children: "\xB7" }),
|
|
2500
|
-
/* @__PURE__ */ jsx("span", { className: "italic truncate max-w-[200px]", children: p.notes })
|
|
2501
|
-
] })
|
|
2502
|
-
] }, p.id);
|
|
2503
|
-
}) }) }) })
|
|
2504
|
-
}
|
|
2505
|
-
)
|
|
2506
|
-
] }, mov.id);
|
|
2507
|
-
}) }) }),
|
|
2508
|
-
movements.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded-lg border bg-muted/20 px-4 py-2.5", children: [
|
|
2509
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4 text-xs", children: [
|
|
2510
|
-
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
2511
|
-
t2("financial.invoice.paidLabel"),
|
|
2512
|
-
" ",
|
|
2513
|
-
/* @__PURE__ */ jsx("span", { className: "font-semibold text-success", children: formatCurrency(totalPaid, currency) })
|
|
2514
|
-
] }),
|
|
2515
|
-
totalRemaining > 0 && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
2516
|
-
t2("financial.invoice.remaining"),
|
|
2517
|
-
" ",
|
|
2518
|
-
/* @__PURE__ */ jsx("span", { className: "font-semibold text-foreground", children: formatCurrency(totalRemaining, currency) })
|
|
2519
|
-
] })
|
|
2520
|
-
] }),
|
|
2521
|
-
totalRemaining <= 0 && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-xs font-medium text-success", children: [
|
|
2522
|
-
/* @__PURE__ */ jsx(CircleCheckBig, { className: "h-3 w-3" }),
|
|
2523
|
-
" ",
|
|
2524
|
-
t2("financial.invoice.fullyPaid")
|
|
2525
|
-
] })
|
|
2526
|
-
] })
|
|
2527
|
-
] }),
|
|
2528
2156
|
payingMovement && /* @__PURE__ */ jsx(
|
|
2529
2157
|
PaymentModal,
|
|
2530
2158
|
{
|
|
@@ -3289,6 +2917,7 @@ function StatementsView({ onNavigate }) {
|
|
|
3289
2917
|
return d.toISOString().slice(0, 10);
|
|
3290
2918
|
});
|
|
3291
2919
|
const [dateTo, setDateTo] = useState(() => (/* @__PURE__ */ new Date()).toISOString().slice(0, 10));
|
|
2920
|
+
const [search, setSearch] = useState("");
|
|
3292
2921
|
const [showTransfer, setShowTransfer] = useState(false);
|
|
3293
2922
|
useEffect(() => {
|
|
3294
2923
|
fetchBankAccounts();
|
|
@@ -3423,54 +3052,105 @@ function StatementsView({ onNavigate }) {
|
|
|
3423
3052
|
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-right block font-medium text-xs", children: fmt(row.original.runningBalance) })
|
|
3424
3053
|
}
|
|
3425
3054
|
], [currency, t2, methodName, onNavigate]);
|
|
3426
|
-
const entries =
|
|
3055
|
+
const entries = useMemo(() => {
|
|
3056
|
+
const all = statement?.entries ?? [];
|
|
3057
|
+
const term = search.trim().toLowerCase();
|
|
3058
|
+
if (!term) return all;
|
|
3059
|
+
return all.filter((e) => {
|
|
3060
|
+
const haystack = [e.invoice?.contactName, e.movement.notes, e.invoice?.fiscalNumber, methodName(e.movement)];
|
|
3061
|
+
return haystack.some((v) => v?.toLowerCase().includes(term));
|
|
3062
|
+
});
|
|
3063
|
+
}, [statement, search, methodName]);
|
|
3064
|
+
const exportEntries = () => {
|
|
3065
|
+
exportCSV(
|
|
3066
|
+
[
|
|
3067
|
+
{ key: "date", label: t2("financial.statements.columnDate") },
|
|
3068
|
+
{ key: "description", label: t2("financial.statements.columnDescription") },
|
|
3069
|
+
{ key: "method", label: t2("financial.statements.columnMethod") },
|
|
3070
|
+
{ key: "debit", label: t2("financial.statements.columnDebit") },
|
|
3071
|
+
{ key: "credit", label: t2("financial.statements.columnCredit") },
|
|
3072
|
+
{ key: "balance", label: t2("financial.statements.columnBalance") }
|
|
3073
|
+
],
|
|
3074
|
+
entries.map((e) => ({
|
|
3075
|
+
date: e.movement.paymentDate ?? e.movement.dueDate,
|
|
3076
|
+
description: e.invoice?.contactName ?? e.movement.notes ?? "",
|
|
3077
|
+
method: methodName(e.movement),
|
|
3078
|
+
debit: e.movement.direction === "debit" ? fmt(e.movement.amount) : "",
|
|
3079
|
+
credit: e.movement.direction === "credit" ? fmt(e.movement.amount) : "",
|
|
3080
|
+
balance: fmt(e.runningBalance)
|
|
3081
|
+
})),
|
|
3082
|
+
"extrato"
|
|
3083
|
+
);
|
|
3084
|
+
};
|
|
3427
3085
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
3428
|
-
/* @__PURE__ */ jsx(
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
subtitle: t2("financial.statements.subtitle"),
|
|
3433
|
-
actions: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => setShowTransfer(true), disabled: bankAccounts.length < 2, children: [
|
|
3434
|
-
/* @__PURE__ */ jsx(ArrowRightLeft, { className: "h-3.5 w-3.5" }),
|
|
3435
|
-
t2("financial.statements.newTransfer")
|
|
3436
|
-
] })
|
|
3437
|
-
}
|
|
3438
|
-
),
|
|
3439
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row gap-3 sm:items-end", children: [
|
|
3440
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1 max-w-xs", children: [
|
|
3441
|
-
/* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium text-muted-foreground uppercase", children: t2("financial.statements.account") }),
|
|
3442
|
-
/* @__PURE__ */ jsxs(
|
|
3443
|
-
"select",
|
|
3444
|
-
{
|
|
3445
|
-
value: accountId,
|
|
3446
|
-
onChange: (e) => setAccountId(e.target.value),
|
|
3447
|
-
className: "w-full mt-0.5 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm",
|
|
3448
|
-
children: [
|
|
3449
|
-
/* @__PURE__ */ jsx("option", { value: ALL, children: t2("financial.summary.allAccounts") }),
|
|
3450
|
-
bankAccounts.map((a) => /* @__PURE__ */ jsx("option", { value: a.id, children: a.name }, a.id))
|
|
3451
|
-
]
|
|
3452
|
-
}
|
|
3453
|
-
)
|
|
3454
|
-
] }),
|
|
3455
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
3456
|
-
/* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium text-muted-foreground uppercase", children: t2("financial.statements.from") }),
|
|
3457
|
-
/* @__PURE__ */ jsx(DatePicker, { value: dateFrom, onChange: setDateFrom, className: "mt-0.5" })
|
|
3458
|
-
] }),
|
|
3459
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
3460
|
-
/* @__PURE__ */ jsx("label", { className: "text-[10px] font-medium text-muted-foreground uppercase", children: t2("financial.statements.to") }),
|
|
3461
|
-
/* @__PURE__ */ jsx(DatePicker, { value: dateTo, onChange: setDateTo, className: "mt-0.5" })
|
|
3462
|
-
] })
|
|
3463
|
-
] }),
|
|
3086
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center justify-end gap-3", children: /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => setShowTransfer(true), disabled: bankAccounts.length < 2, children: [
|
|
3087
|
+
/* @__PURE__ */ jsx(ArrowRightLeft, { className: "h-3.5 w-3.5" }),
|
|
3088
|
+
t2("financial.statements.newTransfer")
|
|
3089
|
+
] }) }),
|
|
3464
3090
|
accountId && statement && /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-2", children: [
|
|
3465
3091
|
/* @__PURE__ */ jsx(SummaryStat, { label: t2("financial.statements.opening"), value: fmt(statement.openingBalance) }),
|
|
3466
3092
|
/* @__PURE__ */ jsx(SummaryStat, { label: t2("financial.statements.totalCredits"), value: fmt(statement.totalCredits), positive: true }),
|
|
3467
3093
|
/* @__PURE__ */ jsx(SummaryStat, { label: t2("financial.statements.totalDebits"), value: fmt(statement.totalDebits), negative: true }),
|
|
3468
3094
|
/* @__PURE__ */ jsx(SummaryStat, { label: t2("financial.statements.closing"), value: fmt(statement.closingBalance), accent: true })
|
|
3469
3095
|
] }),
|
|
3470
|
-
|
|
3471
|
-
/* @__PURE__ */
|
|
3472
|
-
|
|
3473
|
-
|
|
3096
|
+
/* @__PURE__ */ jsxs(Card, { className: "overflow-hidden", children: [
|
|
3097
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b border-border px-3 py-2", children: [
|
|
3098
|
+
/* @__PURE__ */ jsxs("div", { className: "relative min-w-0 flex-1", children: [
|
|
3099
|
+
/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }),
|
|
3100
|
+
/* @__PURE__ */ jsx(
|
|
3101
|
+
"input",
|
|
3102
|
+
{
|
|
3103
|
+
type: "text",
|
|
3104
|
+
value: search,
|
|
3105
|
+
onChange: (e) => setSearch(e.target.value),
|
|
3106
|
+
placeholder: t2("financial.statements.searchPlaceholder"),
|
|
3107
|
+
className: "w-full bg-transparent py-1.5 pl-8 pr-3 text-sm placeholder:text-muted-foreground focus:outline-none"
|
|
3108
|
+
}
|
|
3109
|
+
)
|
|
3110
|
+
] }),
|
|
3111
|
+
/* @__PURE__ */ jsxs("div", { className: "ml-auto flex flex-wrap items-center justify-end gap-2", children: [
|
|
3112
|
+
/* @__PURE__ */ jsx(
|
|
3113
|
+
FilterChip,
|
|
3114
|
+
{
|
|
3115
|
+
icon: Landmark,
|
|
3116
|
+
label: t2("financial.statements.account"),
|
|
3117
|
+
allLabel: t2("financial.summary.allAccounts"),
|
|
3118
|
+
value: accountId && accountId !== ALL ? accountId : void 0,
|
|
3119
|
+
onChange: (next) => setAccountId(next ?? ALL),
|
|
3120
|
+
options: bankAccounts.map((a) => ({ value: a.id, label: a.name }))
|
|
3121
|
+
}
|
|
3122
|
+
),
|
|
3123
|
+
/* @__PURE__ */ jsx(DatePicker, { value: dateFrom, onChange: setDateFrom, variant: "chip" }),
|
|
3124
|
+
/* @__PURE__ */ jsx(DatePicker, { value: dateTo, onChange: setDateTo, variant: "chip" }),
|
|
3125
|
+
entries.length > 0 && /* @__PURE__ */ jsx(
|
|
3126
|
+
"button",
|
|
3127
|
+
{
|
|
3128
|
+
type: "button",
|
|
3129
|
+
onClick: exportEntries,
|
|
3130
|
+
"aria-label": t2("crud.export.action"),
|
|
3131
|
+
title: t2("crud.export.action"),
|
|
3132
|
+
className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
3133
|
+
children: /* @__PURE__ */ jsx(Download, { className: "h-4 w-4" })
|
|
3134
|
+
}
|
|
3135
|
+
)
|
|
3136
|
+
] })
|
|
3137
|
+
] }),
|
|
3138
|
+
/* @__PURE__ */ jsx(
|
|
3139
|
+
DataTable,
|
|
3140
|
+
{
|
|
3141
|
+
variant: "bare",
|
|
3142
|
+
compact: true,
|
|
3143
|
+
columns,
|
|
3144
|
+
data: statementLoading ? [] : entries,
|
|
3145
|
+
loading: statementLoading,
|
|
3146
|
+
skeletonRows: statementLoading ? 8 : void 0,
|
|
3147
|
+
emptyMessage: /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-1 py-6", children: [
|
|
3148
|
+
/* @__PURE__ */ jsx(Receipt, { className: "mb-2 h-9 w-9 text-muted-foreground/40" }),
|
|
3149
|
+
/* @__PURE__ */ jsx("p", { className: "text-base font-semibold text-foreground", children: accountId ? t2("financial.statements.noTransactions") : t2("financial.statements.selectToView") })
|
|
3150
|
+
] })
|
|
3151
|
+
}
|
|
3152
|
+
)
|
|
3153
|
+
] }),
|
|
3474
3154
|
showTransfer && /* @__PURE__ */ jsx(
|
|
3475
3155
|
TransferModal,
|
|
3476
3156
|
{
|
|
@@ -4227,6 +3907,8 @@ function FinancialPage({ config, provider, store, registries }) {
|
|
|
4227
3907
|
) }) }) });
|
|
4228
3908
|
}
|
|
4229
3909
|
function renderView() {
|
|
3910
|
+
const off = intent.view === "commissions" && !config.modules.commissions || intent.view === "cards" && !config.modules.cards || intent.view === "cash-registers" && !config.modules.cashRegisters || intent.view === "reconciliation" && !config.modules.reconciliation || intent.view === "notas-fiscais" && !config.modules.notasFiscais;
|
|
3911
|
+
if (off) return /* @__PURE__ */ jsx(SummaryView, {});
|
|
4230
3912
|
switch (intent.view) {
|
|
4231
3913
|
case "payables":
|
|
4232
3914
|
return /* @__PURE__ */ jsx(PayablesView, { intent, onNavigate: navigate, previousView, back });
|
|
@@ -4259,6 +3941,7 @@ function FinancialPage({ config, provider, store, registries }) {
|
|
|
4259
3941
|
title: config.labels.pageTitle,
|
|
4260
3942
|
subtitle: config.labels.pageSubtitle,
|
|
4261
3943
|
nav,
|
|
3944
|
+
navVariant: config.moduleNav,
|
|
4262
3945
|
showHeader: intent.view === "summary" || view === "summary",
|
|
4263
3946
|
viewKey: view,
|
|
4264
3947
|
direction,
|
|
@@ -6190,6 +5873,143 @@ function createSupabaseFinancialProvider() {
|
|
|
6190
5873
|
};
|
|
6191
5874
|
return provider;
|
|
6192
5875
|
}
|
|
5876
|
+
|
|
5877
|
+
// src/lib/message-events.ts
|
|
5878
|
+
var INVOICE_FIELDS = [
|
|
5879
|
+
{ name: "customer_name", label: "Cliente", type: "string", example: "Maria Silva" },
|
|
5880
|
+
{ name: "invoice_number", label: "N\xFAmero", type: "string", example: "0042" },
|
|
5881
|
+
{ name: "invoice_total", label: "Valor", type: "money", example: "R$ 1.250,00" },
|
|
5882
|
+
{ name: "due_date", label: "Vencimento", type: "date", example: "20/08/2026" },
|
|
5883
|
+
{ name: "invoice_url", label: "Link da fatura", type: "url", example: "https://sua.app/faturas/0042" },
|
|
5884
|
+
{ name: "business_name", label: "Seu neg\xF3cio", type: "string", example: "Seu Neg\xF3cio" }
|
|
5885
|
+
];
|
|
5886
|
+
function buildFinancialDomainEvents() {
|
|
5887
|
+
return [
|
|
5888
|
+
{
|
|
5889
|
+
name: "financial.invoice_created",
|
|
5890
|
+
title: "Fatura emitida",
|
|
5891
|
+
icon: "FilePlus2",
|
|
5892
|
+
audit: true,
|
|
5893
|
+
description: "Uma cobran\xE7a foi gerada",
|
|
5894
|
+
trigger: "Quando a fatura \xE9 emitida",
|
|
5895
|
+
fields: INVOICE_FIELDS
|
|
5896
|
+
},
|
|
5897
|
+
{
|
|
5898
|
+
name: "financial.invoice_due_soon",
|
|
5899
|
+
title: "Fatura a vencer",
|
|
5900
|
+
icon: "CalendarClock",
|
|
5901
|
+
audit: false,
|
|
5902
|
+
description: "O vencimento est\xE1 pr\xF3ximo",
|
|
5903
|
+
trigger: "Nos dias que antecedem o vencimento",
|
|
5904
|
+
fields: INVOICE_FIELDS
|
|
5905
|
+
},
|
|
5906
|
+
{
|
|
5907
|
+
name: "financial.invoice_paid",
|
|
5908
|
+
title: "Fatura paga",
|
|
5909
|
+
icon: "CircleCheck",
|
|
5910
|
+
audit: true,
|
|
5911
|
+
description: "O pagamento foi recebido",
|
|
5912
|
+
trigger: "Quando a fatura \xE9 baixada como paga",
|
|
5913
|
+
fields: INVOICE_FIELDS
|
|
5914
|
+
},
|
|
5915
|
+
{
|
|
5916
|
+
name: "financial.invoice_overdue",
|
|
5917
|
+
title: "Fatura vencida",
|
|
5918
|
+
icon: "AlertTriangle",
|
|
5919
|
+
audit: true,
|
|
5920
|
+
description: "A cobran\xE7a passou do vencimento",
|
|
5921
|
+
trigger: "Quando a fatura vence sem pagamento",
|
|
5922
|
+
fields: INVOICE_FIELDS
|
|
5923
|
+
},
|
|
5924
|
+
{
|
|
5925
|
+
name: "financial.invoice_cancelled",
|
|
5926
|
+
title: "Fatura cancelada",
|
|
5927
|
+
icon: "FileX2",
|
|
5928
|
+
audit: true,
|
|
5929
|
+
description: "A cobran\xE7a foi cancelada",
|
|
5930
|
+
trigger: "Quando a fatura \xE9 cancelada",
|
|
5931
|
+
fields: INVOICE_FIELDS
|
|
5932
|
+
}
|
|
5933
|
+
];
|
|
5934
|
+
}
|
|
5935
|
+
function buildFinancialMessageDefaults() {
|
|
5936
|
+
return [
|
|
5937
|
+
{
|
|
5938
|
+
eventKey: "financial.invoice_created",
|
|
5939
|
+
kind: "transactional",
|
|
5940
|
+
channels: ["email", "whatsapp"],
|
|
5941
|
+
defaults: {
|
|
5942
|
+
email: {
|
|
5943
|
+
subject: "Fatura {{invoice_number}} \u2014 {{invoice_total}}",
|
|
5944
|
+
body: "Ol\xE1 {{customer_name}},\n\nSegue a fatura {{invoice_number}}, no valor de {{invoice_total}}, com vencimento em {{due_date}}.\n\n{{invoice_url}}\n\nQualquer d\xFAvida, \xE9 s\xF3 responder este e-mail.\n\n{{business_name}}"
|
|
5945
|
+
},
|
|
5946
|
+
whatsapp: {
|
|
5947
|
+
body: "Oi {{customer_name}}! Segue a fatura {{invoice_number}} \u{1F4C4}\n\nValor: {{invoice_total}}\nVencimento: {{due_date}}\n{{invoice_url}}\n\n\u2014 {{business_name}}"
|
|
5948
|
+
}
|
|
5949
|
+
},
|
|
5950
|
+
defaultEnabled: { email: true }
|
|
5951
|
+
},
|
|
5952
|
+
{
|
|
5953
|
+
eventKey: "financial.invoice_due_soon",
|
|
5954
|
+
kind: "transactional",
|
|
5955
|
+
channels: ["whatsapp", "email"],
|
|
5956
|
+
defaults: {
|
|
5957
|
+
whatsapp: {
|
|
5958
|
+
body: "Oi {{customer_name}}! Lembrete tranquilo: a fatura {{invoice_number}} ({{invoice_total}}) vence em {{due_date}}.\n\n{{invoice_url}}\n\n\u2014 {{business_name}}"
|
|
5959
|
+
},
|
|
5960
|
+
email: {
|
|
5961
|
+
subject: "Sua fatura {{invoice_number}} vence em {{due_date}}",
|
|
5962
|
+
body: "Ol\xE1 {{customer_name}},\n\nUm lembrete: a fatura {{invoice_number}}, de {{invoice_total}}, vence em {{due_date}}.\n\n{{invoice_url}}\n\nSe j\xE1 pagou, pode desconsiderar.\n\n{{business_name}}"
|
|
5963
|
+
}
|
|
5964
|
+
},
|
|
5965
|
+
defaultEnabled: { email: true }
|
|
5966
|
+
},
|
|
5967
|
+
{
|
|
5968
|
+
eventKey: "financial.invoice_paid",
|
|
5969
|
+
kind: "transactional",
|
|
5970
|
+
channels: ["email", "whatsapp"],
|
|
5971
|
+
defaults: {
|
|
5972
|
+
email: {
|
|
5973
|
+
subject: "Recebemos o pagamento da fatura {{invoice_number}}",
|
|
5974
|
+
body: "Ol\xE1 {{customer_name}},\n\nConfirmamos o recebimento de {{invoice_total}} referente \xE0 fatura {{invoice_number}}.\n\nObrigado!\n\n{{business_name}}"
|
|
5975
|
+
},
|
|
5976
|
+
whatsapp: {
|
|
5977
|
+
body: "Oi {{customer_name}}! Pagamento da fatura {{invoice_number}} confirmado \u2705\n\nObrigado! \u2014 {{business_name}}"
|
|
5978
|
+
}
|
|
5979
|
+
},
|
|
5980
|
+
// Confirmar recebimento é o mínimo: sem isso o cliente fica sem saber se
|
|
5981
|
+
// o dinheiro chegou, e a próxima mensagem dele é uma pergunta.
|
|
5982
|
+
defaultEnabled: { email: true }
|
|
5983
|
+
},
|
|
5984
|
+
{
|
|
5985
|
+
eventKey: "financial.invoice_overdue",
|
|
5986
|
+
kind: "transactional",
|
|
5987
|
+
channels: ["whatsapp", "email"],
|
|
5988
|
+
defaults: {
|
|
5989
|
+
whatsapp: {
|
|
5990
|
+
body: "Oi {{customer_name}}, tudo bem?\n\nA fatura {{invoice_number}} ({{invoice_total}}) venceu em {{due_date}} e consta em aberto por aqui.\n\n{{invoice_url}}\n\nSe precisar de outra data, me avisa que a gente resolve. \u2014 {{business_name}}"
|
|
5991
|
+
},
|
|
5992
|
+
email: {
|
|
5993
|
+
subject: "Fatura {{invoice_number}} em aberto",
|
|
5994
|
+
body: "Ol\xE1 {{customer_name}},\n\nA fatura {{invoice_number}}, de {{invoice_total}}, venceu em {{due_date}} e consta em aberto.\n\n{{invoice_url}}\n\nSe j\xE1 efetuou o pagamento, desconsidere este aviso. Se precisar de outra data, \xE9 s\xF3 responder.\n\n{{business_name}}"
|
|
5995
|
+
}
|
|
5996
|
+
}
|
|
5997
|
+
// Cobrança de atraso NÃO nasce ligada: a primeira mensagem que uma conta
|
|
5998
|
+
// nova manda sozinha não pode ser uma cobrança escrita por outra pessoa.
|
|
5999
|
+
},
|
|
6000
|
+
{
|
|
6001
|
+
eventKey: "financial.invoice_cancelled",
|
|
6002
|
+
kind: "transactional",
|
|
6003
|
+
channels: ["email"],
|
|
6004
|
+
defaults: {
|
|
6005
|
+
email: {
|
|
6006
|
+
subject: "Fatura {{invoice_number}} cancelada",
|
|
6007
|
+
body: "Ol\xE1 {{customer_name}},\n\nA fatura {{invoice_number}}, de {{invoice_total}}, foi cancelada e n\xE3o precisa ser paga.\n\n{{business_name}}"
|
|
6008
|
+
}
|
|
6009
|
+
}
|
|
6010
|
+
}
|
|
6011
|
+
];
|
|
6012
|
+
}
|
|
6193
6013
|
function createFinancialStore(provider) {
|
|
6194
6014
|
return createStore$1((set, get) => ({
|
|
6195
6015
|
invoices: [],
|
|
@@ -6420,7 +6240,7 @@ function createFinancialStore(provider) {
|
|
|
6420
6240
|
}));
|
|
6421
6241
|
}
|
|
6422
6242
|
|
|
6423
|
-
// src/registries.ts
|
|
6243
|
+
// src/data/registries.ts
|
|
6424
6244
|
var paymentMethodTypeEntity = {
|
|
6425
6245
|
name: "Payment Type",
|
|
6426
6246
|
namePlural: "Payment Types",
|
|
@@ -6762,6 +6582,10 @@ var en = {
|
|
|
6762
6582
|
"financial.invoice.paidOn": "Paid on:",
|
|
6763
6583
|
"financial.invoice.pay": "Pay",
|
|
6764
6584
|
"financial.invoice.paymentSchedule": "Payment Schedule",
|
|
6585
|
+
"financial.invoiceForm.scheduleLocked": "Some installments are paid \u2014 the schedule can no longer be regenerated.",
|
|
6586
|
+
"financial.invoice.payment": "Payment",
|
|
6587
|
+
"financial.invoice.balance": "Remaining",
|
|
6588
|
+
"financial.invoice.details": "Details",
|
|
6765
6589
|
"financial.invoice.remaining": "Remaining:",
|
|
6766
6590
|
"financial.invoice.searchPlaceholder": "Search by contact or description...",
|
|
6767
6591
|
"financial.invoice.showing": "Showing {{shown}} of {{total}}",
|
|
@@ -6781,6 +6605,8 @@ var en = {
|
|
|
6781
6605
|
"financial.invoiceForm.discountSurcharge": "Discount / Surcharge",
|
|
6782
6606
|
"financial.invoiceForm.done": "Done",
|
|
6783
6607
|
"financial.invoiceForm.editing": "Editing invoice",
|
|
6608
|
+
"financial.invoiceForm.editTitle": "Edit",
|
|
6609
|
+
"financial.invoiceForm.newTitle": "New",
|
|
6784
6610
|
"financial.invoiceForm.fiscalNumber": "Fiscal Number (optional)",
|
|
6785
6611
|
"financial.invoiceForm.headquarters": "Headquarters",
|
|
6786
6612
|
"financial.invoiceForm.installments": "Installments",
|
|
@@ -6917,6 +6743,7 @@ var en = {
|
|
|
6917
6743
|
"financial.statements.from": "From",
|
|
6918
6744
|
"financial.statements.noTransactions": "No transactions in this period",
|
|
6919
6745
|
"financial.statements.selectAccount": "Select an account to view statement",
|
|
6746
|
+
"financial.statements.searchPlaceholder": "Search by contact, description or document...",
|
|
6920
6747
|
"financial.statements.selectToView": "Select an account to view statement",
|
|
6921
6748
|
"financial.statements.subtitle": "Bank account transactions and balances",
|
|
6922
6749
|
"financial.statements.title": "Statements",
|
|
@@ -7256,6 +7083,10 @@ var ptBR = {
|
|
|
7256
7083
|
"financial.invoice.paidOn": "Pago em:",
|
|
7257
7084
|
"financial.invoice.pay": "Pagar",
|
|
7258
7085
|
"financial.invoice.paymentSchedule": "Cronograma de Pagamento",
|
|
7086
|
+
"financial.invoiceForm.scheduleLocked": "Parcelas j\xE1 pagas \u2014 o parcelamento n\xE3o pode mais ser refeito.",
|
|
7087
|
+
"financial.invoice.payment": "Pagamento",
|
|
7088
|
+
"financial.invoice.balance": "Restante",
|
|
7089
|
+
"financial.invoice.details": "Detalhes",
|
|
7259
7090
|
"financial.invoice.remaining": "Restante:",
|
|
7260
7091
|
"financial.invoice.searchPlaceholder": "Buscar por contato ou descri\xE7\xE3o...",
|
|
7261
7092
|
"financial.invoice.showing": "Exibindo {{shown}} de {{total}}",
|
|
@@ -7275,6 +7106,11 @@ var ptBR = {
|
|
|
7275
7106
|
"financial.invoiceForm.discountSurcharge": "Desconto / Acr\xE9scimo",
|
|
7276
7107
|
"financial.invoiceForm.done": "Pronto",
|
|
7277
7108
|
"financial.invoiceForm.editing": "Editando fatura",
|
|
7109
|
+
// Título da migalha ("← Mensalidades / Nova"). Genérico de propósito: o nome
|
|
7110
|
+
// do documento é do app (Mensalidades, Contas a Receber), e repeti-lo aqui
|
|
7111
|
+
// escreveria "fatura" numa tela que o app chama de outra coisa.
|
|
7112
|
+
"financial.invoiceForm.editTitle": "Editar",
|
|
7113
|
+
"financial.invoiceForm.newTitle": "Nova",
|
|
7278
7114
|
"financial.invoiceForm.fiscalNumber": "N\xFAmero Fiscal (opcional)",
|
|
7279
7115
|
"financial.invoiceForm.headquarters": "Sede",
|
|
7280
7116
|
"financial.invoiceForm.installments": "Parcelas",
|
|
@@ -7411,6 +7247,7 @@ var ptBR = {
|
|
|
7411
7247
|
"financial.statements.from": "De",
|
|
7412
7248
|
"financial.statements.noTransactions": "Sem transa\xE7\xF5es neste per\xEDodo",
|
|
7413
7249
|
"financial.statements.selectAccount": "Selecione uma conta para ver o extrato",
|
|
7250
|
+
"financial.statements.searchPlaceholder": "Buscar por contato, descri\xE7\xE3o ou documento...",
|
|
7414
7251
|
"financial.statements.selectToView": "Selecione uma conta para ver o extrato",
|
|
7415
7252
|
"financial.statements.subtitle": "Transa\xE7\xF5es e saldos de contas banc\xE1rias",
|
|
7416
7253
|
"financial.statements.title": "Extratos",
|
|
@@ -7844,6 +7681,7 @@ function resolveConfig(options) {
|
|
|
7844
7681
|
function createFinancialPlugin(options) {
|
|
7845
7682
|
const config = resolveConfig(options);
|
|
7846
7683
|
registerTranslations(financialLocales);
|
|
7684
|
+
registerMessageDefaults(buildFinancialMessageDefaults());
|
|
7847
7685
|
const provider = options?.dataProvider ?? createSafeFinancialProvider();
|
|
7848
7686
|
const store = createFinancialStore(provider);
|
|
7849
7687
|
const dashboardWidgets = createFinancialDashboardWidgets({ config, provider, store });
|
|
@@ -7860,6 +7698,8 @@ function createFinancialPlugin(options) {
|
|
|
7860
7698
|
verticalId: options?.verticalId,
|
|
7861
7699
|
defaultEnabled: true,
|
|
7862
7700
|
dependencies: [],
|
|
7701
|
+
// Os FATOS do financeiro — só expostos enquanto o módulo está ATIVO.
|
|
7702
|
+
events: buildFinancialDomainEvents(),
|
|
7863
7703
|
declaredFeatures: [
|
|
7864
7704
|
{ id: "financial", label: config.labels.pageTitle, group: config.labels.pageTitle },
|
|
7865
7705
|
...config.modules.receivables ? [{ id: "financial.receivables", label: config.labels.receivables, group: config.labels.pageTitle }] : [],
|
|
@@ -8073,6 +7913,6 @@ function createFinancialPlugin(options) {
|
|
|
8073
7913
|
};
|
|
8074
7914
|
}
|
|
8075
7915
|
|
|
8076
|
-
export { FINANCIAL_NAV_FEATURES, createFinancialPlugin, createMockFinancialProvider, createSafeFinancialProvider, openQuickAdd };
|
|
7916
|
+
export { FINANCIAL_NAV_FEATURES, buildFinancialDomainEvents, buildFinancialMessageDefaults, createFinancialPlugin, createMockFinancialProvider, createSafeFinancialProvider, openQuickAdd };
|
|
8077
7917
|
//# sourceMappingURL=index.js.map
|
|
8078
7918
|
//# sourceMappingURL=index.js.map
|