xertica-ui 2.2.0 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/README.md +1 -1
  3. package/dist/ThemeContext-BgclCB35.js +1856 -0
  4. package/dist/ThemeContext-DQUOeziy.cjs +1855 -0
  5. package/dist/VerifyEmailPage-RrUApqBN.js +3214 -0
  6. package/dist/VerifyEmailPage-VoMI7MYH.cjs +3213 -0
  7. package/dist/XerticaProvider-BSyFrmC0.js +49 -0
  8. package/dist/XerticaProvider-CiNKjMx1.cjs +48 -0
  9. package/dist/XerticaXLogo-B2svDGZh.cjs +251 -0
  10. package/dist/XerticaXLogo-CowGv7BC.js +252 -0
  11. package/dist/brand.cjs.js +2 -2
  12. package/dist/brand.es.js +2 -2
  13. package/dist/hooks.cjs.js +1 -1
  14. package/dist/hooks.es.js +1 -1
  15. package/dist/index.cjs.js +5 -5
  16. package/dist/index.es.js +5 -5
  17. package/dist/layout.cjs.js +1 -1
  18. package/dist/layout.es.js +1 -1
  19. package/dist/pages.cjs.js +1 -1
  20. package/dist/pages.es.js +1 -1
  21. package/dist/sidebar-CRMiBtAi.js +801 -0
  22. package/dist/sidebar-CZ2mWaMM.cjs +800 -0
  23. package/dist/xertica-ui.css +1 -1
  24. package/package.json +1 -1
  25. package/templates/CLAUDE.md +165 -180
  26. package/templates/package.json +2 -2
  27. package/templates/src/features/auth/ui/ForgotPasswordContent.tsx +9 -7
  28. package/templates/src/features/auth/ui/LoginContent.tsx +10 -8
  29. package/templates/src/features/auth/ui/ResetPasswordContent.tsx +179 -177
  30. package/templates/src/features/auth/ui/SocialLoginButtons.tsx +9 -4
  31. package/templates/src/features/auth/ui/VerifyEmailContent.tsx +84 -82
  32. package/templates/src/features/template/ui/TemplateContent.tsx +1 -1
  33. package/templates/src/locales/en/components/assistant.json +14 -0
  34. package/templates/src/locales/en/pages/forgotPassword.json +10 -0
  35. package/templates/src/locales/en/pages/templates.json +1 -1
  36. package/templates/src/locales/es/components/assistant.json +14 -0
  37. package/templates/src/locales/es/pages/forgotPassword.json +10 -0
  38. package/templates/src/locales/es/pages/templates.json +1 -1
  39. package/templates/src/locales/pt-BR/components/assistant.json +14 -0
  40. package/templates/src/locales/pt-BR/pages/forgotPassword.json +10 -0
  41. package/templates/src/locales/pt-BR/pages/templates.json +1 -1
  42. package/templates/src/pages/AssistantPage.tsx +464 -463
