Ajustes final etapa1 #69

Merged
deyvisonwanderley merged 22 commits from ajustes_final_etapa1 into master 2025-12-22 17:29:58 +00:00
49 changed files with 1860 additions and 7779 deletions
Showing only changes of commit 60b53dac74 - Show all commits

View File

@@ -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<Array<string | { content: string; styles?: { fillColor?: number[]; textColor?: number[]; fontStyle?: string } }>>,
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 {

View File

@@ -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;

View File

@@ -85,3 +85,4 @@

View File

@@ -85,3 +85,4 @@

View File

@@ -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<DataModel> {
// 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<DataModel>;
}
/**
* 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,

View File

@@ -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<DataModel>(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<DataModel> {
// 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<DataModel>;
}
export const createAuth = (
ctx: GenericCtx<DataModel>,
{ 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,