@snksergio/design-system 0.62.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.
- package/dist-lib/ai/blocos/chart/budget-breakdown.tsx +174 -0
- package/dist-lib/ai/blocos/indice.md +43 -0
- package/dist-lib/ai/blocos/paneldetail/detalhe-com-tabela.tsx +450 -0
- package/dist-lib/ai/blocos/paneldetail/detalhe-de-tarefa-com-abas.tsx +570 -0
- package/dist-lib/ai/blocos/paneldetail/detalhe-do-registro.tsx +555 -0
- package/dist-lib/ai/exemplos/app-shell/app-shell-example.tsx +140 -0
- package/dist-lib/ai/exemplos/app-shell/index.ts +3 -0
- package/dist-lib/ai/exemplos/app-shell/nav-data.ts +97 -0
- package/dist-lib/ai/exemplos/app-shell/routes.tsx +75 -0
- package/dist-lib/ai/exemplos/chat/chat-screen.tsx +152 -0
- package/dist-lib/ai/exemplos/chat/chat-v2-mocks.ts +171 -0
- package/dist-lib/ai/exemplos/chat/chat-v2.styles.ts +23 -0
- package/dist-lib/ai/exemplos/chat/chat-v2.types.ts +81 -0
- package/dist-lib/ai/exemplos/chat/components/ChannelDot/channel-dot.tsx +28 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationActionsMenu/conversation-actions-menu.tsx +83 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationActionsMenu/index.ts +4 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.styles.ts +22 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.tsx +203 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.types.ts +10 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationColumn/index.ts +2 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.styles.ts +55 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.tsx +52 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.types.ts +7 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationListItem/index.ts +2 -0
- package/dist-lib/ai/exemplos/chat/components/DateSeparator/date-separator.tsx +18 -0
- package/dist-lib/ai/exemplos/chat/components/DetailField/detail-field.tsx +23 -0
- package/dist-lib/ai/exemplos/chat/components/DetailSection/detail-section.tsx +41 -0
- package/dist-lib/ai/exemplos/chat/components/DetailSection/index.ts +1 -0
- package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.styles.ts +30 -0
- package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.tsx +142 -0
- package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.types.ts +16 -0
- package/dist-lib/ai/exemplos/chat/components/DetailsColumn/index.ts +5 -0
- package/dist-lib/ai/exemplos/chat/components/FilterRow/filter-row.styles.ts +37 -0
- package/dist-lib/ai/exemplos/chat/components/FilterRow/filter-row.tsx +45 -0
- package/dist-lib/ai/exemplos/chat/components/FilterRow/index.ts +1 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.styles.ts +27 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.tsx +66 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.types.ts +13 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-rail.tsx +38 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/index.ts +6 -0
- package/dist-lib/ai/exemplos/chat/components/MessageBubble/index.ts +2 -0
- package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.styles.ts +37 -0
- package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.tsx +29 -0
- package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.types.ts +5 -0
- package/dist-lib/ai/exemplos/chat/components/PersonAvatar/index.ts +5 -0
- package/dist-lib/ai/exemplos/chat/components/PersonAvatar/person-avatar.tsx +31 -0
- package/dist-lib/ai/exemplos/chat/components/QueueColumn/index.ts +2 -0
- package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.styles.ts +19 -0
- package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.tsx +140 -0
- package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.types.ts +13 -0
- package/dist-lib/ai/exemplos/chat/components/RailItem/index.ts +1 -0
- package/dist-lib/ai/exemplos/chat/components/RailItem/rail-item.styles.ts +30 -0
- package/dist-lib/ai/exemplos/chat/components/RailItem/rail-item.tsx +42 -0
- package/dist-lib/ai/exemplos/chat/hooks/use-resizable.ts +98 -0
- package/dist-lib/ai/exemplos/chat/index.ts +1 -0
- package/dist-lib/ai/exemplos/clientes/_table-data.ts +59 -0
- package/dist-lib/ai/exemplos/clientes/clientes-screen.tsx +505 -0
- package/dist-lib/ai/exemplos/clientes/clientes-showcase-mocks.ts +117 -0
- package/dist-lib/ai/exemplos/clientes/clientes-showcase.styles.ts +13 -0
- package/dist-lib/ai/exemplos/clientes/clientes-showcase.types.ts +16 -0
- package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.styles.ts +28 -0
- package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.tsx +303 -0
- package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.types.ts +14 -0
- package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/index.ts +2 -0
- package/dist-lib/ai/exemplos/clientes/components/NovoClienteDrawer/index.ts +2 -0
- package/dist-lib/ai/exemplos/clientes/components/NovoClienteDrawer/novo-cliente-drawer.tsx +207 -0
- package/dist-lib/ai/exemplos/clientes/index.ts +1 -0
- package/dist-lib/ai/exemplos/edit-page/components/StepNav.tsx +81 -0
- package/dist-lib/ai/exemplos/edit-page/components/section-card.tsx +85 -0
- package/dist-lib/ai/exemplos/edit-page/edit-page-screen.tsx +234 -0
- package/dist-lib/ai/exemplos/edit-page/index.ts +1 -0
- package/dist-lib/ai/exemplos/finance/_table-data.ts +57 -0
- package/dist-lib/ai/exemplos/finance/clientes-financeiro-mocks.ts +227 -0
- package/dist-lib/ai/exemplos/finance/clientes-financeiro.types.ts +75 -0
- package/dist-lib/ai/exemplos/finance/clientes-showcase-mocks.ts +117 -0
- package/dist-lib/ai/exemplos/finance/clientes-showcase.styles.ts +13 -0
- package/dist-lib/ai/exemplos/finance/clientes-showcase.types.ts +16 -0
- package/dist-lib/ai/exemplos/finance/components/EditarFinanceDrawer/editar-finance-drawer.tsx +241 -0
- package/dist-lib/ai/exemplos/finance/components/EditarFinanceDrawer/index.ts +5 -0
- package/dist-lib/ai/exemplos/finance/components/ExtratoExpansion/extrato-expansion.tsx +172 -0
- package/dist-lib/ai/exemplos/finance/components/ExtratoExpansion/index.ts +1 -0
- package/dist-lib/ai/exemplos/finance/components/FinanceDetailPanel/finance-detail-panel.tsx +241 -0
- package/dist-lib/ai/exemplos/finance/components/FinanceDetailPanel/index.ts +2 -0
- package/dist-lib/ai/exemplos/finance/components/NovoClienteDrawer/index.ts +2 -0
- package/dist-lib/ai/exemplos/finance/components/NovoClienteDrawer/novo-cliente-drawer.tsx +207 -0
- package/dist-lib/ai/exemplos/finance/components/SacarDialog/index.ts +2 -0
- package/dist-lib/ai/exemplos/finance/components/SacarDialog/sacar-dialog.tsx +346 -0
- package/dist-lib/ai/exemplos/finance/finance-screen.tsx +821 -0
- package/dist-lib/ai/exemplos/finance/index.ts +1 -0
- package/dist-lib/ai/exemplos/gantt/_gantt-data.tsx +402 -0
- package/dist-lib/ai/exemplos/gantt/gantt-screen.tsx +512 -0
- package/dist-lib/ai/exemplos/gantt/index.ts +1 -0
- package/dist-lib/ai/exemplos/login/index.ts +1 -0
- package/dist-lib/ai/exemplos/login/login-screen.tsx +246 -0
- package/dist-lib/ai/exemplos/mapa-rede/components/ConsultorDetailPanel/consultor-detail-panel.tsx +158 -0
- package/dist-lib/ai/exemplos/mapa-rede/components/ConsultorDetailPanel/index.ts +2 -0
- package/dist-lib/ai/exemplos/mapa-rede/index.ts +1 -0
- package/dist-lib/ai/exemplos/mapa-rede/mapa-de-rede-mocks.ts +655 -0
- package/dist-lib/ai/exemplos/mapa-rede/mapa-de-rede.types.ts +53 -0
- package/dist-lib/ai/exemplos/mapa-rede/mapa-rede-screen.tsx +227 -0
- package/dist-lib/ai/exemplos/order-detail/components/ActivityTab.tsx +77 -0
- package/dist-lib/ai/exemplos/order-detail/components/AttachmentsTab.tsx +45 -0
- package/dist-lib/ai/exemplos/order-detail/components/CommentsTab.tsx +111 -0
- package/dist-lib/ai/exemplos/order-detail/components/DetailsTab.tsx +189 -0
- package/dist-lib/ai/exemplos/order-detail/components/OverviewTab.tsx +200 -0
- package/dist-lib/ai/exemplos/order-detail/components/section-card.tsx +85 -0
- package/dist-lib/ai/exemplos/order-detail/index.ts +1 -0
- package/dist-lib/ai/exemplos/order-detail/order-detail-screen.tsx +119 -0
- package/dist-lib/ai/exemplos/order-detail/order-mocks.ts +186 -0
- package/dist-lib/ai/exemplos/order-detail/order.types.ts +118 -0
- package/dist-lib/ai/indice.json +30 -15
- package/dist-lib/ai/manifest.json +31 -5
- package/dist-lib/ai/roteiros/app-builder/roteiro.md +155 -0
- package/dist-lib/ai/roteiros/auth-builder/roteiro.md +42 -0
- package/dist-lib/ai/roteiros/cards/roteiro.md +34 -0
- package/dist-lib/ai/roteiros/charts/roteiro.md +32 -0
- package/dist-lib/ai/roteiros/chat/roteiro.md +31 -0
- package/dist-lib/ai/roteiros/crud-builder/blueprint.md +63 -0
- package/dist-lib/ai/roteiros/crud-builder/entrevista.md +139 -0
- package/dist-lib/ai/roteiros/crud-builder/geracao.md +113 -0
- package/dist-lib/ai/roteiros/crud-builder/roteiro.md +89 -0
- package/dist-lib/ai/roteiros/drawers/roteiro.md +41 -0
- package/dist-lib/ai/roteiros/list-builder/blueprint.md +94 -0
- package/dist-lib/ai/roteiros/list-builder/entrevista.md +188 -0
- package/dist-lib/ai/roteiros/list-builder/geracao.md +63 -0
- package/dist-lib/ai/roteiros/list-builder/roteiro.md +91 -0
- package/dist-lib/ai/roteiros/module-replicator/roteiro.md +56 -0
- package/dist-lib/ai/roteiros/page-detail/roteiro.md +32 -0
- package/dist-lib/ai/roteiros/page-edit/roteiro.md +31 -0
- package/dist-lib/ai/roteiros/screen-composer/roteiro.md +95 -0
- package/package.json +1 -1
- /package/dist-lib/ai/roteiros/{dashboard → dashboard-builder}/blueprint.md +0 -0
- /package/dist-lib/ai/roteiros/{dashboard → dashboard-builder}/entrevista.md +0 -0
- /package/dist-lib/ai/roteiros/{dashboard → dashboard-builder}/geracao.md +0 -0
- /package/dist-lib/ai/roteiros/{dashboard → dashboard-builder}/roteiro.md +0 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CLIENTS_MOCK,
|
|
3
|
+
STATUSES,
|
|
4
|
+
CATEGORIES,
|
|
5
|
+
AGENTS,
|
|
6
|
+
type ClientRow,
|
|
7
|
+
} from "./_table-data";
|
|
8
|
+
|
|
9
|
+
/* ── Dataset 87 clientes (bate com badge do print) ─────────────── */
|
|
10
|
+
|
|
11
|
+
export const CLIENTS_87: ClientRow[] = Array.from({ length: 9 }, (_, batch) =>
|
|
12
|
+
CLIENTS_MOCK.map((row, i) => ({
|
|
13
|
+
...row,
|
|
14
|
+
id: `CLI-${2400 + batch * 10 + i}`,
|
|
15
|
+
})),
|
|
16
|
+
)
|
|
17
|
+
.flat()
|
|
18
|
+
.slice(0, 87);
|
|
19
|
+
|
|
20
|
+
/* ── Lookups por categoria de status (column color mapping) ────── */
|
|
21
|
+
|
|
22
|
+
export const STATUS_COLOR_MAP: Record<string, string> = {
|
|
23
|
+
active: "success",
|
|
24
|
+
pending: "warning",
|
|
25
|
+
paused: "info",
|
|
26
|
+
inactive: "neutral",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const STATUS_DOT: Record<string, string> = {
|
|
30
|
+
active: "var(--color-fg-success)",
|
|
31
|
+
pending: "var(--color-fg-warning)",
|
|
32
|
+
paused: "var(--color-fg-info)",
|
|
33
|
+
inactive: "var(--color-fg-muted)",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const CATEGORY_KIND: Record<string, "warning" | "info" | "success"> = {
|
|
37
|
+
royal: "warning",
|
|
38
|
+
licenciado: "info",
|
|
39
|
+
lead: "success",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/* ── Options pra filters/selects ───────────────────────────────── */
|
|
43
|
+
|
|
44
|
+
export const STATUS_OPTIONS = Object.entries(STATUSES).map(([v, m]) => ({
|
|
45
|
+
value: v,
|
|
46
|
+
label: m.label,
|
|
47
|
+
color: STATUS_COLOR_MAP[v] ?? "neutral",
|
|
48
|
+
}));
|
|
49
|
+
|
|
50
|
+
export const CATEGORY_OPTIONS = Object.entries(CATEGORIES).map(([v, m]) => ({
|
|
51
|
+
value: v,
|
|
52
|
+
label: m.label,
|
|
53
|
+
color: m.kind === "neutral" ? "neutral" : m.kind,
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
export const AGENT_OPTIONS = Object.entries(AGENTS).map(([v, a]) => ({
|
|
57
|
+
value: v,
|
|
58
|
+
label: a.name,
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
/* ── Avatar lookup pro column type "user" ──────────────────────── */
|
|
62
|
+
|
|
63
|
+
export const AGENTS_LOOKUP = Object.fromEntries(
|
|
64
|
+
Object.entries(AGENTS).map(([id, a]) => [
|
|
65
|
+
id,
|
|
66
|
+
{
|
|
67
|
+
name: a.name,
|
|
68
|
+
initials: a.name
|
|
69
|
+
.split(" ")
|
|
70
|
+
.map((s) => s[0])
|
|
71
|
+
.join("")
|
|
72
|
+
.slice(0, 2),
|
|
73
|
+
color: a.color,
|
|
74
|
+
},
|
|
75
|
+
]),
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
/* ── Kanban columns (1 por status) ─────────────────────────────── */
|
|
79
|
+
|
|
80
|
+
export const KANBAN_COLUMNS = Object.entries(STATUSES).map(([id, s]) => ({
|
|
81
|
+
id,
|
|
82
|
+
label: s.label,
|
|
83
|
+
dotColor: STATUS_DOT[id] ?? "var(--color-fg-muted)",
|
|
84
|
+
}));
|
|
85
|
+
|
|
86
|
+
/* ── Cidades pro select do drawer ──────────────────────────────── */
|
|
87
|
+
|
|
88
|
+
export const CITIES = [
|
|
89
|
+
"São Paulo, SP",
|
|
90
|
+
"Rio de Janeiro, RJ",
|
|
91
|
+
"Belo Horizonte, MG",
|
|
92
|
+
"Porto Alegre, RS",
|
|
93
|
+
"Curitiba, PR",
|
|
94
|
+
"Recife, PE",
|
|
95
|
+
"Salvador, BA",
|
|
96
|
+
"Fortaleza, CE",
|
|
97
|
+
"Brasília, DF",
|
|
98
|
+
"Manaus, AM",
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
/* ── Helpers de formatação ─────────────────────────────────────── */
|
|
102
|
+
|
|
103
|
+
export function formatBRL(value: number) {
|
|
104
|
+
return value.toLocaleString("pt-BR", {
|
|
105
|
+
style: "currency",
|
|
106
|
+
currency: "BRL",
|
|
107
|
+
minimumFractionDigits: 2,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function formatShortDate(ms: number) {
|
|
112
|
+
if (!ms) return "—";
|
|
113
|
+
const d = new Date(ms);
|
|
114
|
+
const day = d.getDate().toString().padStart(2, "0");
|
|
115
|
+
const months = ["jan", "fev", "mar", "abr", "mai", "jun", "jul", "ago", "set", "out", "nov", "dez"];
|
|
116
|
+
return `${day} ${months[d.getMonth()]}`;
|
|
117
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { tv } from "@/utils/tv";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Layout em grid de 2 colunas usado dentro do drawer pra pares de campos
|
|
5
|
+
* (Email + WhatsApp, Cidade + Valor).
|
|
6
|
+
* `gap-form-gap` (20px) = token DS pra spacing entre FormField units (L-024).
|
|
7
|
+
*/
|
|
8
|
+
export const drawerFormStyles = tv({
|
|
9
|
+
slots: {
|
|
10
|
+
twoCols: "grid grid-cols-2 gap-form-gap",
|
|
11
|
+
statusDot: "inline-block size-[8px] rounded-radius-full mr-gp-xs shrink-0",
|
|
12
|
+
},
|
|
13
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { STATUSES, CATEGORIES } from "./_table-data";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Payload do formulário do drawer "Novo cliente".
|
|
5
|
+
* Consumido por <NovoClienteDrawer onSubmit={...}> no main page.
|
|
6
|
+
*/
|
|
7
|
+
export type NovoClienteFormData = {
|
|
8
|
+
name: string;
|
|
9
|
+
email: string;
|
|
10
|
+
whatsapp: string;
|
|
11
|
+
statusId: keyof typeof STATUSES;
|
|
12
|
+
categoryId: keyof typeof CATEGORIES;
|
|
13
|
+
city: string;
|
|
14
|
+
value: number;
|
|
15
|
+
notes: string;
|
|
16
|
+
};
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { Panel } from "@/components/ui/Panel";
|
|
3
|
+
import { Button } from "@/components/ui/Button/button";
|
|
4
|
+
import {
|
|
5
|
+
FormField,
|
|
6
|
+
FormFieldInput,
|
|
7
|
+
FormFieldSelect,
|
|
8
|
+
} from "@/components/ui/FormField";
|
|
9
|
+
import { ChipGroup, ChipGroupItem } from "@/components/ui/Chip";
|
|
10
|
+
import { Switch } from "@/components/shadcn/switch";
|
|
11
|
+
import { AGENTS } from "../../_table-data";
|
|
12
|
+
import { ACCOUNT_STATUS, BANKS } from "../../clientes-financeiro-mocks";
|
|
13
|
+
import type {
|
|
14
|
+
AccountStatus,
|
|
15
|
+
BankId,
|
|
16
|
+
FinanceClientRow,
|
|
17
|
+
PaymentMethod,
|
|
18
|
+
} from "../../clientes-financeiro.types";
|
|
19
|
+
|
|
20
|
+
const PAYMENT_METHODS: { value: PaymentMethod; label: string }[] = [
|
|
21
|
+
{ value: "pix", label: "PIX" },
|
|
22
|
+
{ value: "ted", label: "TED" },
|
|
23
|
+
{ value: "boleto", label: "Boleto" },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const BANK_OPTIONS = (Object.keys(BANKS) as BankId[]).map((id) => ({
|
|
27
|
+
value: id,
|
|
28
|
+
label: BANKS[id].name,
|
|
29
|
+
}));
|
|
30
|
+
|
|
31
|
+
const AGENT_OPTIONS = Object.entries(AGENTS).map(([id, a]) => ({
|
|
32
|
+
value: id,
|
|
33
|
+
label: a.name,
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
const STATUS_OPTIONS = (
|
|
37
|
+
Object.entries(ACCOUNT_STATUS) as [AccountStatus, (typeof ACCOUNT_STATUS)[AccountStatus]][]
|
|
38
|
+
).map(([value, m]) => ({ value, label: m.label, color: m.color }));
|
|
39
|
+
|
|
40
|
+
/** Campos editáveis da row financeira. */
|
|
41
|
+
export type FinanceEditPatch = {
|
|
42
|
+
name: string;
|
|
43
|
+
email: string;
|
|
44
|
+
phone: string;
|
|
45
|
+
companyName: string;
|
|
46
|
+
cnpj: string;
|
|
47
|
+
accountStatus: AccountStatus;
|
|
48
|
+
bank: BankId;
|
|
49
|
+
commissionRate: number;
|
|
50
|
+
autoWithdraw: boolean;
|
|
51
|
+
paymentMethods: PaymentMethod[];
|
|
52
|
+
agentId: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export type EditarFinanceDrawerProps = {
|
|
56
|
+
/** Row em edição — null fecha o drawer. */
|
|
57
|
+
client: FinanceClientRow | null;
|
|
58
|
+
onOpenChange: (open: boolean) => void;
|
|
59
|
+
/** Salva as alterações (mock no showcase). */
|
|
60
|
+
onSave?: (id: string, patch: FinanceEditPatch) => void;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Drawer "Editar licenciado" — campos REAIS da row financeira (não o form
|
|
65
|
+
* genérico de criação). Panel + FormField (Input/Select) + ChipGroup + Switch.
|
|
66
|
+
*/
|
|
67
|
+
export function EditarFinanceDrawer({
|
|
68
|
+
client,
|
|
69
|
+
onOpenChange,
|
|
70
|
+
onSave,
|
|
71
|
+
}: EditarFinanceDrawerProps) {
|
|
72
|
+
const [form, setForm] = useState<FinanceEditPatch | null>(null);
|
|
73
|
+
|
|
74
|
+
// (Re)hidrata o form ao abrir/trocar de cliente.
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (!client) return;
|
|
77
|
+
setForm({
|
|
78
|
+
name: client.name,
|
|
79
|
+
email: client.email,
|
|
80
|
+
phone: client.phone,
|
|
81
|
+
companyName: client.companyName,
|
|
82
|
+
cnpj: client.cnpj,
|
|
83
|
+
accountStatus: client.accountStatus,
|
|
84
|
+
bank: client.bankAccount.bank,
|
|
85
|
+
commissionRate: client.commissionRate,
|
|
86
|
+
autoWithdraw: client.autoWithdraw,
|
|
87
|
+
paymentMethods: client.paymentMethods,
|
|
88
|
+
agentId: client.agentId as string,
|
|
89
|
+
});
|
|
90
|
+
}, [client]);
|
|
91
|
+
|
|
92
|
+
const patch = (p: Partial<FinanceEditPatch>) =>
|
|
93
|
+
setForm((f) => (f ? { ...f, ...p } : f));
|
|
94
|
+
|
|
95
|
+
const canSave = !!form && form.name.trim().length > 0;
|
|
96
|
+
|
|
97
|
+
const handleSave = () => {
|
|
98
|
+
if (!client || !form || !canSave) return;
|
|
99
|
+
onSave?.(client.id, form);
|
|
100
|
+
onOpenChange(false);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<Panel
|
|
105
|
+
open={client !== null}
|
|
106
|
+
onOpenChange={onOpenChange}
|
|
107
|
+
title={client ? `Editar — ${client.name}` : "Editar licenciado"}
|
|
108
|
+
side="right"
|
|
109
|
+
size="md"
|
|
110
|
+
footer={
|
|
111
|
+
<>
|
|
112
|
+
<Button variant="outline" color="secondary" size="md" onClick={() => onOpenChange(false)}>
|
|
113
|
+
Cancelar
|
|
114
|
+
</Button>
|
|
115
|
+
<Button
|
|
116
|
+
variant="filled"
|
|
117
|
+
color="primary"
|
|
118
|
+
size="md"
|
|
119
|
+
disabled={!canSave}
|
|
120
|
+
onClick={handleSave}
|
|
121
|
+
>
|
|
122
|
+
Salvar alterações
|
|
123
|
+
</Button>
|
|
124
|
+
</>
|
|
125
|
+
}
|
|
126
|
+
>
|
|
127
|
+
{form && (
|
|
128
|
+
<>
|
|
129
|
+
<FormFieldInput
|
|
130
|
+
label="Licenciado"
|
|
131
|
+
required
|
|
132
|
+
value={form.name}
|
|
133
|
+
onChange={(e) => patch({ name: e.target.value })}
|
|
134
|
+
/>
|
|
135
|
+
|
|
136
|
+
<div className="grid grid-cols-2 gap-form-gap">
|
|
137
|
+
<FormFieldInput
|
|
138
|
+
label="Email"
|
|
139
|
+
type="email"
|
|
140
|
+
value={form.email}
|
|
141
|
+
onChange={(e) => patch({ email: e.target.value })}
|
|
142
|
+
/>
|
|
143
|
+
<FormFieldInput
|
|
144
|
+
label="Telefone"
|
|
145
|
+
type="tel"
|
|
146
|
+
value={form.phone}
|
|
147
|
+
onChange={(e) => patch({ phone: e.target.value })}
|
|
148
|
+
/>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<FormFieldInput
|
|
152
|
+
label="Razão Social"
|
|
153
|
+
value={form.companyName}
|
|
154
|
+
onChange={(e) => patch({ companyName: e.target.value })}
|
|
155
|
+
/>
|
|
156
|
+
<FormFieldInput
|
|
157
|
+
label="CNPJ"
|
|
158
|
+
value={form.cnpj}
|
|
159
|
+
onChange={(e) => patch({ cnpj: e.target.value })}
|
|
160
|
+
/>
|
|
161
|
+
|
|
162
|
+
<FormField label="Situação">
|
|
163
|
+
{() => (
|
|
164
|
+
<ChipGroup
|
|
165
|
+
type="single"
|
|
166
|
+
value={form.accountStatus}
|
|
167
|
+
onValueChange={(v) => v && patch({ accountStatus: v as AccountStatus })}
|
|
168
|
+
size="md"
|
|
169
|
+
shape="pill"
|
|
170
|
+
ariaLabel="Situação da conta"
|
|
171
|
+
>
|
|
172
|
+
{STATUS_OPTIONS.map((o) => (
|
|
173
|
+
<ChipGroupItem key={o.value} value={o.value}>
|
|
174
|
+
{o.label}
|
|
175
|
+
</ChipGroupItem>
|
|
176
|
+
))}
|
|
177
|
+
</ChipGroup>
|
|
178
|
+
)}
|
|
179
|
+
</FormField>
|
|
180
|
+
|
|
181
|
+
<div className="grid grid-cols-2 gap-form-gap">
|
|
182
|
+
<FormFieldSelect
|
|
183
|
+
label="Banco"
|
|
184
|
+
value={form.bank}
|
|
185
|
+
onValueChange={(v) => patch({ bank: v as BankId })}
|
|
186
|
+
options={BANK_OPTIONS}
|
|
187
|
+
/>
|
|
188
|
+
<FormFieldInput
|
|
189
|
+
label="Comissão (%)"
|
|
190
|
+
type="number"
|
|
191
|
+
value={String(form.commissionRate)}
|
|
192
|
+
onChange={(e) => patch({ commissionRate: Number(e.target.value) })}
|
|
193
|
+
min={0}
|
|
194
|
+
max={100}
|
|
195
|
+
step={0.1}
|
|
196
|
+
/>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<FormFieldSelect
|
|
200
|
+
label="Gestor da conta"
|
|
201
|
+
value={form.agentId}
|
|
202
|
+
onValueChange={(v) => patch({ agentId: v })}
|
|
203
|
+
options={AGENT_OPTIONS}
|
|
204
|
+
/>
|
|
205
|
+
|
|
206
|
+
<FormField label="Métodos de recebimento">
|
|
207
|
+
{() => (
|
|
208
|
+
<ChipGroup
|
|
209
|
+
type="multiple"
|
|
210
|
+
value={form.paymentMethods}
|
|
211
|
+
onValueChange={(v) => patch({ paymentMethods: v as PaymentMethod[] })}
|
|
212
|
+
size="md"
|
|
213
|
+
shape="rounded"
|
|
214
|
+
ariaLabel="Métodos de recebimento"
|
|
215
|
+
>
|
|
216
|
+
{PAYMENT_METHODS.map((m) => (
|
|
217
|
+
<ChipGroupItem key={m.value} value={m.value}>
|
|
218
|
+
{m.label}
|
|
219
|
+
</ChipGroupItem>
|
|
220
|
+
))}
|
|
221
|
+
</ChipGroup>
|
|
222
|
+
)}
|
|
223
|
+
</FormField>
|
|
224
|
+
|
|
225
|
+
<FormField
|
|
226
|
+
label="Saque automático"
|
|
227
|
+
helperText="Quando ligado, saques são processados automaticamente."
|
|
228
|
+
>
|
|
229
|
+
{() => (
|
|
230
|
+
<Switch
|
|
231
|
+
checked={form.autoWithdraw}
|
|
232
|
+
onCheckedChange={(next) => patch({ autoWithdraw: next })}
|
|
233
|
+
aria-label="Saque automático"
|
|
234
|
+
/>
|
|
235
|
+
)}
|
|
236
|
+
</FormField>
|
|
237
|
+
</>
|
|
238
|
+
)}
|
|
239
|
+
</Panel>
|
|
240
|
+
);
|
|
241
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ArrowDownLeft,
|
|
3
|
+
ArrowUpRight,
|
|
4
|
+
Mail,
|
|
5
|
+
MapPin,
|
|
6
|
+
Phone,
|
|
7
|
+
Wallet,
|
|
8
|
+
} from "lucide-react";
|
|
9
|
+
import { Avatar } from "@/components/ui/avatar-ig";
|
|
10
|
+
import { Chip } from "@/components/ui/Chip";
|
|
11
|
+
import {
|
|
12
|
+
ACCOUNT_STATUS,
|
|
13
|
+
BANKS,
|
|
14
|
+
formatBRL,
|
|
15
|
+
formatDateTimeShort,
|
|
16
|
+
} from "../../clientes-financeiro-mocks";
|
|
17
|
+
import type { FinanceClientRow } from "../../clientes-financeiro.types";
|
|
18
|
+
|
|
19
|
+
/* ── Linha do extrato ───────────────────────────────────────────── */
|
|
20
|
+
|
|
21
|
+
function TransactionRow({
|
|
22
|
+
date,
|
|
23
|
+
type,
|
|
24
|
+
amount,
|
|
25
|
+
description,
|
|
26
|
+
}: FinanceClientRow["transactions"][number]) {
|
|
27
|
+
const isIn = type === "in";
|
|
28
|
+
return (
|
|
29
|
+
<li className="flex items-center gap-gp-md py-pad-sm">
|
|
30
|
+
<span
|
|
31
|
+
className={`grid place-items-center size-form-md shrink-0 rounded-radius-md ${
|
|
32
|
+
isIn
|
|
33
|
+
? "bg-bg-success-muted text-fg-success"
|
|
34
|
+
: "bg-bg-danger-muted text-fg-danger"
|
|
35
|
+
}`}
|
|
36
|
+
aria-hidden="true"
|
|
37
|
+
>
|
|
38
|
+
{isIn ? (
|
|
39
|
+
<ArrowDownLeft className="size-icon-sm" strokeWidth={2} />
|
|
40
|
+
) : (
|
|
41
|
+
<ArrowUpRight className="size-icon-sm" strokeWidth={2} />
|
|
42
|
+
)}
|
|
43
|
+
</span>
|
|
44
|
+
<div className="flex flex-col min-w-0 flex-1">
|
|
45
|
+
<span className="text-body-sm font-medium text-fg-default truncate">
|
|
46
|
+
{description}
|
|
47
|
+
</span>
|
|
48
|
+
<span className="text-caption-md text-fg-muted tabular-nums">
|
|
49
|
+
{formatDateTimeShort(date)}
|
|
50
|
+
</span>
|
|
51
|
+
</div>
|
|
52
|
+
<span
|
|
53
|
+
className={`text-body-sm font-semibold tabular-nums shrink-0 ${
|
|
54
|
+
isIn ? "text-fg-success" : "text-fg-danger"
|
|
55
|
+
}`}
|
|
56
|
+
>
|
|
57
|
+
{isIn ? "+" : "−"} {formatBRL(amount)}
|
|
58
|
+
</span>
|
|
59
|
+
</li>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* ── Item label/valor das colunas laterais ─────────────────────── */
|
|
64
|
+
|
|
65
|
+
function InfoRow({
|
|
66
|
+
icon: Icon,
|
|
67
|
+
label,
|
|
68
|
+
children,
|
|
69
|
+
}: {
|
|
70
|
+
icon: React.ComponentType<{ className?: string; strokeWidth?: number }>;
|
|
71
|
+
label: string;
|
|
72
|
+
children: React.ReactNode;
|
|
73
|
+
}) {
|
|
74
|
+
return (
|
|
75
|
+
<div className="flex items-start gap-gp-sm">
|
|
76
|
+
<Icon className="size-icon-sm text-fg-muted mt-[2px] shrink-0" strokeWidth={1.8} />
|
|
77
|
+
<div className="flex flex-col gap-gp-2xs min-w-0">
|
|
78
|
+
<span className="text-body-sm text-fg-strong break-words">{children}</span>
|
|
79
|
+
<span className="text-body-xs font-normal text-fg-muted">{label}</span>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* ── Painel de expansão da row ──────────────────────────────────── */
|
|
86
|
+
|
|
87
|
+
export function ExtratoExpansion({ row }: { row: FinanceClientRow }) {
|
|
88
|
+
const status = ACCOUNT_STATUS[row.accountStatus];
|
|
89
|
+
const bank = BANKS[row.bankAccount.bank];
|
|
90
|
+
// Extrato exibe as 5 movimentações mais recentes (transactions já vem ordenado desc).
|
|
91
|
+
const recent = row.transactions.slice(0, 5);
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div className="w-full p-pad-4xl bg-bg-subtle">
|
|
95
|
+
<div className="grid grid-cols-1 lg:grid-cols-[1.5fr_1fr] gap-gp-4xl lg:gap-gp-6xl max-w-[1100px]">
|
|
96
|
+
{/* Extrato */}
|
|
97
|
+
<section className="flex flex-col gap-gp-lg">
|
|
98
|
+
<header className="flex items-center justify-between gap-gp-md">
|
|
99
|
+
<h4 className="m-0 text-body-md font-semibold text-fg-muted uppercase tracking-wider">
|
|
100
|
+
Extrato recente
|
|
101
|
+
</h4>
|
|
102
|
+
<Chip color={status.color} variant="soft" size="sm" shape="pill">
|
|
103
|
+
{status.label}
|
|
104
|
+
</Chip>
|
|
105
|
+
</header>
|
|
106
|
+
<ul className="flex flex-col divide-y divide-border-subtle m-0 p-0 list-none">
|
|
107
|
+
{recent.map((tx) => (
|
|
108
|
+
<TransactionRow key={tx.id} {...tx} />
|
|
109
|
+
))}
|
|
110
|
+
</ul>
|
|
111
|
+
</section>
|
|
112
|
+
|
|
113
|
+
{/* Dados bancários + contato + resumo — separados do extrato por
|
|
114
|
+
divisória sutil + respiro (evita poluição visual de "grudado"). */}
|
|
115
|
+
<div className="flex flex-col gap-gp-3xl lg:pl-gp-6xl lg:border-l lg:border-border-subtle">
|
|
116
|
+
<section className="flex flex-col gap-gp-md">
|
|
117
|
+
<h4 className="m-0 text-body-md font-semibold text-fg-muted uppercase tracking-wider">
|
|
118
|
+
Conta bancária
|
|
119
|
+
</h4>
|
|
120
|
+
<div className="flex items-center gap-gp-md">
|
|
121
|
+
<Avatar size="lg" colorHex={bank.color} className="text-caption-md font-bold">
|
|
122
|
+
{bank.initials}
|
|
123
|
+
</Avatar>
|
|
124
|
+
<div className="flex flex-col min-w-0">
|
|
125
|
+
<span className="text-body-sm font-medium text-fg-default truncate">
|
|
126
|
+
{row.bankAccount.bankName}
|
|
127
|
+
</span>
|
|
128
|
+
<span className="text-caption-md text-fg-muted tabular-nums">
|
|
129
|
+
Ag {row.bankAccount.agency} · {row.bankAccount.account}
|
|
130
|
+
</span>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</section>
|
|
134
|
+
|
|
135
|
+
<section className="flex flex-col gap-gp-md">
|
|
136
|
+
<h4 className="m-0 text-body-md font-semibold text-fg-muted uppercase tracking-wider">
|
|
137
|
+
Contato
|
|
138
|
+
</h4>
|
|
139
|
+
<InfoRow icon={Mail} label="Email">{row.email}</InfoRow>
|
|
140
|
+
<InfoRow icon={Phone} label="Telefone">{row.phone}</InfoRow>
|
|
141
|
+
<InfoRow icon={MapPin} label="Localização">{row.location}</InfoRow>
|
|
142
|
+
</section>
|
|
143
|
+
|
|
144
|
+
<section className="flex flex-col gap-gp-md">
|
|
145
|
+
<h4 className="m-0 text-body-md font-semibold text-fg-muted uppercase tracking-wider">
|
|
146
|
+
Resumo financeiro
|
|
147
|
+
</h4>
|
|
148
|
+
<InfoRow icon={Wallet} label="Saldo disponível">
|
|
149
|
+
<span className="font-semibold tabular-nums text-fg-success">
|
|
150
|
+
{formatBRL(row.availableBalance)}
|
|
151
|
+
</span>
|
|
152
|
+
</InfoRow>
|
|
153
|
+
<div className="flex gap-gp-2xl">
|
|
154
|
+
<div className="flex flex-col gap-gp-2xs">
|
|
155
|
+
<span className="text-body-sm font-medium text-fg-default tabular-nums">
|
|
156
|
+
{formatBRL(row.monthlyVolume)}
|
|
157
|
+
</span>
|
|
158
|
+
<span className="text-body-xs font-normal text-fg-muted">Volume mensal</span>
|
|
159
|
+
</div>
|
|
160
|
+
<div className="flex flex-col gap-gp-2xs">
|
|
161
|
+
<span className="text-body-sm font-medium text-fg-default tabular-nums">
|
|
162
|
+
{row.commissionRate.toLocaleString("pt-BR", { minimumFractionDigits: 1 })}%
|
|
163
|
+
</span>
|
|
164
|
+
<span className="text-body-xs font-normal text-fg-muted">Comissão</span>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</section>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ExtratoExpansion } from "./extrato-expansion";
|