- Added regime de trabalho selection to employee forms for better categorization. - Updated backend validation to include regime de trabalho options for employees. - Enhanced employee data handling by integrating regime de trabalho into various components. - Removed the print modal for financial data to streamline the employee profile interface. - Improved overall code clarity and maintainability across multiple files.
511 lines
19 KiB
Svelte
511 lines
19 KiB
Svelte
<script lang="ts">
|
|
import jsPDF from 'jspdf';
|
|
import autoTable from 'jspdf-autotable';
|
|
import { maskCPF, maskCEP, maskPhone } from "$lib/utils/masks";
|
|
import {
|
|
SEXO_OPTIONS, ESTADO_CIVIL_OPTIONS, GRAU_INSTRUCAO_OPTIONS,
|
|
GRUPO_SANGUINEO_OPTIONS, FATOR_RH_OPTIONS, APOSENTADO_OPTIONS
|
|
} from "$lib/utils/constants";
|
|
import logoGovPE from "$lib/assets/logo_governo_PE.png";
|
|
import { CheckCircle2, X, Printer } from "lucide-svelte";
|
|
|
|
interface Props {
|
|
funcionario: any;
|
|
onClose: () => void;
|
|
}
|
|
|
|
let { funcionario, onClose }: Props = $props();
|
|
|
|
let modalRef: HTMLDialogElement;
|
|
let generating = $state(false);
|
|
|
|
// Seções selecionáveis
|
|
let sections = $state({
|
|
dadosPessoais: true,
|
|
filiacao: true,
|
|
naturalidade: true,
|
|
documentos: true,
|
|
formacao: true,
|
|
saude: true,
|
|
endereco: true,
|
|
contato: true,
|
|
cargo: true,
|
|
financeiro: true,
|
|
bancario: true,
|
|
});
|
|
|
|
const REGIME_LABELS: Record<string, string> = {
|
|
clt: "CLT",
|
|
estatutario_municipal: "Estatutário Municipal",
|
|
estatutario_pe: "Estatutário PE",
|
|
estatutario_federal: "Estatutário Federal",
|
|
};
|
|
|
|
function getLabelFromOptions(value: string | undefined, options: Array<{value: string, label: string}>): string {
|
|
if (!value) return "-";
|
|
return options.find(opt => opt.value === value)?.label || value;
|
|
}
|
|
|
|
function getRegimeLabel(value?: string) {
|
|
if (!value) return "-";
|
|
return REGIME_LABELS[value] ?? value;
|
|
}
|
|
|
|
function selectAll() {
|
|
Object.keys(sections).forEach(key => {
|
|
sections[key as keyof typeof sections] = true;
|
|
});
|
|
}
|
|
|
|
function deselectAll() {
|
|
Object.keys(sections).forEach(key => {
|
|
sections[key as keyof typeof sections] = false;
|
|
});
|
|
}
|
|
|
|
async function gerarPDF() {
|
|
try {
|
|
generating = true;
|
|
|
|
const doc = new jsPDF();
|
|
|
|
// Logo no canto superior esquerdo (proporcional)
|
|
let yPosition = 20;
|
|
try {
|
|
const logoImg = new Image();
|
|
logoImg.src = logoGovPE;
|
|
await new Promise<void>((resolve, reject) => {
|
|
logoImg.onload = () => resolve();
|
|
logoImg.onerror = () => reject();
|
|
setTimeout(() => reject(), 3000); // timeout após 3s
|
|
});
|
|
|
|
// Logo proporcional: largura 25mm, altura ajustada automaticamente
|
|
const logoWidth = 25;
|
|
const aspectRatio = logoImg.height / logoImg.width;
|
|
const logoHeight = logoWidth * aspectRatio;
|
|
|
|
doc.addImage(logoImg, 'PNG', 15, 10, logoWidth, logoHeight);
|
|
|
|
// Ajustar posição inicial do texto para ficar ao lado da logo
|
|
yPosition = Math.max(20, 10 + logoHeight / 2);
|
|
} catch (err) {
|
|
console.warn('Não foi possível carregar a logo:', err);
|
|
}
|
|
|
|
// Cabeçalho (alinhado com a logo)
|
|
doc.setFontSize(16);
|
|
doc.setFont('helvetica', 'bold');
|
|
doc.text('Secretaria de Esportes', 50, yPosition);
|
|
doc.setFontSize(12);
|
|
doc.setFont('helvetica', 'normal');
|
|
doc.text('Governo de Pernambuco', 50, yPosition + 7);
|
|
|
|
yPosition = Math.max(45, yPosition + 25);
|
|
|
|
// Título da ficha
|
|
doc.setFontSize(18);
|
|
doc.setFont('helvetica', 'bold');
|
|
doc.text('FICHA CADASTRAL DE FUNCIONÁRIO', 105, yPosition, { align: 'center' });
|
|
|
|
yPosition += 8;
|
|
doc.setFontSize(10);
|
|
doc.setFont('helvetica', 'normal');
|
|
doc.text(`Gerado em: ${new Date().toLocaleString('pt-BR')}`, 105, yPosition, { align: 'center' });
|
|
|
|
yPosition += 12;
|
|
|
|
// Dados Pessoais
|
|
if (sections.dadosPessoais) {
|
|
const dadosPessoais: any[] = [
|
|
['Nome', funcionario.nome],
|
|
['Matrícula', funcionario.matricula],
|
|
['CPF', maskCPF(funcionario.cpf)],
|
|
['RG', funcionario.rg],
|
|
['Data Nascimento', funcionario.nascimento],
|
|
];
|
|
|
|
if (funcionario.rgOrgaoExpedidor) dadosPessoais.push(['Órgão Expedidor RG', funcionario.rgOrgaoExpedidor]);
|
|
if (funcionario.rgDataEmissao) dadosPessoais.push(['Data Emissão RG', funcionario.rgDataEmissao]);
|
|
if (funcionario.sexo) dadosPessoais.push(['Sexo', getLabelFromOptions(funcionario.sexo, SEXO_OPTIONS)]);
|
|
if (funcionario.estadoCivil) dadosPessoais.push(['Estado Civil', getLabelFromOptions(funcionario.estadoCivil, ESTADO_CIVIL_OPTIONS)]);
|
|
if (funcionario.nacionalidade) dadosPessoais.push(['Nacionalidade', funcionario.nacionalidade]);
|
|
|
|
autoTable(doc, {
|
|
startY: yPosition,
|
|
head: [['DADOS PESSOAIS', '']],
|
|
body: dadosPessoais,
|
|
theme: 'grid',
|
|
headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' },
|
|
styles: { fontSize: 9 },
|
|
});
|
|
|
|
yPosition = (doc as any).lastAutoTable.finalY + 10;
|
|
}
|
|
|
|
// Filiação
|
|
if (sections.filiacao && (funcionario.nomePai || funcionario.nomeMae)) {
|
|
const filiacao: any[] = [];
|
|
if (funcionario.nomePai) filiacao.push(['Nome do Pai', funcionario.nomePai]);
|
|
if (funcionario.nomeMae) filiacao.push(['Nome da Mãe', funcionario.nomeMae]);
|
|
|
|
autoTable(doc, {
|
|
startY: yPosition,
|
|
head: [['FILIAÇÃO', '']],
|
|
body: filiacao,
|
|
theme: 'grid',
|
|
headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' },
|
|
styles: { fontSize: 9 },
|
|
});
|
|
|
|
yPosition = (doc as any).lastAutoTable.finalY + 10;
|
|
}
|
|
|
|
// Naturalidade
|
|
if (sections.naturalidade && (funcionario.naturalidade || funcionario.naturalidadeUF)) {
|
|
const naturalidade: any[] = [];
|
|
if (funcionario.naturalidade) naturalidade.push(['Cidade', funcionario.naturalidade]);
|
|
if (funcionario.naturalidadeUF) naturalidade.push(['UF', funcionario.naturalidadeUF]);
|
|
|
|
autoTable(doc, {
|
|
startY: yPosition,
|
|
head: [['NATURALIDADE', '']],
|
|
body: naturalidade,
|
|
theme: 'grid',
|
|
headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' },
|
|
styles: { fontSize: 9 },
|
|
});
|
|
|
|
yPosition = (doc as any).lastAutoTable.finalY + 10;
|
|
}
|
|
|
|
// Documentos
|
|
if (sections.documentos) {
|
|
const documentosData: any[] = [];
|
|
|
|
if (funcionario.carteiraProfissionalNumero) {
|
|
documentosData.push(['Cart. Profissional', `Nº ${funcionario.carteiraProfissionalNumero}${funcionario.carteiraProfissionalSerie ? ' - Série: ' + funcionario.carteiraProfissionalSerie : ''}`]);
|
|
}
|
|
if (funcionario.carteiraProfissionalDataEmissao) {
|
|
documentosData.push(['Emissão Cart. Profissional', funcionario.carteiraProfissionalDataEmissao]);
|
|
}
|
|
if (funcionario.reservistaNumero) {
|
|
documentosData.push(['Reservista', `Nº ${funcionario.reservistaNumero}${funcionario.reservistaSerie ? ' - Série: ' + funcionario.reservistaSerie : ''}`]);
|
|
}
|
|
if (funcionario.tituloEleitorNumero) {
|
|
let titulo = `Nº ${funcionario.tituloEleitorNumero}`;
|
|
if (funcionario.tituloEleitorZona) titulo += ` - Zona: ${funcionario.tituloEleitorZona}`;
|
|
if (funcionario.tituloEleitorSecao) titulo += ` - Seção: ${funcionario.tituloEleitorSecao}`;
|
|
documentosData.push(['Título Eleitor', titulo]);
|
|
}
|
|
if (funcionario.pisNumero) documentosData.push(['PIS/PASEP', funcionario.pisNumero]);
|
|
|
|
if (documentosData.length > 0) {
|
|
autoTable(doc, {
|
|
startY: yPosition,
|
|
head: [['DOCUMENTOS', '']],
|
|
body: documentosData,
|
|
theme: 'grid',
|
|
headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' },
|
|
styles: { fontSize: 9 },
|
|
});
|
|
|
|
yPosition = (doc as any).lastAutoTable.finalY + 10;
|
|
}
|
|
}
|
|
|
|
// Formação
|
|
if (sections.formacao && (funcionario.grauInstrucao || funcionario.formacao)) {
|
|
const formacaoData: any[] = [];
|
|
if (funcionario.grauInstrucao) formacaoData.push(['Grau Instrução', getLabelFromOptions(funcionario.grauInstrucao, GRAU_INSTRUCAO_OPTIONS)]);
|
|
if (funcionario.formacao) formacaoData.push(['Formação', funcionario.formacao]);
|
|
if (funcionario.formacaoRegistro) formacaoData.push(['Registro Nº', funcionario.formacaoRegistro]);
|
|
|
|
autoTable(doc, {
|
|
startY: yPosition,
|
|
head: [['FORMAÇÃO', '']],
|
|
body: formacaoData,
|
|
theme: 'grid',
|
|
headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' },
|
|
styles: { fontSize: 9 },
|
|
});
|
|
|
|
yPosition = (doc as any).lastAutoTable.finalY + 10;
|
|
}
|
|
|
|
// Saúde
|
|
if (sections.saude && (funcionario.grupoSanguineo || funcionario.fatorRH)) {
|
|
const saudeData: any[] = [];
|
|
if (funcionario.grupoSanguineo) saudeData.push(['Grupo Sanguíneo', funcionario.grupoSanguineo]);
|
|
if (funcionario.fatorRH) saudeData.push(['Fator RH', getLabelFromOptions(funcionario.fatorRH, FATOR_RH_OPTIONS)]);
|
|
|
|
autoTable(doc, {
|
|
startY: yPosition,
|
|
head: [['SAÚDE', '']],
|
|
body: saudeData,
|
|
theme: 'grid',
|
|
headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' },
|
|
styles: { fontSize: 9 },
|
|
});
|
|
|
|
yPosition = (doc as any).lastAutoTable.finalY + 10;
|
|
}
|
|
|
|
// Endereço
|
|
if (sections.endereco) {
|
|
const enderecoData: any[] = [
|
|
['Endereço', funcionario.endereco],
|
|
['Cidade', funcionario.cidade],
|
|
['UF', funcionario.uf],
|
|
['CEP', maskCEP(funcionario.cep)],
|
|
];
|
|
|
|
autoTable(doc, {
|
|
startY: yPosition,
|
|
head: [['ENDEREÇO', '']],
|
|
body: enderecoData,
|
|
theme: 'grid',
|
|
headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' },
|
|
styles: { fontSize: 9 },
|
|
});
|
|
|
|
yPosition = (doc as any).lastAutoTable.finalY + 10;
|
|
}
|
|
|
|
// Contato
|
|
if (sections.contato) {
|
|
const contatoData: any[] = [
|
|
['E-mail', funcionario.email],
|
|
['Telefone', maskPhone(funcionario.telefone)],
|
|
];
|
|
|
|
autoTable(doc, {
|
|
startY: yPosition,
|
|
head: [['CONTATO', '']],
|
|
body: contatoData,
|
|
theme: 'grid',
|
|
headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' },
|
|
styles: { fontSize: 9 },
|
|
});
|
|
|
|
yPosition = (doc as any).lastAutoTable.finalY + 10;
|
|
}
|
|
|
|
// Nova página para cargo
|
|
if (yPosition > 200) {
|
|
doc.addPage();
|
|
yPosition = 20;
|
|
}
|
|
|
|
// Cargo e Vínculo
|
|
if (sections.cargo) {
|
|
const cargoData: any[] = [
|
|
['Tipo', funcionario.simboloTipo === 'cargo_comissionado' ? 'Cargo Comissionado' : 'Função Gratificada'],
|
|
];
|
|
|
|
const simboloInfo = funcionario.simbolo ?? funcionario.simboloDetalhes ?? funcionario.simboloDados;
|
|
if (simboloInfo) {
|
|
cargoData.push(['Símbolo', simboloInfo.nome]);
|
|
if (simboloInfo.descricao) cargoData.push(['Descrição do Símbolo', simboloInfo.descricao]);
|
|
}
|
|
if (funcionario.descricaoCargo) cargoData.push(['Descrição', funcionario.descricaoCargo]);
|
|
if (funcionario.regimeTrabalho) cargoData.push(['Regime do Funcionário', getRegimeLabel(funcionario.regimeTrabalho)]);
|
|
if (funcionario.admissaoData) cargoData.push(['Data Admissão', funcionario.admissaoData]);
|
|
if (funcionario.nomeacaoPortaria) cargoData.push(['Portaria', funcionario.nomeacaoPortaria]);
|
|
if (funcionario.nomeacaoData) cargoData.push(['Data Nomeação', funcionario.nomeacaoData]);
|
|
if (funcionario.nomeacaoDOE) cargoData.push(['DOE', funcionario.nomeacaoDOE]);
|
|
cargoData.push(['Pertence Órgão Público', funcionario.pertenceOrgaoPublico ? 'Sim' : 'Não']);
|
|
if (funcionario.pertenceOrgaoPublico && funcionario.orgaoOrigem) cargoData.push(['Órgão Origem', funcionario.orgaoOrigem]);
|
|
if (funcionario.aposentado && funcionario.aposentado !== 'nao') {
|
|
cargoData.push(['Aposentado', getLabelFromOptions(funcionario.aposentado, APOSENTADO_OPTIONS)]);
|
|
}
|
|
|
|
autoTable(doc, {
|
|
startY: yPosition,
|
|
head: [['CARGO E VÍNCULO', '']],
|
|
body: cargoData,
|
|
theme: 'grid',
|
|
headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' },
|
|
styles: { fontSize: 9 },
|
|
});
|
|
|
|
yPosition = (doc as any).lastAutoTable.finalY + 10;
|
|
}
|
|
|
|
// Dados Financeiros
|
|
if (sections.financeiro && funcionario.simbolo) {
|
|
const simbolo = funcionario.simbolo;
|
|
const financeiroData: any[] = [
|
|
['Símbolo', simbolo.nome],
|
|
['Tipo', simbolo.tipo === 'cargo_comissionado' ? 'Cargo Comissionado' : 'Função Gratificada'],
|
|
['Remuneração Total', `R$ ${simbolo.valor}`],
|
|
];
|
|
|
|
if (funcionario.simboloTipo === 'cargo_comissionado') {
|
|
if (simbolo.vencValor) {
|
|
financeiroData.push(['Vencimento', `R$ ${simbolo.vencValor}`]);
|
|
}
|
|
if (simbolo.repValor) {
|
|
financeiroData.push(['Representação', `R$ ${simbolo.repValor}`]);
|
|
}
|
|
}
|
|
|
|
autoTable(doc, {
|
|
startY: yPosition,
|
|
head: [['DADOS FINANCEIROS', '']],
|
|
body: financeiroData,
|
|
theme: 'grid',
|
|
headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' },
|
|
styles: { fontSize: 9 },
|
|
});
|
|
|
|
yPosition = (doc as any).lastAutoTable.finalY + 10;
|
|
}
|
|
|
|
// Dados Bancários
|
|
if (sections.bancario && funcionario.contaBradescoNumero) {
|
|
const bancarioData: any[] = [
|
|
['Conta', `${funcionario.contaBradescoNumero}${funcionario.contaBradescoDV ? '-' + funcionario.contaBradescoDV : ''}`],
|
|
];
|
|
if (funcionario.contaBradescoAgencia) bancarioData.push(['Agência', funcionario.contaBradescoAgencia]);
|
|
|
|
autoTable(doc, {
|
|
startY: yPosition,
|
|
head: [['DADOS BANCÁRIOS - BRADESCO', '']],
|
|
body: bancarioData,
|
|
theme: 'grid',
|
|
headStyles: { fillColor: [41, 128, 185], fontStyle: 'bold' },
|
|
styles: { fontSize: 9 },
|
|
});
|
|
|
|
yPosition = (doc as any).lastAutoTable.finalY + 10;
|
|
}
|
|
|
|
// Adicionar rodapé em todas as páginas
|
|
const pageCount = (doc as any).internal.getNumberOfPages();
|
|
for (let i = 1; i <= pageCount; i++) {
|
|
doc.setPage(i);
|
|
doc.setFontSize(9);
|
|
doc.setFont('helvetica', 'normal');
|
|
doc.setTextColor(128, 128, 128);
|
|
doc.text('SGSE - Sistema de Gerenciamento da Secretaria de Esportes', 105, 285, { align: 'center' });
|
|
doc.text(`Página ${i} de ${pageCount}`, 195, 285, { align: 'right' });
|
|
}
|
|
|
|
// Salvar PDF
|
|
doc.save(`Ficha_${funcionario.nome.replace(/ /g, '_')}_${new Date().getTime()}.pdf`);
|
|
|
|
onClose();
|
|
} catch (error) {
|
|
console.error('Erro ao gerar PDF:', error);
|
|
alert('Erro ao gerar PDF. Verifique o console para mais detalhes.');
|
|
} finally {
|
|
generating = false;
|
|
}
|
|
}
|
|
|
|
$effect(() => {
|
|
if (modalRef) {
|
|
modalRef.showModal();
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<dialog bind:this={modalRef} class="modal">
|
|
<div class="modal-box max-w-4xl">
|
|
<h3 class="font-bold text-2xl mb-4">Imprimir Ficha Cadastral</h3>
|
|
<p class="text-sm text-base-content/70 mb-6">Selecione as seções que deseja incluir no PDF</p>
|
|
|
|
<!-- Botões de seleção -->
|
|
<div class="flex gap-2 mb-6">
|
|
<button type="button" class="btn btn-sm btn-outline" onclick={selectAll}>
|
|
<CheckCircle2 class="h-4 w-4" strokeWidth={2} />
|
|
Selecionar Todos
|
|
</button>
|
|
<button type="button" class="btn btn-sm btn-outline" onclick={deselectAll}>
|
|
<X class="h-4 w-4" strokeWidth={2} />
|
|
Desmarcar Todos
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Grid de checkboxes -->
|
|
<div class="grid grid-cols-2 md:grid-cols-3 gap-4 mb-6 max-h-96 overflow-y-auto p-2 border rounded-lg bg-base-200">
|
|
<label class="label cursor-pointer justify-start gap-3">
|
|
<input type="checkbox" class="checkbox checkbox-primary" bind:checked={sections.dadosPessoais} />
|
|
<span class="label-text">Dados Pessoais</span>
|
|
</label>
|
|
|
|
<label class="label cursor-pointer justify-start gap-3">
|
|
<input type="checkbox" class="checkbox checkbox-primary" bind:checked={sections.filiacao} />
|
|
<span class="label-text">Filiação</span>
|
|
</label>
|
|
|
|
<label class="label cursor-pointer justify-start gap-3">
|
|
<input type="checkbox" class="checkbox checkbox-primary" bind:checked={sections.naturalidade} />
|
|
<span class="label-text">Naturalidade</span>
|
|
</label>
|
|
|
|
<label class="label cursor-pointer justify-start gap-3">
|
|
<input type="checkbox" class="checkbox checkbox-primary" bind:checked={sections.documentos} />
|
|
<span class="label-text">Documentos</span>
|
|
</label>
|
|
|
|
<label class="label cursor-pointer justify-start gap-3">
|
|
<input type="checkbox" class="checkbox checkbox-primary" bind:checked={sections.formacao} />
|
|
<span class="label-text">Formação</span>
|
|
</label>
|
|
|
|
<label class="label cursor-pointer justify-start gap-3">
|
|
<input type="checkbox" class="checkbox checkbox-primary" bind:checked={sections.saude} />
|
|
<span class="label-text">Saúde</span>
|
|
</label>
|
|
|
|
<label class="label cursor-pointer justify-start gap-3">
|
|
<input type="checkbox" class="checkbox checkbox-primary" bind:checked={sections.endereco} />
|
|
<span class="label-text">Endereço</span>
|
|
</label>
|
|
|
|
<label class="label cursor-pointer justify-start gap-3">
|
|
<input type="checkbox" class="checkbox checkbox-primary" bind:checked={sections.contato} />
|
|
<span class="label-text">Contato</span>
|
|
</label>
|
|
|
|
<label class="label cursor-pointer justify-start gap-3">
|
|
<input type="checkbox" class="checkbox checkbox-primary" bind:checked={sections.cargo} />
|
|
<span class="label-text">Cargo e Vínculo</span>
|
|
</label>
|
|
|
|
<label class="label cursor-pointer justify-start gap-3">
|
|
<input type="checkbox" class="checkbox checkbox-primary" bind:checked={sections.financeiro} />
|
|
<span class="label-text">Dados Financeiros</span>
|
|
</label>
|
|
|
|
<label class="label cursor-pointer justify-start gap-3">
|
|
<input type="checkbox" class="checkbox checkbox-primary" bind:checked={sections.bancario} />
|
|
<span class="label-text">Dados Bancários</span>
|
|
</label>
|
|
</div>
|
|
|
|
<!-- Ações -->
|
|
<div class="modal-action">
|
|
<button type="button" class="btn btn-ghost" onclick={onClose} disabled={generating}>
|
|
Cancelar
|
|
</button>
|
|
<button type="button" class="btn btn-primary gap-2" onclick={gerarPDF} disabled={generating}>
|
|
{#if generating}
|
|
<span class="loading loading-spinner loading-sm"></span>
|
|
Gerando PDF...
|
|
{:else}
|
|
<Printer class="h-5 w-5" strokeWidth={2} />
|
|
Gerar PDF
|
|
{/if}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<form method="dialog" class="modal-backdrop">
|
|
<button type="button" onclick={onClose}>fechar</button>
|
|
</form>
|
|
</dialog>
|
|
|