@snksergio/design-system 0.62.0 → 0.64.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 (149) 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/DataTable.md +2 -1
  7. package/dist-lib/ai/componentes/TableToolbar.md +1 -1
  8. package/dist-lib/ai/exemplos/app-shell/app-shell-example.tsx +140 -0
  9. package/dist-lib/ai/exemplos/app-shell/index.ts +3 -0
  10. package/dist-lib/ai/exemplos/app-shell/nav-data.ts +97 -0
  11. package/dist-lib/ai/exemplos/app-shell/routes.tsx +75 -0
  12. package/dist-lib/ai/exemplos/chat/chat-screen.tsx +152 -0
  13. package/dist-lib/ai/exemplos/chat/chat-v2-mocks.ts +171 -0
  14. package/dist-lib/ai/exemplos/chat/chat-v2.styles.ts +23 -0
  15. package/dist-lib/ai/exemplos/chat/chat-v2.types.ts +81 -0
  16. package/dist-lib/ai/exemplos/chat/components/ChannelDot/channel-dot.tsx +28 -0
  17. package/dist-lib/ai/exemplos/chat/components/ConversationActionsMenu/conversation-actions-menu.tsx +83 -0
  18. package/dist-lib/ai/exemplos/chat/components/ConversationActionsMenu/index.ts +4 -0
  19. package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.styles.ts +22 -0
  20. package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.tsx +203 -0
  21. package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.types.ts +10 -0
  22. package/dist-lib/ai/exemplos/chat/components/ConversationColumn/index.ts +2 -0
  23. package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.styles.ts +55 -0
  24. package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.tsx +52 -0
  25. package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.types.ts +7 -0
  26. package/dist-lib/ai/exemplos/chat/components/ConversationListItem/index.ts +2 -0
  27. package/dist-lib/ai/exemplos/chat/components/DateSeparator/date-separator.tsx +18 -0
  28. package/dist-lib/ai/exemplos/chat/components/DetailField/detail-field.tsx +23 -0
  29. package/dist-lib/ai/exemplos/chat/components/DetailSection/detail-section.tsx +41 -0
  30. package/dist-lib/ai/exemplos/chat/components/DetailSection/index.ts +1 -0
  31. package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.styles.ts +30 -0
  32. package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.tsx +142 -0
  33. package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.types.ts +16 -0
  34. package/dist-lib/ai/exemplos/chat/components/DetailsColumn/index.ts +5 -0
  35. package/dist-lib/ai/exemplos/chat/components/FilterRow/filter-row.styles.ts +37 -0
  36. package/dist-lib/ai/exemplos/chat/components/FilterRow/filter-row.tsx +45 -0
  37. package/dist-lib/ai/exemplos/chat/components/FilterRow/index.ts +1 -0
  38. package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.styles.ts +27 -0
  39. package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.tsx +66 -0
  40. package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.types.ts +13 -0
  41. package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-rail.tsx +38 -0
  42. package/dist-lib/ai/exemplos/chat/components/FiltersColumn/index.ts +6 -0
  43. package/dist-lib/ai/exemplos/chat/components/MessageBubble/index.ts +2 -0
  44. package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.styles.ts +37 -0
  45. package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.tsx +29 -0
  46. package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.types.ts +5 -0
  47. package/dist-lib/ai/exemplos/chat/components/PersonAvatar/index.ts +5 -0
  48. package/dist-lib/ai/exemplos/chat/components/PersonAvatar/person-avatar.tsx +31 -0
  49. package/dist-lib/ai/exemplos/chat/components/QueueColumn/index.ts +2 -0
  50. package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.styles.ts +19 -0
  51. package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.tsx +140 -0
  52. package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.types.ts +13 -0
  53. package/dist-lib/ai/exemplos/chat/components/RailItem/index.ts +1 -0
  54. package/dist-lib/ai/exemplos/chat/components/RailItem/rail-item.styles.ts +30 -0
  55. package/dist-lib/ai/exemplos/chat/components/RailItem/rail-item.tsx +42 -0
  56. package/dist-lib/ai/exemplos/chat/hooks/use-resizable.ts +98 -0
  57. package/dist-lib/ai/exemplos/chat/index.ts +1 -0
  58. package/dist-lib/ai/exemplos/clientes/_table-data.ts +59 -0
  59. package/dist-lib/ai/exemplos/clientes/clientes-screen.tsx +505 -0
  60. package/dist-lib/ai/exemplos/clientes/clientes-showcase-mocks.ts +117 -0
  61. package/dist-lib/ai/exemplos/clientes/clientes-showcase.styles.ts +13 -0
  62. package/dist-lib/ai/exemplos/clientes/clientes-showcase.types.ts +16 -0
  63. package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.styles.ts +28 -0
  64. package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.tsx +303 -0
  65. package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.types.ts +14 -0
  66. package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/index.ts +2 -0
  67. package/dist-lib/ai/exemplos/clientes/components/NovoClienteDrawer/index.ts +2 -0
  68. package/dist-lib/ai/exemplos/clientes/components/NovoClienteDrawer/novo-cliente-drawer.tsx +207 -0
  69. package/dist-lib/ai/exemplos/clientes/index.ts +1 -0
  70. package/dist-lib/ai/exemplos/edit-page/components/StepNav.tsx +81 -0
  71. package/dist-lib/ai/exemplos/edit-page/components/section-card.tsx +85 -0
  72. package/dist-lib/ai/exemplos/edit-page/edit-page-screen.tsx +234 -0
  73. package/dist-lib/ai/exemplos/edit-page/index.ts +1 -0
  74. package/dist-lib/ai/exemplos/finance/_table-data.ts +57 -0
  75. package/dist-lib/ai/exemplos/finance/clientes-financeiro-mocks.ts +227 -0
  76. package/dist-lib/ai/exemplos/finance/clientes-financeiro.types.ts +75 -0
  77. package/dist-lib/ai/exemplos/finance/clientes-showcase-mocks.ts +117 -0
  78. package/dist-lib/ai/exemplos/finance/clientes-showcase.styles.ts +13 -0
  79. package/dist-lib/ai/exemplos/finance/clientes-showcase.types.ts +16 -0
  80. package/dist-lib/ai/exemplos/finance/components/EditarFinanceDrawer/editar-finance-drawer.tsx +241 -0
  81. package/dist-lib/ai/exemplos/finance/components/EditarFinanceDrawer/index.ts +5 -0
  82. package/dist-lib/ai/exemplos/finance/components/ExtratoExpansion/extrato-expansion.tsx +172 -0
  83. package/dist-lib/ai/exemplos/finance/components/ExtratoExpansion/index.ts +1 -0
  84. package/dist-lib/ai/exemplos/finance/components/FinanceDetailPanel/finance-detail-panel.tsx +241 -0
  85. package/dist-lib/ai/exemplos/finance/components/FinanceDetailPanel/index.ts +2 -0
  86. package/dist-lib/ai/exemplos/finance/components/NovoClienteDrawer/index.ts +2 -0
  87. package/dist-lib/ai/exemplos/finance/components/NovoClienteDrawer/novo-cliente-drawer.tsx +207 -0
  88. package/dist-lib/ai/exemplos/finance/components/SacarDialog/index.ts +2 -0
  89. package/dist-lib/ai/exemplos/finance/components/SacarDialog/sacar-dialog.tsx +346 -0
  90. package/dist-lib/ai/exemplos/finance/finance-screen.tsx +821 -0
  91. package/dist-lib/ai/exemplos/finance/index.ts +1 -0
  92. package/dist-lib/ai/exemplos/gantt/_gantt-data.tsx +402 -0
  93. package/dist-lib/ai/exemplos/gantt/gantt-screen.tsx +512 -0
  94. package/dist-lib/ai/exemplos/gantt/index.ts +1 -0
  95. package/dist-lib/ai/exemplos/login/index.ts +1 -0
  96. package/dist-lib/ai/exemplos/login/login-screen.tsx +246 -0
  97. package/dist-lib/ai/exemplos/mapa-rede/components/ConsultorDetailPanel/consultor-detail-panel.tsx +158 -0
  98. package/dist-lib/ai/exemplos/mapa-rede/components/ConsultorDetailPanel/index.ts +2 -0
  99. package/dist-lib/ai/exemplos/mapa-rede/index.ts +1 -0
  100. package/dist-lib/ai/exemplos/mapa-rede/mapa-de-rede-mocks.ts +655 -0
  101. package/dist-lib/ai/exemplos/mapa-rede/mapa-de-rede.types.ts +53 -0
  102. package/dist-lib/ai/exemplos/mapa-rede/mapa-rede-screen.tsx +227 -0
  103. package/dist-lib/ai/exemplos/order-detail/components/ActivityTab.tsx +77 -0
  104. package/dist-lib/ai/exemplos/order-detail/components/AttachmentsTab.tsx +45 -0
  105. package/dist-lib/ai/exemplos/order-detail/components/CommentsTab.tsx +111 -0
  106. package/dist-lib/ai/exemplos/order-detail/components/DetailsTab.tsx +189 -0
  107. package/dist-lib/ai/exemplos/order-detail/components/OverviewTab.tsx +200 -0
  108. package/dist-lib/ai/exemplos/order-detail/components/section-card.tsx +85 -0
  109. package/dist-lib/ai/exemplos/order-detail/index.ts +1 -0
  110. package/dist-lib/ai/exemplos/order-detail/order-detail-screen.tsx +119 -0
  111. package/dist-lib/ai/exemplos/order-detail/order-mocks.ts +186 -0
  112. package/dist-lib/ai/exemplos/order-detail/order.types.ts +118 -0
  113. package/dist-lib/ai/global/componentes.md +7 -0
  114. package/dist-lib/ai/indice.json +30 -15
  115. package/dist-lib/ai/manifest.json +32 -6
  116. package/dist-lib/ai/roteiros/app-builder/roteiro.md +155 -0
  117. package/dist-lib/ai/roteiros/auth-builder/roteiro.md +42 -0
  118. package/dist-lib/ai/roteiros/cards/roteiro.md +34 -0
  119. package/dist-lib/ai/roteiros/charts/roteiro.md +32 -0
  120. package/dist-lib/ai/roteiros/chat/roteiro.md +31 -0
  121. package/dist-lib/ai/roteiros/crud-builder/blueprint.md +63 -0
  122. package/dist-lib/ai/roteiros/crud-builder/entrevista.md +146 -0
  123. package/dist-lib/ai/roteiros/crud-builder/geracao.md +113 -0
  124. package/dist-lib/ai/roteiros/crud-builder/roteiro.md +89 -0
  125. package/dist-lib/ai/roteiros/drawers/roteiro.md +41 -0
  126. package/dist-lib/ai/roteiros/list-builder/blueprint.md +94 -0
  127. package/dist-lib/ai/roteiros/list-builder/entrevista.md +193 -0
  128. package/dist-lib/ai/roteiros/list-builder/geracao.md +63 -0
  129. package/dist-lib/ai/roteiros/list-builder/roteiro.md +91 -0
  130. package/dist-lib/ai/roteiros/module-replicator/roteiro.md +56 -0
  131. package/dist-lib/ai/roteiros/page-detail/roteiro.md +32 -0
  132. package/dist-lib/ai/roteiros/page-edit/roteiro.md +31 -0
  133. package/dist-lib/ai/roteiros/screen-composer/roteiro.md +95 -0
  134. package/dist-lib/chunks/{panel-B-liPlxr.mjs → panel-CN0ChoHM.mjs} +5 -2
  135. package/dist-lib/chunks/{panel-B-liPlxr.mjs.map → panel-CN0ChoHM.mjs.map} +1 -1
  136. package/dist-lib/chunks/{panel-DoNbjeqX.cjs → panel-Cpq1xHck.cjs} +5 -2
  137. package/dist-lib/chunks/{panel-DoNbjeqX.cjs.map → panel-Cpq1xHck.cjs.map} +1 -1
  138. package/dist-lib/index.cjs +1 -1
  139. package/dist-lib/index.mjs +2 -2
  140. package/dist-lib/preview/clientes.cjs +1 -1
  141. package/dist-lib/preview/clientes.mjs +1 -1
  142. package/dist-lib/src/components/ui/DataTable/data-table.d.ts.map +1 -1
  143. package/dist-lib/src/components/ui/DataTable/data-table.types.d.ts +20 -2
  144. package/dist-lib/src/components/ui/DataTable/data-table.types.d.ts.map +1 -1
  145. package/package.json +1 -1
  146. /package/dist-lib/ai/roteiros/{dashboard → dashboard-builder}/blueprint.md +0 -0
  147. /package/dist-lib/ai/roteiros/{dashboard → dashboard-builder}/entrevista.md +0 -0
  148. /package/dist-lib/ai/roteiros/{dashboard → dashboard-builder}/geracao.md +0 -0
  149. /package/dist-lib/ai/roteiros/{dashboard → dashboard-builder}/roteiro.md +0 -0
