@snksergio/design-system 0.61.0 → 0.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/dist-lib/ai/blocos/chart/budget-breakdown.tsx +174 -0
  2. package/dist-lib/ai/blocos/indice.md +43 -0
  3. package/dist-lib/ai/blocos/paneldetail/detalhe-com-tabela.tsx +450 -0
  4. package/dist-lib/ai/blocos/paneldetail/detalhe-de-tarefa-com-abas.tsx +570 -0
  5. package/dist-lib/ai/blocos/paneldetail/detalhe-do-registro.tsx +555 -0
  6. package/dist-lib/ai/componentes/AlertModal.md +47 -0
  7. package/dist-lib/ai/componentes/AppShell.md +117 -0
  8. package/dist-lib/ai/componentes/Breadcrumb.md +187 -0
  9. package/dist-lib/ai/componentes/Button.md +116 -0
  10. package/dist-lib/ai/componentes/ButtonGroup.md +162 -0
  11. package/dist-lib/ai/componentes/CardCheckbox.md +99 -0
  12. package/dist-lib/ai/componentes/CardOption.md +133 -0
  13. package/dist-lib/ai/componentes/Chart.md +93 -0
  14. package/dist-lib/ai/componentes/Chip.md +68 -0
  15. package/dist-lib/ai/componentes/ChoroplethMap.md +118 -0
  16. package/dist-lib/ai/componentes/ColorPicker.md +70 -0
  17. package/dist-lib/ai/componentes/Combobox.md +51 -0
  18. package/dist-lib/ai/componentes/ConversationListItem.md +90 -0
  19. package/dist-lib/ai/componentes/DataList.md +111 -0
  20. package/dist-lib/ai/componentes/DataTable.md +867 -0
  21. package/dist-lib/ai/componentes/DatePicker.md +84 -0
  22. package/dist-lib/ai/componentes/DateSeparatorChip.md +59 -0
  23. package/dist-lib/ai/componentes/EmptyState.md +72 -0
  24. package/dist-lib/ai/componentes/FileUploadField.md +95 -0
  25. package/dist-lib/ai/componentes/FloatingPanel.md +118 -0
  26. package/dist-lib/ai/componentes/FooterTable.md +62 -0
  27. package/dist-lib/ai/componentes/FormField.md +110 -0
  28. package/dist-lib/ai/componentes/Gantt.md +552 -0
  29. package/dist-lib/ai/componentes/Header.md +98 -0
  30. package/dist-lib/ai/componentes/Icon.md +65 -0
  31. package/dist-lib/ai/componentes/Kanban.md +343 -0
  32. package/dist-lib/ai/componentes/Kpi.md +103 -0
  33. package/dist-lib/ai/componentes/List.md +61 -0
  34. package/dist-lib/ai/componentes/MarkdownText.md +59 -0
  35. package/dist-lib/ai/componentes/MenuSidebar.md +128 -0
  36. package/dist-lib/ai/componentes/MessageAck.md +53 -0
  37. package/dist-lib/ai/componentes/MessageBubble.md +115 -0
  38. package/dist-lib/ai/componentes/MessageComposer.md +80 -0
  39. package/dist-lib/ai/componentes/MessageVariablesPicker.md +104 -0
  40. package/dist-lib/ai/componentes/Modal.md +88 -0
  41. package/dist-lib/ai/componentes/MonthYearPicker.md +49 -0
  42. package/dist-lib/ai/componentes/PageHeader.md +129 -0
  43. package/dist-lib/ai/componentes/Panel.md +84 -0
  44. package/dist-lib/ai/componentes/Scheduler.md +421 -0
  45. package/dist-lib/ai/componentes/ScreenLoader.md +60 -0
  46. package/dist-lib/ai/componentes/SingleMenuSidebar.md +171 -0
  47. package/dist-lib/ai/componentes/Spinner.md +52 -0
  48. package/dist-lib/ai/componentes/Table.md +192 -0
  49. package/dist-lib/ai/componentes/TableToolbar.md +87 -0
  50. package/dist-lib/ai/componentes/TabsNavigation.md +152 -0
  51. package/dist-lib/ai/componentes/Toast.md +49 -0
  52. package/dist-lib/ai/componentes/_primitivos.md +74 -0
  53. package/dist-lib/ai/componentes/avatar-ig.md +181 -0
  54. package/dist-lib/ai/componentes/indice.json +49 -0
  55. package/dist-lib/ai/exemplos/app-shell/app-shell-example.tsx +140 -0
  56. package/dist-lib/ai/exemplos/app-shell/index.ts +3 -0
  57. package/dist-lib/ai/exemplos/app-shell/nav-data.ts +97 -0
  58. package/dist-lib/ai/exemplos/app-shell/routes.tsx +75 -0
  59. package/dist-lib/ai/exemplos/chat/chat-screen.tsx +152 -0
  60. package/dist-lib/ai/exemplos/chat/chat-v2-mocks.ts +171 -0
  61. package/dist-lib/ai/exemplos/chat/chat-v2.styles.ts +23 -0
  62. package/dist-lib/ai/exemplos/chat/chat-v2.types.ts +81 -0
  63. package/dist-lib/ai/exemplos/chat/components/ChannelDot/channel-dot.tsx +28 -0
  64. package/dist-lib/ai/exemplos/chat/components/ConversationActionsMenu/conversation-actions-menu.tsx +83 -0
  65. package/dist-lib/ai/exemplos/chat/components/ConversationActionsMenu/index.ts +4 -0
  66. package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.styles.ts +22 -0
  67. package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.tsx +203 -0
  68. package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.types.ts +10 -0
  69. package/dist-lib/ai/exemplos/chat/components/ConversationColumn/index.ts +2 -0
  70. package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.styles.ts +55 -0
  71. package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.tsx +52 -0
  72. package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.types.ts +7 -0
  73. package/dist-lib/ai/exemplos/chat/components/ConversationListItem/index.ts +2 -0
  74. package/dist-lib/ai/exemplos/chat/components/DateSeparator/date-separator.tsx +18 -0
  75. package/dist-lib/ai/exemplos/chat/components/DetailField/detail-field.tsx +23 -0
  76. package/dist-lib/ai/exemplos/chat/components/DetailSection/detail-section.tsx +41 -0
  77. package/dist-lib/ai/exemplos/chat/components/DetailSection/index.ts +1 -0
  78. package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.styles.ts +30 -0
  79. package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.tsx +142 -0
  80. package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.types.ts +16 -0
  81. package/dist-lib/ai/exemplos/chat/components/DetailsColumn/index.ts +5 -0
  82. package/dist-lib/ai/exemplos/chat/components/FilterRow/filter-row.styles.ts +37 -0
  83. package/dist-lib/ai/exemplos/chat/components/FilterRow/filter-row.tsx +45 -0
  84. package/dist-lib/ai/exemplos/chat/components/FilterRow/index.ts +1 -0
  85. package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.styles.ts +27 -0
  86. package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.tsx +66 -0
  87. package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.types.ts +13 -0
  88. package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-rail.tsx +38 -0
  89. package/dist-lib/ai/exemplos/chat/components/FiltersColumn/index.ts +6 -0
  90. package/dist-lib/ai/exemplos/chat/components/MessageBubble/index.ts +2 -0
  91. package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.styles.ts +37 -0
  92. package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.tsx +29 -0
  93. package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.types.ts +5 -0
  94. package/dist-lib/ai/exemplos/chat/components/PersonAvatar/index.ts +5 -0
  95. package/dist-lib/ai/exemplos/chat/components/PersonAvatar/person-avatar.tsx +31 -0
  96. package/dist-lib/ai/exemplos/chat/components/QueueColumn/index.ts +2 -0
  97. package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.styles.ts +19 -0
  98. package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.tsx +140 -0
  99. package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.types.ts +13 -0
  100. package/dist-lib/ai/exemplos/chat/components/RailItem/index.ts +1 -0
  101. package/dist-lib/ai/exemplos/chat/components/RailItem/rail-item.styles.ts +30 -0
  102. package/dist-lib/ai/exemplos/chat/components/RailItem/rail-item.tsx +42 -0
  103. package/dist-lib/ai/exemplos/chat/hooks/use-resizable.ts +98 -0
  104. package/dist-lib/ai/exemplos/chat/index.ts +1 -0
  105. package/dist-lib/ai/exemplos/clientes/_table-data.ts +59 -0
  106. package/dist-lib/ai/exemplos/clientes/clientes-screen.tsx +505 -0
  107. package/dist-lib/ai/exemplos/clientes/clientes-showcase-mocks.ts +117 -0
  108. package/dist-lib/ai/exemplos/clientes/clientes-showcase.styles.ts +13 -0
  109. package/dist-lib/ai/exemplos/clientes/clientes-showcase.types.ts +16 -0
  110. package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.styles.ts +28 -0
  111. package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.tsx +303 -0
  112. package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.types.ts +14 -0
  113. package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/index.ts +2 -0
  114. package/dist-lib/ai/exemplos/clientes/components/NovoClienteDrawer/index.ts +2 -0
  115. package/dist-lib/ai/exemplos/clientes/components/NovoClienteDrawer/novo-cliente-drawer.tsx +207 -0
  116. package/dist-lib/ai/exemplos/clientes/index.ts +1 -0
  117. package/dist-lib/ai/exemplos/dashboard/dashboard-brazil-map.ts +33 -0
  118. package/dist-lib/ai/exemplos/dashboard/dashboard-screen.tsx +1110 -0
  119. package/dist-lib/ai/exemplos/dashboard/index.ts +1 -0
  120. package/dist-lib/ai/exemplos/edit-page/components/StepNav.tsx +81 -0
  121. package/dist-lib/ai/exemplos/edit-page/components/section-card.tsx +85 -0
  122. package/dist-lib/ai/exemplos/edit-page/edit-page-screen.tsx +234 -0
  123. package/dist-lib/ai/exemplos/edit-page/index.ts +1 -0
  124. package/dist-lib/ai/exemplos/finance/_table-data.ts +57 -0
  125. package/dist-lib/ai/exemplos/finance/clientes-financeiro-mocks.ts +227 -0
  126. package/dist-lib/ai/exemplos/finance/clientes-financeiro.types.ts +75 -0
  127. package/dist-lib/ai/exemplos/finance/clientes-showcase-mocks.ts +117 -0
  128. package/dist-lib/ai/exemplos/finance/clientes-showcase.styles.ts +13 -0
  129. package/dist-lib/ai/exemplos/finance/clientes-showcase.types.ts +16 -0
  130. package/dist-lib/ai/exemplos/finance/components/EditarFinanceDrawer/editar-finance-drawer.tsx +241 -0
  131. package/dist-lib/ai/exemplos/finance/components/EditarFinanceDrawer/index.ts +5 -0
  132. package/dist-lib/ai/exemplos/finance/components/ExtratoExpansion/extrato-expansion.tsx +172 -0
  133. package/dist-lib/ai/exemplos/finance/components/ExtratoExpansion/index.ts +1 -0
  134. package/dist-lib/ai/exemplos/finance/components/FinanceDetailPanel/finance-detail-panel.tsx +241 -0
  135. package/dist-lib/ai/exemplos/finance/components/FinanceDetailPanel/index.ts +2 -0
  136. package/dist-lib/ai/exemplos/finance/components/NovoClienteDrawer/index.ts +2 -0
  137. package/dist-lib/ai/exemplos/finance/components/NovoClienteDrawer/novo-cliente-drawer.tsx +207 -0
  138. package/dist-lib/ai/exemplos/finance/components/SacarDialog/index.ts +2 -0
  139. package/dist-lib/ai/exemplos/finance/components/SacarDialog/sacar-dialog.tsx +346 -0
  140. package/dist-lib/ai/exemplos/finance/finance-screen.tsx +821 -0
  141. package/dist-lib/ai/exemplos/finance/index.ts +1 -0
  142. package/dist-lib/ai/exemplos/gantt/_gantt-data.tsx +402 -0
  143. package/dist-lib/ai/exemplos/gantt/gantt-screen.tsx +512 -0
  144. package/dist-lib/ai/exemplos/gantt/index.ts +1 -0
  145. package/dist-lib/ai/exemplos/login/index.ts +1 -0
  146. package/dist-lib/ai/exemplos/login/login-screen.tsx +246 -0
  147. package/dist-lib/ai/exemplos/mapa-rede/components/ConsultorDetailPanel/consultor-detail-panel.tsx +158 -0
  148. package/dist-lib/ai/exemplos/mapa-rede/components/ConsultorDetailPanel/index.ts +2 -0
  149. package/dist-lib/ai/exemplos/mapa-rede/index.ts +1 -0
  150. package/dist-lib/ai/exemplos/mapa-rede/mapa-de-rede-mocks.ts +655 -0
  151. package/dist-lib/ai/exemplos/mapa-rede/mapa-de-rede.types.ts +53 -0
  152. package/dist-lib/ai/exemplos/mapa-rede/mapa-rede-screen.tsx +227 -0
  153. package/dist-lib/ai/exemplos/order-detail/components/ActivityTab.tsx +77 -0
  154. package/dist-lib/ai/exemplos/order-detail/components/AttachmentsTab.tsx +45 -0
  155. package/dist-lib/ai/exemplos/order-detail/components/CommentsTab.tsx +111 -0
  156. package/dist-lib/ai/exemplos/order-detail/components/DetailsTab.tsx +189 -0
  157. package/dist-lib/ai/exemplos/order-detail/components/OverviewTab.tsx +200 -0
  158. package/dist-lib/ai/exemplos/order-detail/components/section-card.tsx +85 -0
  159. package/dist-lib/ai/exemplos/order-detail/index.ts +1 -0
  160. package/dist-lib/ai/exemplos/order-detail/order-detail-screen.tsx +119 -0
  161. package/dist-lib/ai/exemplos/order-detail/order-mocks.ts +186 -0
  162. package/dist-lib/ai/exemplos/order-detail/order.types.ts +118 -0
  163. package/dist-lib/ai/global/componentes.md +217 -0
  164. package/dist-lib/ai/global/composicao.md +182 -0
  165. package/dist-lib/ai/indice.json +192 -0
  166. package/dist-lib/ai/lint/ds-lint-patterns.mjs +115 -0
  167. package/dist-lib/ai/manifest.json +42 -0
  168. package/dist-lib/ai/regras/design.md +88 -0
  169. package/dist-lib/ai/regras/temas.md +192 -0
  170. package/dist-lib/ai/regras-por-componente.json +103 -0
  171. package/dist-lib/ai/roteiros/app-builder/roteiro.md +155 -0
  172. package/dist-lib/ai/roteiros/auth-builder/roteiro.md +42 -0
  173. package/dist-lib/ai/roteiros/cards/roteiro.md +34 -0
  174. package/dist-lib/ai/roteiros/charts/roteiro.md +32 -0
  175. package/dist-lib/ai/roteiros/chat/roteiro.md +31 -0
  176. package/dist-lib/ai/roteiros/crud-builder/blueprint.md +63 -0
  177. package/dist-lib/ai/roteiros/crud-builder/entrevista.md +139 -0
  178. package/dist-lib/ai/roteiros/crud-builder/geracao.md +113 -0
  179. package/dist-lib/ai/roteiros/crud-builder/roteiro.md +89 -0
  180. package/dist-lib/ai/roteiros/dashboard-builder/blueprint.md +47 -0
  181. package/dist-lib/ai/roteiros/dashboard-builder/entrevista.md +62 -0
  182. package/dist-lib/ai/roteiros/dashboard-builder/geracao.md +88 -0
  183. package/dist-lib/ai/roteiros/dashboard-builder/roteiro.md +88 -0
  184. package/dist-lib/ai/roteiros/drawers/roteiro.md +41 -0
  185. package/dist-lib/ai/roteiros/list-builder/blueprint.md +94 -0
  186. package/dist-lib/ai/roteiros/list-builder/entrevista.md +188 -0
  187. package/dist-lib/ai/roteiros/list-builder/geracao.md +63 -0
  188. package/dist-lib/ai/roteiros/list-builder/roteiro.md +91 -0
  189. package/dist-lib/ai/roteiros/module-replicator/roteiro.md +56 -0
  190. package/dist-lib/ai/roteiros/page-detail/roteiro.md +32 -0
  191. package/dist-lib/ai/roteiros/page-edit/roteiro.md +31 -0
  192. package/dist-lib/ai/roteiros/screen-composer/roteiro.md +95 -0
  193. package/package.json +4 -1
