refactor: update fichaPontoPDF and processamento to enhance legend styling and accumulate saldo for all days, improving report accuracy
This commit is contained in:
@@ -372,29 +372,59 @@ export function adicionarLegenda(doc: jsPDF, yPosition: number): number {
|
|||||||
doc.text('LEGENDA', 15, yPosition);
|
doc.text('LEGENDA', 15, yPosition);
|
||||||
yPosition += 10;
|
yPosition += 10;
|
||||||
|
|
||||||
const legendaData: Array<[string, string]> = [
|
// Corpo da legenda com cores aplicadas e siglas intuitivas
|
||||||
['Cor de Fundo - Branco', 'Dia normal'],
|
const legendaData: Array<
|
||||||
['Cor de Fundo - Azul Claro', 'Dia com atestado médico'],
|
[
|
||||||
['Cor de Fundo - Amarelo Claro', 'Dia com ausência aprovada'],
|
string | { content: string; styles?: { fillColor?: number[]; textColor?: number[]; fontStyle?: string } },
|
||||||
['Cor de Fundo - Verde Claro', 'Dia abonado'],
|
string
|
||||||
['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'],
|
{ content: 'Fundo Branco (DN)', styles: { fillColor: [255, 255, 255] } },
|
||||||
['✓', 'Registro marcado'],
|
'Dia normal'
|
||||||
['✗', 'Registro não marcado'],
|
],
|
||||||
['⚠', 'Inconsistência detectada'],
|
[
|
||||||
['🏥', 'Atestado médico'],
|
{ content: 'Fundo Azul Claro (AT)', styles: { fillColor: [230, 240, 255] } },
|
||||||
['🚫', 'Ausência'],
|
'Dia com atestado médico'
|
||||||
['📋', 'Licença'],
|
],
|
||||||
['✅', 'Abonado'],
|
[
|
||||||
['⏸', 'Não computado']
|
{ 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, {
|
autoTable(doc, {
|
||||||
startY: yPosition,
|
startY: yPosition,
|
||||||
head: [['Símbolo/Cor', 'Significado']],
|
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',
|
theme: 'striped',
|
||||||
headStyles: {
|
headStyles: {
|
||||||
fillColor: [60, 60, 60],
|
fillColor: [60, 60, 60],
|
||||||
@@ -410,7 +440,25 @@ export function adicionarLegenda(doc: jsPDF, yPosition: number): number {
|
|||||||
1: { cellWidth: 110 }
|
1: { cellWidth: 110 }
|
||||||
},
|
},
|
||||||
margin: { left: 15, right: 15 },
|
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;
|
const finalYLegenda = (doc as JsPDFWithAutoTable).lastAutoTable?.finalY ?? yPosition + 10;
|
||||||
@@ -446,3 +494,4 @@ export function adicionarRodape(doc: jsPDF): void {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -628,25 +628,34 @@ export async function processarDadosFichaPonto(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calcular saldo acumulado para cada dia
|
// 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;
|
let saldoAcumulado = 0;
|
||||||
|
|
||||||
for (const dia of diasProcessados) {
|
for (const dia of diasProcessados) {
|
||||||
if (dia.computado && dia.saldoDiario) {
|
if (dia.saldoDiario) {
|
||||||
saldoAcumulado += dia.saldoDiario.diferencaMinutos;
|
saldoAcumulado += dia.saldoDiario.diferencaMinutos;
|
||||||
}
|
}
|
||||||
dia.saldoAcumulado = saldoAcumulado;
|
dia.saldoAcumulado = saldoAcumulado;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calcular resumo com formatações
|
// Calcular resumo com formatações
|
||||||
const totalHorasTrabalhadas = diasProcessados
|
// Total de horas trabalhadas e esperadas passa a considerar todos os dias,
|
||||||
.filter((d) => d.computado)
|
// não apenas os marcados como "computados", para que trechos trabalhados
|
||||||
.reduce((acc, d) => acc + (d.saldoDiario?.trabalhadoMinutos || 0), 0);
|
// em dias de ausência/dispensa também apareçam no resumo.
|
||||||
const totalHorasEsperadas = diasProcessados
|
const totalHorasTrabalhadas = diasProcessados.reduce(
|
||||||
.filter((d) => d.computado)
|
(acc, d) => acc + (d.saldoDiario?.trabalhadoMinutos || 0),
|
||||||
.reduce((acc, d) => acc + (d.saldoDiario?.esperadoMinutos || 0), 0);
|
0
|
||||||
const diferencaTotal = diasProcessados
|
);
|
||||||
.filter((d) => d.computado)
|
const totalHorasEsperadas = diasProcessados.reduce(
|
||||||
.reduce((acc, d) => acc + (d.saldoDiario?.diferencaMinutos || 0), 0);
|
(acc, d) => acc + (d.saldoDiario?.esperadoMinutos || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const diferencaTotal = diasProcessados.reduce(
|
||||||
|
(acc, d) => acc + (d.saldoDiario?.diferencaMinutos || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
const saldoPeriodo = diferencaTotal;
|
const saldoPeriodo = diferencaTotal;
|
||||||
const saldoFinal =
|
const saldoFinal =
|
||||||
diasProcessados.length > 0 ? diasProcessados[diasProcessados.length - 1]!.saldoAcumulado : 0;
|
diasProcessados.length > 0 ? diasProcessados[diasProcessados.length - 1]!.saldoAcumulado : 0;
|
||||||
|
|||||||
@@ -85,3 +85,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -85,3 +85,4 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,18 @@
|
|||||||
import { v } from 'convex/values';
|
import { v } from 'convex/values';
|
||||||
import { mutation } from './_generated/server';
|
import { mutation, type MutationCtx } from './_generated/server';
|
||||||
import { authComponent, updatePassword } from './auth';
|
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
|
* Alterar senha do usuário autenticado
|
||||||
@@ -19,7 +31,7 @@ export const alterarSenha = mutation({
|
|||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
try {
|
try {
|
||||||
// Verificar se o usuário está autenticado
|
// Verificar se o usuário está autenticado
|
||||||
const authUser = await authComponent.safeGetAuthUser(ctx);
|
const authUser = await authComponent.safeGetAuthUser(toGenericCtx(ctx));
|
||||||
if (!authUser) {
|
if (!authUser) {
|
||||||
return {
|
return {
|
||||||
sucesso: false as const,
|
sucesso: false as const,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { convex } from '@convex-dev/better-auth/plugins';
|
|||||||
import { betterAuth } from 'better-auth';
|
import { betterAuth } from 'better-auth';
|
||||||
import { components } from './_generated/api';
|
import { components } from './_generated/api';
|
||||||
import type { DataModel } from './_generated/dataModel';
|
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
|
// 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';
|
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.
|
// as well as helper methods for general use.
|
||||||
export const authComponent = createClient<DataModel>(components.betterAuth);
|
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 = (
|
export const createAuth = (
|
||||||
ctx: GenericCtx<DataModel>,
|
ctx: GenericCtx<DataModel>,
|
||||||
{ optionsOnly } = { optionsOnly: false }
|
{ optionsOnly } = { optionsOnly: false }
|
||||||
@@ -45,7 +61,7 @@ export const getCurrentUser = query({
|
|||||||
args: {},
|
args: {},
|
||||||
handler: async (ctx) => {
|
handler: async (ctx) => {
|
||||||
try {
|
try {
|
||||||
const authUser = await authComponent.safeGetAuthUser(ctx);
|
const authUser = await authComponent.safeGetAuthUser(toGenericCtx(ctx));
|
||||||
if (!authUser) {
|
if (!authUser) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -83,7 +99,7 @@ export const getCurrentUser = query({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const getCurrentUserFunction = async (ctx: QueryCtx | MutationCtx) => {
|
export const getCurrentUserFunction = async (ctx: QueryCtx | MutationCtx) => {
|
||||||
const authUser = await authComponent.safeGetAuthUser(ctx);
|
const authUser = await authComponent.safeGetAuthUser(toGenericCtx(ctx));
|
||||||
if (!authUser) {
|
if (!authUser) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -102,7 +118,7 @@ export const createAuthUser = async (
|
|||||||
ctx: MutationCtx,
|
ctx: MutationCtx,
|
||||||
args: { nome: string; email: string; password: string }
|
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({
|
const result = await auth.api.signUpEmail({
|
||||||
headers,
|
headers,
|
||||||
@@ -120,7 +136,7 @@ export const updatePassword = async (
|
|||||||
ctx: MutationCtx,
|
ctx: MutationCtx,
|
||||||
args: { newPassword: string; currentPassword: string }
|
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({
|
await auth.api.changePassword({
|
||||||
headers,
|
headers,
|
||||||
|
|||||||
Reference in New Issue
Block a user