@snksergio/design-system 0.61.0 → 0.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/AlertModal.md +47 -0
- package/dist-lib/ai/componentes/AppShell.md +117 -0
- package/dist-lib/ai/componentes/Breadcrumb.md +187 -0
- package/dist-lib/ai/componentes/Button.md +116 -0
- package/dist-lib/ai/componentes/ButtonGroup.md +162 -0
- package/dist-lib/ai/componentes/CardCheckbox.md +99 -0
- package/dist-lib/ai/componentes/CardOption.md +133 -0
- package/dist-lib/ai/componentes/Chart.md +93 -0
- package/dist-lib/ai/componentes/Chip.md +68 -0
- package/dist-lib/ai/componentes/ChoroplethMap.md +118 -0
- package/dist-lib/ai/componentes/ColorPicker.md +70 -0
- package/dist-lib/ai/componentes/Combobox.md +51 -0
- package/dist-lib/ai/componentes/ConversationListItem.md +90 -0
- package/dist-lib/ai/componentes/DataList.md +111 -0
- package/dist-lib/ai/componentes/DataTable.md +867 -0
- package/dist-lib/ai/componentes/DatePicker.md +84 -0
- package/dist-lib/ai/componentes/DateSeparatorChip.md +59 -0
- package/dist-lib/ai/componentes/EmptyState.md +72 -0
- package/dist-lib/ai/componentes/FileUploadField.md +95 -0
- package/dist-lib/ai/componentes/FloatingPanel.md +118 -0
- package/dist-lib/ai/componentes/FooterTable.md +62 -0
- package/dist-lib/ai/componentes/FormField.md +110 -0
- package/dist-lib/ai/componentes/Gantt.md +552 -0
- package/dist-lib/ai/componentes/Header.md +98 -0
- package/dist-lib/ai/componentes/Icon.md +65 -0
- package/dist-lib/ai/componentes/Kanban.md +343 -0
- package/dist-lib/ai/componentes/Kpi.md +103 -0
- package/dist-lib/ai/componentes/List.md +61 -0
- package/dist-lib/ai/componentes/MarkdownText.md +59 -0
- package/dist-lib/ai/componentes/MenuSidebar.md +128 -0
- package/dist-lib/ai/componentes/MessageAck.md +53 -0
- package/dist-lib/ai/componentes/MessageBubble.md +115 -0
- package/dist-lib/ai/componentes/MessageComposer.md +80 -0
- package/dist-lib/ai/componentes/MessageVariablesPicker.md +104 -0
- package/dist-lib/ai/componentes/Modal.md +88 -0
- package/dist-lib/ai/componentes/MonthYearPicker.md +49 -0
- package/dist-lib/ai/componentes/PageHeader.md +129 -0
- package/dist-lib/ai/componentes/Panel.md +84 -0
- package/dist-lib/ai/componentes/Scheduler.md +421 -0
- package/dist-lib/ai/componentes/ScreenLoader.md +60 -0
- package/dist-lib/ai/componentes/SingleMenuSidebar.md +171 -0
- package/dist-lib/ai/componentes/Spinner.md +52 -0
- package/dist-lib/ai/componentes/Table.md +192 -0
- package/dist-lib/ai/componentes/TableToolbar.md +87 -0
- package/dist-lib/ai/componentes/TabsNavigation.md +152 -0
- package/dist-lib/ai/componentes/Toast.md +49 -0
- package/dist-lib/ai/componentes/_primitivos.md +74 -0
- package/dist-lib/ai/componentes/avatar-ig.md +181 -0
- package/dist-lib/ai/componentes/indice.json +49 -0
- package/dist-lib/ai/exemplos/app-shell/app-shell-example.tsx +140 -0
- package/dist-lib/ai/exemplos/app-shell/index.ts +3 -0
- package/dist-lib/ai/exemplos/app-shell/nav-data.ts +97 -0
- package/dist-lib/ai/exemplos/app-shell/routes.tsx +75 -0
- package/dist-lib/ai/exemplos/chat/chat-screen.tsx +152 -0
- package/dist-lib/ai/exemplos/chat/chat-v2-mocks.ts +171 -0
- package/dist-lib/ai/exemplos/chat/chat-v2.styles.ts +23 -0
- package/dist-lib/ai/exemplos/chat/chat-v2.types.ts +81 -0
- package/dist-lib/ai/exemplos/chat/components/ChannelDot/channel-dot.tsx +28 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationActionsMenu/conversation-actions-menu.tsx +83 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationActionsMenu/index.ts +4 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.styles.ts +22 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.tsx +203 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.types.ts +10 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationColumn/index.ts +2 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.styles.ts +55 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.tsx +52 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.types.ts +7 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationListItem/index.ts +2 -0
- package/dist-lib/ai/exemplos/chat/components/DateSeparator/date-separator.tsx +18 -0
- package/dist-lib/ai/exemplos/chat/components/DetailField/detail-field.tsx +23 -0
- package/dist-lib/ai/exemplos/chat/components/DetailSection/detail-section.tsx +41 -0
- package/dist-lib/ai/exemplos/chat/components/DetailSection/index.ts +1 -0
- package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.styles.ts +30 -0
- package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.tsx +142 -0
- package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.types.ts +16 -0
- package/dist-lib/ai/exemplos/chat/components/DetailsColumn/index.ts +5 -0
- package/dist-lib/ai/exemplos/chat/components/FilterRow/filter-row.styles.ts +37 -0
- package/dist-lib/ai/exemplos/chat/components/FilterRow/filter-row.tsx +45 -0
- package/dist-lib/ai/exemplos/chat/components/FilterRow/index.ts +1 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.styles.ts +27 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.tsx +66 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.types.ts +13 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-rail.tsx +38 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/index.ts +6 -0
- package/dist-lib/ai/exemplos/chat/components/MessageBubble/index.ts +2 -0
- package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.styles.ts +37 -0
- package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.tsx +29 -0
- package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.types.ts +5 -0
- package/dist-lib/ai/exemplos/chat/components/PersonAvatar/index.ts +5 -0
- package/dist-lib/ai/exemplos/chat/components/PersonAvatar/person-avatar.tsx +31 -0
- package/dist-lib/ai/exemplos/chat/components/QueueColumn/index.ts +2 -0
- package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.styles.ts +19 -0
- package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.tsx +140 -0
- package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.types.ts +13 -0
- package/dist-lib/ai/exemplos/chat/components/RailItem/index.ts +1 -0
- package/dist-lib/ai/exemplos/chat/components/RailItem/rail-item.styles.ts +30 -0
- package/dist-lib/ai/exemplos/chat/components/RailItem/rail-item.tsx +42 -0
- package/dist-lib/ai/exemplos/chat/hooks/use-resizable.ts +98 -0
- package/dist-lib/ai/exemplos/chat/index.ts +1 -0
- package/dist-lib/ai/exemplos/clientes/_table-data.ts +59 -0
- package/dist-lib/ai/exemplos/clientes/clientes-screen.tsx +505 -0
- package/dist-lib/ai/exemplos/clientes/clientes-showcase-mocks.ts +117 -0
- package/dist-lib/ai/exemplos/clientes/clientes-showcase.styles.ts +13 -0
- package/dist-lib/ai/exemplos/clientes/clientes-showcase.types.ts +16 -0
- package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.styles.ts +28 -0
- package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.tsx +303 -0
- package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.types.ts +14 -0
- package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/index.ts +2 -0
- package/dist-lib/ai/exemplos/clientes/components/NovoClienteDrawer/index.ts +2 -0
- package/dist-lib/ai/exemplos/clientes/components/NovoClienteDrawer/novo-cliente-drawer.tsx +207 -0
- package/dist-lib/ai/exemplos/clientes/index.ts +1 -0
- package/dist-lib/ai/exemplos/dashboard/dashboard-brazil-map.ts +33 -0
- package/dist-lib/ai/exemplos/dashboard/dashboard-screen.tsx +1110 -0
- package/dist-lib/ai/exemplos/dashboard/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 +217 -0
- package/dist-lib/ai/global/composicao.md +182 -0
- package/dist-lib/ai/indice.json +192 -0
- package/dist-lib/ai/lint/ds-lint-patterns.mjs +115 -0
- package/dist-lib/ai/manifest.json +42 -0
- package/dist-lib/ai/regras/design.md +88 -0
- package/dist-lib/ai/regras/temas.md +192 -0
- package/dist-lib/ai/regras-por-componente.json +103 -0
- package/dist-lib/ai/roteiros/app-builder/roteiro.md +155 -0
- package/dist-lib/ai/roteiros/auth-builder/roteiro.md +42 -0
- package/dist-lib/ai/roteiros/cards/roteiro.md +34 -0
- package/dist-lib/ai/roteiros/charts/roteiro.md +32 -0
- package/dist-lib/ai/roteiros/chat/roteiro.md +31 -0
- package/dist-lib/ai/roteiros/crud-builder/blueprint.md +63 -0
- package/dist-lib/ai/roteiros/crud-builder/entrevista.md +139 -0
- package/dist-lib/ai/roteiros/crud-builder/geracao.md +113 -0
- package/dist-lib/ai/roteiros/crud-builder/roteiro.md +89 -0
- package/dist-lib/ai/roteiros/dashboard-builder/blueprint.md +47 -0
- package/dist-lib/ai/roteiros/dashboard-builder/entrevista.md +62 -0
- package/dist-lib/ai/roteiros/dashboard-builder/geracao.md +88 -0
- package/dist-lib/ai/roteiros/dashboard-builder/roteiro.md +88 -0
- package/dist-lib/ai/roteiros/drawers/roteiro.md +41 -0
- package/dist-lib/ai/roteiros/list-builder/blueprint.md +94 -0
- package/dist-lib/ai/roteiros/list-builder/entrevista.md +188 -0
- package/dist-lib/ai/roteiros/list-builder/geracao.md +63 -0
- package/dist-lib/ai/roteiros/list-builder/roteiro.md +91 -0
- package/dist-lib/ai/roteiros/module-replicator/roteiro.md +56 -0
- package/dist-lib/ai/roteiros/page-detail/roteiro.md +32 -0
- package/dist-lib/ai/roteiros/page-edit/roteiro.md +31 -0
- package/dist-lib/ai/roteiros/screen-composer/roteiro.md +95 -0
- package/package.json +4 -1
|
@@ -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";
|