@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,97 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LayoutDashboard,
|
|
3
|
+
Users,
|
|
4
|
+
BarChart3,
|
|
5
|
+
Settings,
|
|
6
|
+
User,
|
|
7
|
+
UsersRound,
|
|
8
|
+
} from "lucide-react";
|
|
9
|
+
import type {
|
|
10
|
+
SidebarContext,
|
|
11
|
+
SidebarMenuItem,
|
|
12
|
+
} from "@/components/ui/MenuSidebar";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* nav-data — navegação do esqueleto de app, por CONTEXTO (módulo no rail).
|
|
16
|
+
*
|
|
17
|
+
* Modelo genérico de SaaS (adapte ao seu app): cada CONTEXTO é um módulo com
|
|
18
|
+
* seus itens; itens podem ter `subitems` (grupo expansível). O `href` é o id de
|
|
19
|
+
* navegação — o AppShell casa o item ativo por href e o mapa de rotas
|
|
20
|
+
* (`routes.tsx`) resolve o componente. Sem router externo (troque por
|
|
21
|
+
* react-router/next se quiser: mantenha o href como path).
|
|
22
|
+
*/
|
|
23
|
+
export const NAV_CONTEXTS: SidebarContext[] = [
|
|
24
|
+
{
|
|
25
|
+
id: "app",
|
|
26
|
+
label: "Aplicação",
|
|
27
|
+
icon: LayoutDashboard,
|
|
28
|
+
items: [
|
|
29
|
+
{ name: "Início", icon: LayoutDashboard, href: "#/app/inicio" },
|
|
30
|
+
{ name: "Clientes", icon: Users, href: "#/app/clientes" },
|
|
31
|
+
{
|
|
32
|
+
name: "Relatórios",
|
|
33
|
+
icon: BarChart3,
|
|
34
|
+
subitems: [
|
|
35
|
+
{ name: "Vendas", href: "#/app/relatorios/vendas" },
|
|
36
|
+
{ name: "Desempenho", href: "#/app/relatorios/desempenho" },
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "config",
|
|
43
|
+
label: "Configurações",
|
|
44
|
+
icon: Settings,
|
|
45
|
+
items: [
|
|
46
|
+
{ name: "Perfil", icon: User, href: "#/config/perfil" },
|
|
47
|
+
{ name: "Equipe", icon: UsersRound, href: "#/config/equipe" },
|
|
48
|
+
],
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
/** Entrada achatada por href — pro breadcrumb, command palette (⌘K) e lookups. */
|
|
53
|
+
export type NavEntry = {
|
|
54
|
+
href: string;
|
|
55
|
+
label: string;
|
|
56
|
+
parentLabel?: string;
|
|
57
|
+
contextId: string;
|
|
58
|
+
contextLabel: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function flattenItem(
|
|
62
|
+
item: SidebarMenuItem,
|
|
63
|
+
contextId: string,
|
|
64
|
+
contextLabel: string,
|
|
65
|
+
): NavEntry[] {
|
|
66
|
+
if (item.subitems && item.subitems.length > 0) {
|
|
67
|
+
return item.subitems
|
|
68
|
+
.filter((s) => s.href)
|
|
69
|
+
.map((s) => ({
|
|
70
|
+
href: s.href!,
|
|
71
|
+
label: s.name,
|
|
72
|
+
parentLabel: item.name,
|
|
73
|
+
contextId,
|
|
74
|
+
contextLabel,
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
if (item.href) {
|
|
78
|
+
return [{ href: item.href, label: item.name, contextId, contextLabel }];
|
|
79
|
+
}
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Todas as folhas navegáveis (itens-link + sub-itens), achatadas. */
|
|
84
|
+
export const NAV_ENTRIES: NavEntry[] = NAV_CONTEXTS.flatMap((ctx) =>
|
|
85
|
+
ctx.items.flatMap((item) => flattenItem(item, ctx.id, ctx.label)),
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
/** Primeiro href navegável de um contexto (evita item órfão ao trocar de módulo). */
|
|
89
|
+
export function firstHrefOfContext(contextId: string): string {
|
|
90
|
+
const entry = NAV_ENTRIES.find((e) => e.contextId === contextId);
|
|
91
|
+
return entry?.href ?? NAV_ENTRIES[0].href;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Entrada de um href (ou a primeira como fallback). */
|
|
95
|
+
export function entryOfHref(href: string): NavEntry {
|
|
96
|
+
return NAV_ENTRIES.find((e) => e.href === href) ?? NAV_ENTRIES[0];
|
|
97
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { PageHeader } from "@/components/ui/PageHeader";
|
|
3
|
+
import { entryOfHref } from "./nav-data";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Mapa de rotas DECLARATIVO — href → conteúdo. Substitui a cadeia de
|
|
7
|
+
* `if (href === ...) return <X/>` (frágil e verbosa quando cresce): registrar
|
|
8
|
+
* uma tela = adicionar 1 linha aqui. `resolveRoute` faz o lookup com fallback.
|
|
9
|
+
*
|
|
10
|
+
* Cada tela real vem dos builders do DS (crud/list/dashboard) — aqui os stubs
|
|
11
|
+
* só mostram o esqueleto navegável. Troque `<StubPage/>` pelo componente da tela.
|
|
12
|
+
*/
|
|
13
|
+
function StubPage({ href }: { href: string }) {
|
|
14
|
+
const entry = entryOfHref(href);
|
|
15
|
+
return (
|
|
16
|
+
<div className="flex h-full min-h-0 flex-col gap-gp-2xl">
|
|
17
|
+
<PageHeader
|
|
18
|
+
title={entry.label}
|
|
19
|
+
description={
|
|
20
|
+
entry.parentLabel
|
|
21
|
+
? `${entry.contextLabel} › ${entry.parentLabel}`
|
|
22
|
+
: entry.contextLabel
|
|
23
|
+
}
|
|
24
|
+
/>
|
|
25
|
+
<div className="grid flex-1 place-items-center rounded-radius-xl border border-dashed border-border-default bg-bg-subtle p-pad-6xl">
|
|
26
|
+
<div className="flex max-w-[420px] flex-col items-center gap-gp-sm text-center">
|
|
27
|
+
<p className="text-title-md font-semibold text-fg-default">
|
|
28
|
+
Conteúdo de “{entry.label}”
|
|
29
|
+
</p>
|
|
30
|
+
<p className="text-body-sm text-fg-muted">
|
|
31
|
+
Monte esta tela com os builders do DS —{" "}
|
|
32
|
+
<code className="rounded-radius-sm bg-bg-muted px-pad-xs py-[1px] text-caption-sm">
|
|
33
|
+
/ds-create-crud
|
|
34
|
+
</code>
|
|
35
|
+
,{" "}
|
|
36
|
+
<code className="rounded-radius-sm bg-bg-muted px-pad-xs py-[1px] text-caption-sm">
|
|
37
|
+
/ds-create-list
|
|
38
|
+
</code>{" "}
|
|
39
|
+
ou{" "}
|
|
40
|
+
<code className="rounded-radius-sm bg-bg-muted px-pad-xs py-[1px] text-caption-sm">
|
|
41
|
+
/ds-create-dashboard
|
|
42
|
+
</code>{" "}
|
|
43
|
+
— e troque o stub pela tela no mapa de rotas.
|
|
44
|
+
</p>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Registro de rotas: href → factory de conteúdo. Adicione telas reais aqui
|
|
53
|
+
* (ex.: `"#/app/clientes": () => <ClientesScreen />`).
|
|
54
|
+
*/
|
|
55
|
+
export const ROUTES: Record<string, () => ReactNode> = {
|
|
56
|
+
"#/app/inicio": () => <StubPage href="#/app/inicio" />,
|
|
57
|
+
"#/app/clientes": () => <StubPage href="#/app/clientes" />,
|
|
58
|
+
"#/app/relatorios/vendas": () => <StubPage href="#/app/relatorios/vendas" />,
|
|
59
|
+
"#/app/relatorios/desempenho": () => (
|
|
60
|
+
<StubPage href="#/app/relatorios/desempenho" />
|
|
61
|
+
),
|
|
62
|
+
"#/config/perfil": () => <StubPage href="#/config/perfil" />,
|
|
63
|
+
"#/config/equipe": () => <StubPage href="#/config/equipe" />,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Resolve o conteúdo de um href, com placeholder pra rota não registrada. */
|
|
67
|
+
export function resolveRoute(href: string): ReactNode {
|
|
68
|
+
const route = ROUTES[href];
|
|
69
|
+
if (route) return route();
|
|
70
|
+
return (
|
|
71
|
+
<div className="grid h-full min-h-[60vh] place-items-center">
|
|
72
|
+
<p className="text-body-sm text-fg-muted">Rota não encontrada: {href}</p>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { cn } from "@/lib/utils";
|
|
3
|
+
import { chatV2Styles, mobileOverlay } from "./chat-v2.styles";
|
|
4
|
+
import { CONVERSATIONS, CONVERSATION_DETAILS, DEFAULT_DETAIL } from "./chat-v2-mocks";
|
|
5
|
+
import type { FilterGroupKey, QueueFilter } from "./chat-v2.types";
|
|
6
|
+
import { useResizable } from "./hooks/use-resizable";
|
|
7
|
+
import { ConversationColumn } from "./components/ConversationColumn";
|
|
8
|
+
import { DetailsColumn, type SectionsState } from "./components/DetailsColumn";
|
|
9
|
+
import { FiltersColumn, FiltersRail, type FiltersState } from "./components/FiltersColumn";
|
|
10
|
+
import { QueueColumn } from "./components/QueueColumn";
|
|
11
|
+
|
|
12
|
+
/** Em mobile (<md) só uma view fica visível por vez. */
|
|
13
|
+
type MobileView = "list" | "chat" | "details";
|
|
14
|
+
|
|
15
|
+
export function ChatScreen() {
|
|
16
|
+
const s = chatV2Styles();
|
|
17
|
+
|
|
18
|
+
const [activeId, setActiveId] = useState<string>(CONVERSATIONS[0].id);
|
|
19
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
20
|
+
const [mobileView, setMobileView] = useState<MobileView>("list");
|
|
21
|
+
|
|
22
|
+
const handleSelectConversation = (id: string) => {
|
|
23
|
+
setActiveId(id);
|
|
24
|
+
setMobileView("chat");
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Toggle de detalhes:
|
|
28
|
+
// - Desktop: alterna o painel lateral (detailsOpen).
|
|
29
|
+
// - Mobile (<md): navega entre "chat" ↔ "details" SEM fechar o painel.
|
|
30
|
+
const handleToggleDetails = () => {
|
|
31
|
+
const isMobile =
|
|
32
|
+
typeof window !== "undefined" &&
|
|
33
|
+
window.matchMedia("(max-width: 767px)").matches;
|
|
34
|
+
if (isMobile) {
|
|
35
|
+
setDetailsOpen(true);
|
|
36
|
+
setMobileView((v) => (v === "details" ? "chat" : "details"));
|
|
37
|
+
} else {
|
|
38
|
+
setDetailsOpen((o) => !o);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Filtros multi-seleção (Set por grupo)
|
|
43
|
+
const [filters, setFilters] = useState<FiltersState>({
|
|
44
|
+
status: new Set(["open"]),
|
|
45
|
+
channels: new Set(),
|
|
46
|
+
categories: new Set(),
|
|
47
|
+
innerStatus: new Set(),
|
|
48
|
+
});
|
|
49
|
+
const toggleFilter = (group: FilterGroupKey, id: string) => {
|
|
50
|
+
setFilters((prev) => {
|
|
51
|
+
const next = new Set(prev[group]);
|
|
52
|
+
if (next.has(id)) next.delete(id);
|
|
53
|
+
else next.add(id);
|
|
54
|
+
return { ...prev, [group]: next };
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const [filtersCollapsed, setFiltersCollapsed] = useState(false);
|
|
59
|
+
const [queueFilter, setQueueFilter] = useState<QueueFilter>("all");
|
|
60
|
+
|
|
61
|
+
const [detailsOpen, setDetailsOpen] = useState(true);
|
|
62
|
+
const [openSections, setOpenSections] = useState<SectionsState>({
|
|
63
|
+
contact: true,
|
|
64
|
+
ticket: true,
|
|
65
|
+
history: true,
|
|
66
|
+
});
|
|
67
|
+
const toggleSection = (id: keyof SectionsState) =>
|
|
68
|
+
setOpenSections((m) => ({ ...m, [id]: !m[id] }));
|
|
69
|
+
|
|
70
|
+
const detailsResize = useResizable({
|
|
71
|
+
initial: 360,
|
|
72
|
+
min: 280,
|
|
73
|
+
max: 520,
|
|
74
|
+
storageKey: "chat-screen.detailsWidth",
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const activeConversation =
|
|
78
|
+
CONVERSATIONS.find((c) => c.id === activeId) ?? CONVERSATIONS[0];
|
|
79
|
+
const activeDetail =
|
|
80
|
+
CONVERSATION_DETAILS[activeConversation.id] ?? DEFAULT_DETAIL;
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
// Conteúdo da página (sem shell). Embrulhe no seu AppShell/layout — o
|
|
84
|
+
// parent precisa dar altura (h-full/flex) pra a tela de chat preencher.
|
|
85
|
+
<div className="flex flex-col h-full min-h-0">
|
|
86
|
+
{/* Body — desktop: 4 colunas lado a lado.
|
|
87
|
+
Mobile (<md): só uma view visível por vez, fullscreen, controlada por
|
|
88
|
+
`mobileView` (list → chat → details). Simula navegação de app. */}
|
|
89
|
+
<div className={s.root()}>
|
|
90
|
+
{/* Filters: escondido em mobile. `md:contents` mantém a coluna no flex parent. */}
|
|
91
|
+
<div className={s.filtersWrap()}>
|
|
92
|
+
{filtersCollapsed ? (
|
|
93
|
+
<FiltersRail filters={filters} onToggleFilter={toggleFilter} />
|
|
94
|
+
) : (
|
|
95
|
+
<FiltersColumn filters={filters} onToggleFilter={toggleFilter} />
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<QueueColumn
|
|
100
|
+
activeId={activeId}
|
|
101
|
+
onSelect={handleSelectConversation}
|
|
102
|
+
searchQuery={searchQuery}
|
|
103
|
+
onSearchChange={setSearchQuery}
|
|
104
|
+
queueFilter={queueFilter}
|
|
105
|
+
onQueueFilterChange={setQueueFilter}
|
|
106
|
+
filtersCollapsed={filtersCollapsed}
|
|
107
|
+
onToggleFilters={() => setFiltersCollapsed((c) => !c)}
|
|
108
|
+
className={cn(
|
|
109
|
+
mobileView === "list"
|
|
110
|
+
? `${mobileOverlay.active} max-md:z-[1]`
|
|
111
|
+
: mobileOverlay.hidden,
|
|
112
|
+
)}
|
|
113
|
+
/>
|
|
114
|
+
|
|
115
|
+
<ConversationColumn
|
|
116
|
+
conversation={activeConversation}
|
|
117
|
+
detailsOpen={detailsOpen}
|
|
118
|
+
onToggleDetails={handleToggleDetails}
|
|
119
|
+
onBackToList={() => setMobileView("list")}
|
|
120
|
+
className={cn(
|
|
121
|
+
mobileView === "chat"
|
|
122
|
+
? `${mobileOverlay.active} max-md:z-[2]`
|
|
123
|
+
: mobileOverlay.hidden,
|
|
124
|
+
)}
|
|
125
|
+
/>
|
|
126
|
+
|
|
127
|
+
{detailsOpen && (
|
|
128
|
+
<DetailsColumn
|
|
129
|
+
conversation={activeConversation}
|
|
130
|
+
detail={activeDetail}
|
|
131
|
+
openSections={openSections}
|
|
132
|
+
onToggleSection={toggleSection}
|
|
133
|
+
onClose={() => {
|
|
134
|
+
setDetailsOpen(false);
|
|
135
|
+
setMobileView("chat");
|
|
136
|
+
}}
|
|
137
|
+
onBackToChat={() => setMobileView("chat")}
|
|
138
|
+
width={detailsResize.width}
|
|
139
|
+
onResizeStart={detailsResize.onResizeStart}
|
|
140
|
+
className={cn(
|
|
141
|
+
mobileView === "details"
|
|
142
|
+
? `${mobileOverlay.active} max-md:!w-full max-md:z-[3]`
|
|
143
|
+
: mobileOverlay.hidden,
|
|
144
|
+
)}
|
|
145
|
+
/>
|
|
146
|
+
)}
|
|
147
|
+
</div>
|
|
148
|
+
</div>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export default ChatScreen;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CheckCircle2,
|
|
3
|
+
Clock,
|
|
4
|
+
Crown,
|
|
5
|
+
Inbox,
|
|
6
|
+
Mail,
|
|
7
|
+
MessageSquare,
|
|
8
|
+
Send,
|
|
9
|
+
Shield,
|
|
10
|
+
Users,
|
|
11
|
+
} from "lucide-react";
|
|
12
|
+
import type {
|
|
13
|
+
ChannelId,
|
|
14
|
+
Conversation,
|
|
15
|
+
ConversationDetail,
|
|
16
|
+
ConversationStatus,
|
|
17
|
+
FilterGroupKey,
|
|
18
|
+
FilterItem,
|
|
19
|
+
Message,
|
|
20
|
+
} from "./chat-v2.types";
|
|
21
|
+
|
|
22
|
+
/* ── Lookups por canal ──────────────────────────────────────────── */
|
|
23
|
+
|
|
24
|
+
export const CHANNEL_LABEL: Record<ChannelId, string> = {
|
|
25
|
+
whatsapp: "WhatsApp",
|
|
26
|
+
telegram: "Telegram",
|
|
27
|
+
instagram: "Instagram",
|
|
28
|
+
webchat: "Web Chat",
|
|
29
|
+
email: "Email",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const CHANNEL_HEX: Record<ChannelId, string> = {
|
|
33
|
+
whatsapp: "#25d366",
|
|
34
|
+
telegram: "#0088cc",
|
|
35
|
+
instagram: "#e1306c",
|
|
36
|
+
webchat: "#0a3a2e",
|
|
37
|
+
email: "#8754ec",
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/* ── Status dot (cor por status na sidebar/header) ──────────────── */
|
|
41
|
+
|
|
42
|
+
export const STATUS_DOT: Record<ConversationStatus, string> = {
|
|
43
|
+
attending: "var(--color-fg-success)",
|
|
44
|
+
waiting: "var(--color-fg-warning)",
|
|
45
|
+
ai: "var(--color-fg-info)",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/* ── Conversations (fila) ───────────────────────────────────────── */
|
|
49
|
+
|
|
50
|
+
export const CONVERSATIONS: Conversation[] = [
|
|
51
|
+
{
|
|
52
|
+
id: "#A-2453", name: "Maria Hernandez", initials: "MH", avatarHex: "#f59e0b",
|
|
53
|
+
channel: "whatsapp", tag: "Royal", tagKind: "warning",
|
|
54
|
+
status: "attending", statusLabel: "Em atendimento",
|
|
55
|
+
last: "Quanto seria o investimento inicial?", time: "10:42", unread: 0,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: "#A-2455", name: "James Johnson", initials: "JJ", avatarHex: "#0a3a2e",
|
|
59
|
+
channel: "telegram", tag: "Licenciado", tagKind: "info",
|
|
60
|
+
status: "waiting", statusLabel: "Em espera",
|
|
61
|
+
last: "Hey, how are you?", time: "10:30", unread: 2,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: "#A-2458", name: "Camila Ribeiro", initials: "CR", avatarHex: "#8754ec",
|
|
65
|
+
channel: "instagram", tag: "Royal", tagKind: "warning",
|
|
66
|
+
status: "attending", statusLabel: "Em atendimento",
|
|
67
|
+
last: "Vou enviar os documentos hoje à tarde", time: "09:55", unread: 1,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "#A-2461", name: "Roberto Souza", initials: "RS", avatarHex: "#0088cc",
|
|
71
|
+
channel: "whatsapp", tag: "Lead", tagKind: "success",
|
|
72
|
+
status: "ai", statusLabel: "IA",
|
|
73
|
+
last: "Obrigado! Vou avaliar a proposta.", time: "Ontem", unread: 0,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
id: "#A-2466", name: "Ana Costa", initials: "AC", avatarHex: "#1cb280",
|
|
77
|
+
channel: "email", tag: "Licenciado", tagKind: "info",
|
|
78
|
+
status: "waiting", statusLabel: "Em espera",
|
|
79
|
+
last: "Bom dia! Quando podemos conversar?", time: "Ontem", unread: 0,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: "#A-2470", name: "Pedro Pereira", initials: "PP", avatarHex: "#ef4444",
|
|
83
|
+
channel: "webchat", tag: "Lead", tagKind: "success",
|
|
84
|
+
status: "attending", statusLabel: "Em atendimento",
|
|
85
|
+
last: "Recebi os materiais, muito bons!", time: "Seg", unread: 0,
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
/* ── Filtros (sidebar esquerda) ─────────────────────────────────── */
|
|
90
|
+
|
|
91
|
+
export const STATUS_WORKFLOW: FilterItem[] = [
|
|
92
|
+
{ id: "open", name: "Abertos", count: 24, hasUnread: true, icon: Inbox },
|
|
93
|
+
{ id: "pending", name: "Pendentes", count: 6, icon: Clock },
|
|
94
|
+
{ id: "resolved", name: "Resolvidos", count: 18, icon: CheckCircle2 },
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
export const CHANNELS_LIST: FilterItem[] = [
|
|
98
|
+
{ id: "whatsapp", name: "WhatsApp", count: 12, hasUnread: true, color: "#25d366", icon: MessageSquare },
|
|
99
|
+
{ id: "telegram", name: "Telegram", count: 4, color: "#0088cc", icon: Send },
|
|
100
|
+
{ id: "instagram", name: "Instagram", count: 5, color: "#e1306c", icon: MessageSquare },
|
|
101
|
+
{ id: "webchat", name: "Web Chat", count: 2, color: "#0a3a2e", icon: MessageSquare },
|
|
102
|
+
{ id: "email", name: "Email", count: 1, color: "#8754ec", icon: Mail },
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
export const CATEGORIES_LIST: FilterItem[] = [
|
|
106
|
+
{ id: "royal", name: "Royals", count: 9, color: "#f6b51e", icon: Crown },
|
|
107
|
+
{ id: "licenciado", name: "Licenciados", count: 11, color: "#8754ec", icon: Shield },
|
|
108
|
+
{ id: "lead", name: "Leads", count: 4, color: "#70c748", icon: Users },
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
export const STATUS_INNER_LIST: FilterItem[] = [
|
|
112
|
+
{ id: "attending", name: "Em atendimento", count: 8, color: "var(--color-fg-success)" },
|
|
113
|
+
{ id: "waiting", name: "Em espera", count: 7, color: "var(--color-fg-warning)" },
|
|
114
|
+
{ id: "ai", name: "IA", count: 9, color: "var(--color-fg-info)" },
|
|
115
|
+
];
|
|
116
|
+
|
|
117
|
+
/* ── Ordem canônica dos 4 grupos de filtros ───────────────────── */
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Fonte única dos grupos de filtros. Consumido por `FiltersColumn` (com
|
|
121
|
+
* `title`) e `FiltersRail` (sem `title`, divisor entre grupos).
|
|
122
|
+
*/
|
|
123
|
+
export const FILTER_GROUPS: {
|
|
124
|
+
group: FilterGroupKey;
|
|
125
|
+
title: string;
|
|
126
|
+
items: FilterItem[];
|
|
127
|
+
}[] = [
|
|
128
|
+
{ group: "status", title: "STATUS", items: STATUS_WORKFLOW },
|
|
129
|
+
{ group: "channels", title: "CANAIS", items: CHANNELS_LIST },
|
|
130
|
+
{ group: "categories", title: "CATEGORIAS", items: CATEGORIES_LIST },
|
|
131
|
+
{ group: "innerStatus", title: "STATUS INTERNO", items: STATUS_INNER_LIST },
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
/* ── Conversation details (painel direito) ──────────────────────── */
|
|
135
|
+
|
|
136
|
+
export const CONVERSATION_DETAILS: Record<string, ConversationDetail> = {
|
|
137
|
+
"#A-2453": {
|
|
138
|
+
phone: "+55 11 98765-4321",
|
|
139
|
+
email: "maria.hernandez@example.com",
|
|
140
|
+
firstContact: "12 mar 2024",
|
|
141
|
+
assignedTo: "Você",
|
|
142
|
+
createdAt: "Hoje, 09:30",
|
|
143
|
+
extraTags: ["VIP", "Setor financeiro"],
|
|
144
|
+
history: [
|
|
145
|
+
{ id: "#A-2401", date: "20 abr 2024", subject: "Dúvida sobre comissão", status: "Resolvido" },
|
|
146
|
+
{ id: "#A-2389", date: "15 abr 2024", subject: "Reativação de conta", status: "Resolvido" },
|
|
147
|
+
{ id: "#A-2367", date: "10 abr 2024", subject: "Materiais exclusivos", status: "Resolvido" },
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export const DEFAULT_DETAIL: ConversationDetail = {
|
|
153
|
+
phone: "—",
|
|
154
|
+
email: "—",
|
|
155
|
+
firstContact: "—",
|
|
156
|
+
assignedTo: "—",
|
|
157
|
+
createdAt: "—",
|
|
158
|
+
extraTags: [],
|
|
159
|
+
history: [],
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/* ── Sample messages (thread inicial da conversa ativa) ─────────── */
|
|
163
|
+
|
|
164
|
+
export const SAMPLE_MESSAGES: Message[] = [
|
|
165
|
+
{ id: 1, from: "them", text: "Olá! Bom dia 👋", time: "10:30" },
|
|
166
|
+
{ id: 2, from: "them", text: "Vi a oferta de vocês para licenciados Royal e gostaria de mais informações.", time: "10:30" },
|
|
167
|
+
{ id: 3, from: "me", text: "Bom dia, Maria! Tudo bem? 😊 Posso te ajudar com toda alegria.", time: "10:32", status: "read" },
|
|
168
|
+
{ id: 4, from: "me", text: "A oferta para Royals inclui 30% de comissão recorrente, suporte prioritário e materiais exclusivos.", time: "10:32", status: "read" },
|
|
169
|
+
{ id: 5, from: "them", text: "Have good budget for the project.", time: "10:42" },
|
|
170
|
+
{ id: 6, from: "them", text: "Quanto seria o investimento inicial?", time: "10:42" },
|
|
171
|
+
];
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { tv } from "@/utils/tv";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Layout root da página Chat V2 — 4 colunas side-by-side em desktop,
|
|
5
|
+
* fullscreen overlays em mobile (<md), controladas por `mobileView`.
|
|
6
|
+
*/
|
|
7
|
+
export const chatV2Styles = tv({
|
|
8
|
+
slots: {
|
|
9
|
+
/** Body do AppShell — flex row com gap entre colunas. `relative` pra mobile overlays. */
|
|
10
|
+
root: "flex flex-1 min-h-0 gap-gp-2xl relative",
|
|
11
|
+
/** Wrapper dos filtros (col 1) — escondido em mobile. */
|
|
12
|
+
filtersWrap: "hidden md:contents",
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Helpers de className pros 3 estados de mobile view (list/chat/details).
|
|
18
|
+
* Cada coluna recebe a classe correspondente; em desktop ficam sem efeito.
|
|
19
|
+
*/
|
|
20
|
+
export const mobileOverlay = {
|
|
21
|
+
active: "max-md:absolute max-md:inset-0 max-md:w-full max-md:rounded-radius-none max-md:border-0",
|
|
22
|
+
hidden: "max-md:hidden",
|
|
23
|
+
} as const;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { LucideIcon } from "@/lib/lucide-types";
|
|
2
|
+
|
|
3
|
+
/* ── Channels (canais de comunicação) ───────────────────────────── */
|
|
4
|
+
|
|
5
|
+
export type ChannelId =
|
|
6
|
+
| "whatsapp"
|
|
7
|
+
| "telegram"
|
|
8
|
+
| "instagram"
|
|
9
|
+
| "webchat"
|
|
10
|
+
| "email";
|
|
11
|
+
|
|
12
|
+
/* ── Conversation (item da fila) ─────────────────────────────────── */
|
|
13
|
+
|
|
14
|
+
export type ConvTagKind = "warning" | "info" | "success";
|
|
15
|
+
|
|
16
|
+
export type ConversationStatus = "attending" | "waiting" | "ai";
|
|
17
|
+
|
|
18
|
+
export type Conversation = {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
initials: string;
|
|
22
|
+
avatarHex: string;
|
|
23
|
+
channel: ChannelId;
|
|
24
|
+
tag: string;
|
|
25
|
+
tagKind: ConvTagKind;
|
|
26
|
+
status: ConversationStatus;
|
|
27
|
+
statusLabel: string;
|
|
28
|
+
last: string;
|
|
29
|
+
time: string;
|
|
30
|
+
unread: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/* ── Details (painel direito da tela) ────────────────────────────── */
|
|
34
|
+
|
|
35
|
+
export type ConversationHistoryItem = {
|
|
36
|
+
id: string;
|
|
37
|
+
subject: string;
|
|
38
|
+
date: string;
|
|
39
|
+
status: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type ConversationDetail = {
|
|
43
|
+
phone: string;
|
|
44
|
+
email: string;
|
|
45
|
+
firstContact: string;
|
|
46
|
+
assignedTo: string;
|
|
47
|
+
createdAt: string;
|
|
48
|
+
extraTags: string[];
|
|
49
|
+
history: ConversationHistoryItem[];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/* ── Filters (sidebar esquerda) ──────────────────────────────────── */
|
|
53
|
+
|
|
54
|
+
export type FilterGroupKey = "status" | "channels" | "categories" | "innerStatus";
|
|
55
|
+
|
|
56
|
+
export type FilterItem = {
|
|
57
|
+
id: string;
|
|
58
|
+
name: string;
|
|
59
|
+
count: number;
|
|
60
|
+
hasUnread?: boolean;
|
|
61
|
+
/** Cor do dot/ícone leading — aceita CSS var ou hex. */
|
|
62
|
+
color?: string;
|
|
63
|
+
/** Componente lucide opcional pra leading. */
|
|
64
|
+
icon?: LucideIcon;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/* ── Messages (thread) ───────────────────────────────────────────── */
|
|
68
|
+
|
|
69
|
+
export type MessageStatus = "sent" | "read";
|
|
70
|
+
|
|
71
|
+
export type Message = {
|
|
72
|
+
id: number;
|
|
73
|
+
from: "me" | "them";
|
|
74
|
+
text: string;
|
|
75
|
+
time: string;
|
|
76
|
+
status?: MessageStatus;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/* ── Queue filter chips (Todas / Não lidas / Lidas) ─────────────── */
|
|
80
|
+
|
|
81
|
+
export type QueueFilter = "all" | "unread" | "read";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ChannelId } from "../../chat-v2.types";
|
|
2
|
+
import { CHANNEL_HEX, CHANNEL_LABEL } from "../../chat-v2-mocks";
|
|
3
|
+
|
|
4
|
+
export type ChannelDotProps = {
|
|
5
|
+
channel: ChannelId;
|
|
6
|
+
/** Quando true, mostra apenas o dot (sem label). Default: false. */
|
|
7
|
+
dotOnly?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Dot 8px + label do canal. Cor vem de `CHANNEL_HEX`. Usado em
|
|
12
|
+
* `ConversationListItem`, header de `ConversationColumn` e tooltips.
|
|
13
|
+
*/
|
|
14
|
+
export function ChannelDot({ channel, dotOnly = false }: ChannelDotProps) {
|
|
15
|
+
return (
|
|
16
|
+
<span
|
|
17
|
+
className="inline-flex items-center gap-[4px] text-caption-sm text-fg-muted"
|
|
18
|
+
title={CHANNEL_LABEL[channel]}
|
|
19
|
+
>
|
|
20
|
+
<span
|
|
21
|
+
className="size-[8px] rounded-radius-full shrink-0"
|
|
22
|
+
style={{ background: CHANNEL_HEX[channel] }}
|
|
23
|
+
aria-hidden
|
|
24
|
+
/>
|
|
25
|
+
{!dotOnly && CHANNEL_LABEL[channel]}
|
|
26
|
+
</span>
|
|
27
|
+
);
|
|
28
|
+
}
|