@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,512 @@
1
+ /**
2
+ * Cronograma de projeto — a tela completa, extraída do showcase do DS (L-034).
3
+ *
4
+ * ## Cuidado ao adaptar
5
+ *
6
+ * **O que ligar ao seu estado:** `rows` e `links` são estado DESTA tela, e é
7
+ * de propósito. O `Gantt` é *dumb* sobre mutação: arrastar emite `onBarMove`,
8
+ * vínculo violado emite `onLinkViolations` — **nada se move sozinho**. Troque
9
+ * os `useState` por dados do servidor e mantenha os handlers aplicando; se
10
+ * você ligar `draggable`/`resizable` sem handler que aplique, o usuário
11
+ * arrasta e vê a barra **voltar**.
12
+ *
13
+ * **O que trocar primeiro:** `_gantt-data.tsx` — linhas, vínculos, colunas e
14
+ * filtros. O resto da tela não muda.
15
+ *
16
+ * **O que remover se não servir:** o `FloatingPanel` de detalhe e o drawer de
17
+ * nova tarefa são **da tela**, não do componente. O `Gantt` só emite
18
+ * `onBarClick` e `onDayAdd` e devolve o payload intacto — quem decide que
19
+ * campos existem é você.
20
+ *
21
+ * **O que NÃO mexer:** a divisão entre `colorKey` (categoria — qual frente) e
22
+ * `row.trailing` (status, como `Chip`). Cor por status quebra a leitura que o
23
+ * componente foi desenhado pra dar.
24
+ *
25
+ * ⚠️ **O pai precisa ter ALTURA.** O componente é `h-full`; sem altura no pai
26
+ * você vê só a toolbar.
27
+ *
28
+ * ⚠️ Se os dados vêm do servidor, passe **`loading`**. Sem ele, `rows={[]}`
29
+ * durante o fetch afirma "Nenhuma tarefa neste período" — o componente não tem
30
+ * como saber que isso é verdade.
31
+ */
32
+
33
+ import { useMemo, useState } from "react";
34
+ import { ptBR } from "date-fns/locale";
35
+ import { addDays, format, startOfDay } from "date-fns";
36
+ import { Plus } from "lucide-react";
37
+ import { Gantt, ganttDaySegment } from "@/components/ui/Gantt";
38
+ import type {
39
+ GanttBarChange,
40
+ GanttColorKey,
41
+ GanttColumn,
42
+ GanttFilterField,
43
+ GanttLink,
44
+ GanttLinkViolation,
45
+ GanttRow,
46
+ } from "@/components/ui/Gantt";
47
+ import { Button } from "@/components/ui/Button";
48
+ import { Chip } from "@/components/ui/Chip";
49
+ import { Avatar } from "@/components/ui/avatar-ig";
50
+ import { FloatingPanel } from "@/components/ui/FloatingPanel";
51
+ import { PageHeader } from "@/components/ui/PageHeader";
52
+ import {
53
+ FormFieldInput,
54
+ FormFieldSelect,
55
+ FormFieldTextarea,
56
+ } from "@/components/ui/FormField";
57
+ import {
58
+ COLUNAS,
59
+ CORES_DISPONIVEIS,
60
+ FILTROS,
61
+ LINKS_SEMENTE,
62
+ PESSOAS,
63
+ ROWS_SEMENTE,
64
+ hoje,
65
+ iniciais,
66
+ type Meta,
67
+ } from "./_gantt-data";
68
+
69
+ export function GanttScreen() {
70
+
71
+ /**
72
+ * ⚠️ As linhas e os vínculos são ESTADO DA TELA, e isso é o ponto do exemplo.
73
+ *
74
+ * O `Gantt` é dumb sobre mutação: arrastar emite `onBarMove`, e quem reescreve
75
+ * as datas é o consumidor. Um exemplo com `rows` constante ligaria
76
+ * `draggable` e o usuário arrastaria pra ver a barra **voltar** — que é
77
+ * exatamente o que o JSDoc de `draggable` avisa que acontece sem handler.
78
+ *
79
+ * Aqui a tela aplica de verdade, e é por isso que o gesto pode ser verificado.
80
+ */
81
+ const [rows, setRows] = useState<GanttRow[]>(ROWS_SEMENTE);
82
+
83
+ /**
84
+ * O drawer de "nova tarefa" — e ele é DA TELA, não do componente.
85
+ *
86
+ * O `Gantt` emite `onDayAdd(dia)` e para aí. Quem decide que campos existem,
87
+ * que cores são oferecidas e o que acontece no salvar é o consumidor — mesma
88
+ * divisão do painel de detalhe (`onBarClick` devolve o payload e a tela
89
+ * desenha a ficha).
90
+ *
91
+ * ⚠️ A versão anterior criava a tarefa DIRETO no clique do "+", com nome e
92
+ * cor chutados. Estava errado por dois motivos: o usuário não escolhia nada, e
93
+ * o exemplo ensinava que `onDayAdd` é "adicione isto" quando ele é "o usuário
94
+ * pediu pra adicionar aqui".
95
+ */
96
+ const [novo, setNovo] = useState<{
97
+ dia: Date;
98
+ nome: string;
99
+ dias: string;
100
+ cor: string;
101
+ responsavel: string;
102
+ frente: string;
103
+ descricao: string;
104
+ } | null>(null);
105
+
106
+ /**
107
+ * Abre o drawer num dia — DUAS portas, um formulário só.
108
+ *
109
+ * O "+" da célula sabe o dia (o usuário apontou pra ele); o "Nova tarefa" da
110
+ * toolbar não sabe, e abre em `hoje`. Por isso a data virou CAMPO em vez de
111
+ * ficar só no subtítulo: sem ela, a porta da toolbar criaria tudo no mesmo dia
112
+ * sem o usuário poder corrigir.
113
+ */
114
+ const abrirNovaTarefa = (dia: Date) =>
115
+ setNovo({
116
+ dia,
117
+ nome: "",
118
+ dias: "3",
119
+ cor: "chart-2",
120
+ responsavel: "ana",
121
+ frente: "integração",
122
+ descricao: "",
123
+ });
124
+ const [links, setLinks] = useState<GanttLink[]>(LINKS_SEMENTE);
125
+
126
+ /**
127
+ * Duração, fim e estilo da prévia — derivados do formulário.
128
+ *
129
+ * `fim` usa `duracao - 1` porque `end` é INCLUSIVO no `Gantt`: 3 dias a
130
+ * partir do dia 10 terminam no 12, não no 13. É a mesma conta do salvar, e
131
+ * está aqui pra ser UMA — se o subtítulo dissesse um fim e o salvar gravasse
132
+ * outro, o usuário só descobriria depois de criar (L-038).
133
+ */
134
+ const duracaoNova = Math.max(1, Number(novo?.dias) || 1);
135
+ const fimNovo = novo ? addDays(novo.dia, duracaoNova - 1) : null;
136
+ const previaDoSegmento = ganttDaySegment({
137
+ colorKey: (novo?.cor ?? "chart-1") as GanttColorKey,
138
+ });
139
+
140
+ /** Reescreve as datas de UMA barra, preservando o resto da linha. */
141
+ const aplicarMudanca = ({ bar, start, end }: GanttBarChange) =>
142
+ setRows((atuais) =>
143
+ atuais.map((r) =>
144
+ r.bars.some((b) => b.id === bar.id)
145
+ ? {
146
+ ...r,
147
+ bars: r.bars.map((b) =>
148
+ b.id === bar.id ? { ...b, start, end } : b,
149
+ ),
150
+ }
151
+ : r,
152
+ ),
153
+ );
154
+ const [detalhe, setDetalhe] = useState<{
155
+ titulo: string;
156
+ meta: Meta;
157
+ inicio: Date;
158
+ fim: Date;
159
+ } | null>(null);
160
+ const [violacoes, setViolacoes] = useState<GanttLinkViolation[]>([]);
161
+
162
+ const rodape = useMemo(() => {
163
+ if (violacoes.length === 0) return null;
164
+ const total = violacoes.reduce((s, v) => s + v.deficitDays, 0);
165
+ return `${violacoes.length} vínculo(s) em conflito · ${total} dia(s) de déficit`;
166
+ }, [violacoes]);
167
+
168
+ return (
169
+ <div className="flex h-full min-h-0 flex-col gap-gp-2xl">
170
+ {/*
171
+ `PageHeader` e não um `<h1>` na unha — é o que `mapa-rede`, `finance` e
172
+ `order-detail` usam, e título de tela é decisão do DS, não de cada
173
+ exemplo. O contador de conflito entra como `badge` porque ele qualifica
174
+ a tela inteira; `Recolher`/`Expandir` são `actions` porque operam nela.
175
+ */}
176
+ <PageHeader
177
+ title="Implantação do portal de operação"
178
+ description="20 linhas · 4 níveis · 16 vínculos dos 4 tipos · 2 conflitos de prazo. Arraste uma barra pra ver o conflito e o caminho crítico recalcularem no mesmo gesto."
179
+ badge={
180
+ rodape ? (
181
+ <Chip size="sm" variant="soft" color="danger" shape="rounded">
182
+ {rodape}
183
+ </Chip>
184
+ ) : undefined
185
+ }
186
+ />
187
+
188
+ {/*
189
+ `min-h-0` no wrapper é o que faz o Gantt receber a altura restante em vez
190
+ de estourar a página. Sem isso, `h-full` num filho de flex-col resolve
191
+ contra `auto` e o componente cresce indefinidamente.
192
+ */}
193
+ <div className="flex min-h-0 flex-1 flex-col">
194
+ <Gantt
195
+ rows={rows}
196
+ links={links}
197
+ draggable
198
+ resizable
199
+ linkable
200
+ onBarMove={aplicarMudanca}
201
+ onBarResize={aplicarMudanca}
202
+ onLinkCreate={(novo) =>
203
+ setLinks((atuais) => [
204
+ ...atuais,
205
+ // O id é do CONSUMIDOR — o componente emite a intenção sem id
206
+ // justamente porque não sabe como você os gera.
207
+ { ...novo, id: `l-${novo.source}-${novo.target}-${atuais.length}` },
208
+ ])
209
+ }
210
+ /*
211
+ O "+" da célula do calendário ABRE O DRAWER — não cria nada.
212
+
213
+ Sem handler o "+" nem renderiza; com ele, o componente diz "o
214
+ usuário pediu pra adicionar no dia X" e a tela decide o resto.
215
+ */
216
+ onDayAdd={abrirNovaTarefa}
217
+ onLinkDelete={(alvo) =>
218
+ setLinks((atuais) => atuais.filter((l) => l.id !== alvo.id))
219
+ }
220
+ columns={COLUNAS}
221
+ filterFields={FILTROS}
222
+ searchable
223
+ criticalPath
224
+ criticalPathToggle={false}
225
+ locale={ptBR}
226
+ now={hoje}
227
+ gridWidth={460}
228
+ onLinkViolations={setViolacoes}
229
+ onGraphError={({ barIds }) =>
230
+ window.alert(`Ciclo no grafo: ${barIds.join(", ")}`)
231
+ }
232
+ onBarClick={(bar, row) => {
233
+ const meta = bar.meta as Meta | undefined;
234
+ if (!meta) return;
235
+ setDetalhe({
236
+ titulo: typeof row.label === "string" ? row.label : bar.id,
237
+ meta,
238
+ inicio: bar.start,
239
+ fim: bar.end,
240
+ });
241
+ }}
242
+ /*
243
+ A ação primária abre o MESMO drawer do "+" da grade de mês.
244
+
245
+ Duas portas pra criar tarefa com dois formulários diferentes é o
246
+ defeito clássico de tela de cadastro: o usuário aprende um e
247
+ encontra o outro. A única diferença entre as portas é o dia em que
248
+ o formulário abre — e o dia é campo, então dá pra mudar nas duas.
249
+ */
250
+ primaryAction={
251
+ <Button
252
+ variant="filled"
253
+ size="md"
254
+ iconLeft={<Plus />}
255
+ onClick={() => abrirNovaTarefa(hoje)}
256
+ >
257
+ Nova tarefa
258
+ </Button>
259
+ }
260
+ />
261
+ </div>
262
+
263
+ {/*
264
+ O painel de detalhe é da TELA, não do componente — o `Gantt` só emite
265
+ `onBarClick` e devolve `bar.meta` intacto. Mesma divisão do `Scheduler`.
266
+ */}
267
+ <FloatingPanel
268
+ open={detalhe !== null}
269
+ onOpenChange={(aberto) => {
270
+ if (!aberto) setDetalhe(null);
271
+ }}
272
+ side="right"
273
+ size="md"
274
+ title={detalhe?.titulo ?? ""}
275
+ description={
276
+ detalhe
277
+ ? `${format(detalhe.inicio, "dd/MM/yyyy", { locale: ptBR })} → ${format(detalhe.fim, "dd/MM/yyyy", { locale: ptBR })}`
278
+ : undefined
279
+ }
280
+ >
281
+ {detalhe ? (
282
+ <div className="flex flex-col gap-gp-2xl">
283
+ <div className="flex flex-col gap-gp-sm">
284
+ <span className="text-caption-md font-semibold uppercase text-fg-subtle">
285
+ Responsável
286
+ </span>
287
+ <div className="flex items-center gap-gp-md">
288
+ <Avatar
289
+ size="sm"
290
+ color="brand"
291
+ aria-label={PESSOAS[detalhe.meta.responsavel]}
292
+ >
293
+ {iniciais(PESSOAS[detalhe.meta.responsavel])}
294
+ </Avatar>
295
+ <span className="text-body-sm text-fg-default">
296
+ {PESSOAS[detalhe.meta.responsavel]}
297
+ </span>
298
+ </div>
299
+ </div>
300
+
301
+ <div className="flex flex-col gap-gp-sm">
302
+ <span className="text-caption-md font-semibold uppercase text-fg-subtle">
303
+ Frente
304
+ </span>
305
+ <span className="text-body-sm text-fg-default">
306
+ {detalhe.meta.frente}
307
+ </span>
308
+ </div>
309
+
310
+ <div className="flex flex-col gap-gp-sm">
311
+ <span className="text-caption-md font-semibold uppercase text-fg-subtle">
312
+ Descrição
313
+ </span>
314
+ <p className="text-body-sm leading-relaxed text-fg-muted">
315
+ {detalhe.meta.descricao}
316
+ </p>
317
+ </div>
318
+ </div>
319
+ ) : null}
320
+ </FloatingPanel>
321
+
322
+ {/*
323
+ Drawer de nova tarefa. `FormFieldInput`/`FormFieldSelect` do DS e não
324
+ `<label>` + `<input>` na unha — L-023: label solta divergia em peso e em
325
+ cor no dark, silenciosamente.
326
+ */}
327
+ <FloatingPanel
328
+ open={novo !== null}
329
+ onOpenChange={(aberto) => {
330
+ if (!aberto) setNovo(null);
331
+ }}
332
+ side="right"
333
+ size="md"
334
+ title="Nova tarefa"
335
+ /*
336
+ O subtítulo diz o PERÍODO, não só o começo: o campo de duração está
337
+ logo abaixo e o usuário mexe nele: ver o fim se mover é o retorno
338
+ imediato de que a conta é inclusiva.
339
+ */
340
+ description={
341
+ novo && fimNovo
342
+ ? `${format(novo.dia, "dd 'de' MMM", { locale: ptBR })} → ${format(
343
+ fimNovo,
344
+ "dd 'de' MMM 'de' yyyy",
345
+ { locale: ptBR },
346
+ )} · ${duracaoNova} ${duracaoNova === 1 ? "dia" : "dias"}`
347
+ : undefined
348
+ }
349
+ footer={
350
+ <div className="flex w-full items-center justify-end gap-gp-md">
351
+ <Button variant="ghost" color="secondary" size="md" onClick={() => setNovo(null)}>
352
+ Cancelar
353
+ </Button>
354
+ <Button
355
+ variant="filled"
356
+ size="md"
357
+ // Nome vazio não cria: o rótulo é a única coisa que a linha
358
+ // mostra na grade, e uma tarefa sem nome é uma linha em branco.
359
+ disabled={!novo?.nome.trim()}
360
+ onClick={() => {
361
+ if (!novo?.nome.trim() || !fimNovo) return;
362
+ const id = `nova-${novo.dia.getTime()}`;
363
+ setRows((atuais) => [
364
+ ...atuais,
365
+ {
366
+ id,
367
+ label: novo.nome.trim(),
368
+ sublabel: PESSOAS[novo.responsavel],
369
+ parent: "f3",
370
+ bars: [
371
+ {
372
+ id: `${id}-b`,
373
+ label: novo.nome.trim(),
374
+ start: novo.dia,
375
+ end: fimNovo,
376
+ colorKey: novo.cor as GanttRow["bars"][number]["colorKey"],
377
+ meta: {
378
+ responsavel: novo.responsavel,
379
+ frente: novo.frente,
380
+ // A descrição digitada alimenta o painel de detalhe,
381
+ // que já a renderiza — sem ela o campo seria enfeite.
382
+ descricao:
383
+ novo.descricao.trim() ||
384
+ `Sem descrição · ${duracaoNova} dia(s).`,
385
+ } satisfies Meta,
386
+ },
387
+ ],
388
+ },
389
+ ]);
390
+ setNovo(null);
391
+ }}
392
+ >
393
+ Criar tarefa
394
+ </Button>
395
+ </div>
396
+ }
397
+ >
398
+ {novo ? (
399
+ <div className="flex flex-col gap-form-gap">
400
+ <FormFieldInput
401
+ label="Nome da tarefa"
402
+ placeholder="Ex.: Revisar contrato de integração"
403
+ value={novo.nome}
404
+ onChange={(e) => setNovo({ ...novo, nome: e.target.value })}
405
+ />
406
+
407
+ <div className="grid grid-cols-2 gap-form-gap">
408
+ {/*
409
+ A data é CAMPO porque o drawer tem duas portas: o "+" da grade
410
+ sabe o dia, o "Nova tarefa" da toolbar não.
411
+
412
+ ⚠️ `value` e `onChange` passam por `yyyy-MM-dd`, e o parse é na
413
+ mão: `new Date("2026-09-30")` é lido como UTC e volta um dia em
414
+ UTC−3 — o mesmo defeito que o filtro de período já teve aqui.
415
+ `new Date(a, m - 1, d)` é meia-noite LOCAL.
416
+ */}
417
+ <FormFieldInput
418
+ label="Início"
419
+ type="date"
420
+ value={format(novo.dia, "yyyy-MM-dd")}
421
+ onChange={(e) => {
422
+ const [a, m, d] = e.target.value.split("-").map(Number);
423
+ if (!a || !m || !d) return;
424
+ setNovo({ ...novo, dia: new Date(a, m - 1, d) });
425
+ }}
426
+ />
427
+ <FormFieldInput
428
+ label="Duração (dias)"
429
+ type="number"
430
+ min={1}
431
+ value={novo.dias}
432
+ onChange={(e) => setNovo({ ...novo, dias: e.target.value })}
433
+ />
434
+ </div>
435
+
436
+ <div className="grid grid-cols-2 gap-form-gap">
437
+ <FormFieldSelect
438
+ label="Frente (cor)"
439
+ options={CORES_DISPONIVEIS.map((c) => ({
440
+ value: c.value,
441
+ label: c.label,
442
+ }))}
443
+ value={novo.cor}
444
+ onValueChange={(v) =>
445
+ setNovo({
446
+ ...novo,
447
+ cor: v,
448
+ // A frente do filtro acompanha a cor escolhida: as duas
449
+ // dizem a mesma coisa, e deixá-las divergir faria a tarefa
450
+ // nova sumir ao filtrar pela frente que ela aparenta ser.
451
+ frente:
452
+ CORES_DISPONIVEIS.find((c) => c.value === v)?.label.toLowerCase() ??
453
+ novo.frente,
454
+ })
455
+ }
456
+ />
457
+ <FormFieldSelect
458
+ label="Responsável"
459
+ options={Object.entries(PESSOAS).map(([value, label]) => ({
460
+ value,
461
+ label,
462
+ }))}
463
+ value={novo.responsavel}
464
+ onValueChange={(v) => setNovo({ ...novo, responsavel: v })}
465
+ />
466
+ </div>
467
+
468
+ <FormFieldTextarea
469
+ label="Descrição"
470
+ placeholder="O que precisa acontecer, e o que trava se não acontecer"
471
+ rows={3}
472
+ helperText="Aparece no painel de detalhe, ao clicar na tarefa."
473
+ value={novo.descricao}
474
+ onChange={(e) => setNovo({ ...novo, descricao: e.target.value })}
475
+ />
476
+
477
+ {/*
478
+ A prévia é o SEGMENTO REAL da grade de mês.
479
+
480
+ `ganttDaySegment` é o mesmo `tv()` que a visão `calendar` usa pra
481
+ desenhar a pílula — mesmo ponto de cor, mesmo tingimento do
482
+ `colorKey`, mesma altura de 18px. Antes aqui havia um `Chip`
483
+ neutro com o NOME da frente escrito dentro: não tinha a cor que a
484
+ escolha produz e não tinha a forma que a grade mostra, então
485
+ prometia uma coisa e entregava outra.
486
+
487
+ Desenhar a pílula na mão daria o mesmo defeito com um passo a
488
+ mais: duas definições da mesma coisa, divergindo na primeira vez
489
+ que uma das duas mudasse.
490
+
491
+ O `relative w-full` neutraliza o `absolute` do slot — na grade ele
492
+ é posicionado por `left`/`top` dentro da semana; aqui, não.
493
+ */}
494
+ <div className="flex flex-col gap-gp-md">
495
+ <span className="text-caption-md font-semibold uppercase text-fg-subtle">
496
+ Prévia na grade de mês
497
+ </span>
498
+ <div className="rounded-radius-lg border border-border-default bg-bg-surface p-pad-xl">
499
+ <div className={previaDoSegmento.root({ class: "relative w-full" })}>
500
+ <span className={previaDoSegmento.dot()} aria-hidden />
501
+ <span className={previaDoSegmento.label()}>
502
+ {novo.nome.trim() || "Nova tarefa"}
503
+ </span>
504
+ </div>
505
+ </div>
506
+ </div>
507
+ </div>
508
+ ) : null}
509
+ </FloatingPanel>
510
+ </div>
511
+ );
512
+ }
@@ -0,0 +1 @@
1
+ export { GanttScreen } from "./gantt-screen";
@@ -0,0 +1 @@
1
+ export { LoginScreen, default } from "./login-screen";