@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.
- 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/componentes/DataTable.md +2 -1
- package/dist-lib/ai/componentes/TableToolbar.md +1 -1
- 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/global/componentes.md +7 -0
- package/dist-lib/ai/indice.json +30 -15
- package/dist-lib/ai/manifest.json +32 -6
- 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 +146 -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 +193 -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/dist-lib/chunks/{panel-B-liPlxr.mjs → panel-CN0ChoHM.mjs} +5 -2
- package/dist-lib/chunks/{panel-B-liPlxr.mjs.map → panel-CN0ChoHM.mjs.map} +1 -1
- package/dist-lib/chunks/{panel-DoNbjeqX.cjs → panel-Cpq1xHck.cjs} +5 -2
- package/dist-lib/chunks/{panel-DoNbjeqX.cjs.map → panel-Cpq1xHck.cjs.map} +1 -1
- package/dist-lib/index.cjs +1 -1
- package/dist-lib/index.mjs +2 -2
- package/dist-lib/preview/clientes.cjs +1 -1
- package/dist-lib/preview/clientes.mjs +1 -1
- package/dist-lib/src/components/ui/DataTable/data-table.d.ts.map +1 -1
- package/dist-lib/src/components/ui/DataTable/data-table.types.d.ts +20 -2
- package/dist-lib/src/components/ui/DataTable/data-table.types.d.ts.map +1 -1
- 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,174 @@
|
|
|
1
|
+
import { Cell, Pie, PieChart } from "recharts";
|
|
2
|
+
// Import por ARQUIVO, não pelo barrel `@/components/shadcn` — o barrel (`index.ts`) não é
|
|
3
|
+
// distribuído por nenhum item do registry, então um bloco que o importasse chegaria no
|
|
4
|
+
// consumidor de copy-in com import que não resolve. O gate `registry-imports` pega isso; foi
|
|
5
|
+
// ele que reprovou a primeira versão deste arquivo (L-037).
|
|
6
|
+
import {
|
|
7
|
+
Card,
|
|
8
|
+
CardHeader,
|
|
9
|
+
CardTitle,
|
|
10
|
+
CardDescription,
|
|
11
|
+
CardContent,
|
|
12
|
+
} from "@/components/shadcn/card";
|
|
13
|
+
import { Tabs, TabsList, TabsTrigger } from "@/components/shadcn/tabs";
|
|
14
|
+
import { Chip } from "@/components/ui/Chip";
|
|
15
|
+
import {
|
|
16
|
+
ChartContainer,
|
|
17
|
+
ChartTooltip,
|
|
18
|
+
ChartTooltipContent,
|
|
19
|
+
} from "@/components/ui/Chart";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* dsgreen-chart-1 — donut de distribuição + lista de rateio no mesmo card.
|
|
23
|
+
*
|
|
24
|
+
* ## O que este bloco resolve
|
|
25
|
+
*
|
|
26
|
+
* A composição que a IA não monta sozinha: donut com **total no centro** e, abaixo,
|
|
27
|
+
* a **lista das mesmas fatias** onde a cor é a chave que liga linha ↔ setor. Pedindo
|
|
28
|
+
* "gráfico de pizza com os valores", sai o donut e uma legenda genérica ao lado; a
|
|
29
|
+
* leitura boa é esta — o olho vai ao total, depois desce a lista e usa a cor pra
|
|
30
|
+
* voltar ao setor.
|
|
31
|
+
*
|
|
32
|
+
* ## Regras do DS que este bloco carrega (e que copiar sem elas quebra)
|
|
33
|
+
*
|
|
34
|
+
* - **Cor só por token de chart** (`--color-chart-1..5`). Nunca hex, nunca paleta
|
|
35
|
+
* nativa do Tailwind. 5 fatias = as 5 chaves, na ordem.
|
|
36
|
+
* - **`fg-danger` NÃO entra como cor de categoria.** Ele é semântico de erro/queda
|
|
37
|
+
* (`chart-patterns.md` §2: "Status/uptime → down"). Usar vermelho de perigo numa
|
|
38
|
+
* categoria normal faz o leitor inferir problema onde não há.
|
|
39
|
+
* - **Valor numérico com `tabular-nums`**, senão os dígitos dançam entre linhas.
|
|
40
|
+
* - **Superfície = `Card` do DS.** Não recriar `<section>` com bg/shadow na unha.
|
|
41
|
+
*
|
|
42
|
+
* ## Cuidado ao adaptar
|
|
43
|
+
*
|
|
44
|
+
* As abas são **de composição visual**: elas mostram que o card cabe num contexto de
|
|
45
|
+
* 2+ recortes, mas não trocam dado nenhum aqui. Se o seu caso tem dois recortes de
|
|
46
|
+
* verdade, ligue-as a estado e troque o `data`; se tem um só, **remova-as** — controle
|
|
47
|
+
* que não faz nada é pior que controle ausente.
|
|
48
|
+
*/
|
|
49
|
+
export const BLOCK = {
|
|
50
|
+
id: "dsgreen-chart-1",
|
|
51
|
+
nome: "Donut de distribuição com rateio",
|
|
52
|
+
descricao:
|
|
53
|
+
"Donut com total no centro + lista das fatias abaixo, onde a cor liga linha e setor. Para distribuição de um total por 3–5 categorias nomeadas.",
|
|
54
|
+
usa: ["Card (size=\"md\")", "Tabs", "Chip", "ChartContainer", "PieChart (recharts)"],
|
|
55
|
+
} as const;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Fixture do bloco. Fica aqui de propósito: bloco é auto-contido, então quem copia
|
|
59
|
+
* vê a forma do dado esperado sem ter que inferir do gráfico.
|
|
60
|
+
*/
|
|
61
|
+
type BudgetRow = { readonly grupo: string; readonly valor: number; readonly cor: string };
|
|
62
|
+
|
|
63
|
+
const BUDGET: readonly BudgetRow[] = [
|
|
64
|
+
{ grupo: "Engenharia", valor: 950, cor: "var(--color-chart-1)" },
|
|
65
|
+
{ grupo: "Marketing", valor: 680, cor: "var(--color-chart-2)" },
|
|
66
|
+
{ grupo: "Comercial", valor: 520, cor: "var(--color-chart-3)" },
|
|
67
|
+
{ grupo: "Operações", valor: 310, cor: "var(--color-chart-4)" },
|
|
68
|
+
{ grupo: "RH & Admin", valor: 195, cor: "var(--color-chart-5)" },
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
const TOTAL = BUDGET.reduce((soma, linha) => soma + linha.valor, 0);
|
|
72
|
+
|
|
73
|
+
const brl = new Intl.NumberFormat("pt-BR", {
|
|
74
|
+
style: "currency",
|
|
75
|
+
currency: "BRL",
|
|
76
|
+
maximumFractionDigits: 0,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const share = (valor: number) => `${((valor / TOTAL) * 100).toFixed(1)}%`;
|
|
80
|
+
|
|
81
|
+
export function BudgetBreakdownBlock() {
|
|
82
|
+
// `size` cuida do padding de TODAS as partes do card — não escrever `px-*`/`p-0` na
|
|
83
|
+
// mão (era o que este bloco fazia antes do `Card` ganhar a prop, em 2026-08-19).
|
|
84
|
+
return (
|
|
85
|
+
<Card size="md" className="w-full max-w-[450px]">
|
|
86
|
+
<CardHeader>
|
|
87
|
+
<CardTitle>Distribuição de custo</CardTitle>
|
|
88
|
+
<CardDescription>
|
|
89
|
+
Rateio do gasto por área e categoria de custo.
|
|
90
|
+
</CardDescription>
|
|
91
|
+
</CardHeader>
|
|
92
|
+
|
|
93
|
+
{/* Cada seção vai em `CardContent` porque é ELE que carrega o padding
|
|
94
|
+
horizontal — o `Card` só carrega o vertical. `<div>` cru como filho direto
|
|
95
|
+
do Card encosta nas bordas, que é o que este bloco fazia por engano. */}
|
|
96
|
+
<CardContent>
|
|
97
|
+
{/* Ver o JSDoc: abas de composição. Ligue a estado ou remova. */}
|
|
98
|
+
<Tabs fullWidth defaultValue="area">
|
|
99
|
+
<TabsList>
|
|
100
|
+
<TabsTrigger value="area">
|
|
101
|
+
Por área
|
|
102
|
+
</TabsTrigger>
|
|
103
|
+
<TabsTrigger value="categoria">
|
|
104
|
+
Por categoria
|
|
105
|
+
</TabsTrigger>
|
|
106
|
+
</TabsList>
|
|
107
|
+
</Tabs>
|
|
108
|
+
</CardContent>
|
|
109
|
+
|
|
110
|
+
{/* Donut + total no centro. O overlay é pointer-events-none pra não roubar o
|
|
111
|
+
hover do setor — sem isso o tooltip do Recharts não abre no meio. */}
|
|
112
|
+
<CardContent className="relative mx-auto flex items-center justify-center">
|
|
113
|
+
<ChartContainer config={{}} className="aspect-square h-[210px]">
|
|
114
|
+
<PieChart>
|
|
115
|
+
<ChartTooltip
|
|
116
|
+
cursor={false}
|
|
117
|
+
content={<ChartTooltipContent hideLabel nameKey="grupo" />}
|
|
118
|
+
/>
|
|
119
|
+
<Pie
|
|
120
|
+
data={BUDGET as BudgetRow[]}
|
|
121
|
+
dataKey="valor"
|
|
122
|
+
nameKey="grupo"
|
|
123
|
+
innerRadius={64}
|
|
124
|
+
outerRadius={92}
|
|
125
|
+
paddingAngle={3}
|
|
126
|
+
strokeWidth={0}
|
|
127
|
+
>
|
|
128
|
+
{BUDGET.map((linha) => (
|
|
129
|
+
<Cell key={linha.grupo} fill={linha.cor} />
|
|
130
|
+
))}
|
|
131
|
+
</Pie>
|
|
132
|
+
</PieChart>
|
|
133
|
+
</ChartContainer>
|
|
134
|
+
<div className="pointer-events-none absolute inset-0 flex flex-col items-center justify-center">
|
|
135
|
+
<span className="text-title-lg font-bold tabular-nums text-fg-default">
|
|
136
|
+
{brl.format(TOTAL)}
|
|
137
|
+
</span>
|
|
138
|
+
<span className="text-caption-sm text-fg-muted">gasto total</span>
|
|
139
|
+
</div>
|
|
140
|
+
</CardContent>
|
|
141
|
+
|
|
142
|
+
<CardContent>
|
|
143
|
+
<div className="mb-pad-md flex items-center justify-between text-caption-md text-fg-muted">
|
|
144
|
+
<span>ÁREA</span>
|
|
145
|
+
<span>VALOR / PARTICIPAÇÃO</span>
|
|
146
|
+
</div>
|
|
147
|
+
<div className="flex flex-col">
|
|
148
|
+
{BUDGET.map((linha) => (
|
|
149
|
+
<div
|
|
150
|
+
key={linha.grupo}
|
|
151
|
+
className="flex items-center gap-gp-md border-b border-border-subtle py-pad-lg last:border-0"
|
|
152
|
+
>
|
|
153
|
+
{/* A barrinha de cor é o que liga esta linha ao setor do donut. */}
|
|
154
|
+
<span
|
|
155
|
+
aria-hidden
|
|
156
|
+
className="h-[16px] w-[3px] rounded-radius-full"
|
|
157
|
+
style={{ background: linha.cor }}
|
|
158
|
+
/>
|
|
159
|
+
<span className="flex-1 text-body-sm font-medium text-fg-default">
|
|
160
|
+
{linha.grupo}
|
|
161
|
+
</span>
|
|
162
|
+
<span className="text-body-sm font-semibold tabular-nums text-fg-default">
|
|
163
|
+
{brl.format(linha.valor)}
|
|
164
|
+
</span>
|
|
165
|
+
<Chip color="neutral" variant="soft" size="sm" shape="pill">
|
|
166
|
+
{share(linha.valor)}
|
|
167
|
+
</Chip>
|
|
168
|
+
</div>
|
|
169
|
+
))}
|
|
170
|
+
</div>
|
|
171
|
+
</CardContent>
|
|
172
|
+
</Card>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<!-- GERADO por `npm run blocks:build` a partir dos `export const BLOCK` em src/blocks/**.
|
|
2
|
+
NÃO editar à mão: a próxima geração sobrescreve, e o gate `blocks-index` reprova divergência. -->
|
|
3
|
+
|
|
4
|
+
# Índice de blocos
|
|
5
|
+
|
|
6
|
+
**Bloco não é componente.** É uma composição de referência — feita só com componentes que você
|
|
7
|
+
já tem — que existe porque a IA sabe as peças e os tokens mas não sabe o **arranjo** que um
|
|
8
|
+
designer escolheu. Não tem props nem versão própria: você lê a estrutura e reconstrói com os
|
|
9
|
+
dados do usuário.
|
|
10
|
+
|
|
11
|
+
Este índice é lido pelo **Passo 0** da skill `ds-kit`, quando o usuário cita um código.
|
|
12
|
+
|
|
13
|
+
## chart
|
|
14
|
+
|
|
15
|
+
| Código | Composição | Quando serve | Usa | Arquivo |
|
|
16
|
+
|---|---|---|---|---|
|
|
17
|
+
| `dsgreen-chart-1` | Donut de distribuição com rateio | Donut com total no centro + lista das fatias abaixo, onde a cor liga linha e setor. Para distribuição de um total por 3–5 categorias nomeadas. | Card (size="md") · Tabs · Chip · ChartContainer · PieChart (recharts) | `src/blocks/chart/budget-breakdown.tsx` |
|
|
18
|
+
|
|
19
|
+
## paneldetail
|
|
20
|
+
|
|
21
|
+
| Código | Composição | Quando serve | Usa | Arquivo |
|
|
22
|
+
|---|---|---|---|---|
|
|
23
|
+
| `dsgreen-paneldetail-1` | Painel de detalhe do registro | Painel lateral aberto a partir de uma linha de tabela: header com avatar + nome + código e status, ações de ícone e maximizar; métricas em cards compactos; e os dados em seções colapsáveis — inclusive as que não são label:valor (conta bancária com marca, métodos em chips, e-mail e telefone acionáveis, gestor com avatar). Ação primária no footer. Mesmo padrão dos painéis de detalhe do Virtual Office e do CRUD de clientes. | FloatingPanel (side="right", size="lg", titleSlot + headerActions + maximizable + resizable + bodyPadded={false}) · FloatingPanelSection + FloatingPanelField · MetricaCartoes — cards compactos próprios (ícone + valor + rótulo); NÃO o Kpi · Avatar (colorHex, contraste WCAG automático) · Chip (soft) · Button (icon-sm no header, sm no footer) | `src/blocks/paneldetail/detalhe-do-registro.tsx` |
|
|
24
|
+
| `dsgreen-paneldetail-2` | Detalhe de tarefa com abas | Painel de detalhe de uma TAREFA: contexto (projeto/coluna) no header, título grande no corpo, propriedades em lista plana com ícone à esquerda e valor à direita, descrição, e abas pros conteúdos que crescem — subtarefas, anexos, comentários e atividade. | FloatingPanel (side="right", size="lg", titleSlot + headerActions + maximizable + resizable; body PADDED, sem Section) · Tabs (fullWidth, variante default) + TabsContent · Avatar (pilha com sobreposição) · Chip (soft) · Input · Button | `src/blocks/paneldetail/detalhe-de-tarefa-com-abas.tsx` |
|
|
25
|
+
| `dsgreen-paneldetail-3` | Detalhe largo com métricas e tabela | Painel de detalhe LARGO (720px): identidade e propriedades em duas colunas no topo, faixa de métricas do período, e uma tabela curta escopada no registro (últimos lançamentos) com navegação de período e exportar. Pra quando o detalhe inclui uma série de linhas — em 560px isso viraria scroll horizontal. | FloatingPanel (size="xl" 720px, titleSlot + headerActions + maximizable + resizable) · Table + TableHead/TableHeadCell/TableBody/TableRow/TableCell (o primitivo, NÃO o DataTable) · Avatar · Chip (soft) · Button (icon-sm e sm) | `src/blocks/paneldetail/detalhe-com-tabela.tsx` |
|
|
26
|
+
|
|
27
|
+
## Como pegar o código do bloco
|
|
28
|
+
|
|
29
|
+
| modo de consumo | onde o arquivo está |
|
|
30
|
+
|---|---|
|
|
31
|
+
| **submódulo** (`.claude/ds-config.json` com `"mode": "submodule"`) | já no disco: `<dsPath>/<arquivo>` da tabela acima. Leia direto |
|
|
32
|
+
| **copy-in / scaffold** | não vem instalado: `npm run igreen:add -- <código>` traz o arquivo |
|
|
33
|
+
|
|
34
|
+
Leia o arquivo **inteiro**, incluindo o JSDoc do topo — ele carrega as regras que a composição
|
|
35
|
+
embute (qual token de cor, por que `tabular-nums`, o que NÃO copiar) e uma seção **Cuidado ao
|
|
36
|
+
adaptar** que diz o que ligar a estado e o que remover. Medido num consumidor real em
|
|
37
|
+
2026-08-20: foi esse JSDoc que produziu o resultado bom, não só a resolução do ID.
|
|
38
|
+
|
|
39
|
+
Adapte dados e rótulos **preservando estrutura e espaçamento**. Não "melhore" o arranjo — ele é
|
|
40
|
+
o motivo pelo qual o código foi citado.
|
|
41
|
+
|
|
42
|
+
Se o código citado não estiver na tabela, **diga isso** em vez de montar algo parecido: o valor
|
|
43
|
+
do código é ser determinístico, e um palpite silencioso destrói exatamente isso.
|
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Briefcase,
|
|
4
|
+
Building2,
|
|
5
|
+
CalendarDays,
|
|
6
|
+
ChevronLeft,
|
|
7
|
+
ChevronRight,
|
|
8
|
+
Download,
|
|
9
|
+
FileText,
|
|
10
|
+
Mail,
|
|
11
|
+
MapPin,
|
|
12
|
+
Pencil,
|
|
13
|
+
Phone,
|
|
14
|
+
TrendingUp,
|
|
15
|
+
UserRound,
|
|
16
|
+
Users,
|
|
17
|
+
Zap,
|
|
18
|
+
} from "lucide-react";
|
|
19
|
+
import type { LucideIcon } from "@/lib/lucide-types";
|
|
20
|
+
import { Avatar } from "@/components/ui/avatar-ig";
|
|
21
|
+
import { Button } from "@/components/ui/Button";
|
|
22
|
+
import { Chip } from "@/components/ui/Chip";
|
|
23
|
+
import { FloatingPanel } from "@/components/ui/FloatingPanel";
|
|
24
|
+
import {
|
|
25
|
+
Table,
|
|
26
|
+
TableBody,
|
|
27
|
+
TableCell,
|
|
28
|
+
TableHead,
|
|
29
|
+
TableHeadCell,
|
|
30
|
+
TableRow,
|
|
31
|
+
} from "@/components/ui/Table";
|
|
32
|
+
|
|
33
|
+
export const BLOCK = {
|
|
34
|
+
id: "dsgreen-paneldetail-3",
|
|
35
|
+
nome: "Detalhe largo com métricas e tabela",
|
|
36
|
+
descricao:
|
|
37
|
+
"Painel de detalhe LARGO (720px): identidade e propriedades em duas colunas no topo, faixa de métricas do período, e uma tabela curta escopada no registro (últimos lançamentos) com navegação de período e exportar. Pra quando o detalhe inclui uma série de linhas — em 560px isso viraria scroll horizontal.",
|
|
38
|
+
usa: [
|
|
39
|
+
"FloatingPanel (size=\"xl\" 720px, titleSlot + headerActions + maximizable + resizable)",
|
|
40
|
+
"Table + TableHead/TableHeadCell/TableBody/TableRow/TableCell (o primitivo, NÃO o DataTable)",
|
|
41
|
+
"Avatar · Chip (soft) · Button (icon-sm e sm)",
|
|
42
|
+
],
|
|
43
|
+
} as const;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* dsgreen-paneldetail-3 — detalhe largo, com métricas e tabela.
|
|
47
|
+
*
|
|
48
|
+
* ## Quando este, e não o `-1` nem o `-2`
|
|
49
|
+
*
|
|
50
|
+
* O que decide é **se o detalhe contém uma série de linhas**:
|
|
51
|
+
*
|
|
52
|
+
* | | conteúdo | largura |
|
|
53
|
+
* |---|---|---|
|
|
54
|
+
* | `-1` | registro com muitos campos, em seções colapsáveis | `lg` (560px) |
|
|
55
|
+
* | `-2` | tarefa: poucas propriedades + abas de conteúdo que cresce | `lg` (560px) |
|
|
56
|
+
* | **`-3`** (este) | **campos + métricas + TABELA** escopada no registro | **`xl` (720px)** |
|
|
57
|
+
*
|
|
58
|
+
* ⚠️ **A largura não é gosto, é requisito da tabela.** Cinco colunas de dados precisam de
|
|
59
|
+
* ~640px úteis; em `lg` (560px, ~524 úteis) a mesma tabela entra em scroll horizontal — e
|
|
60
|
+
* tabela que rola de lado dentro de painel é pior que abrir a tela cheia, porque o usuário
|
|
61
|
+
* perde a coluna de referência ao rolar. Se não couber em `xl`, **o detalhe não é painel**: é
|
|
62
|
+
* tela própria.
|
|
63
|
+
*
|
|
64
|
+
* ## Por que `Table` e não `DataTable`
|
|
65
|
+
*
|
|
66
|
+
* O `DataTable` traz toolbar, filtros, visões salvas, paginação e virtualização. Dentro de um
|
|
67
|
+
* painel isso **compete com o painel**: dois níveis de filtro na tela, dois lugares de
|
|
68
|
+
* paginação, e o toolbar dele encostado no header do painel. Aqui a tabela é um **recorte
|
|
69
|
+
* curto e fechado** — os últimos N lançamentos do período — então o primitivo `Table` é o
|
|
70
|
+
* certo.
|
|
71
|
+
*
|
|
72
|
+
* Corolário: **se você precisar de filtro, ordenação persistente ou paginação nessa tabela,
|
|
73
|
+
* ela deixou de ser recorte.** Aí o lugar dela é uma tela com `DataTable`, e o painel volta a
|
|
74
|
+
* ser só o detalhe.
|
|
75
|
+
*
|
|
76
|
+
* ## O topo em duas colunas só existe por causa do `xl`
|
|
77
|
+
*
|
|
78
|
+
* São duas colunas de propriedades **de igual peso** (4 + 4), cada uma com ~320px em 720px —
|
|
79
|
+
* o que cabe rótulo de 104px + valor sem truncar. Não copie este arranjo pra um painel `lg`:
|
|
80
|
+
* ali cada coluna cairia pra ~250px e os valores começariam a quebrar. Em `lg` o desenho certo
|
|
81
|
+
* é o do `-2`: lista plana, uma propriedade por linha.
|
|
82
|
+
*
|
|
83
|
+
* ⚠️ E **nenhuma das duas leva card em volta.** Dado do mesmo nível não ganha superfície
|
|
84
|
+
* própria — ver a nota no `PROPRIEDADES`.
|
|
85
|
+
*
|
|
86
|
+
* ## Regras do DS que este bloco carrega (e que copiar sem elas quebra)
|
|
87
|
+
*
|
|
88
|
+
* - **`width` do `TableHeadCell` e do `TableCell` da mesma coluna têm que ser IGUAIS.** O
|
|
89
|
+
* `Table` do DS posiciona por largura declarada, não por `<colgroup>`: divergir desalinha
|
|
90
|
+
* header e corpo silenciosamente. Aqui as larguras vivem numa constante única (`COLUNAS`),
|
|
91
|
+
* que é o que impede a divergência.
|
|
92
|
+
* - **Faixa de métricas com `divide-x` + `divide-border-default`.** `divide-x` cru é só
|
|
93
|
+
* largura: sem classe de cor a divisória cai em `currentColor` (L-039 aplicada ao `divide`).
|
|
94
|
+
* - **Ação de ícone é `variant="soft"` + `aria-label`.** O maximize e o close que o
|
|
95
|
+
* `FloatingPanel` renderiza são `soft`+`secondary`; `ghost` no meio da fileira fica sem
|
|
96
|
+
* container e lê como desabilitada.
|
|
97
|
+
* - **Número e data com `tabular-nums`.** Numa tabela isso não é cosmético: sem ele os dígitos
|
|
98
|
+
* dançam entre as linhas e a coluna deixa de ser varrível.
|
|
99
|
+
* - **Status é `Chip`**, nunca `<span>` estilizado.
|
|
100
|
+
* - **Wrapper de `gap` é obrigatório no corpo.** O body do `FloatingPanel` **não tem gap entre
|
|
101
|
+
* filhos** (`row-gap: normal`, medido) — só padding. Sem o wrapper, topo, métricas e tabela
|
|
102
|
+
* ficam com 0px entre si. Não confunda com o `PanelBody` do `Panel`, que tem gap embutido.
|
|
103
|
+
*
|
|
104
|
+
* ## Cuidado ao adaptar
|
|
105
|
+
*
|
|
106
|
+
* - **O botão daqui é do EXEMPLO.** Na tela real quem abre o painel é a linha clicada.
|
|
107
|
+
* - **A navegação de período não é decorativa.** Se `‹ ›` não trocam o dado, tire — controle
|
|
108
|
+
* que não controla é pior que ausência de controle.
|
|
109
|
+
* - **`Exportar` exporta o RECORTE, não a base.** O usuário assume que o botão dentro do
|
|
110
|
+
* painel leva o que ele está vendo; exportar tudo é surpresa desagradável. Se o export é da
|
|
111
|
+
* base inteira, o botão pertence à tela, não ao painel.
|
|
112
|
+
* - **Linha da tabela clicável?** Só se levar a algum lugar. `TableRow` tem `onClick` e
|
|
113
|
+
* variante `clickable` — cursor de mão numa linha inerte é promessa falsa.
|
|
114
|
+
*/
|
|
115
|
+
|
|
116
|
+
/** Fixture do bloco. Fica aqui de propósito: bloco é auto-contido, então quem copia vê a
|
|
117
|
+
* forma do dado esperado sem inferir do render. */
|
|
118
|
+
const LICENCIADO = {
|
|
119
|
+
nome: "Marina Duarte",
|
|
120
|
+
iniciais: "MD",
|
|
121
|
+
hex: "#0F766E",
|
|
122
|
+
codigo: "LIC-04812",
|
|
123
|
+
papel: "Licenciada Green",
|
|
124
|
+
status: "Ativa",
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Propriedades em DUAS colunas de igual peso — é o desenho de ficha, e é o que o painel `xl`
|
|
129
|
+
* permite.
|
|
130
|
+
*
|
|
131
|
+
* ⚠️ A 1ª versão tinha um **card "Contato"** com borda e fundo à esquerda e a lista de
|
|
132
|
+
* propriedades à direita. Ficou ruim, e a razão é estrutural: aquele card criava uma segunda
|
|
133
|
+
* hierarquia visual pra dados do MESMO nível — e-mail não é mais importante que cargo. Card
|
|
134
|
+
* dentro do topo só se justifica pra algo que se destaque de verdade (um saldo, um alerta).
|
|
135
|
+
* Aqui e-mail e telefone são propriedades como as outras, com o mesmo ícone-rótulo-valor.
|
|
136
|
+
*/
|
|
137
|
+
const PROPRIEDADES: { icone: LucideIcon; label: string; valor: React.ReactNode }[][] = [
|
|
138
|
+
[
|
|
139
|
+
{ icone: Briefcase, label: "Cargo", valor: "Licenciada Green" },
|
|
140
|
+
{ icone: Building2, label: "Escritório", valor: "Belo Horizonte — MG" },
|
|
141
|
+
{ icone: MapPin, label: "Atuação", valor: "Zona Sul e Centro" },
|
|
142
|
+
{ icone: CalendarDays, label: "Ativa desde", valor: <span className="tabular-nums">12/03/2024</span> },
|
|
143
|
+
],
|
|
144
|
+
[
|
|
145
|
+
{
|
|
146
|
+
icone: Mail,
|
|
147
|
+
label: "E-mail",
|
|
148
|
+
valor: (
|
|
149
|
+
<a
|
|
150
|
+
href="mailto:marina.duarte@example.com"
|
|
151
|
+
className="truncate text-fg-brand hover:underline"
|
|
152
|
+
>
|
|
153
|
+
marina.duarte@example.com
|
|
154
|
+
</a>
|
|
155
|
+
),
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
icone: Phone,
|
|
159
|
+
label: "Telefone",
|
|
160
|
+
valor: (
|
|
161
|
+
<a
|
|
162
|
+
href="tel:5531989012345"
|
|
163
|
+
className="tabular-nums text-fg-brand hover:underline"
|
|
164
|
+
>
|
|
165
|
+
+55 31 98901-2345
|
|
166
|
+
</a>
|
|
167
|
+
),
|
|
168
|
+
},
|
|
169
|
+
{ icone: UserRound, label: "Gestor", valor: "Aline Castro" },
|
|
170
|
+
{ icone: FileText, label: "Contrato", valor: <span className="tabular-nums">CT-2024-0481</span> },
|
|
171
|
+
],
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
const METRICAS = [
|
|
175
|
+
{ icone: Users, label: "Clientes ativos", valor: "64", tom: "brand" as const },
|
|
176
|
+
{ icone: Zap, label: "kWh no mês", valor: "38.240", tom: "brand" as const },
|
|
177
|
+
{ icone: TrendingUp, label: "Comissão", valor: "R$ 4.812", tom: "brand" as const },
|
|
178
|
+
{ icone: CalendarDays, label: "Em análise", valor: "3", tom: "warning" as const },
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Larguras das colunas numa constante única — é isso que impede header e corpo de divergirem.
|
|
183
|
+
*
|
|
184
|
+
* Somam **678px**, que é exatamente a largura útil da linha num painel `xl`: 720 do painel
|
|
185
|
+
* − 18 de gutter de cada lado − 1 de borda de cada lado do container. Medido, não calculado
|
|
186
|
+
* de cabeça: a 1ª versão somava 644 e sobravam **34px vazios depois da coluna de Status**, que
|
|
187
|
+
* lêem como coluna faltando.
|
|
188
|
+
*
|
|
189
|
+
* **A folga vai sempre pra coluna de TEXTO** (`cliente`), nunca pra uma de número: número tem
|
|
190
|
+
* largura previsível e ganhar espaço não o ajuda; nome ganha caractere antes de truncar.
|
|
191
|
+
*/
|
|
192
|
+
const COLUNAS = {
|
|
193
|
+
data: 92,
|
|
194
|
+
cliente: 170,
|
|
195
|
+
/* 128, não 112: a 112 o próprio HEADER truncava em "Distribuido…" — o rótulo é mais largo
|
|
196
|
+
que os valores ("Cemig", "Energisa"). Dimensione a coluna pelo MAIOR entre header e
|
|
197
|
+
conteúdo; header truncado lê como bug, não como economia de espaço. */
|
|
198
|
+
distribuidora: 128,
|
|
199
|
+
consumo: 80,
|
|
200
|
+
comissao: 104,
|
|
201
|
+
status: 104,
|
|
202
|
+
} as const;
|
|
203
|
+
|
|
204
|
+
const LANCAMENTOS = [
|
|
205
|
+
{ data: "02/09", cliente: "Padaria Estrela do Sul", distribuidora: "Cemig", consumo: "1.240", comissao: "R$ 186,00", status: "Pago", tom: "success" as const },
|
|
206
|
+
{ data: "05/09", cliente: "Mercearia Bom Preço", distribuidora: "Cemig", consumo: "890", comissao: "R$ 133,50", status: "Pago", tom: "success" as const },
|
|
207
|
+
{ data: "08/09", cliente: "Oficina do Marcos", distribuidora: "Cemig", consumo: "1.470", comissao: "R$ 220,50", status: "Pago", tom: "success" as const },
|
|
208
|
+
{ data: "11/09", cliente: "Auto Peças Duarte", distribuidora: "Energisa", consumo: "2.310", comissao: "R$ 346,50", status: "Em análise", tom: "warning" as const },
|
|
209
|
+
{ data: "14/09", cliente: "Pet Shop Amigo Fiel", distribuidora: "Cemig", consumo: "620", comissao: "R$ 93,00", status: "Pago", tom: "success" as const },
|
|
210
|
+
{ data: "17/09", cliente: "Studio Vitória", distribuidora: "Energisa", consumo: "540", comissao: "R$ 81,00", status: "Pago", tom: "success" as const },
|
|
211
|
+
{ data: "21/09", cliente: "Mercado Central 24h", distribuidora: "Cemig", consumo: "4.180", comissao: "R$ 627,00", status: "Pago", tom: "success" as const },
|
|
212
|
+
{ data: "23/09", cliente: "Clínica São Bento", distribuidora: "Cemig", consumo: "1.780", comissao: "R$ 267,00", status: "Em análise", tom: "warning" as const },
|
|
213
|
+
{ data: "26/09", cliente: "Escola Novo Saber", distribuidora: "Energisa", consumo: "2.940", comissao: "R$ 441,00", status: "Em análise", tom: "warning" as const },
|
|
214
|
+
{ data: "28/09", cliente: "Restaurante Aurora", distribuidora: "Cemig", consumo: "3.120", comissao: "R$ 468,00", status: "Atrasado", tom: "danger" as const },
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
const COR_DO_TOM = {
|
|
218
|
+
brand: "text-fg-brand",
|
|
219
|
+
warning: "text-fg-warning",
|
|
220
|
+
danger: "text-fg-danger",
|
|
221
|
+
} as const;
|
|
222
|
+
|
|
223
|
+
/** Métrica compacta da faixa: ícone + valor na mesma linha, rótulo abaixo. */
|
|
224
|
+
function Metrica({
|
|
225
|
+
icone: Icone,
|
|
226
|
+
label,
|
|
227
|
+
valor,
|
|
228
|
+
tom,
|
|
229
|
+
}: {
|
|
230
|
+
icone: LucideIcon;
|
|
231
|
+
label: string;
|
|
232
|
+
valor: string;
|
|
233
|
+
tom: keyof typeof COR_DO_TOM;
|
|
234
|
+
}) {
|
|
235
|
+
return (
|
|
236
|
+
<div className="flex flex-col gap-gp-2xs p-pad-2xl">
|
|
237
|
+
<div className={`flex items-center gap-gp-sm ${COR_DO_TOM[tom]}`}>
|
|
238
|
+
<Icone className="size-icon-sm shrink-0" aria-hidden="true" />
|
|
239
|
+
<span className="text-body-xl font-bold tabular-nums leading-none">{valor}</span>
|
|
240
|
+
</div>
|
|
241
|
+
<span className="text-caption-sm text-fg-muted">{label}</span>
|
|
242
|
+
</div>
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function DetalheComTabela() {
|
|
247
|
+
const [aberto, setAberto] = useState(false);
|
|
248
|
+
|
|
249
|
+
return (
|
|
250
|
+
<>
|
|
251
|
+
{/* Na tela real este botão não existe: quem abre o painel é a linha clicada. */}
|
|
252
|
+
<Button variant="outline" color="secondary" onClick={() => setAberto(true)}>
|
|
253
|
+
Abrir detalhe da licenciada
|
|
254
|
+
</Button>
|
|
255
|
+
|
|
256
|
+
<FloatingPanel
|
|
257
|
+
open={aberto}
|
|
258
|
+
onOpenChange={setAberto}
|
|
259
|
+
side="right"
|
|
260
|
+
/* `xl` = 720px. Requisito da tabela, não preferência — ver o JSDoc. */
|
|
261
|
+
size="xl"
|
|
262
|
+
resizable
|
|
263
|
+
maximizable
|
|
264
|
+
resizableStorageKey="dsgreen-paneldetail-3.width"
|
|
265
|
+
titleSlot={
|
|
266
|
+
<div className="flex min-w-0 items-center gap-gp-md">
|
|
267
|
+
<Avatar
|
|
268
|
+
size="lg"
|
|
269
|
+
colorHex={LICENCIADO.hex}
|
|
270
|
+
className="shrink-0"
|
|
271
|
+
aria-label={LICENCIADO.nome}
|
|
272
|
+
>
|
|
273
|
+
{LICENCIADO.iniciais}
|
|
274
|
+
</Avatar>
|
|
275
|
+
<div className="flex min-w-0 flex-col">
|
|
276
|
+
<span className="truncate text-body-md font-semibold text-fg-default">
|
|
277
|
+
{LICENCIADO.nome}
|
|
278
|
+
</span>
|
|
279
|
+
<span className="mt-[2px] flex items-center gap-gp-sm text-body-xs text-fg-muted">
|
|
280
|
+
<span className="tabular-nums">{LICENCIADO.codigo}</span>
|
|
281
|
+
<span className="opacity-50">·</span>
|
|
282
|
+
<Chip color="success" variant="soft" size="sm">
|
|
283
|
+
{LICENCIADO.status}
|
|
284
|
+
</Chip>
|
|
285
|
+
</span>
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
288
|
+
}
|
|
289
|
+
headerActions={
|
|
290
|
+
<Button variant="soft" color="secondary" size="icon-sm" aria-label="Editar licenciada">
|
|
291
|
+
<Pencil />
|
|
292
|
+
</Button>
|
|
293
|
+
}
|
|
294
|
+
footer={
|
|
295
|
+
<>
|
|
296
|
+
<Button
|
|
297
|
+
variant="outline"
|
|
298
|
+
color="secondary"
|
|
299
|
+
size="sm"
|
|
300
|
+
onClick={() => setAberto(false)}
|
|
301
|
+
>
|
|
302
|
+
Fechar
|
|
303
|
+
</Button>
|
|
304
|
+
<Button variant="filled" size="sm">
|
|
305
|
+
Abrir ficha completa
|
|
306
|
+
</Button>
|
|
307
|
+
</>
|
|
308
|
+
}
|
|
309
|
+
>
|
|
310
|
+
{/* Wrapper de gap OBRIGATÓRIO: o body do FloatingPanel não tem gap entre filhos. */}
|
|
311
|
+
<div className="flex flex-col gap-gp-3xl">
|
|
312
|
+
{/* 1. Ficha em duas colunas de igual peso — só cabe porque o painel é `xl`. Em `lg`
|
|
313
|
+
viraria lista plana de uma coluna (ver o `-2`). Sem card em volta: dado do
|
|
314
|
+
mesmo nível não ganha superfície própria. */}
|
|
315
|
+
<div className="grid grid-cols-2 gap-x-gp-4xl">
|
|
316
|
+
{PROPRIEDADES.map((coluna, i) => (
|
|
317
|
+
<div key={i} className="grid grid-cols-[104px_1fr] items-center gap-x-gp-md">
|
|
318
|
+
{coluna.map(({ icone: Icone, label, valor }) => (
|
|
319
|
+
<div key={label} className="contents">
|
|
320
|
+
{/* `min-h-form-sm` (32px) nas duas células: sem piso, a linha tem altura
|
|
321
|
+
do conteúdo e o ritmo da ficha fica irregular. */}
|
|
322
|
+
<div className="flex min-h-form-sm items-center gap-gp-sm text-body-sm text-fg-muted">
|
|
323
|
+
<Icone
|
|
324
|
+
className="size-icon-sm shrink-0 text-fg-subtle"
|
|
325
|
+
aria-hidden="true"
|
|
326
|
+
/>
|
|
327
|
+
<span className="truncate">{label}</span>
|
|
328
|
+
</div>
|
|
329
|
+
<div className="flex min-h-form-sm min-w-0 items-center text-body-sm text-fg-default">
|
|
330
|
+
<span className="min-w-0 truncate">{valor}</span>
|
|
331
|
+
</div>
|
|
332
|
+
</div>
|
|
333
|
+
))}
|
|
334
|
+
</div>
|
|
335
|
+
))}
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
{/* 2. Faixa de métricas do período. `divide-border-default` é obrigatório: `divide-x`
|
|
339
|
+
cru é só largura e a divisória cairia em `currentColor` (L-039). */}
|
|
340
|
+
<div className="grid grid-cols-4 divide-x divide-border-default overflow-hidden rounded-radius-lg border border-border-default bg-bg-surface">
|
|
341
|
+
{METRICAS.map((m) => (
|
|
342
|
+
<Metrica key={m.label} {...m} />
|
|
343
|
+
))}
|
|
344
|
+
</div>
|
|
345
|
+
|
|
346
|
+
{/* 3. Tabela do recorte: cabeçalho de período + exportar, e a série. */}
|
|
347
|
+
<div className="flex flex-col gap-gp-lg">
|
|
348
|
+
<div className="flex flex-wrap items-center justify-between gap-gp-md">
|
|
349
|
+
<div className="flex items-center gap-gp-md">
|
|
350
|
+
<span className="text-body-md font-semibold text-fg-default">
|
|
351
|
+
Setembro 2026
|
|
352
|
+
</span>
|
|
353
|
+
{/* Se `‹ ›` não trocarem o dado de verdade, tire: controle que não controla é
|
|
354
|
+
pior que ausência de controle. */}
|
|
355
|
+
<div className="flex items-center gap-gp-xs">
|
|
356
|
+
<Button
|
|
357
|
+
variant="soft"
|
|
358
|
+
color="secondary"
|
|
359
|
+
size="icon-xs"
|
|
360
|
+
aria-label="Mês anterior"
|
|
361
|
+
>
|
|
362
|
+
<ChevronLeft />
|
|
363
|
+
</Button>
|
|
364
|
+
<Button
|
|
365
|
+
variant="soft"
|
|
366
|
+
color="secondary"
|
|
367
|
+
size="icon-xs"
|
|
368
|
+
aria-label="Mês seguinte"
|
|
369
|
+
>
|
|
370
|
+
<ChevronRight />
|
|
371
|
+
</Button>
|
|
372
|
+
</div>
|
|
373
|
+
</div>
|
|
374
|
+
{/* Exporta o RECORTE (o mês visível), não a base — ver "Cuidado ao adaptar". */}
|
|
375
|
+
<Button variant="outline" color="secondary" size="sm" iconLeft={<Download />}>
|
|
376
|
+
Exportar mês
|
|
377
|
+
</Button>
|
|
378
|
+
</div>
|
|
379
|
+
|
|
380
|
+
{/* ⚠️ **Sem wrapper de borda aqui.** O `Table` do DS já É um card: ele traz
|
|
381
|
+
`bg-bg-table`, `border border-border-default`, raio de **14px** e
|
|
382
|
+
`overflow-hidden` no próprio container. Eu tinha envolvido ele num
|
|
383
|
+
`rounded-radius-lg border` (10px), e o resultado eram **dois raios
|
|
384
|
+
concêntricos** — 14px dentro de 10px — com o canto de dentro estourando o de
|
|
385
|
+
fora. Lê como bug de render, e é card-dentro-de-card: a mesma L-050 que proíbe
|
|
386
|
+
`PropsTable` dentro de `ExampleSection`.
|
|
387
|
+
|
|
388
|
+
Antes de embrulhar qualquer componente do DS numa superfície, verifique se ele
|
|
389
|
+
já tem a sua. */}
|
|
390
|
+
{/* `density="compact"` = linha de **40px** contra 56 do `standard`. É prop do
|
|
391
|
+
componente, não fonte reduzida na mão: a tipografia da tabela já é
|
|
392
|
+
`text-body-sm` (13px), o menor body do projeto. Numa série curta dentro de
|
|
393
|
+
painel, compact é o certo — cabe mais linha sem o corpo do painel rolar. */}
|
|
394
|
+
<Table density="compact" ariaLabel="Lançamentos do mês">
|
|
395
|
+
<TableHead>
|
|
396
|
+
<TableHeadCell field="data" width={COLUNAS.data}>
|
|
397
|
+
Data
|
|
398
|
+
</TableHeadCell>
|
|
399
|
+
<TableHeadCell field="cliente" width={COLUNAS.cliente}>
|
|
400
|
+
Cliente
|
|
401
|
+
</TableHeadCell>
|
|
402
|
+
<TableHeadCell field="distribuidora" width={COLUNAS.distribuidora}>
|
|
403
|
+
Distribuidora
|
|
404
|
+
</TableHeadCell>
|
|
405
|
+
<TableHeadCell field="consumo" width={COLUNAS.consumo} align="right">
|
|
406
|
+
kWh
|
|
407
|
+
</TableHeadCell>
|
|
408
|
+
<TableHeadCell field="comissao" width={COLUNAS.comissao} align="right">
|
|
409
|
+
Comissão
|
|
410
|
+
</TableHeadCell>
|
|
411
|
+
<TableHeadCell field="status" width={COLUNAS.status}>
|
|
412
|
+
Status
|
|
413
|
+
</TableHeadCell>
|
|
414
|
+
</TableHead>
|
|
415
|
+
|
|
416
|
+
<TableBody>
|
|
417
|
+
{LANCAMENTOS.map((l) => (
|
|
418
|
+
<TableRow key={l.data}>
|
|
419
|
+
{/* ⚠️ O `width` de cada célula tem que ser o MESMO do head — o Table do
|
|
420
|
+
DS posiciona por largura declarada. Daí a constante `COLUNAS`. */}
|
|
421
|
+
<TableCell field="data" width={COLUNAS.data}>
|
|
422
|
+
<span className="tabular-nums">{l.data}</span>
|
|
423
|
+
</TableCell>
|
|
424
|
+
<TableCell field="cliente" width={COLUNAS.cliente} ellipsis>
|
|
425
|
+
{l.cliente}
|
|
426
|
+
</TableCell>
|
|
427
|
+
<TableCell field="distribuidora" width={COLUNAS.distribuidora} ellipsis>
|
|
428
|
+
{l.distribuidora}
|
|
429
|
+
</TableCell>
|
|
430
|
+
<TableCell field="consumo" width={COLUNAS.consumo} align="right">
|
|
431
|
+
<span className="tabular-nums">{l.consumo}</span>
|
|
432
|
+
</TableCell>
|
|
433
|
+
<TableCell field="comissao" width={COLUNAS.comissao} align="right">
|
|
434
|
+
<span className="tabular-nums">{l.comissao}</span>
|
|
435
|
+
</TableCell>
|
|
436
|
+
<TableCell field="status" width={COLUNAS.status}>
|
|
437
|
+
<Chip color={l.tom} variant="soft" size="sm">
|
|
438
|
+
{l.status}
|
|
439
|
+
</Chip>
|
|
440
|
+
</TableCell>
|
|
441
|
+
</TableRow>
|
|
442
|
+
))}
|
|
443
|
+
</TableBody>
|
|
444
|
+
</Table>
|
|
445
|
+
</div>
|
|
446
|
+
</div>
|
|
447
|
+
</FloatingPanel>
|
|
448
|
+
</>
|
|
449
|
+
);
|
|
450
|
+
}
|