- Refactored multiple Svelte components to enhance code clarity and maintainability. - Standardized formatting and indentation across various files for consistency. - Improved error handling messages in the AprovarAusencias component for better user feedback. - Updated class names in the UI components to align with the new design system. - Removed unnecessary whitespace and comments to streamline the codebase.
2576 lines
96 KiB
Svelte
2576 lines
96 KiB
Svelte
<script lang="ts">
|
|
import { useConvexClient, useQuery } from "convex-svelte";
|
|
import { api } from "@sgse-app/backend/convex/_generated/api";
|
|
import { authStore } from "$lib/stores/auth.svelte";
|
|
import AprovarFerias from "$lib/components/AprovarFerias.svelte";
|
|
import WizardSolicitacaoFerias from "$lib/components/ferias/WizardSolicitacaoFerias.svelte";
|
|
import WizardSolicitacaoAusencia from "$lib/components/ausencias/WizardSolicitacaoAusencia.svelte";
|
|
import AprovarAusencias from "$lib/components/AprovarAusencias.svelte";
|
|
import CalendarioAusencias from "$lib/components/ausencias/CalendarioAusencias.svelte";
|
|
import { generateAvatarGallery, type Avatar } from "$lib/utils/avatars";
|
|
import type { Id } from "@sgse-app/backend/convex/_generated/dataModel";
|
|
import { page } from "$app/stores";
|
|
import { X, Calendar } from "lucide-svelte";
|
|
|
|
const client = useConvexClient();
|
|
|
|
let abaAtiva = $state<
|
|
| "meu-perfil"
|
|
| "minhas-ferias"
|
|
| "minhas-ausencias"
|
|
| "aprovar-ferias"
|
|
| "aprovar-ausencias"
|
|
>("meu-perfil");
|
|
let solicitacaoSelecionada = $state<any>(null);
|
|
let mostrarModalFoto = $state(false);
|
|
let uploadandoFoto = $state(false);
|
|
let erroUpload = $state("");
|
|
let modoFoto = $state<"upload" | "avatar">("avatar");
|
|
let avatarSelecionado = $state<string>("");
|
|
let mostrarBotaoCamera = $state(false);
|
|
|
|
// Estados locais para atualização imediata
|
|
let fotoPerfilLocal = $state<string | null>(null);
|
|
let avatarLocal = $state<string | null>(null);
|
|
let perfilCarregado = $state(false);
|
|
|
|
// Estados para Minhas Férias
|
|
let mostrarWizard = $state(false);
|
|
let filtroStatusFerias = $state<string>("todos");
|
|
|
|
// Estados para Minhas Ausências
|
|
let mostrarWizardAusencia = $state(false);
|
|
let filtroStatusAusencias = $state<string>("todos");
|
|
let solicitacaoAusenciaSelecionada =
|
|
$state<Id<"solicitacoesAusencias"> | null>(null);
|
|
|
|
// Estados para Aprovar Ausências (Gestores)
|
|
let solicitacaoAusenciaAprovar = $state<Id<"solicitacoesAusencias"> | null>(
|
|
null,
|
|
);
|
|
|
|
// Galeria de avatares (30 avatares profissionais 3D realistas)
|
|
const avatarGallery = generateAvatarGallery(30);
|
|
|
|
// Carregar perfil ao montar a página para garantir dados atualizados (apenas uma vez)
|
|
$effect(() => {
|
|
if (authStore.autenticado && authStore.usuario && !perfilCarregado) {
|
|
perfilCarregado = true;
|
|
// Atualizar authStore com dados mais recentes do backend
|
|
authStore.refresh().catch((error) => {
|
|
console.error("Erro ao carregar perfil:", error);
|
|
perfilCarregado = false; // Permite tentar novamente em caso de erro
|
|
});
|
|
}
|
|
});
|
|
|
|
// Sincronizar com authStore - atualiza automaticamente quando o authStore muda
|
|
// Isso garante que a foto/avatar seja carregada imediatamente ao abrir a página
|
|
$effect(() => {
|
|
const usuario = authStore.usuario;
|
|
if (usuario) {
|
|
// Atualizar foto de perfil (pode ser null ou string)
|
|
fotoPerfilLocal = usuario.fotoPerfilUrl ?? null;
|
|
// Atualizar avatar (pode ser undefined ou string)
|
|
avatarLocal = usuario.avatar ?? null;
|
|
} else {
|
|
// Se não há usuário, limpar estados locais
|
|
fotoPerfilLocal = null;
|
|
avatarLocal = null;
|
|
perfilCarregado = false; // Reset para permitir recarregar quando houver usuário novamente
|
|
}
|
|
});
|
|
|
|
// FuncionarioId disponível diretamente do authStore
|
|
const funcionarioIdDisponivel = $derived(
|
|
authStore.usuario?.funcionarioId ?? null,
|
|
);
|
|
|
|
// Debug: Verificar funcionarioId
|
|
$effect(() => {
|
|
console.log("🔍 [Perfil] funcionarioId:", authStore.usuario?.funcionarioId);
|
|
console.log("🔍 [Perfil] Usuário completo:", authStore.usuario);
|
|
console.log(
|
|
"🔍 [Perfil] funcionarioIdDisponivel:",
|
|
funcionarioIdDisponivel,
|
|
);
|
|
console.log("🔍 [Perfil] Botão habilitado?", !!funcionarioIdDisponivel);
|
|
});
|
|
|
|
// Queries
|
|
const funcionarioQuery = $derived(
|
|
authStore.usuario?.funcionarioId
|
|
? useQuery(api.funcionarios.getById, {
|
|
id: authStore.usuario.funcionarioId,
|
|
})
|
|
: { data: null },
|
|
);
|
|
|
|
const solicitacoesSubordinadosQuery = $derived(
|
|
authStore.usuario?._id
|
|
? useQuery(api.ferias.listarSolicitacoesSubordinados, {
|
|
gestorId: authStore.usuario._id as Id<"usuarios">,
|
|
})
|
|
: { data: [] },
|
|
);
|
|
|
|
const ausenciasSubordinadosQuery = $derived(
|
|
authStore.usuario?._id
|
|
? useQuery(api.ausencias.listarSolicitacoesSubordinados, {
|
|
gestorId: authStore.usuario._id as Id<"usuarios">,
|
|
})
|
|
: { data: [] },
|
|
);
|
|
|
|
const minhasSolicitacoesQuery = $derived(
|
|
funcionarioQuery.data
|
|
? useQuery(api.ferias.listarMinhasSolicitacoes, {
|
|
funcionarioId: funcionarioQuery.data._id,
|
|
})
|
|
: { data: [] },
|
|
);
|
|
|
|
const minhasAusenciasQuery = $derived(
|
|
funcionarioQuery.data
|
|
? useQuery(api.ausencias.listarMinhasSolicitacoes, {
|
|
funcionarioId: funcionarioQuery.data._id,
|
|
})
|
|
: { data: [] },
|
|
);
|
|
|
|
const meuTimeQuery = $derived(
|
|
funcionarioQuery.data
|
|
? useQuery(api.times.obterTimeFuncionario, {
|
|
funcionarioId: funcionarioQuery.data._id,
|
|
})
|
|
: { data: null },
|
|
);
|
|
|
|
const meusTimesGestorQuery = $derived(
|
|
authStore.usuario?._id
|
|
? useQuery(api.times.listarPorGestor, {
|
|
gestorId: authStore.usuario._id as Id<"usuarios">,
|
|
})
|
|
: { data: [] },
|
|
);
|
|
|
|
const funcionario = $derived(funcionarioQuery.data);
|
|
const solicitacoesSubordinados = $derived(
|
|
solicitacoesSubordinadosQuery?.data || [],
|
|
);
|
|
const ausenciasSubordinados = $derived(
|
|
ausenciasSubordinadosQuery?.data || [],
|
|
);
|
|
const minhasSolicitacoes = $derived(minhasSolicitacoesQuery?.data || []);
|
|
const minhasAusencias = $derived(minhasAusenciasQuery?.data || []);
|
|
const meuTime = $derived(meuTimeQuery?.data);
|
|
const meusTimesGestor = $derived(meusTimesGestorQuery?.data || []);
|
|
|
|
// Verificar se é gestor
|
|
const ehGestor = $derived((meusTimesGestor || []).length > 0);
|
|
|
|
// Filtrar minhas solicitações
|
|
const solicitacoesFiltradas = $derived(
|
|
minhasSolicitacoes.filter((s) => {
|
|
if (filtroStatusFerias !== "todos" && s.status !== filtroStatusFerias)
|
|
return false;
|
|
return true;
|
|
}),
|
|
);
|
|
|
|
// Filtrar minhas ausências
|
|
const ausenciasFiltradas = $derived(
|
|
minhasAusencias.filter((a) => {
|
|
if (
|
|
filtroStatusAusencias !== "todos" &&
|
|
a.status !== filtroStatusAusencias
|
|
)
|
|
return false;
|
|
return true;
|
|
}),
|
|
);
|
|
|
|
// Formatar ausências para o calendário
|
|
const ausenciasParaCalendario = $derived(
|
|
minhasAusencias.map((a) => ({
|
|
dataInicio: a.dataInicio,
|
|
dataFim: a.dataFim,
|
|
status: a.status as "aguardando_aprovacao" | "aprovado" | "reprovado",
|
|
})),
|
|
);
|
|
|
|
// Estatísticas das minhas férias
|
|
const statsMinhasFerias = $derived({
|
|
total: minhasSolicitacoes.length,
|
|
aguardando: minhasSolicitacoes.filter(
|
|
(s) => s.status === "aguardando_aprovacao",
|
|
).length,
|
|
aprovadas: minhasSolicitacoes.filter(
|
|
(s) => s.status === "aprovado" || s.status === "data_ajustada_aprovada",
|
|
).length,
|
|
reprovadas: minhasSolicitacoes.filter((s) => s.status === "reprovado")
|
|
.length,
|
|
emFerias: funcionario?.statusFerias === "em_ferias" ? 1 : 0,
|
|
});
|
|
|
|
// Estatísticas das minhas ausências
|
|
const statsMinhasAusencias = $derived({
|
|
total: minhasAusencias.length,
|
|
aguardando: minhasAusencias.filter(
|
|
(a) => a.status === "aguardando_aprovacao",
|
|
).length,
|
|
aprovadas: minhasAusencias.filter((a) => a.status === "aprovado").length,
|
|
reprovadas: minhasAusencias.filter((a) => a.status === "reprovado").length,
|
|
});
|
|
|
|
async function recarregar() {
|
|
solicitacaoSelecionada = null;
|
|
}
|
|
|
|
async function selecionarSolicitacao(solicitacaoId: string) {
|
|
const detalhes = await client.query(api.ferias.obterDetalhes, {
|
|
solicitacaoId: solicitacaoId as Id<"solicitacoesFerias">,
|
|
});
|
|
solicitacaoSelecionada = detalhes;
|
|
}
|
|
|
|
function getStatusBadge(status: string) {
|
|
const badges: Record<string, string> = {
|
|
aguardando_aprovacao: "badge-warning",
|
|
aprovado: "badge-success",
|
|
reprovado: "badge-error",
|
|
data_ajustada_aprovada: "badge-info",
|
|
};
|
|
return badges[status] || "badge-neutral";
|
|
}
|
|
|
|
function getStatusTexto(status: string) {
|
|
const textos: Record<string, string> = {
|
|
aguardando_aprovacao: "Aguardando",
|
|
aprovado: "Aprovado",
|
|
reprovado: "Reprovado",
|
|
data_ajustada_aprovada: "Ajustado",
|
|
};
|
|
return textos[status] || status;
|
|
}
|
|
|
|
async function handleUploadFoto(event: Event) {
|
|
const input = event.target as HTMLInputElement;
|
|
const file = input.files?.[0];
|
|
|
|
if (!file) return;
|
|
|
|
// Validar tipo de arquivo
|
|
if (!file.type.startsWith("image/")) {
|
|
erroUpload = "Por favor, selecione uma imagem válida";
|
|
return;
|
|
}
|
|
|
|
// Validar tamanho (max 5MB)
|
|
if (file.size > 5 * 1024 * 1024) {
|
|
erroUpload = "A imagem deve ter no máximo 5MB";
|
|
return;
|
|
}
|
|
|
|
uploadandoFoto = true;
|
|
erroUpload = "";
|
|
|
|
try {
|
|
// 1. Criar preview local IMEDIATAMENTE para feedback visual
|
|
const reader = new FileReader();
|
|
reader.onload = (e) => {
|
|
fotoPerfilLocal = e.target?.result as string;
|
|
avatarLocal = null;
|
|
};
|
|
reader.readAsDataURL(file);
|
|
|
|
// 2. Gerar URL de upload
|
|
const uploadUrl = await client.mutation(
|
|
api.usuarios.uploadFotoPerfil,
|
|
{},
|
|
);
|
|
|
|
// 3. Upload do arquivo
|
|
const response = await fetch(uploadUrl, {
|
|
method: "POST",
|
|
headers: { "Content-Type": file.type },
|
|
body: file,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error("Falha no upload da imagem");
|
|
}
|
|
|
|
const { storageId } = await response.json();
|
|
|
|
// 4. Atualizar perfil com o novo storageId
|
|
await client.mutation(api.usuarios.atualizarPerfil, {
|
|
fotoPerfil: storageId,
|
|
avatar: undefined, // Remove avatar se colocar foto
|
|
});
|
|
|
|
// 5. Aguardar um pouco para garantir que o backend processou
|
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
|
|
// 6. Atualizar authStore para obter a URL da foto atualizada
|
|
await authStore.refresh();
|
|
|
|
// 7. Atualizar localmente com a URL do authStore (substitui o preview temporário)
|
|
if (authStore.usuario?.fotoPerfilUrl) {
|
|
fotoPerfilLocal = authStore.usuario.fotoPerfilUrl;
|
|
avatarLocal = null;
|
|
}
|
|
|
|
// 8. Limpar o input para permitir novo upload
|
|
input.value = "";
|
|
|
|
// 9. Fechar modal após sucesso
|
|
mostrarModalFoto = false;
|
|
|
|
// Toast de sucesso
|
|
const toast = document.createElement("div");
|
|
toast.className = "toast toast-top toast-end";
|
|
toast.innerHTML = `
|
|
<div class="alert alert-success">
|
|
<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="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
<span>Foto de perfil atualizada com sucesso!</span>
|
|
</div>
|
|
`;
|
|
document.body.appendChild(toast);
|
|
setTimeout(() => toast.remove(), 3000);
|
|
} catch (e: any) {
|
|
erroUpload = e.message || "Erro ao fazer upload da foto";
|
|
// Reverter mudança local se houver erro
|
|
fotoPerfilLocal = authStore.usuario?.fotoPerfilUrl || null;
|
|
avatarLocal = authStore.usuario?.avatar || null;
|
|
} finally {
|
|
uploadandoFoto = false;
|
|
}
|
|
}
|
|
|
|
async function handleSelecionarAvatar(avatarUrl: string) {
|
|
uploadandoFoto = true;
|
|
erroUpload = "";
|
|
|
|
try {
|
|
// 1. Atualizar localmente IMEDIATAMENTE para feedback visual instantâneo
|
|
avatarLocal = avatarUrl;
|
|
fotoPerfilLocal = null;
|
|
|
|
// 2. Salvar avatar selecionado no backend
|
|
await client.mutation(api.usuarios.atualizarPerfil, {
|
|
avatar: avatarUrl,
|
|
fotoPerfil: undefined, // Remove foto se colocar avatar
|
|
});
|
|
|
|
// 3. Aguardar um pouco para garantir que o backend processou
|
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
|
|
// 4. Atualizar authStore e aguardar conclusão
|
|
await authStore.refresh();
|
|
|
|
// 5. Garantir que os estados locais estão sincronizados com o authStore
|
|
if (authStore.usuario?.avatar) {
|
|
avatarLocal = authStore.usuario.avatar;
|
|
fotoPerfilLocal = null;
|
|
}
|
|
|
|
// 6. Fechar modal após sucesso
|
|
mostrarModalFoto = false;
|
|
|
|
// Toast de sucesso
|
|
const toast = document.createElement("div");
|
|
toast.className = "toast toast-top toast-end";
|
|
toast.innerHTML = `
|
|
<div class="alert alert-success">
|
|
<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="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
<span>Avatar atualizado com sucesso!</span>
|
|
</div>
|
|
`;
|
|
document.body.appendChild(toast);
|
|
setTimeout(() => toast.remove(), 3000);
|
|
} catch (e: any) {
|
|
erroUpload = e.message || "Erro ao salvar avatar";
|
|
// Reverter mudança local se houver erro
|
|
avatarLocal = authStore.usuario?.avatar || null;
|
|
fotoPerfilLocal = authStore.usuario?.fotoPerfilUrl || null;
|
|
} finally {
|
|
uploadandoFoto = false;
|
|
}
|
|
}
|
|
|
|
function abrirModalFoto() {
|
|
erroUpload = "";
|
|
modoFoto = "avatar";
|
|
avatarSelecionado = "";
|
|
mostrarModalFoto = true;
|
|
}
|
|
</script>
|
|
|
|
<main class="min-h-screen pb-12">
|
|
<!-- BANNER HERO PREMIUM -->
|
|
<div class="relative overflow-hidden mb-8">
|
|
<!-- Background com gradiente animado -->
|
|
<div
|
|
class="absolute inset-0 bg-linear-to-br from-purple-600 via-blue-600 to-indigo-700 animate-gradient"
|
|
></div>
|
|
|
|
<!-- Overlay pattern -->
|
|
<div
|
|
class="absolute inset-0 opacity-10"
|
|
style="background-image: url('data:image/svg+xml,%3Csvg width="60" height="60" viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg"%3E%3Cg fill="none" fill-rule="evenodd"%3E%3Cg fill="%23ffffff" fill-opacity="1"%3E%3Cpath d="M36 34v-4h-2v4h-4v2h4v4h2v-4h4v-2h-4zm0-30V0h-2v4h-4v2h4v4h2V6h4V4h-4zM6 34v-4H4v4H0v2h4v4h2v-4h4v-2H6zM6 4V0H4v4H0v2h4v4h2V6h4V4H6z"/%3E%3C/g%3E%3C/g%3E%3C/svg%3E');"
|
|
></div>
|
|
|
|
<!-- Conteúdo do Banner -->
|
|
<div class="relative container mx-auto px-6 py-16">
|
|
<div class="flex flex-col md:flex-row items-center gap-8">
|
|
<!-- Avatar PREMIUM -->
|
|
<div
|
|
class="relative group"
|
|
role="button"
|
|
tabindex="0"
|
|
onmouseenter={() => (mostrarBotaoCamera = true)}
|
|
onmouseleave={() => (mostrarBotaoCamera = false)}
|
|
>
|
|
<button
|
|
type="button"
|
|
class="avatar cursor-pointer p-0 border-0 bg-transparent"
|
|
onclick={abrirModalFoto}
|
|
>
|
|
<div
|
|
class="w-40 h-40 rounded-full ring-4 ring-white ring-offset-4 ring-offset-transparent shadow-2xl transition-all duration-300 hover:scale-105 hover:ring-8 animate-float"
|
|
>
|
|
{#if fotoPerfilLocal}
|
|
<img
|
|
src={fotoPerfilLocal}
|
|
alt="Foto de perfil"
|
|
class="object-cover"
|
|
/>
|
|
{:else if avatarLocal}
|
|
<img src={avatarLocal} alt="Avatar" class="object-cover" />
|
|
{:else}
|
|
<div
|
|
class="bg-white text-purple-700 flex items-center justify-center"
|
|
>
|
|
<span class="text-5xl font-black"
|
|
>{authStore.usuario?.nome
|
|
.substring(0, 2)
|
|
.toUpperCase()}</span
|
|
>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</button>
|
|
|
|
<!-- Botão de editar MODERNO -->
|
|
<button
|
|
type="button"
|
|
class={`absolute bottom-2 right-2 flex items-center justify-center w-12 h-12 rounded-full bg-white text-purple-600 shadow-2xl transition-all duration-300 hover:scale-110 ${mostrarBotaoCamera ? "opacity-100 scale-100" : "opacity-0 scale-50"}`}
|
|
onclick={abrirModalFoto}
|
|
aria-label="Editar foto de perfil"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
stroke-width="2.5"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"
|
|
/>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Informações do Usuário PREMIUM -->
|
|
<div class="flex-1 text-white text-center md:text-left">
|
|
<h1 class="text-5xl font-black mb-3 drop-shadow-lg">
|
|
{authStore.usuario?.nome}
|
|
</h1>
|
|
|
|
{#if funcionario?.descricaoCargo}
|
|
<p
|
|
class="text-2xl font-semibold text-white/90 mb-3 flex items-center justify-center md:justify-start gap-2"
|
|
>
|
|
<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="2"
|
|
d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
|
/>
|
|
</svg>
|
|
{funcionario.descricaoCargo}
|
|
</p>
|
|
{/if}
|
|
|
|
<p
|
|
class="text-lg text-white/80 mb-4 flex items-center justify-center md:justify-start gap-2"
|
|
>
|
|
<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 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
|
/>
|
|
</svg>
|
|
{authStore.usuario?.email}
|
|
</p>
|
|
|
|
<div
|
|
class="flex items-center gap-3 flex-wrap justify-center md:justify-start"
|
|
>
|
|
<div
|
|
class="badge badge-lg bg-white/90 text-purple-700 border-0 font-bold shadow-lg px-4"
|
|
>
|
|
{authStore.usuario?.role?.nome || "Usuário"}
|
|
</div>
|
|
|
|
{#if meuTime}
|
|
<div
|
|
class="badge badge-lg bg-white/80 border-0 font-semibold shadow-lg px-4"
|
|
style="color: {meuTime.cor}"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-4 w-4 mr-2"
|
|
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-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
|
|
/>
|
|
</svg>
|
|
{meuTime.nome}
|
|
</div>
|
|
{/if}
|
|
|
|
{#if funcionario?.statusFerias === "em_ferias"}
|
|
<div
|
|
class="badge badge-lg bg-yellow-400 text-yellow-900 border-0 font-bold shadow-lg px-4 animate-pulse"
|
|
>
|
|
🏖️ Em Férias
|
|
</div>
|
|
{:else}
|
|
<div
|
|
class="badge badge-lg bg-green-400 text-green-900 border-0 font-bold shadow-lg px-4"
|
|
>
|
|
✅ Ativo
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="container mx-auto px-6 max-w-7xl">
|
|
<!-- Tabs PREMIUM -->
|
|
<div
|
|
role="tablist"
|
|
class="tabs tabs-boxed mb-8 bg-linear-to-r from-base-200 to-base-300 shadow-xl p-2"
|
|
>
|
|
<button
|
|
type="button"
|
|
role="tab"
|
|
class={`tab tab-lg font-semibold transition-all duration-300 ${abaAtiva === "meu-perfil" ? "tab-active bg-linear-to-r from-purple-600 to-blue-600 text-white shadow-lg scale-105" : "hover:bg-base-100"}`}
|
|
onclick={() => (abaAtiva = "meu-perfil")}
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-5 w-5 mr-2"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
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>
|
|
Meu Perfil
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
role="tab"
|
|
class={`tab tab-lg font-semibold transition-all duration-300 ${abaAtiva === "minhas-ferias" ? "tab-active bg-linear-to-r from-purple-600 to-blue-600 text-white shadow-lg scale-105" : "hover:bg-base-100"}`}
|
|
onclick={() => (abaAtiva = "minhas-ferias")}
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-5 w-5 mr-2"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
|
/>
|
|
</svg>
|
|
Minhas Férias
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
role="tab"
|
|
class={`tab tab-lg font-semibold transition-all duration-300 ${abaAtiva === "minhas-ausencias" ? "tab-active bg-linear-to-r from-orange-600 to-amber-600 text-white shadow-lg scale-105" : "hover:bg-base-100"}`}
|
|
onclick={() => (abaAtiva = "minhas-ausencias")}
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-5 w-5 mr-2"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
Minhas Ausências
|
|
</button>
|
|
|
|
{#if ehGestor}
|
|
<button
|
|
type="button"
|
|
role="tab"
|
|
class={`tab tab-lg font-semibold transition-all duration-300 ${abaAtiva === "aprovar-ferias" ? "tab-active bg-linear-to-r from-purple-600 to-blue-600 text-white shadow-lg scale-105" : "hover:bg-base-100"}`}
|
|
onclick={() => (abaAtiva = "aprovar-ferias")}
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-5 w-5 mr-2"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
Aprovar Férias
|
|
{#if (solicitacoesSubordinados || []).filter((s: any) => s.status === "aguardando_aprovacao").length > 0}
|
|
<span class="badge badge-error badge-sm ml-2 animate-pulse">
|
|
{(solicitacoesSubordinados || []).filter(
|
|
(s: any) => s.status === "aguardando_aprovacao",
|
|
).length}
|
|
</span>
|
|
{/if}
|
|
</button>
|
|
|
|
<button
|
|
type="button"
|
|
role="tab"
|
|
class={`tab tab-lg font-semibold transition-all duration-300 ${abaAtiva === "aprovar-ausencias" ? "tab-active bg-linear-to-r from-orange-600 to-amber-600 text-white shadow-lg scale-105" : "hover:bg-base-100"}`}
|
|
onclick={() => (abaAtiva = "aprovar-ausencias")}
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-5 w-5 mr-2"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
stroke-width="2"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
Aprovar Ausências
|
|
{#if (ausenciasSubordinados || []).filter((a: any) => a.status === "aguardando_aprovacao").length > 0}
|
|
<span class="badge badge-error badge-sm ml-2 animate-pulse">
|
|
{(ausenciasSubordinados || []).filter(
|
|
(a: any) => a.status === "aguardando_aprovacao",
|
|
).length}
|
|
</span>
|
|
{/if}
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- Conteúdo das Abas -->
|
|
{#if abaAtiva === "meu-perfil"}
|
|
<!-- Meu Perfil PREMIUM -->
|
|
<div class="space-y-6">
|
|
<!-- STATS CARDS -->
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-6">
|
|
<!-- Stat 1: Perfil -->
|
|
<div
|
|
class="card bg-linear-to-br from-purple-500 to-purple-700 text-white shadow-2xl hover:scale-105 transition-transform"
|
|
>
|
|
<div class="card-body">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-white/80 text-sm font-medium">Seu Perfil</p>
|
|
<p class="text-2xl font-black">
|
|
{authStore.usuario?.role?.nome || "Usuário"}
|
|
</p>
|
|
</div>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-12 w-12 opacity-80"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M5.121 17.804A13.937 13.937 0 0112 16c2.5 0 4.847.655 6.879 1.804M15 10a3 3 0 11-6 0 3 3 0 016 0zm6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stat 2: Time -->
|
|
<div
|
|
class="card bg-linear-to-br from-blue-500 to-blue-700 text-white shadow-2xl hover:scale-105 transition-transform"
|
|
>
|
|
<div class="card-body">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-white/80 text-sm font-medium">Seu Time</p>
|
|
<p class="text-2xl font-black truncate">
|
|
{meuTime?.nome || "Sem time"}
|
|
</p>
|
|
</div>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-12 w-12 opacity-80"
|
|
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-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stat 3: Status -->
|
|
<div
|
|
class="card bg-linear-to-br from-green-500 to-green-700 text-white shadow-2xl hover:scale-105 transition-transform"
|
|
>
|
|
<div class="card-body">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-white/80 text-sm font-medium">Status</p>
|
|
<p class="text-2xl font-black">
|
|
{funcionario?.statusFerias === "em_ferias"
|
|
? "Em Férias"
|
|
: "Ativo"}
|
|
</p>
|
|
</div>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-12 w-12 opacity-80"
|
|
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>
|
|
</div>
|
|
|
|
<!-- Stat 4: Matrícula -->
|
|
<div
|
|
class="card bg-linear-to-br from-indigo-500 to-indigo-700 text-white shadow-2xl hover:scale-105 transition-transform"
|
|
>
|
|
<div class="card-body">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<p class="text-white/80 text-sm font-medium">Matrícula</p>
|
|
<p class="text-2xl font-black">
|
|
{funcionario?.matricula || "---"}
|
|
</p>
|
|
</div>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-12 w-12 opacity-80"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M10 6H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V8a2 2 0 00-2-2h-5m-4 0V5a2 2 0 114 0v1m-4 0a2 2 0 104 0m-5 8a2 2 0 100-4 2 2 0 000 4zm0 0c1.306 0 2.417.835 2.83 2M9 14a3.001 3.001 0 00-2.83 2M15 11h3m-3 4h2"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- CARDS PRINCIPAIS -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
<!-- Informações Pessoais PREMIUM -->
|
|
<div
|
|
class="card bg-base-100 shadow-2xl hover:shadow-3xl transition-shadow border-t-4 border-purple-500"
|
|
>
|
|
<div class="card-body">
|
|
<h2 class="card-title text-2xl mb-6 flex items-center gap-2">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-7 w-7 text-purple-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>
|
|
Informações Pessoais
|
|
</h2>
|
|
<div class="space-y-4">
|
|
<div
|
|
class="flex items-start gap-3 p-3 rounded-lg hover:bg-base-200 transition-colors"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6 text-primary mt-1"
|
|
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 class="flex-1">
|
|
<span
|
|
class="label-text font-bold text-base-content/70 text-sm"
|
|
>Nome Completo</span
|
|
>
|
|
<p class="text-base-content font-semibold text-lg">
|
|
{authStore.usuario?.nome}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="divider my-1"></div>
|
|
|
|
<div
|
|
class="flex items-start gap-3 p-3 rounded-lg hover:bg-base-200 transition-colors"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6 text-primary mt-1"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
|
/>
|
|
</svg>
|
|
<div class="flex-1">
|
|
<span
|
|
class="label-text font-bold text-base-content/70 text-sm"
|
|
>E-mail Institucional</span
|
|
>
|
|
<p
|
|
class="text-base-content font-semibold text-lg break-all"
|
|
>
|
|
{authStore.usuario?.email}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="divider my-1"></div>
|
|
|
|
<div
|
|
class="flex items-start gap-3 p-3 rounded-lg hover:bg-base-200 transition-colors"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6 text-primary mt-1"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"
|
|
/>
|
|
</svg>
|
|
<div class="flex-1">
|
|
<span
|
|
class="label-text font-bold text-base-content/70 text-sm"
|
|
>Perfil de Acesso</span
|
|
>
|
|
<div class="badge badge-primary badge-lg mt-1 font-bold">
|
|
{authStore.usuario?.role?.nome || "Usuário"}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Dados Funcionais PREMIUM -->
|
|
{#if funcionario}
|
|
<div
|
|
class="card bg-base-100 shadow-2xl hover:shadow-3xl transition-shadow border-t-4 border-blue-500"
|
|
>
|
|
<div class="card-body">
|
|
<h2 class="card-title text-2xl mb-6 flex items-center gap-2">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-7 w-7 text-blue-600"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
|
/>
|
|
</svg>
|
|
Dados Funcionais
|
|
</h2>
|
|
<div class="space-y-4">
|
|
<div
|
|
class="flex items-start gap-3 p-3 rounded-lg hover:bg-base-200 transition-colors"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6 text-primary mt-1"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M10 6H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V8a2 2 0 00-2-2h-5m-4 0V5a2 2 0 114 0v1m-4 0a2 2 0 104 0m-5 8a2 2 0 100-4 2 2 0 000 4zm0 0c1.306 0 2.417.835 2.83 2M9 14a3.001 3.001 0 00-2.83 2M15 11h3m-3 4h2"
|
|
/>
|
|
</svg>
|
|
<div class="flex-1">
|
|
<span
|
|
class="label-text font-bold text-base-content/70 text-sm"
|
|
>Matrícula</span
|
|
>
|
|
<p class="text-base-content font-semibold text-lg">
|
|
{funcionario.matricula || "Não informada"}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="divider my-1"></div>
|
|
|
|
<div
|
|
class="flex items-start gap-3 p-3 rounded-lg hover:bg-base-200 transition-colors"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6 text-primary mt-1"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01"
|
|
/>
|
|
</svg>
|
|
<div class="flex-1">
|
|
<span
|
|
class="label-text font-bold text-base-content/70 text-sm"
|
|
>CPF</span
|
|
>
|
|
<p class="text-base-content font-semibold text-lg">
|
|
{funcionario.cpf}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="divider my-1"></div>
|
|
|
|
<div
|
|
class="flex items-start gap-3 p-3 rounded-lg hover:bg-base-200 transition-colors"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6 text-primary mt-1"
|
|
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-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
|
|
/>
|
|
</svg>
|
|
<div class="flex-1">
|
|
<span
|
|
class="label-text font-bold text-base-content/70 text-sm"
|
|
>Time</span
|
|
>
|
|
{#if meuTime}
|
|
<div class="flex items-center gap-2 mt-1">
|
|
<div
|
|
class="badge badge-lg font-semibold"
|
|
style="background-color: {meuTime.cor}20; border-color: {meuTime.cor}; color: {meuTime.cor}"
|
|
>
|
|
{meuTime.nome}
|
|
</div>
|
|
</div>
|
|
<p class="text-xs text-base-content/60 mt-1">
|
|
Gestor: <span class="font-semibold"
|
|
>{meuTime.gestor?.nome}</span
|
|
>
|
|
</p>
|
|
{:else}
|
|
<p class="text-base-content/50 text-sm mt-1">
|
|
Não atribuído a um time
|
|
</p>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="divider my-1"></div>
|
|
|
|
<div
|
|
class="flex items-start gap-3 p-3 rounded-lg hover:bg-base-200 transition-colors"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6 text-primary mt-1"
|
|
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 class="flex-1">
|
|
<span
|
|
class="label-text font-bold text-base-content/70 text-sm"
|
|
>Status Atual</span
|
|
>
|
|
{#if funcionario.statusFerias === "em_ferias"}
|
|
<div
|
|
class="badge badge-warning badge-lg mt-1 font-bold"
|
|
>
|
|
🏖️ Em Férias
|
|
</div>
|
|
{:else}
|
|
<div
|
|
class="badge badge-success badge-lg mt-1 font-bold"
|
|
>
|
|
✅ Ativo
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- Times Gerenciados PREMIUM -->
|
|
{#if ehGestor}
|
|
<div
|
|
class="card bg-linear-to-br from-amber-50 to-orange-50 dark:from-amber-950 dark:to-orange-950 shadow-2xl border-t-4 border-amber-500"
|
|
>
|
|
<div class="card-body">
|
|
<h2
|
|
class="card-title text-2xl mb-6 flex items-center gap-2 text-amber-700 dark:text-amber-400"
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-7 w-7"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"
|
|
/>
|
|
</svg>
|
|
Times que Você Gerencia
|
|
<div class="badge badge-warning badge-lg ml-2">
|
|
{meusTimesGestor.length}
|
|
</div>
|
|
</h2>
|
|
|
|
{#if meusTimesGestor.length === 0}
|
|
<div class="alert alert-info">
|
|
<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>
|
|
<span>Você não gerencia nenhum time no momento.</span>
|
|
</div>
|
|
{:else}
|
|
<div
|
|
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
|
|
>
|
|
{#each meusTimesGestor as time}
|
|
<div
|
|
class="card bg-white dark:bg-base-100 shadow-xl hover:shadow-2xl hover:scale-105 transition-all border-l-4"
|
|
style="border-color: {time.cor}"
|
|
>
|
|
<div class="card-body">
|
|
<div class="flex items-start gap-2">
|
|
<div class="flex-1">
|
|
<h3
|
|
class="font-black text-xl mb-2"
|
|
style="color: {time.cor}"
|
|
>
|
|
{time.nome}
|
|
</h3>
|
|
<p
|
|
class="text-sm text-base-content/70 mb-4 line-clamp-2"
|
|
>
|
|
{time.descricao || "Sem descrição"}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="divider my-2"></div>
|
|
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center gap-2">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-5 w-5 text-primary"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"
|
|
/>
|
|
</svg>
|
|
<span class="font-bold text-lg"
|
|
>{time.membros?.length || 0}</span
|
|
>
|
|
<span class="text-sm text-base-content/70"
|
|
>membros</span
|
|
>
|
|
</div>
|
|
<div
|
|
class="badge badge-lg font-semibold"
|
|
style="background-color: {time.cor}20; border-color: {time.cor}; color: {time.cor}"
|
|
>
|
|
Gestor
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{:else if abaAtiva === "minhas-ferias"}
|
|
<!-- Minhas Férias -->
|
|
<div class="space-y-6">
|
|
<!-- Estatísticas -->
|
|
<div class="grid grid-cols-1 md:grid-cols-5 gap-4">
|
|
<div
|
|
class="stat bg-base-100 shadow-lg rounded-box border border-base-300"
|
|
>
|
|
<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</div>
|
|
<div class="stat-value text-primary">{statsMinhasFerias.total}</div>
|
|
<div class="stat-desc">Solicitações</div>
|
|
</div>
|
|
|
|
<div
|
|
class="stat bg-base-100 shadow-lg rounded-box border border-warning/30"
|
|
>
|
|
<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 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title">Aguardando</div>
|
|
<div class="stat-value text-warning">
|
|
{statsMinhasFerias.aguardando}
|
|
</div>
|
|
<div class="stat-desc">Pendentes</div>
|
|
</div>
|
|
|
|
<div
|
|
class="stat bg-base-100 shadow-lg rounded-box border border-success/30"
|
|
>
|
|
<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">Aprovadas</div>
|
|
<div class="stat-value text-success">
|
|
{statsMinhasFerias.aprovadas}
|
|
</div>
|
|
<div class="stat-desc">Deferidas</div>
|
|
</div>
|
|
|
|
<div
|
|
class="stat bg-base-100 shadow-lg rounded-box border border-error/30"
|
|
>
|
|
<div class="stat-figure text-error">
|
|
<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="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title">Reprovadas</div>
|
|
<div class="stat-value text-error">
|
|
{statsMinhasFerias.reprovadas}
|
|
</div>
|
|
<div class="stat-desc">Indeferidas</div>
|
|
</div>
|
|
|
|
<div
|
|
class="stat bg-linear-to-br from-purple-500/10 to-purple-600/20 shadow-lg rounded-box border-2 border-purple-500/30"
|
|
>
|
|
<div class="stat-figure text-purple-600">
|
|
<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="M14.828 14.828a4 4 0 01-5.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title">Em Férias</div>
|
|
<div class="stat-value text-purple-600">
|
|
{statsMinhasFerias.emFerias}
|
|
</div>
|
|
<div class="stat-desc">Agora</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filtros e Botão Nova Solicitação -->
|
|
<div class="card bg-base-100 shadow-lg">
|
|
<div class="card-body">
|
|
<div
|
|
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-4"
|
|
>
|
|
<h2 class="card-title text-lg">Filtros</h2>
|
|
{#if funcionario}
|
|
<button
|
|
type="button"
|
|
class="btn btn-primary gap-2"
|
|
onclick={() => (mostrarWizard = 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="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
|
/>
|
|
</svg>
|
|
Agendar Férias
|
|
</button>
|
|
{/if}
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-1 gap-4 mt-4">
|
|
<div class="form-control">
|
|
<label class="label" for="status">
|
|
<span class="label-text">Status</span>
|
|
</label>
|
|
<select
|
|
id="status"
|
|
class="select select-bordered"
|
|
bind:value={filtroStatusFerias}
|
|
>
|
|
<option value="todos">Todos</option>
|
|
<option value="aguardando_aprovacao"
|
|
>Aguardando Aprovação</option
|
|
>
|
|
<option value="aprovado">Aprovado</option>
|
|
<option value="reprovado">Reprovado</option>
|
|
<option value="data_ajustada_aprovada">Data Ajustada</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Lista de Solicitações -->
|
|
<div class="card bg-base-100 shadow-lg">
|
|
<div class="card-body">
|
|
<h2 class="card-title text-lg mb-4">
|
|
Minhas Solicitações ({solicitacoesFiltradas.length})
|
|
</h2>
|
|
|
|
{#if solicitacoesFiltradas.length === 0}
|
|
<div class="alert">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
class="stroke-info 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>
|
|
<span
|
|
>Nenhuma solicitação encontrada com os filtros aplicados.</span
|
|
>
|
|
</div>
|
|
{:else}
|
|
<div class="overflow-x-auto">
|
|
<table class="table table-zebra">
|
|
<thead>
|
|
<tr>
|
|
<th>Ano</th>
|
|
<th>Períodos</th>
|
|
<th>Total Dias</th>
|
|
<th>Status</th>
|
|
<th>Solicitado em</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{#each solicitacoesFiltradas as solicitacao}
|
|
<tr>
|
|
<td>{solicitacao.anoReferencia}</td>
|
|
<td>{solicitacao.periodos.length} período(s)</td>
|
|
<td class="font-bold"
|
|
>{solicitacao.periodos.reduce(
|
|
(acc: number, p: any) => acc + p.diasCorridos,
|
|
0,
|
|
)} dias</td
|
|
>
|
|
<td>
|
|
<div
|
|
class={`badge ${getStatusBadge(solicitacao.status)}`}
|
|
>
|
|
{getStatusTexto(solicitacao.status)}
|
|
</div>
|
|
</td>
|
|
<td class="text-xs"
|
|
>{new Date(
|
|
solicitacao._creationTime,
|
|
).toLocaleDateString("pt-BR")}</td
|
|
>
|
|
</tr>
|
|
{/each}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{:else if abaAtiva === "minhas-ausencias"}
|
|
<!-- Minhas Ausências -->
|
|
<div class="space-y-6">
|
|
<!-- Estatísticas -->
|
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
|
<div
|
|
class="stat bg-base-100 shadow-lg rounded-box border border-base-300"
|
|
>
|
|
<div class="stat-figure text-orange-500">
|
|
<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</div>
|
|
<div class="stat-value text-orange-500">
|
|
{statsMinhasAusencias.total}
|
|
</div>
|
|
<div class="stat-desc">Solicitações</div>
|
|
</div>
|
|
|
|
<div
|
|
class="stat bg-base-100 shadow-lg rounded-box border border-warning/30"
|
|
>
|
|
<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 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title">Aguardando</div>
|
|
<div class="stat-value text-warning">
|
|
{statsMinhasAusencias.aguardando}
|
|
</div>
|
|
<div class="stat-desc">Pendentes</div>
|
|
</div>
|
|
|
|
<div
|
|
class="stat bg-base-100 shadow-lg rounded-box border border-success/30"
|
|
>
|
|
<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">Aprovadas</div>
|
|
<div class="stat-value text-success">
|
|
{statsMinhasAusencias.aprovadas}
|
|
</div>
|
|
<div class="stat-desc">Deferidas</div>
|
|
</div>
|
|
|
|
<div
|
|
class="stat bg-base-100 shadow-lg rounded-box border border-error/30"
|
|
>
|
|
<div class="stat-figure text-error">
|
|
<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="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title">Reprovadas</div>
|
|
<div class="stat-value text-error">
|
|
{statsMinhasAusencias.reprovadas}
|
|
</div>
|
|
<div class="stat-desc">Indeferidas</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Calendário de Ausências -->
|
|
<div class="card bg-base-100 shadow-lg">
|
|
<div class="card-body">
|
|
<h2 class="card-title text-lg mb-4">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6 text-orange-500"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"
|
|
/>
|
|
</svg>
|
|
Calendário de Ausências
|
|
</h2>
|
|
<p class="text-sm text-base-content/70 mb-4">
|
|
Visualize todas as suas solicitações de ausência no calendário
|
|
</p>
|
|
{#if ausenciasParaCalendario.length > 0}
|
|
<CalendarioAusencias
|
|
ausenciasExistentes={ausenciasParaCalendario}
|
|
readonly={true}
|
|
modoVisualizacao="month"
|
|
/>
|
|
{:else}
|
|
<div class="alert alert-info">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
class="stroke-info 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>
|
|
<span
|
|
>Você ainda não possui solicitações de ausência. Clique em
|
|
"Solicitar Ausência" para criar uma nova.</span
|
|
>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filtros e Botão Nova Solicitação -->
|
|
<div class="card bg-base-100 shadow-lg">
|
|
<div class="card-body">
|
|
<div
|
|
class="flex flex-col md:flex-row justify-between items-start md:items-center gap-4"
|
|
>
|
|
<h2 class="card-title text-lg">Filtros</h2>
|
|
<button
|
|
type="button"
|
|
class="btn btn-warning gap-2"
|
|
onclick={() => {
|
|
const funcionarioId = authStore.usuario?.funcionarioId;
|
|
console.log(
|
|
"🔍 [Perfil] Click no botão - funcionarioId:",
|
|
funcionarioId,
|
|
);
|
|
if (funcionarioId) {
|
|
mostrarWizardAusencia = true;
|
|
} else {
|
|
alert(
|
|
`Não foi possível identificar seu funcionário.\n\nVerifique no console (F12) o objeto usuario:\n${JSON.stringify(authStore.usuario, null, 2)}\n\nEntre em contato com o suporte se o problema persistir.`,
|
|
);
|
|
}
|
|
}}
|
|
disabled={!funcionarioIdDisponivel}
|
|
title={funcionarioIdDisponivel
|
|
? "Clique para solicitar uma ausência"
|
|
: "Funcionário não identificado. Entre em contato com o suporte."}
|
|
>
|
|
<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 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
Solicitar Ausência
|
|
</button>
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-1 gap-4 mt-4">
|
|
<div class="form-control">
|
|
<label class="label" for="status-ausencias">
|
|
<span class="label-text">Status</span>
|
|
</label>
|
|
<select
|
|
id="status-ausencias"
|
|
class="select select-bordered"
|
|
bind:value={filtroStatusAusencias}
|
|
>
|
|
<option value="todos">Todos</option>
|
|
<option value="aguardando_aprovacao"
|
|
>Aguardando Aprovação</option
|
|
>
|
|
<option value="aprovado">Aprovado</option>
|
|
<option value="reprovado">Reprovado</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Lista de Solicitações -->
|
|
<div class="card bg-base-100 shadow-lg">
|
|
<div class="card-body">
|
|
<h2 class="card-title text-lg mb-4">
|
|
Minhas Solicitações ({ausenciasFiltradas.length})
|
|
</h2>
|
|
|
|
{#if ausenciasFiltradas.length === 0}
|
|
<div class="alert">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
class="stroke-info 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>
|
|
<span
|
|
>Nenhuma solicitação encontrada com os filtros aplicados.</span
|
|
>
|
|
</div>
|
|
{:else}
|
|
<div class="overflow-x-auto">
|
|
<table class="table table-zebra">
|
|
<thead>
|
|
<tr>
|
|
<th>Período</th>
|
|
<th>Dias</th>
|
|
<th>Motivo</th>
|
|
<th>Status</th>
|
|
<th>Solicitado em</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{#each ausenciasFiltradas as ausencia}
|
|
<tr>
|
|
<td>
|
|
{new Date(ausencia.dataInicio).toLocaleDateString(
|
|
"pt-BR",
|
|
)} até {new Date(ausencia.dataFim).toLocaleDateString(
|
|
"pt-BR",
|
|
)}
|
|
</td>
|
|
<td class="font-bold">
|
|
{Math.ceil(
|
|
(new Date(ausencia.dataFim).getTime() -
|
|
new Date(ausencia.dataInicio).getTime()) /
|
|
(1000 * 60 * 60 * 24),
|
|
) + 1} dias
|
|
</td>
|
|
<td class="max-w-xs truncate" title={ausencia.motivo}>
|
|
{ausencia.motivo}
|
|
</td>
|
|
<td>
|
|
<div
|
|
class={`badge ${getStatusBadge(ausencia.status)}`}
|
|
>
|
|
{getStatusTexto(ausencia.status)}
|
|
</div>
|
|
</td>
|
|
<td class="text-xs"
|
|
>{new Date(ausencia.criadoEm).toLocaleDateString(
|
|
"pt-BR",
|
|
)}</td
|
|
>
|
|
</tr>
|
|
{/each}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{:else if abaAtiva === "aprovar-ferias"}
|
|
<!-- Aprovar Férias (Gestores) PREMIUM -->
|
|
<div class="card bg-base-100 shadow-2xl border-t-4 border-green-500">
|
|
<div class="card-body">
|
|
<h2 class="card-title text-2xl mb-6 flex items-center gap-2">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-7 w-7 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>
|
|
Solicitações da Equipe
|
|
<div class="badge badge-lg badge-primary ml-2">
|
|
{solicitacoesSubordinados.length}
|
|
</div>
|
|
</h2>
|
|
|
|
{#if solicitacoesSubordinados.length === 0}
|
|
<div class="alert alert-success">
|
|
<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="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
></path>
|
|
</svg>
|
|
<span class="font-semibold"
|
|
>Nenhuma solicitação pendente no momento.</span
|
|
>
|
|
</div>
|
|
{:else}
|
|
<div class="overflow-x-auto">
|
|
<table class="table table-zebra table-lg">
|
|
<thead>
|
|
<tr class="bg-base-200">
|
|
<th class="font-bold">Funcionário</th>
|
|
<th class="font-bold">Time</th>
|
|
<th class="font-bold">Ano</th>
|
|
<th class="font-bold">Períodos</th>
|
|
<th class="font-bold">Dias</th>
|
|
<th class="font-bold">Status</th>
|
|
<th class="font-bold">Ações</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{#each solicitacoesSubordinados as solicitacao}
|
|
<tr class="hover:bg-base-200 transition-colors">
|
|
<td>
|
|
<div class="font-bold">
|
|
{solicitacao.funcionario?.nome}
|
|
</div>
|
|
</td>
|
|
<td>
|
|
{#if solicitacao.time}
|
|
<div
|
|
class="badge badge-lg font-semibold"
|
|
style="background-color: {solicitacao.time
|
|
.cor}20; border-color: {solicitacao.time
|
|
.cor}; color: {solicitacao.time.cor}"
|
|
>
|
|
{solicitacao.time.nome}
|
|
</div>
|
|
{/if}
|
|
</td>
|
|
<td class="font-semibold">{solicitacao.anoReferencia}</td>
|
|
<td class="font-semibold"
|
|
>{solicitacao.periodos.length}</td
|
|
>
|
|
<td class="font-bold text-lg"
|
|
>{solicitacao.periodos.reduce(
|
|
(acc: number, p: any) => acc + p.diasCorridos,
|
|
0,
|
|
)}</td
|
|
>
|
|
<td>
|
|
<div
|
|
class={`badge badge-lg font-semibold ${getStatusBadge(solicitacao.status)}`}
|
|
>
|
|
{getStatusTexto(solicitacao.status)}
|
|
</div>
|
|
</td>
|
|
<td>
|
|
{#if solicitacao.status === "aguardando_aprovacao"}
|
|
<button
|
|
type="button"
|
|
class="btn btn-primary btn-sm gap-2 shadow-lg hover:scale-105 transition-transform"
|
|
onclick={() =>
|
|
selecionarSolicitacao(solicitacao._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="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"
|
|
/>
|
|
</svg>
|
|
Analisar
|
|
</button>
|
|
{:else}
|
|
<button
|
|
type="button"
|
|
class="btn btn-ghost btn-sm gap-2"
|
|
onclick={() =>
|
|
selecionarSolicitacao(solicitacao._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="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>
|
|
Detalhes
|
|
</button>
|
|
{/if}
|
|
</td>
|
|
</tr>
|
|
{/each}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{:else if abaAtiva === "aprovar-ausencias"}
|
|
<!-- Aprovar Ausências (Gestores) -->
|
|
<div class="card bg-base-100 shadow-2xl border-t-4 border-orange-500">
|
|
<div class="card-body">
|
|
<h2 class="card-title text-2xl mb-6 flex items-center gap-2">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-7 w-7 text-orange-600"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<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"
|
|
/>
|
|
</svg>
|
|
Solicitações de Ausências da Equipe
|
|
<div class="badge badge-lg badge-warning ml-2">
|
|
{ausenciasSubordinados.length}
|
|
</div>
|
|
</h2>
|
|
|
|
{#if ausenciasSubordinados.length === 0}
|
|
<div class="alert alert-success">
|
|
<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="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
></path>
|
|
</svg>
|
|
<span class="font-semibold"
|
|
>Nenhuma solicitação pendente no momento.</span
|
|
>
|
|
</div>
|
|
{:else}
|
|
<div class="overflow-x-auto">
|
|
<table class="table table-zebra table-lg">
|
|
<thead>
|
|
<tr class="bg-base-200">
|
|
<th class="font-bold">Funcionário</th>
|
|
<th class="font-bold">Time</th>
|
|
<th class="font-bold">Período</th>
|
|
<th class="font-bold">Dias</th>
|
|
<th class="font-bold">Status</th>
|
|
<th class="font-bold">Ações</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{#each ausenciasSubordinados as ausencia}
|
|
<tr class="hover:bg-base-200 transition-colors">
|
|
<td>
|
|
<div class="font-bold">
|
|
{ausencia.funcionario?.nome || "N/A"}
|
|
</div>
|
|
</td>
|
|
<td>
|
|
{#if ausencia.time}
|
|
<div
|
|
class="badge badge-lg font-semibold"
|
|
style="background-color: {ausencia.time
|
|
.cor}20; border-color: {ausencia.time
|
|
.cor}; color: {ausencia.time.cor}"
|
|
>
|
|
{ausencia.time.nome}
|
|
</div>
|
|
{/if}
|
|
</td>
|
|
<td class="font-semibold">
|
|
{new Date(ausencia.dataInicio).toLocaleDateString(
|
|
"pt-BR",
|
|
)} até{" "}
|
|
{new Date(ausencia.dataFim).toLocaleDateString("pt-BR")}
|
|
</td>
|
|
<td class="font-bold text-lg">
|
|
{Math.ceil(
|
|
(new Date(ausencia.dataFim).getTime() -
|
|
new Date(ausencia.dataInicio).getTime()) /
|
|
(1000 * 60 * 60 * 24),
|
|
) + 1} dias
|
|
</td>
|
|
<td>
|
|
<div
|
|
class={`badge badge-lg font-semibold ${getStatusBadge(ausencia.status)}`}
|
|
>
|
|
{getStatusTexto(ausencia.status)}
|
|
</div>
|
|
</td>
|
|
<td>
|
|
{#if ausencia.status === "aguardando_aprovacao"}
|
|
<button
|
|
type="button"
|
|
class="btn btn-warning btn-sm gap-2 shadow-lg hover:scale-105 transition-transform"
|
|
onclick={() =>
|
|
(solicitacaoAusenciaAprovar = ausencia._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="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"
|
|
/>
|
|
</svg>
|
|
Analisar
|
|
</button>
|
|
{:else}
|
|
<button
|
|
type="button"
|
|
class="btn btn-ghost btn-sm gap-2"
|
|
onclick={() =>
|
|
(solicitacaoAusenciaAprovar = ausencia._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="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>
|
|
Detalhes
|
|
</button>
|
|
{/if}
|
|
</td>
|
|
</tr>
|
|
{/each}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
|
|
<!-- Modal de Aprovação de Férias -->
|
|
{#if solicitacaoSelecionada}
|
|
<dialog class="modal modal-open">
|
|
<div class="modal-box max-w-4xl">
|
|
{#if authStore.usuario}
|
|
<AprovarFerias
|
|
solicitacao={solicitacaoSelecionada}
|
|
gestorId={authStore.usuario._id}
|
|
onSucesso={recarregar}
|
|
onCancelar={() => (solicitacaoSelecionada = null)}
|
|
/>
|
|
{/if}
|
|
</div>
|
|
<form method="dialog" class="modal-backdrop">
|
|
<button
|
|
type="button"
|
|
onclick={() => (solicitacaoSelecionada = null)}
|
|
aria-label="Fechar modal">Fechar</button
|
|
>
|
|
</form>
|
|
</dialog>
|
|
{/if}
|
|
|
|
<!-- Modal de Upload de Foto / Escolher Avatar -->
|
|
{#if mostrarModalFoto}
|
|
<dialog class="modal modal-open">
|
|
<div class="modal-box max-w-4xl max-h-[90vh] overflow-y-auto">
|
|
<h3
|
|
class="font-black text-3xl mb-8 text-center bg-linear-to-r from-purple-600 to-blue-600 bg-clip-text text-transparent"
|
|
>
|
|
Alterar Foto de Perfil
|
|
</h3>
|
|
|
|
<!-- Preview da foto atual -->
|
|
<div class="flex justify-center mb-8">
|
|
<div class="avatar">
|
|
<div
|
|
class="w-32 h-32 rounded-full ring-4 ring-primary ring-offset-base-100 ring-offset-4 shadow-2xl"
|
|
>
|
|
{#if fotoPerfilLocal}
|
|
<img
|
|
src={fotoPerfilLocal}
|
|
alt="Foto atual"
|
|
class="object-cover"
|
|
/>
|
|
{:else if avatarLocal}
|
|
<img
|
|
src={avatarLocal}
|
|
alt="Avatar atual"
|
|
class="object-cover"
|
|
/>
|
|
{:else}
|
|
<div
|
|
class="bg-primary text-primary-content flex items-center justify-center"
|
|
>
|
|
<span class="text-4xl font-bold"
|
|
>{authStore.usuario?.nome
|
|
.substring(0, 2)
|
|
.toUpperCase()}</span
|
|
>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tabs: Avatar ou Upload -->
|
|
<div
|
|
role="tablist"
|
|
class="tabs tabs-boxed mb-8 bg-linear-to-r from-base-200 to-base-300 p-2 shadow-xl"
|
|
>
|
|
<button
|
|
type="button"
|
|
role="tab"
|
|
class={`tab tab-lg font-semibold transition-all ${modoFoto === "avatar" ? "tab-active bg-linear-to-r from-purple-600 to-blue-600 text-white" : ""}`}
|
|
onclick={() => (modoFoto = "avatar")}
|
|
disabled={uploadandoFoto}
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-5 w-5 mr-2"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M14.828 14.828a4 4 0 01-5.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
Escolher Avatar
|
|
</button>
|
|
<button
|
|
type="button"
|
|
role="tab"
|
|
class={`tab tab-lg font-semibold transition-all ${modoFoto === "upload" ? "tab-active bg-linear-to-r from-purple-600 to-blue-600 text-white" : ""}`}
|
|
onclick={() => (modoFoto = "upload")}
|
|
disabled={uploadandoFoto}
|
|
>
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-5 w-5 mr-2"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
|
|
/>
|
|
</svg>
|
|
Enviar Foto
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Conteúdo das Tabs -->
|
|
{#if modoFoto === "avatar"}
|
|
<!-- Galeria de Avatares -->
|
|
<div class="mb-4">
|
|
<p class="text-center text-base-content/70 mb-6 text-lg">
|
|
Escolha um dos <strong class="text-primary"
|
|
>30 avatares profissionais</strong
|
|
> para seu perfil
|
|
</p>
|
|
|
|
<div
|
|
class="grid grid-cols-3 md:grid-cols-5 lg:grid-cols-6 gap-4 p-6 bg-base-200 rounded-2xl max-h-[500px] overflow-y-auto shadow-inner"
|
|
>
|
|
{#each avatarGallery as avatar}
|
|
<button
|
|
type="button"
|
|
class={`flex flex-col items-center cursor-pointer transition-all hover:scale-110 p-2 rounded-xl ${avatarSelecionado === avatar.url ? "ring-4 ring-primary bg-primary/10 scale-105" : "hover:ring-2 hover:ring-primary/50 hover:bg-base-100"}`}
|
|
onclick={() => (avatarSelecionado = avatar.url)}
|
|
ondblclick={() => handleSelecionarAvatar(avatar.url)}
|
|
disabled={uploadandoFoto}
|
|
aria-label="Selecionar avatar {avatar.name}"
|
|
>
|
|
<div class="avatar">
|
|
<div class="w-20 h-20 rounded-full shadow-lg">
|
|
<img src={avatar.url} alt={avatar.name} loading="lazy" />
|
|
</div>
|
|
</div>
|
|
<div
|
|
class="text-[10px] text-center mt-2 truncate w-full font-semibold"
|
|
>
|
|
{avatar.name}
|
|
</div>
|
|
</button>
|
|
{/each}
|
|
</div>
|
|
|
|
<div class="alert alert-info mt-6 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>
|
|
<span>
|
|
<strong>Dica:</strong> Clique uma vez para selecionar, clique duas
|
|
vezes para aplicar imediatamente!
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{#if avatarSelecionado}
|
|
<div class="flex justify-center gap-3 mt-6">
|
|
<button
|
|
type="button"
|
|
class="btn btn-lg gap-2 shadow-xl hover:scale-105 transition-all"
|
|
style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none;"
|
|
onclick={() => handleSelecionarAvatar(avatarSelecionado)}
|
|
disabled={uploadandoFoto}
|
|
>
|
|
{#if uploadandoFoto}
|
|
<span class="loading loading-spinner"></span>
|
|
{:else}
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
class="h-6 w-6"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
stroke-width="2.5"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="M5 13l4 4L19 7"
|
|
/>
|
|
</svg>
|
|
{/if}
|
|
{uploadandoFoto ? "Salvando..." : "Confirmar Avatar"}
|
|
</button>
|
|
</div>
|
|
{/if}
|
|
{:else}
|
|
<!-- Upload de nova foto -->
|
|
<div class="form-control">
|
|
<label class="label" for="foto-upload">
|
|
<span class="label-text font-bold text-lg"
|
|
>Selecionar nova foto</span
|
|
>
|
|
</label>
|
|
<input
|
|
id="foto-upload"
|
|
type="file"
|
|
class="file-input file-input-bordered file-input-lg w-full shadow-lg"
|
|
accept="image/*"
|
|
onchange={handleUploadFoto}
|
|
disabled={uploadandoFoto}
|
|
/>
|
|
<div class="label">
|
|
<span class="label-text-alt text-base-content/70"
|
|
>Formatos aceitos: JPG, PNG, GIF. Tamanho máximo: 5MB</span
|
|
>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
{#if erroUpload}
|
|
<div class="alert alert-error mt-6 shadow-lg">
|
|
<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="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
/>
|
|
</svg>
|
|
<span class="font-semibold">{erroUpload}</span>
|
|
</div>
|
|
{/if}
|
|
|
|
{#if uploadandoFoto && modoFoto === "upload"}
|
|
<div class="flex justify-center items-center gap-3 mt-6">
|
|
<span class="loading loading-spinner loading-lg text-primary"
|
|
></span>
|
|
<span class="font-semibold text-lg">Enviando foto...</span>
|
|
</div>
|
|
{/if}
|
|
|
|
<div class="modal-action mt-8">
|
|
<button
|
|
type="button"
|
|
class="btn btn-lg"
|
|
onclick={() => (mostrarModalFoto = false)}
|
|
disabled={uploadandoFoto}
|
|
>
|
|
Cancelar
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<form method="dialog" class="modal-backdrop">
|
|
<button
|
|
type="button"
|
|
onclick={() => (mostrarModalFoto = false)}
|
|
aria-label="Fechar modal"
|
|
disabled={uploadandoFoto}>Fechar</button
|
|
>
|
|
</form>
|
|
</dialog>
|
|
{/if}
|
|
</main>
|
|
|
|
<!-- Modal Wizard Solicitação de Férias -->
|
|
{#if mostrarWizard && funcionario}
|
|
<dialog class="modal modal-open">
|
|
<div class="modal-box max-w-4xl max-h-[90vh] overflow-hidden">
|
|
<h3 class="font-bold text-2xl mb-6 text-center">
|
|
Nova Solicitação de Férias
|
|
</h3>
|
|
<div class="max-h-[80vh] overflow-y-auto">
|
|
<WizardSolicitacaoFerias
|
|
funcionarioId={funcionario._id}
|
|
onSucesso={() => {
|
|
mostrarWizard = false;
|
|
}}
|
|
onCancelar={() => (mostrarWizard = false)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
<div class="modal-backdrop" onclick={() => (mostrarWizard = false)}></div>
|
|
</dialog>
|
|
{/if}
|
|
|
|
<!-- Modal Wizard Solicitação de Ausência -->
|
|
{#if mostrarWizardAusencia && funcionarioIdDisponivel}
|
|
<dialog
|
|
class="modal modal-open"
|
|
onclick={(e) =>
|
|
e.target === e.currentTarget && (mostrarWizardAusencia = false)}
|
|
>
|
|
<div
|
|
class="modal-box max-w-4xl max-h-[90vh] flex flex-col p-0"
|
|
onclick={(e) => e.stopPropagation()}
|
|
>
|
|
<!-- Header -->
|
|
<div
|
|
class="flex items-center justify-between px-6 py-4 border-b border-base-300"
|
|
>
|
|
<h2 id="modal-title" class="text-xl font-bold flex items-center gap-2">
|
|
<Calendar class="w-5 h-5 text-primary" />
|
|
Nova Solicitação de Ausência
|
|
</h2>
|
|
<button
|
|
type="button"
|
|
class="btn btn-ghost btn-sm btn-circle"
|
|
onclick={() => (mostrarWizardAusencia = false)}
|
|
aria-label="Fechar"
|
|
>
|
|
<X class="w-5 h-5" />
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Content -->
|
|
<div class="flex-1 overflow-y-auto p-6">
|
|
<WizardSolicitacaoAusencia
|
|
funcionarioId={funcionarioIdDisponivel}
|
|
onSucesso={async () => {
|
|
mostrarWizardAusencia = false;
|
|
// As queries do Convex são reativas e devem atualizar automaticamente
|
|
// Mas garantimos que o componente será re-renderizado
|
|
// Forçar recarregamento das queries mudando temporariamente o filtro
|
|
const filtroAnterior = filtroStatusAusencias;
|
|
if (filtroAnterior !== "todos") {
|
|
filtroStatusAusencias = "todos";
|
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
}
|
|
filtroStatusAusencias = filtroAnterior;
|
|
}}
|
|
onCancelar={() => (mostrarWizardAusencia = false)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<form method="dialog" class="modal-backdrop">
|
|
<button type="button" onclick={() => (mostrarWizardAusencia = false)}
|
|
>fechar</button
|
|
>
|
|
</form>
|
|
</dialog>
|
|
{/if}
|
|
|
|
<!-- Modal de Aprovação de Ausências -->
|
|
{#if solicitacaoAusenciaAprovar && authStore.usuario}
|
|
{#await client.query( api.ausencias.obterDetalhes, { solicitacaoId: solicitacaoAusenciaAprovar }, ) then detalhes}
|
|
{#if detalhes}
|
|
<dialog class="modal modal-open">
|
|
<div class="modal-box max-w-4xl">
|
|
<AprovarAusencias
|
|
solicitacao={detalhes}
|
|
gestorId={authStore.usuario._id}
|
|
onSucesso={() => {
|
|
solicitacaoAusenciaAprovar = null;
|
|
}}
|
|
onCancelar={() => (solicitacaoAusenciaAprovar = null)}
|
|
/>
|
|
</div>
|
|
<form method="dialog" class="modal-backdrop">
|
|
<button
|
|
type="button"
|
|
onclick={() => (solicitacaoAusenciaAprovar = null)}
|
|
aria-label="Fechar modal">Fechar</button
|
|
>
|
|
</form>
|
|
</dialog>
|
|
{/if}
|
|
{/await}
|
|
{/if}
|
|
|
|
<style>
|
|
@keyframes gradient-shift {
|
|
0%,
|
|
100% {
|
|
background-position: 0% 50%;
|
|
}
|
|
50% {
|
|
background-position: 100% 50%;
|
|
}
|
|
}
|
|
|
|
@keyframes float {
|
|
0%,
|
|
100% {
|
|
transform: translateY(0px);
|
|
}
|
|
50% {
|
|
transform: translateY(-10px);
|
|
}
|
|
}
|
|
|
|
.animate-gradient {
|
|
background-size: 200% 200%;
|
|
animation: gradient-shift 15s ease infinite;
|
|
}
|
|
|
|
.animate-float {
|
|
animation: float 6s ease-in-out infinite;
|
|
}
|
|
</style>
|