Refactor backend code style and improve user profile handling
- Standardize import formatting and indentation in auth and funcionarios modules - Enhance getCurrentUser query to include photo URL retrieval from storage - Add getCurrent funcionario query based on authenticated user - Update controller logic to avoid redundant local state for profile photos - Upgrade dependencies: convex 1.28.2, svelte 5.43.6, vite 7.2.2, rollup 4.53.2, tailwindcss 4.1.17, and others
This commit is contained in:
@@ -8,8 +8,8 @@
|
||||
import CalendarioAusencias from '$lib/components/ausencias/CalendarioAusencias.svelte';
|
||||
import { generateAvatarGallery } 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';
|
||||
import type { FunctionReturnType } from 'convex/server';
|
||||
|
||||
const client = useConvexClient();
|
||||
const currentUser = useQuery(api.auth.getCurrentUser, {});
|
||||
@@ -17,7 +17,9 @@
|
||||
let abaAtiva = $state<
|
||||
'meu-perfil' | 'minhas-ferias' | 'minhas-ausencias' | 'aprovar-ferias' | 'aprovar-ausencias'
|
||||
>('meu-perfil');
|
||||
let solicitacaoSelecionada = $state<any>(null);
|
||||
let solicitacaoSelecionada = $state<FunctionReturnType<typeof api.ferias.obterDetalhes> | null>(
|
||||
null
|
||||
);
|
||||
let mostrarModalFoto = $state(false);
|
||||
let uploadandoFoto = $state(false);
|
||||
let erroUpload = $state('');
|
||||
@@ -25,11 +27,6 @@
|
||||
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');
|
||||
@@ -37,7 +34,6 @@
|
||||
// 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);
|
||||
@@ -45,39 +41,11 @@
|
||||
// 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 (currentUser?.data && !perfilCarregado) {
|
||||
perfilCarregado = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Sincronizar com currentUser - atualiza automaticamente quando o usuário muda
|
||||
$effect(() => {
|
||||
const usuario = currentUser?.data;
|
||||
if (usuario) {
|
||||
// Atualizar foto de perfil (pode ser null ou string)
|
||||
fotoPerfilLocal = usuario.fotoPerfil ?? 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 usuário atual
|
||||
const funcionarioIdDisponivel = $derived(currentUser?.data?.funcionarioId ?? null);
|
||||
|
||||
// Queries
|
||||
const funcionarioQuery = $derived(
|
||||
currentUser?.data?.funcionarioId
|
||||
? useQuery(api.funcionarios.getById, {
|
||||
id: currentUser.data.funcionarioId
|
||||
})
|
||||
: { data: null }
|
||||
const funcionarioQuery = useQuery(api.funcionarios.getCurrent, () =>
|
||||
funcionarioIdDisponivel ? {} : 'skip'
|
||||
);
|
||||
|
||||
const solicitacoesSubordinadosQuery = $derived(
|
||||
@@ -187,9 +155,9 @@
|
||||
solicitacaoSelecionada = null;
|
||||
}
|
||||
|
||||
async function selecionarSolicitacao(solicitacaoId: string) {
|
||||
async function selecionarSolicitacao(solicitacaoId: Id<'solicitacoesFerias'>) {
|
||||
const detalhes = await client.query(api.ferias.obterDetalhes, {
|
||||
solicitacaoId: solicitacaoId as Id<'solicitacoesFerias'>
|
||||
solicitacaoId: solicitacaoId
|
||||
});
|
||||
solicitacaoSelecionada = detalhes;
|
||||
}
|
||||
@@ -236,14 +204,6 @@
|
||||
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, {});
|
||||
|
||||
@@ -266,15 +226,6 @@
|
||||
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 localmente com a URL retornada pelo backend (ou pelo currentUser)
|
||||
if (currentUser?.data?.fotoPerfil) {
|
||||
fotoPerfilLocal = currentUser.data.fotoPerfil;
|
||||
avatarLocal = null;
|
||||
}
|
||||
|
||||
// 8. Limpar o input para permitir novo upload
|
||||
input.value = '';
|
||||
|
||||
@@ -294,11 +245,9 @@
|
||||
`;
|
||||
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 = currentUser?.data?.fotoPerfil || null;
|
||||
avatarLocal = currentUser?.data?.avatar || null;
|
||||
} catch (e: unknown) {
|
||||
const error = e as Error;
|
||||
erroUpload = error.message || 'Erro ao fazer upload da foto';
|
||||
} finally {
|
||||
uploadandoFoto = false;
|
||||
}
|
||||
@@ -309,10 +258,6 @@
|
||||
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,
|
||||
@@ -322,12 +267,6 @@
|
||||
// 3. Aguardar um pouco para garantir que o backend processou
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
|
||||
// 4. Garantir que os estados locais estão sincronizados com o usuário atual
|
||||
if (currentUser?.data?.avatar) {
|
||||
avatarLocal = currentUser.data.avatar;
|
||||
fotoPerfilLocal = null;
|
||||
}
|
||||
|
||||
// 6. Fechar modal após sucesso
|
||||
mostrarModalFoto = false;
|
||||
|
||||
@@ -344,11 +283,9 @@
|
||||
`;
|
||||
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 = currentUser?.data?.avatar || null;
|
||||
fotoPerfilLocal = currentUser?.data?.fotoPerfil || null;
|
||||
} catch (e: unknown) {
|
||||
const error = e as Error;
|
||||
erroUpload = error.message || 'Erro ao salvar avatar';
|
||||
} finally {
|
||||
uploadandoFoto = false;
|
||||
}
|
||||
@@ -395,10 +332,14 @@
|
||||
<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"
|
||||
>
|
||||
{#if fotoPerfilLocal}
|
||||
<img src={fotoPerfilLocal} alt="Foto de perfil" class="object-cover" />
|
||||
{:else if avatarLocal}
|
||||
<img src={avatarLocal} alt="Avatar" class="object-cover" />
|
||||
{#if currentUser.data?.fotoPerfilUrl}
|
||||
<img
|
||||
src={currentUser.data.fotoPerfilUrl}
|
||||
alt="Foto de perfil"
|
||||
class="object-cover"
|
||||
/>
|
||||
{:else if currentUser.data?.avatar}
|
||||
<img src={currentUser.data.avatar} alt="Foto de perfil" class="object-cover" />
|
||||
{:else}
|
||||
<div class="flex items-center justify-center bg-white text-purple-700">
|
||||
<span class="text-5xl font-black"
|
||||
@@ -632,11 +573,10 @@
|
||||
/>
|
||||
</svg>
|
||||
Aprovar Férias
|
||||
{#if (solicitacoesSubordinados || []).filter((s: any) => s.status === 'aguardando_aprovacao').length > 0}
|
||||
{#if (solicitacoesSubordinados || []).filter((s) => 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}
|
||||
{(solicitacoesSubordinados || []).filter((s) => s.status === 'aguardando_aprovacao')
|
||||
.length}
|
||||
</span>
|
||||
{/if}
|
||||
</button>
|
||||
@@ -662,9 +602,9 @@
|
||||
/>
|
||||
</svg>
|
||||
Aprovar Ausências
|
||||
{#if (ausenciasSubordinados || []).filter((a: any) => a.status === 'aguardando_aprovacao').length > 0}
|
||||
{#if (ausenciasSubordinados || []).filter((a) => 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')
|
||||
{(ausenciasSubordinados || []).filter((a) => a.status === 'aguardando_aprovacao')
|
||||
.length}
|
||||
</span>
|
||||
{/if}
|
||||
@@ -1113,7 +1053,7 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
{#each meusTimesGestor as time}
|
||||
{#each meusTimesGestor as time (time._id)}
|
||||
<div
|
||||
class="card dark:bg-base-100 border-l-4 bg-white shadow-xl transition-all hover:scale-105 hover:shadow-2xl"
|
||||
style="border-color: {time.cor}"
|
||||
@@ -1378,15 +1318,12 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each solicitacoesFiltradas as solicitacao}
|
||||
{#each solicitacoesFiltradas as solicitacao (solicitacao._id)}
|
||||
<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
|
||||
>{solicitacao.periodos.reduce((acc, p) => acc + p.diasCorridos, 0)} dias</td
|
||||
>
|
||||
<td>
|
||||
<div class={`badge ${getStatusBadge(solicitacao.status)}`}>
|
||||
@@ -1660,7 +1597,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each ausenciasFiltradas as ausencia}
|
||||
{#each ausenciasFiltradas as ausencia (ausencia._id)}
|
||||
<tr>
|
||||
<td>
|
||||
{new Date(ausencia.dataInicio).toLocaleDateString('pt-BR')} até {new Date(
|
||||
@@ -1751,7 +1688,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each solicitacoesSubordinados as solicitacao}
|
||||
{#each solicitacoesSubordinados as solicitacao (solicitacao._id)}
|
||||
<tr class="hover:bg-base-200 transition-colors">
|
||||
<td>
|
||||
<div class="font-bold">
|
||||
@@ -1773,10 +1710,7 @@
|
||||
<td class="font-semibold">{solicitacao.anoReferencia}</td>
|
||||
<td class="font-semibold">{solicitacao.periodos.length}</td>
|
||||
<td class="text-lg font-bold"
|
||||
>{solicitacao.periodos.reduce(
|
||||
(acc: number, p: any) => acc + p.diasCorridos,
|
||||
0
|
||||
)}</td
|
||||
>{solicitacao.periodos.reduce((acc, p) => acc + p.diasCorridos, 0)}</td
|
||||
>
|
||||
<td>
|
||||
<div
|
||||
@@ -1902,7 +1836,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each ausenciasSubordinados as ausencia}
|
||||
{#each ausenciasSubordinados as ausencia (ausencia._id)}
|
||||
<tr class="hover:bg-base-200 transition-colors">
|
||||
<td>
|
||||
<div class="font-bold">
|
||||
@@ -1921,7 +1855,7 @@
|
||||
{/if}
|
||||
</td>
|
||||
<td class="font-semibold">
|
||||
{new Date(ausencia.dataInicio).toLocaleDateString('pt-BR')} até{' '}
|
||||
{new Date(ausencia.dataInicio).toLocaleDateString('pt-BR')} até
|
||||
{new Date(ausencia.dataFim).toLocaleDateString('pt-BR')}
|
||||
</td>
|
||||
<td class="text-lg font-bold">
|
||||
@@ -2041,10 +1975,8 @@
|
||||
<div
|
||||
class="ring-primary ring-offset-base-100 h-32 w-32 rounded-full shadow-2xl ring-4 ring-offset-4"
|
||||
>
|
||||
{#if fotoPerfilLocal}
|
||||
<img src={fotoPerfilLocal} alt="Foto atual" class="object-cover" />
|
||||
{:else if avatarLocal}
|
||||
<img src={avatarLocal} alt="Avatar atual" class="object-cover" />
|
||||
{#if currentUser.data?.fotoPerfilUrl}
|
||||
<img src={currentUser.data.fotoPerfilUrl} alt="Foto atual" class="object-cover" />
|
||||
{:else}
|
||||
<div class="bg-primary text-primary-content flex items-center justify-center">
|
||||
<span class="text-4xl font-bold"
|
||||
@@ -2121,7 +2053,7 @@
|
||||
<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"
|
||||
>
|
||||
{#each avatarGallery as avatar}
|
||||
{#each avatarGallery as avatar (avatar.id)}
|
||||
<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'}`}
|
||||
|
||||
Reference in New Issue
Block a user