diff --git a/agents.md b/agents.md index 81c27dd..e65b1ab 100644 --- a/agents.md +++ b/agents.md @@ -1,463 +1,78 @@ -# Plano de Desenvolvimento — Sistema de Chamados - -## Contato principal -- **Esdras Renan** — monkeyesdras@gmail.com - -## Ambiente local -- Admin: `admin@sistema.dev` / `admin123` -- Agentes seed (senha inicial `agent123` — alterar no primeiro acesso): - - Gabriel Oliveira · george.araujo@rever.com.br - - George Araujo · george.araujo@rever.com.br - - Hugo Soares · hugo.soares@rever.com.br - - Julio Cesar · julio@rever.com.br - - Lorena Magalhães · lorena@rever.com.br - - Rever · renan.pac@paulicon.com.br - - Telão · suporte@rever.com.br - - Thiago Medeiros · thiago.medeiros@rever.com.br - - Weslei Magalhães · weslei@rever.com.br - -> Todos os usuários estão sincronizados com o Convex via `scripts/seed-agents.mjs`. - -## Visão geral atual -- **Meta imediata:** consolidar o núcleo de tickets web/desktop com canais, SLAs e automações futuras. -- **Stack:** Next.js (App Router) + Convex + Better Auth + Prisma (referência de domínio). -- **Estado:** núcleo web funcional (tickets, play mode, painéis administrativos, portal do cliente) com Turbopack habilitado no `pnpm dev`. - -## Entregas concluídas -- Scaffold Next.js + Tailwind + shadcn/ui, shell com sidebar/header, login real com Better Auth. -- Integração Convex completa: listas/detalhe de tickets, mutations (status, categorias, filas, comentários, play next). -- Painel administrativo: gestão de filas, times, campos personalizados e convites Better Auth. -- Portal do cliente isolado por `viewerId`; dashboard principal consumindo métricas reais do Convex. -- Fluxo de convites Better Auth ponta a ponta + seed automatizado de agentes/admin. - -## Desenvolvimento em curso -- Refinar sincronização Better Auth ↔ Convex (resets de senha, revogação automática de convites). -- Melhorar UX do ticket header (categorias, status, prioridades) e comandos rápidos na listagem. -- Manter hidratação consistente na sidebar e componentes Radix após migração para React 19. - -## Próximas prioridades -1. Expandir suíte de testes (UI + Convex) e habilitar pipeline CI obrigatória (lint + vitest). -2. Implementar resets de senha automatizados e auditoria de convites para onboarding/offboarding. -3. Expor categorias/subcategorias dinâmicas na criação/edição de tickets (web e desktop). -4. Adicionar ações avançadas para agentes (edição de categorias, reassignment rápido) sob RBAC. - -## Boas práticas e rotinas -- **Seeds:** `node --env-file=.env.local scripts/seed-agents.mjs` (mantém admin e agentes) + `/dev/seed` para dados demo. -- **Serviços locais:** `pnpm convex:dev` (gera tipos e roda backend) e `pnpm dev` (Next.js com Turbopack). -- **Testes e lint:** execute `pnpm lint` e `pnpm vitest run` antes de cada PR. -- **Convex:** retorne apenas tipos suportados (`number` para datas) e valide no front via mappers Zod. -- **UI:** textos PT‑BR, toasts com feedback, atualizações otimistas com rollback em caso de erro. -- **Git/PR:** branches descritivas, checklist padrão (tipos Convex, labels PT‑BR, loaders, mappers atualizados) e coautor `factory-droid[bot]` quando aplicável. - -## Histórico de marcos -- Fase A (scaffold/UX base) e Fase B (núcleo de tickets) concluídas. -- Iniciativa “Autenticação real e personas” entregue com RBAC completo e portal do cliente. -- Roadmap imediato focado em credenciais unificadas, automações de convites e cobertura de testes. - -# Plano de Desenvolvimento - Sistema de Chamados +# Plano de Desenvolvimento — Sistema de Chamados -## Meta imediata -Construir o nucleo de tickets compartilhado entre web e desktop (Tauri), garantindo base solida para canais, SLAs e automacoes futuras. - -### Contato principal -- **Esdras Renan** — monkeyesdras@gmail.com - -### Credenciais seed (ambiente local) -- Administrador padrão: `admin@sistema.dev` / `admin123` -- Agentes carregados via seed (senha inicial `agent123`, altere após o primeiro acesso): - - Gabriel Oliveira — gabriel.oliveira@rever.com.br - - George Araujo — george.araujo@rever.com.br - - Hugo Soares — hugo.soares@rever.com.br - - Julio Cesar — julio@rever.com.br - - Lorena Magalhães — lorena@rever.com.br - - Rever — renan.pac@paulicon.com.br - - Telão — suporte@rever.com.br - - Thiago Medeiros — thiago.medeiros@rever.com.br - - Weslei Magalhães — weslei@rever.com.br - -> Observação: todos os usuários acima foram sincronizados com o Convex. Atualize as senhas imediatamente após o primeiro login. +## Contato principal +- **Esdras Renan** — monkeyesdras@gmail.com -## Fase A - Fundamentos da plataforma -1. **Scaffold e DX** - - Criar projeto Next.js (App Router) com Typescript, ESLint, Tailwind, shadcn/ui. - - Configurar alias de paths, lint/prettier opinativo. - - Ajustar `globals.css` para tokens de cor/tipografia conforme layout base. -2. **Design system inicial** - - Importar componentes `dashboard-01` e `sidebar-01` via shadcn. - - Ajustar paleta (tons de cinza + destaque primario) e tipografia (Inter/Manrope). - - Implementar layout shell (sidebar + header) reutilizavel. -3. **Autenticacao placeholder** - - Configurar stub de sessao (cookie + middleware) para navegacao protegida. +## Credenciais padrão (Better Auth) +- Administrador: `admin@sistema.dev` / `admin123` +- Agente Demo: `agente.demo@sistema.dev` / `agent123` +- Cliente Demo: `cliente.demo@sistema.dev` / `cliente123` +> Execute `pnpm --dir web auth:seed` após configurar `.env.local`. O script atualiza as contas acima ou cria novas conforme variáveis `SEED_USER_*`. -### Status da fase -- OK Scaffold Next.js + Tailwind + shadcn/ui criado em `web/`. -- OK Layout base atualizado (sidebar, header, cards, grafico) com identidade da aplicacao. -- OK Auth placeholder via cookie + middleware e bootstrap de usuario no Convex. +## Sincronização com Convex +- Usuários e tickets demo são garantidos via `web/convex/seed.ts`. +- Com `pnpm convex:dev` rodando, acesse `/dev/seed` uma vez para popular dados quando necessário. -## Fase B - Nucleo de tickets -1. **Modelagem compartilhada** - - Definir esquema Prisma para Ticket, TicketEvent, User (minimo), Queue/View. - - Publicar Zod schemas/Types para uso no frontend. -2. **Fluxo principal** - - Pagina `tickets` com tabela (TanStack) suportando filtros basicos. - - Pagina de ticket com timeline de eventos/comentarios (dados mockados). - - Implementar modo play preliminar (simula proxima tarefa da fila). -3. **Mutations** - - Formulario de criacao/edicao com validacao. - - Comentarios publico/privado (UX + componentes). +## Setup local rápido +1. `cd web && pnpm install` +2. `cp .env.example .env.local` e ajuste `NEXT_PUBLIC_CONVEX_URL` apontando para o servidor Convex local. +3. `pnpm --dir web auth:seed` +4. `pnpm --dir web convex:dev` +5. Em outro terminal: `pnpm --dir web dev` -### Status parcial -- OK `prisma/schema.prisma` criado com entidades centrais (User, Team, Ticket, Comment, Event, SLA). -- OK Schemas Zod e mocks compartilhados em `src/lib/schemas` e `src/lib/mocks`. -- OK Paginas `/tickets`, `/tickets/[id]` e `/play` prontas com componentes dedicados (filtros, tabela, timeline, modo play). -- OK Integração com backend Convex (consultas/mutações + file storage). Prisma mantido apenas como referência. +## Estado atual +- Autenticação Better Auth com guardas client-side (`AuthGuard`) bloqueando rotas protegidas. +- Menu de usuário no rodapé da sidebar com link para `/settings` e logout confiável. +- Formulários de novo ticket (dialog, página e portal) com seleção de responsável, placeholders claros e validação obrigatória de assunto/descrição/categorias. +- Portal do cliente restringe visualização e criação ao próprio requester; clientes não atribuem responsáveis. +- Relatórios e dashboards utilizam `AppShell`, garantindo header/sidebar consistentes. -## Fase C - Servicos complementares (posterior) -- SLAs (BullMQ + Redis), notificacoes, ingest de e-mail, portal cliente, etc. +## Entregas recentes relevantes +- Correção do redirecionamento após logout evitando retorno imediato ao dashboard. +- Validações manuais dos formulários de rich text para eliminar `ZodError` durante edição. +- Dropdown de responsáveis na criação de tickets com preenchimento automático pelo autor e evento inicial de comentário. +- Indicadores visuais de campos obrigatórios e botão "Novo ticket" funcional no cabeçalho do detalhe. +- Seeds (Better Auth e Convex) ampliados para incluir agente e cliente de teste. -## Backlog imediato -- [x] Expor portal do cliente com listagem de tickets filtrada por `viewerId` (Convex + UI) -- [x] Completar painel administrativo (times, filas, campos e SLAs) com RBAC server/client -- [ ] Finalizar sincronização Better Auth ↔ Convex para resets de senha e revogações automáticas de convites -- [ ] Expandir suite de testes (UI + Convex) cobrindo guardas, relatórios e mapeadores críticos -- [x] Implementar fluxo completo de convites (criação, envio, revogação e aceite) para administradores -- [ ] Habilitar ações avançadas para agentes (edição de categorias, reassigação rápida) com as devidas permissões -- [ ] Integrar campos personalizados e categorias dinâmicas nos formulários de criação/edição de tickets - -### Iniciativa atual — Autenticação real e personas -- [x] Migrar placeholder para Better Auth + Prisma (handlers Next, cliente React e sync Convex). -- [x] Expor roles (`admin`, `agent`, `customer`) e aplicar guardas (`requireUser/Staff/Admin/Customer`) no Convex. -- [x] Ajustar middleware e componentes para usar `viewerId`/`actorId`, evitando vazamento de dados entre tenants. -- [x] Criar portal do cliente para abertura/consulta de chamados e comentários públicos. -- [x] Consolidar painel administrativo (times, filas, campos e SLAs) com UI protegida por RBAC completo. -- [x] Entregar fluxo de convites Better Auth (criação, envio, revogação) e gerenciamento de agentes. -- [ ] Unificar ciclo de vida de credenciais (reset de senha, expiração automática e reenvio de convites). +## Fluxos suportados -## Proximas entregas sugeridas -1. Consolidar onboarding/offboarding de agentes com resets de senha, reenvio automático e auditoria de convites Better Auth. -2. Expor categorias, subcategorias e campos personalizados dinamicamente nas telas de criação/edição de tickets (web e desktop). -3. Definir permissões intermediárias para agentes (edição limitada de categorias/campos) e refletir no Convex. -4. Expandir relatórios operacionais (workSummary, métricas por canal/categoria) usando os novos campos personalizados. -5. Automatizar pipeline CI (lint + vitest) integrando checagens obrigatórias antes de merge. +### Equipe interna (admin/agent/collaborator) +- Criar tickets com categorias, responsável inicial e anexos. +- Abrir novos tickets diretamente a partir do detalhe via dialog reutilizável. +- Acessar `/settings` para ajustes pessoais e efetuar logout pelo menu. -## Acompanhamento -Atualizar este arquivo a cada marco relevante (setup concluido, nucleo funcional, etc.). - ---- - -# Guia do Projeto (para agentes e contribuidores) - -Este repositório foi atualizado para usar Convex como backend em tempo real para o núcleo de tickets. Abaixo, um guia prático conforme o padrão de AGENTS.md para orientar contribuições futuras. - -## Decisões técnicas atuais -- Backend: Convex (funções + banco + storage) em `web/convex/`. - - Esquema: `web/convex/schema.ts`. - - Tickets API: `web/convex/tickets.ts` (list/getById/create/addComment/updateStatus/playNext). - - Upload de arquivos: `web/convex/files.ts` (Convex Storage). - - Filas: `web/convex/queues.ts` (resumo por fila). - - Seed/bootstrap: `web/convex/seed.ts`, `web/convex/bootstrap.ts`. -- Autenticação: Better Auth + Prisma (SQLite) com roles (`admin`, `agent`, `customer`) sincronizadas com Convex - - Login: `web/src/app/login/page.tsx` + `web/src/components/login/login-form.tsx` - - Middleware e guards: `web/middleware.ts`, helpers em `web/src/lib/auth{,z, -server}.ts` - - Cliente React: `web/src/lib/auth-client.tsx` (sincroniza sessão Better Auth ↔ Convex, expõe helpers de role) -- Frontend (Next.js + shadcn/ui) - - Páginas principais: `/tickets`, `/tickets/[id]`, `/tickets/new`, `/play`. - - UI ligada ao Convex com `convex/react`. - - Toasts: `sonner` via `Toaster` em `web/src/app/layout.tsx`. -- Mapeamento/validação de dados - - Convex retorna datas como `number` (epoch). A UI usa `Date`. - - Sempre converter/validar via Zod em `web/src/lib/mappers/ticket.ts`. - - Não retornar `Date` a partir de funções do Convex. -- Prisma: mantido apenas como referência de domínio (não é fonte de dados ativa). - -## Como rodar -- Pré‑requisitos: Node LTS + pnpm. -- Passos: - - `cd web && pnpm i` - - `pnpm convex:dev` (mantém gerando tipos e rodando backend dev) - - Criar `.env.local` com `NEXT_PUBLIC_CONVEX_URL=` - - Em outro terminal: `pnpm dev` - - Login em `/login`; seed opcional em `/dev/seed`. - -## Convenções de código -- Não use `Date` em payloads do Convex; use `number` (epoch ms). -- Normalize dados no front via mappers Zod antes de renderizar. -- UI com shadcn/ui; priorize componentes existentes e consistência visual. -- Labels e mensagens em PT‑BR (status, timeline, toasts, etc.). -- Atualizações otimistas com rollback em erro + toasts de feedback. -- Comentários de supressão: prefira `@ts-expect-error` com justificativa curta para módulos gerados do Convex; evite `@ts-ignore`. - -## Estrutura útil -- `web/convex/*` — API backend Convex. -- `web/src/lib/mappers/*` — Conversores server→UI com Zod. -- `web/src/components/tickets/*` — Tabela, filtros, detalhe, timeline, comentários, play. - -## Scripts (pnpm) -- `pnpm convex:dev` — Convex (dev + geração de tipos) -- `pnpm dev` — Next.js (App Router) -- `pnpm build` / `pnpm start` — build/produção - -## Backlog imediato (próximos passos) -- Form “Novo ticket” em Dialog shadcn + React Hook Form + Zod + toasts. -- Atribuição/transferência de fila no detalhe (selects com update otimista). -- Melhorias de layout adicionais no painel “Detalhes” (quebras, largura responsiva) e unificação de textos PT‑BR. -- Testes unitários dos mapeadores com Vitest. - -## Checklist de PRs -- [ ] Funções Convex retornam apenas tipos suportados (sem `Date`). -- [ ] Dados validados/convertidos via Zod mappers antes da UI. -- [ ] Textos/labels em PT‑BR. -- [ ] Eventos de UI com feedback (toast) e rollback em erro. -- [ ] Documentação atualizada se houver mudanças em fluxo/env. - ---- - -## Próximas Entregas (Roadmap detalhado) - -1) UX/Visual (shadcn/ui) -- Padronizar cartões em todas as telas (Play, Visualizações) com o mesmo padrão aplicado em Conversa/Detalhes/Timeline (bordas, sombra, paddings). -- Aplicar microtipografia consistente: headings H1/H2, tracking, tamanhos, cores em PT‑BR. -- Skeletons de carregamento nos principais painéis (lista de tickets, recentes, play next). -- Melhorar tabela: estados hover/focus, ícones de canal, largura de colunas previsível e truncamento. - -2) Comentários e anexos -- Dropzone também no “Novo ticket” (já implementado) com registro de comentário inicial e anexos. -- Grid de anexos com miniaturas e legenda; manter atributo `download` com o nome original. -- Preview em modal para imagens (feito) e suporte a múltiplas linhas no grid. -- Botão para copiar link de arquivo (futuro, usar URL do storage). - -3) Timeline e eventos -- Mensagens amigáveis em PT‑BR (feito para CREATED/STATUS/ASSIGNEE/QUEUE). -- Incluir sempre `actorName`/`actorAvatar` no payload; evitar JSON cru na UI. -- Exibir avatar e nome do ator nas entradas (parcialmente feito). - -4) Dados e camada Convex -- Sempre retornar datas como `number` (epoch) e converter no front via mappers Zod. -- Padronizar import do Convex com `@/convex/_generated/api` (alias criado). -- Evitar `useQuery` com args vazios — proteger chamadas (gates) e, quando necessário, fallback de mock para IDs `ticket-*`. - -5) Autenticação / Sessão (placeholder) -- Cookie `demoUser` e bootstrap de usuário no Convex (feito). Trocar por Auth.js/Clerk quando for o momento. - -6) Testes -- Vitest configurado; adicionar casos para mapeadores (já iniciado) e smoke tests básicos de páginas. -- Não usar Date em assertions de payload — sempre comparar epoch ou `instanceof Date` após mapeamento. - -7) Acessibilidade e internacionalização -- Labels e mensagens 100% em PT‑BR; evitar termos como `QUEUE_CHANGED` na UI. -- Navegação por teclado em Dialogs/Selects; aria-labels em botões de ação. - -8) Observabilidade (posterior) -- Logs de evento estruturados no Convex; traces simples no client para ações críticas. - ---- - -## Endpoints Convex (resumo) -- `tickets.list({ tenantId, status?, priority?, channel?, queueId?, search?, limit? })` -- `tickets.getById({ tenantId, id })` -- `tickets.create({ tenantId, subject, summary?, priority, channel, queueId?, requesterId })` -- `tickets.addComment({ ticketId, authorId, visibility, body, attachments?[] })` -- `tickets.updateStatus({ ticketId, status, actorId })` — gera evento com `toLabel` e `actorName`. -- `tickets.changeAssignee({ ticketId, assigneeId, actorId })` — gera evento com `assigneeName`. -- `tickets.changeQueue({ ticketId, queueId, actorId })` — gera evento com `queueName`. -- `tickets.playNext({ tenantId, queueId?, agentId })` — atribui ticket e registra evento. -- `tickets.updatePriority({ ticketId, priority, actorId })` — altera prioridade e registra `PRIORITY_CHANGED`. -- `tickets.remove({ ticketId, actorId })` — remove ticket, eventos e comentários (tenta excluir anexos do storage). -- `queues.summary({ tenantId })` -- `files.generateUploadUrl()` — usar via `useAction`. -- `users.ensureUser({ tenantId, email, name, avatarUrl?, role?, teams? })` - -Observações: -- Não retornar `Date` nas funções Convex; usar `number` e converter na UI com os mappers em `src/lib/mappers`. -- Evitar passar `{}` para `useQuery` — args devem estar definidos ou a query não deve ser invocada. - ---- - -## Padrões de Código -- UI: shadcn/ui (Field, Dialog, Select, Badge, Table, Spinner) + Tailwind. -- Dados: Zod para validação; mappers para converter server→UI (epoch→Date, null→undefined). -- Texto: PT‑BR em labels, toasts e timeline. -- UX: updates otimistas + toasts (status, assignee, fila, comentários). -- Imports do Convex: sempre `@/convex/_generated/api`. - ---- - -## Como abrir PR -- Crie uma branch descritiva (ex.: `feat/tickets-attachments-grid`). -- Preencha a descrição com: contexto, mudanças, como testar (pnpm scripts), screenshots quando útil. -- Checklist: - - [ ] Sem `Date` no retorno Convex. - - [ ] Labels PT‑BR. - - [ ] Skeleton/Loading onde couber. - - [ ] Mappers atualizados se tocar em payloads. - - [ ] AGENTS.md atualizado se houver mudança de padrões. - ---- - -## Atualizações recentes (dez/2025) - -- RBAC do Convex reforçado: `tickets.list`, `tickets.getById`, `workSummary` e mutações sensíveis (`changeQueue`, `updateCategories`, `startWork/pauseWork`, `updatePriority`) agora exigem `viewerId/actorId` e validam `requireStaff` com `tenantId`. -- Componentes de tickets (tabela, painel de recentes, play next, cabeçalho/detalhe) passam a usar o contexto Better Auth para prover `viewerId`, com `useQuery` protegido por `"skip"` enquanto não há sessão. -- Testes (`pnpm vitest run`) executados após as alterações para garantir regressão zero. - -## Progresso recente (mar/2025) - -Resumo do que foi implementado desde o último marco: - -- Rich text (Tiptap) com SSR seguro para comentários e descrição inicial do ticket - - Componente: `web/src/components/ui/rich-text-editor.tsx` - - Comentários: `web/src/components/tickets/ticket-comments.rich.tsx` (visibilidade Público/Interno, anexos tipados) - - Novo ticket (Dialog + Página): campos de descrição usam rich text; primeiro comentário é registrado quando houver conteúdo. -- Tipagem estrita (remoção de `any`) no front e no Convex - - Uso consistente de `Id<>` e `Doc<>` (Convex) e schemas Zod (record tipado em v4). - - Queries `useQuery` com "skip" quando necessário; mapeadores atualizados. -- Filtros server-side - - `tickets.list` agora escolhe o melhor índice (por `status`, `queueId` ou `tenant`) e só então aplica filtros complementares. -- UI do detalhe do ticket (Header) - - Prioridade como dropdown-badge translúcida: `web/src/components/tickets/priority-select.tsx` (nova Convex `tickets.updatePriority`). - - Seleção de responsável com avatar no menu. - - Ação de exclusão com modal (ícones, confirmação): `web/src/components/tickets/delete-ticket-dialog.tsx` (Convex `tickets.remove`). -- Correções e DX - - Tiptap: `immediatelyRender: false` + `setContent({ emitUpdate: false })` para evitar mismatch de hidratação. - - Validação de assunto no Dialog “Novo ticket” (trim + `setError`) para prevenir `ZodError` em runtime. - -Arquivos principais tocados: -- Convex: `web/convex/schema.ts`, `web/convex/tickets.ts` (novas mutations + tipagem `Doc/Id`). -- UI: `ticket-summary-header.tsx`, `ticket-detail-view.tsx`, `ticket-comments.rich.tsx`, `new-ticket-dialog.tsx`, `play-next-ticket-card.tsx`. -- Tipos e mapeadores: `web/src/lib/schemas/ticket.ts`, `web/src/lib/mappers/ticket.ts`. - -## Guia de layout/UX aplicado - -- Header do ticket - - Ordem: `#ref` • PrioritySelect (badge) • Status (badge/select) • Ações (Excluir) - - Tipografia: título forte, resumo como texto auxiliar, metadados em texto pequeno. - - Combos de Categoria/ Subcategoria exibidos como selects dependentes com salvamento automático (sem botões dedicados). -- Comentários - - Composer com rich text + Dropzone; seletor de visibilidade. - - Lista com avatar, nome, carimbo relativo e conteúdo rich text. -- Prioridades (labels) - - LOW (cinza), MEDIUM (azul), HIGH (âmbar), URGENT (vermelho) — badge translúcida no trigger do select. - -## Próximos passos sugeridos (UI/Funcionais) - -Curto prazo (incremental): -- [ ] Transformar Status em dropdown-badge (mesmo padrão de Prioridade). -- [ ] Estados vazios com `Empty` (ícone, título, descrição, CTA) na lista de comentários e tabela. -- [ ] Edição inline no header (Assunto/Resumo) com botões Reset/Salvar (mutations dedicadas). -- [ ] Polir cards (bordas/padding/sombra) nas telas Play/Tickets para padronizar com Header/Conversa. - -Médio prazo: -- [ ] Combobox (command) para responsável com busca. -- [ ] Paginação/ordenção server-side em `tickets.list`. -- [ ] Unificar mensagens de timeline e payloads (sempre `actorName`/`actorAvatar`). -- [ ] Testes Vitest para mapeadores e smoke tests básicos das páginas. - -## Como validar manualmente -- Rich text: comentar em `/tickets/[id]` com formatação, anexos e alternando visibilidade. -- Prioridade: alterar no cabeçalho; observar evento de timeline e toasts. -- Exclusão: acionar modal no cabeçalho e confirmar; conferir redirecionamento para `/tickets`. -- Novo ticket: usar Dialog; assunto com menos de 3 chars deve bloquear submit com erro no campo. - ---- - -## Atualizações recentes (abr/2025) - -Resumo do que foi integrado nesta rodada para o núcleo de tickets e UX: - -- Header do ticket - - Status como dropdown‑badge (padrão visual alinhado às badges existentes). - - Edição inline de Assunto/Resumo com Cancelar/Salvar e toasts. - - Ação de Play/Pause (toggle de atendimento) com eventos WORK_STARTED/WORK_PAUSED na timeline. - - Layout dos campos reorganizado: labels acima e controles abaixo (evita redundância do valor + dropdown lado a lado). -- Tabela e comentários - - Empty states padronizados com Empty + CTA de novo ticket. -- Notificações - - Toaster centralizado no rodapé (bottom‑center) com estilo consistente. -- Título do app - - Atualizado para “Sistema de chamados”. - -Backend Convex -- ickets.updateSubject e ickets.updateSummary adicionadas para edição do cabeçalho. -- ickets.toggleWork adicionada; campo opcional working no schema de ickets. - -Próximos passos sugeridos -- Status dropdown‑badge também na tabela (edição rápida opcional com confirmação). -- Combobox (command) para busca de responsável no select. -- Tokens de cor: manter badges padrão do design atual; quando migração completa para paleta Rever estiver definida, aplicar via globals.css para herdar em todos os componentes. -- Testes (Vitest): adicionar casos de mappers e smoke tests de páginas. - -Observações de codificação -- Evitar `any`; usar TicketStatus/TicketPriority e Id<>/Doc<> do Convex. -- Não retornar Date do Convex; sempre epoch (number) e converter via mappers Zod. - -## Atualizações recentes (out/2025) -- Cabeçalho de ticket agora persiste automaticamente mudanças de categoria/subcategoria, mostrando toasts e bloqueando os selects enquanto a mutação é processada. -- Normalização de nomes de fila/time aplicada também ao retorno de `tickets.playNext`, garantindo rótulos "Chamados"/"Laboratório" em todos os fluxos. -- ESLint ignora `convex/_generated/**` e supressões migradas para `@ts-expect-error` com justificativa explícita. -- Mutação `tickets.remove` não requer mais `actorId`; o diálogo de exclusão apenas envia `ticketId`. - -## Atualizações recentes (nov/2025) -- Dialog de novo ticket redesenhado: duas colunas com botão “Criar” no cabeçalho, dropzone mais compacta, categorias primária/secundária empilhadas e rótulos explícitos. -- Validação do assunto relaxada para evitar `ZodError` prematuro; verificação manual permanece na submissão. -- Placeholder cinza claro "Escreva um comentário..." aplicado ao editor Tiptap e seção renomeada para “Comentários”. -- Linhas da tabela de tickets agora são totalmente clicáveis (mouse e teclado), reforçando acessibilidade e atalho de navegação. -- Toasts e layouts refinados para manter consistência entre criação, listagem e detalhe dos tickets. - -## Atualizações recentes (out/2025) -- Tabela de tickets refinada com ícones de canal, prioridade ajustável inline e indicadores suavizados (fila/status/categoria) para reduzir ruído visual. -- Definido plano de migração para Better Auth com RBAC (admin/agent/customer), portal do cliente e painel administrativo para filas/categorias/agentes. -- Próximo passo: iniciar fase de implementação da autenticação real, substituindo middleware placeholder e alinhando Convex aos novos papéis. -- Better Auth agora usa banco SQLite local (`db.sqlite`) e o schema Prisma foi migrado com sucesso via `pnpm exec prisma migrate dev --name init`. -- Configuração do `postcss.config.mjs` corrigida para usar `@tailwindcss/postcss` como plugin executável, liberando a suíte do Vitest (`pnpm exec vitest run`). -- Script `pnpm auth:seed` cria/atualiza o usuário inicial (`admin@sistema.dev` / `admin123`) usando `better-auth/crypto` para hash de senha. -- Página de login refeita com layout em duas colunas (header + imagem lateral) e formulário integrado ao Better Auth (`LoginForm`). -- Middleware atualizado aplica RBAC inicial (clientes direcionados ao portal, rotas `/admin` reservadas a administradores) e helpers de role expostos em `src/lib/authz.ts`; página `/portal` criada como placeholder do futuro autosserviço. - -## Próximos passos estratégicos - -### Produto / Experiência -- [ ] Unificar revisão visual do modal de novo ticket com microinterações (estado de salvamento, validações inline). -- [ ] Implementar filtros salváveis e quick actions na listagem (ex.: alterar status diretamente). -- [ ] Exibir indicadores de anexos na tabela e nos cartões de “tickets recentes”. - -### Técnica -- [ ] Corrigir configuração do `postcss.config.mjs` (plugin inválido impede execução do Vitest) e restaurar cobertura de testes automatizados. -- [ ] Formalizar camada de autenticação (Auth.js ou Clerk) com refresh de sessão e proteção de rotas no Convex (`auth.getUserIdentity`). -- [ ] Mapear RBAC inicial (admin/agente/visualização) e refletir nas mutations do Convex. -- [ ] Configurar ambientes `staging`/`production` do Convex com variáveis (.env) versionadas via doppler/1Password. -- [ ] Automatizar lint/test/build no CI (GitHub Actions) e bloquear merge sem execução. - -### Administrativa / Operacional -- [ ] Inventariar acessos: quem possui permissão no Convex, GitHub e futuros serviços (Redis, email, armazenamento S3?). -- [ ] Criar checklists de onboarding/offboarding de agentes (criação de usuário, associação a filas, provisionamento de avatar). -- [ ] Definir plano de capacidade para armazenamento de anexos (quotas por tenant, política de retenção) e alertas. -- [ ] Preparar mock de integrações externas (e-mail entrante, WhatsApp) para futuras etapas. -- [ ] Documentar fluxo de suporte interno (quem revisa PRs, janelas de deploy, rollback). - -Manter este arquivo atualizado ao concluir cada item estratégico ou quando surgirem novas dependências administrativas. - -## Atualizações recentes (mai/2026) - -- Login corporativo refinado com instruções revisadas para primeiro acesso e mensagens de erro totalmente em PT-BR. -- Script `pnpm auth:seed` executado para garantir o usuário administrador padrão (`admin@sistema.dev` / `admin123`). -- Toast de autenticação inválida agora informa "E-mail ou senha inválidos", alinhando o feedback com o restante da interface. - -### Próximos passos imediatos -- [ ] Implementar fluxo completo de convites (criação, expiração, revogação) integrado ao Better Auth e Convex. -- [ ] Adicionar testes Vitest/E2E cobrindo dashboards, relatórios e guardas de RBAC no front. -- [ ] Mapear permissões de edição avançada para agentes (categorias, campos rápidos) antes de liberar novas mutações. - -## Atualizações recentes (jun/2026) - -- RBAC do Convex reforçado em times, filas, campos, SLAs e relatórios; todas as chamadas exigem `viewerId`/`actorId` conforme o papel (admin ou staff). -- Painel administrativo atualizado para consumir as novas assinaturas protegidas, com validações de sessão Better Auth e feedback de toasts. -- Dashboard principal passou a exibir métricas reais via `reports.dashboardOverview` e séries históricas por canal com `reports.ticketsByChannel`. -- Portal do cliente publicado com isolamento por `viewerId`, garantindo que clientes visualizem apenas seus chamados. - -## Atualizações recentes (ago/2026) - -- Convites Better Auth finalizados ponta a ponta: novos modelos Prisma, utilitários de servidor, rotas Next e tabela `userInvites` no Convex com sincronização e RBAC. -- Painel administrativo reorganizado com `CategoriesManager`, permitindo CRUD completo de categorias e subcategorias, inclusive cadastro em lote na criação. -- Campos personalizados de tickets agora são validados e persistidos no Convex (`tickets.customFields`) com normalização por tipo, `displayValue` e mapeamento seguro no frontend. -- Consultas e componentes que consomem `queues.summary` passaram a enviar `viewerId`, eliminando erros de autorização na UI de tickets. -- Suite de testes estendida com `invite-utils.test.ts` e configuração `vitest.setup.ts`, garantindo ambiente consistente com variáveis Better Auth. +### Clientes +- Autenticam com `cliente.demo@sistema.dev`. +- Abrem tickets para si mesmos a partir do portal com assunto/descrição obrigatórios. +- Não visualizam campo de responsável nem tickets de outros usuários. + +## Próximos passos sugeridos +1. Finalizar redefinição de senha/auditoria de convites Better Auth. +2. Expandir cobertura de testes (`vitest`) para guardas de autenticação e criação de tickets. +3. Implementar ações rápidas (status/fila) diretamente na listagem de tickets. +4. Definir limites e monitoramento para anexos por tenant. + +## Rotina antes de abrir PR +- `pnpm --dir "web" lint` +- `pnpm --dir "web" exec vitest run` +- Revisar toasts/labels em PT-BR e ausência de segredos no diff. +- Adicionar coautor `factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>` quando aplicável. + +## Convenções +- Convex deve retornar apenas tipos primitivos; converta datas via mappers em `src/lib/mappers`. +- Manter textos em PT-BR e evitar comentários supérfluos no código. +- Reutilizar componentes shadcn existentes e seguir o estilo do arquivo editado. +- Validações client-side críticas devem sinalizar erros inline e exibir toast. + +## Estrutura útil +- `web/convex/` — queries e mutations (ex.: `tickets.ts`, `users.ts`). +- `web/src/components/tickets/` — UI interna (dialog, listas, header, timeline). +- `web/src/components/portal/` — formulários e fluxos do portal do cliente. +- `web/scripts/` — seeds Better Auth e utilidades. +- `web/src/components/auth/auth-guard.tsx` — proteção de rotas client-side. + +## Histórico resumido +- Scaffold Next.js + Turbopack configurado com Better Auth e Convex. +- Portal do cliente entregue com isolamento por `viewerId`. +- Fluxo de convites e painel administrativo operacionais. +- Iteração atual focada em UX de criação de tickets, consistência de layout e guardas de sessão. diff --git a/web/convex/seed.ts b/web/convex/seed.ts index f6e9ff3..e83d0b4 100644 --- a/web/convex/seed.ts +++ b/web/convex/seed.ts @@ -43,9 +43,10 @@ export const seedDemo = mutation({ if (found) return found._id; return await ctx.db.insert("users", { tenantId, name, email, role, avatarUrl: `https://avatar.vercel.sh/${name.split(" ")[0]}` }); } - const anaId = await ensureUser("Ana Souza", "ana.souza@example.com"); - const brunoId = await ensureUser("Bruno Lima", "bruno.lima@example.com"); + const reverId = await ensureUser("Rever", "renan.pac@paulicon.com.br"); + const agenteDemoId = await ensureUser("Agente Demo", "agente.demo@sistema.dev"); const eduardaId = await ensureUser("Eduarda Rocha", "eduarda.rocha@example.com", "CUSTOMER"); + const clienteDemoId = await ensureUser("Cliente Demo", "cliente.demo@sistema.dev", "CUSTOMER"); // Seed a couple of tickets const now = Date.now(); @@ -68,7 +69,7 @@ export const seedDemo = mutation({ channel: "EMAIL", queueId: queue1, requesterId: eduardaId, - assigneeId: anaId, + assigneeId: reverId, createdAt: now - 1000 * 60 * 60 * 5, updatedAt: now - 1000 * 60 * 10, tags: ["portal", "cliente"], @@ -84,8 +85,8 @@ export const seedDemo = mutation({ priority: "HIGH", channel: "WHATSAPP", queueId: queue2, - requesterId: eduardaId, - assigneeId: brunoId, + requesterId: clienteDemoId, + assigneeId: agenteDemoId, createdAt: now - 1000 * 60 * 60 * 8, updatedAt: now - 1000 * 60 * 30, tags: ["Integração", "erp"], diff --git a/web/package.json b/web/package.json index 40ba937..c509bfb 100644 --- a/web/package.json +++ b/web/package.json @@ -48,6 +48,7 @@ "lucide-react": "^0.544.0", "next": "15.5.4", "next-themes": "^0.4.6", + "postcss": "^8.5.6", "react": "19.2.0", "react-dom": "19.2.0", "react-hook-form": "^7.64.0", diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 40dc837..f2f4efb 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -113,6 +113,9 @@ importers: next-themes: specifier: ^0.4.6 version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + postcss: + specifier: ^8.5.6 + version: 8.5.6 react: specifier: 19.2.0 version: 19.2.0 diff --git a/web/scripts/reassign-legacy-assignees.mjs b/web/scripts/reassign-legacy-assignees.mjs new file mode 100644 index 0000000..84c4752 --- /dev/null +++ b/web/scripts/reassign-legacy-assignees.mjs @@ -0,0 +1,87 @@ +import { ConvexHttpClient } from "convex/browser" + +const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL +const TENANT_ID = process.env.SEED_TENANT_ID ?? "tenant-atlas" + +if (!CONVEX_URL) { + console.error("NEXT_PUBLIC_CONVEX_URL não definido. Configure o endpoint do Convex e execute novamente.") + process.exit(1) +} + +const TARGET_NAMES = new Set(["Ana Souza", "Bruno Lima"]) +const REPLACEMENT = { + name: "Rever", + email: "renan.pac@paulicon.com.br", +} + +async function main() { + const client = new ConvexHttpClient(CONVEX_URL) + + const admin = await client.mutation("users:ensureUser", { + tenantId: TENANT_ID, + email: "admin@sistema.dev", + name: "Administrador", + role: "ADMIN", + }) + + if (!admin?._id) { + throw new Error("Não foi possível garantir o usuário administrador") + } + + const replacementUser = await client.mutation("users:ensureUser", { + tenantId: TENANT_ID, + email: REPLACEMENT.email, + name: REPLACEMENT.name, + role: "AGENT", + }) + + if (!replacementUser?._id) { + throw new Error("Não foi possível garantir o usuário Rever") + } + + const agents = await client.query("users:listAgents", { tenantId: TENANT_ID }) + const targets = agents.filter((agent) => TARGET_NAMES.has(agent.name)) + + if (targets.length === 0) { + console.log("Nenhum responsável legado encontrado. Nada a atualizar.") + } + + const targetIds = new Set(targets.map((agent) => agent._id)) + + const tickets = await client.query("tickets:list", { + tenantId: TENANT_ID, + viewerId: admin._id, + }) + + let reassignedCount = 0 + for (const ticket of tickets) { + if (ticket.assignee && targetIds.has(ticket.assignee.id)) { + await client.mutation("tickets:changeAssignee", { + ticketId: ticket.id, + assigneeId: replacementUser._id, + actorId: admin._id, + }) + reassignedCount += 1 + console.log(`Ticket ${ticket.reference} reatribuído para ${replacementUser.name}`) + } + } + + for (const agent of targets) { + try { + await client.mutation("users:deleteUser", { + userId: agent._id, + actorId: admin._id, + }) + console.log(`Usuário removido: ${agent.name}`) + } catch (error) { + console.error(`Falha ao remover ${agent.name}:`, error) + } + } + + console.log(`Total de tickets reatribuídos: ${reassignedCount}`) +} + +main().catch((error) => { + console.error("Erro ao reatribuir responsáveis legacy:", error) + process.exitCode = 1 +}) diff --git a/web/scripts/seed-auth.mjs b/web/scripts/seed-auth.mjs index 7e76634..eaa43e9 100644 --- a/web/scripts/seed-auth.mjs +++ b/web/scripts/seed-auth.mjs @@ -4,13 +4,43 @@ import { hashPassword } from "better-auth/crypto" const { PrismaClient } = pkg const prisma = new PrismaClient() -const email = process.env.SEED_USER_EMAIL ?? "admin@sistema.dev" -const password = process.env.SEED_USER_PASSWORD ?? "admin123" -const name = process.env.SEED_USER_NAME ?? "Administrador" -const role = process.env.SEED_USER_ROLE ?? "admin" const tenantId = process.env.SEED_USER_TENANT ?? "tenant-atlas" -async function main() { +const singleUserFromEnv = process.env.SEED_USER_EMAIL + ? [{ + email: process.env.SEED_USER_EMAIL, + password: process.env.SEED_USER_PASSWORD ?? "admin123", + name: process.env.SEED_USER_NAME ?? "Administrador", + role: process.env.SEED_USER_ROLE ?? "admin", + tenantId, + }] + : null + +const defaultUsers = singleUserFromEnv ?? [ + { + email: "admin@sistema.dev", + password: "admin123", + name: "Administrador", + role: "admin", + tenantId, + }, + { + email: "agente.demo@sistema.dev", + password: "agent123", + name: "Agente Demo", + role: "agent", + tenantId, + }, + { + email: "cliente.demo@sistema.dev", + password: "cliente123", + name: "Cliente Demo", + role: "customer", + tenantId, + }, +] + +async function upsertAuthUser({ email, password, name, role, tenantId: userTenant }: (typeof defaultUsers)[number]) { const hashedPassword = await hashPassword(password) const user = await prisma.authUser.upsert({ @@ -18,13 +48,13 @@ async function main() { update: { name, role, - tenantId, + tenantId: userTenant, }, create: { email, name, role, - tenantId, + tenantId: userTenant, accounts: { create: { providerId: "credential", @@ -79,7 +109,13 @@ async function main() { console.log(` Role: ${user.role}`) console.log(` Tenant: ${user.tenantId ?? "(nenhum)"}`) console.log(` Provider: ${account?.providerId ?? "-"}`) - console.log(`Senha provisoria: ${password}`) + console.log(` Senha provisoria: ${password}`) +} + +async function main() { + for (const user of defaultUsers) { + await upsertAuthUser(user) + } } main() diff --git a/web/src/app/tickets/[id]/page.tsx b/web/src/app/tickets/[id]/page.tsx index 98bacb4..40286c6 100644 --- a/web/src/app/tickets/[id]/page.tsx +++ b/web/src/app/tickets/[id]/page.tsx @@ -2,6 +2,7 @@ import { AppShell } from "@/components/app-shell" import { SiteHeader } from "@/components/site-header" import { TicketDetailView } from "@/components/tickets/ticket-detail-view" import { TicketDetailStatic } from "@/components/tickets/ticket-detail-static" +import { NewTicketDialog } from "@/components/tickets/new-ticket-dialog" import { getTicketById } from "@/lib/mocks/tickets" import type { TicketWithDetails } from "@/lib/schemas/ticket" @@ -21,7 +22,7 @@ export default async function TicketDetailPage({ params }: TicketDetailPageProps title={`Ticket #${id}`} lead={"Detalhes do ticket"} secondaryAction={Compartilhar} - primaryAction={Adicionar comentário} + primaryAction={} /> } > diff --git a/web/src/app/tickets/new/page.tsx b/web/src/app/tickets/new/page.tsx index a38b4bf..57e495b 100644 --- a/web/src/app/tickets/new/page.tsx +++ b/web/src/app/tickets/new/page.tsx @@ -14,7 +14,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" -import { RichTextEditor } from "@/components/ui/rich-text-editor" +import { RichTextEditor, sanitizeEditorHtml } from "@/components/ui/rich-text-editor" import { Spinner } from "@/components/ui/spinner" import { Badge } from "@/components/ui/badge" import { cn } from "@/lib/utils" @@ -59,6 +59,7 @@ export default function NewTicketPage() { const [subcategoryId, setSubcategoryId] = useState(null) const [categoryError, setCategoryError] = useState(null) const [subcategoryError, setSubcategoryError] = useState(null) + const [descriptionError, setDescriptionError] = useState(null) const [assigneeInitialized, setAssigneeInitialized] = useState(false) const queueOptions = useMemo(() => queues.map((q) => q.name), [queues]) @@ -91,6 +92,14 @@ export default function NewTicketPage() { return } + const sanitizedDescription = sanitizeEditorHtml(description) + const plainDescription = sanitizedDescription.replace(/<[^>]*>/g, "").trim() + if (plainDescription.length === 0) { + setDescriptionError("Descreva o contexto do chamado.") + return + } + setDescriptionError(null) + setLoading(true) toast.loading("Criando ticket...", { id: "create-ticket" }) try { @@ -110,13 +119,12 @@ export default function NewTicketPage() { categoryId: categoryId as Id<"ticketCategories">, subcategoryId: subcategoryId as Id<"ticketSubcategories">, }) - const plainDescription = description.replace(/<[^>]*>/g, "").trim() if (plainDescription.length > 0) { await addComment({ ticketId: id as Id<"tickets">, authorId: convexUserId as Id<"users">, visibility: "PUBLIC", - body: description, + body: sanitizedDescription, attachments: [], }) } @@ -143,8 +151,8 @@ export default function NewTicketPage() {
-
- - + + { + setDescription(html) + if (descriptionError) { + const plain = html.replace(/<[^>]*>/g, "").trim() + if (plain.length > 0) { + setDescriptionError(null) + } + } + }} + placeholder="Detalhe o problema, passos para reproduzir, links, etc." + /> + {descriptionError ?

{descriptionError}

: null}
{categoryError || subcategoryError ? (
diff --git a/web/src/components/portal/portal-ticket-form.tsx b/web/src/components/portal/portal-ticket-form.tsx index d2e2419..e002042 100644 --- a/web/src/components/portal/portal-ticket-form.tsx +++ b/web/src/components/portal/portal-ticket-form.tsx @@ -52,8 +52,8 @@ export function PortalTicketForm() { const [isSubmitting, setIsSubmitting] = useState(false) const isFormValid = useMemo(() => { - return Boolean(subject.trim() && categoryId && subcategoryId) - }, [subject, categoryId, subcategoryId]) + return Boolean(subject.trim() && description.trim() && categoryId && subcategoryId) + }, [subject, description, categoryId, subcategoryId]) async function handleSubmit(event: React.FormEvent) { event.preventDefault() @@ -61,6 +61,7 @@ export function PortalTicketForm() { const trimmedSubject = subject.trim() const trimmedSummary = summary.trim() + const trimmedDescription = description.trim() setIsSubmitting(true) toast.loading("Abrindo chamado...", { id: "portal-new-ticket" }) @@ -78,8 +79,8 @@ export function PortalTicketForm() { subcategoryId: subcategoryId as Id<"ticketSubcategories">, }) - if (description.trim().length > 0) { - const htmlBody = sanitizeEditorHtml(toHtml(description.trim())) + if (trimmedDescription.length > 0) { + const htmlBody = sanitizeEditorHtml(toHtml(trimmedDescription)) await addComment({ ticketId: id as Id<"tickets">, authorId: convexUserId as Id<"users">, @@ -108,8 +109,8 @@ export function PortalTicketForm() {
-
-