From 1cccb852a50039c86d12c9b4e07a540ee59a4e92 Mon Sep 17 00:00:00 2001 From: Esdras Renan Date: Mon, 6 Oct 2025 22:59:35 -0300 Subject: [PATCH] chore: reorganize project structure and ensure default queues --- .gitignore | 52 +- web/agents.md => PROXIMOS_PASSOS.md | 4 + README.md | 69 ++ agents.md | 51 +- web/auth.ts => auth.ts | 0 web/build.log => build.log | 2 +- web/components.json => components.json | 0 convex/README.md | 90 +++ {web/convex => convex}/_generated/api.d.ts | 0 {web/convex => convex}/_generated/api.js | 0 .../_generated/dataModel.d.ts | 0 {web/convex => convex}/_generated/server.d.ts | 0 {web/convex => convex}/_generated/server.js | 0 {web/convex => convex}/bootstrap.ts | 0 {web/convex => convex}/categories.ts | 0 {web/convex => convex}/commentTemplates.ts | 0 {web/convex => convex}/convex.config.ts | 0 {web/convex => convex}/fields.ts | 0 {web/convex => convex}/files.ts | 0 {web/convex => convex}/invites.ts | 0 {web/convex => convex}/migrations.ts | 0 {web/convex => convex}/queues.ts | 2 +- {web/convex => convex}/rbac.ts | 0 {web/convex => convex}/reports.ts | 0 {web/convex => convex}/schema.ts | 0 {web/convex => convex}/seed.ts | 0 {web/convex => convex}/slas.ts | 0 {web/convex => convex}/teams.ts | 0 {web/convex => convex}/tickets.ts | 9 +- convex/tsconfig.json | 25 + {web/convex => convex}/users.ts | 0 web/eslint.config.mjs => eslint.config.mjs | 0 globals.css | 683 ------------------ web/middleware.ts => middleware.ts | 0 web/next.config.ts => next.config.ts | 0 web/package.json => package.json | 4 +- web/pnpm-lock.yaml => pnpm-lock.yaml | 3 + web/postcss.config.mjs => postcss.config.mjs | 0 .../20251005183834_init/migration.sql | 0 .../migration.sql | 0 .../migrations/migration_lock.toml | 0 {web/prisma => prisma}/schema.prisma | 0 {web/public => public}/file.svg | 0 {web/public => public}/globe.svg | 0 {web/public => public}/next.svg | 0 {web/public => public}/rever-8.png | Bin {web/public => public}/vercel.svg | 0 {web/public => public}/window.svg | 0 {web/scripts => scripts}/debug-convex.mjs | 1 + scripts/ensure-default-queues.mjs | 73 ++ .../import-convex-to-prisma.mjs | 1 + .../reassign-legacy-assignees.mjs | 1 + {web/scripts => scripts}/seed-agents.mjs | 1 + {web/scripts => scripts}/seed-auth.mjs | 3 +- .../sync-prisma-to-convex.mjs | 1 + {web/src => src}/app/ConvexClientProvider.tsx | 0 {web/src => src}/app/admin/channels/page.tsx | 0 {web/src => src}/app/admin/fields/page.tsx | 0 {web/src => src}/app/admin/layout.tsx | 0 {web/src => src}/app/admin/page.tsx | 0 {web/src => src}/app/admin/slas/page.tsx | 0 {web/src => src}/app/admin/teams/page.tsx | 0 .../app/api/admin/invites/[id]/route.ts | 0 .../app/api/admin/invites/route.ts | 0 {web/src => src}/app/api/admin/users/route.ts | 0 .../app/api/auth/[...all]/route.ts | 0 .../app/api/invites/[token]/route.ts | 0 {web/src => src}/app/dashboard/data.json | 0 {web/src => src}/app/dashboard/page.tsx | 0 {web/src => src}/app/dev/seed/page.tsx | 0 {web/src => src}/app/favicon.ico | Bin {web/src => src}/app/globals.css | 0 {web/src => src}/app/invite/[token]/page.tsx | 0 {web/src => src}/app/layout.tsx | 0 {web/src => src}/app/login/page.tsx | 0 {web/src => src}/app/page.tsx | 0 {web/src => src}/app/play/page.tsx | 0 {web/src => src}/app/portal/layout.tsx | 0 {web/src => src}/app/portal/page.tsx | 0 .../app/portal/tickets/[id]/page.tsx | 0 .../app/portal/tickets/new/page.tsx | 0 {web/src => src}/app/portal/tickets/page.tsx | 0 {web/src => src}/app/reports/backlog/page.tsx | 0 {web/src => src}/app/reports/csat/page.tsx | 0 {web/src => src}/app/reports/sla/page.tsx | 0 {web/src => src}/app/settings/page.tsx | 0 .../app/settings/templates/page.tsx | 0 {web/src => src}/app/tickets/[id]/page.tsx | 0 {web/src => src}/app/tickets/new/page.tsx | 0 {web/src => src}/app/tickets/page.tsx | 0 .../app/tickets/tickets-page-client.tsx | 0 .../components/admin/admin-users-manager.tsx | 0 .../admin/categories/categories-manager.tsx | 0 .../admin/fields/fields-manager.tsx | 0 .../admin/queues/queues-manager.tsx | 2 + .../components/admin/slas/slas-manager.tsx | 0 .../components/admin/teams/teams-manager.tsx | 0 {web/src => src}/components/app-shell.tsx | 0 {web/src => src}/components/app-sidebar.tsx | 0 .../components/auth/auth-guard.tsx | 0 .../background-paper-shaders-wrapper.tsx | 0 .../components/background-paper-shaders.tsx | 0 .../components/chart-area-interactive.tsx | 0 {web/src => src}/components/data-table.tsx | 0 .../components/invite/invite-accept-form.tsx | 0 {web/src => src}/components/login-form.tsx | 0 {web/src => src}/components/nav-documents.tsx | 0 {web/src => src}/components/nav-main.tsx | 0 {web/src => src}/components/nav-secondary.tsx | 0 {web/src => src}/components/nav-user.tsx | 0 {web/src => src}/components/page-header.tsx | 0 .../components/portal/portal-shell.tsx | 0 .../components/portal/portal-ticket-card.tsx | 0 .../portal/portal-ticket-detail.tsx | 0 .../components/portal/portal-ticket-form.tsx | 0 .../components/portal/portal-ticket-list.tsx | 0 .../components/reports/backlog-report.tsx | 0 .../components/reports/csat-report.tsx | 0 .../components/reports/sla-report.tsx | 0 {web/src => src}/components/search-form.tsx | 0 {web/src => src}/components/section-cards.tsx | 0 .../settings/comment-templates-manager.tsx | 0 .../components/settings/settings-content.tsx | 0 {web/src => src}/components/site-header.tsx | 0 .../components/tickets/category-select.tsx | 0 .../tickets/delete-ticket-dialog.tsx | 0 .../components/tickets/new-ticket-dialog.tsx | 3 + .../tickets/play-next-ticket-card.tsx | 0 .../components/tickets/priority-pill.tsx | 0 .../components/tickets/priority-select.tsx | 0 .../tickets/recent-tickets-panel.tsx | 0 .../components/tickets/status-badge.tsx | 0 .../components/tickets/status-select.tsx | 0 .../tickets/ticket-comments.rich.tsx | 0 .../tickets/ticket-detail-static.tsx | 0 .../components/tickets/ticket-detail-view.tsx | 0 .../tickets/ticket-details-panel.tsx | 0 .../tickets/ticket-queue-summary.tsx | 0 .../tickets/ticket-summary-header.tsx | 50 +- .../components/tickets/ticket-timeline.tsx | 0 .../components/tickets/tickets-filters.tsx | 0 .../components/tickets/tickets-table.tsx | 0 .../components/tickets/tickets-view.tsx | 4 +- {web/src => src}/components/ui/avatar.tsx | 0 .../ui/background-paper-shaders.tsx | 0 {web/src => src}/components/ui/badge.tsx | 0 {web/src => src}/components/ui/breadcrumb.tsx | 0 {web/src => src}/components/ui/button.tsx | 0 {web/src => src}/components/ui/card.tsx | 0 {web/src => src}/components/ui/chart.tsx | 0 {web/src => src}/components/ui/checkbox.tsx | 0 {web/src => src}/components/ui/dialog.tsx | 0 {web/src => src}/components/ui/drawer.tsx | 0 .../components/ui/dropdown-menu.tsx | 0 {web/src => src}/components/ui/dropzone.tsx | 0 {web/src => src}/components/ui/empty.tsx | 0 {web/src => src}/components/ui/field.tsx | 0 {web/src => src}/components/ui/input.tsx | 0 {web/src => src}/components/ui/label.tsx | 0 {web/src => src}/components/ui/popover.tsx | 0 {web/src => src}/components/ui/progress.tsx | 0 .../ui/raycast-animated-black-background.tsx | 0 .../components/ui/rich-text-editor.tsx | 0 {web/src => src}/components/ui/select.tsx | 0 {web/src => src}/components/ui/separator.tsx | 0 {web/src => src}/components/ui/sheet.tsx | 0 {web/src => src}/components/ui/sidebar.tsx | 0 {web/src => src}/components/ui/skeleton.tsx | 0 {web/src => src}/components/ui/sonner.tsx | 0 {web/src => src}/components/ui/spinner.tsx | 0 {web/src => src}/components/ui/table.tsx | 0 {web/src => src}/components/ui/tabs.tsx | 0 {web/src => src}/components/ui/textarea.tsx | 0 .../components/ui/toggle-group.tsx | 0 {web/src => src}/components/ui/toggle.tsx | 0 {web/src => src}/components/ui/tooltip.tsx | 0 .../components/version-switcher.tsx | 0 {web/src => src}/convex/_generated/api.ts | 0 src/hooks/use-default-queues.ts | 21 + {web/src => src}/hooks/use-mobile.ts | 0 .../hooks/use-ticket-categories.ts | 0 {web/src => src}/lib/auth-client.tsx | 0 {web/src => src}/lib/auth-server.ts | 0 {web/src => src}/lib/auth.ts | 0 {web/src => src}/lib/authz.ts | 0 {web/src => src}/lib/constants.ts | 0 {web/src => src}/lib/env.ts | 0 .../lib/mappers/__tests__/ticket.test.ts | 0 {web/src => src}/lib/mappers/ticket.ts | 0 {web/src => src}/lib/mocks/tickets.ts | 0 {web/src => src}/lib/prisma.ts | 0 {web/src => src}/lib/schemas/category.ts | 0 {web/src => src}/lib/schemas/ticket.ts | 0 {web/src => src}/lib/utils.ts | 0 .../server/__tests__/invite-utils.test.ts | 0 {web/src => src}/server/invite-utils.ts | 0 web/tsconfig.json => tsconfig.json | 0 web/vitest.config.ts => vitest.config.ts | 0 web/vitest.setup.ts => vitest.setup.ts | 0 web/.gitignore | 42 -- web/README.md | 58 -- 201 files changed, 417 insertions(+), 838 deletions(-) rename web/agents.md => PROXIMOS_PASSOS.md (93%) create mode 100644 README.md rename web/auth.ts => auth.ts (100%) rename web/build.log => build.log (99%) rename web/components.json => components.json (100%) create mode 100644 convex/README.md rename {web/convex => convex}/_generated/api.d.ts (100%) rename {web/convex => convex}/_generated/api.js (100%) rename {web/convex => convex}/_generated/dataModel.d.ts (100%) rename {web/convex => convex}/_generated/server.d.ts (100%) rename {web/convex => convex}/_generated/server.js (100%) rename {web/convex => convex}/bootstrap.ts (100%) rename {web/convex => convex}/categories.ts (100%) rename {web/convex => convex}/commentTemplates.ts (100%) rename {web/convex => convex}/convex.config.ts (100%) rename {web/convex => convex}/fields.ts (100%) rename {web/convex => convex}/files.ts (100%) rename {web/convex => convex}/invites.ts (100%) rename {web/convex => convex}/migrations.ts (100%) rename {web/convex => convex}/queues.ts (99%) rename {web/convex => convex}/rbac.ts (100%) rename {web/convex => convex}/reports.ts (100%) rename {web/convex => convex}/schema.ts (100%) rename {web/convex => convex}/seed.ts (100%) rename {web/convex => convex}/slas.ts (100%) rename {web/convex => convex}/teams.ts (100%) rename {web/convex => convex}/tickets.ts (99%) create mode 100644 convex/tsconfig.json rename {web/convex => convex}/users.ts (100%) rename web/eslint.config.mjs => eslint.config.mjs (100%) delete mode 100644 globals.css rename web/middleware.ts => middleware.ts (100%) rename web/next.config.ts => next.config.ts (100%) rename web/package.json => package.json (94%) rename web/pnpm-lock.yaml => pnpm-lock.yaml (99%) rename web/postcss.config.mjs => postcss.config.mjs (100%) rename {web/prisma => prisma}/migrations/20251005183834_init/migration.sql (100%) rename {web/prisma => prisma}/migrations/20251006235816_add_companies/migration.sql (100%) rename {web/prisma => prisma}/migrations/migration_lock.toml (100%) rename {web/prisma => prisma}/schema.prisma (100%) rename {web/public => public}/file.svg (100%) rename {web/public => public}/globe.svg (100%) rename {web/public => public}/next.svg (100%) rename {web/public => public}/rever-8.png (100%) rename {web/public => public}/vercel.svg (100%) rename {web/public => public}/window.svg (100%) rename {web/scripts => scripts}/debug-convex.mjs (97%) create mode 100644 scripts/ensure-default-queues.mjs rename {web/scripts => scripts}/import-convex-to-prisma.mjs (99%) rename {web/scripts => scripts}/reassign-legacy-assignees.mjs (99%) rename {web/scripts => scripts}/seed-agents.mjs (99%) rename {web/scripts => scripts}/seed-auth.mjs (98%) rename {web/scripts => scripts}/sync-prisma-to-convex.mjs (99%) rename {web/src => src}/app/ConvexClientProvider.tsx (100%) rename {web/src => src}/app/admin/channels/page.tsx (100%) rename {web/src => src}/app/admin/fields/page.tsx (100%) rename {web/src => src}/app/admin/layout.tsx (100%) rename {web/src => src}/app/admin/page.tsx (100%) rename {web/src => src}/app/admin/slas/page.tsx (100%) rename {web/src => src}/app/admin/teams/page.tsx (100%) rename {web/src => src}/app/api/admin/invites/[id]/route.ts (100%) rename {web/src => src}/app/api/admin/invites/route.ts (100%) rename {web/src => src}/app/api/admin/users/route.ts (100%) rename {web/src => src}/app/api/auth/[...all]/route.ts (100%) rename {web/src => src}/app/api/invites/[token]/route.ts (100%) rename {web/src => src}/app/dashboard/data.json (100%) rename {web/src => src}/app/dashboard/page.tsx (100%) rename {web/src => src}/app/dev/seed/page.tsx (100%) rename {web/src => src}/app/favicon.ico (100%) rename {web/src => src}/app/globals.css (100%) rename {web/src => src}/app/invite/[token]/page.tsx (100%) rename {web/src => src}/app/layout.tsx (100%) rename {web/src => src}/app/login/page.tsx (100%) rename {web/src => src}/app/page.tsx (100%) rename {web/src => src}/app/play/page.tsx (100%) rename {web/src => src}/app/portal/layout.tsx (100%) rename {web/src => src}/app/portal/page.tsx (100%) rename {web/src => src}/app/portal/tickets/[id]/page.tsx (100%) rename {web/src => src}/app/portal/tickets/new/page.tsx (100%) rename {web/src => src}/app/portal/tickets/page.tsx (100%) rename {web/src => src}/app/reports/backlog/page.tsx (100%) rename {web/src => src}/app/reports/csat/page.tsx (100%) rename {web/src => src}/app/reports/sla/page.tsx (100%) rename {web/src => src}/app/settings/page.tsx (100%) rename {web/src => src}/app/settings/templates/page.tsx (100%) rename {web/src => src}/app/tickets/[id]/page.tsx (100%) rename {web/src => src}/app/tickets/new/page.tsx (100%) rename {web/src => src}/app/tickets/page.tsx (100%) rename {web/src => src}/app/tickets/tickets-page-client.tsx (100%) rename {web/src => src}/components/admin/admin-users-manager.tsx (100%) rename {web/src => src}/components/admin/categories/categories-manager.tsx (100%) rename {web/src => src}/components/admin/fields/fields-manager.tsx (100%) rename {web/src => src}/components/admin/queues/queues-manager.tsx (99%) rename {web/src => src}/components/admin/slas/slas-manager.tsx (100%) rename {web/src => src}/components/admin/teams/teams-manager.tsx (100%) rename {web/src => src}/components/app-shell.tsx (100%) rename {web/src => src}/components/app-sidebar.tsx (100%) rename {web/src => src}/components/auth/auth-guard.tsx (100%) rename {web/src => src}/components/background-paper-shaders-wrapper.tsx (100%) rename {web/src => src}/components/background-paper-shaders.tsx (100%) rename {web/src => src}/components/chart-area-interactive.tsx (100%) rename {web/src => src}/components/data-table.tsx (100%) rename {web/src => src}/components/invite/invite-accept-form.tsx (100%) rename {web/src => src}/components/login-form.tsx (100%) rename {web/src => src}/components/nav-documents.tsx (100%) rename {web/src => src}/components/nav-main.tsx (100%) rename {web/src => src}/components/nav-secondary.tsx (100%) rename {web/src => src}/components/nav-user.tsx (100%) rename {web/src => src}/components/page-header.tsx (100%) rename {web/src => src}/components/portal/portal-shell.tsx (100%) rename {web/src => src}/components/portal/portal-ticket-card.tsx (100%) rename {web/src => src}/components/portal/portal-ticket-detail.tsx (100%) rename {web/src => src}/components/portal/portal-ticket-form.tsx (100%) rename {web/src => src}/components/portal/portal-ticket-list.tsx (100%) rename {web/src => src}/components/reports/backlog-report.tsx (100%) rename {web/src => src}/components/reports/csat-report.tsx (100%) rename {web/src => src}/components/reports/sla-report.tsx (100%) rename {web/src => src}/components/search-form.tsx (100%) rename {web/src => src}/components/section-cards.tsx (100%) rename {web/src => src}/components/settings/comment-templates-manager.tsx (100%) rename {web/src => src}/components/settings/settings-content.tsx (100%) rename {web/src => src}/components/site-header.tsx (100%) rename {web/src => src}/components/tickets/category-select.tsx (100%) rename {web/src => src}/components/tickets/delete-ticket-dialog.tsx (100%) rename {web/src => src}/components/tickets/new-ticket-dialog.tsx (99%) rename {web/src => src}/components/tickets/play-next-ticket-card.tsx (100%) rename {web/src => src}/components/tickets/priority-pill.tsx (100%) rename {web/src => src}/components/tickets/priority-select.tsx (100%) rename {web/src => src}/components/tickets/recent-tickets-panel.tsx (100%) rename {web/src => src}/components/tickets/status-badge.tsx (100%) rename {web/src => src}/components/tickets/status-select.tsx (100%) rename {web/src => src}/components/tickets/ticket-comments.rich.tsx (100%) rename {web/src => src}/components/tickets/ticket-detail-static.tsx (100%) rename {web/src => src}/components/tickets/ticket-detail-view.tsx (100%) rename {web/src => src}/components/tickets/ticket-details-panel.tsx (100%) rename {web/src => src}/components/tickets/ticket-queue-summary.tsx (100%) rename {web/src => src}/components/tickets/ticket-summary-header.tsx (93%) rename {web/src => src}/components/tickets/ticket-timeline.tsx (100%) rename {web/src => src}/components/tickets/tickets-filters.tsx (100%) rename {web/src => src}/components/tickets/tickets-table.tsx (100%) rename {web/src => src}/components/tickets/tickets-view.tsx (96%) rename {web/src => src}/components/ui/avatar.tsx (100%) rename {web/src => src}/components/ui/background-paper-shaders.tsx (100%) rename {web/src => src}/components/ui/badge.tsx (100%) rename {web/src => src}/components/ui/breadcrumb.tsx (100%) rename {web/src => src}/components/ui/button.tsx (100%) rename {web/src => src}/components/ui/card.tsx (100%) rename {web/src => src}/components/ui/chart.tsx (100%) rename {web/src => src}/components/ui/checkbox.tsx (100%) rename {web/src => src}/components/ui/dialog.tsx (100%) rename {web/src => src}/components/ui/drawer.tsx (100%) rename {web/src => src}/components/ui/dropdown-menu.tsx (100%) rename {web/src => src}/components/ui/dropzone.tsx (100%) rename {web/src => src}/components/ui/empty.tsx (100%) rename {web/src => src}/components/ui/field.tsx (100%) rename {web/src => src}/components/ui/input.tsx (100%) rename {web/src => src}/components/ui/label.tsx (100%) rename {web/src => src}/components/ui/popover.tsx (100%) rename {web/src => src}/components/ui/progress.tsx (100%) rename {web/src => src}/components/ui/raycast-animated-black-background.tsx (100%) rename {web/src => src}/components/ui/rich-text-editor.tsx (100%) rename {web/src => src}/components/ui/select.tsx (100%) rename {web/src => src}/components/ui/separator.tsx (100%) rename {web/src => src}/components/ui/sheet.tsx (100%) rename {web/src => src}/components/ui/sidebar.tsx (100%) rename {web/src => src}/components/ui/skeleton.tsx (100%) rename {web/src => src}/components/ui/sonner.tsx (100%) rename {web/src => src}/components/ui/spinner.tsx (100%) rename {web/src => src}/components/ui/table.tsx (100%) rename {web/src => src}/components/ui/tabs.tsx (100%) rename {web/src => src}/components/ui/textarea.tsx (100%) rename {web/src => src}/components/ui/toggle-group.tsx (100%) rename {web/src => src}/components/ui/toggle.tsx (100%) rename {web/src => src}/components/ui/tooltip.tsx (100%) rename {web/src => src}/components/version-switcher.tsx (100%) rename {web/src => src}/convex/_generated/api.ts (100%) create mode 100644 src/hooks/use-default-queues.ts rename {web/src => src}/hooks/use-mobile.ts (100%) rename {web/src => src}/hooks/use-ticket-categories.ts (100%) rename {web/src => src}/lib/auth-client.tsx (100%) rename {web/src => src}/lib/auth-server.ts (100%) rename {web/src => src}/lib/auth.ts (100%) rename {web/src => src}/lib/authz.ts (100%) rename {web/src => src}/lib/constants.ts (100%) rename {web/src => src}/lib/env.ts (100%) rename {web/src => src}/lib/mappers/__tests__/ticket.test.ts (100%) rename {web/src => src}/lib/mappers/ticket.ts (100%) rename {web/src => src}/lib/mocks/tickets.ts (100%) rename {web/src => src}/lib/prisma.ts (100%) rename {web/src => src}/lib/schemas/category.ts (100%) rename {web/src => src}/lib/schemas/ticket.ts (100%) rename {web/src => src}/lib/utils.ts (100%) rename {web/src => src}/server/__tests__/invite-utils.test.ts (100%) rename {web/src => src}/server/invite-utils.ts (100%) rename web/tsconfig.json => tsconfig.json (100%) rename web/vitest.config.ts => vitest.config.ts (100%) rename web/vitest.setup.ts => vitest.setup.ts (100%) delete mode 100644 web/.gitignore delete mode 100644 web/README.md diff --git a/.gitignore b/.gitignore index f3b0871..d0a28da 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,45 @@ -# Root ignore for monorepo -web/node_modules/ -web/.next/ -web/.turbo/ -web/out/ -web/.env.local -web/.env* +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc .DS_Store -Thumbs.db +*.pem +*.sqlite + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# backups locais +.archive/ diff --git a/web/agents.md b/PROXIMOS_PASSOS.md similarity index 93% rename from web/agents.md rename to PROXIMOS_PASSOS.md index 435aa56..a5f7ca6 100644 --- a/web/agents.md +++ b/PROXIMOS_PASSOS.md @@ -1,3 +1,7 @@ +# Roadmap de Próximos Passos + +Lista priorizada de evoluções propostas para o Sistema de Chamados. Confira `agents.md` para visão geral, escopo atual e diretrizes de uso. + # 🧩 Permissões e acessos - [x] Criar perfil **Gestor da Empresa (cliente)** com permissões específicas diff --git a/README.md b/README.md new file mode 100644 index 0000000..4ffff10 --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +## Sistema de Chamados + +Aplicação Next.js 15 com Convex e Better Auth para gestão de tickets da Rever. Todo o código-fonte está organizado diretamente na raiz do repositório, conforme convenções do Next.js. + +## Requisitos + +- Node.js >= 20 +- pnpm >= 8 +- CLI do Convex (`pnpm dlx convex dev` instalará automaticamente no primeiro uso) + +## Configuração rápida + +1. Instale as dependências: + ```bash + pnpm install + ``` +2. Ajuste o arquivo `.env` (ou crie a partir do exemplo) e confirme os valores de: + - `NEXT_PUBLIC_CONVEX_URL` (gerado pelo Convex Dev) + - `BETTER_AUTH_SECRET`, `BETTER_AUTH_URL`, `DATABASE_URL` +3. Aplique as migrações e gere o client Prisma: + ```bash + pnpm prisma migrate deploy + pnpm prisma:generate + ``` +4. Popule usuários padrão do Better Auth: + ```bash + pnpm auth:seed + ``` +5. (Opcional) Para re-sincronizar manualmente as filas padrão, execute: + ```bash + pnpm queues:ensure + ``` +6. Em um terminal, execute o backend em tempo real do Convex: + ```bash + pnpm convex:dev + ``` +7. Em outro terminal, suba o frontend Next.js: + ```bash + pnpm dev + ``` +8. Com o Convex ativo, acesse `http://localhost:3000/dev/seed` uma vez para popular dados de demonstração (tickets, usuários, comentários) diretamente no banco do Convex. + +> Se o CLI perguntar sobre configuração do projeto Convex, escolha criar um novo deployment local (opção padrão) e confirme. As credenciais são armazenadas em `.convex/` automaticamente. + +## Scripts úteis + +- `pnpm lint` — ESLint com as regras do projeto. +- `pnpm exec vitest run` — suíte de testes unitários. +- `pnpm auth:seed` — atualiza/cria contas padrão do Better Auth (credenciais em `agents.md`). +- `pnpm prisma migrate deploy` — aplica migrações ao banco SQLite local. +- `pnpm convex:dev` — roda o Convex em modo desenvolvimento, gerando tipos em `convex/_generated`. + +## Estrutura principal + +- `app/` dentro de `src/` — rotas e layouts do Next.js (App Router). +- `components/` — componentes reutilizáveis (UI, formulários, layouts). +- `convex/` — queries, mutations e seeds do Convex. +- `prisma/` — schema, migrações e banco SQLite (`prisma/db.sqlite`). +- `scripts/` — utilitários em Node para sincronização e seeds adicionais. +- `agents.md` — guia operacional e contexto funcional (em PT-BR). +- `PROXIMOS_PASSOS.md` — backlog de melhorias futuras. + +## Credenciais de demonstração + +Após executar `pnpm auth:seed`, as credenciais padrão ficam disponíveis conforme descrito em `agents.md` (seção “Credenciais padrão”). Ajuste variáveis `SEED_USER_*` se precisar sobrepor usuários ou senhas durante o seed. + +## Próximos passos + +Consulte `PROXIMOS_PASSOS.md` para acompanhar o backlog funcional e o progresso das iniciativas planejadas. diff --git a/agents.md b/agents.md index e65b1ab..4369117 100644 --- a/agents.md +++ b/agents.md @@ -1,24 +1,27 @@ -# Plano de Desenvolvimento — Sistema de Chamados +# Plano de Desenvolvimento — Sistema de Chamados + +> **Diretriz máxima:** todas as respostas, comunicações e documentações devem ser redigidas em português brasileiro. ## Contato principal - **Esdras Renan** — monkeyesdras@gmail.com -## 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_*`. +## 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 auth:seed` após configurar `.env`. O script atualiza as contas acima ou cria novas conforme variáveis `SEED_USER_*`. -## 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. +## Sincronização com Convex +- Usuários e tickets demo são garantidos via `convex/seed.ts`. +- Após iniciar `pnpm convex:dev`, acesse `/dev/seed` uma vez por ambiente local para carregar dados reais de demonstração no banco do Convex. -## 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` +## Setup local rápido +1. `pnpm install` +2. Ajuste `.env` (ou crie a partir do exemplo) e confirme `NEXT_PUBLIC_CONVEX_URL` apontando para o Convex local. +3. `pnpm auth:seed` +4. (Opcional) `pnpm queues:ensure` +5. `pnpm convex:dev` +6. Em outro terminal: `pnpm dev` ## Estado atual - Autenticação Better Auth com guardas client-side (`AuthGuard`) bloqueando rotas protegidas. @@ -52,9 +55,9 @@ 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` +## Rotina antes de abrir PR +- `pnpm lint` +- `pnpm 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. @@ -64,12 +67,12 @@ - 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. +## Estrutura útil +- `convex/` — queries e mutations (ex.: `tickets.ts`, `users.ts`). +- `src/components/tickets/` — UI interna (dialog, listas, header, timeline). +- `src/components/portal/` — formulários e fluxos do portal do cliente. +- `scripts/` — seeds Better Auth e utilidades. +- `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. diff --git a/web/auth.ts b/auth.ts similarity index 100% rename from web/auth.ts rename to auth.ts diff --git a/web/build.log b/build.log similarity index 99% rename from web/build.log rename to build.log index 2417e48..5a71460 100644 --- a/web/build.log +++ b/build.log @@ -3,7 +3,7 @@ > next build Ôû▓ Next.js 15.5.3 - - Environments: .env.local + - Environments: .env Creating an optimized production build ... Ô£ô Compiled successfully in 3.0s diff --git a/web/components.json b/components.json similarity index 100% rename from web/components.json rename to components.json diff --git a/convex/README.md b/convex/README.md new file mode 100644 index 0000000..7fda0c3 --- /dev/null +++ b/convex/README.md @@ -0,0 +1,90 @@ +# Welcome to your Convex functions directory! + +Write your Convex functions here. +See https://docs.convex.dev/functions for more. + +A query function that takes two arguments looks like: + +```ts +// convex/myFunctions.ts +import { query } from "./_generated/server"; +import { v } from "convex/values"; + +export const myQueryFunction = query({ + // Validators for arguments. + args: { + first: v.number(), + second: v.string(), + }, + + // Function implementation. + handler: async (ctx, args) => { + // Read the database as many times as you need here. + // See https://docs.convex.dev/database/reading-data. + const documents = await ctx.db.query("tablename").collect(); + + // Arguments passed from the client are properties of the args object. + console.log(args.first, args.second); + + // Write arbitrary JavaScript here: filter, aggregate, build derived data, + // remove non-public properties, or create new objects. + return documents; + }, +}); +``` + +Using this query function in a React component looks like: + +```ts +const data = useQuery(api.myFunctions.myQueryFunction, { + first: 10, + second: "hello", +}); +``` + +A mutation function looks like: + +```ts +// convex/myFunctions.ts +import { mutation } from "./_generated/server"; +import { v } from "convex/values"; + +export const myMutationFunction = mutation({ + // Validators for arguments. + args: { + first: v.string(), + second: v.string(), + }, + + // Function implementation. + handler: async (ctx, args) => { + // Insert or modify documents in the database here. + // Mutations can also read from the database like queries. + // See https://docs.convex.dev/database/writing-data. + const message = { body: args.first, author: args.second }; + const id = await ctx.db.insert("messages", message); + + // Optionally, return a value from your mutation. + return await ctx.db.get(id); + }, +}); +``` + +Using this mutation function in a React component looks like: + +```ts +const mutation = useMutation(api.myFunctions.myMutationFunction); +function handleButtonPress() { + // fire and forget, the most common way to use mutations + mutation({ first: "Hello!", second: "me" }); + // OR + // use the result once the mutation has completed + mutation({ first: "Hello!", second: "me" }).then((result) => + console.log(result), + ); +} +``` + +Use the Convex CLI to push your functions to a deployment. See everything +the Convex CLI can do by running `npx convex -h` in your project root +directory. To learn more, launch the docs with `npx convex docs`. diff --git a/web/convex/_generated/api.d.ts b/convex/_generated/api.d.ts similarity index 100% rename from web/convex/_generated/api.d.ts rename to convex/_generated/api.d.ts diff --git a/web/convex/_generated/api.js b/convex/_generated/api.js similarity index 100% rename from web/convex/_generated/api.js rename to convex/_generated/api.js diff --git a/web/convex/_generated/dataModel.d.ts b/convex/_generated/dataModel.d.ts similarity index 100% rename from web/convex/_generated/dataModel.d.ts rename to convex/_generated/dataModel.d.ts diff --git a/web/convex/_generated/server.d.ts b/convex/_generated/server.d.ts similarity index 100% rename from web/convex/_generated/server.d.ts rename to convex/_generated/server.d.ts diff --git a/web/convex/_generated/server.js b/convex/_generated/server.js similarity index 100% rename from web/convex/_generated/server.js rename to convex/_generated/server.js diff --git a/web/convex/bootstrap.ts b/convex/bootstrap.ts similarity index 100% rename from web/convex/bootstrap.ts rename to convex/bootstrap.ts diff --git a/web/convex/categories.ts b/convex/categories.ts similarity index 100% rename from web/convex/categories.ts rename to convex/categories.ts diff --git a/web/convex/commentTemplates.ts b/convex/commentTemplates.ts similarity index 100% rename from web/convex/commentTemplates.ts rename to convex/commentTemplates.ts diff --git a/web/convex/convex.config.ts b/convex/convex.config.ts similarity index 100% rename from web/convex/convex.config.ts rename to convex/convex.config.ts diff --git a/web/convex/fields.ts b/convex/fields.ts similarity index 100% rename from web/convex/fields.ts rename to convex/fields.ts diff --git a/web/convex/files.ts b/convex/files.ts similarity index 100% rename from web/convex/files.ts rename to convex/files.ts diff --git a/web/convex/invites.ts b/convex/invites.ts similarity index 100% rename from web/convex/invites.ts rename to convex/invites.ts diff --git a/web/convex/migrations.ts b/convex/migrations.ts similarity index 100% rename from web/convex/migrations.ts rename to convex/migrations.ts diff --git a/web/convex/queues.ts b/convex/queues.ts similarity index 99% rename from web/convex/queues.ts rename to convex/queues.ts index 9500dd2..c978b21 100644 --- a/web/convex/queues.ts +++ b/convex/queues.ts @@ -13,6 +13,7 @@ const QUEUE_RENAME_LOOKUP: Record = { "suporte-n2": "Laboratório", laboratorio: "Laboratório", Laboratorio: "Laboratório", + visitas: "Visitas", }; function renameQueueString(value: string) { @@ -201,4 +202,3 @@ export const remove = mutation({ await ctx.db.delete(queueId); }, }); - diff --git a/web/convex/rbac.ts b/convex/rbac.ts similarity index 100% rename from web/convex/rbac.ts rename to convex/rbac.ts diff --git a/web/convex/reports.ts b/convex/reports.ts similarity index 100% rename from web/convex/reports.ts rename to convex/reports.ts diff --git a/web/convex/schema.ts b/convex/schema.ts similarity index 100% rename from web/convex/schema.ts rename to convex/schema.ts diff --git a/web/convex/seed.ts b/convex/seed.ts similarity index 100% rename from web/convex/seed.ts rename to convex/seed.ts diff --git a/web/convex/slas.ts b/convex/slas.ts similarity index 100% rename from web/convex/slas.ts rename to convex/slas.ts diff --git a/web/convex/teams.ts b/convex/teams.ts similarity index 100% rename from web/convex/teams.ts rename to convex/teams.ts diff --git a/web/convex/tickets.ts b/convex/tickets.ts similarity index 99% rename from web/convex/tickets.ts rename to convex/tickets.ts index d84ed87..dfda7aa 100644 --- a/web/convex/tickets.ts +++ b/convex/tickets.ts @@ -46,6 +46,7 @@ const QUEUE_RENAME_LOOKUP: Record = { "suporte-n2": "Laboratório", laboratorio: "Laboratório", Laboratorio: "Laboratório", + visitas: "Visitas", }; function renameQueueString(value?: string | null): string | null { @@ -59,14 +60,14 @@ function renameQueueString(value?: string | null): string | null { function normalizeQueueName(queue?: Doc<"queues"> | null): string | null { if (!queue) return null; const normalized = renameQueueString(queue.name); - if (normalized && normalized !== queue.name) { + if (normalized) { return normalized; } if (queue.slug) { const fromSlug = renameQueueString(queue.slug); if (fromSlug) return fromSlug; } - return normalized ?? queue.name; + return queue.name; } function normalizeTeams(teams?: string[] | null): string[] { @@ -1175,7 +1176,7 @@ export const playNext = mutation({ agentId: v.id("users"), }, handler: async (ctx, { tenantId, queueId, agentId }) => { - const agent = await requireStaff(ctx, agentId, tenantId) + const { user: agent } = await requireStaff(ctx, agentId, tenantId) // Find eligible tickets: not resolved/closed and not assigned let candidates: Doc<"tickets">[] = [] if (queueId) { @@ -1211,7 +1212,7 @@ export const playNext = mutation({ await ctx.db.insert("ticketEvents", { ticketId: chosen._id, type: "ASSIGNEE_CHANGED", - payload: { assigneeId: agentId, assigneeName: agent?.name }, + payload: { assigneeId: agentId, assigneeName: agent.name }, createdAt: now, }); diff --git a/convex/tsconfig.json b/convex/tsconfig.json new file mode 100644 index 0000000..7374127 --- /dev/null +++ b/convex/tsconfig.json @@ -0,0 +1,25 @@ +{ + /* This TypeScript project config describes the environment that + * Convex functions run in and is used to typecheck them. + * You can modify it, but some settings are required to use Convex. + */ + "compilerOptions": { + /* These settings are not required by Convex and can be modified. */ + "allowJs": true, + "strict": true, + "moduleResolution": "Bundler", + "jsx": "react-jsx", + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + + /* These compiler options are required by Convex */ + "target": "ESNext", + "lib": ["ES2021", "dom"], + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "isolatedModules": true, + "noEmit": true + }, + "include": ["./**/*"], + "exclude": ["./_generated"] +} diff --git a/web/convex/users.ts b/convex/users.ts similarity index 100% rename from web/convex/users.ts rename to convex/users.ts diff --git a/web/eslint.config.mjs b/eslint.config.mjs similarity index 100% rename from web/eslint.config.mjs rename to eslint.config.mjs diff --git a/globals.css b/globals.css deleted file mode 100644 index c9d164f..0000000 --- a/globals.css +++ /dev/null @@ -1,683 +0,0 @@ -@import "tailwindcss"; -@import "tw-animate-css"; - -@custom-variant dark (&:is(.dark *)); - -@theme inline { - --animate-scroll: scroll var(--animation-duration, 40s) var(--animation-direction, forwards) linear infinite; - --color-background: var(--background); - --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); - --color-sidebar-ring: var(--sidebar-ring); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar: var(--sidebar); - --color-chart-5: var(--chart-5); - --color-chart-4: var(--chart-4); - --color-chart-3: var(--chart-3); - --color-chart-2: var(--chart-2); - --color-chart-1: var(--chart-1); - --color-ring: var(--ring); - --color-input: var(--input); - --color-border: var(--border); - --color-destructive: var(--destructive); - --color-accent-foreground: var(--accent-foreground); - --color-accent: var(--accent); - --color-muted-foreground: var(--muted-foreground); - --color-muted: var(--muted); - --color-secondary-foreground: var(--secondary-foreground); - --color-secondary: var(--secondary); - --color-primary-foreground: var(--primary-foreground); - --color-primary: var(--primary); - --color-popover-foreground: var(--popover-foreground); - --color-popover: var(--popover); - --color-card-foreground: var(--card-foreground); - --color-card: var(--card); - --radius-sm: calc(var(--radius) - 4px); - --radius-md: calc(var(--radius) - 2px); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) + 4px); - @keyframes moveDot { - 0%, 100% { - top: 10%; - right: 10%; - } - 25% { - top: 10%; - right: calc(100% - 35px); - } - 50% { - top: calc(100% - 30px); - right: calc(100% - 35px); - } - 75% { - top: calc(100% - 30px); - right: 10%; - } - } -} - -:root { - --radius: 0.625rem; - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --chart-1: oklch(0.646 0.222 41.116); - --chart-2: oklch(0.6 0.118 184.704); - --chart-3: oklch(0.398 0.07 227.392); - --chart-4: oklch(0.828 0.189 84.429); - --chart-5: oklch(0.769 0.188 70.08); - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.205 0 0); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); - --chart-green: 142.1 76.2% 36.3%; - --chart-orange: 28.3 92.5% 58.4%; - --chart-red: 0 84.2% 60.2%; -} - -.dark { - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.205 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.205 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.922 0 0); - --primary-foreground: oklch(0.205 0 0); - --secondary: oklch(0.269 0 0); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.269 0 0); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); - --chart-1: oklch(0.488 0.243 264.376); - --chart-2: oklch(0.696 0.17 162.48); - --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); - --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.205 0 0); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.269 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.556 0 0); - --chart-green: 142.1 76.2% 36.3%; - --chart-orange: 28.3 92.5% 58.4%; - --chart-red: 0 84.2% 60.2%; -} - -@keyframes scroll { - to { - transform: translate(calc(-50% - 0.5rem)); - } -} - -@layer base { - * { - @apply border-border outline-ring/50; - } - body { - @apply bg-background text-foreground; - } -} - -@layer utilities { - .animate-ripple { - animation: ripple var(--duration,2s) ease calc(var(--i, 0)*.2s) infinite; - } -} - - @keyframes ripple { - 0%, 100% { - transform: translate(-50%, -50%) scale(1); - } - 50% { - transform: translate(-50%, -50%) scale(0.9); - } - } - - @keyframes bounceIn { - 0% { - opacity: 0; - transform: translateY(30px) scale(0.8); - } - 50% { - opacity: 1; - transform: translateY(-3px) scale(1.02); - } - 70% { - transform: translateY(1px) scale(0.99); - } - 100% { - opacity: 1; - transform: translateY(0) scale(1); - } - } - - /* Previne flash branco em navegação durante scroll */ - .rever-nav [data-slot="navigation-menu-viewport"] { - transition: opacity 150ms ease-in-out; - } - - /* Estabiliza hovers durante scroll suave */ - .smooth-scrolling [data-slot="navigation-menu-trigger"] { - pointer-events: none; - } - - /* Otimizações de performance para scroll suave - excluindo marquee */ - [style*="transform"]:not(.transform-3d):not(.transform-3d *) { - will-change: transform; - transform-style: preserve-3d; - } - - /* Otimização específica para elementos animados - excluindo marquee */ - .motion-div:not(.transform-3d *), - [data-framer-name]:not(.transform-3d *), - [style*="translateX"]:not(.transform-3d *), - [style*="translateY"]:not(.transform-3d *), - [style*="scale"]:not(.transform-3d *) { - will-change: transform; - contain: layout style paint; - backface-visibility: hidden; - } - - /* Container de animações 3D - sem otimizações que interferem no hover */ - .transform-3d { - transform-style: preserve-3d; - } - - /* Garante que shadow-2xl funcione corretamente no 3D marquee */ - .transform-3d img.hover\:shadow-2xl:hover { - box-shadow: 0 25px 50px -12px rgb(0 0 0 / 25%); - } - - /* Otimização para imagens em movimento - excluindo 3D marquee */ - img[style*="transform"]:not(.transform-3d img) { - will-change: transform; - image-rendering: optimizeSpeed; - } - - /* Previne layout shift nos logos da Rever */ - img[alt="Rever Logo"], [alt="Rever Logo"] { - transition: none; /* Remove transições que podem causar flash */ - } - - /* Remove scroll bar visível em todos os dispositivos móveis */ - .no-visible-scrollbar::-webkit-scrollbar { - display: none; - } - - /* Estilos específicos do blog - força texto branco */ - .blog-content { - color: white !important; - } - - .blog-content * { - color: white !important; - } - - .blog-content p { - color: white !important; - } - - .blog-content h1, - .blog-content h2, - .blog-content h3, - .blog-content h4, - .blog-content h5, - .blog-content h6 { - color: white !important; - } - - .blog-content ul, - .blog-content ol, - .blog-content li { - color: white !important; - } - - .blog-content blockquote { - color: white !important; - } - - .blog-content div, - .blog-content span { - color: white !important; - } - - /* Mantém cores específicas para links */ - .blog-content a { - color: #00e8ff !important; - } - - .blog-content a:hover { - color: #00d4e6 !important; - } - - /* Mantém cores específicas para código */ - .blog-content code { - color: #00e8ff !important; - } - - /* Estilos para iframes em conteúdo de blog */ - .blog-content iframe { - max-width: 100% !important; - margin: 2rem auto !important; - display: block !important; - border-radius: 12px !important; - box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.8) !important; - } - - /* Container responsivo para vídeos embarcados */ - .video-embed-container { - position: relative; - width: 100%; - height: 0; - padding-bottom: 56.25%; /* Aspect ratio 16:9 */ - margin: 2rem auto; - border-radius: 12px; - overflow: hidden; - box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.8); - } - - .video-embed-container iframe { - position: absolute !important; - top: 0 !important; - left: 0 !important; - width: 100% !important; - height: 100% !important; - border: 0 !important; - margin: 0 !important; - border-radius: 0 !important; - box-shadow: none !important; - } - .no-visible-scrollbar { - -ms-overflow-style: none; - scrollbar-width: none; - } - - /* Para CategoryFilter - esconde scrollbar em modo horizontal */ - .scrollbar-hide { - -ms-overflow-style: none; /* IE e Edge */ - scrollbar-width: none; /* Firefox */ - } - - .scrollbar-hide::-webkit-scrollbar { - display: none; /* Chrome, Safari e Opera */ - } - -/* CSS para placeholder do TipTap Editor */ -.ProseMirror p.is-editor-empty:first-child::before { - content: attr(data-placeholder); - float: left; - color: #adb5bd; - pointer-events: none; - height: 0; - font-size: 0.875rem; /* Equivalente a text-sm */ -} - -.ProseMirror .is-empty::before { - content: attr(data-placeholder); - float: left; - color: #9ca3af !important; - pointer-events: none; - height: 0; - font-style: italic; - font-weight: normal; -} - -/* Alternativa mais específica */ -.prose .ProseMirror p.is-editor-empty:first-child::before { - content: attr(data-placeholder); - color: #9ca3af !important; - pointer-events: none; - height: 0; - font-style: italic; - font-weight: normal; - position: absolute; -} - -/* Estilos de espaçamento do TipTap Editor */ -.ProseMirror p { - margin-bottom: 1.5rem; /* Equivalente a mb-6 */ -} - -.ProseMirror h1 { - margin-top: 3rem; /* mt-12 */ - margin-bottom: 1.5rem; /* mb-6 */ - font-size: 2.25rem; /* text-4xl */ - font-weight: 700; - line-height: 1.2; -} - -.ProseMirror h2 { - margin-top: 2.5rem; /* mt-10 */ - margin-bottom: 1.25rem; /* mb-5 */ - font-size: 1.875rem; /* text-3xl */ - font-weight: 700; - line-height: 1.25; -} - -.ProseMirror h3 { - margin-top: 2rem; /* mt-8 */ - margin-bottom: 1rem; /* mb-4 */ - font-size: 1.5rem; /* text-2xl */ - font-weight: 700; - line-height: 1.375; -} - -.ProseMirror h4 { - margin-top: 1.5rem; /* mt-6 */ - margin-bottom: 0.75rem; /* mb-3 */ - font-size: 1.25rem; /* text-xl */ - font-weight: 700; -} - -.ProseMirror ul, -.ProseMirror ol { - margin-top: 1.5rem; /* my-6 */ - margin-bottom: 1.5rem; - padding-left: 1.5rem; -} - -.ProseMirror li { - margin-bottom: 0.75rem; /* space-y-3 */ -} - -.ProseMirror blockquote { - margin-top: 2rem; /* my-8 */ - margin-bottom: 2rem; - padding-left: 1.5rem; - border-left: 4px solid #00e8ff; - font-style: italic; - color: #6b7280; -} - -.ProseMirror img, -.ProseMirror [data-type="resizableImage"] { - margin-top: 2rem; /* my-8 */ - margin-bottom: 2rem; - border-radius: 0.75rem; - box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); -} - -.ProseMirror pre { - margin-top: 2rem; /* my-8 */ - margin-bottom: 2rem; - padding: 1rem; - background-color: #111827; - border: 1px solid #374151; - border-radius: 0.5rem; - overflow-x: auto; -} - -.ProseMirror code { - background-color: #1f2937; - color: #00e8ff; - padding: 0.125rem 0.5rem; - border-radius: 0.25rem; - font-size: 0.875rem; - font-family: monospace; -} - -.ProseMirror hr { - margin-top: 2rem; - margin-bottom: 2rem; - border-color: #e5e7eb; -} - -/* Remover margens do primeiro e último elemento */ -.ProseMirror > *:first-child { - margin-top: 0; -} - -.ProseMirror > *:last-child { - margin-bottom: 0; -} - -/* Espaçamento entre imagem e texto */ -.ProseMirror p + [data-type="resizableImage"], -.ProseMirror [data-type="resizableImage"] + p, -.ProseMirror p + img, -.ProseMirror img + p { - margin-top: 2rem; -} - -@tailwind base; -@tailwind components; -@tailwind utilities; - -@layer base { - :root { - --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; - - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; - - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; - - --primary: 222.2 47.4% 11.2%; - --primary-foreground: 210 40% 98%; - - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; - - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; - - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; - - --destructive: 0 84.2% 60.2%; - --destructive-foreground: 210 40% 98%; - - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; - - --radius: 0.5rem; - - --chart-1: hsl(188 100% 50%); /* #00e8ff */ - --chart-2: hsl(220 13% 69%); /* light gray */ - --chart-3: hsl(220 14% 96%); /* lighter gray */ - --chart-4: hsl(221 83% 53%); /* blue */ - --chart-5: hsl(215.4 16.3% 46.9%); /* muted text */ - --chart-green: 142.1 76.2% 36.3%; - --chart-orange: 28.3 92.5% 58.4%; - --chart-red: 0 84.2% 60.2%; - } - - .dark { - --background: 222.2 84% 4.9%; - --foreground: 210 40% 98%; - - --card: 222.2 84% 4.9%; - --card-foreground: 210 40% 98%; - - --popover: 222.2 84% 4.9%; - --popover-foreground: 210 40% 98%; - - --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 11.2%; - - --secondary: 217.2 32.6% 17.5%; - --secondary-foreground: 210 40% 98%; - - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; - - --accent: 217.2 32.6% 17.5%; - --accent-foreground: 210 40% 98%; - - --destructive: 0 62.8% 30.6%; - --destructive-foreground: 210 40% 98%; - - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 212.7 26.8% 83.9%; - - --chart-1: hsl(188 100% 50%); /* #00e8ff */ - --chart-2: hsl(217.2 32.6% 17.5%); - --chart-3: hsl(217.2 32.6% 17.5%); - --chart-4: hsl(210 40% 98%); - --chart-5: 12.1 83.7% 60%; - --chart-green: 142.1 76.2% 36.3%; - --chart-orange: 28.3 92.5% 58.4%; - --chart-red: 0 84.2% 60.2%; - } -} - -/* Estilos específicos para o TipTap Editor */ -.ProseMirror ul { - list-style-type: disc; - list-style-position: outside; - margin-left: 1.5rem; - margin-top: 2rem; - margin-bottom: 2rem; -} - -.ProseMirror ol { - list-style-type: decimal; - list-style-position: outside; - margin-left: 1.5rem; - margin-top: 2rem; - margin-bottom: 2rem; -} - -.ProseMirror li { - margin-top: 0; - margin-bottom: -0.25rem; - padding-left: 0.5rem; - line-height: 1.25; -} - -.ProseMirror p { - margin-bottom: 2rem; - line-height: 1.625; -} - -.ProseMirror hr { - margin-top: 2rem; - margin-bottom: 2rem; - border: none; - border-top: 1px solid #ccc; -} - -/* Garantir espaçamento adequado entre elementos */ -.ProseMirror > * + * { - margin-top: 1rem; -} - -.ProseMirror > ul + *, -.ProseMirror > ol + *, -.ProseMirror > * + ul, -.ProseMirror > * + ol { - margin-top: 2rem; -} - -@layer base { - .shader-surface { - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); - --background: oklch(0 0 0); - --foreground: oklch(1 0 0); - --card: oklch(0.1 0 0 / 0.1); - --card-foreground: oklch(1 0 0); - --popover: oklch(0 0 0 / 0.8); - --popover-foreground: oklch(1 0 0); - --primary: oklch(1 0 0); - --primary-foreground: oklch(0 0 0); - --secondary: oklch(0.65 0.25 25); - --secondary-foreground: oklch(1 0 0); - --muted: oklch(1 0 0 / 0.5); - --muted-foreground: oklch(0.7 0 0); - --accent: oklch(0.65 0.25 25); - --accent-foreground: oklch(1 0 0); - --destructive: oklch(0.6 0.25 15); - --destructive-foreground: oklch(1 0 0); - --border: oklch(1 0 0 / 0.2); - --input: oklch(1 0 0 / 0.1); - --ring: oklch(1 0 0 / 0.3); - --chart-1: oklch(0.65 0.25 25); - --chart-2: oklch(0.8 0.15 85); - --chart-3: oklch(0.7 0.2 140); - --chart-4: oklch(0.7 0.2 240); - --chart-5: oklch(0.6 0.25 300); - --radius: 0.5rem; - --sidebar: oklch(0 0 0 / 0.9); - --sidebar-foreground: oklch(1 0 0); - --sidebar-primary: oklch(0.65 0.25 25); - --sidebar-primary-foreground: oklch(1 0 0); - --sidebar-accent: oklch(0.65 0.25 25); - --sidebar-accent-foreground: oklch(1 0 0); - --sidebar-border: oklch(1 0 0 / 0.2); - --sidebar-ring: oklch(1 0 0 / 0.3); - } - - .shader-surface.dark { - --background: oklch(0 0 0); - --foreground: oklch(1 0 0); - --card: oklch(0.1 0 0 / 0.1); - --card-foreground: oklch(1 0 0); - --popover: oklch(0 0 0 / 0.8); - --popover-foreground: oklch(1 0 0); - --primary: oklch(1 0 0); - --primary-foreground: oklch(0 0 0); - --secondary: oklch(0.65 0.25 25); - --secondary-foreground: oklch(1 0 0); - --muted: oklch(1 0 0 / 0.5); - --muted-foreground: oklch(0.7 0 0); - --accent: oklch(0.65 0.25 25); - --accent-foreground: oklch(1 0 0); - --destructive: oklch(0.6 0.25 15); - --destructive-foreground: oklch(1 0 0); - --border: oklch(1 0 0 / 0.2); - --input: oklch(1 0 0 / 0.1); - --ring: oklch(1 0 0 / 0.3); - --chart-1: oklch(0.65 0.25 25); - --chart-2: oklch(0.8 0.15 85); - --chart-3: oklch(0.7 0.2 140); - --chart-4: oklch(0.7 0.2 240); - --chart-5: oklch(0.6 0.25 300); - --sidebar: oklch(0 0 0 / 0.9); - --sidebar-foreground: oklch(1 0 0); - --sidebar-primary: oklch(0.65 0.25 25); - --sidebar-primary-foreground: oklch(1 0 0); - --sidebar-accent: oklch(0.65 0.25 25); - --sidebar-accent-foreground: oklch(1 0 0); - --sidebar-border: oklch(1 0 0 / 0.2); - --sidebar-ring: oklch(1 0 0 / 0.3); - } -} \ No newline at end of file diff --git a/web/middleware.ts b/middleware.ts similarity index 100% rename from web/middleware.ts rename to middleware.ts diff --git a/web/next.config.ts b/next.config.ts similarity index 100% rename from web/next.config.ts rename to next.config.ts diff --git a/web/package.json b/package.json similarity index 94% rename from web/package.json rename to package.json index c509bfb..f627801 100644 --- a/web/package.json +++ b/package.json @@ -10,7 +10,8 @@ "prisma:generate": "prisma generate", "convex:dev": "convex dev", "test": "vitest", - "auth:seed": "node --env-file=.env.local scripts/seed-auth.mjs" + "auth:seed": "node scripts/seed-auth.mjs", + "queues:ensure": "node scripts/ensure-default-queues.mjs" }, "dependencies": { "@dnd-kit/core": "^6.3.1", @@ -43,6 +44,7 @@ "better-auth": "^1.3.26", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", + "dotenv": "^16.4.5", "convex": "^1.27.3", "date-fns": "^4.1.0", "lucide-react": "^0.544.0", diff --git a/web/pnpm-lock.yaml b/pnpm-lock.yaml similarity index 99% rename from web/pnpm-lock.yaml rename to pnpm-lock.yaml index f2f4efb..a7b9535 100644 --- a/web/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -104,6 +104,9 @@ importers: date-fns: specifier: ^4.1.0 version: 4.1.0 + dotenv: + specifier: ^16.4.5 + version: 16.6.1 lucide-react: specifier: ^0.544.0 version: 0.544.0(react@19.2.0) diff --git a/web/postcss.config.mjs b/postcss.config.mjs similarity index 100% rename from web/postcss.config.mjs rename to postcss.config.mjs diff --git a/web/prisma/migrations/20251005183834_init/migration.sql b/prisma/migrations/20251005183834_init/migration.sql similarity index 100% rename from web/prisma/migrations/20251005183834_init/migration.sql rename to prisma/migrations/20251005183834_init/migration.sql diff --git a/web/prisma/migrations/20251006235816_add_companies/migration.sql b/prisma/migrations/20251006235816_add_companies/migration.sql similarity index 100% rename from web/prisma/migrations/20251006235816_add_companies/migration.sql rename to prisma/migrations/20251006235816_add_companies/migration.sql diff --git a/web/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml similarity index 100% rename from web/prisma/migrations/migration_lock.toml rename to prisma/migrations/migration_lock.toml diff --git a/web/prisma/schema.prisma b/prisma/schema.prisma similarity index 100% rename from web/prisma/schema.prisma rename to prisma/schema.prisma diff --git a/web/public/file.svg b/public/file.svg similarity index 100% rename from web/public/file.svg rename to public/file.svg diff --git a/web/public/globe.svg b/public/globe.svg similarity index 100% rename from web/public/globe.svg rename to public/globe.svg diff --git a/web/public/next.svg b/public/next.svg similarity index 100% rename from web/public/next.svg rename to public/next.svg diff --git a/web/public/rever-8.png b/public/rever-8.png similarity index 100% rename from web/public/rever-8.png rename to public/rever-8.png diff --git a/web/public/vercel.svg b/public/vercel.svg similarity index 100% rename from web/public/vercel.svg rename to public/vercel.svg diff --git a/web/public/window.svg b/public/window.svg similarity index 100% rename from web/public/window.svg rename to public/window.svg diff --git a/web/scripts/debug-convex.mjs b/scripts/debug-convex.mjs similarity index 97% rename from web/scripts/debug-convex.mjs rename to scripts/debug-convex.mjs index 258df55..56df258 100644 --- a/web/scripts/debug-convex.mjs +++ b/scripts/debug-convex.mjs @@ -1,3 +1,4 @@ +import "dotenv/config" import { ConvexHttpClient } from "convex/browser"; const url = process.env.NEXT_PUBLIC_CONVEX_URL; diff --git a/scripts/ensure-default-queues.mjs b/scripts/ensure-default-queues.mjs new file mode 100644 index 0000000..4a029ed --- /dev/null +++ b/scripts/ensure-default-queues.mjs @@ -0,0 +1,73 @@ +import "dotenv/config" +import { ConvexHttpClient } from "convex/browser" + +const tenantId = process.env.SYNC_TENANT_ID || "tenant-atlas" +const convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL + +if (!convexUrl) { + console.error("NEXT_PUBLIC_CONVEX_URL não configurado. Ajuste o .env antes de executar o script.") + process.exit(1) +} + +const DEFAULT_QUEUES = [ + { name: "Chamados" }, + { name: "Laboratório" }, + { name: "Visitas" }, +] + +function slugify(value) { + return value + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") + .replace(/[^\w\s-]/g, "") + .trim() + .replace(/\s+/g, "-") + .replace(/-+/g, "-") + .toLowerCase() +} + +async function main() { + const client = new ConvexHttpClient(convexUrl) + + const agents = await client.query("users:listAgents", { tenantId }) + const admin = + agents.find((user) => (user.role ?? "").toUpperCase() === "ADMIN") ?? + agents[0] + + if (!admin?._id) { + console.error("Nenhum usuário ADMIN encontrado no Convex para criar filas padrão.") + process.exit(1) + } + + const existing = await client.query("queues:list", { + tenantId, + viewerId: admin._id, + }) + + const existingSlugs = new Set(existing.map((queue) => queue.slug)) + const created = [] + + for (const def of DEFAULT_QUEUES) { + const slug = slugify(def.name) + if (existingSlugs.has(slug)) { + continue + } + await client.mutation("queues:create", { + tenantId, + actorId: admin._id, + name: def.name, + }) + created.push(def.name) + } + + if (created.length === 0) { + console.log("Nenhuma fila criada. As filas padrão já existem.") + } else { + console.log(`Filas criadas: ${created.join(", ")}`) + } +} + +main().catch((error) => { + console.error("Falha ao garantir filas padrão", error) + process.exit(1) +}) diff --git a/web/scripts/import-convex-to-prisma.mjs b/scripts/import-convex-to-prisma.mjs similarity index 99% rename from web/scripts/import-convex-to-prisma.mjs rename to scripts/import-convex-to-prisma.mjs index 46ecb53..144485d 100644 --- a/web/scripts/import-convex-to-prisma.mjs +++ b/scripts/import-convex-to-prisma.mjs @@ -1,3 +1,4 @@ +import "dotenv/config" import { PrismaClient } from "@prisma/client" import { ConvexHttpClient } from "convex/browser" diff --git a/web/scripts/reassign-legacy-assignees.mjs b/scripts/reassign-legacy-assignees.mjs similarity index 99% rename from web/scripts/reassign-legacy-assignees.mjs rename to scripts/reassign-legacy-assignees.mjs index 84c4752..7f11f08 100644 --- a/web/scripts/reassign-legacy-assignees.mjs +++ b/scripts/reassign-legacy-assignees.mjs @@ -1,3 +1,4 @@ +import "dotenv/config" import { ConvexHttpClient } from "convex/browser" const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL diff --git a/web/scripts/seed-agents.mjs b/scripts/seed-agents.mjs similarity index 99% rename from web/scripts/seed-agents.mjs rename to scripts/seed-agents.mjs index 5e0732f..d29a8fa 100644 --- a/web/scripts/seed-agents.mjs +++ b/scripts/seed-agents.mjs @@ -1,3 +1,4 @@ +import "dotenv/config" import pkg from "@prisma/client" import { hashPassword } from "better-auth/crypto" import { ConvexHttpClient } from "convex/browser" diff --git a/web/scripts/seed-auth.mjs b/scripts/seed-auth.mjs similarity index 98% rename from web/scripts/seed-auth.mjs rename to scripts/seed-auth.mjs index 7c07245..92a0f72 100644 --- a/web/scripts/seed-auth.mjs +++ b/scripts/seed-auth.mjs @@ -1,3 +1,4 @@ +import "dotenv/config" import pkg from "@prisma/client" import { hashPassword } from "better-auth/crypto" @@ -131,7 +132,7 @@ const defaultUsers = singleUserFromEnv ?? [ }, ] -async function upsertAuthUser({ email, password, name, role, tenantId: userTenant }: (typeof defaultUsers)[number]) { +async function upsertAuthUser({ email, password, name, role, tenantId: userTenant }) { const hashedPassword = await hashPassword(password) const user = await prisma.authUser.upsert({ diff --git a/web/scripts/sync-prisma-to-convex.mjs b/scripts/sync-prisma-to-convex.mjs similarity index 99% rename from web/scripts/sync-prisma-to-convex.mjs rename to scripts/sync-prisma-to-convex.mjs index 6397414..dc37822 100644 --- a/web/scripts/sync-prisma-to-convex.mjs +++ b/scripts/sync-prisma-to-convex.mjs @@ -1,3 +1,4 @@ +import "dotenv/config" import { PrismaClient } from "@prisma/client" import { ConvexHttpClient } from "convex/browser" diff --git a/web/src/app/ConvexClientProvider.tsx b/src/app/ConvexClientProvider.tsx similarity index 100% rename from web/src/app/ConvexClientProvider.tsx rename to src/app/ConvexClientProvider.tsx diff --git a/web/src/app/admin/channels/page.tsx b/src/app/admin/channels/page.tsx similarity index 100% rename from web/src/app/admin/channels/page.tsx rename to src/app/admin/channels/page.tsx diff --git a/web/src/app/admin/fields/page.tsx b/src/app/admin/fields/page.tsx similarity index 100% rename from web/src/app/admin/fields/page.tsx rename to src/app/admin/fields/page.tsx diff --git a/web/src/app/admin/layout.tsx b/src/app/admin/layout.tsx similarity index 100% rename from web/src/app/admin/layout.tsx rename to src/app/admin/layout.tsx diff --git a/web/src/app/admin/page.tsx b/src/app/admin/page.tsx similarity index 100% rename from web/src/app/admin/page.tsx rename to src/app/admin/page.tsx diff --git a/web/src/app/admin/slas/page.tsx b/src/app/admin/slas/page.tsx similarity index 100% rename from web/src/app/admin/slas/page.tsx rename to src/app/admin/slas/page.tsx diff --git a/web/src/app/admin/teams/page.tsx b/src/app/admin/teams/page.tsx similarity index 100% rename from web/src/app/admin/teams/page.tsx rename to src/app/admin/teams/page.tsx diff --git a/web/src/app/api/admin/invites/[id]/route.ts b/src/app/api/admin/invites/[id]/route.ts similarity index 100% rename from web/src/app/api/admin/invites/[id]/route.ts rename to src/app/api/admin/invites/[id]/route.ts diff --git a/web/src/app/api/admin/invites/route.ts b/src/app/api/admin/invites/route.ts similarity index 100% rename from web/src/app/api/admin/invites/route.ts rename to src/app/api/admin/invites/route.ts diff --git a/web/src/app/api/admin/users/route.ts b/src/app/api/admin/users/route.ts similarity index 100% rename from web/src/app/api/admin/users/route.ts rename to src/app/api/admin/users/route.ts diff --git a/web/src/app/api/auth/[...all]/route.ts b/src/app/api/auth/[...all]/route.ts similarity index 100% rename from web/src/app/api/auth/[...all]/route.ts rename to src/app/api/auth/[...all]/route.ts diff --git a/web/src/app/api/invites/[token]/route.ts b/src/app/api/invites/[token]/route.ts similarity index 100% rename from web/src/app/api/invites/[token]/route.ts rename to src/app/api/invites/[token]/route.ts diff --git a/web/src/app/dashboard/data.json b/src/app/dashboard/data.json similarity index 100% rename from web/src/app/dashboard/data.json rename to src/app/dashboard/data.json diff --git a/web/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx similarity index 100% rename from web/src/app/dashboard/page.tsx rename to src/app/dashboard/page.tsx diff --git a/web/src/app/dev/seed/page.tsx b/src/app/dev/seed/page.tsx similarity index 100% rename from web/src/app/dev/seed/page.tsx rename to src/app/dev/seed/page.tsx diff --git a/web/src/app/favicon.ico b/src/app/favicon.ico similarity index 100% rename from web/src/app/favicon.ico rename to src/app/favicon.ico diff --git a/web/src/app/globals.css b/src/app/globals.css similarity index 100% rename from web/src/app/globals.css rename to src/app/globals.css diff --git a/web/src/app/invite/[token]/page.tsx b/src/app/invite/[token]/page.tsx similarity index 100% rename from web/src/app/invite/[token]/page.tsx rename to src/app/invite/[token]/page.tsx diff --git a/web/src/app/layout.tsx b/src/app/layout.tsx similarity index 100% rename from web/src/app/layout.tsx rename to src/app/layout.tsx diff --git a/web/src/app/login/page.tsx b/src/app/login/page.tsx similarity index 100% rename from web/src/app/login/page.tsx rename to src/app/login/page.tsx diff --git a/web/src/app/page.tsx b/src/app/page.tsx similarity index 100% rename from web/src/app/page.tsx rename to src/app/page.tsx diff --git a/web/src/app/play/page.tsx b/src/app/play/page.tsx similarity index 100% rename from web/src/app/play/page.tsx rename to src/app/play/page.tsx diff --git a/web/src/app/portal/layout.tsx b/src/app/portal/layout.tsx similarity index 100% rename from web/src/app/portal/layout.tsx rename to src/app/portal/layout.tsx diff --git a/web/src/app/portal/page.tsx b/src/app/portal/page.tsx similarity index 100% rename from web/src/app/portal/page.tsx rename to src/app/portal/page.tsx diff --git a/web/src/app/portal/tickets/[id]/page.tsx b/src/app/portal/tickets/[id]/page.tsx similarity index 100% rename from web/src/app/portal/tickets/[id]/page.tsx rename to src/app/portal/tickets/[id]/page.tsx diff --git a/web/src/app/portal/tickets/new/page.tsx b/src/app/portal/tickets/new/page.tsx similarity index 100% rename from web/src/app/portal/tickets/new/page.tsx rename to src/app/portal/tickets/new/page.tsx diff --git a/web/src/app/portal/tickets/page.tsx b/src/app/portal/tickets/page.tsx similarity index 100% rename from web/src/app/portal/tickets/page.tsx rename to src/app/portal/tickets/page.tsx diff --git a/web/src/app/reports/backlog/page.tsx b/src/app/reports/backlog/page.tsx similarity index 100% rename from web/src/app/reports/backlog/page.tsx rename to src/app/reports/backlog/page.tsx diff --git a/web/src/app/reports/csat/page.tsx b/src/app/reports/csat/page.tsx similarity index 100% rename from web/src/app/reports/csat/page.tsx rename to src/app/reports/csat/page.tsx diff --git a/web/src/app/reports/sla/page.tsx b/src/app/reports/sla/page.tsx similarity index 100% rename from web/src/app/reports/sla/page.tsx rename to src/app/reports/sla/page.tsx diff --git a/web/src/app/settings/page.tsx b/src/app/settings/page.tsx similarity index 100% rename from web/src/app/settings/page.tsx rename to src/app/settings/page.tsx diff --git a/web/src/app/settings/templates/page.tsx b/src/app/settings/templates/page.tsx similarity index 100% rename from web/src/app/settings/templates/page.tsx rename to src/app/settings/templates/page.tsx diff --git a/web/src/app/tickets/[id]/page.tsx b/src/app/tickets/[id]/page.tsx similarity index 100% rename from web/src/app/tickets/[id]/page.tsx rename to src/app/tickets/[id]/page.tsx diff --git a/web/src/app/tickets/new/page.tsx b/src/app/tickets/new/page.tsx similarity index 100% rename from web/src/app/tickets/new/page.tsx rename to src/app/tickets/new/page.tsx diff --git a/web/src/app/tickets/page.tsx b/src/app/tickets/page.tsx similarity index 100% rename from web/src/app/tickets/page.tsx rename to src/app/tickets/page.tsx diff --git a/web/src/app/tickets/tickets-page-client.tsx b/src/app/tickets/tickets-page-client.tsx similarity index 100% rename from web/src/app/tickets/tickets-page-client.tsx rename to src/app/tickets/tickets-page-client.tsx diff --git a/web/src/components/admin/admin-users-manager.tsx b/src/components/admin/admin-users-manager.tsx similarity index 100% rename from web/src/components/admin/admin-users-manager.tsx rename to src/components/admin/admin-users-manager.tsx diff --git a/web/src/components/admin/categories/categories-manager.tsx b/src/components/admin/categories/categories-manager.tsx similarity index 100% rename from web/src/components/admin/categories/categories-manager.tsx rename to src/components/admin/categories/categories-manager.tsx diff --git a/web/src/components/admin/fields/fields-manager.tsx b/src/components/admin/fields/fields-manager.tsx similarity index 100% rename from web/src/components/admin/fields/fields-manager.tsx rename to src/components/admin/fields/fields-manager.tsx diff --git a/web/src/components/admin/queues/queues-manager.tsx b/src/components/admin/queues/queues-manager.tsx similarity index 99% rename from web/src/components/admin/queues/queues-manager.tsx rename to src/components/admin/queues/queues-manager.tsx index 9b34cbf..e0b695d 100644 --- a/web/src/components/admin/queues/queues-manager.tsx +++ b/src/components/admin/queues/queues-manager.tsx @@ -17,6 +17,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { Badge } from "@/components/ui/badge" import { Skeleton } from "@/components/ui/skeleton" import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog" +import { useDefaultQueues } from "@/hooks/use-default-queues" type Queue = { id: string @@ -33,6 +34,7 @@ type TeamOption = { export function QueuesManager() { const { session, convexUserId } = useAuth() const tenantId = session?.user.tenantId ?? DEFAULT_TENANT_ID + useDefaultQueues(tenantId) const NO_TEAM_VALUE = "__none__" diff --git a/web/src/components/admin/slas/slas-manager.tsx b/src/components/admin/slas/slas-manager.tsx similarity index 100% rename from web/src/components/admin/slas/slas-manager.tsx rename to src/components/admin/slas/slas-manager.tsx diff --git a/web/src/components/admin/teams/teams-manager.tsx b/src/components/admin/teams/teams-manager.tsx similarity index 100% rename from web/src/components/admin/teams/teams-manager.tsx rename to src/components/admin/teams/teams-manager.tsx diff --git a/web/src/components/app-shell.tsx b/src/components/app-shell.tsx similarity index 100% rename from web/src/components/app-shell.tsx rename to src/components/app-shell.tsx diff --git a/web/src/components/app-sidebar.tsx b/src/components/app-sidebar.tsx similarity index 100% rename from web/src/components/app-sidebar.tsx rename to src/components/app-sidebar.tsx diff --git a/web/src/components/auth/auth-guard.tsx b/src/components/auth/auth-guard.tsx similarity index 100% rename from web/src/components/auth/auth-guard.tsx rename to src/components/auth/auth-guard.tsx diff --git a/web/src/components/background-paper-shaders-wrapper.tsx b/src/components/background-paper-shaders-wrapper.tsx similarity index 100% rename from web/src/components/background-paper-shaders-wrapper.tsx rename to src/components/background-paper-shaders-wrapper.tsx diff --git a/web/src/components/background-paper-shaders.tsx b/src/components/background-paper-shaders.tsx similarity index 100% rename from web/src/components/background-paper-shaders.tsx rename to src/components/background-paper-shaders.tsx diff --git a/web/src/components/chart-area-interactive.tsx b/src/components/chart-area-interactive.tsx similarity index 100% rename from web/src/components/chart-area-interactive.tsx rename to src/components/chart-area-interactive.tsx diff --git a/web/src/components/data-table.tsx b/src/components/data-table.tsx similarity index 100% rename from web/src/components/data-table.tsx rename to src/components/data-table.tsx diff --git a/web/src/components/invite/invite-accept-form.tsx b/src/components/invite/invite-accept-form.tsx similarity index 100% rename from web/src/components/invite/invite-accept-form.tsx rename to src/components/invite/invite-accept-form.tsx diff --git a/web/src/components/login-form.tsx b/src/components/login-form.tsx similarity index 100% rename from web/src/components/login-form.tsx rename to src/components/login-form.tsx diff --git a/web/src/components/nav-documents.tsx b/src/components/nav-documents.tsx similarity index 100% rename from web/src/components/nav-documents.tsx rename to src/components/nav-documents.tsx diff --git a/web/src/components/nav-main.tsx b/src/components/nav-main.tsx similarity index 100% rename from web/src/components/nav-main.tsx rename to src/components/nav-main.tsx diff --git a/web/src/components/nav-secondary.tsx b/src/components/nav-secondary.tsx similarity index 100% rename from web/src/components/nav-secondary.tsx rename to src/components/nav-secondary.tsx diff --git a/web/src/components/nav-user.tsx b/src/components/nav-user.tsx similarity index 100% rename from web/src/components/nav-user.tsx rename to src/components/nav-user.tsx diff --git a/web/src/components/page-header.tsx b/src/components/page-header.tsx similarity index 100% rename from web/src/components/page-header.tsx rename to src/components/page-header.tsx diff --git a/web/src/components/portal/portal-shell.tsx b/src/components/portal/portal-shell.tsx similarity index 100% rename from web/src/components/portal/portal-shell.tsx rename to src/components/portal/portal-shell.tsx diff --git a/web/src/components/portal/portal-ticket-card.tsx b/src/components/portal/portal-ticket-card.tsx similarity index 100% rename from web/src/components/portal/portal-ticket-card.tsx rename to src/components/portal/portal-ticket-card.tsx diff --git a/web/src/components/portal/portal-ticket-detail.tsx b/src/components/portal/portal-ticket-detail.tsx similarity index 100% rename from web/src/components/portal/portal-ticket-detail.tsx rename to src/components/portal/portal-ticket-detail.tsx diff --git a/web/src/components/portal/portal-ticket-form.tsx b/src/components/portal/portal-ticket-form.tsx similarity index 100% rename from web/src/components/portal/portal-ticket-form.tsx rename to src/components/portal/portal-ticket-form.tsx diff --git a/web/src/components/portal/portal-ticket-list.tsx b/src/components/portal/portal-ticket-list.tsx similarity index 100% rename from web/src/components/portal/portal-ticket-list.tsx rename to src/components/portal/portal-ticket-list.tsx diff --git a/web/src/components/reports/backlog-report.tsx b/src/components/reports/backlog-report.tsx similarity index 100% rename from web/src/components/reports/backlog-report.tsx rename to src/components/reports/backlog-report.tsx diff --git a/web/src/components/reports/csat-report.tsx b/src/components/reports/csat-report.tsx similarity index 100% rename from web/src/components/reports/csat-report.tsx rename to src/components/reports/csat-report.tsx diff --git a/web/src/components/reports/sla-report.tsx b/src/components/reports/sla-report.tsx similarity index 100% rename from web/src/components/reports/sla-report.tsx rename to src/components/reports/sla-report.tsx diff --git a/web/src/components/search-form.tsx b/src/components/search-form.tsx similarity index 100% rename from web/src/components/search-form.tsx rename to src/components/search-form.tsx diff --git a/web/src/components/section-cards.tsx b/src/components/section-cards.tsx similarity index 100% rename from web/src/components/section-cards.tsx rename to src/components/section-cards.tsx diff --git a/web/src/components/settings/comment-templates-manager.tsx b/src/components/settings/comment-templates-manager.tsx similarity index 100% rename from web/src/components/settings/comment-templates-manager.tsx rename to src/components/settings/comment-templates-manager.tsx diff --git a/web/src/components/settings/settings-content.tsx b/src/components/settings/settings-content.tsx similarity index 100% rename from web/src/components/settings/settings-content.tsx rename to src/components/settings/settings-content.tsx diff --git a/web/src/components/site-header.tsx b/src/components/site-header.tsx similarity index 100% rename from web/src/components/site-header.tsx rename to src/components/site-header.tsx diff --git a/web/src/components/tickets/category-select.tsx b/src/components/tickets/category-select.tsx similarity index 100% rename from web/src/components/tickets/category-select.tsx rename to src/components/tickets/category-select.tsx diff --git a/web/src/components/tickets/delete-ticket-dialog.tsx b/src/components/tickets/delete-ticket-dialog.tsx similarity index 100% rename from web/src/components/tickets/delete-ticket-dialog.tsx rename to src/components/tickets/delete-ticket-dialog.tsx diff --git a/web/src/components/tickets/new-ticket-dialog.tsx b/src/components/tickets/new-ticket-dialog.tsx similarity index 99% rename from web/src/components/tickets/new-ticket-dialog.tsx rename to src/components/tickets/new-ticket-dialog.tsx index 76749fb..60fabb5 100644 --- a/web/src/components/tickets/new-ticket-dialog.tsx +++ b/src/components/tickets/new-ticket-dialog.tsx @@ -25,6 +25,7 @@ import { priorityStyles, } from "@/components/tickets/priority-select" import { CategorySelectFields } from "@/components/tickets/category-select" +import { useDefaultQueues } from "@/hooks/use-default-queues" const schema = z.object({ subject: z.string().default(""), @@ -60,6 +61,8 @@ export function NewTicketDialog() { const queueArgs = convexUserId ? { tenantId: DEFAULT_TENANT_ID, viewerId: convexUserId as Id<"users"> } : "skip" + + useDefaultQueues(DEFAULT_TENANT_ID) const queuesRaw = useQuery( convexUserId ? api.queues.summary : "skip", queueArgs diff --git a/web/src/components/tickets/play-next-ticket-card.tsx b/src/components/tickets/play-next-ticket-card.tsx similarity index 100% rename from web/src/components/tickets/play-next-ticket-card.tsx rename to src/components/tickets/play-next-ticket-card.tsx diff --git a/web/src/components/tickets/priority-pill.tsx b/src/components/tickets/priority-pill.tsx similarity index 100% rename from web/src/components/tickets/priority-pill.tsx rename to src/components/tickets/priority-pill.tsx diff --git a/web/src/components/tickets/priority-select.tsx b/src/components/tickets/priority-select.tsx similarity index 100% rename from web/src/components/tickets/priority-select.tsx rename to src/components/tickets/priority-select.tsx diff --git a/web/src/components/tickets/recent-tickets-panel.tsx b/src/components/tickets/recent-tickets-panel.tsx similarity index 100% rename from web/src/components/tickets/recent-tickets-panel.tsx rename to src/components/tickets/recent-tickets-panel.tsx diff --git a/web/src/components/tickets/status-badge.tsx b/src/components/tickets/status-badge.tsx similarity index 100% rename from web/src/components/tickets/status-badge.tsx rename to src/components/tickets/status-badge.tsx diff --git a/web/src/components/tickets/status-select.tsx b/src/components/tickets/status-select.tsx similarity index 100% rename from web/src/components/tickets/status-select.tsx rename to src/components/tickets/status-select.tsx diff --git a/web/src/components/tickets/ticket-comments.rich.tsx b/src/components/tickets/ticket-comments.rich.tsx similarity index 100% rename from web/src/components/tickets/ticket-comments.rich.tsx rename to src/components/tickets/ticket-comments.rich.tsx diff --git a/web/src/components/tickets/ticket-detail-static.tsx b/src/components/tickets/ticket-detail-static.tsx similarity index 100% rename from web/src/components/tickets/ticket-detail-static.tsx rename to src/components/tickets/ticket-detail-static.tsx diff --git a/web/src/components/tickets/ticket-detail-view.tsx b/src/components/tickets/ticket-detail-view.tsx similarity index 100% rename from web/src/components/tickets/ticket-detail-view.tsx rename to src/components/tickets/ticket-detail-view.tsx diff --git a/web/src/components/tickets/ticket-details-panel.tsx b/src/components/tickets/ticket-details-panel.tsx similarity index 100% rename from web/src/components/tickets/ticket-details-panel.tsx rename to src/components/tickets/ticket-details-panel.tsx diff --git a/web/src/components/tickets/ticket-queue-summary.tsx b/src/components/tickets/ticket-queue-summary.tsx similarity index 100% rename from web/src/components/tickets/ticket-queue-summary.tsx rename to src/components/tickets/ticket-queue-summary.tsx diff --git a/web/src/components/tickets/ticket-summary-header.tsx b/src/components/tickets/ticket-summary-header.tsx similarity index 93% rename from web/src/components/tickets/ticket-summary-header.tsx rename to src/components/tickets/ticket-summary-header.tsx index 629c8e3..cb8eec0 100644 --- a/web/src/components/tickets/ticket-summary-header.tsx +++ b/src/components/tickets/ticket-summary-header.tsx @@ -21,6 +21,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { useTicketCategories } from "@/hooks/use-ticket-categories" +import { useDefaultQueues } from "@/hooks/use-default-queues" interface TicketHeaderProps { ticket: TicketWithDetails @@ -62,6 +63,7 @@ function formatDuration(durationMs: number) { export function TicketSummaryHeader({ ticket }: TicketHeaderProps) { const { convexUserId, role } = useAuth() const isManager = role === "manager" + useDefaultQueues(ticket.tenantId) const changeAssignee = useMutation(api.tickets.changeAssignee) const changeQueue = useMutation(api.tickets.changeQueue) const updateSubject = useMutation(api.tickets.updateSubject) @@ -113,7 +115,10 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) { const categoryDirty = useMemo(() => { return selectedCategoryId !== currentCategoryId || selectedSubcategoryId !== currentSubcategoryId }, [selectedCategoryId, selectedSubcategoryId, currentCategoryId, currentSubcategoryId]) - const formDirty = dirty || categoryDirty + const currentQueueName = ticket.queue ?? "" + const [queueSelection, setQueueSelection] = useState(currentQueueName) + const queueDirty = useMemo(() => queueSelection !== currentQueueName, [queueSelection, currentQueueName]) + const formDirty = dirty || categoryDirty || queueDirty const activeCategory = useMemo( () => categories.find((category) => category.id === selectedCategoryId) ?? null, @@ -155,6 +160,30 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) { }) } + if (queueDirty && !isManager) { + const queue = queues.find((item) => item.name === queueSelection) + if (!queue) { + toast.error("Fila selecionada não está disponível.") + setQueueSelection(currentQueueName) + throw new Error("Fila inválida") + } + toast.loading("Atualizando fila...", { id: "queue" }) + try { + await changeQueue({ + ticketId: ticket.id as Id<"tickets">, + queueId: queue.id as Id<"queues">, + actorId: convexUserId as Id<"users">, + }) + toast.success("Fila atualizada!", { id: "queue" }) + } catch (queueError) { + toast.error("Não foi possível atualizar a fila.", { id: "queue" }) + setQueueSelection(currentQueueName) + throw queueError + } + } else if (queueDirty && isManager) { + setQueueSelection(currentQueueName) + } + if (dirty) { toast.loading("Salvando alterações...", { id: "save-header" }) if (subject !== ticket.subject) { @@ -188,6 +217,7 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) { categoryId: currentCategoryId, subcategoryId: currentSubcategoryId, }) + setQueueSelection(currentQueueName) setEditing(false) } @@ -197,7 +227,8 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) { categoryId: ticket.category?.id ?? "", subcategoryId: ticket.subcategory?.id ?? "", }) - }, [editing, ticket.category?.id, ticket.subcategory?.id]) + setQueueSelection(ticket.queue ?? "") + }, [editing, ticket.category?.id, ticket.subcategory?.id, ticket.queue]) useEffect(() => { if (!editing) return @@ -416,19 +447,10 @@ export function TicketSummaryHeader({ ticket }: TicketHeaderProps) { {editing ? (