feat: enhance employee and symbol management with new features, improved UI components, and backend schema updates

This commit is contained in:
2025-10-26 22:21:53 -03:00
parent 5dd00b63e1
commit 2c2b792b4a
48 changed files with 9513 additions and 672 deletions

View File

@@ -73,47 +73,112 @@
}
</script>
<div class="space-y-6 pb-32">
<main class="container mx-auto px-4 py-4">
<!-- Breadcrumb -->
<div class="text-sm breadcrumbs mb-4">
<ul>
<li><a href="/recursos-humanos" class="text-primary hover:underline">Recursos Humanos</a></li>
<li>Símbolos</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-green-500/20 rounded-xl">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z" />
</svg>
</div>
<div>
<h1 class="text-3xl font-bold text-primary">Símbolos Cadastrados</h1>
<p class="text-base-content/70">Gerencie cargos comissionados e funções gratificadas</p>
</div>
</div>
<a href="/recursos-humanos/simbolos/cadastro" class="btn btn-primary btn-lg gap-2">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" clip-rule="evenodd" />
</svg>
Novo Símbolo
</a>
</div>
</div>
<!-- Alertas -->
{#if notice}
<div class="alert" class:alert-success={notice.kind === "success"} class:alert-error={notice.kind === "error"}>
<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}
<div class="flex justify-between items-center">
<h2 class="text-3xl font-bold text-brand-dark">Símbolos</h2>
<a href="/recursos-humanos/simbolos/cadastro" class="btn btn-primary">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z"
clip-rule="evenodd"
/>
</svg>
Novo Símbolo
</a>
</div>
<div class="grid md:grid-cols-3 gap-3 items-end">
<div class="form-control">
<label class="label" for="symbol_nome"><span class="label-text">Nome</span></label>
<input id="symbol_nome" class="input input-bordered" bind:value={filtroNome} />
</div>
<div class="form-control">
<label class="label" for="symbol_tipo"><span class="label-text">Tipo</span></label>
<select id="symbol_tipo" class="select select-bordered" bind:value={filtroTipo}>
<option value="">Todos</option>
<option value="cargo_comissionado">Cargo comissionado</option>
<option value="funcao_gratificada">Função gratificada</option>
</select>
</div>
<div class="form-control">
<label class="label" for="symbol_desc"><span class="label-text">Descrição</span></label>
<input id="symbol_desc" class="input input-bordered" bind:value={filtroDescricao} />
<!-- Filtros -->
<div class="card bg-base-100 shadow-xl mb-6">
<div class="card-body">
<h2 class="card-title text-lg 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="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
</svg>
Filtros de Pesquisa
</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div class="form-control">
<label class="label" for="symbol_nome">
<span class="label-text font-semibold">Nome do Símbolo</span>
</label>
<input
id="symbol_nome"
class="input input-bordered focus:input-primary"
placeholder="Buscar por nome..."
bind:value={filtroNome}
/>
</div>
<div class="form-control">
<label class="label" for="symbol_tipo">
<span class="label-text font-semibold">Tipo</span>
</label>
<select id="symbol_tipo" class="select select-bordered focus:select-primary" bind:value={filtroTipo}>
<option value="">Todos os tipos</option>
<option value="cargo_comissionado">Cargo Comissionado</option>
<option value="funcao_gratificada">Função Gratificada</option>
</select>
</div>
<div class="form-control">
<label class="label" for="symbol_desc">
<span class="label-text font-semibold">Descrição</span>
</label>
<input
id="symbol_desc"
class="input input-bordered focus:input-primary"
placeholder="Buscar na descrição..."
bind:value={filtroDescricao}
/>
</div>
</div>
{#if filtroNome || filtroTipo || filtroDescricao}
<div class="mt-4">
<button
class="btn btn-ghost btn-sm gap-2"
onclick={() => {
filtroNome = "";
filtroTipo = "";
filtroDescricao = "";
}}
>
<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>
Limpar Filtros
</button>
</div>
{/if}
</div>
</div>
@@ -122,38 +187,42 @@
<span class="loading loading-spinner loading-lg"></span>
</div>
{:else}
<div class="overflow-x-auto bg-base-100 rounded-lg shadow-sm mb-2" class:overflow-y-auto={needsScroll} style={needsScroll ? "max-height: calc(100vh - 180px);" : "overflow-y: visible;"}>
<table class="table table-zebra">
<thead>
<tr>
<th>Nome</th>
<th>Tipo</th>
<th>Valor Referência</th>
<th>Valor Vencimento</th>
<th>Valor Total</th>
<th>Descrição</th>
<th class="text-right">Ações</th>
</tr>
</thead>
<tbody>
{#if filtered.length > 0}
{#each filtered as simbolo}
<tr class="hover">
<td class="font-medium">{simbolo.nome}</td>
<td>
<span
class="badge"
class:badge-primary={simbolo.tipo === "cargo_comissionado"}
class:badge-secondary={simbolo.tipo === "funcao_gratificada"}
>
{getTipoLabel(simbolo.tipo)}
</span>
</td>
<td>{simbolo.repValor ? formatMoney(simbolo.repValor) : "—"}</td>
<td>{simbolo.vencValor ? formatMoney(simbolo.vencValor) : "—"}</td>
<td class="font-semibold">{formatMoney(simbolo.valor)}</td>
<td class="max-w-xs truncate">{simbolo.descricao}</td>
<td class="text-right">
<!-- Tabela de Símbolos -->
<div class="card bg-base-100 shadow-xl">
<div class="card-body p-0">
<div class="overflow-x-auto">
<div class="overflow-y-auto" style="max-height: {filtered.length > 8 ? '600px' : 'none'};">
<table class="table table-zebra w-full">
<thead class="sticky top-0 bg-base-200 z-10">
<tr>
<th class="font-bold">Nome</th>
<th class="font-bold">Tipo</th>
<th class="font-bold">Valor Referência</th>
<th class="font-bold">Valor Vencimento</th>
<th class="font-bold">Valor Total</th>
<th class="font-bold">Descrição</th>
<th class="text-right font-bold">Ações</th>
</tr>
</thead>
<tbody>
{#if filtered.length > 0}
{#each filtered as simbolo}
<tr class="hover">
<td class="font-medium">{simbolo.nome}</td>
<td>
<span
class="badge"
class:badge-primary={simbolo.tipo === "cargo_comissionado"}
class:badge-secondary={simbolo.tipo === "funcao_gratificada"}
>
{getTipoLabel(simbolo.tipo)}
</span>
</td>
<td>{simbolo.repValor ? formatMoney(simbolo.repValor) : "—"}</td>
<td>{simbolo.vencValor ? formatMoney(simbolo.vencValor) : "—"}</td>
<td class="font-semibold">{formatMoney(simbolo.valor)}</td>
<td class="max-w-xs truncate">{simbolo.descricao}</td>
<td class="text-right">
<div class="dropdown dropdown-end" class:dropdown-open={openMenuId === simbolo._id}>
<button type="button" class="btn btn-ghost btn-sm" onclick={() => toggleMenu(simbolo._id)}>
<svg
@@ -201,20 +270,28 @@
</button>
</li>
</ul>
</div>
</td>
</tr>
{/each}
{:else}
<tr>
<td colspan="7" class="text-center opacity-70">Nenhum símbolo encontrado com os filtros atuais.</td>
</tr>
{/if}
</tbody>
</table>
</div>
</td>
</tr>
{/each}
{:else}
<tr>
<td colspan="7" class="text-center opacity-70 py-8">Nenhum símbolo encontrado com os filtros atuais.</td>
</tr>
{/if}
</tbody>
</table>
</div>
</div>
</div>
</div>
<!-- Informação sobre resultados -->
<div class="mt-4 text-sm text-base-content/70 text-center">
Exibindo {filtered.length} de {list.length} símbolo(s)
</div>
{/if}
</div>
</main>
<!-- Modal de Confirmação de Exclusão -->
<dialog id="delete_modal" class="modal">

View File

@@ -56,6 +56,7 @@
}
let notice = $state<{ kind: "success" | "error"; text: string } | null>(null);
function getTotalPreview(): string {
if (tipo !== "cargo_comissionado") return "";
const r = unmaskCurrencyToDotDecimal(form.getFieldValue("refValor"));
@@ -77,303 +78,400 @@
valor: !isCargo ? unmaskCurrencyToDotDecimal(value.valor) : undefined,
};
const res = await client.mutation(api.simbolos.create, payload);
if (res) {
formApi.reset();
notice = { kind: "success", text: "Símbolo cadastrado com sucesso." };
setTimeout(() => goto("/recursos-humanos/simbolos"), 600);
} else {
console.log("erro ao registrar cliente");
notice = { kind: "error", text: "Erro ao cadastrar símbolo." };
try {
const res = await client.mutation(api.simbolos.create, payload);
if (res) {
formApi.reset();
notice = { kind: "success", text: "Símbolo cadastrado com sucesso!" };
setTimeout(() => goto("/recursos-humanos/simbolos"), 1500);
}
} catch (error: any) {
notice = { kind: "error", text: error.message || "Erro ao cadastrar símbolo." };
}
},
defaultValues,
}));
</script>
<form
class="max-w-3xl mx-auto p-4"
onsubmit={(e) => {
e.preventDefault();
e.stopPropagation();
form.handleSubmit();
}}
>
<div class="card bg-base-100 shadow-xl">
<div class="card-body space-y-6">
{#if notice}
<div
class="alert"
class:alert-success={notice.kind === "success"}
class:alert-error={notice.kind === "error"}
>
<span>{notice.text}</span>
</div>
{/if}
<div>
<h2 class="card-title text-3xl">Cadastro de Símbolos</h2>
<p class="opacity-70">
Preencha os campos abaixo para cadastrar um novo símbolo.
</p>
<main class="container mx-auto px-4 py-4 max-w-4xl">
<!-- 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/simbolos" class="text-primary hover:underline">Símbolos</a></li>
<li>Cadastrar</li>
</ul>
</div>
<!-- Cabeçalho -->
<div class="mb-6">
<div class="flex items-center gap-4 mb-2">
<div class="p-3 bg-green-500/20 rounded-xl">
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z" />
</svg>
</div>
<div>
<h1 class="text-3xl font-bold text-primary">Cadastro de Símbolo</h1>
<p class="text-base-content/70">Preencha os campos abaixo para cadastrar um novo cargo ou função</p>
</div>
</div>
</div>
<form.Field name="nome" validators={{ onChange: schema.shape.nome }}>
{#snippet children({ name, state, handleChange })}
<div class="form-control">
<label class="label" for="nome">
<span class="label-text font-medium"
>Símbolo <span class="text-error">*</span></span
>
</label>
<input
{name}
value={state.value}
placeholder="Ex.: DAS-1"
class="input input-bordered w-full"
autocomplete="off"
oninput={(e) => {
const target = e.target as HTMLInputElement;
const value = target.value;
handleChange(value);
}}
required
aria-required="true"
/>
<div class="label">
<span class="label-text-alt opacity-60"
>Informe o nome identificador do símbolo.</span
>
</div>
</div>
{/snippet}
</form.Field>
<form.Field
name="descricao"
validators={{ onChange: schema.shape.descricao }}
>
{#snippet children({ name, state, handleChange })}
<div class="form-control">
<label class="label" for="descricao">
<span class="label-text font-medium"
>Descrição <span class="text-error">*</span></span
>
</label>
<input
{name}
value={state.value}
placeholder="Ex.: Cargo de Apoio 1"
class="input input-bordered w-full"
autocomplete="off"
oninput={(e) => {
const target = e.target as HTMLInputElement;
const value = target.value;
handleChange(value);
}}
required
aria-required="true"
/>
<div class="label">
<span class="label-text-alt opacity-60"
>Descreva brevemente o símbolo.</span
>
</div>
</div>
{/snippet}
</form.Field>
<form.Field
name="tipo"
validators={{
onChange: ({ value }) => (value ? undefined : "Obrigatório"),
}}
>
{#snippet children({ name, state, handleChange })}
<div class="form-control">
<label class="label" for="tipo">
<span class="label-text font-medium"
>Tipo <span class="text-error">*</span></span
>
</label>
<select
{name}
class="select select-bordered w-full"
bind:value={tipo}
oninput={(e) => {
const target = e.target as HTMLSelectElement;
const value = target.value;
handleChange(value);
}}
required
aria-required="true"
>
<option value="cargo_comissionado">Cargo comissionado</option>
<option value="funcao_gratificada">Função gratificada</option>
</select>
</div>
{/snippet}
</form.Field>
{#if tipo === "cargo_comissionado"}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<form.Field
name="vencValor"
validators={{
onChange: ({ value }) =>
form.getFieldValue("tipo") === "cargo_comissionado" && !value
? "Obrigatório"
: undefined,
}}
>
{#snippet children({ name, state, handleChange })}
<div class="form-control">
<label class="label" for="vencValor">
<span class="label-text font-medium"
>Valor de Vencimento <span class="text-error">*</span></span
>
</label>
<input
{name}
value={state.value}
placeholder="Ex.: 1200,00"
class="input input-bordered w-full"
inputmode="decimal"
autocomplete="off"
oninput={(e) => {
const target = e.target as HTMLInputElement;
const formatted = formatCurrencyBR(target.value);
target.value = formatted;
handleChange(formatted);
}}
required
aria-required="true"
/>
<div class="label">
<span class="label-text-alt opacity-60"
>Valor efetivo de vencimento.</span
>
</div>
</div>
{/snippet}
</form.Field>
<form.Field
name="refValor"
validators={{
onChange: ({ value }) =>
form.getFieldValue("tipo") === "cargo_comissionado" && !value
? "Obrigatório"
: undefined,
}}
>
{#snippet children({ name, state, handleChange })}
<div class="form-control">
<label class="label" for="refValor">
<span class="label-text font-medium"
>Valor de Referência <span class="text-error">*</span></span
>
</label>
<input
{name}
value={state.value}
placeholder="Ex.: 1000,00"
class="input input-bordered w-full"
inputmode="decimal"
autocomplete="off"
oninput={(e) => {
const target = e.target as HTMLInputElement;
const formatted = formatCurrencyBR(target.value);
target.value = formatted;
handleChange(formatted);
}}
required
aria-required="true"
/>
<div class="label">
<span class="label-text-alt opacity-60"
>Valor base de referência.</span
>
</div>
</div>
{/snippet}
</form.Field>
</div>
{#if getTotalPreview()}
<div class="alert bg-base-200">
<span>Total previsto: R$ {getTotalPreview()}</span>
</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}
{:else}
</svg>
<span>{notice.text}</span>
</div>
{/if}
<!-- Formulário -->
<form
class="space-y-6"
onsubmit={(e) => {
e.preventDefault();
e.stopPropagation();
form.handleSubmit();
}}
>
<div class="card bg-base-100 shadow-xl">
<div class="card-body space-y-6">
<h2 class="card-title text-xl border-b pb-3">Informações Básicas</h2>
<!-- Nome do Símbolo -->
<form.Field name="nome" validators={{ onChange: schema.shape.nome }}>
{#snippet children({ name, state, handleChange })}
<div class="form-control">
<label class="label" for="nome">
<span class="label-text font-semibold">
Nome do Símbolo <span class="text-error">*</span>
</span>
</label>
<input
{name}
id="nome"
value={state.value}
placeholder="Ex.: DAS-1, CAA-2, FDA-3"
class="input input-bordered w-full focus:input-primary"
autocomplete="off"
oninput={(e) => {
const target = e.target as HTMLInputElement;
handleChange(target.value);
}}
required
/>
<label class="label">
<span class="label-text-alt text-base-content/60">
Informe o código identificador do símbolo
</span>
</label>
</div>
{/snippet}
</form.Field>
<!-- Descrição -->
<form.Field name="descricao" validators={{ onChange: schema.shape.descricao }}>
{#snippet children({ name, state, handleChange })}
<div class="form-control">
<label class="label" for="descricao">
<span class="label-text font-semibold">
Descrição <span class="text-error">*</span>
</span>
</label>
<textarea
{name}
id="descricao"
value={state.value}
placeholder="Ex.: Cargo de Direção e Assessoramento Superior - Nível 1"
class="textarea textarea-bordered w-full h-24 focus:textarea-primary"
autocomplete="off"
oninput={(e) => {
const target = e.target as HTMLTextAreaElement;
handleChange(target.value);
}}
required
></textarea>
<label class="label">
<span class="label-text-alt text-base-content/60">
Descreva detalhadamente o símbolo
</span>
</label>
</div>
{/snippet}
</form.Field>
<!-- Tipo -->
<form.Field
name="valor"
name="tipo"
validators={{
onChange: ({ value }) =>
form.getFieldValue("tipo") === "funcao_gratificada" && !value
? "Obrigatório"
: undefined,
onChange: ({ value }) => (value ? undefined : "Obrigatório"),
}}
>
{#snippet children({ name, state, handleChange })}
<div class="form-control">
<label class="label" for="valor">
<span class="label-text font-medium"
>Valor <span class="text-error">*</span></span
>
<label class="label" for="tipo">
<span class="label-text font-semibold">
Tipo <span class="text-error">*</span>
</span>
</label>
<input
<select
{name}
value={state.value}
placeholder="Ex.: 1.500,00"
class="input input-bordered w-full"
inputmode="decimal"
autocomplete="off"
id="tipo"
class="select select-bordered w-full focus:select-primary"
bind:value={tipo}
oninput={(e) => {
const target = e.target as HTMLInputElement;
const formatted = formatCurrencyBR(target.value);
target.value = formatted;
handleChange(formatted);
const target = e.target as HTMLSelectElement;
handleChange(target.value);
}}
required
aria-required="true"
/>
<div class="label">
<span class="label-text-alt opacity-60"
>Informe o valor da função gratificada.</span
>
</div>
>
<option value="cargo_comissionado">Cargo Comissionado (CC)</option>
<option value="funcao_gratificada">Função Gratificada (FG)</option>
</select>
<label class="label">
<span class="label-text-alt text-base-content/60">
Selecione se é um cargo comissionado ou função gratificada
</span>
</label>
</div>
{/snippet}
</form.Field>
{/if}
<form.Subscribe
selector={(state) => ({
canSubmit: state.canSubmit,
isSubmitting: state.isSubmitting,
})}
>
{#snippet children({ canSubmit, isSubmitting })}
<div class="card-actions justify-end pt-2">
<button
type="button"
class="btn btn-ghost"
disabled={isSubmitting}
onclick={() => goto("/recursos-humanos/simbolos")}
>
Cancelar
</button>
<button
type="submit"
class="btn btn-primary"
class:loading={isSubmitting}
disabled={isSubmitting || !canSubmit}
>
<span>Cadastrar Símbolo</span>
</button>
</div>
{/snippet}
</form.Subscribe>
</div>
</div>
</div>
</form>
<!-- Card de Valores -->
<div class="card bg-base-100 shadow-xl">
<div class="card-body space-y-6">
<h2 class="card-title text-xl border-b pb-3">
Valores Financeiros
<span class="badge badge-primary badge-lg ml-2">
{tipo === "cargo_comissionado" ? "Cargo Comissionado" : "Função Gratificada"}
</span>
</h2>
{#if tipo === "cargo_comissionado"}
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Valor de Vencimento -->
<form.Field
name="vencValor"
validators={{
onChange: ({ value }) =>
form.getFieldValue("tipo") === "cargo_comissionado" && !value
? "Obrigatório"
: undefined,
}}
>
{#snippet children({ name, state, handleChange })}
<div class="form-control">
<label class="label" for="vencValor">
<span class="label-text font-semibold">
Valor de Vencimento <span class="text-error">*</span>
</span>
</label>
<label class="input-group">
<span>R$</span>
<input
{name}
id="vencValor"
value={state.value}
placeholder="0,00"
class="input input-bordered w-full focus:input-primary"
inputmode="decimal"
autocomplete="off"
oninput={(e) => {
const target = e.target as HTMLInputElement;
const formatted = formatCurrencyBR(target.value);
target.value = formatted;
handleChange(formatted);
}}
required
/>
</label>
<label class="label">
<span class="label-text-alt text-base-content/60">
Valor base de vencimento
</span>
</label>
</div>
{/snippet}
</form.Field>
<!-- Valor de Referência -->
<form.Field
name="refValor"
validators={{
onChange: ({ value }) =>
form.getFieldValue("tipo") === "cargo_comissionado" && !value
? "Obrigatório"
: undefined,
}}
>
{#snippet children({ name, state, handleChange })}
<div class="form-control">
<label class="label" for="refValor">
<span class="label-text font-semibold">
Valor de Referência <span class="text-error">*</span>
</span>
</label>
<label class="input-group">
<span>R$</span>
<input
{name}
id="refValor"
value={state.value}
placeholder="0,00"
class="input input-bordered w-full focus:input-primary"
inputmode="decimal"
autocomplete="off"
oninput={(e) => {
const target = e.target as HTMLInputElement;
const formatted = formatCurrencyBR(target.value);
target.value = formatted;
handleChange(formatted);
}}
required
/>
</label>
<label class="label">
<span class="label-text-alt text-base-content/60">
Valor de referência do cargo
</span>
</label>
</div>
{/snippet}
</form.Field>
</div>
<!-- Preview do Total -->
{#if getTotalPreview()}
<div class="alert alert-info shadow-lg">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-current shrink-0 w-6 h-6">
<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"></path>
</svg>
<div>
<h3 class="font-bold">Valor Total Calculado</h3>
<div class="text-2xl font-bold mt-1">R$ {getTotalPreview()}</div>
</div>
</div>
{/if}
{:else}
<!-- Valor da Função Gratificada -->
<form.Field
name="valor"
validators={{
onChange: ({ value }) =>
form.getFieldValue("tipo") === "funcao_gratificada" && !value
? "Obrigatório"
: undefined,
}}
>
{#snippet children({ name, state, handleChange })}
<div class="form-control">
<label class="label" for="valor">
<span class="label-text font-semibold">
Valor da Função Gratificada <span class="text-error">*</span>
</span>
</label>
<label class="input-group">
<span>R$</span>
<input
{name}
id="valor"
value={state.value}
placeholder="0,00"
class="input input-bordered w-full focus:input-primary"
inputmode="decimal"
autocomplete="off"
oninput={(e) => {
const target = e.target as HTMLInputElement;
const formatted = formatCurrencyBR(target.value);
target.value = formatted;
handleChange(formatted);
}}
required
/>
</label>
<label class="label">
<span class="label-text-alt text-base-content/60">
Valor mensal da função gratificada
</span>
</label>
</div>
{/snippet}
</form.Field>
{/if}
</div>
</div>
<!-- Botões de Ação -->
<form.Subscribe
selector={(state) => ({
canSubmit: state.canSubmit,
isSubmitting: state.isSubmitting,
})}
>
{#snippet children({ canSubmit, isSubmitting })}
<div class="card bg-base-100 shadow-xl">
<div class="card-body">
<div class="flex flex-col sm:flex-row gap-3 justify-end">
<button
type="button"
class="btn btn-ghost btn-lg"
disabled={isSubmitting}
onclick={() => goto("/recursos-humanos/simbolos")}
>
<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="M6 18L18 6M6 6l12 12" />
</svg>
Cancelar
</button>
<button
type="submit"
class="btn btn-primary btn-lg"
disabled={isSubmitting || !canSubmit}
>
{#if isSubmitting}
<span class="loading loading-spinner"></span>
Cadastrando...
{: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="M5 13l4 4L19 7" />
</svg>
Cadastrar Símbolo
{/if}
</button>
</div>
</div>
</div>
{/snippet}
</form.Subscribe>
</form>
</main>
<style>
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
animation: fadeIn 0.3s ease-out;
}
</style>