Feat chat #3
352
.cursor/plans/sistema-de-documentos-e-impress-o-de0a1ea6.plan.md
Normal file
352
.cursor/plans/sistema-de-documentos-e-impress-o-de0a1ea6.plan.md
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
<!-- de0a1ea6-0e97-42bf-a867-941b2346132b c70cab4f-9f78-4c1a-9087-09a2bf0196c8 -->
|
||||||
|
# Plano: Sistema Completo de Documentos e Cadastro de Funcionários
|
||||||
|
|
||||||
|
## 1. Atualizar Schema do Banco de Dados
|
||||||
|
|
||||||
|
**Arquivo:** `packages/backend/convex/schema.ts`
|
||||||
|
|
||||||
|
### Campos de Dados Pessoais Adicionais (todos opcionais):
|
||||||
|
|
||||||
|
- `nomePai: v.optional(v.string())`
|
||||||
|
- `nomeMae: v.optional(v.string())`
|
||||||
|
- `naturalidade: v.optional(v.string())` - cidade natal
|
||||||
|
- `naturalidadeUF: v.optional(v.string())` - UF com máscara (2 letras)
|
||||||
|
- `sexo: v.optional(v.union(v.literal("masculino"), v.literal("feminino"), v.literal("outro")))`
|
||||||
|
- `estadoCivil: v.optional(v.union(v.literal("solteiro"), v.literal("casado"), v.literal("divorciado"), v.literal("viuvo"), v.literal("uniao_estavel")))`
|
||||||
|
- `nacionalidade: v.optional(v.string())`
|
||||||
|
- `rgOrgaoExpedidor: v.optional(v.string())`
|
||||||
|
- `rgDataEmissao: v.optional(v.string())` - formato dd/mm/aaaa
|
||||||
|
- `carteiraProfissionalNumero: v.optional(v.string())`
|
||||||
|
- `carteiraProfissionalSerie: v.optional(v.string())`
|
||||||
|
- `carteiraProfissionalDataEmissao: v.optional(v.string())`
|
||||||
|
- `reservistaNumero: v.optional(v.string())`
|
||||||
|
- `reservistaSerie: v.optional(v.string())`
|
||||||
|
- `tituloEleitorNumero: v.optional(v.string())`
|
||||||
|
- `tituloEleitorZona: v.optional(v.string())`
|
||||||
|
- `tituloEleitorSecao: v.optional(v.string())`
|
||||||
|
- `grauInstrucao: v.optional(v.union(...))` - fundamental, medio, superior, pos_graduacao, mestrado, doutorado
|
||||||
|
- `formacao: v.optional(v.string())` - curso/formação
|
||||||
|
- `formacaoRegistro: v.optional(v.string())` - número de registro do diploma
|
||||||
|
- `pisNumero: v.optional(v.string())`
|
||||||
|
- `grupoSanguineo: v.optional(v.union(v.literal("A"), v.literal("B"), v.literal("AB"), v.literal("O")))`
|
||||||
|
- `fatorRH: v.optional(v.union(v.literal("positivo"), v.literal("negativo")))`
|
||||||
|
- `nomeacaoPortaria: v.optional(v.string())` - número do ato/portaria
|
||||||
|
- `nomeacaoData: v.optional(v.string())`
|
||||||
|
- `nomeacaoDOE: v.optional(v.string())`
|
||||||
|
- `descricaoCargo: v.optional(v.string())`
|
||||||
|
- `pertenceOrgaoPublico: v.optional(v.boolean())`
|
||||||
|
- `orgaoOrigem: v.optional(v.string())`
|
||||||
|
- `aposentado: v.optional(v.union(v.literal("nao"), v.literal("funape_ipsep"), v.literal("inss")))`
|
||||||
|
- `contaBradescoNumero: v.optional(v.string())`
|
||||||
|
- `contaBradescoDV: v.optional(v.string())`
|
||||||
|
- `contaBradescoAgencia: v.optional(v.string())`
|
||||||
|
|
||||||
|
### Campos de Documentos (Storage IDs opcionais) - 23 campos:
|
||||||
|
|
||||||
|
Todos como `v.optional(v.id("_storage"))`:
|
||||||
|
|
||||||
|
- `certidaoAntecedentesPF`, `certidaoAntecedentesJFPE`, `certidaoAntecedentesSDS`, `certidaoAntecedentesTJPE`, `certidaoImprobidade`, `rgFrente`, `rgVerso`, `cpfFrente`, `cpfVerso`, `situacaoCadastralCPF`, `tituloEleitorFrente`, `tituloEleitorVerso`, `comprovanteVotacao`, `carteiraProfissionalFrente`, `carteiraProfissionalVerso`, `comprovantePIS`, `certidaoRegistroCivil`, `certidaoNascimentoDependentes`, `cpfDependentes`, `reservistaDoc`, `comprovanteEscolaridade`, `comprovanteResidencia`, `comprovanteContaBradesco`
|
||||||
|
|
||||||
|
## 2. Atualizar Backend Convex
|
||||||
|
|
||||||
|
**Arquivo:** `packages/backend/convex/funcionarios.ts`
|
||||||
|
|
||||||
|
- Adicionar todos os novos campos nas mutations `create` e `update`
|
||||||
|
- Criar mutation `uploadDocumento(funcionarioId, tipoDocumento, storageId)` para vincular uploads
|
||||||
|
- Criar query `getDocumentosUrls(funcionarioId)` retornando objeto com URLs de todos os documentos
|
||||||
|
- Criar query `getFichaCompleta(funcionarioId)` retornando todos os dados formatados para impressão
|
||||||
|
|
||||||
|
## 3. Criar Componente de Upload de Arquivo
|
||||||
|
|
||||||
|
**Arquivo:** `apps/web/src/lib/components/FileUpload.svelte`
|
||||||
|
|
||||||
|
Props:
|
||||||
|
|
||||||
|
- `label: string` - nome do documento
|
||||||
|
- `helpUrl?: string` - URL de referência
|
||||||
|
- `value?: string` - storageId atual
|
||||||
|
- `onUpload: (file: File) => Promise<void>`
|
||||||
|
- `onRemove: () => Promise<void>`
|
||||||
|
|
||||||
|
Recursos:
|
||||||
|
|
||||||
|
- Input aceita PDF e imagens (jpg, png, jpeg)
|
||||||
|
- Preview com thumbnail para imagens, ícone para PDF
|
||||||
|
- Botão remover com confirmação
|
||||||
|
- Validação de tamanho máximo 10MB
|
||||||
|
- Loading state durante upload
|
||||||
|
- Tooltip com link de ajuda (ícone ?)
|
||||||
|
|
||||||
|
## 4. Atualizar Formulário de Cadastro
|
||||||
|
|
||||||
|
**Arquivo:** `apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios/cadastro/+page.svelte`
|
||||||
|
|
||||||
|
### Reorganizar em 8 cards:
|
||||||
|
|
||||||
|
**Card 1 - Informações Pessoais:**
|
||||||
|
|
||||||
|
- Nome, Matrícula, CPF (máscara), RG, Órgão Expedidor, Data Emissão RG
|
||||||
|
- Nome Pai, Nome Mãe
|
||||||
|
- Data Nascimento, Naturalidade, UF (máscara 2 letras)
|
||||||
|
- Sexo (select), Estado Civil (select), Nacionalidade
|
||||||
|
|
||||||
|
**Card 2 - Documentos Pessoais:**
|
||||||
|
|
||||||
|
- Carteira Profissional Nº, Série, Data Emissão
|
||||||
|
- Reservista Nº, Série
|
||||||
|
- Título Eleitor Nº, Zona, Seção
|
||||||
|
- PIS/PASEP Nº
|
||||||
|
|
||||||
|
**Card 3 - Formação e Saúde:**
|
||||||
|
|
||||||
|
- Grau Instrução (select), Formação, Registro Nº
|
||||||
|
- Grupo Sanguíneo (select), Fator RH (select)
|
||||||
|
|
||||||
|
**Card 4 - Endereço e Contato:**
|
||||||
|
|
||||||
|
- CEP, Cidade, UF, Endereço
|
||||||
|
- Telefone, Email
|
||||||
|
|
||||||
|
**Card 5 - Cargo e Vínculo:**
|
||||||
|
|
||||||
|
- Símbolo Tipo (CC/FG)
|
||||||
|
- Símbolo (select filtrado)
|
||||||
|
- Descrição Cargo/Função (novo campo opcional)
|
||||||
|
- Nomeação/Portaria Nº, Data, DOE
|
||||||
|
- Data Admissão
|
||||||
|
- Pertence a Órgão Público? (checkbox)
|
||||||
|
- Órgão de Origem (se extra-quadro)
|
||||||
|
- Aposentado (select: Não/FUNAPE-IPSEP/INSS)
|
||||||
|
|
||||||
|
**Card 6 - Dados Bancários:**
|
||||||
|
|
||||||
|
- Conta Bradesco Nº, DV, Agência
|
||||||
|
|
||||||
|
**Card 7 - Documentação Anexa (23 uploads):**
|
||||||
|
|
||||||
|
Organizar em subcategorias com ícones:
|
||||||
|
|
||||||
|
- Antecedentes Criminais (4 docs)
|
||||||
|
- Documentos Pessoais (6 docs)
|
||||||
|
- Documentos Eleitorais (3 docs)
|
||||||
|
- Documentos Profissionais (4 docs)
|
||||||
|
- Certidões e Comprovantes (6 docs)
|
||||||
|
|
||||||
|
Cada campo com tooltip (?) linkando para URL de referência
|
||||||
|
|
||||||
|
**Card 8 - Ações:**
|
||||||
|
|
||||||
|
- Botão Cancelar
|
||||||
|
- Botão Cadastrar
|
||||||
|
|
||||||
|
## 5. Atualizar Formulário de Edição
|
||||||
|
|
||||||
|
**Arquivo:** `apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios/[funcionarioId]/editar/+page.svelte`
|
||||||
|
|
||||||
|
- Mesma estrutura do cadastro
|
||||||
|
- Carregar valores existentes
|
||||||
|
- Mostrar documentos já enviados com opção de substituir
|
||||||
|
- Preview de documentos existentes
|
||||||
|
|
||||||
|
## 6. Criar Página de Detalhes do Funcionário
|
||||||
|
|
||||||
|
**Arquivo:** `apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios/[funcionarioId]/+page.svelte`
|
||||||
|
|
||||||
|
Layout com 3 colunas de cards:
|
||||||
|
|
||||||
|
- Coluna 1: Dados Pessoais, Filiação, Naturalidade
|
||||||
|
- Coluna 2: Documentos, Formação, Saúde
|
||||||
|
- Coluna 3: Cargo, Vínculo, Bancários
|
||||||
|
|
||||||
|
Seção inferior: Grid de documentos anexados com status (enviado/pendente)
|
||||||
|
|
||||||
|
Cabeçalho: Botões "Editar", "Ver Documentos", "Imprimir Ficha"
|
||||||
|
|
||||||
|
## 7. Criar Página de Gerenciamento de Documentos
|
||||||
|
|
||||||
|
**Arquivo:** `apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios/[funcionarioId]/documentos/+page.svelte`
|
||||||
|
|
||||||
|
Grid 3x8 de cards, cada um com:
|
||||||
|
|
||||||
|
- Nome do documento
|
||||||
|
- Ícone de status (verde=enviado, amarelo=pendente)
|
||||||
|
- Preview ou ícone
|
||||||
|
- Botões: Upload/Substituir, Download, Visualizar, Remover
|
||||||
|
- Link de ajuda (?)
|
||||||
|
|
||||||
|
Filtros: Mostrar Todos / Apenas Enviados / Apenas Pendentes
|
||||||
|
|
||||||
|
## 8. Adicionar Botões de Impressão
|
||||||
|
|
||||||
|
**Arquivo:** `apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios/+page.svelte`
|
||||||
|
|
||||||
|
No dropdown de ações de cada linha:
|
||||||
|
|
||||||
|
- Editar
|
||||||
|
- Ver Documentos
|
||||||
|
- **Imprimir Ficha** (novo)
|
||||||
|
- Excluir
|
||||||
|
|
||||||
|
## 9. Criar Modal de Impressão
|
||||||
|
|
||||||
|
**Arquivo:** `apps/web/src/lib/components/PrintModal.svelte`
|
||||||
|
|
||||||
|
Props: `funcionarioId: string`
|
||||||
|
|
||||||
|
Layout em 2 colunas:
|
||||||
|
|
||||||
|
- Coluna esquerda: Checkboxes organizados por seção
|
||||||
|
- Coluna direita: Preview em tempo real (opcional)
|
||||||
|
|
||||||
|
Seções de campos selecionáveis:
|
||||||
|
|
||||||
|
1. Dados Pessoais (15 campos)
|
||||||
|
2. Filiação (2 campos)
|
||||||
|
3. Naturalidade (2 campos)
|
||||||
|
4. Documentos (8 campos)
|
||||||
|
5. Formação (3 campos)
|
||||||
|
6. Saúde (2 campos)
|
||||||
|
7. Endereço (4 campos)
|
||||||
|
8. Contato (2 campos)
|
||||||
|
9. Cargo e Vínculo (9 campos)
|
||||||
|
10. Dados Bancários (3 campos)
|
||||||
|
11. Documentos Anexos (23 campos)
|
||||||
|
|
||||||
|
Botões:
|
||||||
|
|
||||||
|
- Selecionar Todos / Desmarcar Todos (por seção)
|
||||||
|
- Cancelar
|
||||||
|
- Gerar PDF
|
||||||
|
|
||||||
|
Geração do PDF:
|
||||||
|
|
||||||
|
- Usar jsPDF + autotable
|
||||||
|
- Cabeçalho com logo da secretaria
|
||||||
|
- Título "FICHA CADASTRAL DE FUNCIONÁRIO"
|
||||||
|
- Dados em formato de tabela (label: valor)
|
||||||
|
- Seções separadas visualmente
|
||||||
|
- Rodapé com data de geração
|
||||||
|
|
||||||
|
## 10. Criar Helper de Máscaras
|
||||||
|
|
||||||
|
**Arquivo:** `apps/web/src/lib/utils/masks.ts`
|
||||||
|
|
||||||
|
Funções reutilizáveis:
|
||||||
|
|
||||||
|
- `maskCPF(value: string): string`
|
||||||
|
- `maskUF(value: string): string` - força uppercase, 2 chars
|
||||||
|
- `maskCEP(value: string): string`
|
||||||
|
- `maskPhone(value: string): string`
|
||||||
|
- `maskDate(value: string): string`
|
||||||
|
- `validateCPF(value: string): boolean`
|
||||||
|
- `validateDate(value: string): boolean`
|
||||||
|
|
||||||
|
## 11. Criar Seção de Modelos de Declarações
|
||||||
|
|
||||||
|
### Estrutura de Arquivos
|
||||||
|
|
||||||
|
**Pasta:** `apps/web/static/modelos/declaracoes/`
|
||||||
|
|
||||||
|
Armazenar os 5 modelos de declarações em PDF que os funcionários devem preencher e assinar.
|
||||||
|
|
||||||
|
### Componente de Modelos
|
||||||
|
|
||||||
|
**Arquivo:** `apps/web/src/lib/components/ModelosDeclaracoes.svelte`
|
||||||
|
|
||||||
|
Componente exibindo card com:
|
||||||
|
|
||||||
|
- Título: "Modelos de Declarações"
|
||||||
|
- Descrição: "Baixe os modelos, preencha, assine e faça upload no sistema"
|
||||||
|
- Lista dos 5 modelos com:
|
||||||
|
- Nome do documento
|
||||||
|
- Ícone de PDF
|
||||||
|
- Botão "Baixar Modelo"
|
||||||
|
- Botão "Gerar Preenchido" (se dados disponíveis)
|
||||||
|
- Layout em grid responsivo
|
||||||
|
|
||||||
|
### Gerador de Declarações
|
||||||
|
|
||||||
|
**Arquivo:** `apps/web/src/lib/utils/declaracoesGenerator.ts`
|
||||||
|
|
||||||
|
Funções para gerar cada uma das 5 declarações preenchidas com dados do funcionário:
|
||||||
|
|
||||||
|
- `gerarDeclaracao1(funcionario): Blob`
|
||||||
|
- `gerarDeclaracao2(funcionario): Blob`
|
||||||
|
- `gerarDeclaracao3(funcionario): Blob`
|
||||||
|
- `gerarDeclaracao4(funcionario): Blob`
|
||||||
|
- `gerarDeclaracao5(funcionario): Blob`
|
||||||
|
|
||||||
|
Cada função usa jsPDF para:
|
||||||
|
|
||||||
|
- Replicar o layout do modelo
|
||||||
|
- Preencher com dados do funcionário
|
||||||
|
- Deixar campo de assinatura em branco
|
||||||
|
- Retornar PDF pronto para download
|
||||||
|
|
||||||
|
### Modal Seletor de Modelos
|
||||||
|
|
||||||
|
**Arquivo:** `apps/web/src/lib/components/SeletorModelosModal.svelte`
|
||||||
|
|
||||||
|
Modal para escolher quais modelos baixar:
|
||||||
|
|
||||||
|
- Checkboxes para cada um dos 5 modelos
|
||||||
|
- Opção: "Baixar modelos vazios" ou "Gerar preenchidos"
|
||||||
|
- Botão "Selecionar Todos"
|
||||||
|
- Botão "Baixar Selecionados"
|
||||||
|
- Se "gerar preenchidos", preenche com dados do funcionário
|
||||||
|
|
||||||
|
### Integração nas Páginas
|
||||||
|
|
||||||
|
Adicionar componente `<ModelosDeclaracoes />` em:
|
||||||
|
|
||||||
|
1. Formulário de cadastro (antes do card de documentação anexa)
|
||||||
|
2. Página de gerenciamento de documentos (seção superior)
|
||||||
|
3. Página de detalhes do funcionário (botão "Baixar Modelos" no cabeçalho)
|
||||||
|
|
||||||
|
## 12. Instalar Dependências
|
||||||
|
|
||||||
|
**Arquivo:** `apps/web/package.json`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install jspdf jspdf-autotable
|
||||||
|
npm install -D @types/jspdf
|
||||||
|
```
|
||||||
|
|
||||||
|
## Referências dos Documentos
|
||||||
|
|
||||||
|
Manter estrutura de dados com URLs:
|
||||||
|
|
||||||
|
1. Cert. Antecedentes PF: https://servicos.pf.gov.br/epol-sinic-publico/
|
||||||
|
2. Cert. Antecedentes JFPE: https://certidoes.trf5.jus.br/certidoes2022/paginas/certidaocriminal.faces
|
||||||
|
3. Cert. Antecedentes SDS-PE: http://www.servicos.sds.pe.gov.br/antecedentes/...
|
||||||
|
4. Cert. Antecedentes TJPE: https://certidoesunificadas.app.tjpe.jus.br/certidao-criminal-pf
|
||||||
|
5. Cert. Improbidade: https://www.cnj.jus.br/improbidade_adm/consultar_requerido
|
||||||
|
|
||||||
|
6-10. RG, CPF, Situação CPF: URLs fornecidas
|
||||||
|
|
||||||
|
11-23. Demais documentos com URLs correspondentes
|
||||||
|
|
||||||
|
## Design e UX
|
||||||
|
|
||||||
|
- DaisyUI para consistência
|
||||||
|
- Cards com sombras suaves
|
||||||
|
- Ícones lucide-svelte ou heroicons
|
||||||
|
- Cores: verde para sucesso, amarelo para pendente, vermelho para erro
|
||||||
|
- Animações suaves de transição
|
||||||
|
- Layout responsivo (mobile-first)
|
||||||
|
- Tooltips discretos
|
||||||
|
- Feedback imediato em ações
|
||||||
|
- Progress indicators durante uploads
|
||||||
|
|
||||||
|
### To-dos
|
||||||
|
|
||||||
|
- [ ] Atualizar schema do banco com campo descricaoCargo e 23 campos de documentos
|
||||||
|
- [ ] Criar mutations e queries no backend para upload e gerenciamento de documentos
|
||||||
|
- [ ] Criar componente reutilizável FileUpload.svelte com preview e validação
|
||||||
|
- [ ] Adicionar campo descricaoCargo e seção de documentos no formulário de cadastro
|
||||||
|
- [ ] Adicionar campo descricaoCargo e seção de documentos no formulário de edição
|
||||||
|
- [ ] Criar página de detalhes do funcionário com visualização de documentos
|
||||||
|
- [ ] Criar página de gerenciamento centralizado de documentos
|
||||||
|
- [ ] Adicionar botões de impressão na listagem e página de detalhes
|
||||||
|
- [ ] Criar modal de impressão com checkboxes e geração de PDF
|
||||||
|
- [ ] Instalar jspdf e jspdf-autotable no package.json do web
|
||||||
@@ -34,6 +34,8 @@
|
|||||||
"better-auth": "1.3.27",
|
"better-auth": "1.3.27",
|
||||||
"convex": "^1.28.0",
|
"convex": "^1.28.0",
|
||||||
"convex-svelte": "^0.0.11",
|
"convex-svelte": "^0.0.11",
|
||||||
|
"jspdf": "^3.0.3",
|
||||||
|
"jspdf-autotable": "^5.0.2",
|
||||||
"zod": "^4.0.17"
|
"zod": "^4.0.17"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
273
apps/web/src/lib/components/FileUpload.svelte
Normal file
273
apps/web/src/lib/components/FileUpload.svelte
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { useConvexClient } from "convex-svelte";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
label: string;
|
||||||
|
helpUrl?: string;
|
||||||
|
value?: string; // storageId
|
||||||
|
disabled?: boolean;
|
||||||
|
onUpload: (file: File) => Promise<void>;
|
||||||
|
onRemove: () => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
let {
|
||||||
|
label,
|
||||||
|
helpUrl,
|
||||||
|
value = $bindable(),
|
||||||
|
disabled = false,
|
||||||
|
onUpload,
|
||||||
|
onRemove,
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
const client = useConvexClient();
|
||||||
|
|
||||||
|
let fileInput: HTMLInputElement;
|
||||||
|
let uploading = $state(false);
|
||||||
|
let error = $state<string | null>(null);
|
||||||
|
let fileName = $state<string>("");
|
||||||
|
let fileType = $state<string>("");
|
||||||
|
let previewUrl = $state<string | null>(null);
|
||||||
|
let fileUrl = $state<string | null>(null);
|
||||||
|
|
||||||
|
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
||||||
|
const ALLOWED_TYPES = [
|
||||||
|
"application/pdf",
|
||||||
|
"image/jpeg",
|
||||||
|
"image/jpg",
|
||||||
|
"image/png",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Buscar URL do arquivo quando houver um storageId
|
||||||
|
$effect(() => {
|
||||||
|
if (value && !fileName) {
|
||||||
|
// Tem storageId mas não é um upload recente
|
||||||
|
loadExistingFile(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function loadExistingFile(storageId: string) {
|
||||||
|
try {
|
||||||
|
const url = await client.storage.getUrl(storageId as any);
|
||||||
|
if (url) {
|
||||||
|
fileUrl = url;
|
||||||
|
fileName = "Documento anexado";
|
||||||
|
// Detectar tipo pelo URL ou assumir PDF
|
||||||
|
if (url.includes(".pdf") || url.includes("application/pdf")) {
|
||||||
|
fileType = "application/pdf";
|
||||||
|
} else {
|
||||||
|
fileType = "image/jpeg";
|
||||||
|
previewUrl = url; // Para imagens, a URL serve como preview
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Erro ao carregar arquivo existente:", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleFileSelect(event: Event) {
|
||||||
|
const target = event.target as HTMLInputElement;
|
||||||
|
const file = target.files?.[0];
|
||||||
|
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
error = null;
|
||||||
|
|
||||||
|
// Validate file size
|
||||||
|
if (file.size > MAX_FILE_SIZE) {
|
||||||
|
error = "Arquivo muito grande. Tamanho máximo: 10MB";
|
||||||
|
target.value = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate file type
|
||||||
|
if (!ALLOWED_TYPES.includes(file.type)) {
|
||||||
|
error = "Tipo de arquivo não permitido. Use PDF ou imagens (JPG, PNG)";
|
||||||
|
target.value = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
uploading = true;
|
||||||
|
fileName = file.name;
|
||||||
|
fileType = file.type;
|
||||||
|
|
||||||
|
// Create preview for images
|
||||||
|
if (file.type.startsWith("image/")) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
previewUrl = e.target?.result as string;
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
await onUpload(file);
|
||||||
|
|
||||||
|
} catch (err: any) {
|
||||||
|
error = err?.message || "Erro ao fazer upload do arquivo";
|
||||||
|
previewUrl = null;
|
||||||
|
} finally {
|
||||||
|
uploading = false;
|
||||||
|
target.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleRemove() {
|
||||||
|
if (!confirm("Tem certeza que deseja remover este arquivo?")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
uploading = true;
|
||||||
|
await onRemove();
|
||||||
|
fileName = "";
|
||||||
|
fileType = "";
|
||||||
|
previewUrl = null;
|
||||||
|
fileUrl = null;
|
||||||
|
} catch (err: any) {
|
||||||
|
error = err?.message || "Erro ao remover arquivo";
|
||||||
|
} finally {
|
||||||
|
uploading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleView() {
|
||||||
|
if (fileUrl) {
|
||||||
|
window.open(fileUrl, '_blank');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openFileDialog() {
|
||||||
|
fileInput?.click();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="form-control w-full">
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text font-medium flex items-center gap-2">
|
||||||
|
{label}
|
||||||
|
{#if helpUrl}
|
||||||
|
<div class="tooltip tooltip-right" data-tip="Clique para acessar o link">
|
||||||
|
<a
|
||||||
|
href={helpUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="text-primary hover:text-primary-focus transition-colors"
|
||||||
|
aria-label="Acessar link"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
bind:this={fileInput}
|
||||||
|
onchange={handleFileSelect}
|
||||||
|
accept=".pdf,.jpg,.jpeg,.png"
|
||||||
|
class="hidden"
|
||||||
|
{disabled}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{#if value || fileName}
|
||||||
|
<div class="flex items-center gap-2 p-3 border border-base-300 rounded-lg bg-base-100">
|
||||||
|
<!-- Preview -->
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
{#if previewUrl}
|
||||||
|
<img src={previewUrl} alt="Preview" class="w-12 h-12 object-cover rounded" />
|
||||||
|
{:else if fileType === "application/pdf" || fileName.endsWith(".pdf")}
|
||||||
|
<div class="w-12 h-12 bg-error/10 rounded flex items-center justify-center">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-error" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<div class="w-12 h-12 bg-success/10 rounded flex items-center justify-center">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-success" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- File info -->
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<p class="text-sm font-medium truncate">{fileName || "Arquivo anexado"}</p>
|
||||||
|
<p class="text-xs text-base-content/60">
|
||||||
|
{#if uploading}
|
||||||
|
Carregando...
|
||||||
|
{:else}
|
||||||
|
Enviado com sucesso
|
||||||
|
{/if}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Actions -->
|
||||||
|
<div class="flex gap-2">
|
||||||
|
{#if fileUrl}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={handleView}
|
||||||
|
class="btn btn-sm btn-ghost text-info"
|
||||||
|
disabled={uploading || disabled}
|
||||||
|
title="Visualizar arquivo"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={openFileDialog}
|
||||||
|
class="btn btn-sm btn-ghost"
|
||||||
|
disabled={uploading || disabled}
|
||||||
|
title="Substituir arquivo"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={handleRemove}
|
||||||
|
class="btn btn-sm btn-ghost text-error"
|
||||||
|
disabled={uploading || disabled}
|
||||||
|
title="Remover arquivo"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onclick={openFileDialog}
|
||||||
|
class="btn btn-outline btn-block justify-start gap-2"
|
||||||
|
disabled={uploading || disabled}
|
||||||
|
>
|
||||||
|
{#if uploading}
|
||||||
|
<span class="loading loading-spinner loading-sm"></span>
|
||||||
|
Carregando...
|
||||||
|
{:else}
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" />
|
||||||
|
</svg>
|
||||||
|
Selecionar arquivo (PDF ou imagem, máx. 10MB)
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if error}
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text-alt text-error">{error}</span>
|
||||||
|
</label>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
162
apps/web/src/lib/components/ModelosDeclaracoes.svelte
Normal file
162
apps/web/src/lib/components/ModelosDeclaracoes.svelte
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { modelosDeclaracoes } from "$lib/utils/modelosDeclaracoes";
|
||||||
|
import {
|
||||||
|
gerarDeclaracaoAcumulacaoCargo,
|
||||||
|
gerarDeclaracaoDependentesIR,
|
||||||
|
gerarDeclaracaoIdoneidade,
|
||||||
|
gerarTermoNepotismo,
|
||||||
|
gerarTermoOpcaoRemuneracao,
|
||||||
|
downloadBlob
|
||||||
|
} from "$lib/utils/declaracoesGenerator";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
funcionario?: any;
|
||||||
|
showPreencherButton?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { funcionario, showPreencherButton = false }: Props = $props();
|
||||||
|
let generating = $state(false);
|
||||||
|
|
||||||
|
function baixarModelo(arquivoUrl: string, nomeModelo: string) {
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = arquivoUrl;
|
||||||
|
link.download = nomeModelo + '.pdf';
|
||||||
|
link.target = '_blank';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function gerarPreenchido(modeloId: string) {
|
||||||
|
if (!funcionario) {
|
||||||
|
alert('Dados do funcionário não disponíveis');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
generating = true;
|
||||||
|
let blob: Blob;
|
||||||
|
let nomeArquivo: string;
|
||||||
|
|
||||||
|
switch (modeloId) {
|
||||||
|
case 'acumulacao_cargo':
|
||||||
|
blob = await gerarDeclaracaoAcumulacaoCargo(funcionario);
|
||||||
|
nomeArquivo = `Declaracao_Acumulacao_Cargo_${funcionario.nome.replace(/ /g, '_')}_${Date.now()}.pdf`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'dependentes_ir':
|
||||||
|
blob = await gerarDeclaracaoDependentesIR(funcionario);
|
||||||
|
nomeArquivo = `Declaracao_Dependentes_IR_${funcionario.nome.replace(/ /g, '_')}_${Date.now()}.pdf`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'idoneidade':
|
||||||
|
blob = await gerarDeclaracaoIdoneidade(funcionario);
|
||||||
|
nomeArquivo = `Declaracao_Idoneidade_${funcionario.nome.replace(/ /g, '_')}_${Date.now()}.pdf`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'nepotismo':
|
||||||
|
blob = await gerarTermoNepotismo(funcionario);
|
||||||
|
nomeArquivo = `Termo_Nepotismo_${funcionario.nome.replace(/ /g, '_')}_${Date.now()}.pdf`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'opcao_remuneracao':
|
||||||
|
blob = await gerarTermoOpcaoRemuneracao(funcionario);
|
||||||
|
nomeArquivo = `Termo_Opcao_Remuneracao_${funcionario.nome.replace(/ /g, '_')}_${Date.now()}.pdf`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
alert('Modelo não encontrado');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadBlob(blob, nomeArquivo);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erro ao gerar declaração:', error);
|
||||||
|
alert('Erro ao gerar declaração preenchida');
|
||||||
|
} finally {
|
||||||
|
generating = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title text-xl border-b pb-3">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||||
|
</svg>
|
||||||
|
Modelos de Declarações
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="alert alert-info shadow-sm mb-4">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-5 w-5" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<div class="text-sm">
|
||||||
|
<p class="font-semibold">Baixe os modelos, preencha, assine e faça upload no sistema</p>
|
||||||
|
<p class="text-xs opacity-80 mt-1">Estes documentos são necessários para completar o cadastro do funcionário</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{#each modelosDeclaracoes as modelo}
|
||||||
|
<div class="card bg-base-200 shadow-sm hover:shadow-md transition-shadow">
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<div class="flex items-start gap-3">
|
||||||
|
<!-- Ícone PDF -->
|
||||||
|
<div class="flex-shrink-0 w-12 h-12 bg-error/10 rounded-lg flex items-center justify-center">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-error" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Conteúdo -->
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<h3 class="font-semibold text-sm mb-1 line-clamp-2">{modelo.nome}</h3>
|
||||||
|
<p class="text-xs text-base-content/70 mb-3 line-clamp-2">{modelo.descricao}</p>
|
||||||
|
|
||||||
|
<!-- Ações -->
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary btn-xs gap-1"
|
||||||
|
onclick={() => baixarModelo(modelo.arquivo, modelo.nome)}
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||||
|
</svg>
|
||||||
|
Baixar Modelo
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{#if showPreencherButton && modelo.podePreencherAutomaticamente && funcionario}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-outline btn-xs gap-1"
|
||||||
|
onclick={() => gerarPreenchido(modelo.id)}
|
||||||
|
disabled={generating}
|
||||||
|
>
|
||||||
|
{#if generating}
|
||||||
|
<span class="loading loading-spinner loading-xs"></span>
|
||||||
|
Gerando...
|
||||||
|
{:else}
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||||
|
</svg>
|
||||||
|
Gerar Preenchido
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 text-xs text-base-content/60 text-center">
|
||||||
|
<p>💡 Dica: Após preencher e assinar os documentos, faça upload na seção "Documentação Anexa"</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
463
apps/web/src/lib/components/PrintModal.svelte
Normal file
463
apps/web/src/lib/components/PrintModal.svelte
Normal file
@@ -0,0 +1,463 @@
|
|||||||
|
<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";
|
||||||
|
|
||||||
|
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,
|
||||||
|
bancario: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
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 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.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'],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (funcionario.simbolo) {
|
||||||
|
cargoData.push(['Símbolo', funcionario.simbolo.nome]);
|
||||||
|
}
|
||||||
|
if (funcionario.descricaoCargo) cargoData.push(['Descrição', funcionario.descricaoCargo]);
|
||||||
|
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]);
|
||||||
|
if (funcionario.pertenceOrgaoPublico) {
|
||||||
|
cargoData.push(['Pertence Órgão Público', 'Sim']);
|
||||||
|
if (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 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}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
Selecionar Todos
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline" onclick={deselectAll}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
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.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}
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" />
|
||||||
|
</svg>
|
||||||
|
Gerar PDF
|
||||||
|
{/if}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form method="dialog" class="modal-backdrop">
|
||||||
|
<button type="button" onclick={onClose}>fechar</button>
|
||||||
|
</form>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
49
apps/web/src/lib/utils/constants.ts
Normal file
49
apps/web/src/lib/utils/constants.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
// Constantes para selects e opções do formulário
|
||||||
|
|
||||||
|
export const SEXO_OPTIONS = [
|
||||||
|
{ value: "masculino", label: "Masculino" },
|
||||||
|
{ value: "feminino", label: "Feminino" },
|
||||||
|
{ value: "outro", label: "Outro" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ESTADO_CIVIL_OPTIONS = [
|
||||||
|
{ value: "solteiro", label: "Solteiro(a)" },
|
||||||
|
{ value: "casado", label: "Casado(a)" },
|
||||||
|
{ value: "divorciado", label: "Divorciado(a)" },
|
||||||
|
{ value: "viuvo", label: "Viúvo(a)" },
|
||||||
|
{ value: "uniao_estavel", label: "União Estável" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const GRAU_INSTRUCAO_OPTIONS = [
|
||||||
|
{ value: "fundamental", label: "Ensino Fundamental" },
|
||||||
|
{ value: "medio", label: "Ensino Médio" },
|
||||||
|
{ value: "superior", label: "Ensino Superior" },
|
||||||
|
{ value: "pos_graduacao", label: "Pós-Graduação" },
|
||||||
|
{ value: "mestrado", label: "Mestrado" },
|
||||||
|
{ value: "doutorado", label: "Doutorado" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const GRUPO_SANGUINEO_OPTIONS = [
|
||||||
|
{ value: "A", label: "A" },
|
||||||
|
{ value: "B", label: "B" },
|
||||||
|
{ value: "AB", label: "AB" },
|
||||||
|
{ value: "O", label: "O" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const FATOR_RH_OPTIONS = [
|
||||||
|
{ value: "positivo", label: "Positivo (+)" },
|
||||||
|
{ value: "negativo", label: "Negativo (-)" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const APOSENTADO_OPTIONS = [
|
||||||
|
{ value: "nao", label: "Não" },
|
||||||
|
{ value: "funape_ipsep", label: "FUNAPE/IPSEP" },
|
||||||
|
{ value: "inss", label: "INSS" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const UFS_BRASIL = [
|
||||||
|
"AC", "AL", "AP", "AM", "BA", "CE", "DF", "ES", "GO", "MA",
|
||||||
|
"MT", "MS", "MG", "PA", "PB", "PR", "PE", "PI", "RJ", "RN",
|
||||||
|
"RS", "RO", "RR", "SC", "SP", "SE", "TO"
|
||||||
|
];
|
||||||
|
|
||||||
581
apps/web/src/lib/utils/declaracoesGenerator.ts
Normal file
581
apps/web/src/lib/utils/declaracoesGenerator.ts
Normal file
@@ -0,0 +1,581 @@
|
|||||||
|
import jsPDF from 'jspdf';
|
||||||
|
import type { Doc } from '@sgse-app/backend/convex/_generated/dataModel';
|
||||||
|
import logoGovPE from '$lib/assets/logo_governo_PE.png';
|
||||||
|
|
||||||
|
type Funcionario = Doc<'funcionarios'>;
|
||||||
|
|
||||||
|
// Helper para adicionar logo no canto superior esquerdo
|
||||||
|
async function addLogo(doc: jsPDF): Promise<number> {
|
||||||
|
try {
|
||||||
|
// Criar uma promise para carregar a imagem
|
||||||
|
const logoImg = await new Promise<HTMLImageElement>((resolve, reject) => {
|
||||||
|
const img = new Image();
|
||||||
|
img.crossOrigin = 'anonymous'; // Para evitar problemas de CORS
|
||||||
|
|
||||||
|
img.onload = () => resolve(img);
|
||||||
|
img.onerror = (err) => reject(err);
|
||||||
|
|
||||||
|
// Timeout de 3 segundos
|
||||||
|
setTimeout(() => reject(new Error('Timeout loading logo')), 3000);
|
||||||
|
|
||||||
|
// Importante: definir src depois de definir os handlers
|
||||||
|
img.src = logoGovPE;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Logo proporcional: largura 25mm, altura ajustada automaticamente
|
||||||
|
const logoWidth = 25;
|
||||||
|
const aspectRatio = logoImg.height / logoImg.width;
|
||||||
|
const logoHeight = logoWidth * aspectRatio;
|
||||||
|
|
||||||
|
// Adicionar a imagem ao PDF
|
||||||
|
doc.addImage(logoImg, 'PNG', 15, 10, logoWidth, logoHeight);
|
||||||
|
|
||||||
|
// Retorna a posição Y onde o conteúdo pode começar (logo + margem)
|
||||||
|
return 10 + logoHeight + 5;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Erro ao carregar logo:', err);
|
||||||
|
return 20; // Posição padrão se a logo falhar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper para adicionar texto formatado
|
||||||
|
function addText(doc: jsPDF, text: string, x: number, y: number, options?: { bold?: boolean; size?: number; align?: 'left' | 'center' | 'right' }) {
|
||||||
|
if (options?.bold) {
|
||||||
|
doc.setFont('helvetica', 'bold');
|
||||||
|
} else {
|
||||||
|
doc.setFont('helvetica', 'normal');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options?.size) {
|
||||||
|
doc.setFontSize(options.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
const align = options?.align || 'left';
|
||||||
|
doc.text(text, x, y, { align });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper para adicionar campo com valor
|
||||||
|
function addField(doc: jsPDF, label: string, value: string, x: number, y: number, width?: number) {
|
||||||
|
doc.setFontSize(10);
|
||||||
|
doc.setFont('helvetica', 'bold');
|
||||||
|
doc.text(label, x, y);
|
||||||
|
|
||||||
|
doc.setFont('helvetica', 'normal');
|
||||||
|
const labelWidth = doc.getTextWidth(label) + 2;
|
||||||
|
|
||||||
|
if (width) {
|
||||||
|
// Desenhar linha para preenchimento
|
||||||
|
doc.line(x + labelWidth, y + 1, x + width, y + 1);
|
||||||
|
if (value) {
|
||||||
|
doc.text(value, x + labelWidth + 2, y);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
doc.text(value || '_____________________', x + labelWidth + 2, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
return y + 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Declaração de Acumulação de Cargo, Emprego, Função Pública ou Proventos
|
||||||
|
*/
|
||||||
|
export async function gerarDeclaracaoAcumulacaoCargo(funcionario: Funcionario): Promise<Blob> {
|
||||||
|
const doc = new jsPDF();
|
||||||
|
|
||||||
|
// Adicionar logo e obter posição inicial do conteúdo
|
||||||
|
let y = await addLogo(doc);
|
||||||
|
|
||||||
|
// Cabeçalho (ao lado da logo)
|
||||||
|
addText(doc, 'GOVERNO DO ESTADO DE PERNAMBUCO', 105, Math.max(y - 10, 20), { bold: true, size: 14, align: 'center' });
|
||||||
|
addText(doc, 'SECRETARIA DE ESPORTES', 105, Math.max(y - 2, 28), { bold: true, size: 12, align: 'center' });
|
||||||
|
|
||||||
|
y = Math.max(y, 40);
|
||||||
|
y += 5;
|
||||||
|
|
||||||
|
addText(doc, 'DECLARAÇÃO DE ACUMULAÇÃO DE CARGO, EMPREGO,', 105, y, { bold: true, size: 12, align: 'center' });
|
||||||
|
y += 6;
|
||||||
|
addText(doc, 'FUNÇÃO PÚBLICA OU PROVENTOS', 105, y, { bold: true, size: 12, align: 'center' });
|
||||||
|
y += 15;
|
||||||
|
|
||||||
|
// Corpo
|
||||||
|
doc.setFontSize(11);
|
||||||
|
doc.setFont('helvetica', 'normal');
|
||||||
|
|
||||||
|
const text1 = `Eu, ${funcionario.nome}, portador(a) do CPF nº ${funcionario.cpf}, `;
|
||||||
|
const text2 = `inscrito(a) no RG nº ${funcionario.rg}, residente e domiciliado(a) à ${funcionario.endereco}, `;
|
||||||
|
const text3 = `${funcionario.cidade}/${funcionario.uf}, DECLARO, para os devidos fins, que:`;
|
||||||
|
|
||||||
|
doc.text(text1, 20, y, { maxWidth: 170 });
|
||||||
|
y += 7;
|
||||||
|
doc.text(text2, 20, y, { maxWidth: 170 });
|
||||||
|
y += 7;
|
||||||
|
doc.text(text3, 20, y, { maxWidth: 170 });
|
||||||
|
y += 15;
|
||||||
|
|
||||||
|
// Opções
|
||||||
|
doc.setFont('helvetica', 'bold');
|
||||||
|
doc.text('( ) NÃO EXERÇO', 25, y);
|
||||||
|
y += 7;
|
||||||
|
doc.setFont('helvetica', 'normal');
|
||||||
|
doc.text('Outro cargo, emprego ou função pública, bem como não percebo proventos de', 30, y, { maxWidth: 160 });
|
||||||
|
y += 5;
|
||||||
|
doc.text('aposentadoria de regime próprio de previdência social ou do regime geral de', 30, y, { maxWidth: 160 });
|
||||||
|
y += 5;
|
||||||
|
doc.text('previdência social.', 30, y);
|
||||||
|
y += 12;
|
||||||
|
|
||||||
|
doc.setFont('helvetica', 'bold');
|
||||||
|
doc.text('( ) EXERÇO', 25, y);
|
||||||
|
y += 7;
|
||||||
|
doc.setFont('helvetica', 'normal');
|
||||||
|
doc.text('Outro cargo, emprego ou função pública, conforme discriminado abaixo:', 30, y, { maxWidth: 160 });
|
||||||
|
y += 10;
|
||||||
|
|
||||||
|
// Campos para preenchimento de outro cargo
|
||||||
|
y = addField(doc, 'Órgão/Entidade:', funcionario.orgaoOrigem || '', 30, y, 160);
|
||||||
|
y = addField(doc, 'Cargo/Função:', '', 30, y, 160);
|
||||||
|
y = addField(doc, 'Carga Horária:', '', 30, y, 80);
|
||||||
|
y = addField(doc, 'Remuneração:', '', 30, y, 80);
|
||||||
|
y += 5;
|
||||||
|
|
||||||
|
doc.setFont('helvetica', 'bold');
|
||||||
|
doc.text('( ) PERCEBO', 25, y);
|
||||||
|
y += 7;
|
||||||
|
doc.setFont('helvetica', 'normal');
|
||||||
|
doc.text('Proventos de aposentadoria:', 30, y);
|
||||||
|
y += 10;
|
||||||
|
|
||||||
|
y = addField(doc, 'Regime:', funcionario.aposentado === 'funape_ipsep' ? 'FUNAPE/IPSEP' : funcionario.aposentado === 'inss' ? 'INSS' : '', 30, y, 160);
|
||||||
|
y = addField(doc, 'Valor:', '', 30, y, 80);
|
||||||
|
y += 15;
|
||||||
|
|
||||||
|
// Declaração de veracidade
|
||||||
|
doc.text('Declaro, ainda, que estou ciente de que a acumulação ilegal de cargos,', 20, y, { maxWidth: 170 });
|
||||||
|
y += 5;
|
||||||
|
doc.text('empregos ou funções públicas constitui infração administrativa, sujeitando-me', 20, y, { maxWidth: 170 });
|
||||||
|
y += 5;
|
||||||
|
doc.text('às sanções legais cabíveis.', 20, y);
|
||||||
|
y += 20;
|
||||||
|
|
||||||
|
// Data e local
|
||||||
|
const hoje = new Date().toLocaleDateString('pt-BR');
|
||||||
|
doc.text(`Recife, ${hoje}`, 20, y);
|
||||||
|
y += 25;
|
||||||
|
|
||||||
|
// Assinatura
|
||||||
|
doc.line(70, y, 140, y);
|
||||||
|
y += 5;
|
||||||
|
addText(doc, funcionario.nome, 105, y, { align: 'center' });
|
||||||
|
y += 5;
|
||||||
|
addText(doc, `CPF: ${funcionario.cpf}`, 105, y, { size: 9, align: 'center' });
|
||||||
|
|
||||||
|
// Rodapé
|
||||||
|
doc.setFontSize(8);
|
||||||
|
doc.setTextColor(100);
|
||||||
|
doc.text('SGSE - Sistema de Gerenciamento da Secretaria de Esportes', 105, 285, { align: 'center' });
|
||||||
|
|
||||||
|
return doc.output('blob');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 2. Declaração de Dependentes para Fins de Imposto de Renda
|
||||||
|
*/
|
||||||
|
export async function gerarDeclaracaoDependentesIR(funcionario: Funcionario): Promise<Blob> {
|
||||||
|
const doc = new jsPDF();
|
||||||
|
|
||||||
|
// Adicionar logo e obter posição inicial do conteúdo
|
||||||
|
let y = await addLogo(doc);
|
||||||
|
|
||||||
|
// Cabeçalho (ao lado da logo)
|
||||||
|
addText(doc, 'GOVERNO DO ESTADO DE PERNAMBUCO', 105, Math.max(y - 10, 20), { bold: true, size: 14, align: 'center' });
|
||||||
|
addText(doc, 'SECRETARIA DE ESPORTES', 105, Math.max(y - 2, 28), { bold: true, size: 12, align: 'center' });
|
||||||
|
|
||||||
|
y = Math.max(y, 40);
|
||||||
|
y += 5;
|
||||||
|
|
||||||
|
addText(doc, 'DECLARAÇÃO DE DEPENDENTES', 105, y, { bold: true, size: 12, align: 'center' });
|
||||||
|
y += 6;
|
||||||
|
addText(doc, 'PARA FINS DE IMPOSTO DE RENDA', 105, y, { bold: true, size: 12, align: 'center' });
|
||||||
|
y += 15;
|
||||||
|
|
||||||
|
// Corpo
|
||||||
|
doc.setFontSize(11);
|
||||||
|
doc.setFont('helvetica', 'normal');
|
||||||
|
|
||||||
|
const text1 = `Eu, ${funcionario.nome}, portador(a) do CPF nº ${funcionario.cpf}, `;
|
||||||
|
const text2 = `inscrito(a) no RG nº ${funcionario.rg}, matrícula nº ${funcionario.matricula}, `;
|
||||||
|
const text3 = `DECLARO, para fins de dedução no Imposto de Renda na Fonte, que possuo os seguintes dependentes:`;
|
||||||
|
|
||||||
|
doc.text(text1, 20, y, { maxWidth: 170 });
|
||||||
|
y += 7;
|
||||||
|
doc.text(text2, 20, y, { maxWidth: 170 });
|
||||||
|
y += 7;
|
||||||
|
doc.text(text3, 20, y, { maxWidth: 170 });
|
||||||
|
y += 15;
|
||||||
|
|
||||||
|
// Tabela de dependentes
|
||||||
|
doc.setFont('helvetica', 'bold');
|
||||||
|
doc.setFontSize(10);
|
||||||
|
doc.text('NOME', 20, y);
|
||||||
|
doc.text('CPF', 80, y);
|
||||||
|
doc.text('PARENTESCO', 130, y);
|
||||||
|
doc.text('NASC.', 175, y);
|
||||||
|
y += 2;
|
||||||
|
doc.line(20, y, 195, y);
|
||||||
|
y += 8;
|
||||||
|
|
||||||
|
// Linhas para preenchimento (5 linhas)
|
||||||
|
doc.setFont('helvetica', 'normal');
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
doc.line(20, y, 75, y);
|
||||||
|
doc.line(80, y, 125, y);
|
||||||
|
doc.line(130, y, 170, y);
|
||||||
|
doc.line(175, y, 195, y);
|
||||||
|
y += 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
y += 10;
|
||||||
|
|
||||||
|
// Declaração de veracidade
|
||||||
|
doc.setFontSize(11);
|
||||||
|
doc.text('Declaro estar ciente de que a inclusão de dependente sem direito constitui', 20, y, { maxWidth: 170 });
|
||||||
|
y += 5;
|
||||||
|
doc.text('falsidade ideológica, sujeitando-me às penalidades previstas em lei, inclusive', 20, y, { maxWidth: 170 });
|
||||||
|
y += 5;
|
||||||
|
doc.text('ao recolhimento do imposto devido acrescido de multa e juros.', 20, y, { maxWidth: 170 });
|
||||||
|
y += 20;
|
||||||
|
|
||||||
|
// Data e local
|
||||||
|
const hoje = new Date().toLocaleDateString('pt-BR');
|
||||||
|
doc.text(`Recife, ${hoje}`, 20, y);
|
||||||
|
y += 25;
|
||||||
|
|
||||||
|
// Assinatura
|
||||||
|
doc.line(70, y, 140, y);
|
||||||
|
y += 5;
|
||||||
|
addText(doc, funcionario.nome, 105, y, { align: 'center' });
|
||||||
|
y += 5;
|
||||||
|
addText(doc, `CPF: ${funcionario.cpf} | Matrícula: ${funcionario.matricula}`, 105, y, { size: 9, align: 'center' });
|
||||||
|
|
||||||
|
// Rodapé
|
||||||
|
doc.setFontSize(8);
|
||||||
|
doc.setTextColor(100);
|
||||||
|
doc.text('SGSE - Sistema de Gerenciamento da Secretaria de Esportes', 105, 285, { align: 'center' });
|
||||||
|
|
||||||
|
return doc.output('blob');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 3. Declaração de Idoneidade
|
||||||
|
*/
|
||||||
|
export async function gerarDeclaracaoIdoneidade(funcionario: Funcionario): Promise<Blob> {
|
||||||
|
const doc = new jsPDF();
|
||||||
|
|
||||||
|
// Adicionar logo e obter posição inicial do conteúdo
|
||||||
|
let y = await addLogo(doc);
|
||||||
|
|
||||||
|
// Cabeçalho (ao lado da logo)
|
||||||
|
addText(doc, 'GOVERNO DO ESTADO DE PERNAMBUCO', 105, Math.max(y - 10, 20), { bold: true, size: 14, align: 'center' });
|
||||||
|
addText(doc, 'SECRETARIA DE ESPORTES', 105, Math.max(y - 2, 28), { bold: true, size: 12, align: 'center' });
|
||||||
|
|
||||||
|
y = Math.max(y, 40);
|
||||||
|
y += 5;
|
||||||
|
|
||||||
|
addText(doc, 'DECLARAÇÃO DE IDONEIDADE MORAL', 105, y, { bold: true, size: 12, align: 'center' });
|
||||||
|
y += 15;
|
||||||
|
|
||||||
|
// Corpo
|
||||||
|
doc.setFontSize(11);
|
||||||
|
doc.setFont('helvetica', 'normal');
|
||||||
|
|
||||||
|
const text1 = `Eu, ${funcionario.nome}, portador(a) do CPF nº ${funcionario.cpf}, `;
|
||||||
|
const text2 = `inscrito(a) no RG nº ${funcionario.rg}, residente e domiciliado(a) à ${funcionario.endereco}, `;
|
||||||
|
const text3 = `${funcionario.cidade}/${funcionario.uf}, DECLARO, sob as penas da lei, que:`;
|
||||||
|
|
||||||
|
doc.text(text1, 20, y, { maxWidth: 170 });
|
||||||
|
y += 7;
|
||||||
|
doc.text(text2, 20, y, { maxWidth: 170 });
|
||||||
|
y += 7;
|
||||||
|
doc.text(text3, 20, y, { maxWidth: 170 });
|
||||||
|
y += 15;
|
||||||
|
|
||||||
|
// Itens da declaração
|
||||||
|
const itens = [
|
||||||
|
'Gozo de boa saúde física e mental para o exercício das atribuições do cargo/função;',
|
||||||
|
'Não fui condenado(a) por crime contra a Administração Pública;',
|
||||||
|
'Não fui condenado(a) por ato de improbidade administrativa;',
|
||||||
|
'Não sofri, no exercício de função pública, penalidade incompatível com a investidura em cargo público;',
|
||||||
|
'Não estou em situação de incompatibilidade ou impedimento para o exercício de cargo ou função pública;',
|
||||||
|
'Tenho idoneidade moral e reputação ilibada;',
|
||||||
|
'Não respondo a processo administrativo disciplinar em qualquer esfera da Administração Pública;',
|
||||||
|
'Não fui demitido(a) ou exonerado(a) de cargo ou função pública por justa causa.'
|
||||||
|
];
|
||||||
|
|
||||||
|
itens.forEach((item, index) => {
|
||||||
|
doc.text(`${index + 1}. ${item}`, 20, y, { maxWidth: 170 });
|
||||||
|
y += 12;
|
||||||
|
});
|
||||||
|
|
||||||
|
y += 10;
|
||||||
|
|
||||||
|
// Declaração de veracidade
|
||||||
|
doc.text('Declaro, ainda, que todas as informações aqui prestadas são verdadeiras,', 20, y, { maxWidth: 170 });
|
||||||
|
y += 5;
|
||||||
|
doc.text('estando ciente de que a falsidade desta declaração configura crime previsto no', 20, y, { maxWidth: 170 });
|
||||||
|
y += 5;
|
||||||
|
doc.text('Código Penal Brasileiro, passível de apuração na forma da lei.', 20, y);
|
||||||
|
y += 20;
|
||||||
|
|
||||||
|
// Data e local
|
||||||
|
const hoje = new Date().toLocaleDateString('pt-BR');
|
||||||
|
doc.text(`Recife, ${hoje}`, 20, y);
|
||||||
|
y += 25;
|
||||||
|
|
||||||
|
// Assinatura
|
||||||
|
doc.line(70, y, 140, y);
|
||||||
|
y += 5;
|
||||||
|
addText(doc, funcionario.nome, 105, y, { align: 'center' });
|
||||||
|
y += 5;
|
||||||
|
addText(doc, `CPF: ${funcionario.cpf}`, 105, y, { size: 9, align: 'center' });
|
||||||
|
|
||||||
|
// Rodapé
|
||||||
|
doc.setFontSize(8);
|
||||||
|
doc.setTextColor(100);
|
||||||
|
doc.text('SGSE - Sistema de Gerenciamento da Secretaria de Esportes', 105, 285, { align: 'center' });
|
||||||
|
|
||||||
|
return doc.output('blob');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 4. Termo de Declaração de Nepotismo
|
||||||
|
*/
|
||||||
|
export async function gerarTermoNepotismo(funcionario: Funcionario): Promise<Blob> {
|
||||||
|
const doc = new jsPDF();
|
||||||
|
|
||||||
|
// Adicionar logo e obter posição inicial do conteúdo
|
||||||
|
let y = await addLogo(doc);
|
||||||
|
|
||||||
|
// Cabeçalho (ao lado da logo)
|
||||||
|
addText(doc, 'GOVERNO DO ESTADO DE PERNAMBUCO', 105, Math.max(y - 10, 20), { bold: true, size: 14, align: 'center' });
|
||||||
|
addText(doc, 'SECRETARIA DE ESPORTES', 105, Math.max(y - 2, 28), { bold: true, size: 12, align: 'center' });
|
||||||
|
|
||||||
|
y = Math.max(y, 40);
|
||||||
|
y += 5;
|
||||||
|
|
||||||
|
addText(doc, 'TERMO DE DECLARAÇÃO DE NEPOTISMO', 105, y, { bold: true, size: 12, align: 'center' });
|
||||||
|
y += 15;
|
||||||
|
|
||||||
|
// Corpo
|
||||||
|
doc.setFontSize(11);
|
||||||
|
doc.setFont('helvetica', 'normal');
|
||||||
|
|
||||||
|
const text1 = `Eu, ${funcionario.nome}, portador(a) do CPF nº ${funcionario.cpf}, `;
|
||||||
|
const text2 = `inscrito(a) no RG nº ${funcionario.rg}, matrícula nº ${funcionario.matricula}, `;
|
||||||
|
const text3 = `nomeado(a) para o cargo/função de ${funcionario.descricaoCargo || '_________________'}, `;
|
||||||
|
const text4 = `DECLARO, para os fins do disposto na Súmula Vinculante nº 13 do STF e demais `;
|
||||||
|
const text5 = `normas de combate ao nepotismo, que:`;
|
||||||
|
|
||||||
|
doc.text(text1, 20, y, { maxWidth: 170 });
|
||||||
|
y += 7;
|
||||||
|
doc.text(text2, 20, y, { maxWidth: 170 });
|
||||||
|
y += 7;
|
||||||
|
doc.text(text3, 20, y, { maxWidth: 170 });
|
||||||
|
y += 7;
|
||||||
|
doc.text(text4, 20, y, { maxWidth: 170 });
|
||||||
|
y += 5;
|
||||||
|
doc.text(text5, 20, y, { maxWidth: 170 });
|
||||||
|
y += 15;
|
||||||
|
|
||||||
|
// Opções
|
||||||
|
doc.setFont('helvetica', 'bold');
|
||||||
|
doc.text('( ) NÃO POSSUO', 25, y);
|
||||||
|
y += 7;
|
||||||
|
doc.setFont('helvetica', 'normal');
|
||||||
|
doc.text('Cônjuge, companheiro(a) ou parente em linha reta, colateral ou por afinidade, até', 30, y, { maxWidth: 160 });
|
||||||
|
y += 5;
|
||||||
|
doc.text('o terceiro grau, exercendo cargo em comissão ou função de confiança nesta', 30, y, { maxWidth: 160 });
|
||||||
|
y += 5;
|
||||||
|
doc.text('Secretaria ou em órgão a ela vinculado.', 30, y);
|
||||||
|
y += 12;
|
||||||
|
|
||||||
|
doc.setFont('helvetica', 'bold');
|
||||||
|
doc.text('( ) POSSUO', 25, y);
|
||||||
|
y += 7;
|
||||||
|
doc.setFont('helvetica', 'normal');
|
||||||
|
doc.text('O(s) seguinte(s) parente(s) com vínculo nesta Secretaria:', 30, y);
|
||||||
|
y += 10;
|
||||||
|
|
||||||
|
// Campos para parentes
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
y = addField(doc, 'Nome:', '', 30, y, 160);
|
||||||
|
y = addField(doc, 'CPF:', '', 30, y, 80);
|
||||||
|
y = addField(doc, 'Grau de Parentesco:', '', 110, y - 7, 80);
|
||||||
|
y = addField(doc, 'Cargo/Função:', '', 30, y, 160);
|
||||||
|
y = addField(doc, 'Órgão:', '', 30, y, 160);
|
||||||
|
y += 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
y += 5;
|
||||||
|
|
||||||
|
// Declaração de veracidade
|
||||||
|
doc.text('Declaro estar ciente de que a nomeação, designação ou contratação em', 20, y, { maxWidth: 170 });
|
||||||
|
y += 5;
|
||||||
|
doc.text('desconformidade com as vedações ao nepotismo importará em nulidade do ato,', 20, y, { maxWidth: 170 });
|
||||||
|
y += 5;
|
||||||
|
doc.text('sem prejuízo das sanções administrativas, civis e penais cabíveis.', 20, y);
|
||||||
|
y += 20;
|
||||||
|
|
||||||
|
// Data e local
|
||||||
|
const hoje = new Date().toLocaleDateString('pt-BR');
|
||||||
|
doc.text(`Recife, ${hoje}`, 20, y);
|
||||||
|
y += 25;
|
||||||
|
|
||||||
|
// Assinatura
|
||||||
|
doc.line(70, y, 140, y);
|
||||||
|
y += 5;
|
||||||
|
addText(doc, funcionario.nome, 105, y, { align: 'center' });
|
||||||
|
y += 5;
|
||||||
|
addText(doc, `CPF: ${funcionario.cpf} | Matrícula: ${funcionario.matricula}`, 105, y, { size: 9, align: 'center' });
|
||||||
|
|
||||||
|
// Rodapé
|
||||||
|
doc.setFontSize(8);
|
||||||
|
doc.setTextColor(100);
|
||||||
|
doc.text('SGSE - Sistema de Gerenciamento da Secretaria de Esportes', 105, 285, { align: 'center' });
|
||||||
|
|
||||||
|
return doc.output('blob');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 5. Termo de Opção - Remuneração
|
||||||
|
*/
|
||||||
|
export async function gerarTermoOpcaoRemuneracao(funcionario: Funcionario): Promise<Blob> {
|
||||||
|
const doc = new jsPDF();
|
||||||
|
|
||||||
|
// Adicionar logo e obter posição inicial do conteúdo
|
||||||
|
let y = await addLogo(doc);
|
||||||
|
|
||||||
|
// Cabeçalho (ao lado da logo)
|
||||||
|
addText(doc, 'GOVERNO DO ESTADO DE PERNAMBUCO', 105, Math.max(y - 10, 20), { bold: true, size: 14, align: 'center' });
|
||||||
|
addText(doc, 'SECRETARIA DE ESPORTES', 105, Math.max(y - 2, 28), { bold: true, size: 12, align: 'center' });
|
||||||
|
|
||||||
|
y = Math.max(y, 40);
|
||||||
|
y += 5;
|
||||||
|
|
||||||
|
addText(doc, 'TERMO DE OPÇÃO DE REMUNERAÇÃO', 105, y, { bold: true, size: 12, align: 'center' });
|
||||||
|
y += 15;
|
||||||
|
|
||||||
|
// Corpo
|
||||||
|
doc.setFontSize(11);
|
||||||
|
doc.setFont('helvetica', 'normal');
|
||||||
|
|
||||||
|
const text1 = `Eu, ${funcionario.nome}, portador(a) do CPF nº ${funcionario.cpf}, `;
|
||||||
|
const text2 = `inscrito(a) no RG nº ${funcionario.rg}, matrícula nº ${funcionario.matricula}, `;
|
||||||
|
const text3 = `nomeado(a) para o cargo/função de ${funcionario.descricaoCargo || '_________________'}, `;
|
||||||
|
const text4 = `nos termos do Ato/Portaria nº ${funcionario.nomeacaoPortaria || '_____'} de ${funcionario.nomeacaoData || '___/___/___'}, `;
|
||||||
|
const text5 = `DECLARO, para os devidos fins, que:`;
|
||||||
|
|
||||||
|
doc.text(text1, 20, y, { maxWidth: 170 });
|
||||||
|
y += 7;
|
||||||
|
doc.text(text2, 20, y, { maxWidth: 170 });
|
||||||
|
y += 7;
|
||||||
|
doc.text(text3, 20, y, { maxWidth: 170 });
|
||||||
|
y += 7;
|
||||||
|
doc.text(text4, 20, y, { maxWidth: 170 });
|
||||||
|
y += 7;
|
||||||
|
doc.text(text5, 20, y);
|
||||||
|
y += 15;
|
||||||
|
|
||||||
|
// Seção 1 - Vínculo Anterior
|
||||||
|
doc.setFont('helvetica', 'bold');
|
||||||
|
doc.text('1. QUANTO AO VÍNCULO ANTERIOR:', 20, y);
|
||||||
|
y += 10;
|
||||||
|
doc.setFont('helvetica', 'normal');
|
||||||
|
|
||||||
|
doc.text('( ) NÃO POSSUO outro vínculo com a Administração Pública', 25, y);
|
||||||
|
y += 10;
|
||||||
|
|
||||||
|
doc.text('( ) POSSUO vínculo efetivo com:', 25, y);
|
||||||
|
y += 8;
|
||||||
|
|
||||||
|
y = addField(doc, 'Órgão/Entidade:', funcionario.orgaoOrigem || '', 30, y, 160);
|
||||||
|
y = addField(doc, 'Cargo:', '', 30, y, 160);
|
||||||
|
y = addField(doc, 'Matrícula:', '', 30, y, 80);
|
||||||
|
y += 10;
|
||||||
|
|
||||||
|
// Seção 2 - Opção de Remuneração
|
||||||
|
doc.setFont('helvetica', 'bold');
|
||||||
|
doc.text('2. QUANTO À REMUNERAÇÃO, OPTO POR RECEBER:', 20, y);
|
||||||
|
y += 10;
|
||||||
|
doc.setFont('helvetica', 'normal');
|
||||||
|
|
||||||
|
doc.text('( ) A remuneração do cargo em comissão/função gratificada ora assumido', 25, y);
|
||||||
|
y += 10;
|
||||||
|
|
||||||
|
doc.text('( ) A remuneração do cargo efetivo + a gratificação/símbolo', 25, y);
|
||||||
|
y += 10;
|
||||||
|
|
||||||
|
doc.text('( ) A remuneração do cargo efetivo (sem percepção de gratificação)', 25, y);
|
||||||
|
y += 15;
|
||||||
|
|
||||||
|
// Seção 3 - Dados Bancários
|
||||||
|
doc.setFont('helvetica', 'bold');
|
||||||
|
doc.text('3. DADOS BANCÁRIOS PARA PAGAMENTO:', 20, y);
|
||||||
|
y += 10;
|
||||||
|
doc.setFont('helvetica', 'normal');
|
||||||
|
|
||||||
|
y = addField(doc, 'Banco:', 'Bradesco', 20, y, 80);
|
||||||
|
y = addField(doc, 'Agência:', funcionario.contaBradescoAgencia || '', 110, y - 7, 80);
|
||||||
|
y = addField(doc, 'Conta Corrente:', funcionario.contaBradescoNumero || '', 20, y, 80);
|
||||||
|
y = addField(doc, 'Dígito:', funcionario.contaBradescoDV || '', 110, y - 7, 40);
|
||||||
|
y += 15;
|
||||||
|
|
||||||
|
// Declaração de ciência
|
||||||
|
doc.text('Declaro estar ciente de que:', 20, y);
|
||||||
|
y += 8;
|
||||||
|
|
||||||
|
const ciencias = [
|
||||||
|
'A remuneração será paga conforme a opção acima, respeitada a legislação vigente;',
|
||||||
|
'Qualquer alteração na opção deverá ser comunicada formalmente à Secretaria;',
|
||||||
|
'A não apresentação deste termo poderá implicar em atraso no pagamento;',
|
||||||
|
'As informações aqui prestadas são verdadeiras e atualizadas.'
|
||||||
|
];
|
||||||
|
|
||||||
|
ciencias.forEach((item, index) => {
|
||||||
|
doc.text(`${index + 1}. ${item}`, 25, y, { maxWidth: 165 });
|
||||||
|
y += 10;
|
||||||
|
});
|
||||||
|
|
||||||
|
y += 5;
|
||||||
|
|
||||||
|
// Data e local
|
||||||
|
const hoje = new Date().toLocaleDateString('pt-BR');
|
||||||
|
doc.text(`Recife, ${hoje}`, 20, y);
|
||||||
|
y += 25;
|
||||||
|
|
||||||
|
// Assinatura
|
||||||
|
doc.line(70, y, 140, y);
|
||||||
|
y += 5;
|
||||||
|
addText(doc, funcionario.nome, 105, y, { align: 'center' });
|
||||||
|
y += 5;
|
||||||
|
addText(doc, `CPF: ${funcionario.cpf} | Matrícula: ${funcionario.matricula}`, 105, y, { size: 9, align: 'center' });
|
||||||
|
|
||||||
|
// Rodapé
|
||||||
|
doc.setFontSize(8);
|
||||||
|
doc.setTextColor(100);
|
||||||
|
doc.text('SGSE - Sistema de Gerenciamento da Secretaria de Esportes', 105, 285, { align: 'center' });
|
||||||
|
|
||||||
|
return doc.output('blob');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Função helper para download
|
||||||
|
export function downloadBlob(blob: Blob, filename: string) {
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.download = filename;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
187
apps/web/src/lib/utils/documentos.ts
Normal file
187
apps/web/src/lib/utils/documentos.ts
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
// Definições dos documentos com URLs de referência
|
||||||
|
|
||||||
|
export interface DocumentoDefinicao {
|
||||||
|
campo: string;
|
||||||
|
nome: string;
|
||||||
|
helpUrl?: string;
|
||||||
|
categoria: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const documentos: DocumentoDefinicao[] = [
|
||||||
|
// Antecedentes Criminais
|
||||||
|
{
|
||||||
|
campo: "certidaoAntecedentesPF",
|
||||||
|
nome: "Certidão de Antecedentes Criminais - Polícia Federal",
|
||||||
|
helpUrl: "https://servicos.pf.gov.br/epol-sinic-publico/",
|
||||||
|
categoria: "Antecedentes Criminais",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "certidaoAntecedentesJFPE",
|
||||||
|
nome: "Certidão de Antecedentes Criminais - Justiça Federal de Pernambuco",
|
||||||
|
helpUrl: "https://certidoes.trf5.jus.br/certidoes2022/paginas/certidaocriminal.faces",
|
||||||
|
categoria: "Antecedentes Criminais",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "certidaoAntecedentesSDS",
|
||||||
|
nome: "Certidão de Antecedentes Criminais - SDS-PE",
|
||||||
|
helpUrl: "http://www.servicos.sds.pe.gov.br/antecedentes/public/pages/certidaoAntecedentesCriminais/certidaoAntecedentesCriminaisEmitir.jsf",
|
||||||
|
categoria: "Antecedentes Criminais",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "certidaoAntecedentesTJPE",
|
||||||
|
nome: "Certidão de Antecedentes Criminais - TJPE",
|
||||||
|
helpUrl: "https://certidoesunificadas.app.tjpe.jus.br/certidao-criminal-pf",
|
||||||
|
categoria: "Antecedentes Criminais",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "certidaoImprobidade",
|
||||||
|
nome: "Certidão Improbidade Administrativa",
|
||||||
|
helpUrl: "https://www.cnj.jus.br/improbidade_adm/consultar_requerido.php",
|
||||||
|
categoria: "Antecedentes Criminais",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Documentos Pessoais
|
||||||
|
{
|
||||||
|
campo: "rgFrente",
|
||||||
|
nome: "Carteira de Identidade SDS/PE ou (SSP-PE) - Frente",
|
||||||
|
categoria: "Documentos Pessoais",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "rgVerso",
|
||||||
|
nome: "Carteira de Identidade SDS/PE ou (SSP-PE) - Verso",
|
||||||
|
categoria: "Documentos Pessoais",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "cpfFrente",
|
||||||
|
nome: "CPF/CIC - Frente",
|
||||||
|
categoria: "Documentos Pessoais",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "cpfVerso",
|
||||||
|
nome: "CPF/CIC - Verso",
|
||||||
|
categoria: "Documentos Pessoais",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "situacaoCadastralCPF",
|
||||||
|
nome: "Situação Cadastral CPF",
|
||||||
|
helpUrl: "https://servicos.receita.fazenda.gov.br/servicos/cpf/consultasituacao/consultapublica.asp",
|
||||||
|
categoria: "Documentos Pessoais",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "certidaoRegistroCivil",
|
||||||
|
nome: "Certidão de Registro Civil (Nascimento, Casamento ou União Estável)",
|
||||||
|
categoria: "Documentos Pessoais",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Documentos Eleitorais
|
||||||
|
{
|
||||||
|
campo: "tituloEleitorFrente",
|
||||||
|
nome: "Título de Eleitor - Frente",
|
||||||
|
categoria: "Documentos Eleitorais",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "tituloEleitorVerso",
|
||||||
|
nome: "Título de Eleitor - Verso",
|
||||||
|
categoria: "Documentos Eleitorais",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "comprovanteVotacao",
|
||||||
|
nome: "Comprovante de Votação Última Eleição ou Certidão de Quitação Eleitoral",
|
||||||
|
helpUrl: "https://www.tse.jus.br",
|
||||||
|
categoria: "Documentos Eleitorais",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Documentos Profissionais
|
||||||
|
{
|
||||||
|
campo: "carteiraProfissionalFrente",
|
||||||
|
nome: "Carteira Profissional - Frente (página da foto)",
|
||||||
|
categoria: "Documentos Profissionais",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "carteiraProfissionalVerso",
|
||||||
|
nome: "Carteira Profissional - Verso (página da foto)",
|
||||||
|
categoria: "Documentos Profissionais",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "comprovantePIS",
|
||||||
|
nome: "Comprovante de PIS/PASEP",
|
||||||
|
categoria: "Documentos Profissionais",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "reservistaDoc",
|
||||||
|
nome: "Reservista (obrigatória para homem até 45 anos)",
|
||||||
|
categoria: "Documentos Profissionais",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Certidões e Comprovantes
|
||||||
|
{
|
||||||
|
campo: "certidaoNascimentoDependentes",
|
||||||
|
nome: "Certidão de Nascimento do(s) Dependente(s) para Imposto de Renda",
|
||||||
|
categoria: "Certidões e Comprovantes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "cpfDependentes",
|
||||||
|
nome: "CPF do(s) Dependente(s) para Imposto de Renda",
|
||||||
|
categoria: "Certidões e Comprovantes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "comprovanteEscolaridade",
|
||||||
|
nome: "Documento de Comprovação do Nível de Escolaridade",
|
||||||
|
categoria: "Certidões e Comprovantes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "comprovanteResidencia",
|
||||||
|
nome: "Comprovante de Residência",
|
||||||
|
categoria: "Certidões e Comprovantes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "comprovanteContaBradesco",
|
||||||
|
nome: "Comprovante de Conta-Corrente no Banco BRADESCO",
|
||||||
|
categoria: "Certidões e Comprovantes",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Declarações
|
||||||
|
{
|
||||||
|
campo: "declaracaoAcumulacaoCargo",
|
||||||
|
nome: "Declaração de Acumulação de Cargo, Emprego, Função Pública ou Proventos",
|
||||||
|
categoria: "Declarações",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "declaracaoDependentesIR",
|
||||||
|
nome: "Declaração de Dependentes para Fins de Imposto de Renda",
|
||||||
|
categoria: "Declarações",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "declaracaoIdoneidade",
|
||||||
|
nome: "Declaração de Idoneidade",
|
||||||
|
categoria: "Declarações",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "termoNepotismo",
|
||||||
|
nome: "Termo de Declaração de Nepotismo",
|
||||||
|
categoria: "Declarações",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
campo: "termoOpcaoRemuneracao",
|
||||||
|
nome: "Termo de Opção - Remuneração",
|
||||||
|
categoria: "Declarações",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const categoriasDocumentos = [
|
||||||
|
"Antecedentes Criminais",
|
||||||
|
"Documentos Pessoais",
|
||||||
|
"Documentos Eleitorais",
|
||||||
|
"Documentos Profissionais",
|
||||||
|
"Certidões e Comprovantes",
|
||||||
|
"Declarações",
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getDocumentosByCategoria(categoria: string): DocumentoDefinicao[] {
|
||||||
|
return documentos.filter(doc => doc.categoria === categoria);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDocumentoDefinicao(campo: string): DocumentoDefinicao | undefined {
|
||||||
|
return documentos.find(doc => doc.campo === campo);
|
||||||
|
}
|
||||||
|
|
||||||
176
apps/web/src/lib/utils/masks.ts
Normal file
176
apps/web/src/lib/utils/masks.ts
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
// Helper functions for input masks and validations
|
||||||
|
|
||||||
|
/** Remove all non-digit characters from string */
|
||||||
|
export const onlyDigits = (value: string): string => {
|
||||||
|
return (value || "").replace(/\D/g, "");
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Format CPF: 000.000.000-00 */
|
||||||
|
export const maskCPF = (value: string): string => {
|
||||||
|
const digits = onlyDigits(value).slice(0, 11);
|
||||||
|
return digits
|
||||||
|
.replace(/(\d{3})(\d)/, "$1.$2")
|
||||||
|
.replace(/(\d{3})(\d)/, "$1.$2")
|
||||||
|
.replace(/(\d{3})(\d{1,2})$/, "$1-$2");
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Validate CPF format and checksum */
|
||||||
|
export const validateCPF = (value: string): boolean => {
|
||||||
|
const digits = onlyDigits(value);
|
||||||
|
|
||||||
|
if (digits.length !== 11 || /^([0-9])\1+$/.test(digits)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const calculateDigit = (base: string, factor: number): number => {
|
||||||
|
let sum = 0;
|
||||||
|
for (let i = 0; i < base.length; i++) {
|
||||||
|
sum += parseInt(base[i]) * (factor - i);
|
||||||
|
}
|
||||||
|
const rest = (sum * 10) % 11;
|
||||||
|
return rest === 10 ? 0 : rest;
|
||||||
|
};
|
||||||
|
|
||||||
|
const digit1 = calculateDigit(digits.slice(0, 9), 10);
|
||||||
|
const digit2 = calculateDigit(digits.slice(0, 10), 11);
|
||||||
|
|
||||||
|
return digits[9] === String(digit1) && digits[10] === String(digit2);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Format CEP: 00000-000 */
|
||||||
|
export const maskCEP = (value: string): string => {
|
||||||
|
const digits = onlyDigits(value).slice(0, 8);
|
||||||
|
return digits.replace(/(\d{5})(\d{1,3})$/, "$1-$2");
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Format phone: (00) 0000-0000 or (00) 00000-0000 */
|
||||||
|
export const maskPhone = (value: string): string => {
|
||||||
|
const digits = onlyDigits(value).slice(0, 11);
|
||||||
|
|
||||||
|
if (digits.length <= 10) {
|
||||||
|
return digits
|
||||||
|
.replace(/(\d{2})(\d)/, "($1) $2")
|
||||||
|
.replace(/(\d{4})(\d{1,4})$/, "$1-$2");
|
||||||
|
}
|
||||||
|
|
||||||
|
return digits
|
||||||
|
.replace(/(\d{2})(\d)/, "($1) $2")
|
||||||
|
.replace(/(\d{5})(\d{1,4})$/, "$1-$2");
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Format date: dd/mm/aaaa */
|
||||||
|
export const maskDate = (value: string): string => {
|
||||||
|
const digits = onlyDigits(value).slice(0, 8);
|
||||||
|
return digits
|
||||||
|
.replace(/(\d{2})(\d)/, "$1/$2")
|
||||||
|
.replace(/(\d{2})(\d{1,4})$/, "$1/$2");
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Validate date in format dd/mm/aaaa */
|
||||||
|
export const validateDate = (value: string): boolean => {
|
||||||
|
const match = value.match(/^(\d{2})\/(\d{2})\/(\d{4})$/);
|
||||||
|
if (!match) return false;
|
||||||
|
|
||||||
|
const day = Number(match[1]);
|
||||||
|
const month = Number(match[2]) - 1;
|
||||||
|
const year = Number(match[3]);
|
||||||
|
|
||||||
|
const date = new Date(year, month, day);
|
||||||
|
|
||||||
|
return (
|
||||||
|
date.getFullYear() === year &&
|
||||||
|
date.getMonth() === month &&
|
||||||
|
date.getDate() === day
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Format UF: uppercase, max 2 chars */
|
||||||
|
export const maskUF = (value: string): string => {
|
||||||
|
return (value || "").toUpperCase().replace(/[^A-Z]/g, "").slice(0, 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Format RG by UF */
|
||||||
|
const rgFormatByUF: Record<string, [number, number, number, number]> = {
|
||||||
|
RJ: [2, 3, 2, 1],
|
||||||
|
SP: [2, 3, 3, 1],
|
||||||
|
MG: [2, 3, 3, 1],
|
||||||
|
ES: [2, 3, 3, 1],
|
||||||
|
PR: [2, 3, 3, 1],
|
||||||
|
SC: [2, 3, 3, 1],
|
||||||
|
RS: [2, 3, 3, 1],
|
||||||
|
BA: [2, 3, 3, 1],
|
||||||
|
PE: [2, 3, 3, 1],
|
||||||
|
CE: [2, 3, 3, 1],
|
||||||
|
PA: [2, 3, 3, 1],
|
||||||
|
AM: [2, 3, 3, 1],
|
||||||
|
AC: [2, 3, 3, 1],
|
||||||
|
AP: [2, 3, 3, 1],
|
||||||
|
AL: [2, 3, 3, 1],
|
||||||
|
RN: [2, 3, 3, 1],
|
||||||
|
PB: [2, 3, 3, 1],
|
||||||
|
MA: [2, 3, 3, 1],
|
||||||
|
PI: [2, 3, 3, 1],
|
||||||
|
DF: [2, 3, 3, 1],
|
||||||
|
GO: [2, 3, 3, 1],
|
||||||
|
MT: [2, 3, 3, 1],
|
||||||
|
MS: [2, 3, 3, 1],
|
||||||
|
RO: [2, 3, 3, 1],
|
||||||
|
RR: [2, 3, 3, 1],
|
||||||
|
TO: [2, 3, 3, 1],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const maskRGByUF = (uf: string, value: string): string => {
|
||||||
|
const raw = (value || "").toUpperCase().replace(/[^0-9X]/g, "");
|
||||||
|
const [a, b, c, dv] = rgFormatByUF[uf?.toUpperCase()] ?? [2, 3, 3, 1];
|
||||||
|
const baseMax = a + b + c;
|
||||||
|
const baseDigits = raw.replace(/X/g, "").slice(0, baseMax);
|
||||||
|
const verifier = raw.slice(baseDigits.length, baseDigits.length + dv).slice(0, 1);
|
||||||
|
|
||||||
|
const g1 = baseDigits.slice(0, a);
|
||||||
|
const g2 = baseDigits.slice(a, a + b);
|
||||||
|
const g3 = baseDigits.slice(a + b, a + b + c);
|
||||||
|
|
||||||
|
let formatted = g1;
|
||||||
|
if (g2) formatted += `.${g2}`;
|
||||||
|
if (g3) formatted += `.${g3}`;
|
||||||
|
if (verifier) formatted += `-${verifier}`;
|
||||||
|
|
||||||
|
return formatted;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const padRGLeftByUF = (uf: string, value: string): string => {
|
||||||
|
const raw = (value || "").toUpperCase().replace(/[^0-9X]/g, "");
|
||||||
|
const [a, b, c, dv] = rgFormatByUF[uf?.toUpperCase()] ?? [2, 3, 3, 1];
|
||||||
|
const baseMax = a + b + c;
|
||||||
|
let base = raw.replace(/X/g, "");
|
||||||
|
const verifier = raw.slice(base.length, base.length + dv).slice(0, 1);
|
||||||
|
|
||||||
|
if (base.length < baseMax) {
|
||||||
|
base = base.padStart(baseMax, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
return maskRGByUF(uf, base + (verifier || ""));
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Format account number */
|
||||||
|
export const maskContaBancaria = (value: string): string => {
|
||||||
|
const digits = onlyDigits(value);
|
||||||
|
return digits;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Format zone and section for voter title */
|
||||||
|
export const maskZonaSecao = (value: string): string => {
|
||||||
|
const digits = onlyDigits(value).slice(0, 4);
|
||||||
|
return digits;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Format general numeric field */
|
||||||
|
export const maskNumeric = (value: string): string => {
|
||||||
|
return onlyDigits(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Remove extra spaces and trim */
|
||||||
|
export const normalizeText = (value: string): string => {
|
||||||
|
return (value || "").replace(/\s+/g, " ").trim();
|
||||||
|
};
|
||||||
|
|
||||||
52
apps/web/src/lib/utils/modelosDeclaracoes.ts
Normal file
52
apps/web/src/lib/utils/modelosDeclaracoes.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// Definições dos modelos de declaração
|
||||||
|
|
||||||
|
export interface ModeloDeclaracao {
|
||||||
|
id: string;
|
||||||
|
nome: string;
|
||||||
|
descricao: string;
|
||||||
|
arquivo: string;
|
||||||
|
podePreencherAutomaticamente: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const modelosDeclaracoes: ModeloDeclaracao[] = [
|
||||||
|
{
|
||||||
|
id: "acumulacao_cargo",
|
||||||
|
nome: "Declaração de Acumulação de Cargo",
|
||||||
|
descricao: "Declaração sobre acumulação de cargo, emprego, função pública ou proventos",
|
||||||
|
arquivo: "/modelos/declaracoes/Declaração de Acumulação de Cargo, Emprego, Função Pública ou Proventos.pdf",
|
||||||
|
podePreencherAutomaticamente: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "dependentes_ir",
|
||||||
|
nome: "Declaração de Dependentes",
|
||||||
|
descricao: "Declaração de dependentes para fins de Imposto de Renda",
|
||||||
|
arquivo: "/modelos/declaracoes/Declaração de Dependentes para Fins de Imposto de Renda.pdf",
|
||||||
|
podePreencherAutomaticamente: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "idoneidade",
|
||||||
|
nome: "Declaração de Idoneidade",
|
||||||
|
descricao: "Declaração de idoneidade moral e conduta ilibada",
|
||||||
|
arquivo: "/modelos/declaracoes/Declaração de Idoneidade.pdf",
|
||||||
|
podePreencherAutomaticamente: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "nepotismo",
|
||||||
|
nome: "Termo de Declaração de Nepotismo",
|
||||||
|
descricao: "Declaração sobre inexistência de situação de nepotismo",
|
||||||
|
arquivo: "/modelos/declaracoes/Termo de Declaração de Nepotismo.pdf",
|
||||||
|
podePreencherAutomaticamente: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "opcao_remuneracao",
|
||||||
|
nome: "Termo de Opção - Remuneração",
|
||||||
|
descricao: "Termo de opção de remuneração",
|
||||||
|
arquivo: "/modelos/declaracoes/Termo de Opção - Remuneração.pdf",
|
||||||
|
podePreencherAutomaticamente: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export function getModeloById(id: string): ModeloDeclaracao | undefined {
|
||||||
|
return modelosDeclaracoes.find(modelo => modelo.id === id);
|
||||||
|
}
|
||||||
|
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
import { api } from "@sgse-app/backend/convex/_generated/api";
|
import { api } from "@sgse-app/backend/convex/_generated/api";
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
import type { SimboloTipo } from "@sgse-app/backend/convex/schema";
|
import type { SimboloTipo } from "@sgse-app/backend/convex/schema";
|
||||||
|
import PrintModal from "$lib/components/PrintModal.svelte";
|
||||||
|
|
||||||
const client = useConvexClient();
|
const client = useConvexClient();
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@
|
|||||||
let deletingId: string | null = null;
|
let deletingId: string | null = null;
|
||||||
let toDelete: { id: string; nome: string } | null = null;
|
let toDelete: { id: string; nome: string } | null = null;
|
||||||
let openMenuId: string | null = null;
|
let openMenuId: string | null = null;
|
||||||
|
let funcionarioParaImprimir: any = null;
|
||||||
|
|
||||||
let filtroNome = "";
|
let filtroNome = "";
|
||||||
let filtroCPF = "";
|
let filtroCPF = "";
|
||||||
@@ -48,6 +50,18 @@
|
|||||||
toDelete = null;
|
toDelete = null;
|
||||||
(document.getElementById("delete_modal_func") as HTMLDialogElement)?.close();
|
(document.getElementById("delete_modal_func") as HTMLDialogElement)?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function openPrintModal(funcionarioId: string) {
|
||||||
|
try {
|
||||||
|
const data = await client.query(api.funcionarios.getFichaCompleta, {
|
||||||
|
id: funcionarioId as any
|
||||||
|
});
|
||||||
|
funcionarioParaImprimir = data;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Erro ao carregar funcionário:", err);
|
||||||
|
alert("Erro ao carregar dados para impressão");
|
||||||
|
}
|
||||||
|
}
|
||||||
async function confirmDelete() {
|
async function confirmDelete() {
|
||||||
if (!toDelete) return;
|
if (!toDelete) return;
|
||||||
try {
|
try {
|
||||||
@@ -213,8 +227,11 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"><path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"><path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"/></svg>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-content menu bg-base-100 rounded-box z-10 w-52 p-2 shadow-lg border border-base-300">
|
<ul class="dropdown-content menu bg-base-100 rounded-box z-10 w-52 p-2 shadow-lg border border-base-300">
|
||||||
|
<li><a href={`/recursos-humanos/funcionarios/${f._id}`}>Ver Detalhes</a></li>
|
||||||
<li><a href={`/recursos-humanos/funcionarios/${f._id}/editar`}>Editar</a></li>
|
<li><a href={`/recursos-humanos/funcionarios/${f._id}/editar`}>Editar</a></li>
|
||||||
<li><button class="text-error" onclick={() => openDeleteModal(f._id, f.nome)}>Excluir</button></li>
|
<li><a href={`/recursos-humanos/funcionarios/${f._id}/documentos`}>Ver Documentos</a></li>
|
||||||
|
<li><button onclick={() => openPrintModal(f._id)}>Imprimir Ficha</button></li>
|
||||||
|
<li class="border-t mt-1 pt-1"><button class="text-error" onclick={() => openDeleteModal(f._id, f.nome)}>Excluir</button></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -261,5 +278,12 @@
|
|||||||
<button>close</button>
|
<button>close</button>
|
||||||
</form>
|
</form>
|
||||||
</dialog>
|
</dialog>
|
||||||
</main>
|
|
||||||
|
|
||||||
|
<!-- Modal de Impressão -->
|
||||||
|
{#if funcionarioParaImprimir}
|
||||||
|
<PrintModal
|
||||||
|
funcionario={funcionarioParaImprimir}
|
||||||
|
onClose={() => funcionarioParaImprimir = null}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</main>
|
||||||
|
|||||||
@@ -0,0 +1,434 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { useConvexClient } from "convex-svelte";
|
||||||
|
import { api } from "@sgse-app/backend/convex/_generated/api";
|
||||||
|
import { page } from "$app/stores";
|
||||||
|
import { goto } from "$app/navigation";
|
||||||
|
import { maskCPF, maskCEP, maskPhone } from "$lib/utils/masks";
|
||||||
|
import { documentos, getDocumentoDefinicao } from "$lib/utils/documentos";
|
||||||
|
import {
|
||||||
|
SEXO_OPTIONS, ESTADO_CIVIL_OPTIONS, GRAU_INSTRUCAO_OPTIONS,
|
||||||
|
GRUPO_SANGUINEO_OPTIONS, FATOR_RH_OPTIONS, APOSENTADO_OPTIONS
|
||||||
|
} from "$lib/utils/constants";
|
||||||
|
import PrintModal from "$lib/components/PrintModal.svelte";
|
||||||
|
|
||||||
|
const client = useConvexClient();
|
||||||
|
|
||||||
|
let funcionarioId = $derived($page.params.funcionarioId as string);
|
||||||
|
|
||||||
|
let funcionario = $state<any>(null);
|
||||||
|
let simbolo = $state<any>(null);
|
||||||
|
let documentosUrls = $state<Record<string, string | null>>({});
|
||||||
|
let loading = $state(true);
|
||||||
|
let showPrintModal = $state(false);
|
||||||
|
|
||||||
|
async function load() {
|
||||||
|
try {
|
||||||
|
loading = true;
|
||||||
|
const data = await client.query(api.funcionarios.getFichaCompleta, {
|
||||||
|
id: funcionarioId as any
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
goto("/recursos-humanos/funcionarios");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
funcionario = data;
|
||||||
|
simbolo = data.simbolo;
|
||||||
|
|
||||||
|
// Carregar URLs dos documentos
|
||||||
|
try {
|
||||||
|
documentosUrls = await client.query(api.documentos.getDocumentosUrls, {
|
||||||
|
funcionarioId: funcionarioId as any
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Erro ao carregar documentos:", err);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Erro ao carregar funcionário:", err);
|
||||||
|
goto("/recursos-humanos/funcionarios");
|
||||||
|
} finally {
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 downloadDocumento(url: string, nomeDoc: string) {
|
||||||
|
if (!url) return;
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.download = nomeDoc;
|
||||||
|
link.target = '_blank';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
load();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if loading}
|
||||||
|
<div class="flex items-center justify-center min-h-screen">
|
||||||
|
<span class="loading loading-spinner loading-lg"></span>
|
||||||
|
</div>
|
||||||
|
{:else if funcionario}
|
||||||
|
<main class="container mx-auto px-4 py-4 max-w-7xl">
|
||||||
|
<!-- Breadcrumb -->
|
||||||
|
<div class="text-sm breadcrumbs mb-4">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/recursos-humanos" class="text-primary hover:underline">Recursos Humanos</a></li>
|
||||||
|
<li><a href="/recursos-humanos/funcionarios" class="text-primary hover:underline">Funcionários</a></li>
|
||||||
|
<li>Detalhes</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Cabeçalho -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<div class="p-3 bg-blue-500/20 rounded-xl">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 class="text-3xl font-bold text-primary">{funcionario.nome}</h1>
|
||||||
|
<p class="text-base-content/70">Matrícula: {funcionario.matricula}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary gap-2"
|
||||||
|
onclick={() => goto(`/recursos-humanos/funcionarios/${funcionarioId}/editar`)}
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||||
|
</svg>
|
||||||
|
Editar
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-secondary gap-2"
|
||||||
|
onclick={() => goto(`/recursos-humanos/funcionarios/${funcionarioId}/documentos`)}
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
||||||
|
</svg>
|
||||||
|
Ver Documentos
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-accent gap-2" onclick={() => showPrintModal = true}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z" />
|
||||||
|
</svg>
|
||||||
|
Imprimir Ficha
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Grid de Cards -->
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
|
||||||
|
<!-- Coluna 1: Dados Pessoais -->
|
||||||
|
<div class="space-y-6">
|
||||||
|
<!-- Informações Pessoais -->
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-lg border-b pb-2 mb-3">Informações Pessoais</h3>
|
||||||
|
<div class="space-y-2 text-sm">
|
||||||
|
<div><span class="font-semibold">CPF:</span> {maskCPF(funcionario.cpf)}</div>
|
||||||
|
<div><span class="font-semibold">RG:</span> {funcionario.rg}</div>
|
||||||
|
{#if funcionario.rgOrgaoExpedidor}
|
||||||
|
<div><span class="font-semibold">Órgão Expedidor:</span> {funcionario.rgOrgaoExpedidor}</div>
|
||||||
|
{/if}
|
||||||
|
{#if funcionario.rgDataEmissao}
|
||||||
|
<div><span class="font-semibold">Data Emissão RG:</span> {funcionario.rgDataEmissao}</div>
|
||||||
|
{/if}
|
||||||
|
<div><span class="font-semibold">Data Nascimento:</span> {funcionario.nascimento}</div>
|
||||||
|
{#if funcionario.sexo}
|
||||||
|
<div><span class="font-semibold">Sexo:</span> {getLabelFromOptions(funcionario.sexo, SEXO_OPTIONS)}</div>
|
||||||
|
{/if}
|
||||||
|
{#if funcionario.estadoCivil}
|
||||||
|
<div><span class="font-semibold">Estado Civil:</span> {getLabelFromOptions(funcionario.estadoCivil, ESTADO_CIVIL_OPTIONS)}</div>
|
||||||
|
{/if}
|
||||||
|
{#if funcionario.nacionalidade}
|
||||||
|
<div><span class="font-semibold">Nacionalidade:</span> {funcionario.nacionalidade}</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Filiação -->
|
||||||
|
{#if funcionario.nomePai || funcionario.nomeMae}
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-lg border-b pb-2 mb-3">Filiação</h3>
|
||||||
|
<div class="space-y-2 text-sm">
|
||||||
|
{#if funcionario.nomePai}
|
||||||
|
<div><span class="font-semibold">Pai:</span> {funcionario.nomePai}</div>
|
||||||
|
{/if}
|
||||||
|
{#if funcionario.nomeMae}
|
||||||
|
<div><span class="font-semibold">Mãe:</span> {funcionario.nomeMae}</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Naturalidade -->
|
||||||
|
{#if funcionario.naturalidade || funcionario.naturalidadeUF}
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-lg border-b pb-2 mb-3">Naturalidade</h3>
|
||||||
|
<div class="space-y-2 text-sm">
|
||||||
|
{#if funcionario.naturalidade}
|
||||||
|
<div><span class="font-semibold">Cidade:</span> {funcionario.naturalidade}</div>
|
||||||
|
{/if}
|
||||||
|
{#if funcionario.naturalidadeUF}
|
||||||
|
<div><span class="font-semibold">UF:</span> {funcionario.naturalidadeUF}</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Coluna 2: Documentos e Formação -->
|
||||||
|
<div class="space-y-6">
|
||||||
|
<!-- Documentos Pessoais -->
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-lg border-b pb-2 mb-3">Documentos Pessoais</h3>
|
||||||
|
<div class="space-y-2 text-sm">
|
||||||
|
{#if funcionario.carteiraProfissionalNumero}
|
||||||
|
<div><span class="font-semibold">Cart. Profissional:</span> {funcionario.carteiraProfissionalNumero}
|
||||||
|
{#if funcionario.carteiraProfissionalSerie}
|
||||||
|
- Série: {funcionario.carteiraProfissionalSerie}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if funcionario.reservistaNumero}
|
||||||
|
<div><span class="font-semibold">Reservista:</span> {funcionario.reservistaNumero}
|
||||||
|
{#if funcionario.reservistaSerie}
|
||||||
|
- Série: {funcionario.reservistaSerie}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if funcionario.tituloEleitorNumero}
|
||||||
|
<div><span class="font-semibold">Título Eleitor:</span> {funcionario.tituloEleitorNumero}</div>
|
||||||
|
{#if funcionario.tituloEleitorZona || funcionario.tituloEleitorSecao}
|
||||||
|
<div class="ml-4 text-xs">
|
||||||
|
{#if funcionario.tituloEleitorZona}Zona: {funcionario.tituloEleitorZona}{/if}
|
||||||
|
{#if funcionario.tituloEleitorSecao} - Seção: {funcionario.tituloEleitorSecao}{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
{#if funcionario.pisNumero}
|
||||||
|
<div><span class="font-semibold">PIS/PASEP:</span> {funcionario.pisNumero}</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Formação -->
|
||||||
|
{#if funcionario.grauInstrucao || funcionario.formacao}
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-lg border-b pb-2 mb-3">Formação</h3>
|
||||||
|
<div class="space-y-2 text-sm">
|
||||||
|
{#if funcionario.grauInstrucao}
|
||||||
|
<div><span class="font-semibold">Grau Instrução:</span> {getLabelFromOptions(funcionario.grauInstrucao, GRAU_INSTRUCAO_OPTIONS)}</div>
|
||||||
|
{/if}
|
||||||
|
{#if funcionario.formacao}
|
||||||
|
<div><span class="font-semibold">Formação:</span> {funcionario.formacao}</div>
|
||||||
|
{/if}
|
||||||
|
{#if funcionario.formacaoRegistro}
|
||||||
|
<div><span class="font-semibold">Registro Nº:</span> {funcionario.formacaoRegistro}</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Saúde -->
|
||||||
|
{#if funcionario.grupoSanguineo || funcionario.fatorRH}
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-lg border-b pb-2 mb-3">Saúde</h3>
|
||||||
|
<div class="space-y-2 text-sm">
|
||||||
|
{#if funcionario.grupoSanguineo}
|
||||||
|
<div><span class="font-semibold">Grupo Sanguíneo:</span> {funcionario.grupoSanguineo}</div>
|
||||||
|
{/if}
|
||||||
|
{#if funcionario.fatorRH}
|
||||||
|
<div><span class="font-semibold">Fator RH:</span> {getLabelFromOptions(funcionario.fatorRH, FATOR_RH_OPTIONS)}</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Contato -->
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-lg border-b pb-2 mb-3">Contato</h3>
|
||||||
|
<div class="space-y-2 text-sm">
|
||||||
|
<div><span class="font-semibold">E-mail:</span> {funcionario.email}</div>
|
||||||
|
<div><span class="font-semibold">Telefone:</span> {maskPhone(funcionario.telefone)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Coluna 3: Cargo e Bancário -->
|
||||||
|
<div class="space-y-6">
|
||||||
|
<!-- Cargo e Vínculo -->
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-lg border-b pb-2 mb-3">Cargo e Vínculo</h3>
|
||||||
|
<div class="space-y-2 text-sm">
|
||||||
|
<div><span class="font-semibold">Tipo:</span> {funcionario.simboloTipo === 'cargo_comissionado' ? 'Cargo Comissionado' : 'Função Gratificada'}</div>
|
||||||
|
{#if simbolo}
|
||||||
|
<div><span class="font-semibold">Símbolo:</span> {simbolo.nome}</div>
|
||||||
|
<div class="text-xs text-base-content/70">{simbolo.descricao}</div>
|
||||||
|
{/if}
|
||||||
|
{#if funcionario.descricaoCargo}
|
||||||
|
<div class="mt-2"><span class="font-semibold">Descrição:</span> {funcionario.descricaoCargo}</div>
|
||||||
|
{/if}
|
||||||
|
{#if funcionario.admissaoData}
|
||||||
|
<div class="mt-2"><span class="font-semibold">Data Admissão:</span> {funcionario.admissaoData}</div>
|
||||||
|
{/if}
|
||||||
|
{#if funcionario.nomeacaoPortaria}
|
||||||
|
<div><span class="font-semibold">Portaria:</span> {funcionario.nomeacaoPortaria}</div>
|
||||||
|
{/if}
|
||||||
|
{#if funcionario.nomeacaoData}
|
||||||
|
<div><span class="font-semibold">Data Nomeação:</span> {funcionario.nomeacaoData}</div>
|
||||||
|
{/if}
|
||||||
|
{#if funcionario.nomeacaoDOE}
|
||||||
|
<div><span class="font-semibold">DOE:</span> {funcionario.nomeacaoDOE}</div>
|
||||||
|
{/if}
|
||||||
|
{#if funcionario.pertenceOrgaoPublico}
|
||||||
|
<div class="mt-2"><span class="font-semibold">Pertence Órgão Público:</span> Sim</div>
|
||||||
|
{#if funcionario.orgaoOrigem}
|
||||||
|
<div><span class="font-semibold">Órgão Origem:</span> {funcionario.orgaoOrigem}</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
{#if funcionario.aposentado && funcionario.aposentado !== 'nao'}
|
||||||
|
<div><span class="font-semibold">Aposentado:</span> {getLabelFromOptions(funcionario.aposentado, APOSENTADO_OPTIONS)}</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Endereço -->
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-lg border-b pb-2 mb-3">Endereço</h3>
|
||||||
|
<div class="space-y-2 text-sm">
|
||||||
|
<div>{funcionario.endereco}</div>
|
||||||
|
<div>{funcionario.cidade} - {funcionario.uf}</div>
|
||||||
|
<div><span class="font-semibold">CEP:</span> {maskCEP(funcionario.cep)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Dados Bancários -->
|
||||||
|
{#if funcionario.contaBradescoNumero}
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-lg border-b pb-2 mb-3">Dados Bancários - Bradesco</h3>
|
||||||
|
<div class="space-y-2 text-sm">
|
||||||
|
<div><span class="font-semibold">Conta:</span> {funcionario.contaBradescoNumero}
|
||||||
|
{#if funcionario.contaBradescoDV}-{funcionario.contaBradescoDV}{/if}
|
||||||
|
</div>
|
||||||
|
{#if funcionario.contaBradescoAgencia}
|
||||||
|
<div><span class="font-semibold">Agência:</span> {funcionario.contaBradescoAgencia}</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Documentos Anexados -->
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<div class="card-body">
|
||||||
|
<h3 class="card-title text-xl border-b pb-3 mb-4">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||||
|
</svg>
|
||||||
|
Documentos Anexados
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
|
||||||
|
{#each documentos as doc}
|
||||||
|
{@const temDocumento = documentosUrls[doc.campo]}
|
||||||
|
<div
|
||||||
|
class="card bg-base-200 shadow-sm border-2"
|
||||||
|
class:border-success={temDocumento}
|
||||||
|
class:border-base-300={!temDocumento}
|
||||||
|
>
|
||||||
|
<div class="card-body p-3">
|
||||||
|
<div class="flex items-start gap-2">
|
||||||
|
<div
|
||||||
|
class={`w-8 h-8 rounded flex items-center justify-center flex-shrink-0 ${temDocumento ? 'bg-success/20' : 'bg-base-300'}`}
|
||||||
|
>
|
||||||
|
{#if temDocumento}
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-success" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||||
|
</svg>
|
||||||
|
{:else}
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-base-content/40" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<p class="text-xs font-medium line-clamp-2">{doc.nome}</p>
|
||||||
|
<p class="text-xs text-base-content/60 mt-1">
|
||||||
|
{temDocumento ? 'Enviado' : 'Pendente'}
|
||||||
|
</p>
|
||||||
|
{#if temDocumento}
|
||||||
|
<button
|
||||||
|
class="btn btn-xs btn-ghost mt-2 gap-1"
|
||||||
|
onclick={() => downloadDocumento(documentosUrls[doc.campo] || '', doc.nome)}
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
|
||||||
|
</svg>
|
||||||
|
Baixar
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 text-center">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary btn-sm gap-2"
|
||||||
|
onclick={() => goto(`/recursos-humanos/funcionarios/${funcionarioId}/documentos`)}
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
||||||
|
</svg>
|
||||||
|
Gerenciar Documentos
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!-- Modal de Impressão -->
|
||||||
|
{#if showPrintModal}
|
||||||
|
<PrintModal
|
||||||
|
funcionario={funcionario}
|
||||||
|
onClose={() => showPrintModal = false}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|||||||
@@ -0,0 +1,277 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { useConvexClient } from "convex-svelte";
|
||||||
|
import { api } from "@sgse-app/backend/convex/_generated/api";
|
||||||
|
import { page } from "$app/stores";
|
||||||
|
import { goto } from "$app/navigation";
|
||||||
|
import FileUpload from "$lib/components/FileUpload.svelte";
|
||||||
|
import ModelosDeclaracoes from "$lib/components/ModelosDeclaracoes.svelte";
|
||||||
|
import { documentos, categoriasDocumentos, getDocumentosByCategoria } from "$lib/utils/documentos";
|
||||||
|
|
||||||
|
const client = useConvexClient();
|
||||||
|
|
||||||
|
let funcionarioId = $derived($page.params.funcionarioId as string);
|
||||||
|
|
||||||
|
let funcionario = $state<any>(null);
|
||||||
|
let documentosStorage = $state<Record<string, string | undefined>>({});
|
||||||
|
let loading = $state(true);
|
||||||
|
let filtro = $state<string>("todos"); // todos, enviados, pendentes
|
||||||
|
|
||||||
|
async function load() {
|
||||||
|
try {
|
||||||
|
loading = true;
|
||||||
|
|
||||||
|
// Carregar dados do funcionário
|
||||||
|
const data = await client.query(api.funcionarios.getById, {
|
||||||
|
id: funcionarioId as any
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
goto("/recursos-humanos/funcionarios");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
funcionario = data;
|
||||||
|
|
||||||
|
// Mapear storage IDs dos documentos
|
||||||
|
documentos.forEach(doc => {
|
||||||
|
if ((data as any)[doc.campo]) {
|
||||||
|
documentosStorage[doc.campo] = (data as any)[doc.campo];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Erro ao carregar:", err);
|
||||||
|
goto("/recursos-humanos/funcionarios");
|
||||||
|
} finally {
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDocumentoUpload(campo: string, file: File) {
|
||||||
|
try {
|
||||||
|
// Gerar URL de upload
|
||||||
|
const uploadUrl = await client.mutation(api.documentos.generateUploadUrl, {});
|
||||||
|
|
||||||
|
// Fazer upload do arquivo
|
||||||
|
const result = await fetch(uploadUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": file.type },
|
||||||
|
body: file,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { storageId } = await result.json();
|
||||||
|
|
||||||
|
// Atualizar documento no funcionário
|
||||||
|
await client.mutation(api.documentos.updateDocumento, {
|
||||||
|
funcionarioId: funcionarioId as any,
|
||||||
|
campo,
|
||||||
|
storageId: storageId as any,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Atualizar localmente
|
||||||
|
documentosStorage[campo] = storageId;
|
||||||
|
|
||||||
|
// Recarregar
|
||||||
|
await load();
|
||||||
|
} catch (err: any) {
|
||||||
|
throw new Error(err?.message || "Erro ao fazer upload");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDocumentoRemove(campo: string) {
|
||||||
|
try {
|
||||||
|
// Atualizar documento no funcionário (set to null)
|
||||||
|
await client.mutation(api.documentos.updateDocumento, {
|
||||||
|
funcionarioId: funcionarioId as any,
|
||||||
|
campo,
|
||||||
|
storageId: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Atualizar localmente
|
||||||
|
documentosStorage[campo] = undefined;
|
||||||
|
|
||||||
|
// Recarregar
|
||||||
|
await load();
|
||||||
|
} catch (err: any) {
|
||||||
|
alert("Erro ao remover documento: " + (err?.message || ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function documentosFiltrados() {
|
||||||
|
return documentos.filter(doc => {
|
||||||
|
const temDocumento = !!documentosStorage[doc.campo];
|
||||||
|
if (filtro === "enviados") return temDocumento;
|
||||||
|
if (filtro === "pendentes") return !temDocumento;
|
||||||
|
return true; // todos
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function contarDocumentos() {
|
||||||
|
const total = documentos.length;
|
||||||
|
const enviados = documentos.filter(doc => !!documentosStorage[doc.campo]).length;
|
||||||
|
const pendentes = total - enviados;
|
||||||
|
return { total, enviados, pendentes };
|
||||||
|
}
|
||||||
|
|
||||||
|
load();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if loading}
|
||||||
|
<div class="flex items-center justify-center min-h-screen">
|
||||||
|
<span class="loading loading-spinner loading-lg"></span>
|
||||||
|
</div>
|
||||||
|
{:else if funcionario}
|
||||||
|
<main class="container mx-auto px-4 py-4 max-w-7xl">
|
||||||
|
<!-- Breadcrumb -->
|
||||||
|
<div class="text-sm breadcrumbs mb-4">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/recursos-humanos" class="text-primary hover:underline">Recursos Humanos</a></li>
|
||||||
|
<li><a href="/recursos-humanos/funcionarios" class="text-primary hover:underline">Funcionários</a></li>
|
||||||
|
<li><a href={`/recursos-humanos/funcionarios/${funcionarioId}`} class="text-primary hover:underline">{funcionario.nome}</a></li>
|
||||||
|
<li>Documentos</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Cabeçalho -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4">
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<div class="p-3 bg-purple-500/20 rounded-xl">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-purple-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 class="text-3xl font-bold text-primary">Gerenciar Documentos</h1>
|
||||||
|
<p class="text-base-content/70">{funcionario.nome} - Matrícula: {funcionario.matricula}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="btn btn-ghost gap-2"
|
||||||
|
onclick={() => goto(`/recursos-humanos/funcionarios/${funcionarioId}`)}
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
||||||
|
</svg>
|
||||||
|
Voltar aos Detalhes
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Estatísticas -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
|
||||||
|
<div class="stats shadow">
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-figure text-primary">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="stat-title">Total de Documentos</div>
|
||||||
|
<div class="stat-value text-primary">{contarDocumentos().total}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stats shadow">
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-figure text-success">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="stat-title">Documentos Enviados</div>
|
||||||
|
<div class="stat-value text-success">{contarDocumentos().enviados}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="stats shadow">
|
||||||
|
<div class="stat">
|
||||||
|
<div class="stat-figure text-warning">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="stat-title">Documentos Pendentes</div>
|
||||||
|
<div class="stat-value text-warning">{contarDocumentos().pendentes}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modelos de Declarações -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<ModelosDeclaracoes funcionario={funcionario} showPreencherButton={true} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Filtros -->
|
||||||
|
<div class="card bg-base-100 shadow-xl mb-6">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<button
|
||||||
|
class="btn btn-sm"
|
||||||
|
class:btn-primary={filtro === "todos"}
|
||||||
|
onclick={() => filtro = "todos"}
|
||||||
|
>
|
||||||
|
Todos ({contarDocumentos().total})
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-sm"
|
||||||
|
class:btn-success={filtro === "enviados"}
|
||||||
|
onclick={() => filtro = "enviados"}
|
||||||
|
>
|
||||||
|
Enviados ({contarDocumentos().enviados})
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-sm"
|
||||||
|
class:btn-warning={filtro === "pendentes"}
|
||||||
|
onclick={() => filtro = "pendentes"}
|
||||||
|
>
|
||||||
|
Pendentes ({contarDocumentos().pendentes})
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Documentos por Categoria -->
|
||||||
|
{#each categoriasDocumentos as categoria}
|
||||||
|
{@const docsCategoria = getDocumentosByCategoria(categoria).filter(doc => {
|
||||||
|
const temDocumento = !!documentosStorage[doc.campo];
|
||||||
|
if (filtro === "enviados") return temDocumento;
|
||||||
|
if (filtro === "pendentes") return !temDocumento;
|
||||||
|
return true;
|
||||||
|
})}
|
||||||
|
|
||||||
|
{#if docsCategoria.length > 0}
|
||||||
|
<div class="card bg-base-100 shadow-xl mb-6">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title text-xl border-b pb-3 mb-4">
|
||||||
|
{categoria}
|
||||||
|
<div class="badge badge-primary ml-2">{docsCategoria.length}</div>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
{#each docsCategoria as doc}
|
||||||
|
<FileUpload
|
||||||
|
label={doc.nome}
|
||||||
|
helpUrl={doc.helpUrl}
|
||||||
|
value={documentosStorage[doc.campo]}
|
||||||
|
onUpload={(file) => handleDocumentoUpload(doc.campo, file)}
|
||||||
|
onRemove={() => handleDocumentoRemove(doc.campo)}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
{#if documentosFiltrados().length === 0}
|
||||||
|
<div class="alert">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<span>Nenhum documento encontrado com o filtro selecionado.</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</main>
|
||||||
|
{/if}
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
BIN
apps/web/static/modelos/declaracoes/Declaração de Idoneidade.pdf
Normal file
BIN
apps/web/static/modelos/declaracoes/Declaração de Idoneidade.pdf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
400
fix-editar.js
Normal file
400
fix-editar.js
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const baseDir = 'apps/web/src/routes/(dashboard)/recursos-humanos/funcionarios';
|
||||||
|
const cadastroPath = path.join(baseDir, 'cadastro/+page.svelte');
|
||||||
|
const editarPath = path.join(baseDir, '[funcionarioId]/editar/+page.svelte');
|
||||||
|
|
||||||
|
console.log('Reading files...');
|
||||||
|
const cadastro = fs.readFileSync(cadastroPath, 'utf8');
|
||||||
|
|
||||||
|
// Create the edit file from scratch
|
||||||
|
const editContent = `<script lang="ts">
|
||||||
|
import { useConvexClient } from "convex-svelte";
|
||||||
|
import { api } from "@sgse-app/backend/convex/_generated/api";
|
||||||
|
import { goto } from "$app/navigation";
|
||||||
|
import { page } from "$app/stores";
|
||||||
|
import type { SimboloTipo } from "@sgse-app/backend/convex/schema";
|
||||||
|
import FileUpload from "$lib/components/FileUpload.svelte";
|
||||||
|
import {
|
||||||
|
maskCPF, maskCEP, maskPhone, maskDate, onlyDigits,
|
||||||
|
validateCPF, validateDate
|
||||||
|
} from "$lib/utils/masks";
|
||||||
|
import {
|
||||||
|
SEXO_OPTIONS, ESTADO_CIVIL_OPTIONS, GRAU_INSTRUCAO_OPTIONS,
|
||||||
|
GRUPO_SANGUINEO_OPTIONS, FATOR_RH_OPTIONS, APOSENTADO_OPTIONS, UFS_BRASIL
|
||||||
|
} from "$lib/utils/constants";
|
||||||
|
import { documentos, categoriasDocumentos, getDocumentosByCategoria } from "$lib/utils/documentos";
|
||||||
|
import ModelosDeclaracoes from "$lib/components/ModelosDeclaracoes.svelte";
|
||||||
|
|
||||||
|
const client = useConvexClient();
|
||||||
|
|
||||||
|
let funcionarioId = $derived($page.params.funcionarioId as string);
|
||||||
|
|
||||||
|
let simbolos: Array<{
|
||||||
|
_id: string;
|
||||||
|
nome: string;
|
||||||
|
tipo: SimboloTipo;
|
||||||
|
descricao: string;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
let tipo: SimboloTipo = "cargo_comissionado";
|
||||||
|
let loading = $state(false);
|
||||||
|
let loadingData = $state(true);
|
||||||
|
let notice = $state<{ kind: "success" | "error"; text: string } | null>(null);
|
||||||
|
|
||||||
|
// Campos obrigatórios
|
||||||
|
let nome = $state("");
|
||||||
|
let matricula = $state("");
|
||||||
|
let cpf = $state("");
|
||||||
|
let rg = $state("");
|
||||||
|
let nascimento = $state("");
|
||||||
|
let email = $state("");
|
||||||
|
let telefone = $state("");
|
||||||
|
let endereco = $state("");
|
||||||
|
let cep = $state("");
|
||||||
|
let cidade = $state("");
|
||||||
|
let uf = $state("");
|
||||||
|
let simboloId = $state("");
|
||||||
|
let admissaoData = $state("");
|
||||||
|
|
||||||
|
// Dados Pessoais Adicionais
|
||||||
|
let nomePai = $state("");
|
||||||
|
let nomeMae = $state("");
|
||||||
|
let naturalidade = $state("");
|
||||||
|
let naturalidadeUF = $state("");
|
||||||
|
let sexo = $state("");
|
||||||
|
let estadoCivil = $state("");
|
||||||
|
let nacionalidade = $state("Brasileira");
|
||||||
|
|
||||||
|
// Documentos Pessoais
|
||||||
|
let rgOrgaoExpedidor = $state("");
|
||||||
|
let rgDataEmissao = $state("");
|
||||||
|
let carteiraProfissionalNumero = $state("");
|
||||||
|
let carteiraProfissionalSerie = $state("");
|
||||||
|
let carteiraProfissionalDataEmissao = $state("");
|
||||||
|
let reservistaNumero = $state("");
|
||||||
|
let reservistaSerie = $state("");
|
||||||
|
let tituloEleitorNumero = $state("");
|
||||||
|
let tituloEleitorZona = $state("");
|
||||||
|
let tituloEleitorSecao = $state("");
|
||||||
|
let pisNumero = $state("");
|
||||||
|
|
||||||
|
// Formação e Saúde
|
||||||
|
let grauInstrucao = $state("");
|
||||||
|
let formacao = $state("");
|
||||||
|
let formacaoRegistro = $state("");
|
||||||
|
let grupoSanguineo = $state("");
|
||||||
|
let fatorRH = $state("");
|
||||||
|
|
||||||
|
// Cargo e Vínculo
|
||||||
|
let descricaoCargo = $state("");
|
||||||
|
let nomeacaoPortaria = $state("");
|
||||||
|
let nomeacaoData = $state("");
|
||||||
|
let nomeacaoDOE = $state("");
|
||||||
|
let pertenceOrgaoPublico = $state(false);
|
||||||
|
let orgaoOrigem = $state("");
|
||||||
|
let aposentado = $state("nao");
|
||||||
|
|
||||||
|
// Dados Bancários
|
||||||
|
let contaBradescoNumero = $state("");
|
||||||
|
let contaBradescoDV = $state("");
|
||||||
|
let contaBradescoAgencia = $state("");
|
||||||
|
|
||||||
|
// Documentos (Storage IDs)
|
||||||
|
let documentosStorage: Record<string, string | undefined> = $state({});
|
||||||
|
|
||||||
|
async function loadSimbolos() {
|
||||||
|
const list = await client.query(api.simbolos.getAll, {} as any);
|
||||||
|
simbolos = list.map((s: any) => ({
|
||||||
|
_id: s._id,
|
||||||
|
nome: s.nome,
|
||||||
|
tipo: s.tipo,
|
||||||
|
descricao: s.descricao
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadFuncionario() {
|
||||||
|
try {
|
||||||
|
const func = await client.query(api.funcionarios.getById, { id: funcionarioId as any });
|
||||||
|
|
||||||
|
// Preencher campos
|
||||||
|
nome = func.nome;
|
||||||
|
matricula = func.matricula;
|
||||||
|
cpf = maskCPF(func.cpf);
|
||||||
|
rg = func.rg;
|
||||||
|
nascimento = func.nascimento;
|
||||||
|
email = func.email;
|
||||||
|
telefone = maskPhone(func.telefone);
|
||||||
|
endereco = func.endereco;
|
||||||
|
cep = maskCEP(func.cep);
|
||||||
|
cidade = func.cidade;
|
||||||
|
uf = func.uf;
|
||||||
|
simboloId = func.simboloId;
|
||||||
|
tipo = func.simboloTipo;
|
||||||
|
admissaoData = func.admissaoData || "";
|
||||||
|
|
||||||
|
// Dados adicionais
|
||||||
|
nomePai = func.nomePai || "";
|
||||||
|
nomeMae = func.nomeMae || "";
|
||||||
|
naturalidade = func.naturalidade || "";
|
||||||
|
naturalidadeUF = func.naturalidadeUF || "";
|
||||||
|
sexo = func.sexo || "";
|
||||||
|
estadoCivil = func.estadoCivil || "";
|
||||||
|
nacionalidade = func.nacionalidade || "Brasileira";
|
||||||
|
|
||||||
|
rgOrgaoExpedidor = func.rgOrgaoExpedidor || "";
|
||||||
|
rgDataEmissao = func.rgDataEmissao || "";
|
||||||
|
carteiraProfissionalNumero = func.carteiraProfissionalNumero || "";
|
||||||
|
carteiraProfissionalSerie = func.carteiraProfissionalSerie || "";
|
||||||
|
carteiraProfissionalDataEmissao = func.carteiraProfissionalDataEmissao || "";
|
||||||
|
reservistaNumero = func.reservistaNumero || "";
|
||||||
|
reservistaSerie = func.reservistaSerie || "";
|
||||||
|
tituloEleitorNumero = func.tituloEleitorNumero || "";
|
||||||
|
tituloEleitorZona = func.tituloEleitorZona || "";
|
||||||
|
tituloEleitorSecao = func.tituloEleitorSecao || "";
|
||||||
|
pisNumero = func.pisNumero || "";
|
||||||
|
|
||||||
|
grauInstrucao = func.grauInstrucao || "";
|
||||||
|
formacao = func.formacao || "";
|
||||||
|
formacaoRegistro = func.formacaoRegistro || "";
|
||||||
|
grupoSanguineo = func.grupoSanguineo || "";
|
||||||
|
fatorRH = func.fatorRH || "";
|
||||||
|
|
||||||
|
descricaoCargo = func.descricaoCargo || "";
|
||||||
|
nomeacaoPortaria = func.nomeacaoPortaria || "";
|
||||||
|
nomeacaoData = func.nomeacaoData || "";
|
||||||
|
nomeacaoDOE = func.nomeacaoDOE || "";
|
||||||
|
pertenceOrgaoPublico = func.pertenceOrgaoPublico || false;
|
||||||
|
orgaoOrigem = func.orgaoOrigem || "";
|
||||||
|
aposentado = func.aposentado || "nao";
|
||||||
|
|
||||||
|
contaBradescoNumero = func.contaBradescoNumero || "";
|
||||||
|
contaBradescoDV = func.contaBradescoDV || "";
|
||||||
|
contaBradescoAgencia = func.contaBradescoAgencia || "";
|
||||||
|
|
||||||
|
// Documentos
|
||||||
|
documentosStorage = {};
|
||||||
|
documentos.forEach(doc => {
|
||||||
|
const storageId = (func as any)[doc.campo];
|
||||||
|
if (storageId) {
|
||||||
|
documentosStorage[doc.campo] = storageId;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erro ao carregar funcionário:", error);
|
||||||
|
notice = { kind: "error", text: "Erro ao carregar dados do funcionário" };
|
||||||
|
} finally {
|
||||||
|
loadingData = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fillFromCEP(cepValue: string) {
|
||||||
|
const cepDigits = onlyDigits(cepValue);
|
||||||
|
if (cepDigits.length !== 8) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(\`https://viacep.com.br/ws/\${cepDigits}/json/\`);
|
||||||
|
const data = await res.json();
|
||||||
|
if (!data || data.erro) return;
|
||||||
|
|
||||||
|
const enderecoFull = [data.logradouro, data.bairro].filter(Boolean).join(", ");
|
||||||
|
endereco = enderecoFull;
|
||||||
|
cidade = data.localidade || "";
|
||||||
|
uf = data.uf || "";
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDocumentoUpload(campo: string, file: File) {
|
||||||
|
try {
|
||||||
|
const uploadUrl = await client.mutation(api.documentos.generateUploadUrl, {});
|
||||||
|
|
||||||
|
const result = await fetch(uploadUrl, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": file.type },
|
||||||
|
body: file,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { storageId } = await result.json();
|
||||||
|
documentosStorage[campo] = storageId;
|
||||||
|
} catch (err: any) {
|
||||||
|
throw new Error(err?.message || "Erro ao fazer upload");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDocumentoRemove(campo: string) {
|
||||||
|
documentosStorage[campo] = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
if (!nome || !matricula || !cpf || !rg || !nascimento || !email || !telefone) {
|
||||||
|
notice = { kind: "error", text: "Preencha todos os campos obrigatórios" };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validateCPF(cpf)) {
|
||||||
|
notice = { kind: "error", text: "CPF inválido" };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validateDate(nascimento)) {
|
||||||
|
notice = { kind: "error", text: "Data de nascimento inválida" };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!simboloId) {
|
||||||
|
notice = { kind: "error", text: "Selecione um símbolo" };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
loading = true;
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
nome,
|
||||||
|
matricula,
|
||||||
|
cpf: onlyDigits(cpf),
|
||||||
|
rg: onlyDigits(rg),
|
||||||
|
nascimento,
|
||||||
|
email,
|
||||||
|
telefone: onlyDigits(telefone),
|
||||||
|
endereco,
|
||||||
|
cep: onlyDigits(cep),
|
||||||
|
cidade,
|
||||||
|
uf: uf.toUpperCase(),
|
||||||
|
simboloId: simboloId as any,
|
||||||
|
simboloTipo: tipo,
|
||||||
|
admissaoData: admissaoData || undefined,
|
||||||
|
|
||||||
|
nomePai: nomePai || undefined,
|
||||||
|
nomeMae: nomeMae || undefined,
|
||||||
|
naturalidade: naturalidade || undefined,
|
||||||
|
naturalidadeUF: naturalidadeUF ? naturalidadeUF.toUpperCase() : undefined,
|
||||||
|
sexo: sexo || undefined,
|
||||||
|
estadoCivil: estadoCivil || undefined,
|
||||||
|
nacionalidade: nacionalidade || undefined,
|
||||||
|
|
||||||
|
rgOrgaoExpedidor: rgOrgaoExpedidor || undefined,
|
||||||
|
rgDataEmissao: rgDataEmissao || undefined,
|
||||||
|
carteiraProfissionalNumero: carteiraProfissionalNumero || undefined,
|
||||||
|
carteiraProfissionalSerie: carteiraProfissionalSerie || undefined,
|
||||||
|
carteiraProfissionalDataEmissao: carteiraProfissionalDataEmissao || undefined,
|
||||||
|
reservistaNumero: reservistaNumero || undefined,
|
||||||
|
reservistaSerie: reservistaSerie || undefined,
|
||||||
|
tituloEleitorNumero: tituloEleitorNumero || undefined,
|
||||||
|
tituloEleitorZona: tituloEleitorZona || undefined,
|
||||||
|
tituloEleitorSecao: tituloEleitorSecao || undefined,
|
||||||
|
pisNumero: pisNumero || undefined,
|
||||||
|
|
||||||
|
grauInstrucao: grauInstrucao || undefined,
|
||||||
|
formacao: formacao || undefined,
|
||||||
|
formacaoRegistro: formacaoRegistro || undefined,
|
||||||
|
grupoSanguineo: grupoSanguineo || undefined,
|
||||||
|
fatorRH: fatorRH || undefined,
|
||||||
|
|
||||||
|
descricaoCargo: descricaoCargo || undefined,
|
||||||
|
nomeacaoPortaria: nomeacaoPortaria || undefined,
|
||||||
|
nomeacaoData: nomeacaoData || undefined,
|
||||||
|
nomeacaoDOE: nomeacaoDOE || undefined,
|
||||||
|
pertenceOrgaoPublico: pertenceOrgaoPublico || undefined,
|
||||||
|
orgaoOrigem: orgaoOrigem || undefined,
|
||||||
|
aposentado: aposentado || undefined,
|
||||||
|
|
||||||
|
contaBradescoNumero: contaBradescoNumero || undefined,
|
||||||
|
contaBradescoDV: contaBradescoDV || undefined,
|
||||||
|
contaBradescoAgencia: contaBradescoAgencia || undefined,
|
||||||
|
|
||||||
|
...Object.fromEntries(
|
||||||
|
Object.entries(documentosStorage).map(([key, value]) => [key, value as any])
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
await client.mutation(api.funcionarios.update, { id: funcionarioId as any, ...payload as any });
|
||||||
|
notice = { kind: "success", text: "Funcionário atualizado com sucesso!" };
|
||||||
|
setTimeout(() => goto("/recursos-humanos/funcionarios"), 600);
|
||||||
|
} catch (e: any) {
|
||||||
|
notice = { kind: "error", text: "Erro ao atualizar funcionário." };
|
||||||
|
} finally {
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
await loadSimbolos();
|
||||||
|
await loadFuncionario();
|
||||||
|
}
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (funcionarioId) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if loadingData}
|
||||||
|
<div class="flex items-center justify-center min-h-screen">
|
||||||
|
<span class="loading loading-spinner loading-lg"></span>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<main class="container mx-auto px-4 py-4 max-w-7xl">
|
||||||
|
<!-- Breadcrumb -->
|
||||||
|
<div class="text-sm breadcrumbs mb-4">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/recursos-humanos" class="text-primary hover:underline">Recursos Humanos</a></li>
|
||||||
|
<li><a href="/recursos-humanos/funcionarios" class="text-primary hover:underline">Funcionários</a></li>
|
||||||
|
<li>Editar</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Cabeçalho -->
|
||||||
|
<div class="mb-6">
|
||||||
|
<div class="flex items-center gap-4 mb-2">
|
||||||
|
<div class="p-3 bg-yellow-500/20 rounded-xl">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-yellow-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 class="text-3xl font-bold text-primary">Editar Funcionário</h1>
|
||||||
|
<p class="text-base-content/70">Atualize as informações do funcionário</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Alertas -->
|
||||||
|
{#if notice}
|
||||||
|
<div class="alert mb-6 shadow-lg" class:alert-success={notice.kind === "success"} class:alert-error={notice.kind === "error"}>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
|
||||||
|
{#if notice.kind === "success"}
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
{:else}
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
{/if}
|
||||||
|
</svg>
|
||||||
|
<span>{notice.text}</span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Formulário de Edição -->
|
||||||
|
<form class="space-y-6" onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Extract form from cadastro (from line 294 to 1181)
|
||||||
|
const cadastroLines = cadastro.split('\n');
|
||||||
|
const formLines = cadastroLines.slice(293, 1181); // Get lines 294-1181 (0-indexed)
|
||||||
|
const formContent = formLines.join('\n');
|
||||||
|
|
||||||
|
// Replace "Cadastrar" with "Atualizar" in button
|
||||||
|
const fixedForm = formContent
|
||||||
|
.replace('Cadastrar Funcionário', 'Atualizar Funcionário')
|
||||||
|
.replace('Cadastrando...', 'Atualizando...');
|
||||||
|
|
||||||
|
const finalContent = editContent + fixedForm + '\n </form>\n </main>\n{/if}';
|
||||||
|
|
||||||
|
fs.writeFileSync(editarPath, finalContent, 'utf8');
|
||||||
|
|
||||||
|
console.log(`✓ File created successfully!`);
|
||||||
|
console.log(` Total lines: ${finalContent.split('\n').length}`);
|
||||||
|
console.log(` File saved to: ${editarPath}`);
|
||||||
|
|
||||||
258
package-lock.json
generated
258
package-lock.json
generated
@@ -28,6 +28,8 @@
|
|||||||
"better-auth": "1.3.27",
|
"better-auth": "1.3.27",
|
||||||
"convex": "^1.28.0",
|
"convex": "^1.28.0",
|
||||||
"convex-svelte": "^0.0.11",
|
"convex-svelte": "^0.0.11",
|
||||||
|
"jspdf": "^3.0.3",
|
||||||
|
"jspdf-autotable": "^5.0.2",
|
||||||
"zod": "^4.0.17"
|
"zod": "^4.0.17"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -46,6 +48,15 @@
|
|||||||
"vite": "^7.1.2"
|
"vite": "^7.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@babel/runtime": {
|
||||||
|
"version": "7.28.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
|
||||||
|
"integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@better-auth/core": {
|
"node_modules/@better-auth/core": {
|
||||||
"version": "1.3.27",
|
"version": "1.3.27",
|
||||||
"resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.3.27.tgz",
|
"resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.3.27.tgz",
|
||||||
@@ -710,7 +721,6 @@
|
|||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||||
@@ -721,7 +731,6 @@
|
|||||||
"version": "2.3.5",
|
"version": "2.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
||||||
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/gen-mapping": "^0.3.5",
|
"@jridgewell/gen-mapping": "^0.3.5",
|
||||||
@@ -732,7 +741,6 @@
|
|||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
@@ -742,14 +750,12 @@
|
|||||||
"version": "1.5.5",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
"version": "0.3.31",
|
"version": "0.3.31",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "^3.1.0",
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
@@ -1310,14 +1316,13 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
|
||||||
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
|
"integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@sveltejs/acorn-typescript": {
|
"node_modules/@sveltejs/acorn-typescript": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.6.tgz",
|
||||||
"integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==",
|
"integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"acorn": "^8.9.0"
|
"acorn": "^8.9.0"
|
||||||
@@ -1778,7 +1783,6 @@
|
|||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
|
||||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
@@ -1791,11 +1795,30 @@
|
|||||||
"undici-types": "~7.16.0"
|
"undici-types": "~7.16.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/pako": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/raf": {
|
||||||
|
"version": "3.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
|
||||||
|
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/trusted-types": {
|
||||||
|
"version": "2.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||||
|
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/acorn": {
|
"node_modules/acorn": {
|
||||||
"version": "8.15.0",
|
"version": "8.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
@@ -1808,7 +1831,6 @@
|
|||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
|
||||||
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
|
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -1870,12 +1892,21 @@
|
|||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||||
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
|
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/base64-arraybuffer": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/baseline-browser-mapping": {
|
"node_modules/baseline-browser-mapping": {
|
||||||
"version": "2.8.20",
|
"version": "2.8.20",
|
||||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.20.tgz",
|
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.20.tgz",
|
||||||
@@ -2000,6 +2031,26 @@
|
|||||||
],
|
],
|
||||||
"license": "CC-BY-4.0"
|
"license": "CC-BY-4.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/canvg": {
|
||||||
|
"version": "3.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
|
||||||
|
"integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.12.5",
|
||||||
|
"@types/raf": "^3.4.0",
|
||||||
|
"core-js": "^3.8.3",
|
||||||
|
"raf": "^3.4.1",
|
||||||
|
"regenerator-runtime": "^0.13.7",
|
||||||
|
"rgbcolor": "^1.0.1",
|
||||||
|
"stackblur-canvas": "^2.0.0",
|
||||||
|
"svg-pathdata": "^6.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||||
@@ -2020,7 +2071,6 @@
|
|||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
@@ -2562,6 +2612,28 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/core-js": {
|
||||||
|
"version": "3.46.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.46.0.tgz",
|
||||||
|
"integrity": "sha512-vDMm9B0xnqqZ8uSBpZ8sNtRtOdmfShrvT6h2TuQGLs0Is+cR0DYbj/KWP6ALVNbWPpqA/qPLoOuppJN07humpA==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/core-js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/css-line-break": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"utrie": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/daisyui": {
|
"node_modules/daisyui": {
|
||||||
"version": "5.3.10",
|
"version": "5.3.10",
|
||||||
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.3.10.tgz",
|
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.3.10.tgz",
|
||||||
@@ -2623,6 +2695,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/dompurify": {
|
||||||
|
"version": "3.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz",
|
||||||
|
"integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==",
|
||||||
|
"license": "(MPL-2.0 OR Apache-2.0)",
|
||||||
|
"optional": true,
|
||||||
|
"optionalDependencies": {
|
||||||
|
"@types/trusted-types": "^2.0.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.240",
|
"version": "1.5.240",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.240.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.240.tgz",
|
||||||
@@ -2700,19 +2782,28 @@
|
|||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
|
||||||
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
|
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/esrap": {
|
"node_modules/esrap": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.1.tgz",
|
||||||
"integrity": "sha512-ebTT9B6lOtZGMgJ3o5r12wBacHctG7oEWazIda8UlPfA3HD/Wrv8FdXoVo73vzdpwCxNyXjPauyN2bbJzMkB9A==",
|
"integrity": "sha512-ebTT9B6lOtZGMgJ3o5r12wBacHctG7oEWazIda8UlPfA3HD/Wrv8FdXoVo73vzdpwCxNyXjPauyN2bbJzMkB9A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fast-png": {
|
||||||
|
"version": "6.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz",
|
||||||
|
"integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/pako": "^2.0.3",
|
||||||
|
"iobuffer": "^5.3.2",
|
||||||
|
"pako": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fdir": {
|
"node_modules/fdir": {
|
||||||
"version": "6.5.0",
|
"version": "6.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||||
@@ -2731,6 +2822,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/fflate": {
|
||||||
|
"version": "0.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
|
||||||
|
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/fraction.js": {
|
"node_modules/fraction.js": {
|
||||||
"version": "4.3.7",
|
"version": "4.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz",
|
||||||
@@ -2767,6 +2864,26 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/html2canvas": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"css-line-break": "^2.1.0",
|
||||||
|
"text-segmentation": "^1.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/iobuffer": {
|
||||||
|
"version": "5.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz",
|
||||||
|
"integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/is-network-error": {
|
"node_modules/is-network-error": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz",
|
||||||
@@ -2783,7 +2900,6 @@
|
|||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
|
||||||
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
|
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "^1.0.6"
|
"@types/estree": "^1.0.6"
|
||||||
@@ -2808,6 +2924,32 @@
|
|||||||
"url": "https://github.com/sponsors/panva"
|
"url": "https://github.com/sponsors/panva"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jspdf": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-eURjAyz5iX1H8BOYAfzvdPfIKK53V7mCpBTe7Kb16PaM8JSXEcUQNBQaiWMI8wY5RvNOPj4GccMjTlfwRBd+oQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.26.9",
|
||||||
|
"fast-png": "^6.2.0",
|
||||||
|
"fflate": "^0.8.1"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"canvg": "^3.0.11",
|
||||||
|
"core-js": "^3.6.0",
|
||||||
|
"dompurify": "^3.2.4",
|
||||||
|
"html2canvas": "^1.0.0-rc.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jspdf-autotable": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-YNKeB7qmx3pxOLcNeoqAv3qTS7KuvVwkFe5AduCawpop3NOkBUtqDToxNc225MlNecxT4kP2Zy3z/y/yvGdXUQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"jspdf": "^2 || ^3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/kleur": {
|
"node_modules/kleur": {
|
||||||
"version": "4.1.5",
|
"version": "4.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
|
||||||
@@ -3092,7 +3234,6 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
|
||||||
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
|
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/lucide-svelte": {
|
"node_modules/lucide-svelte": {
|
||||||
@@ -3108,7 +3249,6 @@
|
|||||||
"version": "0.30.21",
|
"version": "0.30.21",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||||
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||||
@@ -3192,6 +3332,19 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pako": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
|
||||||
|
"license": "(MIT AND Zlib)"
|
||||||
|
},
|
||||||
|
"node_modules/performance-now": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
@@ -3281,6 +3434,16 @@
|
|||||||
"node": ">=16.0.0"
|
"node": ">=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/raf": {
|
||||||
|
"version": "3.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
|
||||||
|
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"performance-now": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/readdirp": {
|
"node_modules/readdirp": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||||
@@ -3301,6 +3464,13 @@
|
|||||||
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
|
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/regenerator-runtime": {
|
||||||
|
"version": "0.13.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
|
||||||
|
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
"node_modules/remeda": {
|
"node_modules/remeda": {
|
||||||
"version": "2.32.0",
|
"version": "2.32.0",
|
||||||
"resolved": "https://registry.npmjs.org/remeda/-/remeda-2.32.0.tgz",
|
"resolved": "https://registry.npmjs.org/remeda/-/remeda-2.32.0.tgz",
|
||||||
@@ -3310,6 +3480,16 @@
|
|||||||
"type-fest": "^4.41.0"
|
"type-fest": "^4.41.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/rgbcolor": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
|
||||||
|
"license": "MIT OR SEE LICENSE IN FEEL-FREE.md",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.15"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/rollup": {
|
"node_modules/rollup": {
|
||||||
"version": "4.52.5",
|
"version": "4.52.5",
|
||||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz",
|
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz",
|
||||||
@@ -3414,11 +3594,20 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stackblur-canvas": {
|
||||||
|
"version": "2.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
|
||||||
|
"integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.1.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/svelte": {
|
"node_modules/svelte": {
|
||||||
"version": "5.42.2",
|
"version": "5.42.2",
|
||||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.42.2.tgz",
|
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.42.2.tgz",
|
||||||
"integrity": "sha512-iSry5jsBHispVczyt9UrBX/1qu3HQ/UyKPAIjqlvlu3o/eUvc+kpyMyRS2O4HLLx4MvLurLGIUOyyP11pyD59g==",
|
"integrity": "sha512-iSry5jsBHispVczyt9UrBX/1qu3HQ/UyKPAIjqlvlu3o/eUvc+kpyMyRS2O4HLLx4MvLurLGIUOyyP11pyD59g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/remapping": "^2.3.4",
|
"@jridgewell/remapping": "^2.3.4",
|
||||||
@@ -3464,6 +3653,16 @@
|
|||||||
"typescript": ">=5.0.0"
|
"typescript": ">=5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/svg-pathdata": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "4.1.16",
|
"version": "4.1.16",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz",
|
||||||
@@ -3485,6 +3684,16 @@
|
|||||||
"url": "https://opencollective.com/webpack"
|
"url": "https://opencollective.com/webpack"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/text-segmentation": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"utrie": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tinyglobby": {
|
"node_modules/tinyglobby": {
|
||||||
"version": "0.2.15",
|
"version": "0.2.15",
|
||||||
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
|
||||||
@@ -3654,7 +3863,7 @@
|
|||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
@@ -3708,6 +3917,16 @@
|
|||||||
"browserslist": ">= 4.21.0"
|
"browserslist": ">= 4.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/utrie": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"dependencies": {
|
||||||
|
"base64-arraybuffer": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "7.1.12",
|
"version": "7.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz",
|
||||||
@@ -3811,7 +4030,6 @@
|
|||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
|
||||||
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
|
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/zod": {
|
"node_modules/zod": {
|
||||||
|
|||||||
2
packages/backend/convex/_generated/api.d.ts
vendored
2
packages/backend/convex/_generated/api.d.ts
vendored
@@ -16,6 +16,7 @@ import type * as betterAuth__generated_server from "../betterAuth/_generated/ser
|
|||||||
import type * as betterAuth_adapter from "../betterAuth/adapter.js";
|
import type * as betterAuth_adapter from "../betterAuth/adapter.js";
|
||||||
import type * as betterAuth_auth from "../betterAuth/auth.js";
|
import type * as betterAuth_auth from "../betterAuth/auth.js";
|
||||||
import type * as dashboard from "../dashboard.js";
|
import type * as dashboard from "../dashboard.js";
|
||||||
|
import type * as documentos from "../documentos.js";
|
||||||
import type * as funcionarios from "../funcionarios.js";
|
import type * as funcionarios from "../funcionarios.js";
|
||||||
import type * as healthCheck from "../healthCheck.js";
|
import type * as healthCheck from "../healthCheck.js";
|
||||||
import type * as http from "../http.js";
|
import type * as http from "../http.js";
|
||||||
@@ -52,6 +53,7 @@ declare const fullApi: ApiFromModules<{
|
|||||||
"betterAuth/adapter": typeof betterAuth_adapter;
|
"betterAuth/adapter": typeof betterAuth_adapter;
|
||||||
"betterAuth/auth": typeof betterAuth_auth;
|
"betterAuth/auth": typeof betterAuth_auth;
|
||||||
dashboard: typeof dashboard;
|
dashboard: typeof dashboard;
|
||||||
|
documentos: typeof documentos;
|
||||||
funcionarios: typeof funcionarios;
|
funcionarios: typeof funcionarios;
|
||||||
healthCheck: typeof healthCheck;
|
healthCheck: typeof healthCheck;
|
||||||
http: typeof http;
|
http: typeof http;
|
||||||
|
|||||||
138
packages/backend/convex/documentos.ts
Normal file
138
packages/backend/convex/documentos.ts
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import { v } from "convex/values";
|
||||||
|
import { mutation, query } from "./_generated/server";
|
||||||
|
|
||||||
|
// Mutation para fazer upload de arquivo e obter o storage ID
|
||||||
|
export const generateUploadUrl = mutation({
|
||||||
|
args: {},
|
||||||
|
returns: v.string(),
|
||||||
|
handler: async (ctx) => {
|
||||||
|
return await ctx.storage.generateUploadUrl();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mutation para atualizar um campo de documento do funcionário
|
||||||
|
export const updateDocumento = mutation({
|
||||||
|
args: {
|
||||||
|
funcionarioId: v.id("funcionarios"),
|
||||||
|
campo: v.string(),
|
||||||
|
storageId: v.union(v.id("_storage"), v.null()),
|
||||||
|
},
|
||||||
|
returns: v.null(),
|
||||||
|
handler: async (ctx, args) => {
|
||||||
|
const funcionario = await ctx.db.get(args.funcionarioId);
|
||||||
|
if (!funcionario) {
|
||||||
|
throw new Error("Funcionário não encontrado");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atualizar o campo específico do documento
|
||||||
|
await ctx.db.patch(args.funcionarioId, {
|
||||||
|
[args.campo]: args.storageId,
|
||||||
|
} as any);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Query para obter URLs de todos os documentos de um funcionário
|
||||||
|
export const getDocumentosUrls = query({
|
||||||
|
args: { funcionarioId: v.id("funcionarios") },
|
||||||
|
returns: v.object({
|
||||||
|
certidaoAntecedentesPF: v.union(v.string(), v.null()),
|
||||||
|
certidaoAntecedentesJFPE: v.union(v.string(), v.null()),
|
||||||
|
certidaoAntecedentesSDS: v.union(v.string(), v.null()),
|
||||||
|
certidaoAntecedentesTJPE: v.union(v.string(), v.null()),
|
||||||
|
certidaoImprobidade: v.union(v.string(), v.null()),
|
||||||
|
rgFrente: v.union(v.string(), v.null()),
|
||||||
|
rgVerso: v.union(v.string(), v.null()),
|
||||||
|
cpfFrente: v.union(v.string(), v.null()),
|
||||||
|
cpfVerso: v.union(v.string(), v.null()),
|
||||||
|
situacaoCadastralCPF: v.union(v.string(), v.null()),
|
||||||
|
tituloEleitorFrente: v.union(v.string(), v.null()),
|
||||||
|
tituloEleitorVerso: v.union(v.string(), v.null()),
|
||||||
|
comprovanteVotacao: v.union(v.string(), v.null()),
|
||||||
|
carteiraProfissionalFrente: v.union(v.string(), v.null()),
|
||||||
|
carteiraProfissionalVerso: v.union(v.string(), v.null()),
|
||||||
|
comprovantePIS: v.union(v.string(), v.null()),
|
||||||
|
certidaoRegistroCivil: v.union(v.string(), v.null()),
|
||||||
|
certidaoNascimentoDependentes: v.union(v.string(), v.null()),
|
||||||
|
cpfDependentes: v.union(v.string(), v.null()),
|
||||||
|
reservistaDoc: v.union(v.string(), v.null()),
|
||||||
|
comprovanteEscolaridade: v.union(v.string(), v.null()),
|
||||||
|
comprovanteResidencia: v.union(v.string(), v.null()),
|
||||||
|
comprovanteContaBradesco: v.union(v.string(), v.null()),
|
||||||
|
declaracaoAcumulacaoCargo: v.union(v.string(), v.null()),
|
||||||
|
declaracaoDependentesIR: v.union(v.string(), v.null()),
|
||||||
|
declaracaoIdoneidade: v.union(v.string(), v.null()),
|
||||||
|
termoNepotismo: v.union(v.string(), v.null()),
|
||||||
|
termoOpcaoRemuneracao: v.union(v.string(), v.null()),
|
||||||
|
}),
|
||||||
|
handler: async (ctx, args) => {
|
||||||
|
const funcionario = await ctx.db.get(args.funcionarioId);
|
||||||
|
if (!funcionario) {
|
||||||
|
throw new Error("Funcionário não encontrado");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gerar URLs para todos os documentos
|
||||||
|
const urls: Record<string, string | null> = {};
|
||||||
|
const campos = [
|
||||||
|
"certidaoAntecedentesPF",
|
||||||
|
"certidaoAntecedentesJFPE",
|
||||||
|
"certidaoAntecedentesSDS",
|
||||||
|
"certidaoAntecedentesTJPE",
|
||||||
|
"certidaoImprobidade",
|
||||||
|
"rgFrente",
|
||||||
|
"rgVerso",
|
||||||
|
"cpfFrente",
|
||||||
|
"cpfVerso",
|
||||||
|
"situacaoCadastralCPF",
|
||||||
|
"tituloEleitorFrente",
|
||||||
|
"tituloEleitorVerso",
|
||||||
|
"comprovanteVotacao",
|
||||||
|
"carteiraProfissionalFrente",
|
||||||
|
"carteiraProfissionalVerso",
|
||||||
|
"comprovantePIS",
|
||||||
|
"certidaoRegistroCivil",
|
||||||
|
"certidaoNascimentoDependentes",
|
||||||
|
"cpfDependentes",
|
||||||
|
"reservistaDoc",
|
||||||
|
"comprovanteEscolaridade",
|
||||||
|
"comprovanteResidencia",
|
||||||
|
"comprovanteContaBradesco",
|
||||||
|
"declaracaoAcumulacaoCargo",
|
||||||
|
"declaracaoDependentesIR",
|
||||||
|
"declaracaoIdoneidade",
|
||||||
|
"termoNepotismo",
|
||||||
|
"termoOpcaoRemuneracao",
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const campo of campos) {
|
||||||
|
const storageId = (funcionario as any)[campo];
|
||||||
|
if (storageId) {
|
||||||
|
urls[campo] = await ctx.storage.getUrl(storageId);
|
||||||
|
} else {
|
||||||
|
urls[campo] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return urls as any;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Query para obter metadados de um documento
|
||||||
|
export const getDocumentoMetadata = query({
|
||||||
|
args: { storageId: v.id("_storage") },
|
||||||
|
returns: v.union(
|
||||||
|
v.object({
|
||||||
|
_id: v.id("_storage"),
|
||||||
|
_creationTime: v.number(),
|
||||||
|
contentType: v.optional(v.string()),
|
||||||
|
sha256: v.string(),
|
||||||
|
size: v.number(),
|
||||||
|
}),
|
||||||
|
v.null()
|
||||||
|
),
|
||||||
|
handler: async (ctx, args) => {
|
||||||
|
return await ctx.db.system.get(args.storageId);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
@@ -2,58 +2,43 @@ import { v } from "convex/values";
|
|||||||
import { query, mutation } from "./_generated/server";
|
import { query, mutation } from "./_generated/server";
|
||||||
import { simboloTipo } from "./schema";
|
import { simboloTipo } from "./schema";
|
||||||
|
|
||||||
|
// Validadores para campos opcionais
|
||||||
|
const sexoValidator = v.optional(v.union(v.literal("masculino"), v.literal("feminino"), v.literal("outro")));
|
||||||
|
const estadoCivilValidator = v.optional(v.union(v.literal("solteiro"), v.literal("casado"), v.literal("divorciado"), v.literal("viuvo"), v.literal("uniao_estavel")));
|
||||||
|
const grauInstrucaoValidator = v.optional(v.union(v.literal("fundamental"), v.literal("medio"), v.literal("superior"), v.literal("pos_graduacao"), v.literal("mestrado"), v.literal("doutorado")));
|
||||||
|
const grupoSanguineoValidator = v.optional(v.union(v.literal("A"), v.literal("B"), v.literal("AB"), v.literal("O")));
|
||||||
|
const fatorRHValidator = v.optional(v.union(v.literal("positivo"), v.literal("negativo")));
|
||||||
|
const aposentadoValidator = v.optional(v.union(v.literal("nao"), v.literal("funape_ipsep"), v.literal("inss")));
|
||||||
|
|
||||||
export const getAll = query({
|
export const getAll = query({
|
||||||
args: {},
|
args: {},
|
||||||
returns: v.array(
|
|
||||||
v.object({
|
|
||||||
_id: v.id("funcionarios"),
|
|
||||||
_creationTime: v.number(),
|
|
||||||
nome: v.string(),
|
|
||||||
nascimento: v.string(),
|
|
||||||
rg: v.string(),
|
|
||||||
cpf: v.string(),
|
|
||||||
endereco: v.string(),
|
|
||||||
cep: v.string(),
|
|
||||||
cidade: v.string(),
|
|
||||||
uf: v.string(),
|
|
||||||
telefone: v.string(),
|
|
||||||
email: v.string(),
|
|
||||||
matricula: v.string(),
|
|
||||||
admissaoData: v.optional(v.string()),
|
|
||||||
desligamentoData: v.optional(v.string()),
|
|
||||||
simboloId: v.id("simbolos"),
|
|
||||||
simboloTipo: simboloTipo,
|
|
||||||
})
|
|
||||||
),
|
|
||||||
handler: async (ctx) => {
|
handler: async (ctx) => {
|
||||||
return await ctx.db.query("funcionarios").collect();
|
const funcionarios = await ctx.db.query("funcionarios").collect();
|
||||||
|
// Retornar apenas os campos necessários para listagem
|
||||||
|
return funcionarios.map((f: any) => ({
|
||||||
|
_id: f._id,
|
||||||
|
_creationTime: f._creationTime,
|
||||||
|
nome: f.nome,
|
||||||
|
matricula: f.matricula,
|
||||||
|
cpf: f.cpf,
|
||||||
|
rg: f.rg,
|
||||||
|
nascimento: f.nascimento,
|
||||||
|
email: f.email,
|
||||||
|
telefone: f.telefone,
|
||||||
|
endereco: f.endereco,
|
||||||
|
cep: f.cep,
|
||||||
|
cidade: f.cidade,
|
||||||
|
uf: f.uf,
|
||||||
|
simboloId: f.simboloId,
|
||||||
|
simboloTipo: f.simboloTipo,
|
||||||
|
admissaoData: f.admissaoData,
|
||||||
|
desligamentoData: f.desligamentoData,
|
||||||
|
}));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const getById = query({
|
export const getById = query({
|
||||||
args: { id: v.id("funcionarios") },
|
args: { id: v.id("funcionarios") },
|
||||||
returns: v.union(
|
|
||||||
v.object({
|
|
||||||
_id: v.id("funcionarios"),
|
|
||||||
_creationTime: v.number(),
|
|
||||||
nome: v.string(),
|
|
||||||
nascimento: v.string(),
|
|
||||||
rg: v.string(),
|
|
||||||
cpf: v.string(),
|
|
||||||
endereco: v.string(),
|
|
||||||
cep: v.string(),
|
|
||||||
cidade: v.string(),
|
|
||||||
uf: v.string(),
|
|
||||||
telefone: v.string(),
|
|
||||||
email: v.string(),
|
|
||||||
matricula: v.string(),
|
|
||||||
admissaoData: v.optional(v.string()),
|
|
||||||
desligamentoData: v.optional(v.string()),
|
|
||||||
simboloId: v.id("simbolos"),
|
|
||||||
simboloTipo: simboloTipo,
|
|
||||||
}),
|
|
||||||
v.null()
|
|
||||||
),
|
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
return await ctx.db.get(args.id);
|
return await ctx.db.get(args.id);
|
||||||
},
|
},
|
||||||
@@ -61,6 +46,7 @@ export const getById = query({
|
|||||||
|
|
||||||
export const create = mutation({
|
export const create = mutation({
|
||||||
args: {
|
args: {
|
||||||
|
// Campos obrigatórios
|
||||||
nome: v.string(),
|
nome: v.string(),
|
||||||
matricula: v.string(),
|
matricula: v.string(),
|
||||||
simboloId: v.id("simbolos"),
|
simboloId: v.id("simbolos"),
|
||||||
@@ -76,6 +62,81 @@ export const create = mutation({
|
|||||||
admissaoData: v.optional(v.string()),
|
admissaoData: v.optional(v.string()),
|
||||||
desligamentoData: v.optional(v.string()),
|
desligamentoData: v.optional(v.string()),
|
||||||
simboloTipo: simboloTipo,
|
simboloTipo: simboloTipo,
|
||||||
|
|
||||||
|
// Dados Pessoais Adicionais
|
||||||
|
nomePai: v.optional(v.string()),
|
||||||
|
nomeMae: v.optional(v.string()),
|
||||||
|
naturalidade: v.optional(v.string()),
|
||||||
|
naturalidadeUF: v.optional(v.string()),
|
||||||
|
sexo: sexoValidator,
|
||||||
|
estadoCivil: estadoCivilValidator,
|
||||||
|
nacionalidade: v.optional(v.string()),
|
||||||
|
|
||||||
|
// Documentos Pessoais
|
||||||
|
rgOrgaoExpedidor: v.optional(v.string()),
|
||||||
|
rgDataEmissao: v.optional(v.string()),
|
||||||
|
carteiraProfissionalNumero: v.optional(v.string()),
|
||||||
|
carteiraProfissionalSerie: v.optional(v.string()),
|
||||||
|
carteiraProfissionalDataEmissao: v.optional(v.string()),
|
||||||
|
reservistaNumero: v.optional(v.string()),
|
||||||
|
reservistaSerie: v.optional(v.string()),
|
||||||
|
tituloEleitorNumero: v.optional(v.string()),
|
||||||
|
tituloEleitorZona: v.optional(v.string()),
|
||||||
|
tituloEleitorSecao: v.optional(v.string()),
|
||||||
|
pisNumero: v.optional(v.string()),
|
||||||
|
|
||||||
|
// Formação e Saúde
|
||||||
|
grauInstrucao: grauInstrucaoValidator,
|
||||||
|
formacao: v.optional(v.string()),
|
||||||
|
formacaoRegistro: v.optional(v.string()),
|
||||||
|
grupoSanguineo: grupoSanguineoValidator,
|
||||||
|
fatorRH: fatorRHValidator,
|
||||||
|
|
||||||
|
// Cargo e Vínculo
|
||||||
|
descricaoCargo: v.optional(v.string()),
|
||||||
|
nomeacaoPortaria: v.optional(v.string()),
|
||||||
|
nomeacaoData: v.optional(v.string()),
|
||||||
|
nomeacaoDOE: v.optional(v.string()),
|
||||||
|
pertenceOrgaoPublico: v.optional(v.boolean()),
|
||||||
|
orgaoOrigem: v.optional(v.string()),
|
||||||
|
aposentado: aposentadoValidator,
|
||||||
|
|
||||||
|
// Dados Bancários
|
||||||
|
contaBradescoNumero: v.optional(v.string()),
|
||||||
|
contaBradescoDV: v.optional(v.string()),
|
||||||
|
contaBradescoAgencia: v.optional(v.string()),
|
||||||
|
|
||||||
|
// Documentos Anexos (Storage IDs)
|
||||||
|
certidaoAntecedentesPF: v.optional(v.id("_storage")),
|
||||||
|
certidaoAntecedentesJFPE: v.optional(v.id("_storage")),
|
||||||
|
certidaoAntecedentesSDS: v.optional(v.id("_storage")),
|
||||||
|
certidaoAntecedentesTJPE: v.optional(v.id("_storage")),
|
||||||
|
certidaoImprobidade: v.optional(v.id("_storage")),
|
||||||
|
rgFrente: v.optional(v.id("_storage")),
|
||||||
|
rgVerso: v.optional(v.id("_storage")),
|
||||||
|
cpfFrente: v.optional(v.id("_storage")),
|
||||||
|
cpfVerso: v.optional(v.id("_storage")),
|
||||||
|
situacaoCadastralCPF: v.optional(v.id("_storage")),
|
||||||
|
tituloEleitorFrente: v.optional(v.id("_storage")),
|
||||||
|
tituloEleitorVerso: v.optional(v.id("_storage")),
|
||||||
|
comprovanteVotacao: v.optional(v.id("_storage")),
|
||||||
|
carteiraProfissionalFrente: v.optional(v.id("_storage")),
|
||||||
|
carteiraProfissionalVerso: v.optional(v.id("_storage")),
|
||||||
|
comprovantePIS: v.optional(v.id("_storage")),
|
||||||
|
certidaoRegistroCivil: v.optional(v.id("_storage")),
|
||||||
|
certidaoNascimentoDependentes: v.optional(v.id("_storage")),
|
||||||
|
cpfDependentes: v.optional(v.id("_storage")),
|
||||||
|
reservistaDoc: v.optional(v.id("_storage")),
|
||||||
|
comprovanteEscolaridade: v.optional(v.id("_storage")),
|
||||||
|
comprovanteResidencia: v.optional(v.id("_storage")),
|
||||||
|
comprovanteContaBradesco: v.optional(v.id("_storage")),
|
||||||
|
|
||||||
|
// Declarações (Storage IDs)
|
||||||
|
declaracaoAcumulacaoCargo: v.optional(v.id("_storage")),
|
||||||
|
declaracaoDependentesIR: v.optional(v.id("_storage")),
|
||||||
|
declaracaoIdoneidade: v.optional(v.id("_storage")),
|
||||||
|
termoNepotismo: v.optional(v.id("_storage")),
|
||||||
|
termoOpcaoRemuneracao: v.optional(v.id("_storage")),
|
||||||
},
|
},
|
||||||
returns: v.id("funcionarios"),
|
returns: v.id("funcionarios"),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
@@ -97,23 +158,7 @@ export const create = mutation({
|
|||||||
throw new Error("Matrícula já cadastrada");
|
throw new Error("Matrícula já cadastrada");
|
||||||
}
|
}
|
||||||
|
|
||||||
const novoFuncionarioId = await ctx.db.insert("funcionarios", {
|
const novoFuncionarioId = await ctx.db.insert("funcionarios", args as any);
|
||||||
nome: args.nome,
|
|
||||||
nascimento: args.nascimento,
|
|
||||||
rg: args.rg,
|
|
||||||
cpf: args.cpf,
|
|
||||||
endereco: args.endereco,
|
|
||||||
cep: args.cep,
|
|
||||||
cidade: args.cidade,
|
|
||||||
uf: args.uf,
|
|
||||||
telefone: args.telefone,
|
|
||||||
email: args.email,
|
|
||||||
matricula: args.matricula,
|
|
||||||
admissaoData: args.admissaoData,
|
|
||||||
desligamentoData: args.desligamentoData,
|
|
||||||
simboloId: args.simboloId,
|
|
||||||
simboloTipo: args.simboloTipo,
|
|
||||||
});
|
|
||||||
return novoFuncionarioId;
|
return novoFuncionarioId;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -121,6 +166,7 @@ export const create = mutation({
|
|||||||
export const update = mutation({
|
export const update = mutation({
|
||||||
args: {
|
args: {
|
||||||
id: v.id("funcionarios"),
|
id: v.id("funcionarios"),
|
||||||
|
// Campos obrigatórios
|
||||||
nome: v.string(),
|
nome: v.string(),
|
||||||
matricula: v.string(),
|
matricula: v.string(),
|
||||||
simboloId: v.id("simbolos"),
|
simboloId: v.id("simbolos"),
|
||||||
@@ -136,6 +182,81 @@ export const update = mutation({
|
|||||||
admissaoData: v.optional(v.string()),
|
admissaoData: v.optional(v.string()),
|
||||||
desligamentoData: v.optional(v.string()),
|
desligamentoData: v.optional(v.string()),
|
||||||
simboloTipo: simboloTipo,
|
simboloTipo: simboloTipo,
|
||||||
|
|
||||||
|
// Dados Pessoais Adicionais
|
||||||
|
nomePai: v.optional(v.string()),
|
||||||
|
nomeMae: v.optional(v.string()),
|
||||||
|
naturalidade: v.optional(v.string()),
|
||||||
|
naturalidadeUF: v.optional(v.string()),
|
||||||
|
sexo: sexoValidator,
|
||||||
|
estadoCivil: estadoCivilValidator,
|
||||||
|
nacionalidade: v.optional(v.string()),
|
||||||
|
|
||||||
|
// Documentos Pessoais
|
||||||
|
rgOrgaoExpedidor: v.optional(v.string()),
|
||||||
|
rgDataEmissao: v.optional(v.string()),
|
||||||
|
carteiraProfissionalNumero: v.optional(v.string()),
|
||||||
|
carteiraProfissionalSerie: v.optional(v.string()),
|
||||||
|
carteiraProfissionalDataEmissao: v.optional(v.string()),
|
||||||
|
reservistaNumero: v.optional(v.string()),
|
||||||
|
reservistaSerie: v.optional(v.string()),
|
||||||
|
tituloEleitorNumero: v.optional(v.string()),
|
||||||
|
tituloEleitorZona: v.optional(v.string()),
|
||||||
|
tituloEleitorSecao: v.optional(v.string()),
|
||||||
|
pisNumero: v.optional(v.string()),
|
||||||
|
|
||||||
|
// Formação e Saúde
|
||||||
|
grauInstrucao: grauInstrucaoValidator,
|
||||||
|
formacao: v.optional(v.string()),
|
||||||
|
formacaoRegistro: v.optional(v.string()),
|
||||||
|
grupoSanguineo: grupoSanguineoValidator,
|
||||||
|
fatorRH: fatorRHValidator,
|
||||||
|
|
||||||
|
// Cargo e Vínculo
|
||||||
|
descricaoCargo: v.optional(v.string()),
|
||||||
|
nomeacaoPortaria: v.optional(v.string()),
|
||||||
|
nomeacaoData: v.optional(v.string()),
|
||||||
|
nomeacaoDOE: v.optional(v.string()),
|
||||||
|
pertenceOrgaoPublico: v.optional(v.boolean()),
|
||||||
|
orgaoOrigem: v.optional(v.string()),
|
||||||
|
aposentado: aposentadoValidator,
|
||||||
|
|
||||||
|
// Dados Bancários
|
||||||
|
contaBradescoNumero: v.optional(v.string()),
|
||||||
|
contaBradescoDV: v.optional(v.string()),
|
||||||
|
contaBradescoAgencia: v.optional(v.string()),
|
||||||
|
|
||||||
|
// Documentos Anexos (Storage IDs)
|
||||||
|
certidaoAntecedentesPF: v.optional(v.id("_storage")),
|
||||||
|
certidaoAntecedentesJFPE: v.optional(v.id("_storage")),
|
||||||
|
certidaoAntecedentesSDS: v.optional(v.id("_storage")),
|
||||||
|
certidaoAntecedentesTJPE: v.optional(v.id("_storage")),
|
||||||
|
certidaoImprobidade: v.optional(v.id("_storage")),
|
||||||
|
rgFrente: v.optional(v.id("_storage")),
|
||||||
|
rgVerso: v.optional(v.id("_storage")),
|
||||||
|
cpfFrente: v.optional(v.id("_storage")),
|
||||||
|
cpfVerso: v.optional(v.id("_storage")),
|
||||||
|
situacaoCadastralCPF: v.optional(v.id("_storage")),
|
||||||
|
tituloEleitorFrente: v.optional(v.id("_storage")),
|
||||||
|
tituloEleitorVerso: v.optional(v.id("_storage")),
|
||||||
|
comprovanteVotacao: v.optional(v.id("_storage")),
|
||||||
|
carteiraProfissionalFrente: v.optional(v.id("_storage")),
|
||||||
|
carteiraProfissionalVerso: v.optional(v.id("_storage")),
|
||||||
|
comprovantePIS: v.optional(v.id("_storage")),
|
||||||
|
certidaoRegistroCivil: v.optional(v.id("_storage")),
|
||||||
|
certidaoNascimentoDependentes: v.optional(v.id("_storage")),
|
||||||
|
cpfDependentes: v.optional(v.id("_storage")),
|
||||||
|
reservistaDoc: v.optional(v.id("_storage")),
|
||||||
|
comprovanteEscolaridade: v.optional(v.id("_storage")),
|
||||||
|
comprovanteResidencia: v.optional(v.id("_storage")),
|
||||||
|
comprovanteContaBradesco: v.optional(v.id("_storage")),
|
||||||
|
|
||||||
|
// Declarações (Storage IDs)
|
||||||
|
declaracaoAcumulacaoCargo: v.optional(v.id("_storage")),
|
||||||
|
declaracaoDependentesIR: v.optional(v.id("_storage")),
|
||||||
|
declaracaoIdoneidade: v.optional(v.id("_storage")),
|
||||||
|
termoNepotismo: v.optional(v.id("_storage")),
|
||||||
|
termoOpcaoRemuneracao: v.optional(v.id("_storage")),
|
||||||
},
|
},
|
||||||
returns: v.null(),
|
returns: v.null(),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
@@ -157,23 +278,8 @@ export const update = mutation({
|
|||||||
throw new Error("Matrícula já cadastrada");
|
throw new Error("Matrícula já cadastrada");
|
||||||
}
|
}
|
||||||
|
|
||||||
await ctx.db.patch(args.id, {
|
const { id, ...updateData } = args;
|
||||||
nome: args.nome,
|
await ctx.db.patch(id, updateData as any);
|
||||||
nascimento: args.nascimento,
|
|
||||||
rg: args.rg,
|
|
||||||
cpf: args.cpf,
|
|
||||||
endereco: args.endereco,
|
|
||||||
cep: args.cep,
|
|
||||||
cidade: args.cidade,
|
|
||||||
uf: args.uf,
|
|
||||||
telefone: args.telefone,
|
|
||||||
email: args.email,
|
|
||||||
matricula: args.matricula,
|
|
||||||
admissaoData: args.admissaoData,
|
|
||||||
desligamentoData: args.desligamentoData,
|
|
||||||
simboloId: args.simboloId,
|
|
||||||
simboloTipo: args.simboloTipo,
|
|
||||||
});
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -182,7 +288,31 @@ export const remove = mutation({
|
|||||||
args: { id: v.id("funcionarios") },
|
args: { id: v.id("funcionarios") },
|
||||||
returns: v.null(),
|
returns: v.null(),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
|
// TODO: Talvez queiramos também remover os arquivos do storage
|
||||||
await ctx.db.delete(args.id);
|
await ctx.db.delete(args.id);
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Query para obter ficha completa para impressão
|
||||||
|
export const getFichaCompleta = query({
|
||||||
|
args: { id: v.id("funcionarios") },
|
||||||
|
handler: async (ctx, args) => {
|
||||||
|
const funcionario = await ctx.db.get(args.id);
|
||||||
|
if (!funcionario) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buscar informações do símbolo
|
||||||
|
const simbolo = await ctx.db.get(funcionario.simboloId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...funcionario,
|
||||||
|
simbolo: simbolo ? {
|
||||||
|
nome: simbolo.nome,
|
||||||
|
descricao: simbolo.descricao,
|
||||||
|
valor: simbolo.valor,
|
||||||
|
} : null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export default defineSchema({
|
|||||||
completed: v.boolean(),
|
completed: v.boolean(),
|
||||||
}),
|
}),
|
||||||
funcionarios: defineTable({
|
funcionarios: defineTable({
|
||||||
|
// Campos obrigatórios existentes
|
||||||
nome: v.string(),
|
nome: v.string(),
|
||||||
nascimento: v.string(),
|
nascimento: v.string(),
|
||||||
rg: v.string(),
|
rg: v.string(),
|
||||||
@@ -31,6 +32,110 @@ export default defineSchema({
|
|||||||
desligamentoData: v.optional(v.string()),
|
desligamentoData: v.optional(v.string()),
|
||||||
simboloId: v.id("simbolos"),
|
simboloId: v.id("simbolos"),
|
||||||
simboloTipo: simboloTipo,
|
simboloTipo: simboloTipo,
|
||||||
|
|
||||||
|
// Dados Pessoais Adicionais (opcionais)
|
||||||
|
nomePai: v.optional(v.string()),
|
||||||
|
nomeMae: v.optional(v.string()),
|
||||||
|
naturalidade: v.optional(v.string()),
|
||||||
|
naturalidadeUF: v.optional(v.string()),
|
||||||
|
sexo: v.optional(v.union(
|
||||||
|
v.literal("masculino"),
|
||||||
|
v.literal("feminino"),
|
||||||
|
v.literal("outro")
|
||||||
|
)),
|
||||||
|
estadoCivil: v.optional(v.union(
|
||||||
|
v.literal("solteiro"),
|
||||||
|
v.literal("casado"),
|
||||||
|
v.literal("divorciado"),
|
||||||
|
v.literal("viuvo"),
|
||||||
|
v.literal("uniao_estavel")
|
||||||
|
)),
|
||||||
|
nacionalidade: v.optional(v.string()),
|
||||||
|
|
||||||
|
// Documentos Pessoais
|
||||||
|
rgOrgaoExpedidor: v.optional(v.string()),
|
||||||
|
rgDataEmissao: v.optional(v.string()),
|
||||||
|
carteiraProfissionalNumero: v.optional(v.string()),
|
||||||
|
carteiraProfissionalSerie: v.optional(v.string()),
|
||||||
|
carteiraProfissionalDataEmissao: v.optional(v.string()),
|
||||||
|
reservistaNumero: v.optional(v.string()),
|
||||||
|
reservistaSerie: v.optional(v.string()),
|
||||||
|
tituloEleitorNumero: v.optional(v.string()),
|
||||||
|
tituloEleitorZona: v.optional(v.string()),
|
||||||
|
tituloEleitorSecao: v.optional(v.string()),
|
||||||
|
pisNumero: v.optional(v.string()),
|
||||||
|
|
||||||
|
// Formação e Saúde
|
||||||
|
grauInstrucao: v.optional(v.union(
|
||||||
|
v.literal("fundamental"),
|
||||||
|
v.literal("medio"),
|
||||||
|
v.literal("superior"),
|
||||||
|
v.literal("pos_graduacao"),
|
||||||
|
v.literal("mestrado"),
|
||||||
|
v.literal("doutorado")
|
||||||
|
)),
|
||||||
|
formacao: v.optional(v.string()),
|
||||||
|
formacaoRegistro: v.optional(v.string()),
|
||||||
|
grupoSanguineo: v.optional(v.union(
|
||||||
|
v.literal("A"),
|
||||||
|
v.literal("B"),
|
||||||
|
v.literal("AB"),
|
||||||
|
v.literal("O")
|
||||||
|
)),
|
||||||
|
fatorRH: v.optional(v.union(
|
||||||
|
v.literal("positivo"),
|
||||||
|
v.literal("negativo")
|
||||||
|
)),
|
||||||
|
|
||||||
|
// Cargo e Vínculo
|
||||||
|
descricaoCargo: v.optional(v.string()),
|
||||||
|
nomeacaoPortaria: v.optional(v.string()),
|
||||||
|
nomeacaoData: v.optional(v.string()),
|
||||||
|
nomeacaoDOE: v.optional(v.string()),
|
||||||
|
pertenceOrgaoPublico: v.optional(v.boolean()),
|
||||||
|
orgaoOrigem: v.optional(v.string()),
|
||||||
|
aposentado: v.optional(v.union(
|
||||||
|
v.literal("nao"),
|
||||||
|
v.literal("funape_ipsep"),
|
||||||
|
v.literal("inss")
|
||||||
|
)),
|
||||||
|
|
||||||
|
// Dados Bancários
|
||||||
|
contaBradescoNumero: v.optional(v.string()),
|
||||||
|
contaBradescoDV: v.optional(v.string()),
|
||||||
|
contaBradescoAgencia: v.optional(v.string()),
|
||||||
|
|
||||||
|
// Documentos Anexos (Storage IDs)
|
||||||
|
certidaoAntecedentesPF: v.optional(v.id("_storage")),
|
||||||
|
certidaoAntecedentesJFPE: v.optional(v.id("_storage")),
|
||||||
|
certidaoAntecedentesSDS: v.optional(v.id("_storage")),
|
||||||
|
certidaoAntecedentesTJPE: v.optional(v.id("_storage")),
|
||||||
|
certidaoImprobidade: v.optional(v.id("_storage")),
|
||||||
|
rgFrente: v.optional(v.id("_storage")),
|
||||||
|
rgVerso: v.optional(v.id("_storage")),
|
||||||
|
cpfFrente: v.optional(v.id("_storage")),
|
||||||
|
cpfVerso: v.optional(v.id("_storage")),
|
||||||
|
situacaoCadastralCPF: v.optional(v.id("_storage")),
|
||||||
|
tituloEleitorFrente: v.optional(v.id("_storage")),
|
||||||
|
tituloEleitorVerso: v.optional(v.id("_storage")),
|
||||||
|
comprovanteVotacao: v.optional(v.id("_storage")),
|
||||||
|
carteiraProfissionalFrente: v.optional(v.id("_storage")),
|
||||||
|
carteiraProfissionalVerso: v.optional(v.id("_storage")),
|
||||||
|
comprovantePIS: v.optional(v.id("_storage")),
|
||||||
|
certidaoRegistroCivil: v.optional(v.id("_storage")),
|
||||||
|
certidaoNascimentoDependentes: v.optional(v.id("_storage")),
|
||||||
|
cpfDependentes: v.optional(v.id("_storage")),
|
||||||
|
reservistaDoc: v.optional(v.id("_storage")),
|
||||||
|
comprovanteEscolaridade: v.optional(v.id("_storage")),
|
||||||
|
comprovanteResidencia: v.optional(v.id("_storage")),
|
||||||
|
comprovanteContaBradesco: v.optional(v.id("_storage")),
|
||||||
|
|
||||||
|
// Declarações (Storage IDs)
|
||||||
|
declaracaoAcumulacaoCargo: v.optional(v.id("_storage")),
|
||||||
|
declaracaoDependentesIR: v.optional(v.id("_storage")),
|
||||||
|
declaracaoIdoneidade: v.optional(v.id("_storage")),
|
||||||
|
termoNepotismo: v.optional(v.id("_storage")),
|
||||||
|
termoOpcaoRemuneracao: v.optional(v.id("_storage")),
|
||||||
})
|
})
|
||||||
.index("by_matricula", ["matricula"])
|
.index("by_matricula", ["matricula"])
|
||||||
.index("by_nome", ["nome"])
|
.index("by_nome", ["nome"])
|
||||||
|
|||||||
Reference in New Issue
Block a user