@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.
Files changed (91) hide show
  1. package/README.md +3 -3
  2. package/dist/components/PersonStatementWidget.d.ts +1 -1
  3. package/dist/components/PersonStatementWidget.d.ts.map +1 -1
  4. package/dist/{FinancialContext.d.ts → context.d.ts} +4 -2
  5. package/dist/context.d.ts.map +1 -0
  6. package/dist/data/registries.d.ts.map +1 -0
  7. package/dist/index.d.ts +10 -4
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1208 -1368
  10. package/dist/index.js.map +1 -1
  11. package/dist/lib/message-events.d.ts +12 -0
  12. package/dist/lib/message-events.d.ts.map +1 -0
  13. package/dist/{quick-add.d.ts → lib/quick-add.d.ts} +1 -1
  14. package/dist/lib/quick-add.d.ts.map +1 -0
  15. package/dist/locales/en.d.ts.map +1 -1
  16. package/dist/locales/pt-BR.d.ts.map +1 -1
  17. package/dist/{FinancialPage.d.ts → views/FinancialPage.d.ts} +3 -3
  18. package/dist/views/FinancialPage.d.ts.map +1 -0
  19. package/dist/views/InvoiceDetailView.d.ts.map +1 -1
  20. package/dist/views/InvoiceFormView.d.ts.map +1 -1
  21. package/dist/views/InvoiceListView.d.ts.map +1 -1
  22. package/dist/views/PayablesView.d.ts +1 -1
  23. package/dist/views/PayablesView.d.ts.map +1 -1
  24. package/dist/views/ReceivablesView.d.ts +1 -1
  25. package/dist/views/ReceivablesView.d.ts.map +1 -1
  26. package/dist/views/StatementsView.d.ts.map +1 -1
  27. package/dist/views/dashboardWidgets.d.ts +1 -1
  28. package/dist/views/dashboardWidgets.d.ts.map +1 -1
  29. package/package.json +12 -14
  30. package/dist/FinancialContext.d.ts.map +0 -1
  31. package/dist/FinancialPage.d.ts.map +0 -1
  32. package/dist/contract.test.d.ts +0 -2
  33. package/dist/contract.test.d.ts.map +0 -1
  34. package/dist/quick-add.d.ts.map +0 -1
  35. package/dist/registries.d.ts.map +0 -1
  36. package/src/FinancialContext.tsx +0 -59
  37. package/src/FinancialPage.tsx +0 -413
  38. package/src/README.md +0 -155
  39. package/src/components/FinancialGeneralSettings.tsx +0 -39
  40. package/src/components/FinancialOnboarding.tsx +0 -87
  41. package/src/components/FinancialSettings.tsx +0 -31
  42. package/src/components/PaymentModal.tsx +0 -369
  43. package/src/components/PersonStatementWidget.tsx +0 -133
  44. package/src/components/TransferModal.tsx +0 -150
  45. package/src/contract.test.ts +0 -58
  46. package/src/data/capability.test.ts +0 -135
  47. package/src/data/index.ts +0 -3
  48. package/src/data/mock.ts +0 -842
  49. package/src/data/supabase.ts +0 -968
  50. package/src/data/tables.ts +0 -13
  51. package/src/data/types.ts +0 -86
  52. package/src/index.ts +0 -487
  53. package/src/lib/tenant.ts +0 -3
  54. package/src/locales/en.ts +0 -493
  55. package/src/locales/index.ts +0 -7
  56. package/src/locales/pt-BR.ts +0 -493
  57. package/src/migrations/000_plg_rename.sql +0 -30
  58. package/src/migrations/001_financial_base.sql +0 -109
  59. package/src/migrations/002_chart_of_accounts.sql +0 -28
  60. package/src/migrations/003_card_brands.sql +0 -12
  61. package/src/migrations/004_order_to_cash.sql +0 -178
  62. package/src/migrations/005_seed_defaults.sql +0 -38
  63. package/src/migrations/006_rls_policies.sql +0 -37
  64. package/src/migrations/006b_extract_fee_amount.sql +0 -20
  65. package/src/migrations/007_reconciliation.sql +0 -32
  66. package/src/migrations/007b_movement_payment_method_type.sql +0 -6
  67. package/src/migrations/008_split_payment_movements.sql +0 -29
  68. package/src/migrations/009_agent_rpcs.sql +0 -165
  69. package/src/migrations/010_invoice_internal.sql +0 -133
  70. package/src/migrations/011_nfe.sql +0 -58
  71. package/src/migrations/index.ts +0 -923
  72. package/src/quick-add.ts +0 -58
  73. package/src/registries.ts +0 -308
  74. package/src/store.ts +0 -329
  75. package/src/types.ts +0 -592
  76. package/src/views/CardsView.tsx +0 -213
  77. package/src/views/CashRegistersView.tsx +0 -597
  78. package/src/views/CommissionsView.tsx +0 -18
  79. package/src/views/InvoiceDetailView.tsx +0 -616
  80. package/src/views/InvoiceFormView.tsx +0 -945
  81. package/src/views/InvoiceListView.tsx +0 -323
  82. package/src/views/NotasFiscaisView.tsx +0 -209
  83. package/src/views/PayablesView.tsx +0 -48
  84. package/src/views/QuickTransactionForm.tsx +0 -548
  85. package/src/views/ReceivablesView.tsx +0 -48
  86. package/src/views/ReconciliationView.tsx +0 -211
  87. package/src/views/StatementsView.tsx +0 -335
  88. package/src/views/SummaryView.tsx +0 -203
  89. package/src/views/dashboardWidgets.tsx +0 -437
  90. package/src/views/navReferrer.ts +0 -24
  91. /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, Card, 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 } from '@fayz-ai/saas';
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, LayoutList, CircleDashed, CircleAlert, CircleCheckBig, CircleEllipsis, Pencil, User, Calendar, Hash, Building2, ShoppingCart, Car, Home, Utensils, Zap, Film, HeartPulse, Dumbbell, Music, ShoppingBag, CircleDollarSign, Search, Trash2, FileCheck, QrCode, Banknote, ArrowUpRight, ArrowDownRight, AlertTriangle, EyeOff, Eye } from 'lucide-react';
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
- return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
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
- SubpageHeader,
831
+ FilterChip,
815
832
  {
816
- title: listTitle,
817
- subtitle: t2("financial.invoice.invoices", { count: filtered.length })
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("div", { className: "flex flex-wrap items-end gap-3 rounded-lg border bg-muted/20 px-4 py-3", children: [
821
- /* @__PURE__ */ jsxs("label", { className: "flex min-w-[180px] flex-col gap-1 text-xs font-medium text-muted-foreground", children: [
822
- t2("financial.invoice.filterPeriod"),
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
- document.body
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__ */ jsxs("div", { className: "space-y-5", children: [
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
- const StatusIcon = statusConfig.icon;
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 itemColumns = [
2206
- {
2207
- id: "index",
2208
- header: t2("financial.invoice.columnNumber"),
2209
- cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: row.index + 1 })
2210
- },
2211
- {
2212
- accessorKey: "description",
2213
- header: t2("financial.invoice.columnDescription"),
2214
- cell: ({ row }) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
2215
- /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded px-1.5 py-0.5 text-[9px] font-medium bg-muted text-muted-foreground capitalize", children: row.original.itemKind }),
2216
- /* @__PURE__ */ jsx("span", { className: "truncate", children: row.original.description })
2217
- ] })
2218
- },
2219
- {
2220
- accessorKey: "quantity",
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
- return /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
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
- SubpageHeader,
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: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
2243
- invoice.status !== "cancelled" && invoice.status !== "paid" && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: onEdit, children: [
2244
- /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" }),
2245
- " ",
2246
- t2("financial.invoice.edit")
2247
- ] }),
2248
- invoice.status !== "cancelled" && invoice.status !== "paid" && /* @__PURE__ */ jsxs("div", { className: "relative", ref: menuRef, children: [
2249
- /* @__PURE__ */ jsx(Button, { variant: "outline", size: "icon", onClick: () => setMenuOpen(!menuOpen), "aria-label": "More actions", children: /* @__PURE__ */ jsx(MoreVertical, { className: "h-4 w-4" }) }),
2250
- 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(
2251
- "button",
2252
- {
2253
- onClick: () => {
2254
- setConfirmCancel(true);
2255
- setMenuOpen(false);
2256
- },
2257
- className: "flex w-full items-center gap-2 px-3 py-2 text-xs text-destructive hover:bg-destructive/10 transition-colors",
2258
- children: [
2259
- /* @__PURE__ */ jsx(Ban, { className: "h-3.5 w-3.5" }),
2260
- " ",
2261
- t2("financial.invoice.cancelInvoice")
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 = statement?.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
- SubpageHeader,
3430
- {
3431
- title: t2("financial.statements.title"),
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
- !accountId ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-12 text-center rounded-lg border-2 border-dashed border-muted", children: [
3471
- /* @__PURE__ */ jsx(Receipt, { className: "h-8 w-8 text-muted-foreground mb-2" }),
3472
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t2("financial.statements.selectToView") })
3473
- ] }) : statementLoading ? /* @__PURE__ */ jsx(DataTable, { columns, data: [], loading: true, skeletonRows: 8, compact: true }) : entries.length === 0 ? /* @__PURE__ */ jsx(DataTable, { columns, data: [], emptyMessage: t2("financial.statements.noTransactions"), compact: true }) : /* @__PURE__ */ jsx(DataTable, { columns, data: entries, compact: true }),
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