@@ -0,0 +1,821 @@
1
+ import { useCallback, useMemo, useRef, useState } from "react";
2
+ import {
3
+ AlertTriangle,
4
+ Banknote,
5
+ Download,
6
+ History,
7
+ Lock,
8
+ MoreHorizontal,
9
+ Pause,
10
+ Pencil,
11
+ Plus,
12
+ SquareArrowOutUpRight,
13
+ ShieldAlert,
14
+ TrendingUp,
15
+ Unlock,
16
+ Wallet,
17
+ } from "lucide-react";
18
+ import {
19
+ DataTable,
20
+ presetView,
21
+ type DataTableColumnDef,
22
+ type DataTableKanbanConfig,
23
+ type DataTablePresetView,
24
+ type DataTableRef,
25
+ type DataTableViewMode,
26
+ } from "@/components/ui/DataTable";
27
+ import type { KanbanColumn } from "@/components/ui/Kanban";
28
+ import { Avatar } from "@/components/ui/avatar-ig";
29
+ import { Button } from "@/components/ui/Button/button";
30
+ import { Chip } from "@/components/ui/Chip";
31
+ import { PageHeader } from "@/components/ui/PageHeader";
32
+ import { BulkActionButton } from "@/components/ui/TableToolbar";
33
+ import { Switch } from "@/components/shadcn/switch";
34
+ import { AGENTS } from "./_table-data";
35
+ import { NovoClienteDrawer } from "./components/NovoClienteDrawer";
36
+ import {
37
+ ACCOUNT_STATUS,
38
+ BANKS,
39
+ FINANCE_CLIENTS,
40
+ FINANCE_KPIS,
41
+ formatBRL,
42
+ formatRelativeDays,
43
+ } from "./clientes-financeiro-mocks";
44
+ import { SacarDialog } from "./components/SacarDialog";
45
+ import { ExtratoExpansion } from "./components/ExtratoExpansion";
46
+ import { FinanceDetailPanel } from "./components/FinanceDetailPanel";
47
+ import { EditarFinanceDrawer } from "./components/EditarFinanceDrawer";
48
+ import type {
49
+ AccountStatus,
50
+ FinanceClientRow,
51
+ PaymentMethod,
52
+ } from "./clientes-financeiro.types";
53
+
54
+ /* ── Lookups derivados pros filtros/colunas ─────────────────────── */
55
+
56
+ const ACCOUNT_STATUS_OPTIONS = (
57
+ Object.entries(ACCOUNT_STATUS) as [AccountStatus, (typeof ACCOUNT_STATUS)[AccountStatus]][]
58
+ ).map(([value, m]) => ({ value, label: m.label, color: m.color }));
59
+
60
+ const BANK_OPTIONS = [
61
+ { value: "bb", label: "Banco do Brasil" },
62
+ { value: "itau", label: "Itaú" },
63
+ { value: "nubank", label: "Nubank" },
64
+ { value: "santander", label: "Santander" },
65
+ { value: "bradesco", label: "Bradesco" },
66
+ ];
67
+
68
+ const AGENT_OPTIONS = Object.entries(AGENTS).map(([value, a]) => ({
69
+ value,
70
+ label: a.name,
71
+ }));
72
+
73
+ /** typeOptions.users do column-type `user` — id → { name, initials }. */
74
+ const AGENT_USERS = Object.fromEntries(
75
+ Object.entries(AGENTS).map(([id, a]) => [id, { name: a.name, initials: a.initials }]),
76
+ );
77
+
78
+ const PAYMENT_METHOD_OPTIONS: { value: PaymentMethod; label: string }[] = [
79
+ { value: "pix", label: "PIX" },
80
+ { value: "ted", label: "TED" },
81
+ { value: "boleto", label: "Boleto" },
82
+ ];
83
+ const PAYMENT_METHOD_LABEL: Record<PaymentMethod, string> = {
84
+ pix: "PIX",
85
+ ted: "TED",
86
+ boleto: "Boleto",
87
+ };
88
+
89
+ /* ── Columns financeiras ────────────────────────────────────────── */
90
+
91
+ type ColumnHandlers = {
92
+ onEdit: (row: FinanceClientRow) => void;
93
+ onSacar: (row: FinanceClientRow) => void;
94
+ onToggleBlock: (row: FinanceClientRow) => void;
95
+ onToggleAutoWithdraw: (row: FinanceClientRow, next: boolean) => void;
96
+ };
97
+
98
+ function buildColumns(
99
+ handlers: ColumnHandlers,
100
+ ): DataTableColumnDef<FinanceClientRow>[] {
101
+ return [
102
+ {
103
+ field: "id",
104
+ headerName: "ID",
105
+ type: "text",
106
+ width: 110,
107
+ expandable: true, // chevron → abre o extrato (ExtratoExpansion)
108
+ aggregate: (rows) => (
109
+ <span className="text-fg-muted">{rows.length} clientes</span>
110
+ ),
111
+ },
112
+ {
113
+ field: "name",
114
+ headerName: "Licenciado",
115
+ type: "text",
116
+ sortable: true,
117
+ width: 240,
118
+ isPrimary: true,
119
+ enableColumnFilter: true,
120
+ filterType: "text",
121
+ render: ({ row }) => (
122
+ // Afordância de "abrir detalhe": botão-ícone bordado sempre visível,
123
+ // encostado no fim da célula (estilo o atalho ⌘K do header). Sinaliza
124
+ // que o click na linha abre o painel de detalhe.
125
+ <div className="flex items-center gap-gp-md min-w-0 w-full">
126
+ <Avatar size="md" colorHex={row.avatarColor}>
127
+ {row.initials}
128
+ </Avatar>
129
+ <div className="flex flex-col gap-gp-2xs min-w-0 flex-1">
130
+ <span className="text-body-sm font-medium text-fg-default truncate">
131
+ {row.name}
132
+ </span>
133
+ <span className="text-caption-md text-fg-muted truncate">
134
+ {row.email}
135
+ </span>
136
+ </div>
137
+ <span
138
+ className="shrink-0 grid place-items-center size-[24px] rounded-radius-sm border border-border-subtle bg-bg-canvas dark:bg-bg-muted shadow-sh-sm text-fg-muted [&_svg]:size-icon-xs"
139
+ aria-hidden
140
+ >
141
+ <SquareArrowOutUpRight strokeWidth={2} />
142
+ </span>
143
+ </div>
144
+ ),
145
+ },
146
+ {
147
+ field: "companyName",
148
+ headerName: "Razão Social",
149
+ type: "text",
150
+ sortable: true,
151
+ enableColumnFilter: true,
152
+ filterType: "text",
153
+ width: 220,
154
+ },
155
+ {
156
+ field: "cnpj",
157
+ headerName: "CNPJ",
158
+ type: "text",
159
+ width: 170,
160
+ enableColumnFilter: true,
161
+ filterType: "text",
162
+ copyable: true, // ícone de copiar no hover da célula + "Copiado!"
163
+ render: ({ value }) => (
164
+ <span className="text-body-sm tabular-nums text-fg-default">
165
+ {value as string}
166
+ </span>
167
+ ),
168
+ },
169
+ // Situação — badge chip + eixo do Kanban.
170
+ {
171
+ field: "accountStatus",
172
+ headerName: "Situação",
173
+ type: "badge",
174
+ width: 150,
175
+ enableColumnFilter: true,
176
+ filterType: "multiSelect",
177
+ filterOptions: ACCOUNT_STATUS_OPTIONS,
178
+ valueFormatter: (v) =>
179
+ ACCOUNT_STATUS[v as AccountStatus]?.label ?? String(v ?? ""),
180
+ render: ({ value }) => {
181
+ const meta = ACCOUNT_STATUS[value as AccountStatus];
182
+ if (!meta) return null;
183
+ return (
184
+ <Chip color={meta.color} variant="soft" size="sm" shape="pill">
185
+ {meta.label}
186
+ </Chip>
187
+ );
188
+ },
189
+ },
190
+ {
191
+ field: "availableBalance",
192
+ headerName: "Saldo disponível",
193
+ type: "currency",
194
+ sortable: true,
195
+ enableColumnFilter: true,
196
+ filterType: "number",
197
+ align: "right",
198
+ width: 160,
199
+ aggregate: "sum",
200
+ aggregateFormatter: (v) => formatBRL(v),
201
+ render: ({ value }) => (
202
+ <span className="font-semibold tabular-nums text-fg-success">
203
+ {formatBRL(value as number)}
204
+ </span>
205
+ ),
206
+ },
207
+ {
208
+ field: "monthlyVolume",
209
+ headerName: "Volume mensal",
210
+ type: "currency",
211
+ sortable: true,
212
+ enableColumnFilter: true,
213
+ filterType: "number",
214
+ align: "right",
215
+ width: 150,
216
+ aggregate: "sum",
217
+ aggregateFormatter: (v) => formatBRL(v),
218
+ render: ({ value }) => (
219
+ <span className="tabular-nums text-fg-default">
220
+ {formatBRL(value as number)}
221
+ </span>
222
+ ),
223
+ },
224
+ // Comissão — percentage + inline edit (editType number).
225
+ {
226
+ field: "commissionRate",
227
+ headerName: "Comissão",
228
+ type: "percentage",
229
+ typeOptions: { decimals: 1 },
230
+ align: "right",
231
+ width: 130,
232
+ sortable: true,
233
+ editable: true,
234
+ editType: "number",
235
+ enableColumnFilter: true,
236
+ filterType: "number",
237
+ aggregate: "avg",
238
+ aggregateFormatter: (v) =>
239
+ `${v.toLocaleString("pt-BR", { minimumFractionDigits: 1, maximumFractionDigits: 1 })}%`,
240
+ },
241
+ // Conta bancária — avatar do banco; valueGetter expõe o id pro filtro/preset.
242
+ {
243
+ field: "bankAccount",
244
+ headerName: "Conta bancária",
245
+ type: "text",
246
+ width: 220,
247
+ enableColumnFilter: true,
248
+ filterType: "select",
249
+ filterOptions: BANK_OPTIONS,
250
+ valueGetter: (row) => row.bankAccount.bank,
251
+ copyable: {
252
+ value: (row) => `Ag ${row.bankAccount.agency} · ${row.bankAccount.account}`,
253
+ label: "Copiar conta",
254
+ },
255
+ render: ({ row }) => {
256
+ const meta = BANKS[row.bankAccount.bank];
257
+ return (
258
+ <div className="flex items-center gap-gp-md min-w-0">
259
+ <Avatar size="lg" colorHex={meta.color} className="text-caption-md font-bold">
260
+ {meta.initials}
261
+ </Avatar>
262
+ <div className="flex flex-col min-w-0">
263
+ <span className="text-body-sm font-medium text-fg-default truncate">
264
+ {row.bankAccount.bankName}
265
+ </span>
266
+ <span className="text-caption-md text-fg-muted tabular-nums">
267
+ Ag {row.bankAccount.agency} · {row.bankAccount.account}
268
+ </span>
269
+ </div>
270
+ </div>
271
+ );
272
+ },
273
+ },
274
+ // Saque automático — toggle direto (interação, não inline-edit).
275
+ {
276
+ field: "autoWithdraw",
277
+ headerName: "Saque auto",
278
+ type: "boolean",
279
+ width: 150,
280
+ align: "left",
281
+ enableColumnFilter: true,
282
+ filterType: "boolean",
283
+ render: ({ row, value }) => (
284
+ <Switch
285
+ checked={value as boolean}
286
+ onCheckedChange={(next) => handlers.onToggleAutoWithdraw(row, next)}
287
+ onClick={(e) => e.stopPropagation()}
288
+ aria-label={`Saque automático de ${row.name}`}
289
+ />
290
+ ),
291
+ },
292
+ {
293
+ field: "paymentMethods",
294
+ headerName: "Métodos",
295
+ type: "tags",
296
+ width: 170,
297
+ enableColumnFilter: true,
298
+ filterType: "multiSelect",
299
+ filterOptions: PAYMENT_METHOD_OPTIONS,
300
+ valueFormatter: (v) =>
301
+ Array.isArray(v)
302
+ ? (v as PaymentMethod[]).map((m) => PAYMENT_METHOD_LABEL[m]).join(", ")
303
+ : "",
304
+ render: ({ value }) => (
305
+ <div className="flex flex-wrap gap-gp-2xs">
306
+ {(value as PaymentMethod[]).map((m) => (
307
+ <Chip key={m} color="neutral" variant="soft" size="sm" shape="rounded">
308
+ {PAYMENT_METHOD_LABEL[m]}
309
+ </Chip>
310
+ ))}
311
+ </div>
312
+ ),
313
+ },
314
+ {
315
+ field: "agentId",
316
+ headerName: "Gestor",
317
+ type: "user",
318
+ typeOptions: { users: AGENT_USERS },
319
+ width: 180,
320
+ enableColumnFilter: true,
321
+ filterType: "select",
322
+ filterOptions: AGENT_OPTIONS,
323
+ valueFormatter: (v) => AGENTS[v as keyof typeof AGENTS]?.name ?? String(v ?? ""),
324
+ },
325
+ {
326
+ field: "lastMovement",
327
+ headerName: "Última mov.",
328
+ type: "datetime",
329
+ sortable: true,
330
+ enableColumnFilter: true,
331
+ filterType: "date",
332
+ width: 150,
333
+ render: ({ value }) => (
334
+ <span className="text-body-sm text-fg-muted tabular-nums">
335
+ {formatRelativeDays(value as number)}
336
+ </span>
337
+ ),
338
+ },
339
+ {
340
+ field: "createdAt",
341
+ headerName: "Cliente desde",
342
+ type: "date",
343
+ sortable: true,
344
+ enableColumnFilter: true,
345
+ filterType: "date",
346
+ width: 140,
347
+ },
348
+ {
349
+ field: "_actions",
350
+ headerName: "",
351
+ type: "actions",
352
+ // Sem `width` e sem `pinned`, de propósito — o `type` já entrega os dois:
353
+ // a largura é DERIVADA do nº de ações (1→44px · 2→74 · 3→104) e acima de 3 elas
354
+ // colapsam no "…" sozinhas; o pin à direita sai de
355
+ // `col.pinned ?? (isActions ? "right" : undefined)`. Prop redundante aqui vira prop
356
+ // copiada em toda tela gerada — o exemplo é a fonte de maior precedência das skills.
357
+ pinned: "right",
358
+ getActions: ({ row }) => {
359
+ const blocked = row.accountStatus === "bloqueado";
360
+ return [
361
+ {
362
+ id: "edit",
363
+ label: "Editar dados",
364
+ icon: <Pencil />,
365
+ onClick: () => handlers.onEdit(row),
366
+ },
367
+ {
368
+ id: "sacar",
369
+ label: "Realizar saque",
370
+ icon: <Banknote />,
371
+ disabled: blocked,
372
+ onClick: () => handlers.onSacar(row),
373
+ },
374
+ {
375
+ id: "history",
376
+ label: "Histórico de saques",
377
+ icon: <History />,
378
+ onClick: () =>
379
+ window.alert(`Histórico de ${row.name} — ${row.transactions.length} movimentações (mock).`),
380
+ },
381
+ {
382
+ id: "block",
383
+ label: blocked ? "Desbloquear conta" : "Bloquear conta",
384
+ icon: blocked ? <Unlock /> : <Lock />,
385
+ destructive: !blocked,
386
+ onClick: () => handlers.onToggleBlock(row),
387
+ },
388
+ ];
389
+ },
390
+ },
391
+ ];
392
+ }
393
+
394
+ /* ── Preset Views ───────────────────────────────────────────────── */
395
+
396
+ const DEFAULT_VIEWS: DataTablePresetView[] = [
397
+ presetView({
398
+ id: "preset:digitais",
399
+ name: "Digitais",
400
+ filters: [{ field: "bankAccount", value: "nubank" }],
401
+ }),
402
+ presetView({
403
+ id: "preset:alto-valor",
404
+ name: "Alto valor (≥ R$ 5k)",
405
+ filters: [{ field: "availableBalance", operator: "gte", value: 5000 }],
406
+ sort: [{ field: "availableBalance", direction: "desc" }],
407
+ }),
408
+ presetView({
409
+ id: "preset:inadimplentes",
410
+ name: "Inadimplentes",
411
+ // accountStatus é badge → operador isAnyOf (multi-valor).
412
+ filters: [
413
+ { field: "accountStatus", operator: "isAnyOf", value: ["negociacao", "bloqueado"] },
414
+ ],
415
+ }),
416
+ presetView({
417
+ id: "preset:saque-auto",
418
+ name: "Saque automático",
419
+ filters: [{ field: "autoWithdraw", operator: "equals", value: true }],
420
+ }),
421
+ ];
422
+
423
+ /* ── Kanban — board por situação da conta ───────────────────────── */
424
+
425
+ const KANBAN_COLUMNS: KanbanColumn[] = [
426
+ { id: "pendente", label: "Pendente", dotColor: "var(--color-fg-warning)" },
427
+ { id: "ativo", label: "Ativo", dotColor: "var(--color-fg-success)" },
428
+ { id: "negociacao", label: "Em negociação", dotColor: "var(--color-fg-info)" },
429
+ { id: "bloqueado", label: "Bloqueado", dotColor: "var(--color-fg-danger)" },
430
+ ];
431
+
432
+ /**
433
+ * KPIs temporariamente ocultos (Sergio vai retrabalhá-los depois). Mantido o
434
+ * código + a section atrás da flag pra reativar trocando pra `true`.
435
+ */
436
+ const SHOW_KPIS = false;
437
+
438
+ /* ── KPI Card ───────────────────────────────────────────────────── */
439
+
440
+ type KpiTone = "brand" | "success" | "warning" | "info" | "danger" | "neutral";
441
+
442
+ const KPI_TONE_CLASSES: Record<KpiTone, { bg: string; fg: string }> = {
443
+ brand: { bg: "bg-bg-brand-subtle", fg: "text-fg-brand" },
444
+ success: { bg: "bg-bg-success-muted", fg: "text-fg-success" },
445
+ warning: { bg: "bg-bg-warning-muted", fg: "text-fg-warning" },
446
+ info: { bg: "bg-bg-info-muted", fg: "text-fg-info" },
447
+ danger: { bg: "bg-bg-danger-muted", fg: "text-fg-danger" },
448
+ neutral: { bg: "bg-bg-muted", fg: "text-fg-muted" },
449
+ };
450
+
451
+ function KpiCard({
452
+ icon: Icon,
453
+ title,
454
+ value,
455
+ hint,
456
+ tone = "brand",
457
+ }: {
458
+ icon: React.ComponentType<{ className?: string; strokeWidth?: number }>;
459
+ title: string;
460
+ value: string;
461
+ hint?: string;
462
+ tone?: KpiTone;
463
+ }) {
464
+ const cls = KPI_TONE_CLASSES[tone];
465
+ return (
466
+ <article className="flex flex-col gap-gp-lg p-pad-3xl bg-bg-surface border border-border-subtle rounded-radius-xl shadow-sh-sm">
467
+ <header className="flex items-start justify-between gap-gp-md">
468
+ <h3 className="m-0 text-body-md font-semibold text-fg-default">{title}</h3>
469
+ <span
470
+ className={`grid place-items-center size-form-lg rounded-radius-lg shrink-0 ${cls.bg} ${cls.fg}`}
471
+ aria-hidden="true"
472
+ >
473
+ <Icon className="size-icon-md" strokeWidth={1.8} />
474
+ </span>
475
+ </header>
476
+ <div className="flex flex-col gap-gp-xs">
477
+ <span className="text-body-2xl font-bold text-fg-default leading-none [font-variant-numeric:tabular-nums]">
478
+ {value}
479
+ </span>
480
+ {hint && <span className="text-caption-sm text-fg-subtle">{hint}</span>}
481
+ </div>
482
+ </article>
483
+ );
484
+ }
485
+
486
+ /* ═══════════════════════════════════════════════════════════════════
487
+ Page — Clientes Financeiro
488
+ ═══════════════════════════════════════════════════════════════════ */
489
+
490
+ export function FinanceScreen() {
491
+ const [rows, setRows] = useState<FinanceClientRow[]>(FINANCE_CLIENTS);
492
+ const [viewMode, setViewMode] = useState<DataTableViewMode>("table");
493
+ const [novoClienteOpen, setNovoClienteOpen] = useState(false);
494
+ const [editingClient, setEditingClient] = useState<FinanceClientRow | null>(null);
495
+ const [sacarClient, setSacarClient] = useState<FinanceClientRow | null>(null);
496
+ const [detailRowId, setDetailRowId] = useState<string | null>(null);
497
+ const tableRef = useRef<DataTableRef>(null);
498
+
499
+ const detailRow = detailRowId
500
+ ? rows.find((r) => r.id === detailRowId) ?? null
501
+ : null;
502
+
503
+ const handleEdit = useCallback((row: FinanceClientRow) => {
504
+ setEditingClient(row);
505
+ }, []);
506
+
507
+ const handleSacar = useCallback((row: FinanceClientRow) => {
508
+ setSacarClient(row);
509
+ }, []);
510
+
511
+ const handleToggleBlock = useCallback((row: FinanceClientRow) => {
512
+ setRows((prev) =>
513
+ prev.map((r) =>
514
+ r.id === row.id
515
+ ? { ...r, accountStatus: r.accountStatus === "bloqueado" ? "ativo" : "bloqueado" }
516
+ : r,
517
+ ),
518
+ );
519
+ }, []);
520
+
521
+ const handleToggleAutoWithdraw = useCallback(
522
+ (row: FinanceClientRow, next: boolean) => {
523
+ setRows((prev) =>
524
+ prev.map((r) => (r.id === row.id ? { ...r, autoWithdraw: next } : r)),
525
+ );
526
+ },
527
+ [],
528
+ );
529
+
530
+ const handleSacarConfirm = useCallback(
531
+ (data: { clientId: string; amount: number; account: { bankName: string } }) => {
532
+ window.alert(
533
+ `Saque de ${formatBRL(data.amount)} confirmado!\n\n` +
534
+ `Cliente: ${data.clientId}\n` +
535
+ `Conta destino: ${data.account.bankName}\n\n` +
536
+ `Disponível em até 1 dia útil.`,
537
+ );
538
+ },
539
+ [],
540
+ );
541
+
542
+ // Inline edit (commissionRate) — async com latência simulada.
543
+ const handleCellEditCommit = useCallback(
544
+ async ({ id, field, value }: { id: string | number; field: string; value: unknown }) => {
545
+ await new Promise((res) => setTimeout(res, 600));
546
+ setRows((prev) =>
547
+ prev.map((r) => (r.id === id ? { ...r, [field]: Number(value) } : r)),
548
+ );
549
+ },
550
+ [],
551
+ );
552
+
553
+ const columns = useMemo(
554
+ () =>
555
+ buildColumns({
556
+ onEdit: handleEdit,
557
+ onSacar: handleSacar,
558
+ onToggleBlock: handleToggleBlock,
559
+ onToggleAutoWithdraw: handleToggleAutoWithdraw,
560
+ }),
561
+ [handleEdit, handleSacar, handleToggleBlock, handleToggleAutoWithdraw],
562
+ );
563
+
564
+ const kanbanConfig = useMemo<DataTableKanbanConfig<FinanceClientRow>>(
565
+ () => ({
566
+ groupByField: "accountStatus",
567
+ columns: KANBAN_COLUMNS,
568
+ renderCard: ({ row }) => {
569
+ const bank = BANKS[row.bankAccount.bank];
570
+ return {
571
+ title: row.name,
572
+ subtitle: row.companyName,
573
+ avatar: (
574
+ <Avatar size="sm" colorHex={row.avatarColor}>
575
+ {row.initials}
576
+ </Avatar>
577
+ ),
578
+ chip: (
579
+ <Chip color="neutral" variant="soft" size="sm" shape="pill">
580
+ {bank.initials}
581
+ </Chip>
582
+ ),
583
+ value: formatBRL(row.availableBalance),
584
+ footerLeft: (
585
+ <span className="inline-flex flex-wrap gap-gp-2xs min-w-0">
586
+ {row.paymentMethods.map((m) => (
587
+ <Chip key={m} color="neutral" variant="soft" size="sm" shape="rounded">
588
+ {PAYMENT_METHOD_LABEL[m]}
589
+ </Chip>
590
+ ))}
591
+ </span>
592
+ ),
593
+ footerRight: (
594
+ <span className="text-caption-sm text-fg-muted [font-variant-numeric:tabular-nums] shrink-0">
595
+ {formatRelativeDays(row.lastMovement)}
596
+ </span>
597
+ ),
598
+ };
599
+ },
600
+ enableDnD: true,
601
+ onCardMove: (cardId, _from, to) => {
602
+ setRows((prev) =>
603
+ prev.map((r) =>
604
+ r.id === cardId ? { ...r, accountStatus: to as AccountStatus } : r,
605
+ ),
606
+ );
607
+ },
608
+ }),
609
+ [],
610
+ );
611
+
612
+ const isTable = viewMode === "table";
613
+
614
+ return (
615
+ // Conteúdo da página (sem shell). Embrulhe no seu AppShell/layout — o
616
+ // parent precisa dar altura (h-full/flex) pra DataTable preencher.
617
+ <div className="flex flex-col h-full min-h-0 gap-gp-2xl">
618
+ <PageHeader
619
+ title="Clientes — Financeiro"
620
+ description="Acompanhe saldos, situação das contas e fluxo financeiro dos licenciados. Arraste cards no Kanban pra mudar a situação; edite comissão e saque automático direto na tabela."
621
+ badge={
622
+ <Chip color="primary" variant="soft" size="sm" shape="rounded">
623
+ {rows.length.toLocaleString("pt-BR")} clientes
624
+ </Chip>
625
+ }
626
+ actions={
627
+ <>
628
+ <Button
629
+ variant="outline"
630
+ color="secondary"
631
+ size="icon-md"
632
+ aria-label="Mais ações"
633
+ >
634
+ <MoreHorizontal />
635
+ </Button>
636
+ <Button
637
+ variant="filled"
638
+ color="primary"
639
+ size="md"
640
+ iconLeft={<Plus />}
641
+ onClick={() => setNovoClienteOpen(true)}
642
+ >
643
+ Novo cliente
644
+ </Button>
645
+ </>
646
+ }
647
+ />
648
+
649
+ {/* KPI grid — 4 cards. Oculto por enquanto (SHOW_KPIS) — Sergio retrabalha depois. */}
650
+ {SHOW_KPIS && (
651
+ <section className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-gp-2xl pb-pad-2xl">
652
+ <KpiCard
653
+ icon={Wallet}
654
+ title="Disponível total"
655
+ value={formatBRL(FINANCE_KPIS.totalAvailable)}
656
+ hint="Soma de todos os saldos"
657
+ tone="brand"
658
+ />
659
+ <KpiCard
660
+ icon={TrendingUp}
661
+ title="High-value (≥ R$ 5k)"
662
+ value={String(FINANCE_KPIS.highValueCount)}
663
+ hint="Clientes acima de R$ 5.000"
664
+ tone="success"
665
+ />
666
+ <KpiCard
667
+ icon={Banknote}
668
+ title="Saldo médio"
669
+ value={formatBRL(FINANCE_KPIS.averageBalance)}
670
+ hint="Por cliente"
671
+ tone="info"
672
+ />
673
+ <KpiCard
674
+ icon={ShieldAlert}
675
+ title="Em risco"
676
+ value={String(FINANCE_KPIS.atRiskCount)}
677
+ hint="Em negociação ou bloqueado"
678
+ tone="danger"
679
+ />
680
+ </section>
681
+ )}
682
+
683
+ <DataTable<FinanceClientRow>
684
+ ref={tableRef}
685
+ rows={rows}
686
+ columns={columns}
687
+ getRowId={(r) => r.id}
688
+ // v4 — colunas + kanban + inline edit reformulados (auditoria 2026-06-10)
689
+ persistId="showcase-finance-crud-v4"
690
+ defaultViews={DEFAULT_VIEWS}
691
+ viewMode={viewMode}
692
+ onViewModeChange={setViewMode}
693
+ kanbanConfig={kanbanConfig}
694
+ // Click na row (no licenciado) abre o painel de detalhe (FloatingPanel).
695
+ onRowClick={(row) => setDetailRowId(row.id)}
696
+ // Extrato da row (chevron na coluna ID).
697
+ renderRowExpansion={({ row }) => <ExtratoExpansion row={row} />}
698
+ // Inline edit da comissão (async, latência simulada).
699
+ onCellEditCommit={handleCellEditCommit}
700
+ // Totalizers no footer (só faz sentido na tabela).
701
+ showTotalizers={isTable}
702
+ toolbar={{
703
+ enableSearch: true,
704
+ enableFilters: true,
705
+ // Cols/density só na tabela (board não tem colunas/densidade).
706
+ enableColumns: isTable,
707
+ enableDensity: isTable,
708
+ enableExport: true,
709
+ }}
710
+ paginationConfig={{
711
+ enabled: isTable,
712
+ initialPageSize: 25,
713
+ pageSizeOptions: [10, 25, 50, 100],
714
+ }}
715
+ selectionConfig={{
716
+ enabled: true,
717
+ enableGlobal: true,
718
+ actions: (selectedIds, clearSelection) => (
719
+ <>
720
+ <BulkActionButton
721
+ icon={<Download />}
722
+ onClick={() => tableRef.current?.exportCsv("selected")}
723
+ >
724
+ Exportar
725
+ </BulkActionButton>
726
+ <BulkActionButton
727
+ icon={<Pause />}
728
+ onClick={() => {
729
+ const ids = new Set(selectedIds.map(String));
730
+ setRows((prev) =>
731
+ prev.map((r) =>
732
+ ids.has(r.id) ? { ...r, autoWithdraw: false } : r,
733
+ ),
734
+ );
735
+ clearSelection();
736
+ }}
737
+ >
738
+ Pausar saque auto
739
+ </BulkActionButton>
740
+ <BulkActionButton
741
+ icon={<AlertTriangle />}
742
+ variant="danger"
743
+ onClick={() => {
744
+ const ids = new Set(selectedIds.map(String));
745
+ setRows((prev) =>
746
+ prev.map((r) =>
747
+ ids.has(r.id) ? { ...r, accountStatus: "negociacao" } : r,
748
+ ),
749
+ );
750
+ clearSelection();
751
+ }}
752
+ >
753
+ Marcar p/ revisão
754
+ </BulkActionButton>
755
+ </>
756
+ ),
757
+ }}
758
+ className="flex-1 min-h-0"
759
+ />
760
+
761
+ {/* Drawers + Modals */}
762
+ <NovoClienteDrawer
763
+ open={novoClienteOpen}
764
+ onOpenChange={setNovoClienteOpen}
765
+ onSubmit={() => window.alert("Cliente criado com sucesso! (mock)")}
766
+ />
767
+ <EditarFinanceDrawer
768
+ client={editingClient}
769
+ onOpenChange={(o) => !o && setEditingClient(null)}
770
+ onSave={(id, p) => {
771
+ setRows((prev) =>
772
+ prev.map((r) =>
773
+ r.id === id
774
+ ? {
775
+ ...r,
776
+ name: p.name,
777
+ email: p.email,
778
+ phone: p.phone,
779
+ companyName: p.companyName,
780
+ cnpj: p.cnpj,
781
+ accountStatus: p.accountStatus,
782
+ commissionRate: p.commissionRate,
783
+ autoWithdraw: p.autoWithdraw,
784
+ paymentMethods: p.paymentMethods,
785
+ agentId: p.agentId as FinanceClientRow["agentId"],
786
+ bankAccount: {
787
+ ...r.bankAccount,
788
+ bank: p.bank,
789
+ bankName: BANKS[p.bank].name,
790
+ },
791
+ }
792
+ : r,
793
+ ),
794
+ );
795
+ }}
796
+ />
797
+ <SacarDialog
798
+ open={sacarClient !== null}
799
+ onOpenChange={(o) => !o && setSacarClient(null)}
800
+ client={sacarClient}
801
+ onConfirm={handleSacarConfirm}
802
+ />
803
+
804
+ {/* Detalhe do licenciado — FloatingPanel (abre no click da row). */}
805
+ <FinanceDetailPanel
806
+ row={detailRow}
807
+ onClose={() => setDetailRowId(null)}
808
+ onEdit={(row) => {
809
+ setDetailRowId(null);
810
+ setEditingClient(row);
811
+ }}
812
+ onSacar={(row) => {
813
+ setDetailRowId(null);
814
+ setSacarClient(row);
815
+ }}
816
+ />
817
+ </div>
818
+ );
819
+ }
820
+
821
+ export default FinanceScreen;