@snksergio/design-system 0.62.0 → 0.64.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-lib/ai/blocos/chart/budget-breakdown.tsx +174 -0
- package/dist-lib/ai/blocos/indice.md +43 -0
- package/dist-lib/ai/blocos/paneldetail/detalhe-com-tabela.tsx +450 -0
- package/dist-lib/ai/blocos/paneldetail/detalhe-de-tarefa-com-abas.tsx +570 -0
- package/dist-lib/ai/blocos/paneldetail/detalhe-do-registro.tsx +555 -0
- package/dist-lib/ai/componentes/DataTable.md +2 -1
- package/dist-lib/ai/componentes/TableToolbar.md +1 -1
- package/dist-lib/ai/exemplos/app-shell/app-shell-example.tsx +140 -0
- package/dist-lib/ai/exemplos/app-shell/index.ts +3 -0
- package/dist-lib/ai/exemplos/app-shell/nav-data.ts +97 -0
- package/dist-lib/ai/exemplos/app-shell/routes.tsx +75 -0
- package/dist-lib/ai/exemplos/chat/chat-screen.tsx +152 -0
- package/dist-lib/ai/exemplos/chat/chat-v2-mocks.ts +171 -0
- package/dist-lib/ai/exemplos/chat/chat-v2.styles.ts +23 -0
- package/dist-lib/ai/exemplos/chat/chat-v2.types.ts +81 -0
- package/dist-lib/ai/exemplos/chat/components/ChannelDot/channel-dot.tsx +28 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationActionsMenu/conversation-actions-menu.tsx +83 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationActionsMenu/index.ts +4 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.styles.ts +22 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.tsx +203 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationColumn/conversation-column.types.ts +10 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationColumn/index.ts +2 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.styles.ts +55 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.tsx +52 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationListItem/conversation-list-item.types.ts +7 -0
- package/dist-lib/ai/exemplos/chat/components/ConversationListItem/index.ts +2 -0
- package/dist-lib/ai/exemplos/chat/components/DateSeparator/date-separator.tsx +18 -0
- package/dist-lib/ai/exemplos/chat/components/DetailField/detail-field.tsx +23 -0
- package/dist-lib/ai/exemplos/chat/components/DetailSection/detail-section.tsx +41 -0
- package/dist-lib/ai/exemplos/chat/components/DetailSection/index.ts +1 -0
- package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.styles.ts +30 -0
- package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.tsx +142 -0
- package/dist-lib/ai/exemplos/chat/components/DetailsColumn/details-column.types.ts +16 -0
- package/dist-lib/ai/exemplos/chat/components/DetailsColumn/index.ts +5 -0
- package/dist-lib/ai/exemplos/chat/components/FilterRow/filter-row.styles.ts +37 -0
- package/dist-lib/ai/exemplos/chat/components/FilterRow/filter-row.tsx +45 -0
- package/dist-lib/ai/exemplos/chat/components/FilterRow/index.ts +1 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.styles.ts +27 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.tsx +66 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-column.types.ts +13 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/filters-rail.tsx +38 -0
- package/dist-lib/ai/exemplos/chat/components/FiltersColumn/index.ts +6 -0
- package/dist-lib/ai/exemplos/chat/components/MessageBubble/index.ts +2 -0
- package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.styles.ts +37 -0
- package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.tsx +29 -0
- package/dist-lib/ai/exemplos/chat/components/MessageBubble/message-bubble.types.ts +5 -0
- package/dist-lib/ai/exemplos/chat/components/PersonAvatar/index.ts +5 -0
- package/dist-lib/ai/exemplos/chat/components/PersonAvatar/person-avatar.tsx +31 -0
- package/dist-lib/ai/exemplos/chat/components/QueueColumn/index.ts +2 -0
- package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.styles.ts +19 -0
- package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.tsx +140 -0
- package/dist-lib/ai/exemplos/chat/components/QueueColumn/queue-column.types.ts +13 -0
- package/dist-lib/ai/exemplos/chat/components/RailItem/index.ts +1 -0
- package/dist-lib/ai/exemplos/chat/components/RailItem/rail-item.styles.ts +30 -0
- package/dist-lib/ai/exemplos/chat/components/RailItem/rail-item.tsx +42 -0
- package/dist-lib/ai/exemplos/chat/hooks/use-resizable.ts +98 -0
- package/dist-lib/ai/exemplos/chat/index.ts +1 -0
- package/dist-lib/ai/exemplos/clientes/_table-data.ts +59 -0
- package/dist-lib/ai/exemplos/clientes/clientes-screen.tsx +505 -0
- package/dist-lib/ai/exemplos/clientes/clientes-showcase-mocks.ts +117 -0
- package/dist-lib/ai/exemplos/clientes/clientes-showcase.styles.ts +13 -0
- package/dist-lib/ai/exemplos/clientes/clientes-showcase.types.ts +16 -0
- package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.styles.ts +28 -0
- package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.tsx +303 -0
- package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/detail-drawer.types.ts +14 -0
- package/dist-lib/ai/exemplos/clientes/components/DetailDrawer/index.ts +2 -0
- package/dist-lib/ai/exemplos/clientes/components/NovoClienteDrawer/index.ts +2 -0
- package/dist-lib/ai/exemplos/clientes/components/NovoClienteDrawer/novo-cliente-drawer.tsx +207 -0
- package/dist-lib/ai/exemplos/clientes/index.ts +1 -0
- package/dist-lib/ai/exemplos/edit-page/components/StepNav.tsx +81 -0
- package/dist-lib/ai/exemplos/edit-page/components/section-card.tsx +85 -0
- package/dist-lib/ai/exemplos/edit-page/edit-page-screen.tsx +234 -0
- package/dist-lib/ai/exemplos/edit-page/index.ts +1 -0
- package/dist-lib/ai/exemplos/finance/_table-data.ts +57 -0
- package/dist-lib/ai/exemplos/finance/clientes-financeiro-mocks.ts +227 -0
- package/dist-lib/ai/exemplos/finance/clientes-financeiro.types.ts +75 -0
- package/dist-lib/ai/exemplos/finance/clientes-showcase-mocks.ts +117 -0
- package/dist-lib/ai/exemplos/finance/clientes-showcase.styles.ts +13 -0
- package/dist-lib/ai/exemplos/finance/clientes-showcase.types.ts +16 -0
- package/dist-lib/ai/exemplos/finance/components/EditarFinanceDrawer/editar-finance-drawer.tsx +241 -0
- package/dist-lib/ai/exemplos/finance/components/EditarFinanceDrawer/index.ts +5 -0
- package/dist-lib/ai/exemplos/finance/components/ExtratoExpansion/extrato-expansion.tsx +172 -0
- package/dist-lib/ai/exemplos/finance/components/ExtratoExpansion/index.ts +1 -0
- package/dist-lib/ai/exemplos/finance/components/FinanceDetailPanel/finance-detail-panel.tsx +241 -0
- package/dist-lib/ai/exemplos/finance/components/FinanceDetailPanel/index.ts +2 -0
- package/dist-lib/ai/exemplos/finance/components/NovoClienteDrawer/index.ts +2 -0
- package/dist-lib/ai/exemplos/finance/components/NovoClienteDrawer/novo-cliente-drawer.tsx +207 -0
- package/dist-lib/ai/exemplos/finance/components/SacarDialog/index.ts +2 -0
- package/dist-lib/ai/exemplos/finance/components/SacarDialog/sacar-dialog.tsx +346 -0
- package/dist-lib/ai/exemplos/finance/finance-screen.tsx +821 -0
- package/dist-lib/ai/exemplos/finance/index.ts +1 -0
- package/dist-lib/ai/exemplos/gantt/_gantt-data.tsx +402 -0
- package/dist-lib/ai/exemplos/gantt/gantt-screen.tsx +512 -0
- package/dist-lib/ai/exemplos/gantt/index.ts +1 -0
- package/dist-lib/ai/exemplos/login/index.ts +1 -0
- package/dist-lib/ai/exemplos/login/login-screen.tsx +246 -0
- package/dist-lib/ai/exemplos/mapa-rede/components/ConsultorDetailPanel/consultor-detail-panel.tsx +158 -0
- package/dist-lib/ai/exemplos/mapa-rede/components/ConsultorDetailPanel/index.ts +2 -0
- package/dist-lib/ai/exemplos/mapa-rede/index.ts +1 -0
- package/dist-lib/ai/exemplos/mapa-rede/mapa-de-rede-mocks.ts +655 -0
- package/dist-lib/ai/exemplos/mapa-rede/mapa-de-rede.types.ts +53 -0
- package/dist-lib/ai/exemplos/mapa-rede/mapa-rede-screen.tsx +227 -0
- package/dist-lib/ai/exemplos/order-detail/components/ActivityTab.tsx +77 -0
- package/dist-lib/ai/exemplos/order-detail/components/AttachmentsTab.tsx +45 -0
- package/dist-lib/ai/exemplos/order-detail/components/CommentsTab.tsx +111 -0
- package/dist-lib/ai/exemplos/order-detail/components/DetailsTab.tsx +189 -0
- package/dist-lib/ai/exemplos/order-detail/components/OverviewTab.tsx +200 -0
- package/dist-lib/ai/exemplos/order-detail/components/section-card.tsx +85 -0
- package/dist-lib/ai/exemplos/order-detail/index.ts +1 -0
- package/dist-lib/ai/exemplos/order-detail/order-detail-screen.tsx +119 -0
- package/dist-lib/ai/exemplos/order-detail/order-mocks.ts +186 -0
- package/dist-lib/ai/exemplos/order-detail/order.types.ts +118 -0
- package/dist-lib/ai/global/componentes.md +7 -0
- package/dist-lib/ai/indice.json +30 -15
- package/dist-lib/ai/manifest.json +32 -6
- package/dist-lib/ai/roteiros/app-builder/roteiro.md +155 -0
- package/dist-lib/ai/roteiros/auth-builder/roteiro.md +42 -0
- package/dist-lib/ai/roteiros/cards/roteiro.md +34 -0
- package/dist-lib/ai/roteiros/charts/roteiro.md +32 -0
- package/dist-lib/ai/roteiros/chat/roteiro.md +31 -0
- package/dist-lib/ai/roteiros/crud-builder/blueprint.md +63 -0
- package/dist-lib/ai/roteiros/crud-builder/entrevista.md +146 -0
- package/dist-lib/ai/roteiros/crud-builder/geracao.md +113 -0
- package/dist-lib/ai/roteiros/crud-builder/roteiro.md +89 -0
- package/dist-lib/ai/roteiros/drawers/roteiro.md +41 -0
- package/dist-lib/ai/roteiros/list-builder/blueprint.md +94 -0
- package/dist-lib/ai/roteiros/list-builder/entrevista.md +193 -0
- package/dist-lib/ai/roteiros/list-builder/geracao.md +63 -0
- package/dist-lib/ai/roteiros/list-builder/roteiro.md +91 -0
- package/dist-lib/ai/roteiros/module-replicator/roteiro.md +56 -0
- package/dist-lib/ai/roteiros/page-detail/roteiro.md +32 -0
- package/dist-lib/ai/roteiros/page-edit/roteiro.md +31 -0
- package/dist-lib/ai/roteiros/screen-composer/roteiro.md +95 -0
- package/dist-lib/chunks/{panel-B-liPlxr.mjs → panel-CN0ChoHM.mjs} +5 -2
- package/dist-lib/chunks/{panel-B-liPlxr.mjs.map → panel-CN0ChoHM.mjs.map} +1 -1
- package/dist-lib/chunks/{panel-DoNbjeqX.cjs → panel-Cpq1xHck.cjs} +5 -2
- package/dist-lib/chunks/{panel-DoNbjeqX.cjs.map → panel-Cpq1xHck.cjs.map} +1 -1
- package/dist-lib/index.cjs +1 -1
- package/dist-lib/index.mjs +2 -2
- package/dist-lib/preview/clientes.cjs +1 -1
- package/dist-lib/preview/clientes.mjs +1 -1
- package/dist-lib/src/components/ui/DataTable/data-table.d.ts.map +1 -1
- package/dist-lib/src/components/ui/DataTable/data-table.types.d.ts +20 -2
- package/dist-lib/src/components/ui/DataTable/data-table.types.d.ts.map +1 -1
- package/package.json +1 -1
- /package/dist-lib/ai/roteiros/{dashboard → dashboard-builder}/blueprint.md +0 -0
- /package/dist-lib/ai/roteiros/{dashboard → dashboard-builder}/entrevista.md +0 -0
- /package/dist-lib/ai/roteiros/{dashboard → dashboard-builder}/geracao.md +0 -0
- /package/dist-lib/ai/roteiros/{dashboard → dashboard-builder}/roteiro.md +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { Menu as MenuIcon, Search, X } from "lucide-react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
import { Button } from "@/components/ui/Button/button";
|
|
5
|
+
import { Chip, ChipGroup, ChipGroupItem } from "@/components/ui/Chip";
|
|
6
|
+
import { Input } from "@/components/shadcn/input";
|
|
7
|
+
import { CONVERSATIONS } from "../../chat-v2-mocks";
|
|
8
|
+
import type { QueueFilter } from "../../chat-v2.types";
|
|
9
|
+
import { ConversationListItem } from "../ConversationListItem";
|
|
10
|
+
import { queueColumnStyles } from "./queue-column.styles";
|
|
11
|
+
import type { QueueColumnProps } from "./queue-column.types";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Coluna da fila de conversas (340px). Header com toggle de filtros + título +
|
|
15
|
+
* badge de contagem; Search + chips Todas/Não lidas/Lidas; lista scrollável.
|
|
16
|
+
*/
|
|
17
|
+
export function QueueColumn({
|
|
18
|
+
activeId,
|
|
19
|
+
onSelect,
|
|
20
|
+
searchQuery,
|
|
21
|
+
onSearchChange,
|
|
22
|
+
queueFilter,
|
|
23
|
+
onQueueFilterChange,
|
|
24
|
+
filtersCollapsed,
|
|
25
|
+
onToggleFilters,
|
|
26
|
+
className,
|
|
27
|
+
}: QueueColumnProps) {
|
|
28
|
+
const s = queueColumnStyles();
|
|
29
|
+
|
|
30
|
+
const filtered = useMemo(() => {
|
|
31
|
+
const q = searchQuery.trim().toLowerCase();
|
|
32
|
+
const bySearch = q
|
|
33
|
+
? CONVERSATIONS.filter(
|
|
34
|
+
(c) =>
|
|
35
|
+
c.name.toLowerCase().includes(q) ||
|
|
36
|
+
c.id.toLowerCase().includes(q) ||
|
|
37
|
+
c.last.toLowerCase().includes(q),
|
|
38
|
+
)
|
|
39
|
+
: CONVERSATIONS;
|
|
40
|
+
if (queueFilter === "unread") return bySearch.filter((c) => c.unread > 0);
|
|
41
|
+
if (queueFilter === "read") return bySearch.filter((c) => c.unread === 0);
|
|
42
|
+
return bySearch;
|
|
43
|
+
}, [searchQuery, queueFilter]);
|
|
44
|
+
|
|
45
|
+
const counts = useMemo(() => {
|
|
46
|
+
const q = searchQuery.trim().toLowerCase();
|
|
47
|
+
const bySearch = q
|
|
48
|
+
? CONVERSATIONS.filter(
|
|
49
|
+
(c) =>
|
|
50
|
+
c.name.toLowerCase().includes(q) ||
|
|
51
|
+
c.id.toLowerCase().includes(q) ||
|
|
52
|
+
c.last.toLowerCase().includes(q),
|
|
53
|
+
)
|
|
54
|
+
: CONVERSATIONS;
|
|
55
|
+
return {
|
|
56
|
+
all: bySearch.length,
|
|
57
|
+
unread: bySearch.filter((c) => c.unread > 0).length,
|
|
58
|
+
read: bySearch.filter((c) => c.unread === 0).length,
|
|
59
|
+
};
|
|
60
|
+
}, [searchQuery]);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<aside aria-label="Fila de conversas" className={cn(s.root(), className)}>
|
|
64
|
+
<header className={s.header()}>
|
|
65
|
+
<Button
|
|
66
|
+
color="secondary"
|
|
67
|
+
variant="ghost"
|
|
68
|
+
size="icon-sm"
|
|
69
|
+
onClick={onToggleFilters}
|
|
70
|
+
aria-label={filtersCollapsed ? "Expandir filtros" : "Colapsar filtros"}
|
|
71
|
+
aria-pressed={!filtersCollapsed}
|
|
72
|
+
>
|
|
73
|
+
<MenuIcon />
|
|
74
|
+
</Button>
|
|
75
|
+
<h2 className={s.title()}>Conversas</h2>
|
|
76
|
+
<Chip color="primary" variant="soft" size="sm" shape="rounded">
|
|
77
|
+
{filtered.length}
|
|
78
|
+
</Chip>
|
|
79
|
+
</header>
|
|
80
|
+
|
|
81
|
+
<div className={s.searchWrap()}>
|
|
82
|
+
<div className={s.searchInner()}>
|
|
83
|
+
<Search size={14} strokeWidth={1.8} className={s.searchIcon()} />
|
|
84
|
+
<Input
|
|
85
|
+
type="text"
|
|
86
|
+
placeholder="Buscar conversa, ID ou nome..."
|
|
87
|
+
value={searchQuery}
|
|
88
|
+
onChange={(e) => onSearchChange(e.target.value)}
|
|
89
|
+
className={s.searchInput()}
|
|
90
|
+
/>
|
|
91
|
+
{searchQuery && (
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
onClick={() => onSearchChange("")}
|
|
95
|
+
className={s.searchClear()}
|
|
96
|
+
aria-label="Limpar busca"
|
|
97
|
+
>
|
|
98
|
+
<X size={12} />
|
|
99
|
+
</button>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
|
|
104
|
+
<div className={s.chipsWrap()}>
|
|
105
|
+
<ChipGroup
|
|
106
|
+
type="single"
|
|
107
|
+
value={queueFilter}
|
|
108
|
+
onValueChange={(v) => onQueueFilterChange((v || "all") as QueueFilter)}
|
|
109
|
+
size="md"
|
|
110
|
+
ariaLabel="Filtrar conversas"
|
|
111
|
+
>
|
|
112
|
+
<ChipGroupItem value="all">
|
|
113
|
+
Todas{counts.all > 0 && ` (${counts.all})`}
|
|
114
|
+
</ChipGroupItem>
|
|
115
|
+
<ChipGroupItem value="unread">
|
|
116
|
+
Não lidas{counts.unread > 0 && ` (${counts.unread})`}
|
|
117
|
+
</ChipGroupItem>
|
|
118
|
+
<ChipGroupItem value="read">
|
|
119
|
+
Lidas{counts.read > 0 && ` (${counts.read})`}
|
|
120
|
+
</ChipGroupItem>
|
|
121
|
+
</ChipGroup>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div className={s.list()}>
|
|
125
|
+
{filtered.length === 0 ? (
|
|
126
|
+
<div className={s.empty()}>Nenhuma conversa encontrada.</div>
|
|
127
|
+
) : (
|
|
128
|
+
filtered.map((c) => (
|
|
129
|
+
<ConversationListItem
|
|
130
|
+
key={c.id}
|
|
131
|
+
conv={c}
|
|
132
|
+
isActive={c.id === activeId}
|
|
133
|
+
onSelect={onSelect}
|
|
134
|
+
/>
|
|
135
|
+
))
|
|
136
|
+
)}
|
|
137
|
+
</div>
|
|
138
|
+
</aside>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { QueueFilter } from "../../chat-v2.types";
|
|
2
|
+
|
|
3
|
+
export type QueueColumnProps = {
|
|
4
|
+
activeId: string;
|
|
5
|
+
onSelect: (id: string) => void;
|
|
6
|
+
searchQuery: string;
|
|
7
|
+
onSearchChange: (q: string) => void;
|
|
8
|
+
queueFilter: QueueFilter;
|
|
9
|
+
onQueueFilterChange: (f: QueueFilter) => void;
|
|
10
|
+
filtersCollapsed: boolean;
|
|
11
|
+
onToggleFilters: () => void;
|
|
12
|
+
className?: string;
|
|
13
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { RailItem, type RailItemProps } from "./rail-item";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { tv, type VariantProps } from "@/utils/tv";
|
|
2
|
+
|
|
3
|
+
export const railItemStyles = tv({
|
|
4
|
+
slots: {
|
|
5
|
+
button: [
|
|
6
|
+
"relative grid place-items-center size-form-md rounded-radius-md cursor-pointer",
|
|
7
|
+
"transition-colors duration-150",
|
|
8
|
+
].join(" "),
|
|
9
|
+
/** Badge dot 6px (top-right) pra "tem novidades". */
|
|
10
|
+
unreadDot:
|
|
11
|
+
"absolute top-[2px] right-[2px] size-[6px] rounded-radius-full bg-bg-brand",
|
|
12
|
+
/** Fallback dot (quando item não tem icon). */
|
|
13
|
+
fallbackDot: "size-[10px] rounded-radius-full",
|
|
14
|
+
},
|
|
15
|
+
variants: {
|
|
16
|
+
isActive: {
|
|
17
|
+
true: {
|
|
18
|
+
button: "bg-bg-muted dark:bg-bg-emphasis text-fg-default",
|
|
19
|
+
},
|
|
20
|
+
false: {
|
|
21
|
+
button: "text-fg-muted hover:bg-bg-muted hover:text-fg-default",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
defaultVariants: {
|
|
26
|
+
isActive: false,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export type RailItemVariants = VariantProps<typeof railItemStyles>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { FilterItem } from "../../chat-v2.types";
|
|
2
|
+
import { railItemStyles } from "./rail-item.styles";
|
|
3
|
+
|
|
4
|
+
export type RailItemProps = {
|
|
5
|
+
item: FilterItem;
|
|
6
|
+
isActive: boolean;
|
|
7
|
+
onClick: () => void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Botão ícone compacto (32px) usado na `FiltersRail`. Suporta cor por
|
|
12
|
+
* `item.color`, ícone Lucide ou fallback dot, e badge "tem novidades".
|
|
13
|
+
*/
|
|
14
|
+
export function RailItem({ item, isActive, onClick }: RailItemProps) {
|
|
15
|
+
const Icon = item.icon;
|
|
16
|
+
const s = railItemStyles({ isActive });
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<button
|
|
20
|
+
type="button"
|
|
21
|
+
onClick={onClick}
|
|
22
|
+
aria-pressed={isActive}
|
|
23
|
+
title={`${item.name} (${item.count})`}
|
|
24
|
+
className={s.button()}
|
|
25
|
+
>
|
|
26
|
+
{Icon ? (
|
|
27
|
+
<Icon
|
|
28
|
+
size={16}
|
|
29
|
+
strokeWidth={1.8}
|
|
30
|
+
style={{ color: item.color ?? "currentColor" }}
|
|
31
|
+
/>
|
|
32
|
+
) : (
|
|
33
|
+
<span
|
|
34
|
+
className={s.fallbackDot()}
|
|
35
|
+
style={{ background: item.color ?? "var(--color-fg-muted)" }}
|
|
36
|
+
aria-hidden
|
|
37
|
+
/>
|
|
38
|
+
)}
|
|
39
|
+
{item.hasUnread && <span className={s.unreadDot()} aria-label="tem novidades" />}
|
|
40
|
+
</button>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
|
|
3
|
+
export type UseResizableOptions = {
|
|
4
|
+
/** Largura inicial (em px) antes do localStorage hidratar. */
|
|
5
|
+
initial: number;
|
|
6
|
+
/** Largura mínima permitida (em px). */
|
|
7
|
+
min: number;
|
|
8
|
+
/** Largura máxima permitida (em px). */
|
|
9
|
+
max: number;
|
|
10
|
+
/** Chave no localStorage pra persistir o último valor entre sessões. */
|
|
11
|
+
storageKey: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type UseResizableResult = {
|
|
15
|
+
/** Largura atual em px (controlada pelo drag + hidratação localStorage). */
|
|
16
|
+
width: number;
|
|
17
|
+
/** Handler pra começar o drag — atribuir em `onMouseDown` do handle. */
|
|
18
|
+
onResizeStart: (e: React.MouseEvent) => void;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Hook de drag-resize horizontal pra split-panes com persist em localStorage.
|
|
23
|
+
*
|
|
24
|
+
* Comportamento: o handle é assumido como estando no **left edge** do painel —
|
|
25
|
+
* arrastar pra esquerda (delta negativo em X) **aumenta** a largura. Reverter o
|
|
26
|
+
* sinal de `delta` no handler caso o handle esteja no right edge.
|
|
27
|
+
*
|
|
28
|
+
* Hidratação:
|
|
29
|
+
* - No mount, tenta ler `localStorage[storageKey]` e validar dentro de
|
|
30
|
+
* [min, max]. Se válido, usa esse valor; senão, usa `initial`.
|
|
31
|
+
* - Após hidratação, qualquer mudança de `width` é salva (com check de
|
|
32
|
+
* `hydrated.current` pra evitar overwrite do valor persistido com o
|
|
33
|
+
* `initial` no primeiro paint).
|
|
34
|
+
*
|
|
35
|
+
* Cleanup:
|
|
36
|
+
* - `mousemove`/`mouseup` são listeners globais durante o drag. `mouseup`
|
|
37
|
+
* remove os 2 + restaura `cursor` e `user-select` do `<body>`.
|
|
38
|
+
*/
|
|
39
|
+
export function useResizable({
|
|
40
|
+
initial,
|
|
41
|
+
min,
|
|
42
|
+
max,
|
|
43
|
+
storageKey,
|
|
44
|
+
}: UseResizableOptions): UseResizableResult {
|
|
45
|
+
const [width, setWidth] = useState<number>(initial);
|
|
46
|
+
const hydrated = useRef(false);
|
|
47
|
+
|
|
48
|
+
// Hidrata do localStorage no mount
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
if (typeof window === "undefined") return;
|
|
51
|
+
try {
|
|
52
|
+
const raw = window.localStorage.getItem(storageKey);
|
|
53
|
+
const n = raw ? Number(raw) : NaN;
|
|
54
|
+
if (!Number.isNaN(n) && n >= min && n <= max) setWidth(n);
|
|
55
|
+
} catch {
|
|
56
|
+
/* ignore */
|
|
57
|
+
}
|
|
58
|
+
hydrated.current = true;
|
|
59
|
+
}, [min, max, storageKey]);
|
|
60
|
+
|
|
61
|
+
// Salva quando muda (após hidratação)
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (!hydrated.current || typeof window === "undefined") return;
|
|
64
|
+
try {
|
|
65
|
+
window.localStorage.setItem(storageKey, String(width));
|
|
66
|
+
} catch {
|
|
67
|
+
/* ignore */
|
|
68
|
+
}
|
|
69
|
+
}, [width, storageKey]);
|
|
70
|
+
|
|
71
|
+
const onResizeStart = useCallback(
|
|
72
|
+
(e: React.MouseEvent) => {
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
const startX = e.clientX;
|
|
75
|
+
const startWidth = width;
|
|
76
|
+
|
|
77
|
+
const onMove = (ev: MouseEvent) => {
|
|
78
|
+
// Handle no left edge: drag pra ESQUERDA (delta < 0 em X) AUMENTA o width
|
|
79
|
+
const delta = startX - ev.clientX;
|
|
80
|
+
const next = Math.max(min, Math.min(max, startWidth + delta));
|
|
81
|
+
setWidth(next);
|
|
82
|
+
};
|
|
83
|
+
const onUp = () => {
|
|
84
|
+
document.removeEventListener("mousemove", onMove);
|
|
85
|
+
document.removeEventListener("mouseup", onUp);
|
|
86
|
+
document.body.style.cursor = "";
|
|
87
|
+
document.body.style.userSelect = "";
|
|
88
|
+
};
|
|
89
|
+
document.addEventListener("mousemove", onMove);
|
|
90
|
+
document.addEventListener("mouseup", onUp);
|
|
91
|
+
document.body.style.cursor = "col-resize";
|
|
92
|
+
document.body.style.userSelect = "none";
|
|
93
|
+
},
|
|
94
|
+
[width, min, max],
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
return { width, onResizeStart };
|
|
98
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ChatScreen, default } from "./chat-screen";
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dados base compartilhados. Espelha `src/preview/pages/_table-data.ts` do DS — que
|
|
3
|
+
* até v0.30.0 morava dentro do `TableDoc.tsx` e foi extraído pra lá em v0.30.1 pelo
|
|
4
|
+
* mesmo motivo que existe aqui: fixture compartilhada não pode viver numa Doc page.
|
|
5
|
+
* Mantém o exemplo autossuficiente — sem dependência da infra de preview.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const STATUSES: Record<string, { label: string; color: string }> = {
|
|
9
|
+
active: { label: "Ativo", color: "var(--color-fg-success)" },
|
|
10
|
+
pending: { label: "Pendente", color: "var(--color-fg-warning)" },
|
|
11
|
+
paused: { label: "Pausado", color: "var(--color-fg-info)" },
|
|
12
|
+
inactive: { label: "Inativo", color: "var(--color-fg-muted)" },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type CategoryKind = "warning" | "info" | "success" | "neutral";
|
|
16
|
+
export const CATEGORIES: Record<string, { label: string; kind: CategoryKind }> = {
|
|
17
|
+
royal: { label: "Royal", kind: "warning" },
|
|
18
|
+
licenciado: { label: "Licenciado", kind: "info" },
|
|
19
|
+
lead: { label: "Lead", kind: "success" },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const AGENTS: Record<string, { name: string; initials: string; color: string }> = {
|
|
23
|
+
you: { name: "Voce", initials: "VC", color: "#0a3a2e" },
|
|
24
|
+
aline: { name: "Aline Castro", initials: "AC", color: "#f59e0b" },
|
|
25
|
+
carlos: { name: "Carlos Souza", initials: "CS", color: "#8754ec" },
|
|
26
|
+
maria: { name: "Maria Lima", initials: "ML", color: "#ef4444" },
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type ClientRow = {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
initials: string;
|
|
33
|
+
avatarColor: string;
|
|
34
|
+
email: string;
|
|
35
|
+
phone: string;
|
|
36
|
+
statusId: keyof typeof STATUSES;
|
|
37
|
+
categoryId: keyof typeof CATEGORIES;
|
|
38
|
+
location: string;
|
|
39
|
+
value: number;
|
|
40
|
+
agentId: keyof typeof AGENTS;
|
|
41
|
+
createdAt: number;
|
|
42
|
+
lastContact: number;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const BASE_DATE = new Date("2026-04-15T12:00:00Z").getTime();
|
|
46
|
+
const DAY_MS = 86400000;
|
|
47
|
+
|
|
48
|
+
export const CLIENTS_MOCK: ClientRow[] = [
|
|
49
|
+
{ id: "CLI-2401", name: "Maria Silva", initials: "MS", avatarColor: "#f59e0b", email: "maria.silva@example.com", phone: "+55 11 91234-5678", statusId: "active", categoryId: "royal", location: "São Paulo, SP", value: 4800, agentId: "you", createdAt: BASE_DATE - 65 * DAY_MS, lastContact: BASE_DATE - 2 * DAY_MS },
|
|
50
|
+
{ id: "CLI-2402", name: "João Santos", initials: "JS", avatarColor: "#0a3a2e", email: "joao.santos@example.com", phone: "+55 11 92345-6789", statusId: "pending", categoryId: "licenciado", location: "Rio de Janeiro, RJ", value: 12300, agentId: "aline", createdAt: BASE_DATE - 58 * DAY_MS, lastContact: BASE_DATE - 5 * DAY_MS },
|
|
51
|
+
{ id: "CLI-2403", name: "Carlos Oliveira", initials: "CO", avatarColor: "#8754ec", email: "carlos.oliveira@example.com", phone: "+55 11 93456-7890", statusId: "active", categoryId: "lead", location: "Belo Horizonte, MG", value: 2150, agentId: "carlos", createdAt: BASE_DATE - 51 * DAY_MS, lastContact: BASE_DATE - 1 * DAY_MS },
|
|
52
|
+
{ id: "CLI-2404", name: "Ana Costa", initials: "AC", avatarColor: "#1cb280", email: "ana.costa@example.com", phone: "+55 11 94567-8901", statusId: "paused", categoryId: "royal", location: "Porto Alegre, RS", value: 8900, agentId: "maria", createdAt: BASE_DATE - 44 * DAY_MS, lastContact: BASE_DATE - 12 * DAY_MS },
|
|
53
|
+
{ id: "CLI-2405", name: "Pedro Pereira", initials: "PP", avatarColor: "#ef4444", email: "pedro.pereira@example.com", phone: "+55 11 95678-9012", statusId: "inactive", categoryId: "lead", location: "Curitiba, PR", value: 1100, agentId: "you", createdAt: BASE_DATE - 37 * DAY_MS, lastContact: BASE_DATE - 30 * DAY_MS },
|
|
54
|
+
{ id: "CLI-2406", name: "Lúcia Almeida", initials: "LA", avatarColor: "#f9a47a", email: "lucia.almeida@example.com", phone: "+55 11 96789-0123", statusId: "active", categoryId: "licenciado", location: "Recife, PE", value: 6750, agentId: "aline", createdAt: BASE_DATE - 30 * DAY_MS, lastContact: BASE_DATE - 3 * DAY_MS },
|
|
55
|
+
{ id: "CLI-2407", name: "Roberto Souza", initials: "RS", avatarColor: "#0088cc", email: "roberto.souza@example.com", phone: "+55 11 97890-1234", statusId: "pending", categoryId: "royal", location: "São Paulo, SP", value: 15200, agentId: "carlos", createdAt: BASE_DATE - 23 * DAY_MS, lastContact: BASE_DATE - 7 * DAY_MS },
|
|
56
|
+
{ id: "CLI-2408", name: "Fernanda Lima", initials: "FL", avatarColor: "#e1306c", email: "fernanda.lima@example.com", phone: "+55 11 98901-2345", statusId: "active", categoryId: "lead", location: "Rio de Janeiro, RJ", value: 3400, agentId: "maria", createdAt: BASE_DATE - 16 * DAY_MS, lastContact: BASE_DATE - 1 * DAY_MS },
|
|
57
|
+
{ id: "CLI-2409", name: "Bruno Rodrigues", initials: "BR", avatarColor: "#70c748", email: "bruno.rodrigues@example.com", phone: "+55 11 99012-3456", statusId: "paused", categoryId: "licenciado", location: "Belo Horizonte, MG", value: 5600, agentId: "you", createdAt: BASE_DATE - 9 * DAY_MS, lastContact: BASE_DATE - 6 * DAY_MS },
|
|
58
|
+
{ id: "CLI-2410", name: "Camila Ribeiro", initials: "CR", avatarColor: "#8754ec", email: "camila.ribeiro@example.com", phone: "+55 11 90123-4567", statusId: "active", categoryId: "royal", location: "Porto Alegre, RS", value: 9800, agentId: "aline", createdAt: BASE_DATE - 2 * DAY_MS, lastContact: BASE_DATE },
|
|
59
|
+
];
|