- Changed all instances of "Sistema de Gerenciamento da Secretaria de Esportes" to "Sistema de Gerenciamento de Secretaria" for a more concise branding. - Enhanced the PrintModal component with a user-friendly interface for selecting sections to include in PDF generation. - Improved error handling and user feedback during PDF generation processes. - Updated various components and routes to reflect the new branding, ensuring consistency across the application.
892 lines
25 KiB
Svelte
892 lines
25 KiB
Svelte
<script lang="ts">
|
|
import { useQuery, useConvexClient } from 'convex-svelte';
|
|
import { api } from '@sgse-app/backend/convex/_generated/api';
|
|
import { toast } from 'svelte-sonner';
|
|
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
|
|
|
|
interface Props {
|
|
funcionarioId: Id<'funcionarios'>;
|
|
onSucesso?: () => void;
|
|
onCancelar?: () => void;
|
|
}
|
|
|
|
let { funcionarioId, onSucesso, onCancelar }: Props = $props();
|
|
|
|
// Cliente Convex
|
|
const client = useConvexClient();
|
|
|
|
// Estado do wizard
|
|
let passoAtual = $state(1);
|
|
const totalPassos = 3;
|
|
|
|
// Dados da solicitação
|
|
let anoSelecionado = $state(new Date().getFullYear());
|
|
let periodosFerias: Array<{
|
|
dataInicio: string;
|
|
dataFim: string;
|
|
dias: number;
|
|
}> = $state([]);
|
|
let observacao = $state('');
|
|
let processando = $state(false);
|
|
|
|
// Estados para os selects de data
|
|
let dataInicioPeriodo = $state('');
|
|
let dataFimPeriodo = $state('');
|
|
|
|
// Queries
|
|
const funcionarioQuery = useQuery(api.funcionarios.getById, { id: funcionarioId });
|
|
const funcionario = $derived(funcionarioQuery?.data);
|
|
const regimeTrabalho = $derived(funcionario?.regimeTrabalho || 'clt');
|
|
|
|
const saldoQuery = $derived(
|
|
useQuery(api.saldoFerias.obterSaldo, {
|
|
funcionarioId,
|
|
anoReferencia: anoSelecionado
|
|
})
|
|
);
|
|
|
|
const validacaoQuery = $derived(
|
|
periodosFerias.length > 0
|
|
? useQuery(api.saldoFerias.validarSolicitacao, {
|
|
funcionarioId,
|
|
anoReferencia: anoSelecionado,
|
|
periodos: periodosFerias.map((p) => ({
|
|
dataInicio: p.dataInicio,
|
|
dataFim: p.dataFim
|
|
}))
|
|
})
|
|
: { data: null }
|
|
);
|
|
|
|
// Derivados
|
|
const saldo = $derived(saldoQuery.data);
|
|
const validacao = $derived(validacaoQuery.data);
|
|
const totalDiasSelecionados = $derived(periodosFerias.reduce((acc, p) => acc + p.dias, 0));
|
|
|
|
// Anos disponíveis (últimos 3 anos + próximo ano)
|
|
const anosDisponiveis = $derived.by(() => {
|
|
const anoAtual = new Date().getFullYear();
|
|
return [anoAtual - 1, anoAtual, anoAtual + 1];
|
|
});
|
|
|
|
// Verificar se é regime estatutário PE ou Municipal
|
|
const ehEstatutarioPEOuMunicipal = $derived(
|
|
regimeTrabalho === 'estatutario_pe' || regimeTrabalho === 'estatutario_municipal'
|
|
);
|
|
|
|
// Função para calcular dias entre duas datas
|
|
function calcularDias(dataInicio: string, dataFim: string): number {
|
|
if (!dataInicio || !dataFim) return 0;
|
|
const inicio = new Date(dataInicio);
|
|
const fim = new Date(dataFim);
|
|
const diffTime = Math.abs(fim.getTime() - inicio.getTime());
|
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1;
|
|
return diffDays;
|
|
}
|
|
|
|
// Função para formatar data sem problemas de timezone
|
|
function formatarDataString(dataString: string): string {
|
|
if (!dataString) return '';
|
|
// Dividir a string da data (formato YYYY-MM-DD)
|
|
const partes = dataString.split('-');
|
|
if (partes.length !== 3) return dataString;
|
|
// Retornar no formato DD/MM/YYYY
|
|
return `${partes[2]}/${partes[1]}/${partes[0]}`;
|
|
}
|
|
|
|
// Função para adicionar período
|
|
function adicionarPeriodo() {
|
|
if (!dataInicioPeriodo || !dataFimPeriodo) {
|
|
toast.error('Selecione as datas de início e fim');
|
|
return;
|
|
}
|
|
|
|
const dias = calcularDias(dataInicioPeriodo, dataFimPeriodo);
|
|
|
|
if (dias <= 0) {
|
|
toast.error('Data de fim deve ser posterior à data de início');
|
|
return;
|
|
}
|
|
|
|
// Validações específicas para estatutário PE e Municipal
|
|
// Permite períodos fracionados: cada período deve ser 15 ou 30 dias
|
|
// Total não pode exceder 30 dias, mas pode ser menos
|
|
if (ehEstatutarioPEOuMunicipal) {
|
|
// Verificar se o período individual é válido (15 ou 30 dias)
|
|
if (dias !== 15 && dias !== 30) {
|
|
toast.error('Para seu regime, cada período deve ter exatamente 15 ou 30 dias');
|
|
return;
|
|
}
|
|
|
|
// Verificar se já tem 2 períodos
|
|
if (periodosFerias.length >= 2) {
|
|
toast.error('Máximo de 2 períodos permitidos para seu regime');
|
|
return;
|
|
}
|
|
|
|
// Verificar se o total não excede 30 dias
|
|
const novoTotal = totalDiasSelecionados + dias;
|
|
if (novoTotal > 30) {
|
|
toast.error(`O total não pode exceder 30 dias. Você já tem ${totalDiasSelecionados} dias, adicionando ${dias} dias totalizaria ${novoTotal} dias.`);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Verificar se o total não excede o saldo disponível
|
|
const novoTotal = totalDiasSelecionados + dias;
|
|
if (saldo && novoTotal > saldo.diasDisponiveis) {
|
|
toast.error(`Total de dias (${novoTotal}) excede saldo disponível (${saldo.diasDisponiveis})`);
|
|
return;
|
|
}
|
|
|
|
periodosFerias = [
|
|
...periodosFerias,
|
|
{
|
|
dataInicio: dataInicioPeriodo,
|
|
dataFim: dataFimPeriodo,
|
|
dias
|
|
}
|
|
];
|
|
|
|
toast.success(`Período de ${dias} dias adicionado! ✅`);
|
|
|
|
// Limpar campos
|
|
dataInicioPeriodo = '';
|
|
dataFimPeriodo = '';
|
|
}
|
|
|
|
// Função para remover período
|
|
function removerPeriodo(index: number) {
|
|
const removido = periodosFerias[index];
|
|
periodosFerias = periodosFerias.filter((_, i) => i !== index);
|
|
toast.info(`Período de ${removido.dias} dias removido`);
|
|
}
|
|
|
|
// Funções
|
|
function proximoPasso() {
|
|
if (passoAtual === 1 && !saldo) {
|
|
toast.error('Selecione um ano com saldo disponível');
|
|
return;
|
|
}
|
|
|
|
if (passoAtual === 2 && periodosFerias.length === 0) {
|
|
toast.error('Adicione pelo menos 1 período de férias');
|
|
return;
|
|
}
|
|
|
|
if (passoAtual === 2 && validacao && !validacao.valido) {
|
|
toast.error('Corrija os erros antes de continuar');
|
|
return;
|
|
}
|
|
|
|
if (passoAtual < totalPassos) {
|
|
passoAtual++;
|
|
}
|
|
}
|
|
|
|
function passoAnterior() {
|
|
if (passoAtual > 1) {
|
|
passoAtual--;
|
|
}
|
|
}
|
|
|
|
async function enviarSolicitacao() {
|
|
if (!validacao || !validacao.valido) {
|
|
toast.error('Valide os períodos antes de enviar');
|
|
return;
|
|
}
|
|
|
|
processando = true;
|
|
|
|
try {
|
|
await client.mutation(api.ferias.criarSolicitacao, {
|
|
funcionarioId,
|
|
anoReferencia: anoSelecionado,
|
|
periodos: periodosFerias.map((p) => ({
|
|
dataInicio: p.dataInicio,
|
|
dataFim: p.dataFim,
|
|
diasCorridos: p.dias
|
|
})),
|
|
observacao: observacao || undefined
|
|
});
|
|
|
|
toast.success('Solicitação de férias enviada com sucesso! 🎉');
|
|
if (onSucesso) onSucesso();
|
|
} catch (error: unknown) {
|
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
toast.error(errorMessage || 'Erro ao enviar solicitação');
|
|
} finally {
|
|
processando = false;
|
|
}
|
|
}
|
|
|
|
// Calcular dias do período atual
|
|
const diasPeriodoAtual = $derived(calcularDias(dataInicioPeriodo, dataFimPeriodo));
|
|
</script>
|
|
|
|
<div class="wizard-ferias-container">
|
|
<!-- Progress Bar -->
|
|
<div class="mb-8">
|
|
<div class="relative flex items-start">
|
|
{#each Array(totalPassos) as _, i (i)}
|
|
{@const labels = ['Ano & Saldo', 'Períodos', 'Confirmação']}
|
|
<div class="relative z-10 flex flex-1 flex-col items-center">
|
|
<!-- Círculo do passo -->
|
|
<div
|
|
class="relative z-20 flex h-12 w-12 items-center justify-center rounded-full font-bold transition-all duration-300"
|
|
class:bg-primary={passoAtual > i + 1}
|
|
class:text-white={passoAtual > i + 1}
|
|
class:border-4={passoAtual === i + 1}
|
|
class:border-primary={passoAtual === i + 1}
|
|
class:bg-base-200={passoAtual < i + 1}
|
|
class:text-base-content={passoAtual < i + 1}
|
|
style:box-shadow={passoAtual === i + 1 ? '0 0 20px rgba(102, 126, 234, 0.5)' : 'none'}
|
|
>
|
|
{#if passoAtual > i + 1}
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="3"
|
|
d="M5 13l4 4L19 7"
|
|
/>
|
|
</svg>
|
|
{:else}
|
|
{i + 1}
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- Label do passo -->
|
|
<p class="mt-3 text-center text-sm font-semibold" class:text-primary={passoAtual === i + 1}>
|
|
{labels[i]}
|
|
</p>
|
|
|
|
<!-- Linha conectora -->
|
|
{#if i < totalPassos - 1}
|
|
<div
|
|
class="absolute left-1/2 top-6 z-10 h-1 transition-all duration-300"
|
|
style="width: calc(100% - 1.5rem); margin-left: calc(50% + 0.75rem);"
|
|
class:bg-primary={passoAtual > i + 1}
|
|
class:bg-base-300={passoAtual <= i + 1}
|
|
></div>
|
|
{/if}
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Conteúdo dos Passos -->
|
|
<div class="wizard-content">
|
|
<!-- PASSO 1: Ano & Saldo -->
|
|
{#if passoAtual === 1}
|
|
<div class="passo-content animate-fadeIn">
|
|
<h2
|
|
class="from-primary to-secondary mb-6 bg-linear-to-r bg-clip-text text-center text-3xl font-bold text-transparent"
|
|
>
|
|
Escolha o Ano de Referência
|
|
</h2>
|
|
|
|
<!-- Seletor de Ano -->
|
|
<div class="mb-8 grid grid-cols-3 gap-4">
|
|
{#each anosDisponiveis as ano (ano)}
|
|
<button
|
|
type="button"
|
|
class="btn btn-lg transition-all duration-300 hover:scale-105"
|
|
class:btn-outline={anoSelecionado !== ano}
|
|
style:border-color={anoSelecionado === ano ? '#f97316' : undefined}
|
|
style:border-width={anoSelecionado === ano ? '2px' : undefined}
|
|
style:color={anoSelecionado === ano ? '#000000' : undefined}
|
|
style:background-color={anoSelecionado === ano ? 'transparent' : undefined}
|
|
style:box-shadow={anoSelecionado === ano ? '0 0 10px rgba(249, 115, 22, 0.3)' : undefined}
|
|
onclick={() => (anoSelecionado = ano)}
|
|
>
|
|
{ano}
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
|
|
<!-- Card de Saldo -->
|
|
{#if saldoQuery.isLoading}
|
|
<div class="skeleton h-64 w-full rounded-2xl"></div>
|
|
{:else if saldo}
|
|
<div
|
|
class="card from-primary/10 to-secondary/10 border-primary/20 border-2 bg-linear-to-br shadow-2xl"
|
|
>
|
|
<div class="card-body">
|
|
<h3 class="card-title mb-4 text-2xl">
|
|
📊 Saldo de Férias {anoSelecionado}
|
|
</h3>
|
|
|
|
<div class="stats stats-vertical lg:stats-horizontal w-full shadow-lg">
|
|
<div class="stat">
|
|
<div class="stat-figure text-primary">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
class="inline-block h-8 w-8 stroke-current"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M13 10V3L4 14h7v7l9-11h-7z"
|
|
></path>
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title">Total Direito</div>
|
|
<div class="stat-value text-primary">{saldo.diasDireito}</div>
|
|
<div class="stat-desc">dias no ano</div>
|
|
</div>
|
|
|
|
<div class="stat">
|
|
<div class="stat-figure text-success">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
class="inline-block h-8 w-8 stroke-current"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M5 13l4 4L19 7"
|
|
></path>
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title">Disponível</div>
|
|
<div class="stat-value text-success">
|
|
{saldo.diasDisponiveis}
|
|
</div>
|
|
<div class="stat-desc">para usar</div>
|
|
</div>
|
|
|
|
<div class="stat">
|
|
<div class="stat-figure text-warning">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
class="inline-block h-8 w-8 stroke-current"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
></path>
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title">Usado</div>
|
|
<div class="stat-value text-warning">{saldo.diasUsados}</div>
|
|
<div class="stat-desc">até agora</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Informações do Regime -->
|
|
<div class="alert alert-info mt-4">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
class="h-6 w-6 shrink-0 stroke-current"
|
|
>
|
|
<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>
|
|
<h4 class="font-bold">{saldo.regimeTrabalho}</h4>
|
|
<p class="text-sm">
|
|
Período aquisitivo: {formatarDataString(saldo.dataInicio)}
|
|
a {formatarDataString(saldo.dataFim)}
|
|
</p>
|
|
{#if ehEstatutarioPEOuMunicipal}
|
|
<p class="mt-2 text-sm font-semibold">
|
|
⚠️ Regras: Períodos de 15 ou 30 dias. Máximo 2 períodos. Total não pode exceder 30 dias.
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
{#if saldo.diasDisponiveis === 0}
|
|
<div class="alert alert-warning mt-4">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6 shrink-0 stroke-current"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
|
/>
|
|
</svg>
|
|
<span>Você não tem saldo disponível para este ano.</span>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{:else}
|
|
<div class="alert alert-warning">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6 shrink-0 stroke-current"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
|
/>
|
|
</svg>
|
|
<span>Nenhum saldo encontrado para este ano.</span>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- PASSO 2: Seleção de Períodos -->
|
|
{#if passoAtual === 2}
|
|
<div class="passo-content animate-fadeIn">
|
|
<h2
|
|
class="from-primary to-secondary mb-6 bg-linear-to-r bg-clip-text text-center text-3xl font-bold text-transparent"
|
|
>
|
|
Selecione os Períodos de Férias
|
|
</h2>
|
|
|
|
<!-- Resumo rápido -->
|
|
<div class="alert bg-base-200 mb-6">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
class="stroke-info h-6 w-6 shrink-0"
|
|
>
|
|
<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>
|
|
<p>
|
|
<strong>Saldo disponível:</strong>
|
|
{saldo?.diasDisponiveis || 0} dias |
|
|
<strong>Selecionados:</strong>
|
|
{totalDiasSelecionados} dias | <strong>Restante:</strong>
|
|
{(saldo?.diasDisponiveis || 0) - totalDiasSelecionados} dias
|
|
</p>
|
|
{#if ehEstatutarioPEOuMunicipal}
|
|
<p class="mt-2 text-sm font-semibold">
|
|
⚠️ Regras: Períodos de 15 ou 30 dias. Máximo 2 períodos. Total não pode exceder 30 dias.
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Formulário para adicionar período -->
|
|
<div class="card bg-base-100 shadow-lg mb-6">
|
|
<div class="card-body">
|
|
<h3 class="card-title mb-4">Adicionar Período</h3>
|
|
|
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text font-semibold">Data Início</span>
|
|
</label>
|
|
<input
|
|
type="date"
|
|
class="input input-bordered"
|
|
bind:value={dataInicioPeriodo}
|
|
/>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text font-semibold">Data Fim</span>
|
|
</label>
|
|
<input
|
|
type="date"
|
|
class="input input-bordered"
|
|
bind:value={dataFimPeriodo}
|
|
min={dataInicioPeriodo || undefined}
|
|
/>
|
|
</div>
|
|
|
|
<div class="form-control">
|
|
<label class="label">
|
|
<span class="label-text font-semibold">Dias</span>
|
|
</label>
|
|
<div class="input input-bordered flex items-center">
|
|
<span class="font-bold text-primary">{diasPeriodoAtual}</span>
|
|
<span class="ml-2 text-sm opacity-70">dias</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-actions mt-4">
|
|
<button
|
|
type="button"
|
|
class="btn btn-primary gap-2"
|
|
onclick={adicionarPeriodo}
|
|
disabled={!dataInicioPeriodo || !dataFimPeriodo || diasPeriodoAtual <= 0}
|
|
>
|
|
<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="M12 4v16m8-8H4"
|
|
/>
|
|
</svg>
|
|
Adicionar Período
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Lista de períodos adicionados -->
|
|
{#if periodosFerias.length > 0}
|
|
<div class="card bg-base-100 shadow-lg mb-6">
|
|
<div class="card-body">
|
|
<h3 class="card-title mb-4">Períodos Adicionados ({periodosFerias.length})</h3>
|
|
<div class="space-y-3">
|
|
{#each periodosFerias as periodo, index (index)}
|
|
<div class="bg-base-200 flex items-center gap-4 rounded-lg p-4">
|
|
<div
|
|
class="badge badge-lg badge-primary flex h-12 w-12 items-center justify-center font-bold text-white"
|
|
>
|
|
{index + 1}
|
|
</div>
|
|
<div class="flex-1">
|
|
<p class="font-semibold">
|
|
{formatarDataString(periodo.dataInicio)}
|
|
até
|
|
{formatarDataString(periodo.dataFim)}
|
|
</p>
|
|
<p class="text-base-content/70 text-sm">
|
|
{periodo.dias} dias corridos
|
|
</p>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
class="btn btn-error btn-sm gap-2"
|
|
onclick={() => removerPeriodo(index)}
|
|
>
|
|
<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>
|
|
Remover
|
|
</button>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Validações -->
|
|
{#if validacao && periodosFerias.length > 0}
|
|
<div class="mt-6">
|
|
{#if validacao.valido}
|
|
<div class="alert alert-success">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6 shrink-0 stroke-current"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<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>
|
|
<span>✅ Períodos válidos! Total: {validacao.totalDias} dias</span>
|
|
</div>
|
|
{:else}
|
|
<div class="alert alert-error">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6 shrink-0 stroke-current"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<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"
|
|
/>
|
|
</svg>
|
|
<div>
|
|
<p class="font-bold">Erros encontrados:</p>
|
|
<ul class="list-inside list-disc">
|
|
{#each validacao.erros as erro (erro)}
|
|
<li>{erro}</li>
|
|
{/each}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
{#if validacao.avisos.length > 0}
|
|
<div class="alert alert-warning mt-4">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6 shrink-0 stroke-current"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
|
/>
|
|
</svg>
|
|
<div>
|
|
<p class="font-bold">Avisos:</p>
|
|
<ul class="list-inside list-disc">
|
|
{#each validacao.avisos as aviso (aviso)}
|
|
<li>{aviso}</li>
|
|
{/each}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- PASSO 3: Confirmação -->
|
|
{#if passoAtual === 3}
|
|
<div class="passo-content animate-fadeIn">
|
|
<h2
|
|
class="from-primary to-secondary mb-6 bg-linear-to-r bg-clip-text text-center text-3xl font-bold text-transparent"
|
|
>
|
|
Confirme sua Solicitação
|
|
</h2>
|
|
|
|
<!-- Resumo Final -->
|
|
<div class="card bg-base-100 shadow-2xl">
|
|
<div class="card-body">
|
|
<h3 class="card-title mb-4 text-xl">📝 Resumo da Solicitação</h3>
|
|
|
|
<div class="mb-6 grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
<div class="stat bg-base-200 rounded-lg">
|
|
<div class="stat-title">Ano de Referência</div>
|
|
<div class="stat-value text-primary">{anoSelecionado}</div>
|
|
</div>
|
|
|
|
<div class="stat bg-base-200 rounded-lg">
|
|
<div class="stat-title">Total de Dias</div>
|
|
<div class="stat-value text-success">
|
|
{totalDiasSelecionados}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h4 class="mb-2 text-lg font-bold">Períodos Selecionados:</h4>
|
|
<div class="space-y-3">
|
|
{#each periodosFerias as periodo, index (index)}
|
|
<div class="bg-base-200 flex items-center gap-4 rounded-lg p-4">
|
|
<div
|
|
class="badge badge-lg badge-primary flex h-12 w-12 items-center justify-center font-bold text-white"
|
|
>
|
|
{index + 1}
|
|
</div>
|
|
<div class="flex-1">
|
|
<p class="font-semibold">
|
|
{formatarDataString(periodo.dataInicio)}
|
|
até
|
|
{formatarDataString(periodo.dataFim)}
|
|
</p>
|
|
<p class="text-base-content/70 text-sm">
|
|
{periodo.dias} dias corridos
|
|
</p>
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
|
|
<!-- Campo de Observação -->
|
|
<div class="form-control mt-6">
|
|
<label for="observacao" class="label">
|
|
<span class="label-text font-semibold">Observações (opcional)</span>
|
|
</label>
|
|
<textarea
|
|
id="observacao"
|
|
class="textarea textarea-bordered h-24"
|
|
placeholder="Adicione alguma observação ou justificativa..."
|
|
bind:value={observacao}
|
|
></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- Botões de Navegação -->
|
|
<div class="mt-8 flex justify-between">
|
|
<div>
|
|
{#if passoAtual > 1}
|
|
<button type="button" class="btn btn-outline btn-lg gap-2" onclick={passoAnterior}>
|
|
<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="M15 19l-7-7 7-7"
|
|
/>
|
|
</svg>
|
|
Voltar
|
|
</button>
|
|
{:else if onCancelar}
|
|
<button type="button" class="btn btn-lg" onclick={onCancelar}> Cancelar </button>
|
|
{/if}
|
|
</div>
|
|
|
|
<div>
|
|
{#if passoAtual < totalPassos}
|
|
<button
|
|
type="button"
|
|
class="btn btn-primary btn-lg gap-2"
|
|
onclick={proximoPasso}
|
|
disabled={passoAtual === 1 && (!saldo || saldo.diasDisponiveis === 0)}
|
|
>
|
|
Próximo
|
|
<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 5l7 7-7 7"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
{:else}
|
|
<button
|
|
type="button"
|
|
class="btn btn-success btn-lg gap-2"
|
|
onclick={enviarSolicitacao}
|
|
disabled={processando}
|
|
>
|
|
{#if processando}
|
|
<span class="loading loading-spinner"></span>
|
|
Enviando...
|
|
{: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>
|
|
Enviar Solicitação
|
|
{/if}
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
@keyframes fadeIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(20px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
.animate-fadeIn {
|
|
animation: fadeIn 0.5s ease-out;
|
|
}
|
|
|
|
.wizard-ferias-container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 2rem;
|
|
}
|
|
|
|
.passo-content {
|
|
min-height: 500px;
|
|
}
|
|
|
|
/* Gradiente no texto */
|
|
.bg-clip-text {
|
|
-webkit-background-clip: text;
|
|
background-clip: text;
|
|
}
|
|
|
|
/* Responsive */
|
|
@media (max-width: 768px) {
|
|
.wizard-ferias-container {
|
|
padding: 1rem;
|
|
}
|
|
|
|
.passo-content {
|
|
min-height: 400px;
|
|
}
|
|
}
|
|
</style>
|