Feat controle ponto #29

Merged
deyvisonwanderley merged 8 commits from feat-controle-ponto into master 2025-11-19 09:41:57 +00:00
22 changed files with 5945 additions and 128 deletions
Showing only changes of commit ed5695cf28 - Show all commits

View File

@@ -238,7 +238,7 @@
</script> </script>
<div class="modal modal-open" style="display: flex; align-items: center; justify-content: center;"> <div class="modal modal-open" style="display: flex; align-items: center; justify-content: center;">
<div class="modal-box max-w-2xl w-[95%] max-h-[90vh] overflow-hidden flex flex-col" style="margin: auto;"> <div class="modal-box max-w-2xl w-[95%] max-h-[85vh] overflow-hidden flex flex-col" style="margin: auto; max-height: 85vh;">
<!-- Header fixo --> <!-- Header fixo -->
<div class="flex items-center justify-between mb-4 pb-4 border-b border-base-300 flex-shrink-0"> <div class="flex items-center justify-between mb-4 pb-4 border-b border-base-300 flex-shrink-0">
<h3 class="font-bold text-lg">Comprovante de Registro de Ponto</h3> <h3 class="font-bold text-lg">Comprovante de Registro de Ponto</h3>
@@ -312,7 +312,7 @@
<img <img
src={registro.imagemUrl} src={registro.imagemUrl}
alt="Foto do registro de ponto" alt="Foto do registro de ponto"
class="max-w-full max-h-96 rounded-lg border-2 border-primary object-contain" class="max-w-full max-h-[250px] rounded-lg border-2 border-primary object-contain"
onerror={(e) => { onerror={(e) => {
console.error('Erro ao carregar imagem:', e); console.error('Erro ao carregar imagem:', e);
(e.target as HTMLImageElement).style.display = 'none'; (e.target as HTMLImageElement).style.display = 'none';

View File

@@ -874,7 +874,7 @@
<!-- Modal de Confirmação --> <!-- Modal de Confirmação -->
{#if mostrandoModalConfirmacao && imagemCapturada && dataHoraAtual} {#if mostrandoModalConfirmacao && imagemCapturada && dataHoraAtual}
<div class="modal modal-open" style="display: flex; align-items: center; justify-content: center;"> <div class="modal modal-open" style="display: flex; align-items: center; justify-content: center;">
<div class="modal-box max-w-3xl w-[95%] max-h-[90vh] overflow-hidden flex flex-col" style="margin: auto;"> <div class="modal-box max-w-3xl w-[95%] max-h-[85vh] overflow-hidden flex flex-col" style="margin: auto; max-height: 85vh;">
<!-- Header fixo --> <!-- Header fixo -->
<div class="flex items-center justify-between mb-6 pb-4 border-b border-base-300 flex-shrink-0"> <div class="flex items-center justify-between mb-6 pb-4 border-b border-base-300 flex-shrink-0">
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
@@ -923,7 +923,7 @@
<img <img
src={URL.createObjectURL(imagemCapturada)} src={URL.createObjectURL(imagemCapturada)}
alt="Foto capturada do registro de ponto" alt="Foto capturada do registro de ponto"
class="max-w-full max-h-[400px] rounded-lg shadow-md object-contain" class="max-w-full max-h-[250px] rounded-lg shadow-md object-contain"
/> />
</div> </div>
</div> </div>

View File

@@ -163,6 +163,9 @@
r.dentroDoPrazo ? 'Sim' : 'Não', r.dentroDoPrazo ? 'Sim' : 'Não',
]); ]);
// Salvar a posição Y antes da tabela
const yPosAntesTabela = yPosition;
autoTable(doc, { autoTable(doc, {
startY: yPosition, startY: yPosition,
head: [['Data', 'Tipo', 'Horário', 'Dentro do Prazo']], head: [['Data', 'Tipo', 'Horário', 'Dentro do Prazo']],
@@ -172,6 +175,91 @@
styles: { fontSize: 9 }, styles: { fontSize: 9 },
}); });
// Obter banco de horas do funcionário
const bancoHoras = await client.query(api.pontos.obterBancoHorasFuncionario, {
funcionarioId,
});
// Calcular posição Y após a tabela
// autoTable armazena a posição final em doc.lastAutoTable.finalY
const lastPage = doc.getNumberOfPages();
doc.setPage(lastPage);
const finalY = (doc as any).lastAutoTable?.finalY;
// Se não conseguir obter a posição final, estimar baseado no número de linhas
if (finalY) {
yPosition = finalY;
} else {
// Estimativa: cada linha da tabela ocupa aproximadamente 7mm
const linhasTabela = tableData.length + 1; // +1 para o cabeçalho
yPosition = yPosAntesTabela + (linhasTabela * 7) + 10;
}
// Adicionar espaço antes do resumo
yPosition += 10;
// Verificar se precisa de nova página
if (yPosition > doc.internal.pageSize.getHeight() - 60) {
doc.addPage();
yPosition = 20;
}
// Resumo do Banco de Horas
doc.setFontSize(12);
doc.setFont('helvetica', 'bold');
doc.setTextColor(41, 128, 185);
doc.text('RESUMO DO BANCO DE HORAS', 15, yPosition);
doc.setFont('helvetica', 'normal');
doc.setTextColor(0, 0, 0);
yPosition += 10;
doc.setFontSize(10);
if (bancoHoras) {
const saldoMinutos = bancoHoras.saldoAcumuladoMinutos;
const horas = Math.floor(Math.abs(saldoMinutos) / 60);
const minutos = Math.abs(saldoMinutos) % 60;
const sinal = saldoMinutos >= 0 ? '+' : '-';
const saldoFormatado = `${sinal}${horas}h ${minutos}min`;
// Saldo Atual
doc.setFont('helvetica', 'bold');
doc.text('Saldo Atual:', 15, yPosition);
doc.setFont('helvetica', 'normal');
doc.text(saldoFormatado, 60, yPosition);
yPosition += 8;
// Horas Excedentes (se positivo)
if (saldoMinutos > 0) {
doc.setFont('helvetica', 'bold');
doc.setTextColor(0, 128, 0); // Verde
doc.text('Horas Excedentes:', 15, yPosition);
doc.setFont('helvetica', 'normal');
doc.text(`${horas}h ${minutos}min`, 75, yPosition);
doc.setTextColor(0, 0, 0);
yPosition += 8;
}
// Horas a Pagar (se negativo)
if (saldoMinutos < 0) {
doc.setFont('helvetica', 'bold');
doc.setTextColor(200, 0, 0); // Vermelho
doc.text('Horas a Pagar:', 15, yPosition);
doc.setFont('helvetica', 'normal');
doc.text(`${horas}h ${minutos}min`, 70, yPosition);
doc.setTextColor(0, 0, 0);
yPosition += 8;
}
// Total de dias registrados
doc.setFont('helvetica', 'bold');
doc.text('Total de Dias com Registro:', 15, yPosition);
doc.setFont('helvetica', 'normal');
doc.text(`${bancoHoras.totalDias} dias`, 95, yPosition);
} else {
doc.text('Banco de horas não disponível', 15, yPosition);
}
// Rodapé // Rodapé
const pageCount = doc.getNumberOfPages(); const pageCount = doc.getNumberOfPages();
for (let i = 1; i <= pageCount; i++) { for (let i = 1; i <= pageCount; i++) {

View File

@@ -1408,6 +1408,8 @@ export default defineSchema({
nomeSaidaAlmoco: v.optional(v.string()), // Padrão: "Saída 1" nomeSaidaAlmoco: v.optional(v.string()), // Padrão: "Saída 1"
nomeRetornoAlmoco: v.optional(v.string()), // Padrão: "Entrada 2" nomeRetornoAlmoco: v.optional(v.string()), // Padrão: "Entrada 2"
nomeSaida: v.optional(v.string()), // Padrão: "Saída 2" nomeSaida: v.optional(v.string()), // Padrão: "Saída 2"
// Ajuste de fuso horário (GMT offset em horas)
gmtOffset: v.optional(v.number()), // Padrão: 0 (UTC)
ativo: v.boolean(), ativo: v.boolean(),
atualizadoPor: v.id("usuarios"), atualizadoPor: v.id("usuarios"),
atualizadoEm: v.number(), atualizadoEm: v.number(),