@@ -1,463 +1,464 @@
1
- import { useState, useMemo, useEffect } from 'react';
2
- import { useNavigate, Link } from 'react-router-dom';
3
- import { XerticaAssistant, generateDemoResponse } from 'xertica-ui/assistant';
4
- import { useLayout } from 'xertica-ui/hooks';
5
- import { Header } from 'xertica-ui/layout';
6
- import {
7
- Button,
8
- AlertDialog,
9
- AlertDialogAction,
10
- AlertDialogCancel,
11
- AlertDialogContent,
12
- AlertDialogDescription,
13
- AlertDialogFooter,
14
- AlertDialogHeader,
15
- AlertDialogTitle,
16
- Dialog,
17
- DialogContent,
18
- DialogFooter,
19
- DialogHeader,
20
- DialogTitle,
21
- Input,
22
- Label,
23
- } from 'xertica-ui/ui';
24
- import { AppLayout } from '../app/components/AppLayout';
25
- import { ArrowLeft, Plus, MessageSquare, Trash2, FileEdit } from 'lucide-react';
26
-
27
- // ─── Types ───────────────────────────────────────────────────────────────────
28
-
29
- interface ChatMessage {
30
- id: string;
31
- type: 'user' | 'assistant';
32
- content: string;
33
- timestamp: Date;
34
- }
35
-
36
- interface Conversation {
37
- id: string;
38
- label: string;
39
- group: 'hoje' | 'ontem' | 'semana';
40
- messages: ChatMessage[];
41
- }
42
-
43
- // ─── Data ────────────────────────────────────────────────────────────────────
44
-
45
- const richSuggestions = [
46
- { id: 'chart-1', text: 'Ver análise de performance' },
47
- { id: 'table-1', text: 'Gerar relatório de projetos' },
48
- { id: 'doc-1', text: 'Criar documento de requisitos' },
49
- { id: 'pod-1', text: 'Gerar podcast do resumo diário' },
50
- ];
51
-
52
- const feedbackOptions = [
53
- 'Não era o que eu procurava',
54
- 'Informação incorreta',
55
- 'Resposta incompleta',
56
- ];
57
-
58
- const d = (offsetMinutes: number) => {
59
- const t = new Date();
60
- t.setMinutes(t.getMinutes() - offsetMinutes);
61
- return t;
62
- };
63
-
64
- const INITIAL_CONVERSATIONS: Conversation[] = [
65
- {
66
- id: 'conversa-1',
67
- label: 'Análise de dados de vendas Q1',
68
- group: 'hoje',
69
- messages: [
70
- {
71
- id: 'm1',
72
- type: 'user',
73
- content:
74
- 'Preciso de uma análise dos dados de vendas do primeiro trimestre. Tivemos crescimento em algumas regiões mas queda em outras.',
75
- timestamp: d(60),
76
- },
77
- {
78
- id: 'm2',
79
- type: 'assistant',
80
- content:
81
- 'Com base nos dados do Q1, identifiquei os seguintes padrões:\n\n**Regiões com crescimento:**\n- Sudeste: +18% comparado ao Q1 do ano anterior\n- Sul: +12%\n\n**Regiões com queda:**\n- Norte: -8%\n- Nordeste: -4%\n\nO crescimento no Sudeste foi impulsionado principalmente pelo aumento no ticket médio (+22%), enquanto a queda no Norte está correlacionada com a redução de representantes comerciais ativos na região. Recomendo aumentar a cobertura comercial no Norte e Nordeste para o Q2.',
82
- timestamp: d(58),
83
- },
84
- {
85
- id: 'm3',
86
- type: 'user',
87
- content: 'Que ações específicas você recomenda para o Norte?',
88
- timestamp: d(55),
89
- },
90
- {
91
- id: 'm4',
92
- type: 'assistant',
93
- content:
94
- 'Para reverter a queda no Norte, recomendo:\n\n1. **Contratar 2 representantes regionais** focados nos estados do Pará e Amazonas\n2. **Programa de incentivo para distribuidores locais** com bonificação por volume trimestral\n3. **Campanhas segmentadas** com preços ajustados ao poder aquisitivo regional\n4. **Visitas presenciais quinzenais** nos 5 maiores clientes da região\n\nEssas ações têm potencial de recuperar 6-8% das vendas já no Q2.',
95
- timestamp: d(53),
96
- },
97
- ],
98
- },
99
- {
100
- id: 'conversa-2',
101
- label: 'Relatório de performance mensal',
102
- group: 'hoje',
103
- messages: [
104
- {
105
- id: 'm1',
106
- type: 'user',
107
- content: 'Gera um relatório de performance do time de engenharia para o mês de abril.',
108
- timestamp: d(120),
109
- },
110
- {
111
- id: 'm2',
112
- type: 'assistant',
113
- content:
114
- '# Relatório de Performance — Abril\n\n**Time de Engenharia**\n\n## Entregas\n- 42 tarefas concluídas (meta: 38) ✅\n- 3 sprints fechados dentro do prazo\n- Velocity médio: 87 pontos/sprint\n\n## Qualidade\n- Taxa de bugs em produção: 0,8% (melhora de 0,3pp vs março)\n- Cobertura de testes: 78%\n- Tempo médio de resolução de incidentes: 2h 15min\n\n## Destaques\n- Deploy do módulo de relatórios avançados (2 semanas antes do prazo)\n- Redução de 40% no tempo de build do pipeline\n\n## Pontos de atenção\n- Dívida técnica no módulo de autenticação precisa de endereçamento no Q2',
115
- timestamp: d(118),
116
- },
117
- ],
118
- },
119
- {
120
- id: 'conversa-3',
121
- label: 'Resumo das reuniões da semana',
122
- group: 'ontem',
123
- messages: [
124
- {
125
- id: 'm1',
126
- type: 'user',
127
- content: 'Resume as principais decisões tomadas nas reuniões desta semana.',
128
- timestamp: d(1440 + 60),
129
- },
130
- {
131
- id: 'm2',
132
- type: 'assistant',
133
- content:
134
- '## Decisões da Semana\n\n**Segunda — Planejamento Q2**\n- Meta de receita: R$ 4,2M (+15% vs Q1)\n- Novo produto B2B entra em beta em junho\n- Contratação de 3 devs aprovada para maio\n\n**Quarta — Review de Produto**\n- Funcionalidade de relatórios vai para produção na sexta\n- UX do onboarding será redesenhado (kickoff em 15/05)\n- Integração com ERP do cliente Alfa postergada para julho\n\n**Sexta — All Hands**\n- Empresa atingiu 500 clientes ativos 🎉\n- Novo programa de equity para colaboradores aprovado pelo board\n- Hackathon interno marcado para 20-21 de junho',
135
- timestamp: d(1440 + 58),
136
- },
137
- {
138
- id: 'm3',
139
- type: 'user',
140
- content: 'Quando é o kickoff do redesign de UX exatamente?',
141
- timestamp: d(1440 + 50),
142
- },
143
- {
144
- id: 'm4',
145
- type: 'assistant',
146
- content:
147
- 'O kickoff do redesign do onboarding está marcado para **15 de maio**, conforme decidido na reunião de Review de Produto de quarta-feira. O time de produto e design estará presente, e foi solicitado que o lead de engenharia frontend também participe.',
148
- timestamp: d(1440 + 48),
149
- },
150
- ],
151
- },
152
- {
153
- id: 'conversa-4',
154
- label: 'Dashboard de projetos ativos',
155
- group: 'ontem',
156
- messages: [
157
- {
158
- id: 'm1',
159
- type: 'user',
160
- content: 'Quais projetos estão ativos agora e qual o status de cada um?',
161
- timestamp: d(1440 + 180),
162
- },
163
- {
164
- id: 'm2',
165
- type: 'assistant',
166
- content:
167
- '## Projetos Ativos\n\n| Projeto | Status | Prazo | Responsável |\n|---|---|---|---|\n| Portal do Cliente | 🟢 No prazo | 30/05 | Ana Costa |\n| Integração ERP | 🟡 Em risco | 15/05 | Bruno Lima |\n| App Mobile v2 | 🟢 No prazo | 30/06 | Carla Melo |\n| Redesign Onboarding | 🔵 Iniciando | 30/07 | Diana Park |\n| API Pública v3 | 🟡 Em risco | 01/06 | Eduardo Cruz |\n\n**Projetos em risco:** A Integração ERP está aguardando credenciais do cliente Alfa há 10 dias. A API v3 tem bloqueio técnico na autenticação OAuth que precisa de decisão arquitetural.',
168
- timestamp: d(1440 + 178),
169
- },
170
- ],
171
- },
172
- {
173
- id: 'conversa-5',
174
- label: 'Análise de churn de clientes',
175
- group: 'ontem',
176
- messages: [
177
- {
178
- id: 'm1',
179
- type: 'user',
180
- content: 'Analisa o churn do mês passado. Tivemos 12 cancelamentos.',
181
- timestamp: d(1440 + 300),
182
- },
183
- {
184
- id: 'm2',
185
- type: 'assistant',
186
- content:
187
- '## Análise de Churn — Abril\n\n**12 cancelamentos** representam uma taxa de churn de **2,4%** (base de 500 clientes).\n\n### Principais motivos\n1. Preço / custo-benefício — 5 clientes (42%)\n2. Migração para concorrente — 4 clientes (33%)\n3. Encerramento do negócio — 2 clientes (17%)\n4. Insatisfação com suporte — 1 cliente (8%)\n\n### Perfil dos churned\n- Plano Basic: 8 (67%)\n- Plano Pro: 3 (25%)\n- Plano Enterprise: 1 (8%)\n\n**Insight:** A concentração no plano Basic sugere que o preço de renovação anual está sendo percebido como alto para clientes nesse tier. Recomendo revisar a proposta de valor ou criar um plano intermediário.',
188
- timestamp: d(1440 + 298),
189
- },
190
- ],
191
- },
192
- {
193
- id: 'conversa-6',
194
- label: 'Proposta comercial Projeto Alpha',
195
- group: 'semana',
196
- messages: [
197
- {
198
- id: 'm1',
199
- type: 'user',
200
- content:
201
- 'Precisa de ajuda para montar a proposta comercial para o Projeto Alpha. É um cliente enterprise do setor de logística.',
202
- timestamp: d(3 * 1440),
203
- },
204
- {
205
- id: 'm2',
206
- type: 'assistant',
207
- content:
208
- 'Para um cliente enterprise de logística, estruturo a proposta nos seguintes blocos:\n\n**1. Contexto e Desafios**\nIdentificar os 3 principais pain points do cliente (rastreamento, gestão de frotas, custo operacional).\n\n**2. Solução Proposta**\nDetalhar os módulos relevantes para logística: dashboard de rotas, alertas em tempo real, relatórios de KPI operacional.\n\n**3. Modelo Comercial**\n- Licença Enterprise: R$ 8.500/mês\n- Implementação e customização: R$ 45.000 (único)\n- SLA garantido: 99,9% uptime com suporte 24/7\n\n**4. ROI Estimado**\nRedução de 15-20% em custo operacional no primeiro ano.\n\n**5. Próximos Passos**\nPiloto de 30 dias sem custo para validar integração com sistema legado.\n\nDeseja que eu elabore alguma seção específica?',
209
- timestamp: d(3 * 1440 - 2),
210
- },
211
- ],
212
- },
213
- {
214
- id: 'conversa-7',
215
- label: 'KPIs do time de engenharia',
216
- group: 'semana',
217
- messages: [
218
- {
219
- id: 'm1',
220
- type: 'user',
221
- content: 'Quais KPIs devo acompanhar para o time de engenharia?',
222
- timestamp: d(4 * 1440),
223
- },
224
- {
225
- id: 'm2',
226
- type: 'assistant',
227
- content:
228
- '## KPIs Essenciais — Time de Engenharia\n\n**Velocidade e Entrega**\n- Velocity (pontos/sprint)\n- Lead time (do commit ao deploy)\n- Frequência de deploy\n- Taxa de conclusão de sprint\n\n**Qualidade**\n- Taxa de bugs em produção\n- Cobertura de testes (%)\n- MTTR — Mean Time to Recovery\n- Change failure rate\n\n**Saúde do Time**\n- Dívida técnica (story points estimados)\n- Satisfação do time (eNPS)\n- Tempo médio de code review\n\n**Negócio**\n- Tempo de entrega de features (time-to-market)\n- Uptime do sistema\n- Custo por deploy\n\nRecomendo começar com 5-7 KPIs no máximo para não sobrecarregar o processo de coleta.',
229
- timestamp: d(4 * 1440 - 2),
230
- },
231
- ],
232
- },
233
- {
234
- id: 'conversa-8',
235
- label: 'Planejamento de sprint Q2',
236
- group: 'semana',
237
- messages: [
238
- {
239
- id: 'm1',
240
- type: 'user',
241
- content: 'Me ajuda a planejar o Q2. Temos 3 sprints de 2 semanas cada.',
242
- timestamp: d(5 * 1440),
243
- },
244
- {
245
- id: 'm2',
246
- type: 'assistant',
247
- content:
248
- '## Planejamento Q2 3 Sprints\n\n**Sprint 1 (01–14/mai)**\n- Finalizar módulo de relatórios avançados\n- Correções críticas de UX do onboarding\n- Setup da infraestrutura para o App Mobile v2\n\n**Sprint 2 (15–28/mai)**\n- Kickoff do redesign de onboarding\n- Início da API Pública v3\n- Integração com ERP (depende de credenciais do cliente)\n\n**Sprint 3 (29/mai–11/jun)**\n- Beta fechado do App Mobile v2\n- Estabilização da API v3\n- Testes de carga e segurança\n\n**Capacidade estimada:** 85 pontos/sprint × 3 = 255 pontos no Q2\n\nSugestão: reserve 15% da capacidade de cada sprint para bugs e dívida técnica.',
249
- timestamp: d(5 * 1440 - 2),
250
- },
251
- ],
252
- },
253
- ];
254
-
255
- const GROUP_CONFIG = [
256
- { id: 'hoje', label: 'Hoje' },
257
- { id: 'ontem', label: 'Ontem' },
258
- { id: 'semana', label: 'Esta semana' },
259
- ];
260
-
261
- // ─── Component ───────────────────────────────────────────────────────────────
262
-
263
- export function AssistantPage() {
264
- const navigate = useNavigate();
265
- const { sidebarExpanded, setSidebarExpanded } = useLayout();
266
-
267
- const [conversations, setConversations] = useState<Conversation[]>(INITIAL_CONVERSATIONS);
268
- const [selectedId, setSelectedId] = useState<string | null>(null);
269
- const [searchValue, setSearchValue] = useState('');
270
- const [deleteTarget, setDeleteTarget] = useState<Conversation | null>(null);
271
- const [renameTarget, setRenameTarget] = useState<Conversation | null>(null);
272
- const [renameValue, setRenameValue] = useState('');
273
-
274
- useEffect(() => {
275
- if (!sidebarExpanded) setSidebarExpanded(true);
276
- }, [sidebarExpanded, setSidebarExpanded]);
277
-
278
- const selectedConversation = conversations.find(c => c.id === selectedId) ?? null;
279
-
280
- const navigationGroups = useMemo(() => {
281
- const query = searchValue.toLowerCase();
282
- return GROUP_CONFIG.map(group => ({
283
- id: group.id,
284
- label: group.label,
285
- icon: MessageSquare,
286
- items: conversations
287
- .filter(c => c.group === group.id)
288
- .filter(c => !query || c.label.toLowerCase().includes(query))
289
- .map(c => ({
290
- path: `/assistente/${c.id}`,
291
- label: c.label,
292
- actions: [
293
- {
294
- label: 'Renomear',
295
- icon: FileEdit,
296
- onClick: () => {
297
- setRenameTarget(c);
298
- setRenameValue(c.label);
299
- },
300
- },
301
- {
302
- label: 'Excluir',
303
- icon: Trash2,
304
- onClick: () => setDeleteTarget(c),
305
- variant: 'destructive' as const,
306
- },
307
- ],
308
- })),
309
- })).filter(g => g.items.length > 0);
310
- }, [conversations, searchValue]);
311
-
312
- const handleSelectConversation = (path: string) => {
313
- if (path.startsWith('/assistente/')) {
314
- setSelectedId(path.replace('/assistente/', ''));
315
- } else {
316
- navigate(path);
317
- }
318
- };
319
-
320
- const handleDelete = () => {
321
- if (!deleteTarget) return;
322
- setConversations(prev => prev.filter(c => c.id !== deleteTarget.id));
323
- if (selectedId === deleteTarget.id) setSelectedId(null);
324
- setDeleteTarget(null);
325
- };
326
-
327
- const handleRename = () => {
328
- if (!renameTarget || !renameValue.trim()) return;
329
- setConversations(prev =>
330
- prev.map(c => (c.id === renameTarget.id ? { ...c, label: renameValue.trim() } : c))
331
- );
332
- setRenameTarget(null);
333
- };
334
-
335
- const sidebarProps = {
336
- width: 320,
337
- fixedArea: {
338
- show: true,
339
- icon: Plus,
340
- onClick: () => setSelectedId(null),
341
- content: (
342
- <Button
343
- variant="secondary"
344
- className="w-full font-semibold"
345
- onClick={() => setSelectedId(null)}
346
- >
347
- <Plus className="w-4 h-4 mr-2" />
348
- Nova Conversa
349
- </Button>
350
- ),
351
- },
352
- search: {
353
- show: true,
354
- placeholder: 'Buscar conversas...',
355
- value: searchValue,
356
- onChange: setSearchValue,
357
- },
358
- navigationGroups,
359
- location: { pathname: selectedId ? `/assistente/${selectedId}` : '/assistente' },
360
- navigate: handleSelectConversation,
361
- };
362
-
363
- return (
364
- <>
365
- <AppLayout
366
- sidebarVariant="assistant"
367
- sidebarProps={sidebarProps}
368
- >
369
- <div
370
- style={{ paddingLeft: sidebarExpanded ? '320px' : '80px' }}
371
- className="flex-1 flex flex-col overflow-hidden transition-all duration-300"
372
- >
373
- <Header
374
- breadcrumbs={[
375
- { label: 'Design System', href: '/home' },
376
- { label: selectedConversation?.label ?? 'Nova Conversa' },
377
- ]}
378
- renderLink={(href: string, props: any) => <Link to={href} {...props} />}
379
- actions={[
380
- {
381
- id: 'back',
382
- label: 'Voltar',
383
- icon: <ArrowLeft className="w-4 h-4" />,
384
- onClick: () => navigate('/home'),
385
- className:
386
- 'bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground',
387
- },
388
- ]}
389
- />
390
- <main className="flex-1 overflow-hidden bg-muted">
391
- <XerticaAssistant
392
- key={selectedId ?? 'new'}
393
- mode="fullPage"
394
- demoMode={true}
395
- userName="Ariel Santos"
396
- responseGenerator={generateDemoResponse}
397
- initialMessages={selectedConversation?.messages as any}
398
- richSuggestions={richSuggestions}
399
- feedbackOptions={feedbackOptions}
400
- showHistory={false}
401
- showFavorites={false}
402
- onNavigateSettings={() => navigate('/settings')}
403
- onEvaluation={(messageId, type, reason) => {
404
- console.log(
405
- `Avaliação recebida: ${type} na mensagem ${messageId}. Motivo: ${reason}`
406
- );
407
- }}
408
- />
409
- </main>
410
- </div>
411
- </AppLayout>
412
-
413
- {/* Modal: Excluir conversa */}
414
- <AlertDialog open={!!deleteTarget} onOpenChange={open => !open && setDeleteTarget(null)}>
415
- <AlertDialogContent>
416
- <AlertDialogHeader>
417
- <AlertDialogTitle>Excluir conversa</AlertDialogTitle>
418
- <AlertDialogDescription>
419
- Tem certeza que deseja excluir <strong>"{deleteTarget?.label}"</strong>? Esta ação não
420
- pode ser desfeita.
421
- </AlertDialogDescription>
422
- </AlertDialogHeader>
423
- <AlertDialogFooter>
424
- <AlertDialogCancel>Cancelar</AlertDialogCancel>
425
- <AlertDialogAction
426
- onClick={handleDelete}
427
- className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
428
- >
429
- Excluir
430
- </AlertDialogAction>
431
- </AlertDialogFooter>
432
- </AlertDialogContent>
433
- </AlertDialog>
434
-
435
- {/* Modal: Renomear conversa */}
436
- <Dialog open={!!renameTarget} onOpenChange={open => !open && setRenameTarget(null)}>
437
- <DialogContent>
438
- <DialogHeader>
439
- <DialogTitle>Renomear conversa</DialogTitle>
440
- </DialogHeader>
441
- <div className="space-y-2 py-2">
442
- <Label htmlFor="rename-input">Nome da conversa</Label>
443
- <Input
444
- id="rename-input"
445
- value={renameValue}
446
- onChange={e => setRenameValue(e.target.value)}
447
- onKeyDown={e => e.key === 'Enter' && handleRename()}
448
- autoFocus
449
- />
450
- </div>
451
- <DialogFooter>
452
- <Button variant="outline" onClick={() => setRenameTarget(null)}>
453
- Cancelar
454
- </Button>
455
- <Button onClick={handleRename} disabled={!renameValue.trim()}>
456
- Salvar
457
- </Button>
458
- </DialogFooter>
459
- </DialogContent>
460
- </Dialog>
461
- </>
462
- );
463
- }
1
+ import { useState, useMemo, useEffect } from 'react';
2
+ import { useNavigate, Link } from 'react-router-dom';
3
+ import { XerticaAssistant, generateDemoResponse } from 'xertica-ui/assistant';
4
+ import { useLayout } from 'xertica-ui/hooks';
5
+ import { Header } from 'xertica-ui/layout';
6
+ import { useTranslation } from 'react-i18next';
7
+ import {
8
+ Button,
9
+ AlertDialog,
10
+ AlertDialogAction,
11
+ AlertDialogCancel,
12
+ AlertDialogContent,
13
+ AlertDialogDescription,
14
+ AlertDialogFooter,
15
+ AlertDialogHeader,
16
+ AlertDialogTitle,
17
+ Dialog,
18
+ DialogContent,
19
+ DialogFooter,
20
+ DialogHeader,
21
+ DialogTitle,
22
+ Input,
23
+ Label,
24
+ } from 'xertica-ui/ui';
25
+ import { AppLayout } from '../app/components/AppLayout';
26
+ import { ArrowLeft, Plus, MessageSquare, Trash2, FileEdit } from 'lucide-react';
27
+
28
+ // ─── Types ───────────────────────────────────────────────────────────────────
29
+
30
+ interface ChatMessage {
31
+ id: string;
32
+ type: 'user' | 'assistant';
33
+ content: string;
34
+ timestamp: Date;
35
+ }
36
+
37
+ interface Conversation {
38
+ id: string;
39
+ label: string;
40
+ group: 'hoje' | 'ontem' | 'semana';
41
+ messages: ChatMessage[];
42
+ }
43
+
44
+ // ─── Data ────────────────────────────────────────────────────────────────────
45
+
46
+ const d = (offsetMinutes: number) => {
47
+ const t = new Date();
48
+ t.setMinutes(t.getMinutes() - offsetMinutes);
49
+ return t;
50
+ };
51
+
52
+ const INITIAL_CONVERSATIONS: Conversation[] = [
53
+ {
54
+ id: 'conversa-1',
55
+ label: 'Análise de dados de vendas Q1',
56
+ group: 'hoje',
57
+ messages: [
58
+ {
59
+ id: 'm1',
60
+ type: 'user',
61
+ content:
62
+ 'Preciso de uma análise dos dados de vendas do primeiro trimestre. Tivemos crescimento em algumas regiões mas queda em outras.',
63
+ timestamp: d(60),
64
+ },
65
+ {
66
+ id: 'm2',
67
+ type: 'assistant',
68
+ content:
69
+ 'Com base nos dados do Q1, identifiquei os seguintes padrões:\n\n**Regiões com crescimento:**\n- Sudeste: +18% comparado ao Q1 do ano anterior\n- Sul: +12%\n\n**Regiões com queda:**\n- Norte: -8%\n- Nordeste: -4%\n\nO crescimento no Sudeste foi impulsionado principalmente pelo aumento no ticket médio (+22%), enquanto a queda no Norte está correlacionada com a redução de representantes comerciais ativos na região. Recomendo aumentar a cobertura comercial no Norte e Nordeste para o Q2.',
70
+ timestamp: d(58),
71
+ },
72
+ {
73
+ id: 'm3',
74
+ type: 'user',
75
+ content: 'Que ações específicas você recomenda para o Norte?',
76
+ timestamp: d(55),
77
+ },
78
+ {
79
+ id: 'm4',
80
+ type: 'assistant',
81
+ content:
82
+ 'Para reverter a queda no Norte, recomendo:\n\n1. **Contratar 2 representantes regionais** focados nos estados do Pará e Amazonas\n2. **Programa de incentivo para distribuidores locais** com bonificação por volume trimestral\n3. **Campanhas segmentadas** com preços ajustados ao poder aquisitivo regional\n4. **Visitas presenciais quinzenais** nos 5 maiores clientes da região\n\nEssas ações têm potencial de recuperar 6-8% das vendas já no Q2.',
83
+ timestamp: d(53),
84
+ },
85
+ ],
86
+ },
87
+ {
88
+ id: 'conversa-2',
89
+ label: 'Relatório de performance mensal',
90
+ group: 'hoje',
91
+ messages: [
92
+ {
93
+ id: 'm1',
94
+ type: 'user',
95
+ content: 'Gera um relatório de performance do time de engenharia para o mês de abril.',
96
+ timestamp: d(120),
97
+ },
98
+ {
99
+ id: 'm2',
100
+ type: 'assistant',
101
+ content:
102
+ '# Relatório de Performance — Abril\n\n**Time de Engenharia**\n\n## Entregas\n- 42 tarefas concluídas (meta: 38) ✅\n- 3 sprints fechados dentro do prazo\n- Velocity médio: 87 pontos/sprint\n\n## Qualidade\n- Taxa de bugs em produção: 0,8% (melhora de 0,3pp vs março)\n- Cobertura de testes: 78%\n- Tempo médio de resolução de incidentes: 2h 15min\n\n## Destaques\n- Deploy do módulo de relatórios avançados (2 semanas antes do prazo)\n- Redução de 40% no tempo de build do pipeline\n\n## Pontos de atenção\n- Dívida técnica no módulo de autenticação precisa de endereçamento no Q2',
103
+ timestamp: d(118),
104
+ },
105
+ ],
106
+ },
107
+ {
108
+ id: 'conversa-3',
109
+ label: 'Resumo das reuniões da semana',
110
+ group: 'ontem',
111
+ messages: [
112
+ {
113
+ id: 'm1',
114
+ type: 'user',
115
+ content: 'Resume as principais decisões tomadas nas reuniões desta semana.',
116
+ timestamp: d(1440 + 60),
117
+ },
118
+ {
119
+ id: 'm2',
120
+ type: 'assistant',
121
+ content:
122
+ '## Decisões da Semana\n\n**Segunda — Planejamento Q2**\n- Meta de receita: R$ 4,2M (+15% vs Q1)\n- Novo produto B2B entra em beta em junho\n- Contratação de 3 devs aprovada para maio\n\n**Quarta — Review de Produto**\n- Funcionalidade de relatórios vai para produção na sexta\n- UX do onboarding será redesenhado (kickoff em 15/05)\n- Integração com ERP do cliente Alfa postergada para julho\n\n**Sexta — All Hands**\n- Empresa atingiu 500 clientes ativos 🎉\n- Novo programa de equity para colaboradores aprovado pelo board\n- Hackathon interno marcado para 20-21 de junho',
123
+ timestamp: d(1440 + 58),
124
+ },
125
+ {
126
+ id: 'm3',
127
+ type: 'user',
128
+ content: 'Quando é o kickoff do redesign de UX exatamente?',
129
+ timestamp: d(1440 + 50),
130
+ },
131
+ {
132
+ id: 'm4',
133
+ type: 'assistant',
134
+ content:
135
+ 'O kickoff do redesign do onboarding está marcado para **15 de maio**, conforme decidido na reunião de Review de Produto de quarta-feira. O time de produto e design estará presente, e foi solicitado que o lead de engenharia frontend também participe.',
136
+ timestamp: d(1440 + 48),
137
+ },
138
+ ],
139
+ },
140
+ {
141
+ id: 'conversa-4',
142
+ label: 'Dashboard de projetos ativos',
143
+ group: 'ontem',
144
+ messages: [
145
+ {
146
+ id: 'm1',
147
+ type: 'user',
148
+ content: 'Quais projetos estão ativos agora e qual o status de cada um?',
149
+ timestamp: d(1440 + 180),
150
+ },
151
+ {
152
+ id: 'm2',
153
+ type: 'assistant',
154
+ content:
155
+ '## Projetos Ativos\n\n| Projeto | Status | Prazo | Responsável |\n|---|---|---|---|\n| Portal do Cliente | 🟢 No prazo | 30/05 | Ana Costa |\n| Integração ERP | 🟡 Em risco | 15/05 | Bruno Lima |\n| App Mobile v2 | 🟢 No prazo | 30/06 | Carla Melo |\n| Redesign Onboarding | 🔵 Iniciando | 30/07 | Diana Park |\n| API Pública v3 | 🟡 Em risco | 01/06 | Eduardo Cruz |\n\n**Projetos em risco:** A Integração ERP está aguardando credenciais do cliente Alfa há 10 dias. A API v3 tem bloqueio técnico na autenticação OAuth que precisa de decisão arquitetural.',
156
+ timestamp: d(1440 + 178),
157
+ },
158
+ ],
159
+ },
160
+ {
161
+ id: 'conversa-5',
162
+ label: 'Análise de churn de clientes',
163
+ group: 'ontem',
164
+ messages: [
165
+ {
166
+ id: 'm1',
167
+ type: 'user',
168
+ content: 'Analisa o churn do mês passado. Tivemos 12 cancelamentos.',
169
+ timestamp: d(1440 + 300),
170
+ },
171
+ {
172
+ id: 'm2',
173
+ type: 'assistant',
174
+ content:
175
+ '## Análise de Churn — Abril\n\n**12 cancelamentos** representam uma taxa de churn de **2,4%** (base de 500 clientes).\n\n### Principais motivos\n1. Preço / custo-benefício — 5 clientes (42%)\n2. Migração para concorrente — 4 clientes (33%)\n3. Encerramento do negócio — 2 clientes (17%)\n4. Insatisfação com suporte — 1 cliente (8%)\n\n### Perfil dos churned\n- Plano Basic: 8 (67%)\n- Plano Pro: 3 (25%)\n- Plano Enterprise: 1 (8%)\n\n**Insight:** A concentração no plano Basic sugere que o preço de renovação anual está sendo percebido como alto para clientes nesse tier. Recomendo revisar a proposta de valor ou criar um plano intermediário.',
176
+ timestamp: d(1440 + 298),
177
+ },
178
+ ],
179
+ },
180
+ {
181
+ id: 'conversa-6',
182
+ label: 'Proposta comercial Projeto Alpha',
183
+ group: 'semana',
184
+ messages: [
185
+ {
186
+ id: 'm1',
187
+ type: 'user',
188
+ content:
189
+ 'Precisa de ajuda para montar a proposta comercial para o Projeto Alpha. É um cliente enterprise do setor de logística.',
190
+ timestamp: d(3 * 1440),
191
+ },
192
+ {
193
+ id: 'm2',
194
+ type: 'assistant',
195
+ content:
196
+ 'Para um cliente enterprise de logística, estruturo a proposta nos seguintes blocos:\n\n**1. Contexto e Desafios**\nIdentificar os 3 principais pain points do cliente (rastreamento, gestão de frotas, custo operacional).\n\n**2. Solução Proposta**\nDetalhar os módulos relevantes para logística: dashboard de rotas, alertas em tempo real, relatórios de KPI operacional.\n\n**3. Modelo Comercial**\n- Licença Enterprise: R$ 8.500/mês\n- Implementação e customização: R$ 45.000 (único)\n- SLA garantido: 99,9% uptime com suporte 24/7\n\n**4. ROI Estimado**\nRedução de 15-20% em custo operacional no primeiro ano.\n\n**5. Próximos Passos**\nPiloto de 30 dias sem custo para validar integração com sistema legado.\n\nDeseja que eu elabore alguma seção específica?',
197
+ timestamp: d(3 * 1440 - 2),
198
+ },
199
+ ],
200
+ },
201
+ {
202
+ id: 'conversa-7',
203
+ label: 'KPIs do time de engenharia',
204
+ group: 'semana',
205
+ messages: [
206
+ {
207
+ id: 'm1',
208
+ type: 'user',
209
+ content: 'Quais KPIs devo acompanhar para o time de engenharia?',
210
+ timestamp: d(4 * 1440),
211
+ },
212
+ {
213
+ id: 'm2',
214
+ type: 'assistant',
215
+ content:
216
+ '## KPIs Essenciais — Time de Engenharia\n\n**Velocidade e Entrega**\n- Velocity (pontos/sprint)\n- Lead time (do commit ao deploy)\n- Frequência de deploy\n- Taxa de conclusão de sprint\n\n**Qualidade**\n- Taxa de bugs em produção\n- Cobertura de testes (%)\n- MTTR — Mean Time to Recovery\n- Change failure rate\n\n**Saúde do Time**\n- Dívida técnica (story points estimados)\n- Satisfação do time (eNPS)\n- Tempo médio de code review\n\n**Negócio**\n- Tempo de entrega de features (time-to-market)\n- Uptime do sistema\n- Custo por deploy\n\nRecomendo começar com 5-7 KPIs no máximo para não sobrecarregar o processo de coleta.',
217
+ timestamp: d(4 * 1440 - 2),
218
+ },
219
+ ],
220
+ },
221
+ {
222
+ id: 'conversa-8',
223
+ label: 'Planejamento de sprint Q2',
224
+ group: 'semana',
225
+ messages: [
226
+ {
227
+ id: 'm1',
228
+ type: 'user',
229
+ content: 'Me ajuda a planejar o Q2. Temos 3 sprints de 2 semanas cada.',
230
+ timestamp: d(5 * 1440),
231
+ },
232
+ {
233
+ id: 'm2',
234
+ type: 'assistant',
235
+ content:
236
+ '## Planejamento Q2 — 3 Sprints\n\n**Sprint 1 (01–14/mai)**\n- Finalizar módulo de relatórios avançados\n- Correções críticas de UX do onboarding\n- Setup da infraestrutura para o App Mobile v2\n\n**Sprint 2 (15–28/mai)**\n- Kickoff do redesign de onboarding\n- Início da API Pública v3\n- Integração com ERP (depende de credenciais do cliente)\n\n**Sprint 3 (29/mai–11/jun)**\n- Beta fechado do App Mobile v2\n- Estabilização da API v3\n- Testes de carga e segurança\n\n**Capacidade estimada:** 85 pontos/sprint × 3 = 255 pontos no Q2\n\nSugestão: reserve 15% da capacidade de cada sprint para bugs e dívida técnica.',
237
+ timestamp: d(5 * 1440 - 2),
238
+ },
239
+ ],
240
+ },
241
+ ];
242
+
243
+ // ─── Component ───────────────────────────────────────────────────────────────
244
+
245
+ export function AssistantPage() {
246
+ const navigate = useNavigate();
247
+ const { t } = useTranslation();
248
+ const { sidebarExpanded, setSidebarExpanded } = useLayout();
249
+
250
+ const [conversations, setConversations] = useState<Conversation[]>(INITIAL_CONVERSATIONS);
251
+ const [selectedId, setSelectedId] = useState<string | null>(null);
252
+ const [searchValue, setSearchValue] = useState('');
253
+ const [deleteTarget, setDeleteTarget] = useState<Conversation | null>(null);
254
+ const [renameTarget, setRenameTarget] = useState<Conversation | null>(null);
255
+ const [renameValue, setRenameValue] = useState('');
256
+
257
+ useEffect(() => {
258
+ if (!sidebarExpanded) setSidebarExpanded(true);
259
+ }, [sidebarExpanded, setSidebarExpanded]);
260
+
261
+ const selectedConversation = conversations.find(c => c.id === selectedId) ?? null;
262
+
263
+ const GROUP_CONFIG = useMemo(() => [
264
+ { id: 'hoje', label: t('assistant.page.today') },
265
+ { id: 'ontem', label: t('assistant.page.yesterday') },
266
+ { id: 'semana', label: t('assistant.page.thisWeek') },
267
+ ], [t]);
268
+
269
+ const richSuggestions = useMemo(() => [
270
+ { id: 'chart-1', text: t('assistant.richSuggestions.viewPerformance') },
271
+ { id: 'table-1', text: t('assistant.richSuggestions.generateReport') },
272
+ { id: 'doc-1', text: t('assistant.richSuggestions.createDocument') },
273
+ { id: 'pod-1', text: t('assistant.richSuggestions.generatePodcast') },
274
+ ], [t]);
275
+
276
+ const feedbackOptions = useMemo(() => [
277
+ t('assistant.feedback.notWhatIWanted'),
278
+ t('assistant.feedback.incorrectInfo'),
279
+ t('assistant.feedback.incompleteAnswer'),
280
+ ], [t]);
281
+
282
+ const navigationGroups = useMemo(() => {
283
+ const query = searchValue.toLowerCase();
284
+ return GROUP_CONFIG.map(group => ({
285
+ id: group.id,
286
+ label: group.label,
287
+ icon: MessageSquare,
288
+ items: conversations
289
+ .filter(c => c.group === group.id)
290
+ .filter(c => !query || c.label.toLowerCase().includes(query))
291
+ .map(c => ({
292
+ path: `/assistente/${c.id}`,
293
+ label: c.label,
294
+ actions: [
295
+ {
296
+ label: t('assistant.page.rename'),
297
+ icon: FileEdit,
298
+ onClick: () => {
299
+ setRenameTarget(c);
300
+ setRenameValue(c.label);
301
+ },
302
+ },
303
+ {
304
+ label: t('assistant.page.delete'),
305
+ icon: Trash2,
306
+ onClick: () => setDeleteTarget(c),
307
+ variant: 'destructive' as const,
308
+ },
309
+ ],
310
+ })),
311
+ })).filter(g => g.items.length > 0);
312
+ }, [conversations, searchValue, GROUP_CONFIG, t]);
313
+
314
+ const handleSelectConversation = (path: string) => {
315
+ if (path.startsWith('/assistente/')) {
316
+ setSelectedId(path.replace('/assistente/', ''));
317
+ } else {
318
+ navigate(path);
319
+ }
320
+ };
321
+
322
+ const handleDelete = () => {
323
+ if (!deleteTarget) return;
324
+ setConversations(prev => prev.filter(c => c.id !== deleteTarget.id));
325
+ if (selectedId === deleteTarget.id) setSelectedId(null);
326
+ setDeleteTarget(null);
327
+ };
328
+
329
+ const handleRename = () => {
330
+ if (!renameTarget || !renameValue.trim()) return;
331
+ setConversations(prev =>
332
+ prev.map(c => (c.id === renameTarget.id ? { ...c, label: renameValue.trim() } : c))
333
+ );
334
+ setRenameTarget(null);
335
+ };
336
+
337
+ const sidebarProps = {
338
+ width: 320,
339
+ fixedArea: {
340
+ show: true,
341
+ icon: Plus,
342
+ onClick: () => setSelectedId(null),
343
+ content: (
344
+ <Button
345
+ variant="secondary"
346
+ className="w-full font-semibold"
347
+ onClick={() => setSelectedId(null)}
348
+ >
349
+ <Plus className="w-4 h-4 mr-2" />
350
+ {t('assistant.newConversation')}
351
+ </Button>
352
+ ),
353
+ },
354
+ search: {
355
+ show: true,
356
+ placeholder: t('assistant.page.searchConversations'),
357
+ value: searchValue,
358
+ onChange: setSearchValue,
359
+ },
360
+ navigationGroups,
361
+ location: { pathname: selectedId ? `/assistente/${selectedId}` : '/assistente' },
362
+ navigate: handleSelectConversation,
363
+ };
364
+
365
+ return (
366
+ <>
367
+ <AppLayout
368
+ sidebarVariant="assistant"
369
+ sidebarProps={sidebarProps}
370
+ >
371
+ <div
372
+ style={{ paddingLeft: sidebarExpanded ? '320px' : '80px' }}
373
+ className="flex-1 flex flex-col overflow-hidden transition-all duration-300"
374
+ >
375
+ <Header
376
+ breadcrumbs={[
377
+ { label: t('nav.designSystem'), href: '/home' },
378
+ { label: selectedConversation?.label ?? t('assistant.newConversation') },
379
+ ]}
380
+ renderLink={(href: string, props: any) => <Link to={href} {...props} />}
381
+ actions={[
382
+ {
383
+ id: 'back',
384
+ label: t('assistant.back'),
385
+ icon: <ArrowLeft className="w-4 h-4" />,
386
+ onClick: () => navigate('/home'),
387
+ className:
388
+ 'bg-primary text-primary-foreground hover:bg-primary/90 hover:text-primary-foreground',
389
+ },
390
+ ]}
391
+ />
392
+ <main className="flex-1 overflow-hidden bg-muted">
393
+ <XerticaAssistant
394
+ key={selectedId ?? 'new'}
395
+ mode="fullPage"
396
+ demoMode={true}
397
+ userName="Ariel Santos"
398
+ responseGenerator={generateDemoResponse}
399
+ initialMessages={selectedConversation?.messages as any}
400
+ richSuggestions={richSuggestions}
401
+ feedbackOptions={feedbackOptions}
402
+ showHistory={false}
403
+ showFavorites={false}
404
+ onNavigateSettings={() => navigate('/settings')}
405
+ onEvaluation={(messageId, type, reason) => {
406
+ console.log(
407
+ `Avaliação recebida: ${type} na mensagem ${messageId}. Motivo: ${reason}`
408
+ );
409
+ }}
410
+ />
411
+ </main>
412
+ </div>
413
+ </AppLayout>
414
+
415
+ {/* Modal: Excluir conversa */}
416
+ <AlertDialog open={!!deleteTarget} onOpenChange={open => !open && setDeleteTarget(null)}>
417
+ <AlertDialogContent>
418
+ <AlertDialogHeader>
419
+ <AlertDialogTitle>{t('assistant.page.deleteConversationTitle')}</AlertDialogTitle>
420
+ <AlertDialogDescription>
421
+ {t('assistant.page.deleteConversationDesc', { name: deleteTarget?.label })}
422
+ </AlertDialogDescription>
423
+ </AlertDialogHeader>
424
+ <AlertDialogFooter>
425
+ <AlertDialogCancel>{t('assistant.page.cancel')}</AlertDialogCancel>
426
+ <AlertDialogAction
427
+ onClick={handleDelete}
428
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
429
+ >
430
+ {t('assistant.page.delete')}
431
+ </AlertDialogAction>
432
+ </AlertDialogFooter>
433
+ </AlertDialogContent>
434
+ </AlertDialog>
435
+
436
+ {/* Modal: Renomear conversa */}
437
+ <Dialog open={!!renameTarget} onOpenChange={open => !open && setRenameTarget(null)}>
438
+ <DialogContent>
439
+ <DialogHeader>
440
+ <DialogTitle>{t('assistant.page.renameConversationTitle')}</DialogTitle>
441
+ </DialogHeader>
442
+ <div className="space-y-2 py-2">
443
+ <Label htmlFor="rename-input">{t('assistant.page.conversationNameLabel')}</Label>
444
+ <Input
445
+ id="rename-input"
446
+ value={renameValue}
447
+ onChange={e => setRenameValue(e.target.value)}
448
+ onKeyDown={e => e.key === 'Enter' && handleRename()}
449
+ autoFocus
450
+ />
451
+ </div>
452
+ <DialogFooter>
453
+ <Button variant="outline" onClick={() => setRenameTarget(null)}>
454
+ {t('assistant.page.cancel')}
455
+ </Button>
456
+ <Button onClick={handleRename} disabled={!renameValue.trim()}>
457
+ {t('assistant.page.save')}
458
+ </Button>
459
+ </DialogFooter>
460
+ </DialogContent>
461
+ </Dialog>
462
+ </>
463
+ );
464
+ }