diff --git a/apps/web/src/lib/utils/fichaPontoPDF.ts b/apps/web/src/lib/utils/fichaPontoPDF.ts index 9f98f87..7c0a09c 100644 --- a/apps/web/src/lib/utils/fichaPontoPDF.ts +++ b/apps/web/src/lib/utils/fichaPontoPDF.ts @@ -372,29 +372,59 @@ export function adicionarLegenda(doc: jsPDF, yPosition: number): number { doc.text('LEGENDA', 15, yPosition); yPosition += 10; - const legendaData: Array<[string, string]> = [ - ['Cor de Fundo - Branco', 'Dia normal'], - ['Cor de Fundo - Azul Claro', 'Dia com atestado médico'], - ['Cor de Fundo - Amarelo Claro', 'Dia com ausência aprovada'], - ['Cor de Fundo - Verde Claro', 'Dia abonado'], - ['Cor de Fundo - Cinza Claro', 'Dia não computado (dispensa/férias)'], - ['Cor de Fundo - Laranja Claro', 'Dia com inconsistência'], - ['Texto Verde', 'Saldo positivo / Registro marcado'], - ['Texto Vermelho', 'Saldo negativo / Registro não marcado'], - ['✓', 'Registro marcado'], - ['✗', 'Registro não marcado'], - ['⚠', 'Inconsistência detectada'], - ['🏥', 'Atestado médico'], - ['🚫', 'Ausência'], - ['📋', 'Licença'], - ['✅', 'Abonado'], - ['⏸', 'Não computado'] + // Corpo da legenda com cores aplicadas e siglas intuitivas + const legendaData: Array< + [ + string | { content: string; styles?: { fillColor?: number[]; textColor?: number[]; fontStyle?: string } }, + string + ] + > = [ + [ + { content: 'Fundo Branco (DN)', styles: { fillColor: [255, 255, 255] } }, + 'Dia normal' + ], + [ + { content: 'Fundo Azul Claro (AT)', styles: { fillColor: [230, 240, 255] } }, + 'Dia com atestado médico' + ], + [ + { content: 'Fundo Amarelo Claro (AUS)', styles: { fillColor: [255, 255, 230] } }, + 'Dia com ausência aprovada' + ], + [ + { content: 'Fundo Verde Claro (ABO)', styles: { fillColor: [230, 255, 230] } }, + 'Dia abonado' + ], + [ + { content: 'Fundo Cinza Claro (NC)', styles: { fillColor: [240, 240, 240] } }, + 'Dia não computado (dispensa/férias)' + ], + [ + { content: 'Fundo Laranja Claro (INC)', styles: { fillColor: [255, 240, 230] } }, + 'Dia com inconsistência' + ], + [ + { content: 'Texto Verde', styles: { textColor: [0, 128, 0], fontStyle: 'bold' } }, + 'Saldo positivo / Registro marcado' + ], + [ + { content: 'Texto Vermelho', styles: { textColor: [200, 0, 0], fontStyle: 'bold' } }, + 'Saldo negativo / Registro não marcado' + ], + ['RM', 'Registro marcado'], + ['RNM', 'Registro não marcado'], + ['INC', 'Inconsistência detectada'], + ['AT', 'Atestado médico'], + ['AUS', 'Ausência'], + ['LIC', 'Licença'], + ['ABO', 'Abonado'], + ['NC', 'Não computado'] ]; autoTable(doc, { startY: yPosition, head: [['Símbolo/Cor', 'Significado']], - body: legendaData, + body: legendaData as unknown as Array>, theme: 'striped', headStyles: { fillColor: [60, 60, 60], @@ -410,7 +440,25 @@ export function adicionarLegenda(doc: jsPDF, yPosition: number): number { 1: { cellWidth: 110 } }, margin: { left: 15, right: 15 }, - styles: { cellPadding: 3 } + styles: { cellPadding: 3 }, + didParseCell: (data) => { + // aplicar estilos de cor/texto definidos nas células da primeira coluna + if (data.section === 'body' && data.column.index === 0) { + const raw = data.row.raw?.[0]; + if (raw && typeof raw === 'object' && 'styles' in raw && raw.styles) { + const styles = raw.styles as { fillColor?: number[]; textColor?: number[]; fontStyle?: string }; + if (styles.fillColor) { + data.cell.styles.fillColor = styles.fillColor; + } + if (styles.textColor) { + data.cell.styles.textColor = styles.textColor; + } + if (styles.fontStyle) { + data.cell.styles.fontStyle = styles.fontStyle; + } + } + } + } }); const finalYLegenda = (doc as JsPDFWithAutoTable).lastAutoTable?.finalY ?? yPosition + 10; @@ -446,3 +494,4 @@ export function adicionarRodape(doc: jsPDF): void { + diff --git a/apps/web/src/lib/utils/ponto/processamento.ts b/apps/web/src/lib/utils/ponto/processamento.ts index 63a19a7..8f40993 100644 --- a/apps/web/src/lib/utils/ponto/processamento.ts +++ b/apps/web/src/lib/utils/ponto/processamento.ts @@ -628,25 +628,34 @@ export async function processarDadosFichaPonto( } // Calcular saldo acumulado para cada dia + // Agora consideramos todos os dias que possuem saldo diário, inclusive + // atestados, ausências e dias não computados, para que o resumo do período + // reflita qualquer trabalho realizado e a carga horária esperada. let saldoAcumulado = 0; for (const dia of diasProcessados) { - if (dia.computado && dia.saldoDiario) { + if (dia.saldoDiario) { saldoAcumulado += dia.saldoDiario.diferencaMinutos; } dia.saldoAcumulado = saldoAcumulado; } // Calcular resumo com formatações - const totalHorasTrabalhadas = diasProcessados - .filter((d) => d.computado) - .reduce((acc, d) => acc + (d.saldoDiario?.trabalhadoMinutos || 0), 0); - const totalHorasEsperadas = diasProcessados - .filter((d) => d.computado) - .reduce((acc, d) => acc + (d.saldoDiario?.esperadoMinutos || 0), 0); - const diferencaTotal = diasProcessados - .filter((d) => d.computado) - .reduce((acc, d) => acc + (d.saldoDiario?.diferencaMinutos || 0), 0); + // Total de horas trabalhadas e esperadas passa a considerar todos os dias, + // não apenas os marcados como "computados", para que trechos trabalhados + // em dias de ausência/dispensa também apareçam no resumo. + const totalHorasTrabalhadas = diasProcessados.reduce( + (acc, d) => acc + (d.saldoDiario?.trabalhadoMinutos || 0), + 0 + ); + const totalHorasEsperadas = diasProcessados.reduce( + (acc, d) => acc + (d.saldoDiario?.esperadoMinutos || 0), + 0 + ); + const diferencaTotal = diasProcessados.reduce( + (acc, d) => acc + (d.saldoDiario?.diferencaMinutos || 0), + 0 + ); const saldoPeriodo = diferencaTotal; const saldoFinal = diasProcessados.length > 0 ? diasProcessados[diasProcessados.length - 1]!.saldoAcumulado : 0; diff --git a/apps/web/src/routes/(dashboard)/+error.svelte b/apps/web/src/routes/(dashboard)/+error.svelte index 1c780c7..b17b2df 100644 --- a/apps/web/src/routes/(dashboard)/+error.svelte +++ b/apps/web/src/routes/(dashboard)/+error.svelte @@ -85,3 +85,4 @@ + diff --git a/apps/web/src/routes/+error.svelte b/apps/web/src/routes/+error.svelte index ae3a9ab..daecb76 100644 --- a/apps/web/src/routes/+error.svelte +++ b/apps/web/src/routes/+error.svelte @@ -85,3 +85,4 @@ + diff --git a/packages/backend/convex/autenticacao.ts b/packages/backend/convex/autenticacao.ts index d5b773e..0f1c381 100644 --- a/packages/backend/convex/autenticacao.ts +++ b/packages/backend/convex/autenticacao.ts @@ -1,6 +1,18 @@ import { v } from 'convex/values'; -import { mutation } from './_generated/server'; +import { mutation, type MutationCtx } from './_generated/server'; import { authComponent, updatePassword } from './auth'; +import type { GenericCtx } from '@convex-dev/better-auth'; +import type { DataModel } from './_generated/dataModel'; + +/** + * Helper para converter MutationCtx para GenericCtx do better-auth + * Os tipos são estruturalmente compatíveis, apenas há diferença nas definições de tipo + */ +function toGenericCtx(ctx: MutationCtx): GenericCtx { + // Os tipos são estruturalmente idênticos, apenas há diferença nas definições de tipo + // entre a versão do Convex usada pelo projeto e a usada pelo @convex-dev/better-auth + return ctx as unknown as GenericCtx; +} /** * Alterar senha do usuário autenticado @@ -19,7 +31,7 @@ export const alterarSenha = mutation({ handler: async (ctx, args) => { try { // Verificar se o usuário está autenticado - const authUser = await authComponent.safeGetAuthUser(ctx); + const authUser = await authComponent.safeGetAuthUser(toGenericCtx(ctx)); if (!authUser) { return { sucesso: false as const, diff --git a/packages/backend/convex/auth.ts b/packages/backend/convex/auth.ts index 74bcde5..9c1c877 100644 --- a/packages/backend/convex/auth.ts +++ b/packages/backend/convex/auth.ts @@ -3,7 +3,7 @@ import { convex } from '@convex-dev/better-auth/plugins'; import { betterAuth } from 'better-auth'; import { components } from './_generated/api'; import type { DataModel } from './_generated/dataModel'; -import { type MutationCtx, type QueryCtx, query } from './_generated/server'; +import { type MutationCtx, type QueryCtx, type ActionCtx, query } from './_generated/server'; // Usar SITE_URL se disponível, caso contrário usar CONVEX_SITE_URL ou um valor padrão const siteUrl = process.env.SITE_URL || process.env.CONVEX_SITE_URL || 'http://localhost:5173'; @@ -14,6 +14,22 @@ console.log('siteUrl:', siteUrl); // as well as helper methods for general use. export const authComponent = createClient(components.betterAuth); +/** + * Helper type para converter contextos do Convex para GenericCtx do better-auth + * Isso resolve incompatibilidade de tipos entre versões do Convex sem usar 'any' + */ +type ConvexCtx = QueryCtx | MutationCtx | ActionCtx; + +/** + * Função helper para converter contexto do Convex para GenericCtx do better-auth + * Os tipos são estruturalmente compatíveis, apenas há diferença nas definições de tipo + */ +function toGenericCtx(ctx: ConvexCtx): GenericCtx { + // Os tipos são estruturalmente idênticos, apenas há diferença nas definições de tipo + // entre a versão do Convex usada pelo projeto e a usada pelo @convex-dev/better-auth + return ctx as unknown as GenericCtx; +} + export const createAuth = ( ctx: GenericCtx, { optionsOnly } = { optionsOnly: false } @@ -45,7 +61,7 @@ export const getCurrentUser = query({ args: {}, handler: async (ctx) => { try { - const authUser = await authComponent.safeGetAuthUser(ctx); + const authUser = await authComponent.safeGetAuthUser(toGenericCtx(ctx)); if (!authUser) { return; } @@ -83,7 +99,7 @@ export const getCurrentUser = query({ }); export const getCurrentUserFunction = async (ctx: QueryCtx | MutationCtx) => { - const authUser = await authComponent.safeGetAuthUser(ctx); + const authUser = await authComponent.safeGetAuthUser(toGenericCtx(ctx)); if (!authUser) { return; } @@ -102,7 +118,7 @@ export const createAuthUser = async ( ctx: MutationCtx, args: { nome: string; email: string; password: string } ) => { - const { auth, headers } = await authComponent.getAuth(createAuth, ctx); + const { auth, headers } = await authComponent.getAuth(createAuth, toGenericCtx(ctx)); const result = await auth.api.signUpEmail({ headers, @@ -120,7 +136,7 @@ export const updatePassword = async ( ctx: MutationCtx, args: { newPassword: string; currentPassword: string } ) => { - const { auth, headers } = await authComponent.getAuth(createAuth, ctx); + const { auth, headers } = await authComponent.getAuth(createAuth, toGenericCtx(ctx)); await auth.api.changePassword({ headers,