feat: add UserAvatar component to display employee profile pictures in absence and vacation requests; update backend to include profile picture URLs for employees

This commit is contained in:
2025-12-02 14:54:45 -03:00
parent ffa4dc5fb2
commit 75ab4d261d
4 changed files with 94 additions and 15 deletions

View File

@@ -3,6 +3,7 @@
import { api } from '@sgse-app/backend/convex/_generated/api'; import { api } from '@sgse-app/backend/convex/_generated/api';
import type { Id, Doc } from '@sgse-app/backend/convex/_generated/dataModel'; import type { Id, Doc } from '@sgse-app/backend/convex/_generated/dataModel';
import ErrorModal from './ErrorModal.svelte'; import ErrorModal from './ErrorModal.svelte';
import UserAvatar from './chat/UserAvatar.svelte';
type SolicitacaoAusencia = Doc<'solicitacoesAusencias'> & { type SolicitacaoAusencia = Doc<'solicitacoesAusencias'> & {
funcionario?: Doc<'funcionarios'> | null; funcionario?: Doc<'funcionarios'> | null;
@@ -166,9 +167,16 @@
<p class="mb-2 text-sm font-semibold uppercase tracking-wide text-base-content/60"> <p class="mb-2 text-sm font-semibold uppercase tracking-wide text-base-content/60">
Nome Nome
</p> </p>
<p class="text-lg font-bold text-base-content"> <div class="flex items-center gap-3">
{solicitacao.funcionario?.nome || 'N/A'} <UserAvatar
</p> fotoPerfilUrl={solicitacao.funcionario?.fotoPerfilUrl}
nome={solicitacao.funcionario?.nome || 'N/A'}
size="md"
/>
<p class="text-lg font-bold text-base-content">
{solicitacao.funcionario?.nome || 'N/A'}
</p>
</div>
</div> </div>
{#if solicitacao.time} {#if solicitacao.time}
<div class="rounded-xl bg-base-200/50 p-4 transition-all hover:bg-base-200"> <div class="rounded-xl bg-base-200/50 p-4 transition-all hover:bg-base-200">

View File

@@ -2,6 +2,7 @@
import { useConvexClient } from 'convex-svelte'; import { useConvexClient } from 'convex-svelte';
import { api } from '@sgse-app/backend/convex/_generated/api'; import { api } from '@sgse-app/backend/convex/_generated/api';
import type { Id, Doc } from '@sgse-app/backend/convex/_generated/dataModel'; import type { Id, Doc } from '@sgse-app/backend/convex/_generated/dataModel';
import UserAvatar from './chat/UserAvatar.svelte';
type PeriodoFerias = Doc<'ferias'> & { type PeriodoFerias = Doc<'ferias'> & {
funcionario?: Doc<'funcionarios'> | null; funcionario?: Doc<'funcionarios'> | null;
@@ -215,13 +216,20 @@
<div class="card bg-base-100 shadow-xl"> <div class="card bg-base-100 shadow-xl">
<div class="card-body"> <div class="card-body">
<div class="mb-4 flex items-start justify-between"> <div class="mb-4 flex items-start justify-between">
<div> <div class="flex items-center gap-3">
<h2 class="card-title text-2xl"> <UserAvatar
{periodo.funcionario?.nome || 'Funcionário'} fotoPerfilUrl={periodo.funcionario?.fotoPerfilUrl}
</h2> nome={periodo.funcionario?.nome || 'Funcionário'}
<p class="text-base-content/70 mt-1 text-sm"> size="md"
Ano de Referência: {periodo.anoReferencia} />
</p> <div>
<h2 class="card-title text-2xl">
{periodo.funcionario?.nome || 'Funcionário'}
</h2>
<p class="text-base-content/70 mt-1 text-sm">
Ano de Referência: {periodo.anoReferencia}
</p>
</div>
</div> </div>
<div class={`badge ${getStatusBadge(periodo.status)} badge-lg`}> <div class={`badge ${getStatusBadge(periodo.status)} badge-lg`}>
{getStatusTexto(periodo.status)} {getStatusTexto(periodo.status)}

View File

@@ -97,7 +97,7 @@ export const listarSolicitacoesSubordinados = query({
.collect(); .collect();
const solicitacoes: Array<Doc<"solicitacoesAusencias"> & { const solicitacoes: Array<Doc<"solicitacoesAusencias"> & {
funcionario: Doc<"funcionarios"> | null; funcionario: (Doc<"funcionarios"> & { fotoPerfilUrl: string | null }) | null;
time: Doc<"times"> | null; time: Doc<"times"> | null;
}> = []; }> = [];
@@ -122,9 +122,25 @@ export const listarSolicitacoesSubordinados = query({
// Adicionar info do funcionário // Adicionar info do funcionário
for (const s of solic) { for (const s of solic) {
const funcionario = await ctx.db.get(s.funcionarioId); const funcionario = await ctx.db.get(s.funcionarioId);
// Buscar usuário do funcionário para obter fotoPerfilUrl
let fotoPerfilUrl: string | null = null;
if (funcionario) {
const usuario = await ctx.db
.query("usuarios")
.withIndex("by_funcionarioId", (q) => q.eq("funcionarioId", funcionario._id))
.first();
if (usuario?.fotoPerfil) {
fotoPerfilUrl = await ctx.storage.getUrl(usuario.fotoPerfil);
}
}
solicitacoes.push({ solicitacoes.push({
...s, ...s,
funcionario, funcionario: funcionario ? {
...funcionario,
fotoPerfilUrl,
} : null,
time, time,
}); });
} }
@@ -143,6 +159,19 @@ export const obterDetalhes = query({
if (!solicitacao) return null; if (!solicitacao) return null;
const funcionario = await ctx.db.get(solicitacao.funcionarioId); const funcionario = await ctx.db.get(solicitacao.funcionarioId);
// Buscar usuário do funcionário para obter fotoPerfilUrl
let fotoPerfilUrl: string | null = null;
if (funcionario) {
const usuario = await ctx.db
.query("usuarios")
.withIndex("by_funcionarioId", (q) => q.eq("funcionarioId", funcionario._id))
.first();
if (usuario?.fotoPerfil) {
fotoPerfilUrl = await ctx.storage.getUrl(usuario.fotoPerfil);
}
}
let gestor = null; let gestor = null;
if (solicitacao.gestorId) { if (solicitacao.gestorId) {
gestor = await ctx.db.get(solicitacao.gestorId); gestor = await ctx.db.get(solicitacao.gestorId);
@@ -164,7 +193,10 @@ export const obterDetalhes = query({
return { return {
...solicitacao, ...solicitacao,
funcionario, funcionario: funcionario ? {
...funcionario,
fotoPerfilUrl,
} : null,
gestor, gestor,
time, time,
}; };

View File

@@ -213,6 +213,18 @@ export const listarSolicitacoesSubordinados = query({
todasFerias.map(async (ferias) => { todasFerias.map(async (ferias) => {
const funcionario = await ctx.db.get(ferias.funcionarioId); const funcionario = await ctx.db.get(ferias.funcionarioId);
// Buscar usuário do funcionário para obter fotoPerfilUrl
let fotoPerfilUrl: string | null = null;
if (funcionario) {
const usuario = await ctx.db
.query("usuarios")
.withIndex("by_funcionarioId", (q) => q.eq("funcionarioId", funcionario._id))
.first();
if (usuario?.fotoPerfil) {
fotoPerfilUrl = await ctx.storage.getUrl(usuario.fotoPerfil);
}
}
// Buscar time do funcionário // Buscar time do funcionário
const membroTime = await ctx.db const membroTime = await ctx.db
.query("timesMembros") .query("timesMembros")
@@ -229,7 +241,10 @@ export const listarSolicitacoesSubordinados = query({
return { return {
...ferias, ...ferias,
funcionario, funcionario: funcionario ? {
...funcionario,
fotoPerfilUrl,
} : null,
time, time,
}; };
}) })
@@ -250,6 +265,19 @@ export const obterDetalhes = query({
if (!ferias) return null; if (!ferias) return null;
const funcionario = await ctx.db.get(ferias.funcionarioId); const funcionario = await ctx.db.get(ferias.funcionarioId);
// Buscar usuário do funcionário para obter fotoPerfilUrl
let fotoPerfilUrl: string | null = null;
if (funcionario) {
const usuario = await ctx.db
.query("usuarios")
.withIndex("by_funcionarioId", (q) => q.eq("funcionarioId", funcionario._id))
.first();
if (usuario?.fotoPerfil) {
fotoPerfilUrl = await ctx.storage.getUrl(usuario.fotoPerfil);
}
}
let gestor = null; let gestor = null;
if (ferias.gestorId) { if (ferias.gestorId) {
gestor = await ctx.db.get(ferias.gestorId); gestor = await ctx.db.get(ferias.gestorId);
@@ -271,7 +299,10 @@ export const obterDetalhes = query({
return { return {
...ferias, ...ferias,
funcionario, funcionario: funcionario ? {
...funcionario,
fotoPerfilUrl,
} : null,
gestor, gestor,
time, time,
}; };