Fix usuarios page #6

Merged
killer-cf merged 28 commits from fix-usuarios-page into master 2025-11-04 17:42:21 +00:00
78 changed files with 15762 additions and 8826 deletions
Showing only changes of commit d5c01aabab - Show all commits

View File

@@ -99,6 +99,18 @@
let mostrarFormularioCurso = $state(false);
let cursoAtual = $state({ descricao: "", data: "", arquivo: null as File | null });
// Dependentes
let dependentes = $state<Array<{ id: string; parentesco: string; nome: string; cpf: string; nascimento: string; documentoId?: string }>>([]);
let mostrarFormularioDependente = $state(false);
let dependenteAtual = $state<{ parentesco: string; nome: string; cpf: string; nascimento: string; arquivo: File | null; documentoId?: string }>({
parentesco: "",
nome: "",
cpf: "",
nascimento: "",
arquivo: null,
documentoId: undefined,
});
function adicionarCurso() {
if (!cursoAtual.descricao.trim() || !cursoAtual.data.trim()) {
alert("Preencha a descrição e a data do curso");
@@ -129,6 +141,49 @@
return result.storageId;
}
async function uploadDocumentoDependente(file: File): Promise<string> {
const uploadUrl = await client.mutation(api.documentos.generateUploadUrl, {});
const response = await fetch(uploadUrl, {
method: "POST",
headers: { "Content-Type": file.type },
body: file,
});
const result = await response.json();
return result.storageId as string;
}
function adicionarDependente() {
if (dependentes.length >= 10) {
alert("Limite de 10 dependentes atingido");
return;
}
if (!dependenteAtual.parentesco || !dependenteAtual.nome.trim() || !dependenteAtual.cpf.trim() || !dependenteAtual.nascimento.trim()) {
alert("Preencha Parentesco, Nome, CPF e Data de Nascimento do dependente");
return;
}
if (!validateCPF(dependenteAtual.cpf)) {
alert("CPF do dependente inválido");
return;
}
if (!validateDate(dependenteAtual.nascimento)) {
alert("Data de nascimento do dependente inválida");
return;
}
dependentes.push({
id: crypto.randomUUID(),
parentesco: dependenteAtual.parentesco,
nome: dependenteAtual.nome.trim(),
cpf: onlyDigits(dependenteAtual.cpf),
nascimento: dependenteAtual.nascimento,
documentoId: dependenteAtual.documentoId,
});
dependenteAtual = { parentesco: "", nome: "", cpf: "", nascimento: "", arquivo: null, documentoId: undefined };
}
function removerDependente(id: string) {
dependentes = dependentes.filter((d) => d.id !== id);
}
async function loadSimbolos() {
const list = await client.query(api.simbolos.getAll, {} as any);
simbolos = list.map((s: any) => ({
@@ -267,6 +322,16 @@
...Object.fromEntries(
Object.entries(documentosStorage).map(([key, value]) => [key, value as any])
),
// Dependentes (opcional)
dependentes: dependentes.length
? dependentes.map((d) => ({
parentesco: d.parentesco,
nome: d.nome,
cpf: d.cpf,
nascimento: d.nascimento,
documentoId: d.documentoId as any,
}))
: undefined,
};
const novoFuncionarioId = await client.mutation(api.funcionarios.create, payload as any);
@@ -358,7 +423,7 @@
{/if}
<!-- Formulário -->
<form class="space-y-6" onsubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
<form class="space-y-6" onsubmit={(e) => { e.preventDefault(); }}>
<!-- Card 1: Informações Pessoais -->
<div class="card bg-base-100 shadow-xl">
@@ -1060,6 +1125,96 @@
</div>
</div>
<!-- Card 4.5: Dependentes -->
<div class="card bg-base-100 shadow-xl">
<div class="card-body space-y-4">
<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="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2a3 3 0 00-5.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2a3 3 0 015.356-1.857M15 7a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
Dependentes
</h2>
{#if dependentes.length > 0}
<div class="space-y-2">
<h3 class="font-semibold text-sm">Dependentes adicionados ({dependentes.length}/10)</h3>
{#each dependentes as dep}
<div class="flex items-center gap-3 p-3 bg-base-200 rounded-lg">
<div class="flex-1 text-sm">
<p class="font-semibold">{dep.nome}{dep.parentesco}</p>
<p class="text-xs text-base-content/70">CPF: {dep.cpf} • Nasc.: {dep.nascimento}</p>
</div>
<button type="button" class="btn btn-sm btn-error btn-square" aria-label="Remover dependente" onclick={() => removerDependente(dep.id)}>
<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>
</button>
</div>
{/each}
</div>
{/if}
{#if dependentes.length < 10}
<div class="collapse collapse-arrow border border-base-300 bg-base-200">
<input type="checkbox" bind:checked={mostrarFormularioDependente} />
<div class="collapse-title font-medium">Adicionar Dependente</div>
<div class="collapse-content">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 pt-2">
<div class="form-control">
<label class="label" for="dep-parentesco">
<span class="label-text font-medium">Grau de Parentesco</span>
</label>
<select id="dep-parentesco" class="select select-bordered w-full" bind:value={dependenteAtual.parentesco}>
<option value="">Selecione...</option>
<option value="filho">Filho</option>
<option value="filha">Filha</option>
<option value="conjuge">Cônjuge</option>
<option value="outro">Outros</option>
</select>
</div>
<div class="form-control lg:col-span-2">
<label class="label" for="dep-nome">
<span class="label-text font-medium">Nome do Dependente</span>
</label>
<input id="dep-nome" type="text" class="input input-bordered w-full" bind:value={dependenteAtual.nome} />
</div>
<div class="form-control">
<label class="label" for="dep-cpf">
<span class="label-text font-medium">CPF do Dependente</span>
</label>
<input id="dep-cpf" type="text" inputmode="numeric" class="input input-bordered w-full" value={dependenteAtual.cpf}
oninput={(e) => { const t = e.target as HTMLInputElement; dependenteAtual.cpf = maskCPF(t.value); t.value = dependenteAtual.cpf; }} />
</div>
<div class="form-control">
<label class="label" for="dep-nascimento">
<span class="label-text font-medium">Data de Nascimento</span>
</label>
<input id="dep-nascimento" type="text" inputmode="numeric" placeholder="dd/mm/aaaa" class="input input-bordered w-full" value={dependenteAtual.nascimento}
oninput={(e) => { const t = e.target as HTMLInputElement; dependenteAtual.nascimento = maskDate(t.value); t.value = dependenteAtual.nascimento; }} />
</div>
<div class="form-control lg:col-span-3">
<label class="label" for="dep-doc">
<span class="label-text font-medium">Certidão de Nascimento/Casamento ou Outros (opcional)</span>
</label>
<input id="dep-doc" type="file" class="file-input file-input-bordered w-full" accept=".pdf,.jpg,.jpeg,.png"
onchange={async (e) => { const file = e.currentTarget.files?.[0]; dependenteAtual.arquivo = file || null; if (file) { try { dependenteAtual.documentoId = await uploadDocumentoDependente(file); } catch { alert("Falha no upload do documento do dependente"); } } }} />
</div>
<div class="lg:col-span-3">
<button type="button" class="btn btn-primary btn-sm gap-2" onclick={adicionarDependente}>
<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="M12 4v16m8-8H4" /></svg>
Adicionar Dependente
</button>
</div>
</div>
</div>
</div>
{/if}
</div>
</div>
<!-- Card 5: Cargo e Vínculo -->
<div class="card bg-base-100 shadow-xl">
<div class="card-body space-y-4">
@@ -1345,9 +1500,10 @@
Cancelar
</button>
<button
type="submit"
type="button"
class="btn btn-primary btn-lg"
disabled={loading}
onclick={() => (document.getElementById('confirmar_cadastro_func') as HTMLDialogElement)?.showModal()}
>
{#if loading}
<span class="loading loading-spinner"></span>
@@ -1362,5 +1518,23 @@
</div>
</div>
</div>
<!-- Modal de Confirmação Legal -->
<dialog id="confirmar_cadastro_func" class="modal">
<div class="modal-box max-w-3xl">
<h3 class="font-bold text-lg mb-3">Declaração de Veracidade</h3>
<div class="prose text-sm max-w-none">
<p>
Declaro, sob as penas da lei (art. 299 do Código Penal Brasileiro, que trata do crime de falsidade ideológica, e demais cominações legais aplicáveis), a veracidade e autenticidade de todas as informações e documentos por mim prestados/enviados neste ato. Tenho ciência de que a falsidade das informações implicará nas penalidades cabíveis, podendo resultar na nulidade do ato ou no dever de ressarcir eventuais valores recebidos indevidamente.
</p>
</div>
<div class="modal-action">
<div class="flex gap-2">
<button type="button" class="btn" onclick={() => (document.getElementById('confirmar_cadastro_func') as HTMLDialogElement)?.close()}>Cancelar</button>
<button type="button" class="btn btn-primary" onclick={() => { (document.getElementById('confirmar_cadastro_func') as HTMLDialogElement)?.close(); handleSubmit(); }}>Ciente</button>
</div>
</div>
</div>
</dialog>
</form>
</main>

View File

@@ -174,6 +174,23 @@ export const create = mutation({
declaracaoIdoneidade: v.optional(v.id("_storage")),
termoNepotismo: v.optional(v.id("_storage")),
termoOpcaoRemuneracao: v.optional(v.id("_storage")),
// Dependentes (opcional)
dependentes: v.optional(
v.array(
v.object({
parentesco: v.union(
v.literal("filho"),
v.literal("filha"),
v.literal("conjuge"),
v.literal("outro")
),
nome: v.string(),
cpf: v.string(),
nascimento: v.string(),
documentoId: v.optional(v.id("_storage")),
})
)
),
},
returns: v.id("funcionarios"),
handler: async (ctx, args) => {
@@ -303,6 +320,23 @@ export const update = mutation({
declaracaoIdoneidade: v.optional(v.id("_storage")),
termoNepotismo: v.optional(v.id("_storage")),
termoOpcaoRemuneracao: v.optional(v.id("_storage")),
// Dependentes (opcional)
dependentes: v.optional(
v.array(
v.object({
parentesco: v.union(
v.literal("filho"),
v.literal("filha"),
v.literal("conjuge"),
v.literal("outro")
),
nome: v.string(),
cpf: v.string(),
nascimento: v.string(),
documentoId: v.optional(v.id("_storage")),
})
)
),
},
returns: v.null(),
handler: async (ctx, args) => {

View File

@@ -134,6 +134,24 @@ export default defineSchema({
comprovanteResidencia: v.optional(v.id("_storage")),
comprovanteContaBradesco: v.optional(v.id("_storage")),
// Dependentes do funcionário (uploads opcionais)
dependentes: v.optional(
v.array(
v.object({
parentesco: v.union(
v.literal("filho"),
v.literal("filha"),
v.literal("conjuge"),
v.literal("outro")
),
nome: v.string(),
cpf: v.string(),
nascimento: v.string(),
documentoId: v.optional(v.id("_storage")),
})
)
),
// Declarações (Storage IDs)
declaracaoAcumulacaoCargo: v.optional(v.id("_storage")),
declaracaoDependentesIR: v.optional(v.id("_storage")),