diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile index 71f9ef7..073d6e9 100644 --- a/apps/web/Dockerfile +++ b/apps/web/Dockerfile @@ -35,21 +35,17 @@ FROM oven/bun:1-slim AS production # Set working directory to match builder structure WORKDIR /app -# Create non-root user -RUN addgroup --system --gid 1001 sveltekit -RUN adduser --system --uid 1001 sveltekit - # Copy root node_modules (contains hoisted dependencies) -COPY --from=builder --chown=sveltekit:sveltekit /app/node_modules ./node_modules +COPY --from=builder /app/node_modules ./node_modules # Copy built application and workspace files -COPY --from=builder --chown=sveltekit:sveltekit /app/apps/web/build ./apps/web/build -COPY --from=builder --chown=sveltekit:sveltekit /app/apps/web/package.json ./apps/web/package.json +COPY --from=builder /app/apps/web/build ./apps/web/build +COPY --from=builder /app/apps/web/package.json ./apps/web/package.json # Copy workspace node_modules (contains symlinks to root node_modules) -COPY --from=builder --chown=sveltekit:sveltekit /app/apps/web/node_modules ./apps/web/node_modules +COPY --from=builder /app/apps/web/node_modules ./apps/web/node_modules # Copy any additional files needed for runtime -COPY --from=builder --chown=sveltekit:sveltekit /app/apps/web/static ./apps/web/static +COPY --from=builder /app/apps/web/static ./apps/web/static # Switch to non-root user USER sveltekit diff --git a/apps/web/src/routes/(dashboard)/gestao-pessoas/gestao-ausencias/+page.svelte b/apps/web/src/routes/(dashboard)/gestao-pessoas/gestao-ausencias/+page.svelte index bad2544..16875cf 100644 --- a/apps/web/src/routes/(dashboard)/gestao-pessoas/gestao-ausencias/+page.svelte +++ b/apps/web/src/routes/(dashboard)/gestao-pessoas/gestao-ausencias/+page.svelte @@ -5,7 +5,6 @@ import { goto } from '$app/navigation'; import { resolve } from '$app/paths'; import AprovarAusencias from '$lib/components/AprovarAusencias.svelte'; - import type { Id } from '@sgse-app/backend/convex/_generated/dataModel'; import { parseLocalDate } from '$lib/utils/datas'; import jsPDF from 'jspdf'; import autoTable from 'jspdf-autotable'; @@ -15,13 +14,14 @@ import logoGovPE from '$lib/assets/logo_governo_PE.png'; import { FileDown, FileSpreadsheet } from 'lucide-svelte'; import { toast } from 'svelte-sonner'; + import { SvelteDate } from 'svelte/reactivity'; const client = useConvexClient(); const currentUser = useQuery(api.auth.getCurrentUser, {}); // Buscar TODAS as solicitações de ausências const todasAusenciasQuery = useQuery(api.ausencias.listarTodas, {}); - + // Buscar funcionários para filtro const funcionariosQuery = useQuery(api.funcionarios.getAll, {}); @@ -34,7 +34,9 @@ const ausencias = $derived(todasAusenciasQuery?.data || []); const funcionarios = $derived( - Array.isArray(funcionariosQuery?.data) ? funcionariosQuery.data : funcionariosQuery?.data?.data || [] + Array.isArray(funcionariosQuery?.data) + ? funcionariosQuery.data + : funcionariosQuery?.data?.data || [] ); // Filtrar solicitações @@ -42,26 +44,26 @@ ausencias.filter((a) => { // Filtro de status if (filtroStatus !== 'todos' && a.status !== filtroStatus) return false; - + // Filtro por funcionário if (filtroFuncionario) { if (a.funcionario?._id !== filtroFuncionario) return false; } - + // Filtro por período if (filtroPeriodoInicio) { const inicioFiltro = new Date(filtroPeriodoInicio); const inicioAusencia = parseLocalDate(a.dataInicio); if (inicioAusencia < inicioFiltro) return false; } - + if (filtroPeriodoFim) { - const fimFiltro = new Date(filtroPeriodoFim); + const fimFiltro = new SvelteDate(filtroPeriodoFim); fimFiltro.setHours(23, 59, 59, 999); // Incluir o dia inteiro const fimAusencia = parseLocalDate(a.dataFim); if (fimAusencia > fimFiltro) return false; } - + return true; }) ); @@ -690,7 +692,7 @@ bind:value={filtroFuncionario} > - {#each funcionarios as funcionario} + {#each funcionarios as funcionario (funcionario._id)} {/each} @@ -761,7 +763,7 @@ - {#each ausenciasFiltradas as ausencia} + {#each ausenciasFiltradas as ausencia (ausencia._id)} {ausencia.funcionario?.nome || 'N/A'} @@ -769,7 +771,7 @@ {#if ausencia.time}
import { api } from '@sgse-app/backend/convex/_generated/api'; - import type { Id } from '@sgse-app/backend/convex/_generated/dataModel'; import { useConvexClient, useQuery } from 'convex-svelte'; import { goto } from '$app/navigation'; import { resolve } from '$app/paths'; import AprovarAusencias from '$lib/components/AprovarAusencias.svelte'; import type { Id } from '@sgse-app/backend/convex/_generated/dataModel'; - import { Clock, ArrowLeft, FileText, CheckCircle, XCircle, Info, Eye, FileDown, FileSpreadsheet } from 'lucide-svelte'; + import { + Clock, + ArrowLeft, + FileText, + CheckCircle, + XCircle, + Info, + Eye, + FileDown, + FileSpreadsheet + } from 'lucide-svelte'; import { parseLocalDate } from '$lib/utils/datas'; import jsPDF from 'jspdf'; import autoTable from 'jspdf-autotable'; @@ -15,13 +24,14 @@ import ExcelJS from 'exceljs'; import logoGovPE from '$lib/assets/logo_governo_PE.png'; import { toast } from 'svelte-sonner'; + import { SvelteDate } from 'svelte/reactivity'; const client = useConvexClient(); const currentUser = useQuery(api.auth.getCurrentUser, {}); // Buscar TODAS as solicitações de ausências (Dashboard RH) const todasAusenciasQuery = useQuery(api.ausencias.listarTodas, {}); - + // Buscar funcionários para filtro const funcionariosQuery = useQuery(api.funcionarios.getAll, {}); @@ -34,7 +44,9 @@ const ausencias = $derived(todasAusenciasQuery?.data || []); const funcionarios = $derived( - Array.isArray(funcionariosQuery?.data) ? funcionariosQuery.data : funcionariosQuery?.data?.data || [] + Array.isArray(funcionariosQuery?.data) + ? funcionariosQuery.data + : funcionariosQuery?.data?.data || [] ); // Filtrar solicitações @@ -42,26 +54,26 @@ ausencias.filter((a) => { // Filtro de status if (filtroStatus !== 'todos' && a.status !== filtroStatus) return false; - + // Filtro por funcionário if (filtroFuncionario) { if (a.funcionario?._id !== filtroFuncionario) return false; } - + // Filtro por período if (filtroPeriodoInicio) { const inicioFiltro = new Date(filtroPeriodoInicio); const inicioAusencia = parseLocalDate(a.dataInicio); if (inicioAusencia < inicioFiltro) return false; } - + if (filtroPeriodoFim) { - const fimFiltro = new Date(filtroPeriodoFim); + const fimFiltro = new SvelteDate(filtroPeriodoFim); fimFiltro.setHours(23, 59, 59, 999); // Incluir o dia inteiro const fimAusencia = parseLocalDate(a.dataFim); if (fimAusencia > fimFiltro) return false; } - + return true; }) ); @@ -679,7 +691,7 @@ {#if ausencia.time}
); + let funcionarioId = $derived(page.params.funcionarioId as Id<'funcionarios'>); // Queries const funcionarioQuery = useQuery( diff --git a/apps/web/src/routes/(dashboard)/secretaria-executiva/gestao-ausencias/+page.svelte b/apps/web/src/routes/(dashboard)/secretaria-executiva/gestao-ausencias/+page.svelte index 03d1a5e..1a1b006 100644 --- a/apps/web/src/routes/(dashboard)/secretaria-executiva/gestao-ausencias/+page.svelte +++ b/apps/web/src/routes/(dashboard)/secretaria-executiva/gestao-ausencias/+page.svelte @@ -5,8 +5,17 @@ import { goto } from '$app/navigation'; import { resolve } from '$app/paths'; import AprovarAusencias from '$lib/components/AprovarAusencias.svelte'; - import type { Id } from '@sgse-app/backend/convex/_generated/dataModel'; - import { Clock, ArrowLeft, FileText, CheckCircle, XCircle, Info, Eye, FileDown, FileSpreadsheet } from 'lucide-svelte'; + import { + Clock, + ArrowLeft, + FileText, + CheckCircle, + XCircle, + Info, + Eye, + FileDown, + FileSpreadsheet + } from 'lucide-svelte'; import { parseLocalDate } from '$lib/utils/datas'; import jsPDF from 'jspdf'; import autoTable from 'jspdf-autotable'; @@ -15,13 +24,14 @@ import ExcelJS from 'exceljs'; import logoGovPE from '$lib/assets/logo_governo_PE.png'; import { toast } from 'svelte-sonner'; + import { SvelteDate } from 'svelte/reactivity'; const client = useConvexClient(); const currentUser = useQuery(api.auth.getCurrentUser, {}); // Buscar TODAS as solicitações de ausências const todasAusenciasQuery = useQuery(api.ausencias.listarTodas, {}); - + // Buscar funcionários para filtro const funcionariosQuery = useQuery(api.funcionarios.getAll, {}); @@ -34,7 +44,9 @@ let ausencias = $derived(todasAusenciasQuery?.data || []); let funcionarios = $derived( - Array.isArray(funcionariosQuery?.data) ? funcionariosQuery.data : funcionariosQuery?.data?.data || [] + Array.isArray(funcionariosQuery?.data) + ? funcionariosQuery.data + : funcionariosQuery?.data?.data || [] ); // Filtrar solicitações @@ -42,26 +54,26 @@ ausencias.filter((a) => { // Filtro de status if (filtroStatus !== 'todos' && a.status !== filtroStatus) return false; - + // Filtro por funcionário if (filtroFuncionario) { if (a.funcionario?._id !== filtroFuncionario) return false; } - + // Filtro por período if (filtroPeriodoInicio) { const inicioFiltro = new Date(filtroPeriodoInicio); const inicioAusencia = parseLocalDate(a.dataInicio); if (inicioAusencia < inicioFiltro) return false; } - + if (filtroPeriodoFim) { - const fimFiltro = new Date(filtroPeriodoFim); + const fimFiltro = new SvelteDate(filtroPeriodoFim); fimFiltro.setHours(23, 59, 59, 999); // Incluir o dia inteiro const fimAusencia = parseLocalDate(a.dataFim); if (fimAusencia > fimFiltro) return false; } - + return true; }) ); @@ -612,7 +624,7 @@ bind:value={filtroFuncionario} > - {#each funcionarios as funcionario} + {#each funcionarios as funcionario (funcionario._id)} {/each} @@ -671,7 +683,7 @@ - {#each ausenciasFiltradas as ausencia} + {#each ausenciasFiltradas as ausencia (ausencia._id)} {ausencia.funcionario?.nome || 'N/A'} diff --git a/apps/web/src/routes/(dashboard)/ti/notificacoes/templates/novo/+page.svelte b/apps/web/src/routes/(dashboard)/ti/notificacoes/templates/novo/+page.svelte index 3ec669b..57096b4 100644 --- a/apps/web/src/routes/(dashboard)/ti/notificacoes/templates/novo/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/notificacoes/templates/novo/+page.svelte @@ -7,58 +7,54 @@ import { resolve } from '$app/paths'; import { FileText } from 'lucide-svelte'; -const client = useConvexClient(); -const currentUser = useQuery( - api.auth.getCurrentUser as FunctionReference<"query">, -); + const client = useConvexClient(); + const currentUser = useQuery(api.auth.getCurrentUser as FunctionReference<'query'>); -let codigo = $state(""); -let nome = $state(""); -let titulo = $state(""); -let corpo = $state(""); -let categoria = $state<"email" | "chat" | "ambos">("email"); -let variaveisTexto = $state(""); -let tagsTexto = $state(""); -let criando = $state(false); -let mensagem = $state<{ - tipo: "success" | "error" | "info"; - texto: string; -} | null>(null); + let codigo = $state(''); + let nome = $state(''); + let titulo = $state(''); + let corpo = $state(''); + let categoria = $state<'email' | 'chat' | 'ambos'>('email'); + let variaveisTexto = $state(''); + let tagsTexto = $state(''); + let criando = $state(false); + let mensagem = $state<{ + tipo: 'success' | 'error' | 'info'; + texto: string; + } | null>(null); -function mostrarMensagem(tipo: "success" | "error" | "info", texto: string) { - mensagem = { tipo, texto }; - setTimeout(() => { - mensagem = null; - }, 5000); -} - -function parseLista(input: string): string[] { - return input - .split(/[;,\n]/) - .map((v) => v.trim()) - .filter((v) => v.length > 0); -} - -async function salvar() { - if (!currentUser.data) { - mostrarMensagem("error", "Usuário não autenticado."); - return; + function mostrarMensagem(tipo: 'success' | 'error' | 'info', texto: string) { + mensagem = { tipo, texto }; + setTimeout(() => { + mensagem = null; + }, 5000); } - if (!codigo.trim() || !nome.trim() || !titulo.trim() || !corpo.trim()) { - mostrarMensagem("error", "Preencha todos os campos obrigatórios."); - return; + function parseLista(input: string): string[] { + return input + .split(/[;,\n]/) + .map((v) => v.trim()) + .filter((v) => v.length > 0); } - const codigoNormalizado = codigo.trim().toUpperCase().replace(/\s+/g, "_"); - const variaveis = parseLista(variaveisTexto); - const tags = parseLista(tagsTexto); + async function salvar() { + if (!currentUser.data) { + mostrarMensagem('error', 'Usuário não autenticado.'); + return; + } - try { - criando = true; - const resultado = await client.mutation( - api.templatesMensagens.criarTemplate, - { + if (!codigo.trim() || !nome.trim() || !titulo.trim() || !corpo.trim()) { + mostrarMensagem('error', 'Preencha todos os campos obrigatórios.'); + return; + } + + const codigoNormalizado = codigo.trim().toUpperCase().replace(/\s+/g, '_'); + const variaveis = parseLista(variaveisTexto); + const tags = parseLista(tagsTexto); + + try { + criando = true; + const resultado = await client.mutation(api.templatesMensagens.criarTemplate, { codigo: codigoNormalizado, nome: nome.trim(), titulo: titulo.trim(), @@ -66,39 +62,38 @@ async function salvar() { variaveis, categoria, tags, - criadoPorId: currentUser.data._id as Id<"usuarios">, - }, - ); + criadoPorId: currentUser.data._id as Id<'usuarios'> + }); - if (resultado.sucesso) { - mostrarMensagem("success", "Template criado com sucesso!"); - await goto(resolve("/ti/notificacoes/templates")); - } else { - mostrarMensagem("error", resultado.erro || "Erro ao criar template."); + if (resultado.sucesso) { + mostrarMensagem('success', 'Template criado com sucesso!'); + await goto(resolve('/ti/notificacoes/templates')); + } else { + mostrarMensagem('error', resultado.erro || 'Erro ao criar template.'); + } + } catch (error) { + const erro = error instanceof Error ? error.message : 'Erro desconhecido'; + mostrarMensagem('error', `Erro ao criar template: ${erro}`); + } finally { + criando = false; } - } catch (error) { - const erro = error instanceof Error ? error.message : "Erro desconhecido"; - mostrarMensagem("error", `Erro ao criar template: ${erro}`); - } finally { - criando = false; } -}
-
+

Novo Template

- Defina o texto base que será usado em chat e na versão - HTML de email com o estilo padrão do SGSE. + Defina o texto base que será usado em chat e na + versão HTML de email com o estilo padrão do SGSE.

@@ -125,129 +120,131 @@ async function salvar() {
{/if} -
+
-
+
+
+ + +
+
+ + +
+
+
-
+
-
-
- -
- - -
- -
- - - -
- -
- - - -
- -
-
- - -
-
- - -
-
-
- Cancelar - +
+ + +
+ + Este texto será usado diretamente nas mensagens de chat. Para email, o sistema gera automaticamente um + layout HTML padronizado com logo e assinatura. + +
+
+ +
+
+ + + +
+
+ + +
+
+ +
+ Cancelar + +
diff --git a/package.json b/package.json index 3f6625b..26912c9 100644 --- a/package.json +++ b/package.json @@ -48,5 +48,5 @@ "svelte-chartjs": "^3.1.5", "svelte-sonner": "^1.0.7" }, - "packageManager": "bun@1.3.4" + "packageManager": "bun@1.3.5" }