@@ -0,0 +1,303 @@
1
+ import { useState, type ReactNode } from "react";
2
+ import { ChevronDown, Pencil, Trash2 } from "lucide-react";
3
+ import { Avatar, AvatarFallback } from "@/components/shadcn/avatar";
4
+ import { Button } from "@/components/ui/Button/button";
5
+ import { Chip } from "@/components/ui/Chip";
6
+ import { FloatingPanel } from "@/components/ui/FloatingPanel";
7
+ import { STATUSES, CATEGORIES, AGENTS } from "../../_table-data";
8
+ import {
9
+ CATEGORY_KIND,
10
+ STATUS_DOT,
11
+ formatBRL,
12
+ formatShortDate,
13
+ } from "../../clientes-showcase-mocks";
14
+ import { detailDrawerStyles } from "./detail-drawer.styles";
15
+ import type { DetailDrawerProps } from "./detail-drawer.types";
16
+
17
+ /* ── Helpers internos ────────────────────────────────────────────── */
18
+
19
+ function formatLongDate(ms: number): string {
20
+ if (!ms) return "—";
21
+ const d = new Date(ms);
22
+ return d.toLocaleDateString("pt-BR", {
23
+ day: "numeric",
24
+ month: "long",
25
+ year: "numeric",
26
+ });
27
+ }
28
+
29
+ type SectionId = "contact" | "meta" | "notes";
30
+
31
+ /* ── DetailField (label : value) ────────────────────────────────── */
32
+
33
+ type DetailFieldProps = {
34
+ label: string;
35
+ value: ReactNode;
36
+ s: ReturnType<typeof detailDrawerStyles>;
37
+ };
38
+
39
+ function DetailField({ label, value, s }: DetailFieldProps) {
40
+ return (
41
+ <div className={s.field()}>
42
+ <span className={s.fieldLabel()}>{label}</span>
43
+ <span className={s.fieldValue()}>{value || "—"}</span>
44
+ </div>
45
+ );
46
+ }
47
+
48
+ /* ── DetailSection (collapsible) ─────────────────────────────────── */
49
+
50
+ type DetailSectionProps = {
51
+ id: SectionId;
52
+ title: string;
53
+ open: boolean;
54
+ onToggle: (id: SectionId) => void;
55
+ children: ReactNode;
56
+ s: ReturnType<typeof detailDrawerStyles>;
57
+ };
58
+
59
+ function DetailSection({
60
+ id,
61
+ title,
62
+ open,
63
+ onToggle,
64
+ children,
65
+ s,
66
+ }: DetailSectionProps) {
67
+ return (
68
+ <section className={s.section()}>
69
+ <button
70
+ type="button"
71
+ className={s.sectionHead()}
72
+ onClick={() => onToggle(id)}
73
+ aria-expanded={open}
74
+ >
75
+ <span>{title}</span>
76
+ <ChevronDown
77
+ className={[s.sectionChev(), open ? s.sectionChevOpen() : ""].join(" ")}
78
+ />
79
+ </button>
80
+ {open && <div className={s.sectionBody()}>{children}</div>}
81
+ </section>
82
+ );
83
+ }
84
+
85
+ /* ── DetailDrawer ────────────────────────────────────────────────── */
86
+
87
+ /**
88
+ * Drawer "Detalhes do cliente" — usa `<FloatingPanel>` do DS (non-modal,
89
+ * resizable, maximizable, responsive). Replica visual do `TblDetailsPanel`
90
+ * do sandbox /design-and-table-v2.
91
+ */
92
+ export function DetailDrawer({
93
+ row,
94
+ onClose,
95
+ onEdit,
96
+ onDelete,
97
+ onSave,
98
+ }: DetailDrawerProps) {
99
+ const s = detailDrawerStyles();
100
+ const [openSections, setOpenSections] = useState<Set<SectionId>>(
101
+ () => new Set(["contact", "meta", "notes"]),
102
+ );
103
+
104
+ const toggle = (id: SectionId) => {
105
+ setOpenSections((prev) => {
106
+ const next = new Set(prev);
107
+ if (next.has(id)) next.delete(id);
108
+ else next.add(id);
109
+ return next;
110
+ });
111
+ };
112
+
113
+ // Mantém ordem dos hooks estável independente de `row` — depois do toggle.
114
+ if (!row) return null;
115
+
116
+ const status = STATUSES[row.statusId];
117
+ const cat = CATEGORIES[row.categoryId as keyof typeof CATEGORIES];
118
+ const agent = AGENTS[row.agentId as keyof typeof AGENTS];
119
+ const agentInitials =
120
+ agent?.name.split(" ").map((p) => p[0]).join("").slice(0, 2) ?? "?";
121
+
122
+ return (
123
+ <FloatingPanel
124
+ open={row !== null}
125
+ onOpenChange={(open) => {
126
+ if (!open) onClose();
127
+ }}
128
+ side="right"
129
+ size="md"
130
+ resizable
131
+ maximizable
132
+ // Sections internas (DetailSection) gerenciam o próprio padding edge-to-edge.
133
+ bodyPadded={false}
134
+ resizableStorageKey="clientes-showcase.detail-drawer.width"
135
+ titleSlot={
136
+ <div className="flex items-center gap-gp-md min-w-0">
137
+ <Avatar
138
+ className="size-[40px] shrink-0"
139
+ style={{ background: row.avatarColor }}
140
+ >
141
+ <AvatarFallback className="bg-transparent text-white text-body-md font-bold">
142
+ {row.initials}
143
+ </AvatarFallback>
144
+ </Avatar>
145
+ <div className="flex flex-col min-w-0 flex-1">
146
+ <div className="text-body-md font-semibold text-fg-default whitespace-nowrap overflow-hidden text-ellipsis">
147
+ {row.name}
148
+ </div>
149
+ <div className="flex items-center gap-[6px] mt-[2px] text-body-xs font-normal text-fg-muted">
150
+ <span>{row.id}</span>
151
+ <span className="opacity-50">·</span>
152
+ <span className="inline-flex items-center gap-[6px]">
153
+ <span
154
+ className="inline-block size-[8px] rounded-radius-full shrink-0"
155
+ style={{ background: STATUS_DOT[row.statusId] ?? status?.color }}
156
+ aria-hidden
157
+ />
158
+ {status?.label}
159
+ </span>
160
+ </div>
161
+ </div>
162
+ </div>
163
+ }
164
+ headerActions={
165
+ <>
166
+ <Button
167
+ variant="soft"
168
+ color="secondary"
169
+ size="icon-sm"
170
+ aria-label="Editar"
171
+ onClick={() => onEdit?.(row)}
172
+ >
173
+ <Pencil />
174
+ </Button>
175
+ <Button
176
+ variant="soft"
177
+ color="critical"
178
+ size="icon-sm"
179
+ aria-label="Excluir"
180
+ onClick={() => onDelete?.(row)}
181
+ >
182
+ <Trash2 />
183
+ </Button>
184
+ </>
185
+ }
186
+ footer={
187
+ <>
188
+ <Button variant="outline" color="secondary" size="sm" onClick={onClose}>
189
+ Cancelar
190
+ </Button>
191
+ <Button
192
+ variant="filled"
193
+ color="primary"
194
+ size="sm"
195
+ onClick={() => onSave?.(row)}
196
+ >
197
+ Salvar alterações
198
+ </Button>
199
+ </>
200
+ }
201
+ >
202
+ <DetailSection
203
+ id="contact"
204
+ title="Contato"
205
+ open={openSections.has("contact")}
206
+ onToggle={toggle}
207
+ s={s}
208
+ >
209
+ <DetailField
210
+ label="Email"
211
+ value={
212
+ <a
213
+ href={`mailto:${row.email}`}
214
+ className="text-fg-brand hover:underline"
215
+ >
216
+ {row.email}
217
+ </a>
218
+ }
219
+ s={s}
220
+ />
221
+ <DetailField
222
+ label="Telefone"
223
+ value={
224
+ <a
225
+ href={`tel:${row.phone.replace(/\D/g, "")}`}
226
+ className="text-fg-brand hover:underline"
227
+ >
228
+ {row.phone}
229
+ </a>
230
+ }
231
+ s={s}
232
+ />
233
+ <DetailField label="Localização" value={row.location} s={s} />
234
+ </DetailSection>
235
+
236
+ <DetailSection
237
+ id="meta"
238
+ title="Atendimento"
239
+ open={openSections.has("meta")}
240
+ onToggle={toggle}
241
+ s={s}
242
+ >
243
+ <DetailField label="ID" value={row.id} s={s} />
244
+ <DetailField
245
+ label="Categoria"
246
+ value={
247
+ cat ? (
248
+ <Chip
249
+ color={CATEGORY_KIND[row.categoryId] ?? "info"}
250
+ variant="soft"
251
+ size="sm"
252
+ shape="pill"
253
+ >
254
+ {cat.label}
255
+ </Chip>
256
+ ) : (
257
+ "—"
258
+ )
259
+ }
260
+ s={s}
261
+ />
262
+ <DetailField
263
+ label="Atribuído a"
264
+ value={
265
+ agent ? (
266
+ <span className="inline-flex items-center gap-gp-sm">
267
+ <Avatar
268
+ className="size-[22px]"
269
+ style={{ background: agent.color }}
270
+ >
271
+ <AvatarFallback className="bg-transparent text-white text-caption-xs font-bold">
272
+ {agentInitials}
273
+ </AvatarFallback>
274
+ </Avatar>
275
+ {agent.name}
276
+ </span>
277
+ ) : (
278
+ "—"
279
+ )
280
+ }
281
+ s={s}
282
+ />
283
+ <DetailField label="Valor" value={formatBRL(row.value)} s={s} />
284
+ <DetailField label="Criado em" value={formatLongDate(row.createdAt)} s={s} />
285
+ <DetailField
286
+ label="Último contato"
287
+ value={formatShortDate(row.lastContact)}
288
+ s={s}
289
+ />
290
+ </DetailSection>
291
+
292
+ <DetailSection
293
+ id="notes"
294
+ title="Anotações"
295
+ open={openSections.has("notes")}
296
+ onToggle={toggle}
297
+ s={s}
298
+ >
299
+ <p className="text-body-xs font-normal text-fg-muted">Sem anotações.</p>
300
+ </DetailSection>
301
+ </FloatingPanel>
302
+ );
303
+ }
@@ -0,0 +1,14 @@
1
+ import type { ClientRow } from "../../_table-data";
2
+
3
+ export type DetailDrawerProps = {
4
+ /** Cliente em exibição. Quando `null` o drawer fica desmontado. */
5
+ row: ClientRow | null;
6
+ /** Fecha o drawer (chamado por ESC, X do header e Cancelar). */
7
+ onClose: () => void;
8
+ /** Disparado pelo botão Editar (ícone Pencil no header). Opcional. */
9
+ onEdit?: (row: ClientRow) => void;
10
+ /** Disparado pelo botão Excluir (ícone Trash no header). Opcional. */
11
+ onDelete?: (row: ClientRow) => void;
12
+ /** Disparado pelo CTA "Salvar alterações" do footer. Opcional. */
13
+ onSave?: (row: ClientRow) => void;
14
+ };
@@ -0,0 +1,2 @@
1
+ export { DetailDrawer } from "./detail-drawer";
2
+ export type { DetailDrawerProps } from "./detail-drawer.types";
@@ -0,0 +1,2 @@
1
+ export { NovoClienteDrawer } from "./novo-cliente-drawer";
2
+ export type { NovoClienteDrawerProps } from "./novo-cliente-drawer";
@@ -0,0 +1,207 @@
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
+ FormFieldTextarea,
9
+ } from "@/components/ui/FormField";
10
+ import { ChipGroup, ChipGroupItem } from "@/components/ui/Chip";
11
+ import { STATUSES, CATEGORIES } from "../../_table-data";
12
+ import { CITIES, STATUS_DOT } from "../../clientes-showcase-mocks";
13
+ import { drawerFormStyles } from "../../clientes-showcase.styles";
14
+ import type { NovoClienteFormData } from "../../clientes-showcase.types";
15
+
16
+ const INITIAL_FORM: NovoClienteFormData = {
17
+ name: "",
18
+ email: "",
19
+ whatsapp: "",
20
+ statusId: "active",
21
+ categoryId: "lead",
22
+ city: "",
23
+ value: 0,
24
+ notes: "",
25
+ };
26
+
27
+ export type NovoClienteDrawerProps = {
28
+ open: boolean;
29
+ onOpenChange: (open: boolean) => void;
30
+ /** Disparado ao confirmar — drawer fecha automaticamente após o submit. */
31
+ onSubmit?: (data: NovoClienteFormData) => void;
32
+ };
33
+
34
+ /**
35
+ * Drawer "Novo cliente" — replica visual do TblClientFormDrawer
36
+ * (sandbox /design-and-table-v2), recompilado em cima dos componentes do DS:
37
+ * Panel + FormField (Input / Select / Textarea) + ChipGroup + Button.
38
+ *
39
+ * Layout (top → bottom):
40
+ * 1. Nome* — full width
41
+ * 2. Email + WhatsApp — 2 cols
42
+ * 3. Status — ChipGroup com dot colorido
43
+ * 4. Categoria — ChipGroup
44
+ * 5. Cidade + Valor R$ — 2 cols
45
+ * 6. Notas — textarea full width
46
+ */
47
+ export function NovoClienteDrawer({
48
+ open,
49
+ onOpenChange,
50
+ onSubmit,
51
+ }: NovoClienteDrawerProps) {
52
+ const s = drawerFormStyles();
53
+ const [form, setForm] = useState<NovoClienteFormData>(INITIAL_FORM);
54
+
55
+ // Reseta form sempre que abrir
56
+ useEffect(() => {
57
+ if (open) setForm(INITIAL_FORM);
58
+ }, [open]);
59
+
60
+ const canSubmit = form.name.trim().length > 0;
61
+
62
+ const handleSubmit = () => {
63
+ if (!canSubmit) return;
64
+ onSubmit?.(form);
65
+ onOpenChange(false);
66
+ };
67
+
68
+ return (
69
+ <Panel
70
+ open={open}
71
+ onOpenChange={onOpenChange}
72
+ title="Novo cliente"
73
+ side="right"
74
+ size="md"
75
+ footer={
76
+ <>
77
+ <Button
78
+ variant="outline"
79
+ color="secondary"
80
+ size="md"
81
+ onClick={() => onOpenChange(false)}
82
+ >
83
+ Cancelar
84
+ </Button>
85
+ <Button
86
+ variant="filled"
87
+ color="primary"
88
+ size="md"
89
+ disabled={!canSubmit}
90
+ onClick={handleSubmit}
91
+ >
92
+ Criar cliente
93
+ </Button>
94
+ </>
95
+ }
96
+ >
97
+ <FormFieldInput
98
+ label="Nome"
99
+ required
100
+ placeholder="Maria Silva"
101
+ value={form.name}
102
+ onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}
103
+ />
104
+
105
+ <div className={s.twoCols()}>
106
+ <FormFieldInput
107
+ label="Email"
108
+ type="email"
109
+ placeholder="cliente@exemplo.com"
110
+ value={form.email}
111
+ onChange={(e) => setForm((f) => ({ ...f, email: e.target.value }))}
112
+ />
113
+ <FormFieldInput
114
+ label="WhatsApp"
115
+ type="tel"
116
+ placeholder="55 11 99999-9999"
117
+ value={form.whatsapp}
118
+ onChange={(e) => setForm((f) => ({ ...f, whatsapp: e.target.value }))}
119
+ />
120
+ </div>
121
+
122
+ <FormField label="Status">
123
+ {() => (
124
+ <ChipGroup
125
+ type="single"
126
+ value={form.statusId}
127
+ onValueChange={(v) =>
128
+ v &&
129
+ setForm((f) => ({
130
+ ...f,
131
+ statusId: v as keyof typeof STATUSES,
132
+ }))
133
+ }
134
+ size="md"
135
+ shape="pill"
136
+ ariaLabel="Status do cliente"
137
+ >
138
+ {Object.entries(STATUSES).map(([id, st]) => (
139
+ <ChipGroupItem key={id} value={id}>
140
+ <span
141
+ className={s.statusDot()}
142
+ style={{ background: STATUS_DOT[id] ?? st.color }}
143
+ aria-hidden
144
+ />
145
+ {st.label}
146
+ </ChipGroupItem>
147
+ ))}
148
+ </ChipGroup>
149
+ )}
150
+ </FormField>
151
+
152
+ <FormField label="Categoria">
153
+ {() => (
154
+ <ChipGroup
155
+ type="single"
156
+ value={form.categoryId}
157
+ onValueChange={(v) =>
158
+ v &&
159
+ setForm((f) => ({
160
+ ...f,
161
+ categoryId: v as keyof typeof CATEGORIES,
162
+ }))
163
+ }
164
+ size="md"
165
+ shape="pill"
166
+ ariaLabel="Categoria do cliente"
167
+ >
168
+ {Object.entries(CATEGORIES).map(([id, cat]) => (
169
+ <ChipGroupItem key={id} value={id}>
170
+ {cat.label}
171
+ </ChipGroupItem>
172
+ ))}
173
+ </ChipGroup>
174
+ )}
175
+ </FormField>
176
+
177
+ <div className={s.twoCols()}>
178
+ <FormFieldSelect
179
+ label="Cidade"
180
+ placeholder="Selecionar..."
181
+ value={form.city || undefined}
182
+ onValueChange={(v) => setForm((f) => ({ ...f, city: v }))}
183
+ options={CITIES.map((c) => ({ value: c, label: c }))}
184
+ />
185
+ <FormFieldInput
186
+ label="Valor (R$)"
187
+ type="number"
188
+ startAddon="R$"
189
+ value={String(form.value)}
190
+ onChange={(e) =>
191
+ setForm((f) => ({ ...f, value: Number(e.target.value) }))
192
+ }
193
+ min={0}
194
+ step={0.01}
195
+ />
196
+ </div>
197
+
198
+ <FormFieldTextarea
199
+ label="Notas"
200
+ placeholder="Observações sobre o cliente..."
201
+ rows={4}
202
+ value={form.notes}
203
+ onChange={(e) => setForm((f) => ({ ...f, notes: e.target.value }))}
204
+ />
205
+ </Panel>
206
+ );
207
+ }
@@ -0,0 +1 @@
1
+ export { ClientesScreen, default } from "./clientes-screen";
@@ -0,0 +1,81 @@
1
+ import type { LucideIcon } from "@/lib/lucide-types";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ export type Step = {
5
+ id: string;
6
+ icon: LucideIcon;
7
+ title: string;
8
+ description: string;
9
+ };
10
+
11
+ /**
12
+ * Nav lateral (stepper) da tela de edição — itens com ícone + título +
13
+ * descrição; o ativo ganha destaque de marca. Clicar seleciona + rola até a
14
+ * seção. Composição local do showcase usando tokens DS.
15
+ */
16
+ export function StepNav({
17
+ steps,
18
+ activeId,
19
+ onSelect,
20
+ className,
21
+ }: {
22
+ steps: Step[];
23
+ activeId: string;
24
+ onSelect: (id: string) => void;
25
+ className?: string;
26
+ }) {
27
+ return (
28
+ <nav
29
+ className={cn(
30
+ "flex flex-col gap-gp-xs rounded-radius-lg border border-border-subtle bg-bg-surface p-pad-md shadow-sh-sm",
31
+ className,
32
+ )}
33
+ aria-label="Etapas do pedido"
34
+ >
35
+ {steps.map((s) => {
36
+ const Icon = s.icon;
37
+ const active = s.id === activeId;
38
+ return (
39
+ <button
40
+ key={s.id}
41
+ type="button"
42
+ onClick={() => onSelect(s.id)}
43
+ aria-current={active ? "step" : undefined}
44
+ className={cn(
45
+ "flex items-start gap-gp-md rounded-radius-md border p-pad-lg text-left transition-colors",
46
+ active
47
+ ? "border-border-brand bg-bg-brand-subtle"
48
+ : "border-transparent hover:bg-bg-muted",
49
+ )}
50
+ >
51
+ <span
52
+ className={cn(
53
+ "grid size-8 shrink-0 place-items-center rounded-radius-md border",
54
+ active
55
+ ? "border-transparent bg-bg-brand text-fg-on-brand"
56
+ // bg-surface (branco no light) + borda: contrasta tanto no card
57
+ // quanto no hover cinza (senão o ícone funde com o hover).
58
+ : "border-border-subtle bg-bg-surface text-fg-muted",
59
+ )}
60
+ >
61
+ <Icon className="size-icon-sm" />
62
+ </span>
63
+ <span className="min-w-0 flex-1">
64
+ <span
65
+ className={cn(
66
+ "block text-body-sm font-semibold",
67
+ active ? "text-fg-brand" : "text-fg-default",
68
+ )}
69
+ >
70
+ {s.title}
71
+ </span>
72
+ <span className="mt-gp-2xs block text-caption-sm leading-snug text-fg-muted">
73
+ {s.description}
74
+ </span>
75
+ </span>
76
+ </button>
77
+ );
78
+ })}
79
+ </nav>
80
+ );
81
+ }
@@ -0,0 +1,85 @@
1
+ import type { ReactNode } from "react";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ /**
5
+ * Card de seção flat — bg-surface + borda sutil, sem elevação pesada.
6
+ * Espelha o visual dos blocos da referência (Products ordered, Customer, etc).
7
+ * Composição local do showcase consumindo tokens DS via classes.
8
+ */
9
+ export function SectionCard({
10
+ title,
11
+ icon,
12
+ action,
13
+ children,
14
+ className,
15
+ bodyClassName,
16
+ }: {
17
+ title?: ReactNode;
18
+ icon?: ReactNode;
19
+ action?: ReactNode;
20
+ children: ReactNode;
21
+ className?: string;
22
+ bodyClassName?: string;
23
+ }) {
24
+ const hasHeader = !!(title || action);
25
+ const header = hasHeader && (
26
+ // Header: padding 16px (pad-2xl) + divider full-bleed embaixo.
27
+ <header className="flex items-center justify-between gap-gp-md border-b border-border-subtle bg-bg-subtle p-pad-2xl">
28
+ {/* gap ícone↔título = 12px (gap-gp-md) */}
29
+ <div className="flex items-center gap-gp-md">
30
+ {icon && (
31
+ <span className="grid size-8 shrink-0 place-items-center rounded-radius-md bg-bg-muted text-fg-muted">
32
+ {icon}
33
+ </span>
34
+ )}
35
+ {title && (
36
+ <h2 className="text-title-md font-semibold text-fg-default">
37
+ {title}
38
+ </h2>
39
+ )}
40
+ </div>
41
+ {action}
42
+ </header>
43
+ );
44
+
45
+ return (
46
+ <section className={cn("overflow-hidden rounded-radius-lg border border-border-subtle bg-bg-surface shadow-sh-sm", className)}>
47
+ {header}
48
+ {/* body: padding 16px (pad-2xl) — uniforme; abaixo do divider quando há header */}
49
+ <div className={cn("p-pad-2xl", bodyClassName)}>{children}</div>
50
+ </section>
51
+ );
52
+ }
53
+
54
+ /** Divisória sutil de seção. `my` controla o respiro vertical (default md). */
55
+ export function SectionDivider({ className }: { className?: string }) {
56
+ return <div className={cn("h-px bg-border-subtle", className)} />;
57
+ }
58
+
59
+ /** Par label-uppercase + valor (grid de Customer/Shipping/Billing Information). */
60
+ export function Field({
61
+ label,
62
+ value,
63
+ accent,
64
+ }: {
65
+ label: string;
66
+ value: ReactNode;
67
+ /** Destaca o valor com a cor de marca (links, ids de transação, etc). */
68
+ accent?: boolean;
69
+ }) {
70
+ return (
71
+ <div className="flex flex-col gap-gp-2xs">
72
+ <span className="text-caption-sm font-medium uppercase tracking-wider text-fg-muted">
73
+ {label}
74
+ </span>
75
+ <span
76
+ className={cn(
77
+ "text-body-md",
78
+ accent ? "text-fg-brand" : "text-fg-default",
79
+ )}
80
+ >
81
+ {value}
82
+ </span>
83
+ </div>
84
+ );
85
+ }