@@ -7,7 +7,6 @@
|
||||
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 } from '$lib/utils/avatars';
|
||||
import ProtectedRoute from '$lib/components/ProtectedRoute.svelte';
|
||||
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
|
||||
import type { FunctionReturnType } from 'convex/server';
|
||||
@@ -75,7 +74,6 @@
|
||||
let uploadandoFoto = $state(false);
|
||||
let erroUpload = $state('');
|
||||
let modoFoto = $state<'upload' | 'avatar'>('avatar');
|
||||
let avatarSelecionado = $state<string>('');
|
||||
let mostrarBotaoCamera = $state(false);
|
||||
|
||||
// Estados para Minhas Férias
|
||||
@@ -100,8 +98,19 @@
|
||||
let erroMensagemChamado = $state<string | null>(null);
|
||||
let sucessoMensagemChamado = $state<string | null>(null);
|
||||
|
||||
// Galeria de avatares (30 avatares profissionais 3D realistas)
|
||||
const avatarGallery = generateAvatarGallery(30);
|
||||
// Avatares padrão disponíveis
|
||||
const defaultAvatars = [
|
||||
'/avatars/avatar-1.png',
|
||||
'/avatars/avatar-2.png',
|
||||
'/avatars/avatar-3.png',
|
||||
'/avatars/avatar-4.png',
|
||||
'/avatars/avatar-5.png',
|
||||
'/avatars/avatar-6.png',
|
||||
'/avatars/avatar-7.png',
|
||||
'/avatars/avatar-8.png',
|
||||
'/avatars/avatar-9.png',
|
||||
'/avatars/avatar-10.png'
|
||||
];
|
||||
|
||||
// FuncionarioId disponível diretamente do usuário atual
|
||||
const funcionarioIdDisponivel = $derived(currentUser?.data?.funcionarioId ?? null);
|
||||
@@ -441,6 +450,30 @@
|
||||
return;
|
||||
}
|
||||
|
||||
await processarUploadFoto(file);
|
||||
}
|
||||
|
||||
async function handleEscolherAvatarPadrao(avatarPath: string) {
|
||||
try {
|
||||
uploadandoFoto = true;
|
||||
erroUpload = '';
|
||||
|
||||
// Buscar a imagem
|
||||
const response = await fetch(avatarPath);
|
||||
const blob = await response.blob();
|
||||
const file = new File([blob], avatarPath.split('/').pop() || 'avatar.png', {
|
||||
type: 'image/png'
|
||||
});
|
||||
|
||||
await processarUploadFoto(file);
|
||||
} catch (e: unknown) {
|
||||
const errorMessage = e instanceof Error ? e.message : String(e);
|
||||
erroUpload = errorMessage || 'Erro ao processar avatar padrão';
|
||||
uploadandoFoto = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function processarUploadFoto(file: File) {
|
||||
uploadandoFoto = true;
|
||||
erroUpload = '';
|
||||
|
||||
@@ -463,16 +496,12 @@
|
||||
|
||||
// 4. Atualizar perfil com o novo storageId
|
||||
await client.mutation(api.usuarios.atualizarPerfil, {
|
||||
fotoPerfil: storageId,
|
||||
avatar: undefined // Remove avatar se colocar foto
|
||||
fotoPerfil: storageId
|
||||
});
|
||||
|
||||
// 5. Aguardar um pouco para garantir que o backend processou
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
|
||||
// 8. Limpar o input para permitir novo upload
|
||||
input.value = '';
|
||||
|
||||
// 9. Fechar modal após sucesso
|
||||
mostrarModalFoto = false;
|
||||
|
||||
@@ -496,45 +525,9 @@
|
||||
uploadandoFoto = false;
|
||||
}
|
||||
|
||||
async function handleSelecionarAvatar(avatarUrl: string) {
|
||||
uploadandoFoto = true;
|
||||
erroUpload = '';
|
||||
|
||||
try {
|
||||
// 2. Salvar avatar selecionado no backend
|
||||
await client.mutation(api.usuarios.atualizarPerfil, {
|
||||
avatar: avatarUrl,
|
||||
fotoPerfil: undefined // Remove foto se colocar avatar
|
||||
});
|
||||
|
||||
// 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: unknown) {
|
||||
const errorMessage = e instanceof Error ? e.message : String(e);
|
||||
erroUpload = errorMessage || 'Erro ao salvar avatar';
|
||||
} finally {
|
||||
uploadandoFoto = false;
|
||||
}
|
||||
}
|
||||
|
||||
function abrirModalFoto() {
|
||||
erroUpload = '';
|
||||
modoFoto = 'avatar';
|
||||
avatarSelecionado = '';
|
||||
mostrarModalFoto = true;
|
||||
}
|
||||
</script>
|
||||
@@ -571,22 +564,16 @@
|
||||
onclick={abrirModalFoto}
|
||||
>
|
||||
<div
|
||||
class="animate-float h-40 w-40 rounded-full shadow-2xl ring-4 ring-white ring-offset-4 ring-offset-transparent transition-all duration-300 hover:scale-105 hover:ring-8"
|
||||
class="animate-float flex h-40 w-40 items-center justify-center overflow-hidden rounded-full bg-white shadow-2xl ring-4 ring-white ring-offset-4 ring-offset-transparent transition-all duration-300 hover:scale-105 hover:ring-8"
|
||||
>
|
||||
{#if currentUser.data?.fotoPerfilUrl}
|
||||
<img
|
||||
src={currentUser.data.fotoPerfilUrl}
|
||||
alt="Foto de perfil"
|
||||
class="object-cover"
|
||||
class="h-full w-full object-cover"
|
||||
/>
|
||||
{:else if currentUser.data?.avatar}
|
||||
<img src={currentUser.data.avatar} alt="Avatar" class="object-cover" />
|
||||
{:else}
|
||||
<div class="flex items-center justify-center bg-white text-purple-700">
|
||||
<span class="text-5xl font-black"
|
||||
>{currentUser.data?.nome.substring(0, 2).toUpperCase()}</span
|
||||
>
|
||||
</div>
|
||||
<User class="h-20 w-20 text-purple-700" />
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
@@ -2338,18 +2325,16 @@
|
||||
<div class="mb-8 flex justify-center">
|
||||
<div class="avatar">
|
||||
<div
|
||||
class="ring-primary ring-offset-base-100 h-32 w-32 rounded-full shadow-2xl ring-4 ring-offset-4"
|
||||
class="ring-primary ring-offset-base-100 bg-base-200 flex h-32 w-32 items-center justify-center overflow-hidden rounded-full shadow-2xl ring-4 ring-offset-4"
|
||||
>
|
||||
{#if currentUser.data?.fotoPerfilUrl}
|
||||
<img src={currentUser.data.fotoPerfilUrl} alt="Foto atual" class="object-cover" />
|
||||
{:else if currentUser.data?.avatar}
|
||||
<img src={currentUser.data.avatar} alt="Avatar atual" class="object-cover" />
|
||||
<img
|
||||
src={currentUser.data.fotoPerfilUrl}
|
||||
alt="Foto atual"
|
||||
class="h-full w-full object-cover"
|
||||
/>
|
||||
{:else}
|
||||
<div class="bg-primary text-primary-content flex items-center justify-center">
|
||||
<span class="text-4xl font-bold"
|
||||
>{currentUser.data?.nome.substring(0, 2).toUpperCase()}</span
|
||||
>
|
||||
</div>
|
||||
<User class="text-base-content/50 h-16 w-16" />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
@@ -2413,82 +2398,37 @@
|
||||
<!-- Galeria de Avatares -->
|
||||
<div class="mb-4">
|
||||
<p class="text-base-content/70 mb-6 text-center text-lg">
|
||||
Escolha um dos <strong class="text-primary">30 avatares profissionais</strong> para seu
|
||||
Escolha um dos <strong class="text-primary">avatares profissionais</strong> para seu
|
||||
perfil
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="bg-base-200 grid max-h-[500px] grid-cols-3 gap-4 overflow-y-auto rounded-2xl p-6 shadow-inner md:grid-cols-5 lg:grid-cols-6"
|
||||
class="bg-base-200 grid max-h-[500px] grid-cols-3 gap-4 overflow-y-auto rounded-2xl p-6 shadow-inner md:grid-cols-4 lg:grid-cols-5"
|
||||
>
|
||||
{#each avatarGallery as avatar (avatar.id)}
|
||||
{#each defaultAvatars as avatarPath, i (avatarPath)}
|
||||
<button
|
||||
type="button"
|
||||
class={`flex cursor-pointer flex-col items-center rounded-xl p-2 transition-all hover:scale-110 ${avatarSelecionado === avatar.url ? 'ring-primary bg-primary/10 scale-105 ring-4' : 'hover:ring-primary/50 hover:bg-base-100 hover:ring-2'}`}
|
||||
onclick={() => (avatarSelecionado = avatar.url)}
|
||||
ondblclick={() => handleSelecionarAvatar(avatar.url)}
|
||||
class="hover:ring-primary/50 hover:bg-base-100 flex cursor-pointer flex-col items-center rounded-xl p-2 transition-all hover:scale-110 hover:ring-2"
|
||||
onclick={() => handleEscolherAvatarPadrao(avatarPath)}
|
||||
disabled={uploadandoFoto}
|
||||
aria-label="Selecionar avatar {avatar.name}"
|
||||
aria-label="Selecionar avatar {i + 1}"
|
||||
>
|
||||
<div class="avatar">
|
||||
<div class="h-20 w-20 rounded-full shadow-lg">
|
||||
<img src={avatar.url} alt={avatar.name} loading="lazy" />
|
||||
<img src={avatarPath} alt="Avatar {i + 1}" loading="lazy" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 w-full truncate text-center text-[10px] 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="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>
|
||||
<Info class="h-6 w-6 shrink-0" />
|
||||
<span>
|
||||
<strong>Dica:</strong> Clique uma vez para selecionar, clique duas vezes para aplicar
|
||||
imediatamente!
|
||||
<strong>Dica:</strong> Clique em um avatar para defini-lo como sua foto de perfil.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if avatarSelecionado}
|
||||
<div class="mt-6 flex justify-center gap-3">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-lg gap-2 shadow-xl transition-all hover:scale-105"
|
||||
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">
|
||||
|
||||
Reference in New Issue
Block a user