diff --git a/.cursor/commands/svelte-task.md b/.cursor/commands/svelte-task.md new file mode 100644 index 0000000..2c74ec0 --- /dev/null +++ b/.cursor/commands/svelte-task.md @@ -0,0 +1,189 @@ +You are a Svelte expert tasked to build components and utilities for Svelte developers. If you need documentation for anything related to Svelte you can invoke the tool `get_documentation` with one of the following paths: + + +- title: Overview, use_cases: project setup, creating new svelte apps, scaffolding, cli tools, initializing projects, path: cli/overview +- title: Frequently asked questions, use_cases: project setup, initializing new svelte projects, troubleshooting cli installation, package manager configuration, path: cli/faq +- title: sv create, use_cases: project setup, starting new sveltekit app, initializing project, creating from playground, choosing project template, path: cli/sv-create +- title: sv add, use_cases: project setup, adding features to existing projects, integrating tools, testing setup, styling setup, authentication, database setup, deployment adapters, path: cli/sv-add +- title: sv check, use_cases: code quality, ci/cd pipelines, error checking, typescript projects, pre-commit hooks, finding unused css, accessibility auditing, production builds, path: cli/sv-check +- title: sv migrate, use_cases: migration, upgrading svelte versions, upgrading sveltekit versions, modernizing codebase, svelte 3 to 4, svelte 4 to 5, sveltekit 1 to 2, adopting runes, refactoring deprecated apis, path: cli/sv-migrate +- title: devtools-json, use_cases: development setup, chrome devtools integration, browser-based editing, local development workflow, debugging setup, path: cli/devtools-json +- title: drizzle, use_cases: database setup, sql queries, orm integration, data modeling, postgresql, mysql, sqlite, server-side data access, database migrations, type-safe queries, path: cli/drizzle +- title: eslint, use_cases: code quality, linting, error detection, project setup, code standards, team collaboration, typescript projects, path: cli/eslint +- title: lucia, use_cases: authentication, login systems, user management, registration pages, session handling, auth setup, path: cli/lucia +- title: mcp, use_cases: use title and path to estimate use case, path: cli/mcp +- title: mdsvex, use_cases: blog, content sites, markdown rendering, documentation sites, technical writing, cms integration, article pages, path: cli/mdsvex +- title: paraglide, use_cases: internationalization, multi-language sites, i18n, translation, localization, language switching, global apps, multilingual content, path: cli/paraglide +- title: playwright, use_cases: browser testing, e2e testing, integration testing, test automation, quality assurance, ci/cd pipelines, testing user flows, path: cli/playwright +- title: prettier, use_cases: code formatting, project setup, code style consistency, team collaboration, linting configuration, path: cli/prettier +- title: storybook, use_cases: component development, design systems, ui library, isolated component testing, documentation, visual testing, component showcase, path: cli/storybook +- title: sveltekit-adapter, use_cases: deployment, production builds, hosting setup, choosing deployment platform, configuring adapters, static site generation, node server, vercel, cloudflare, netlify, path: cli/sveltekit-adapter +- title: tailwindcss, use_cases: project setup, styling, css framework, rapid prototyping, utility-first css, design systems, responsive design, adding tailwind to svelte, path: cli/tailwind +- title: vitest, use_cases: testing, unit tests, component testing, test setup, quality assurance, ci/cd pipelines, test-driven development, path: cli/vitest +- title: Introduction, use_cases: learning sveltekit, project setup, understanding framework basics, choosing between svelte and sveltekit, getting started with full-stack apps, path: kit/introduction +- title: Creating a project, use_cases: project setup, starting new sveltekit app, initial development environment, first-time sveltekit users, scaffolding projects, path: kit/creating-a-project +- title: Project types, use_cases: deployment, project setup, choosing adapters, ssg, spa, ssr, serverless, mobile apps, desktop apps, pwa, offline apps, browser extensions, separate backend, docker containers, path: kit/project-types +- title: Project structure, use_cases: project setup, understanding file structure, organizing code, starting new project, learning sveltekit basics, path: kit/project-structure +- title: Web standards, use_cases: always, any sveltekit project, data fetching, forms, api routes, server-side rendering, deployment to various platforms, path: kit/web-standards +- title: Routing, use_cases: routing, navigation, multi-page apps, project setup, file structure, api endpoints, data loading, layouts, error pages, always, path: kit/routing +- title: Loading data, use_cases: data fetching, api calls, database queries, dynamic routes, page initialization, loading states, authentication checks, ssr data, form data, content rendering, path: kit/load +- title: Form actions, use_cases: forms, user input, data submission, authentication, login systems, user registration, progressive enhancement, validation errors, path: kit/form-actions +- title: Page options, use_cases: prerendering static sites, ssr configuration, spa setup, client-side rendering control, url trailing slash handling, adapter deployment config, build optimization, path: kit/page-options +- title: State management, use_cases: sveltekit, server-side rendering, ssr, state management, authentication, data persistence, load functions, context api, navigation, component lifecycle, path: kit/state-management +- title: Remote functions, use_cases: data fetching, server-side logic, database queries, type-safe client-server communication, forms, user input, mutations, authentication, crud operations, optimistic updates, path: kit/remote-functions +- title: Building your app, use_cases: production builds, deployment preparation, build process optimization, adapter configuration, preview before deployment, path: kit/building-your-app +- title: Adapters, use_cases: deployment, production builds, hosting setup, choosing deployment platform, configuring adapters, path: kit/adapters +- title: Zero-config deployments, use_cases: deployment, production builds, hosting setup, choosing deployment platform, ci/cd configuration, path: kit/adapter-auto +- title: Node servers, use_cases: deployment, production builds, node.js hosting, custom server setup, environment configuration, reverse proxy setup, docker deployment, systemd services, path: kit/adapter-node +- title: Static site generation, use_cases: static site generation, ssg, prerendering, deployment, github pages, spa mode, blogs, documentation sites, marketing sites, path: kit/adapter-static +- title: Single-page apps, use_cases: spa mode, single-page apps, client-only rendering, static hosting, mobile app wrappers, no server-side logic, adapter-static setup, fallback pages, path: kit/single-page-apps +- title: Cloudflare, use_cases: deployment, cloudflare workers, cloudflare pages, hosting setup, production builds, serverless deployment, edge computing, path: kit/adapter-cloudflare +- title: Cloudflare Workers, use_cases: deploying to cloudflare workers, cloudflare workers sites deployment, legacy cloudflare adapter, wrangler configuration, cloudflare platform bindings, path: kit/adapter-cloudflare-workers +- title: Netlify, use_cases: deployment, netlify hosting, production builds, serverless functions, edge functions, static site hosting, path: kit/adapter-netlify +- title: Vercel, use_cases: deployment, vercel hosting, production builds, serverless functions, edge functions, isr, image optimization, environment variables, path: kit/adapter-vercel +- title: Writing adapters, use_cases: custom deployment, building adapters, unsupported platforms, adapter development, custom hosting environments, path: kit/writing-adapters +- title: Advanced routing, use_cases: advanced routing, dynamic routes, file viewers, nested paths, custom 404 pages, url validation, route parameters, multi-level navigation, path: kit/advanced-routing +- title: Hooks, use_cases: authentication, logging, error tracking, request interception, api proxying, custom routing, internationalization, database initialization, middleware logic, session management, path: kit/hooks +- title: Errors, use_cases: error handling, custom error pages, 404 pages, api error responses, production error logging, error tracking, type-safe errors, path: kit/errors +- title: Link options, use_cases: routing, navigation, multi-page apps, performance optimization, link preloading, forms with get method, search functionality, focus management, scroll behavior, path: kit/link-options +- title: Service workers, use_cases: offline support, pwa, caching strategies, performance optimization, precaching assets, network resilience, progressive web apps, path: kit/service-workers +- title: Server-only modules, use_cases: api keys, environment variables, sensitive data protection, backend security, preventing data leaks, server-side code isolation, path: kit/server-only-modules +- title: Snapshots, use_cases: forms, user input, preserving form data, multi-step forms, navigation state, preventing data loss, textarea content, input fields, comment systems, surveys, path: kit/snapshots +- title: Shallow routing, use_cases: modals, dialogs, image galleries, overlays, history-driven ui, mobile-friendly navigation, photo viewers, lightboxes, drawer menus, path: kit/shallow-routing +- title: Observability, use_cases: performance monitoring, debugging, observability, tracing requests, production diagnostics, analyzing slow requests, finding bottlenecks, monitoring server-side operations, path: kit/observability +- title: Packaging, use_cases: building component libraries, publishing npm packages, creating reusable svelte components, library development, package distribution, path: kit/packaging +- title: Auth, use_cases: authentication, login systems, user management, session handling, jwt tokens, protected routes, user credentials, authorization checks, path: kit/auth +- title: Performance, use_cases: performance optimization, slow loading pages, production deployment, debugging performance issues, reducing bundle size, improving load times, path: kit/performance +- title: Icons, use_cases: icons, ui components, styling, css frameworks, tailwind, unocss, performance optimization, dependency management, path: kit/icons +- title: Images, use_cases: image optimization, responsive images, performance, hero images, product photos, galleries, cms integration, cdn setup, asset management, path: kit/images +- title: Accessibility, use_cases: always, any sveltekit project, screen reader support, keyboard navigation, multi-page apps, client-side routing, internationalization, multilingual sites, path: kit/accessibility +- title: SEO, use_cases: seo optimization, search engine ranking, content sites, blogs, marketing sites, public-facing apps, sitemaps, amp pages, meta tags, performance optimization, path: kit/seo +- title: Frequently asked questions, use_cases: troubleshooting package imports, library compatibility issues, client-side code execution, external api integration, middleware setup, database configuration, view transitions, yarn configuration, path: kit/faq +- title: Integrations, use_cases: project setup, css preprocessors, postcss, scss, sass, less, stylus, typescript setup, adding integrations, tailwind, testing, auth, linting, formatting, path: kit/integrations +- title: Breakpoint Debugging, use_cases: debugging, breakpoints, development workflow, troubleshooting issues, vscode setup, ide configuration, inspecting code execution, path: kit/debugging +- title: Migrating to SvelteKit v2, use_cases: migration, upgrading from sveltekit 1 to 2, breaking changes, version updates, path: kit/migrating-to-sveltekit-2 +- title: Migrating from Sapper, use_cases: migrating from sapper, upgrading legacy projects, sapper to sveltekit conversion, project modernization, path: kit/migrating +- title: Additional resources, use_cases: troubleshooting, getting help, finding examples, learning sveltekit, project templates, common issues, community support, path: kit/additional-resources +- title: Glossary, use_cases: rendering strategies, performance optimization, deployment configuration, seo requirements, static sites, spas, server-side rendering, prerendering, edge deployment, pwa development, path: kit/glossary +- title: @sveltejs/kit, use_cases: forms, form actions, server-side validation, form submission, error handling, redirects, json responses, http errors, server utilities, path: kit/@sveltejs-kit +- title: @sveltejs/kit/hooks, use_cases: middleware, request processing, authentication chains, logging, multiple hooks, request/response transformation, path: kit/@sveltejs-kit-hooks +- title: @sveltejs/kit/node/polyfills, use_cases: node.js environments, custom servers, non-standard runtimes, ssr setup, web api compatibility, polyfill requirements, path: kit/@sveltejs-kit-node-polyfills +- title: @sveltejs/kit/node, use_cases: node.js adapter, custom server setup, http integration, streaming files, node deployment, server-side rendering with node, path: kit/@sveltejs-kit-node +- title: @sveltejs/kit/vite, use_cases: project setup, vite configuration, initial sveltekit setup, build tooling, path: kit/@sveltejs-kit-vite +- title: $app/environment, use_cases: always, conditional logic, client-side code, server-side code, build-time logic, prerendering, development vs production, environment detection, path: kit/$app-environment +- title: $app/forms, use_cases: forms, user input, data submission, progressive enhancement, custom form handling, form validation, path: kit/$app-forms +- title: $app/navigation, use_cases: routing, navigation, multi-page apps, programmatic navigation, data reloading, preloading, shallow routing, navigation lifecycle, scroll handling, view transitions, path: kit/$app-navigation +- title: $app/paths, use_cases: static assets, images, fonts, public files, base path configuration, subdirectory deployment, cdn setup, asset urls, links, navigation, path: kit/$app-paths +- title: $app/server, use_cases: remote functions, server-side logic, data fetching, form handling, api endpoints, client-server communication, prerendering, file reading, batch queries, path: kit/$app-server +- title: $app/state, use_cases: routing, navigation, multi-page apps, loading states, url parameters, form handling, error states, version updates, page metadata, shallow routing, path: kit/$app-state +- title: $app/stores, use_cases: legacy projects, sveltekit pre-2.12, migration from stores to runes, maintaining older codebases, accessing page data, navigation state, app version updates, path: kit/$app-stores +- title: $app/types, use_cases: routing, navigation, type safety, route parameters, dynamic routes, link generation, pathname validation, multi-page apps, path: kit/$app-types +- title: $env/dynamic/private, use_cases: api keys, secrets management, server-side config, environment variables, backend logic, deployment-specific settings, private data handling, path: kit/$env-dynamic-private +- title: $env/dynamic/public, use_cases: environment variables, client-side config, runtime configuration, public api keys, deployment-specific settings, multi-environment apps, path: kit/$env-dynamic-public +- title: $env/static/private, use_cases: server-side api keys, backend secrets, database credentials, private configuration, build-time optimization, server endpoints, authentication tokens, path: kit/$env-static-private +- title: $env/static/public, use_cases: environment variables, public config, client-side data, api endpoints, build-time configuration, public constants, path: kit/$env-static-public +- title: $lib, use_cases: project setup, component organization, importing shared components, reusable ui elements, code structure, path: kit/$lib +- title: $service-worker, use_cases: offline support, pwa, service workers, caching strategies, progressive web apps, offline-first apps, path: kit/$service-worker +- title: Configuration, use_cases: project setup, configuration, adapters, deployment, build settings, environment variables, routing customization, prerendering, csp security, csrf protection, path configuration, typescript setup, path: kit/configuration +- title: Command Line Interface, use_cases: project setup, typescript configuration, generated types, ./$types imports, initial project configuration, path: kit/cli +- title: Types, use_cases: typescript, type safety, route parameters, api endpoints, load functions, form actions, generated types, jsconfig setup, path: kit/types +- title: Overview, use_cases: use title and path to estimate use case, path: mcp/overview +- title: Local setup, use_cases: use title and path to estimate use case, path: mcp/local-setup +- title: Remote setup, use_cases: use title and path to estimate use case, path: mcp/remote-setup +- title: Tools, use_cases: use title and path to estimate use case, path: mcp/tools +- title: Resources, use_cases: use title and path to estimate use case, path: mcp/resources +- title: Prompts, use_cases: use title and path to estimate use case, path: mcp/prompts +- title: Overview, use_cases: always, any svelte project, getting started, learning svelte, introduction, project setup, understanding framework basics, path: svelte/overview +- title: Getting started, use_cases: project setup, starting new svelte project, initial installation, choosing between sveltekit and vite, editor configuration, path: svelte/getting-started +- title: .svelte files, use_cases: always, any svelte project, component creation, project setup, learning svelte basics, path: svelte/svelte-files +- title: .svelte.js and .svelte.ts files, use_cases: shared reactive state, reusable reactive logic, state management across components, global stores, custom reactive utilities, path: svelte/svelte-js-files +- title: What are runes?, use_cases: always, any svelte 5 project, understanding core syntax, learning svelte 5, migration from svelte 4, path: svelte/what-are-runes +- title: $state, use_cases: always, any svelte project, core reactivity, state management, counters, forms, todo apps, interactive ui, data updates, class-based components, path: svelte/$state +- title: $derived, use_cases: always, any svelte project, computed values, reactive calculations, derived data, transforming state, dependent values, path: svelte/$derived +- title: $effect, use_cases: canvas drawing, third-party library integration, dom manipulation, side effects, intervals, timers, network requests, analytics tracking, path: svelte/$effect +- title: $props, use_cases: always, any svelte project, passing data to components, component communication, reusable components, component props, path: svelte/$props +- title: $bindable, use_cases: forms, user input, two-way data binding, custom input components, parent-child communication, reusable form fields, path: svelte/$bindable +- title: $inspect, use_cases: debugging, development, tracking state changes, reactive state monitoring, troubleshooting reactivity issues, path: svelte/$inspect +- title: $host, use_cases: custom elements, web components, dispatching custom events, component library, framework-agnostic components, path: svelte/$host +- title: Basic markup, use_cases: always, any svelte project, basic markup, html templating, component structure, attributes, events, props, text rendering, path: svelte/basic-markup +- title: {#if ...}, use_cases: always, conditional rendering, showing/hiding content, dynamic ui, user permissions, loading states, error handling, form validation, path: svelte/if +- title: {#each ...}, use_cases: always, lists, arrays, iteration, product listings, todos, tables, grids, dynamic content, shopping carts, user lists, comments, feeds, path: svelte/each +- title: {#key ...}, use_cases: animations, transitions, component reinitialization, forcing component remount, value-based ui updates, resetting component state, path: svelte/key +- title: {#await ...}, use_cases: async data fetching, api calls, loading states, promises, error handling, lazy loading components, dynamic imports, path: svelte/await +- title: {#snippet ...}, use_cases: reusable markup, component composition, passing content to components, table rows, list items, conditional rendering, reducing duplication, path: svelte/snippet +- title: {@render ...}, use_cases: reusable ui patterns, component composition, conditional rendering, fallback content, layout components, slot alternatives, template reuse, path: svelte/@render +- title: {@html ...}, use_cases: rendering html strings, cms content, rich text editors, markdown to html, blog posts, wysiwyg output, sanitized html injection, dynamic html content, path: svelte/@html +- title: {@attach ...}, use_cases: tooltips, popovers, dom manipulation, third-party libraries, canvas drawing, element lifecycle, interactive ui, custom directives, wrapper components, path: svelte/@attach +- title: {@const ...}, use_cases: computed values in loops, derived calculations in blocks, local variables in each iterations, complex list rendering, path: svelte/@const +- title: {@debug ...}, use_cases: debugging, development, troubleshooting, tracking state changes, monitoring variables, reactive data inspection, path: svelte/@debug +- title: bind:, use_cases: forms, user input, two-way data binding, interactive ui, media players, file uploads, checkboxes, radio buttons, select dropdowns, contenteditable, dimension tracking, path: svelte/bind +- title: use:, use_cases: custom directives, dom manipulation, third-party library integration, tooltips, click outside, gestures, focus management, element lifecycle hooks, path: svelte/use +- title: transition:, use_cases: animations, interactive ui, modals, dropdowns, notifications, conditional content, show/hide elements, smooth state changes, path: svelte/transition +- title: in: and out:, use_cases: animation, transitions, interactive ui, conditional rendering, independent enter/exit effects, modals, tooltips, notifications, path: svelte/in-and-out +- title: animate:, use_cases: sortable lists, drag and drop, reorderable items, todo lists, kanban boards, playlist editors, priority queues, animated list reordering, path: svelte/animate +- title: style:, use_cases: dynamic styling, conditional styles, theming, dark mode, responsive design, interactive ui, component styling, path: svelte/style +- title: class, use_cases: always, conditional styling, dynamic classes, tailwind css, component styling, reusable components, responsive design, path: svelte/class +- title: await, use_cases: async data fetching, loading states, server-side rendering, awaiting promises in components, async validation, concurrent data loading, path: svelte/await-expressions +- title: Scoped styles, use_cases: always, styling components, scoped css, component-specific styles, preventing style conflicts, animations, keyframes, path: svelte/scoped-styles +- title: Global styles, use_cases: global styles, third-party libraries, css resets, animations, styling body/html, overriding component styles, shared keyframes, base styles, path: svelte/global-styles +- title: Custom properties, use_cases: theming, custom styling, reusable components, design systems, dynamic colors, component libraries, ui customization, path: svelte/custom-properties +- title: Nested diff --git a/apps/web/src/lib/hooks/useConvexWithAuth.ts b/apps/web/src/lib/hooks/useConvexWithAuth.ts index ca7514b..593440f 100644 --- a/apps/web/src/lib/hooks/useConvexWithAuth.ts +++ b/apps/web/src/lib/hooks/useConvexWithAuth.ts @@ -30,15 +30,25 @@ export function useConvexWithAuth() { const clientWithAuth = client as ConvexClientWithAuth; // Configurar token se disponível - if (clientWithAuth && typeof clientWithAuth.setAuth === "function" && token) { + if (clientWithAuth && token) { try { - clientWithAuth.setAuth(token); - if (import.meta.env.DEV) { - console.log("✅ [useConvexWithAuth] Token configurado:", token.substring(0, 20) + "..."); + // Tentar setAuth se disponível + if (typeof clientWithAuth.setAuth === "function") { + clientWithAuth.setAuth(token); + if (import.meta.env.DEV) { + console.log("✅ [useConvexWithAuth] Token configurado via setAuth:", token.substring(0, 20) + "..."); + } + } else { + // Se setAuth não estiver disponível, o token deve ser passado via createSvelteAuthClient + if (import.meta.env.DEV) { + console.log("ℹ️ [useConvexWithAuth] Token disponível, autenticação gerenciada por createSvelteAuthClient"); + } } } catch (e) { console.warn("⚠️ [useConvexWithAuth] Erro ao configurar token:", e); } + } else if (!token && import.meta.env.DEV) { + console.warn("⚠️ [useConvexWithAuth] Token não disponível"); } return client; diff --git a/apps/web/src/lib/stores/chamados.ts b/apps/web/src/lib/stores/chamados.ts new file mode 100644 index 0000000..acf1d74 --- /dev/null +++ b/apps/web/src/lib/stores/chamados.ts @@ -0,0 +1,53 @@ +import { writable } from "svelte/store"; +import type { Doc, Id } from "@sgse-app/backend/convex/_generated/dataModel"; + +export type TicketDetalhe = { + ticket: Doc<"tickets">; + interactions: Doc<"ticketInteractions">[]; +}; + +function createChamadosStore() { + const tickets = writable>>([]); + const detalhes = writable>({}); + const carregando = writable(false); + + function setTickets(lista: Array>) { + tickets.set(lista); + } + + function upsertTicket(ticket: Doc<"tickets">) { + tickets.update((current) => { + const existente = current.findIndex((t) => t._id === ticket._id); + if (existente >= 0) { + const copia = [...current]; + copia[existente] = ticket; + return copia; + } + return [ticket, ...current]; + }); + } + + function setDetalhe(ticketId: Id<"tickets">, detalhe: TicketDetalhe) { + detalhes.update((mapa) => ({ + ...mapa, + [ticketId]: detalhe, + })); + } + + function setCarregando(flag: boolean) { + carregando.set(flag); + } + + return { + tickets, + detalhes, + carregando, + setTickets, + upsertTicket, + setDetalhe, + setCarregando, + }; +} + +export const chamadosStore = createChamadosStore(); + diff --git a/apps/web/src/lib/utils/chamados.ts b/apps/web/src/lib/utils/chamados.ts new file mode 100644 index 0000000..d1aa518 --- /dev/null +++ b/apps/web/src/lib/utils/chamados.ts @@ -0,0 +1,123 @@ +import type { Doc } from "@sgse-app/backend/convex/_generated/dataModel"; + +type Ticket = Doc<"tickets">; +type TicketStatus = Ticket["status"]; +type TimelineEntry = NonNullable[number]; + +const UM_DIA_MS = 24 * 60 * 60 * 1000; + +const statusConfig: Record< + TicketStatus, + { + label: string; + badge: string; + description: string; + } +> = { + aberto: { + label: "Aberto", + badge: "badge badge-info badge-outline", + description: "Chamado recebido e aguardando triagem.", + }, + em_andamento: { + label: "Em andamento", + badge: "badge badge-primary", + description: "Equipe de TI trabalhando no chamado.", + }, + aguardando_usuario: { + label: "Aguardando usuário", + badge: "badge badge-warning", + description: "Aguardando retorno ou aprovação do solicitante.", + }, + resolvido: { + label: "Resolvido", + badge: "badge badge-success badge-outline", + description: "Solução aplicada, aguardando confirmação.", + }, + encerrado: { + label: "Encerrado", + badge: "badge badge-success", + description: "Chamado finalizado.", + }, + cancelado: { + label: "Cancelado", + badge: "badge badge-neutral", + description: "Chamado cancelado.", + }, +}; + +export function getStatusLabel(status: TicketStatus): string { + return statusConfig[status]?.label ?? status; +} + +export function getStatusBadge(status: TicketStatus): string { + return statusConfig[status]?.badge ?? "badge"; +} + +export function getStatusDescription(status: TicketStatus): string { + return statusConfig[status]?.description ?? ""; +} + +export function formatarData(timestamp?: number | null) { + if (!timestamp) return "--"; + return new Date(timestamp).toLocaleString("pt-BR", { + day: "2-digit", + month: "short", + hour: "2-digit", + minute: "2-digit", + }); +} + +export function prazoRestante(timestamp?: number | null) { + if (!timestamp) return null; + const diff = timestamp - Date.now(); + const dias = Math.floor(diff / UM_DIA_MS); + const horas = Math.floor((diff % UM_DIA_MS) / (60 * 60 * 1000)); + + if (diff < 0) { + return `Vencido há ${Math.abs(dias)}d ${Math.abs(horas)}h`; + } + + if (dias === 0 && horas >= 0) { + return `Vence em ${horas}h`; + } + + return `Vence em ${dias}d ${Math.abs(horas)}h`; +} + +export function corPrazo(timestamp?: number | null) { + if (!timestamp) return "info"; + const diff = timestamp - Date.now(); + if (diff < 0) return "error"; + if (diff <= UM_DIA_MS) return "warning"; + return "success"; +} + +export function timelineStatus(entry: TimelineEntry) { + if (entry.status === "concluido") { + return "success"; + } + if (!entry.prazo) { + return "info"; + } + const diff = entry.prazo - Date.now(); + if (diff < 0) { + return "error"; + } + if (diff <= UM_DIA_MS) { + return "warning"; + } + return "info"; +} + +export function formatarTimelineEtapa(etapa: string) { + const mapa: Record = { + abertura: "Registro", + resposta_inicial: "Resposta inicial", + conclusao: "Conclusão", + encerramento: "Encerramento", + }; + + return mapa[etapa] ?? etapa; +} + diff --git a/apps/web/src/routes/(dashboard)/+layout.svelte b/apps/web/src/routes/(dashboard)/+layout.svelte index 3e3778a..c8b1523 100644 --- a/apps/web/src/routes/(dashboard)/+layout.svelte +++ b/apps/web/src/routes/(dashboard)/+layout.svelte @@ -8,7 +8,7 @@ // Resolver recurso/ação a partir da rota const routeAction = $derived.by(() => { const p = page.url.pathname; - if (p === '/' || p === '/solicitar-acesso') return null; + if (p === '/' || p === '/abrir-chamado') return null; // Funcionários if (p.startsWith('/recursos-humanos/funcionarios')) { diff --git a/apps/web/src/routes/(dashboard)/+page.svelte b/apps/web/src/routes/(dashboard)/+page.svelte index 0c7070b..005b954 100644 --- a/apps/web/src/routes/(dashboard)/+page.svelte +++ b/apps/web/src/routes/(dashboard)/+page.svelte @@ -7,6 +7,7 @@ import { resolve } from "$app/paths"; import { UserPlus, Mail } from "lucide-svelte"; import { useAuth } from "@mmailaender/convex-better-auth-svelte/svelte"; + import ProtectedRoute from "$lib/components/ProtectedRoute.svelte"; let { data } = $props(); @@ -128,6 +129,7 @@ } +
{#if showAlert} @@ -146,13 +148,13 @@

{alertData.message}

{#if alertType === "access_denied"} {/if}
+
diff --git a/apps/web/src/routes/(dashboard)/ti/+page.svelte b/apps/web/src/routes/(dashboard)/ti/+page.svelte index 1ec6a7f..786366e 100644 --- a/apps/web/src/routes/(dashboard)/ti/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/+page.svelte @@ -1,472 +1,487 @@
-
-
-
-
-
- - Tecnologia da Informação - -

- Sistemas de Informação -

-

- Acesso restrito para gerenciamento de solicitações de acesso ao - sistema, configuração de permissões e monitoramento técnico das - operações do SGSE. -

-
-
-
-

Status

-

Operacional

-
-
-

- Última atualização -

-

Agora mesmo

-
-
-
- Monitoramento em tempo real. - SGSE -
-
-
-
+
+
+
+
+
+ + Tecnologia da Informação + +

+ Sistemas de Informação +

+

+ Acesso restrito para gerenciamento de solicitações de acesso ao sistema, configuração de + permissões e monitoramento técnico das operações do SGSE. +

+
+
+
+

Status

+

Operacional

+
+
+

Última atualização

+

Agora mesmo

+
+
+
+ Monitoramento em tempo real. + SGSE +
+
+
+
-
- {#each featureCards as card (card.title)} -
-
-
-
- - {#each iconPaths[card.icon] as path (path.d)} - - {/each} - -
-
-

- {card.title} -

-

- {card.description} -

-
-
+
+ {#each featureCards as card (card.title)} +
+
+
+
+ + {#each iconPaths[card.icon] as path (path.d)} + + {/each} + +
+
+

+ {card.title} +

+

+ {card.description} +

+
+
- {#if card.highlightBadges} -
- {#each card.highlightBadges as badge (badge.label)} - {#if badge.variant === "solid"} - {badge.label} - {:else} - - {badge.label} - - {/if} - {/each} -
- {/if} + {#if card.highlightBadges} +
+ {#each card.highlightBadges as badge (badge.label)} + {#if badge.variant === 'solid'} + {badge.label} + {:else} + + {badge.label} + + {/if} + {/each} +
+ {/if} -
- {#if card.href && !card.disabled} - - {card.ctaLabel} - - {:else} - - {/if} -
-
- {/each} -
+
+ {#if card.href && !card.disabled} + + {card.ctaLabel} + + {:else} + + {/if} +
+
+ {/each} +
-
-
-
-
- - - -
-
-

Área Restrita

-

- Esta área é exclusiva da equipe de Tecnologia da Informação. Garanta - que apenas usuários autorizados acessem o Painel Administrativo e - mantenha suas credenciais em segurança. -

-
-
-
+
+
+
+
+ + + +
+
+

Área Restrita

+

+ Esta área é exclusiva da equipe de Tecnologia da Informação. Garanta que apenas usuários + autorizados acessem o Painel Administrativo e mantenha suas credenciais em segurança. +

+
+
+
diff --git a/apps/web/src/routes/(dashboard)/ti/central-chamados/+page.svelte b/apps/web/src/routes/(dashboard)/ti/central-chamados/+page.svelte new file mode 100644 index 0000000..a8b1ddf --- /dev/null +++ b/apps/web/src/routes/(dashboard)/ti/central-chamados/+page.svelte @@ -0,0 +1,1098 @@ + + +
+
+
+

Total de chamados

+

{estatisticas.total ?? 0}

+
+
+

Abertos

+

{estatisticas.abertos ?? 0}

+
+
+

Em andamento

+

{estatisticas.emAndamento ?? 0}

+
+
+

Vencidos/Cancelados

+

{estatisticas.vencidos ?? 0}

+
+
+ + +
+
+
+

Performance de SLA

+

Monitoramento em tempo real do cumprimento de SLA por prioridade

+
+ {#if dadosSlaGraficoQuery !== undefined && dadosSlaGraficoQuery !== null} + {@const dadosSla = typeof dadosSlaGraficoQuery === 'object' && 'data' in dadosSlaGraficoQuery + ? dadosSlaGraficoQuery.data + : (typeof dadosSlaGraficoQuery === 'object' && 'taxaCumprimento' in dadosSlaGraficoQuery + ? dadosSlaGraficoQuery + : null)} + {#if dadosSla} +
+
+

Taxa de Cumprimento

+

+ {dadosSla.taxaCumprimento}% +

+
+
+

Última atualização

+

+ {new Date(dadosSla.atualizadoEm).toLocaleTimeString('pt-BR')} +

+
+
+ {/if} + {/if} +
+ + {#if dadosSlaGraficoQuery === undefined || dadosSlaGraficoQuery === null} +
+ +
+ {:else} + {@const dadosSla = typeof dadosSlaGraficoQuery === 'object' && 'data' in dadosSlaGraficoQuery + ? dadosSlaGraficoQuery.data + : (typeof dadosSlaGraficoQuery === 'object' && 'taxaCumprimento' in dadosSlaGraficoQuery + ? dadosSlaGraficoQuery + : null)} + {#if dadosSla} +
+
+

Dentro do Prazo

+

{dadosSla.statusSla.dentroPrazo}

+
+
+

Próximo Vencimento

+

{dadosSla.statusSla.proximoVencimento}

+
+
+

Vencidos

+

{dadosSla.statusSla.vencido}

+
+
+

Sem Prazo

+

{dadosSla.statusSla.semPrazo}

+
+
+ + {:else} +
+

Carregando dados de SLA...

+
+ {/if} + {/if} +
+ +
+
+
+

Painel de chamados

+

+ Filtros por status, responsável e setor. +

+
+
+ + + +
+
+ +
+ + + + + + + + + + + + + {#if carregandoChamados} + + + + {:else if tickets.length === 0} + + + + {:else} + {#each tickets as ticket (ticket._id)} + selecionarTicket(ticket._id)} + > + + + + + + + + {/each} + {/if} + +
TicketTipoStatusResponsávelPrioridadePrazo
+
+ +
+
+ Nenhum chamado encontrado. +
+
{ticket.numero}
+
{ticket.solicitanteNome}
+
{ticket.tipo} + {getStatusLabel(ticket.status)} + {(ticket as any).responsavelNome ?? ticket.setorResponsavel ?? "—"}{ticket.prioridade} + {ticket.prazoConclusao ? prazoRestante(ticket.prazoConclusao) : "--"} +
+
+
+ +
+
+

Detalhes do chamado

+ {#if !detalheSelecionado} +

Selecione um chamado na tabela.

+ {:else} +
+
+
+

Solicitante

+

{detalheSelecionado.solicitanteNome}

+
+ + {getStatusLabel(detalheSelecionado.status)} + +
+

{detalheSelecionado.descricao}

+
+
+

Prazo resposta

+

{prazoRestante(detalheSelecionado.prazoResposta) ?? "--"}

+
+
+

Prazo conclusão

+

{prazoRestante(detalheSelecionado.prazoConclusao) ?? "--"}

+
+
+
+ +
+
+ {/if} +
+ +
+

Atribuir responsável

+
+ + + {#if assignFeedback} +
+ {assignFeedback} +
+ {/if} + +
+ + +
+

Prorrogar prazo

+

Recurso exclusivo para a equipe de TI

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ {#if prorrogacaoFeedback} +
+ {prorrogacaoFeedback} +
+ {/if} + +
+
+
+
+ + +
+
+
+

SLAs Configurados

+

Visualize todos os SLAs ativos com seus tempos e configurações

+
+
+ + {#if slaConfigsQuery === undefined || slaConfigsQuery === null || ('data' in slaConfigsQuery && slaConfigsQuery.data === undefined)} +
+ +
+ {:else} + {@const slaConfigs = ('data' in slaConfigsQuery && slaConfigsQuery.data !== undefined) + ? (Array.isArray(slaConfigsQuery.data) ? slaConfigsQuery.data : []) + : (Array.isArray(slaConfigsQuery) ? slaConfigsQuery : [])} + {@const slaConfigsAtivos = slaConfigs.filter((s: SlaConfig) => s.ativo)} + {@const slaConfigsPorPrioridadeCount = { + baixa: slaConfigsAtivos.filter((s: SlaConfig) => s.prioridade === 'baixa').length, + media: slaConfigsAtivos.filter((s: SlaConfig) => s.prioridade === 'media').length, + alta: slaConfigsAtivos.filter((s: SlaConfig) => s.prioridade === 'alta').length, + critica: slaConfigsAtivos.filter((s: SlaConfig) => s.prioridade === 'critica').length, + }} + + {#if slaConfigsAtivos.length === 0} +
+

Nenhum SLA configurado

+

Configure SLAs para cada prioridade na seção abaixo

+
+ {:else} + +
+
+
{slaConfigsAtivos.length}
+
Total de SLAs
+
+
+
{slaConfigsPorPrioridadeCount.baixa}
+
Prioridade Baixa
+
+
+
{slaConfigsPorPrioridadeCount.media}
+
Prioridade Média
+
+
+
{slaConfigsPorPrioridadeCount.alta + slaConfigsPorPrioridadeCount.critica}
+
Prioridade Alta/Crítica
+
+
+ + +
+ + + + + + + + + + + + + + + {#each slaConfigsAtivos as sla (sla._id)} + + + + + + + + + + + {/each} + +
NomePrioridadeTempo de RespostaTempo de ConclusãoAuto-encerramentoAlerta AntecedênciaStatusAções
+
{sla.nome}
+ {#if sla.descricao} +
{sla.descricao}
+ {/if} +
+ + {sla.prioridade} + + +
+ {sla.tempoRespostaHoras}h + {#if sla.tempoRespostaHoras >= 24} + + ({Math.floor(sla.tempoRespostaHoras / 24)}d {sla.tempoRespostaHoras % 24}h) + + {/if} +
+
+
+ {sla.tempoConclusaoHoras}h + {#if sla.tempoConclusaoHoras >= 24} + + ({Math.floor(sla.tempoConclusaoHoras / 24)}d {sla.tempoConclusaoHoras % 24}h) + + {/if} +
+
+ {#if sla.tempoEncerramentoHoras} +
+ {sla.tempoEncerramentoHoras}h + {#if sla.tempoEncerramentoHoras >= 24} + + ({Math.floor(sla.tempoEncerramentoHoras / 24)}d {sla.tempoEncerramentoHoras % 24}h) + + {/if} +
+ {:else} + Não configurado + {/if} +
+
+ {sla.alertaAntecedenciaHoras}h + antes +
+
+ + + Ativo + + +
+ + +
+
+
+ {/if} + {/if} +
+ + +
+
+

Configuração de SLA por Prioridade

+

Configure SLAs separados para cada nível de prioridade

+
+ + +
+ {#each ["baixa", "media", "alta", "critica"] as prioridade} + {@const slaAtual = slaConfigsPorPrioridade[prioridade]} +
+
+

{prioridade}

+ {#if slaAtual} + Configurado + {:else} + Não configurado + {/if} +
+ {#if slaAtual} +
+
+ Resposta: + {slaAtual.tempoRespostaHoras}h +
+
+ Conclusão: + {slaAtual.tempoConclusaoHoras}h +
+ {#if slaAtual.tempoEncerramentoHoras} +
+ Auto-encerramento: + {slaAtual.tempoEncerramentoHoras}h +
+ {/if} +
+ Alerta: + {slaAtual.alertaAntecedenciaHoras}h antes +
+
+
+ + +
+ {:else} + + {/if} +
+ {/each} +
+ + +
+

+ {slaForm.slaId ? "Editar" : "Novo"} SLA - Prioridade {slaForm.prioridade.charAt(0).toUpperCase() + slaForm.prioridade.slice(1)} +

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ {#if slaFeedback} +
+

{slaFeedback}

+
+ {/if} +
+ + {#if slaForm.slaId} + + {/if} +
+
+
+ + + {#if slaParaExcluir} + + {/if} + +
+ diff --git a/apps/web/src/routes/(dashboard)/ti/cibersecurity/+page.svelte b/apps/web/src/routes/(dashboard)/ti/cibersecurity/+page.svelte new file mode 100644 index 0000000..b31d71b --- /dev/null +++ b/apps/web/src/routes/(dashboard)/ti/cibersecurity/+page.svelte @@ -0,0 +1,40 @@ + + + + Cibersecurity SGSE • Wizcard TI + + +
+
+
+

+ Cibersecurity • SGSE +

+

Segurança Avançada

+

+ Detecta DDoS, SQLi, ataques avançados e comportamentos anômalos em tempo real. Permite + bloquear IPs/portas, gerar relatórios refinados, configurar políticas e manter a operação do + SGSE blindada. +

+
+ Voltar para TI +
+ + {#if browser && Comp} + + {:else} +
+ Carregando módulo de cibersegurança… +
+ {/if} +
diff --git a/apps/web/src/routes/(dashboard)/ti/painel-permissoes/+page.svelte b/apps/web/src/routes/(dashboard)/ti/painel-permissoes/+page.svelte index 130a768..495a86d 100644 --- a/apps/web/src/routes/(dashboard)/ti/painel-permissoes/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/painel-permissoes/+page.svelte @@ -543,7 +543,7 @@ - {:else if catalogoQuery.data} + {:else if catalogoQuery.data && catalogoQuery.data.length > 0}
{#each catalogoQuery.data as item (item.recurso)} {@const recursoExpandido = isRecursoExpandido(roleId, item.recurso)} @@ -576,28 +576,40 @@ {#if recursoExpandido}
-
- {#each ['ver', 'listar', 'criar', 'editar', 'excluir'] as acao (acao)} - - {/each} -
+ {#if item.acoes.length === 0} +

+ Nenhuma permissão cadastrada para este recurso. +

+ {:else} +
+ {#each item.acoes as acao (acao)} + + {/each} +
+ {/if}
{/if}
{/each} + {:else} +
+ + Nenhuma permissão cadastrada ainda. Use o botão “Criar permissão” para começar. + +
{/if} diff --git a/apps/web/src/routes/(dashboard)/ti/perfis/+page.svelte b/apps/web/src/routes/(dashboard)/ti/perfis/+page.svelte index c3017c3..544b7a4 100644 --- a/apps/web/src/routes/(dashboard)/ti/perfis/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/perfis/+page.svelte @@ -41,19 +41,15 @@ const stats = $derived.by(() => { if (carregando) return null; - const porNivel = { - 0: roles.filter((r) => r.nivel === 0).length, - 1: roles.filter((r) => r.nivel === 1).length, - 2: roles.filter((r) => r.nivel === 2).length, - 3: roles.filter((r) => r.nivel >= 3).length - }; + const nivelMaximo = roles.filter((r) => r.nivel === 0).length; + const nivelAdministrativo = roles.filter((r) => r.nivel === 1).length; + const niveisLegado = roles.filter((r) => r.nivel > 1).length; return { total: roles.length, - nivelMaximo: porNivel[0], - nivelAlto: porNivel[1], - nivelMedio: porNivel[2], - nivelBaixo: porNivel[3], + nivelMaximo, + nivelAdministrativo, + niveisLegado, comSetor: roles.filter((r) => r.setor).length }; }); @@ -78,10 +74,11 @@ // Filtro por nível if (filtroNivel !== '') { - if (filtroNivel === 3) { - resultado = resultado.filter((r) => r.nivel >= 3); - } else { + if (filtroNivel === 0 || filtroNivel === 1) { resultado = resultado.filter((r) => r.nivel === filtroNivel); + } else { + // Qualquer outro valor é considerado legado + resultado = resultado.filter((r) => r.nivel > 1); } } @@ -96,22 +93,19 @@ function obterCorNivel(nivel: number): string { if (nivel === 0) return 'badge-error'; if (nivel === 1) return 'badge-warning'; - if (nivel === 2) return 'badge-info'; + // Níveis > 1 são considerados legado return 'badge-ghost'; } function obterTextoNivel(nivel: number): string { if (nivel === 0) return 'Máximo'; - if (nivel === 1) return 'Alto'; - if (nivel === 2) return 'Médio'; - if (nivel === 3) return 'Baixo'; - return `Nível ${nivel}`; + if (nivel === 1) return 'Administrativo'; + return `Legado (${nivel})`; } function obterCorCardNivel(nivel: number): string { if (nivel === 0) return 'border-l-4 border-error'; if (nivel === 1) return 'border-l-4 border-warning'; - if (nivel === 2) return 'border-l-4 border-info'; return 'border-l-4 border-base-300'; } @@ -144,7 +138,7 @@ ); - +
@@ -176,31 +170,24 @@ {#if stats} -
+
- 0 ? ((stats.comSetor / stats.total) * 100).toFixed(0) + '% do total' @@ -209,6 +196,18 @@ color="secondary" />
+ {#if stats.niveisLegado > 0} +
+ +
+

Perfis com níveis legados

+

+ Existem {stats.niveisLegado} perfis com nível acima de 1. Esses perfis continuarão + sendo tratados como nível 1 (administrativo) após a migração. +

+
+
+ {/if} {/if} diff --git a/apps/web/src/routes/(dashboard)/ti/personalizar-permissoes/+page.svelte b/apps/web/src/routes/(dashboard)/ti/personalizar-permissoes/+page.svelte index 51ca3c6..2599cda 100644 --- a/apps/web/src/routes/(dashboard)/ti/personalizar-permissoes/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/personalizar-permissoes/+page.svelte @@ -4,7 +4,7 @@ import { resolve } from '$app/paths'; - +