feat: enhance file upload component and backend schema
- Updated the FileUpload component to improve file handling and state management, including better cancellation logic and input handling. - Added a new alias in the Svelte configuration for easier backend access. - Enhanced the backend schema to include a gestorSuperiorId for better team management and query capabilities. - Refactored queries and mutations in the backend to support the new structure and improve data retrieval for subordinate teams.
This commit is contained in:
@@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
const client = useConvexClient();
|
const client = useConvexClient();
|
||||||
|
|
||||||
let fileInput: HTMLInputElement;
|
let fileInput: HTMLInputElement | null = null;
|
||||||
let uploading = $state(false);
|
let uploading = $state(false);
|
||||||
let error = $state<string | null>(null);
|
let error = $state<string | null>(null);
|
||||||
let fileName = $state<string>("");
|
let fileName = $state<string>("");
|
||||||
@@ -50,31 +50,52 @@
|
|||||||
|
|
||||||
// Buscar URL do arquivo quando houver um storageId
|
// Buscar URL do arquivo quando houver um storageId
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (value && !fileName) {
|
if (!value || fileName) {
|
||||||
// Tem storageId mas não é um upload recente
|
return;
|
||||||
loadExistingFile(value);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
async function loadExistingFile(storageId: string) {
|
let cancelled = false;
|
||||||
|
const storageId = value;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const url = await client.storage.getUrl(storageId as any);
|
const url = await client.storage.getUrl(storageId as any);
|
||||||
if (url) {
|
if (!url || cancelled) {
|
||||||
async function handleFileSelect(event: Event) {
|
return;
|
||||||
const target = event.target as HTMLInputElement;
|
}
|
||||||
const file = target.files?.[0];
|
|
||||||
// Detectar tipo pelo URL ou assumir PDF
|
fileUrl = url;
|
||||||
if (url.includes('.pdf') || url.includes('application/pdf')) {
|
|
||||||
|
const path = url.split('?')[0] ?? '';
|
||||||
|
const nameFromUrl = path.split('/').pop() ?? 'arquivo';
|
||||||
|
fileName = decodeURIComponent(nameFromUrl);
|
||||||
|
|
||||||
|
const extension = fileName.toLowerCase().split('.').pop();
|
||||||
|
const isPdf =
|
||||||
|
extension === 'pdf' || url.includes('.pdf') || url.includes('application/pdf');
|
||||||
|
|
||||||
|
if (isPdf) {
|
||||||
fileType = 'application/pdf';
|
fileType = 'application/pdf';
|
||||||
|
previewUrl = null;
|
||||||
} else {
|
} else {
|
||||||
fileType = 'image/jpeg';
|
fileType = 'image/jpeg';
|
||||||
previewUrl = url; // Para imagens, a URL serve como preview
|
previewUrl = url;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (!cancelled) {
|
||||||
console.error('Erro ao carregar arquivo existente:', err);
|
console.error('Erro ao carregar arquivo existente:', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
async function handleFileSelect(event: Event) {
|
||||||
|
const target = event.target as HTMLInputElement;
|
||||||
|
const file = target.files?.[0];
|
||||||
|
|
||||||
error = null;
|
error = null;
|
||||||
|
|
||||||
@@ -144,6 +165,17 @@
|
|||||||
function openFileDialog() {
|
function openFileDialog() {
|
||||||
fileInput?.click();
|
fileInput?.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setFileInput(node: HTMLInputElement) {
|
||||||
|
fileInput = node;
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
if (fileInput === node) {
|
||||||
|
fileInput = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="form-control w-full">
|
<div class="form-control w-full">
|
||||||
@@ -175,7 +207,7 @@
|
|||||||
<input
|
<input
|
||||||
id="file-upload-input"
|
id="file-upload-input"
|
||||||
type="file"
|
type="file"
|
||||||
bind:this={fileInput}
|
use:setFileInput
|
||||||
onchange={handleFileSelect}
|
onchange={handleFileSelect}
|
||||||
accept=".pdf,.jpg,.jpeg,.png"
|
accept=".pdf,.jpg,.jpeg,.png"
|
||||||
class="hidden"
|
class="hidden"
|
||||||
@@ -194,7 +226,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="bg-success/10 flex h-12 w-12 items-center justify-center rounded">
|
<div class="bg-success/10 flex h-12 w-12 items-center justify-center rounded">
|
||||||
<File class="text-success h-6 w-6" strokeWidth={2} />
|
<FileIcon class="text-success h-6 w-6" strokeWidth={2} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { FileText, ClipboardCopy, Plus, Users, FileDoc } from "lucide-svelte";
|
import { FileText, ClipboardCopy, Plus, Users, File } from "lucide-svelte";
|
||||||
import { resolve } from "$app/paths";
|
import { resolve } from "$app/paths";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="flex flex-col items-center justify-center py-12 text-center">
|
<div class="flex flex-col items-center justify-center py-12 text-center">
|
||||||
<div class="mb-6">
|
<div class="mb-6">
|
||||||
<FileDoc class="h-24 w-24 text-base-content/20" strokeWidth={1.5} />
|
<File class="h-24 w-24 text-base-content/20" strokeWidth={1.5} />
|
||||||
</div>
|
</div>
|
||||||
<h2 class="text-2xl font-bold mb-2">Módulo em Desenvolvimento</h2>
|
<h2 class="text-2xl font-bold mb-2">Módulo em Desenvolvimento</h2>
|
||||||
<p class="text-base-content/70 max-w-md mb-6">
|
<p class="text-base-content/70 max-w-md mb-6">
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="flex items-center gap-3 mb-2">
|
<div class="flex items-center gap-3 mb-2">
|
||||||
<div class="p-2 bg-primary/10 rounded-lg">
|
<div class="p-2 bg-primary/10 rounded-lg">
|
||||||
<FileDoc class="h-6 w-6 text-primary" strokeWidth={2} />
|
<File class="h-6 w-6 text-primary" strokeWidth={2} />
|
||||||
</div>
|
</div>
|
||||||
<h4 class="font-semibold">Documentação</h4>
|
<h4 class="font-semibold">Documentação</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,12 +8,21 @@
|
|||||||
import CalendarioAusencias from '$lib/components/ausencias/CalendarioAusencias.svelte';
|
import CalendarioAusencias from '$lib/components/ausencias/CalendarioAusencias.svelte';
|
||||||
import { generateAvatarGallery } from '$lib/utils/avatars';
|
import { generateAvatarGallery } from '$lib/utils/avatars';
|
||||||
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
|
import type { Id } from '@sgse-app/backend/convex/_generated/dataModel';
|
||||||
import { X, Calendar } from 'lucide-svelte';
|
|
||||||
import type { FunctionReturnType } from 'convex/server';
|
import type { FunctionReturnType } from 'convex/server';
|
||||||
|
import { X, Calendar } from 'lucide-svelte';
|
||||||
|
|
||||||
const client = useConvexClient();
|
const client = useConvexClient();
|
||||||
const currentUser = useQuery(api.auth.getCurrentUser, {});
|
const currentUser = useQuery(api.auth.getCurrentUser, {});
|
||||||
|
|
||||||
|
type FuncionarioAtual = FunctionReturnType<typeof api.funcionarios.getCurrent>;
|
||||||
|
type TimeAtual = FunctionReturnType<typeof api.times.obterTimeFuncionario>;
|
||||||
|
type MinhasSolicitacoes = FunctionReturnType<typeof api.ferias.listarMinhasSolicitacoes>;
|
||||||
|
type MinhasAusencias = FunctionReturnType<typeof api.ausencias.listarMinhasSolicitacoes>;
|
||||||
|
let funcionarioEstavel = $state<FuncionarioAtual | null>(null);
|
||||||
|
let meuTimeEstavel = $state<TimeAtual | null>(null);
|
||||||
|
let minhasSolicitacoesEstaveis = $state<MinhasSolicitacoes>([]);
|
||||||
|
let minhasAusenciasEstaveis = $state<MinhasAusencias>([]);
|
||||||
|
|
||||||
let abaAtiva = $state<
|
let abaAtiva = $state<
|
||||||
'meu-perfil' | 'minhas-ferias' | 'minhas-ausencias' | 'aprovar-ferias' | 'aprovar-ausencias'
|
'meu-perfil' | 'minhas-ferias' | 'minhas-ausencias' | 'aprovar-ferias' | 'aprovar-ausencias'
|
||||||
>('meu-perfil');
|
>('meu-perfil');
|
||||||
@@ -49,6 +58,14 @@
|
|||||||
currentUser?.data?.funcionarioId ? useQuery(api.funcionarios.getCurrent, {}) : { data: null }
|
currentUser?.data?.funcionarioId ? useQuery(api.funcionarios.getCurrent, {}) : { data: null }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
if (funcionarioQuery?.data) {
|
||||||
|
funcionarioEstavel = funcionarioQuery.data;
|
||||||
|
} else if (!currentUser?.data?.funcionarioId) {
|
||||||
|
funcionarioEstavel = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const solicitacoesSubordinadosQuery = $derived(
|
const solicitacoesSubordinadosQuery = $derived(
|
||||||
currentUser?.data?._id
|
currentUser?.data?._id
|
||||||
? useQuery(api.ferias.listarSolicitacoesSubordinados, {
|
? useQuery(api.ferias.listarSolicitacoesSubordinados, {
|
||||||
@@ -66,104 +83,71 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
const minhasSolicitacoesQuery = $derived(
|
const minhasSolicitacoesQuery = $derived(
|
||||||
funcionarioQuery.data
|
funcionarioEstavel?._id
|
||||||
? useQuery(api.ferias.listarMinhasSolicitacoes, {
|
? useQuery(api.ferias.listarMinhasSolicitacoes, {
|
||||||
funcionarioId: funcionarioQuery.data._id
|
funcionarioId: funcionarioEstavel._id
|
||||||
})
|
})
|
||||||
: { data: [] }
|
: { data: [] }
|
||||||
);
|
);
|
||||||
|
|
||||||
const minhasAusenciasQuery = $derived(
|
const minhasAusenciasQuery = $derived(
|
||||||
funcionarioQuery.data
|
funcionarioEstavel?._id
|
||||||
? useQuery(api.ausencias.listarMinhasSolicitacoes, {
|
? useQuery(api.ausencias.listarMinhasSolicitacoes, {
|
||||||
funcionarioId: funcionarioQuery.data._id
|
funcionarioId: funcionarioEstavel._id
|
||||||
})
|
})
|
||||||
: { data: [] }
|
: { data: [] }
|
||||||
);
|
);
|
||||||
|
|
||||||
const meuTimeQuery = $derived(
|
const meuTimeQuery = $derived(
|
||||||
funcionarioQuery.data
|
funcionarioEstavel?._id
|
||||||
? useQuery(api.times.obterTimeFuncionario, {
|
? useQuery(api.times.obterTimeFuncionario, {
|
||||||
funcionarioId: funcionarioQuery.data._id
|
funcionarioId: funcionarioEstavel._id
|
||||||
})
|
})
|
||||||
: { data: null }
|
: { data: null }
|
||||||
);
|
);
|
||||||
|
|
||||||
// Query para times onde o usuário é gestor - usando $derived para garantir reatividade
|
$effect(() => {
|
||||||
const meusTimesGestorQuery = $derived(
|
if (meuTimeQuery?.data) {
|
||||||
currentUser?.data?._id
|
meuTimeEstavel = meuTimeQuery.data;
|
||||||
? useQuery(api.times.listarPorGestor, {
|
} else if (!funcionarioEstavel?._id) {
|
||||||
gestorId: currentUser.data._id as Id<'usuarios'>
|
meuTimeEstavel = null;
|
||||||
})
|
}
|
||||||
: { data: [] }
|
});
|
||||||
);
|
|
||||||
|
|
||||||
const funcionario = $derived(funcionarioQuery.data);
|
const funcionario = $derived(funcionarioEstavel);
|
||||||
const solicitacoesSubordinados = $derived(solicitacoesSubordinadosQuery?.data || []);
|
const solicitacoesSubordinados = $derived(solicitacoesSubordinadosQuery?.data || []);
|
||||||
const ausenciasSubordinados = $derived(ausenciasSubordinadosQuery?.data || []);
|
const ausenciasSubordinados = $derived(ausenciasSubordinadosQuery?.data || []);
|
||||||
const minhasSolicitacoes = $derived(minhasSolicitacoesQuery?.data || []);
|
const meuTime = $derived(meuTimeEstavel);
|
||||||
const minhasAusencias = $derived(minhasAusenciasQuery?.data || []);
|
|
||||||
const meuTime = $derived(meuTimeQuery?.data);
|
|
||||||
|
|
||||||
// Extração de meusTimesGestor
|
|
||||||
const meusTimesGestor = $derived(meusTimesGestorQuery?.data || []);
|
|
||||||
|
|
||||||
// Estado estável para controlar se é gestor (evita desaparecimento das abas)
|
|
||||||
let ehGestorEstavel = $state(false);
|
|
||||||
let ultimaVerificacaoGestor = $state<number | null>(null);
|
|
||||||
|
|
||||||
// Calcular se é gestor - com lógica mais robusta (sem atualizar estado aqui)
|
|
||||||
const ehGestorCalculado = $derived.by(() => {
|
|
||||||
// Verificar se tem times como gestor
|
|
||||||
const temTimesComoGestor = (meusTimesGestor || []).length > 0;
|
|
||||||
|
|
||||||
// Verificar se tem role de TI que pode aprovar (TI_MASTER, TI_ADMIN)
|
|
||||||
const rolePermiteAprovacao = currentUser?.data?.role?.nome === 'TI_MASTER' ||
|
|
||||||
currentUser?.data?.role?.nome === 'TI_ADMIN';
|
|
||||||
|
|
||||||
// Verificar se tem solicitações de subordinados (indica que é gestor)
|
|
||||||
const temSolicitacoesSubordinados = (solicitacoesSubordinados || []).length > 0 ||
|
|
||||||
(ausenciasSubordinados || []).length > 0;
|
|
||||||
|
|
||||||
// É gestor se: tem times OU tem role de TI OU tem solicitações de subordinados
|
|
||||||
return temTimesComoGestor || rolePermiteAprovacao || temSolicitacoesSubordinados;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Efeito para atualizar o estado estável (evita desaparecimento das abas)
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const resultado = ehGestorCalculado;
|
if (Array.isArray(minhasSolicitacoesQuery?.data)) {
|
||||||
|
minhasSolicitacoesEstaveis = minhasSolicitacoesQuery.data;
|
||||||
// Log para depuração (apenas quando mudar)
|
} else if (!funcionarioEstavel?._id) {
|
||||||
if (import.meta.env.DEV && resultado !== ehGestorEstavel) {
|
minhasSolicitacoesEstaveis = [];
|
||||||
console.log('🔍 [Perfil] Status de gestor mudou:', {
|
|
||||||
temTimesComoGestor: (meusTimesGestor || []).length > 0,
|
|
||||||
rolePermiteAprovacao: currentUser?.data?.role?.nome === 'TI_MASTER' || currentUser?.data?.role?.nome === 'TI_ADMIN',
|
|
||||||
role: currentUser?.data?.role?.nome,
|
|
||||||
temSolicitacoesSubordinados: (solicitacoesSubordinados || []).length > 0 || (ausenciasSubordinados || []).length > 0,
|
|
||||||
resultado,
|
|
||||||
meusTimesGestor: meusTimesGestor?.length || 0,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Atualizar estado estável apenas se o resultado for true (para manter as abas visíveis)
|
|
||||||
// ou se já passou tempo suficiente desde a última verificação
|
|
||||||
const agora = Date.now();
|
|
||||||
if (resultado) {
|
|
||||||
ehGestorEstavel = true;
|
|
||||||
ultimaVerificacaoGestor = agora;
|
|
||||||
} else if (ultimaVerificacaoGestor === null || (agora - ultimaVerificacaoGestor) > 5000) {
|
|
||||||
// Só atualiza para false se passou mais de 5 segundos desde a última verificação positiva
|
|
||||||
// Isso evita que as abas desapareçam durante atualizações temporárias das queries
|
|
||||||
ehGestorEstavel = resultado;
|
|
||||||
if (!resultado) {
|
|
||||||
ultimaVerificacaoGestor = agora;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Valor final usado para renderizar as abas
|
$effect(() => {
|
||||||
const ehGestor = $derived(ehGestorEstavel);
|
if (Array.isArray(minhasAusenciasQuery?.data)) {
|
||||||
|
minhasAusenciasEstaveis = minhasAusenciasQuery.data;
|
||||||
|
} else if (!funcionarioEstavel?._id) {
|
||||||
|
minhasAusenciasEstaveis = [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const minhasSolicitacoes = $derived(minhasSolicitacoesEstaveis);
|
||||||
|
const minhasAusencias = $derived(minhasAusenciasEstaveis);
|
||||||
|
|
||||||
|
const timesSubordinadosQuery = $derived(useQuery(api.times.listarSubordinadosDoGestorAtual, {}));
|
||||||
|
const timesSubordinados = $derived(timesSubordinadosQuery?.data || []);
|
||||||
|
// Times gerenciados usam a query que já infere o gestor logado
|
||||||
|
const meusTimesGestor = $derived(timesSubordinados);
|
||||||
|
|
||||||
|
const rolePermiteAprovacao = $derived(
|
||||||
|
currentUser?.data?.role?.nome === 'TI_MASTER' || currentUser?.data?.role?.nome === 'TI_ADMIN'
|
||||||
|
);
|
||||||
|
|
||||||
|
const ehGestor = $derived((timesSubordinados || []).length > 0 || rolePermiteAprovacao);
|
||||||
|
|
||||||
// Filtrar minhas solicitações
|
// Filtrar minhas solicitações
|
||||||
const solicitacoesFiltradas = $derived(
|
const solicitacoesFiltradas = $derived(
|
||||||
@@ -195,7 +179,8 @@
|
|||||||
total: minhasSolicitacoes.length,
|
total: minhasSolicitacoes.length,
|
||||||
aguardando: minhasSolicitacoes.filter((s) => s.status === 'aguardando_aprovacao').length,
|
aguardando: minhasSolicitacoes.filter((s) => s.status === 'aguardando_aprovacao').length,
|
||||||
aprovadas: minhasSolicitacoes.filter(
|
aprovadas: minhasSolicitacoes.filter(
|
||||||
(s) => s.status === 'aprovado' || s.status === 'data_ajustada_aprovada' || s.status === 'EmFérias'
|
(s) =>
|
||||||
|
s.status === 'aprovado' || s.status === 'data_ajustada_aprovada' || s.status === 'EmFérias'
|
||||||
).length,
|
).length,
|
||||||
reprovadas: minhasSolicitacoes.filter((s) => s.status === 'reprovado').length,
|
reprovadas: minhasSolicitacoes.filter((s) => s.status === 'reprovado').length,
|
||||||
emFerias: funcionario?.statusFerias === 'em_ferias' ? 1 : 0
|
emFerias: funcionario?.statusFerias === 'em_ferias' ? 1 : 0
|
||||||
@@ -1396,7 +1381,9 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{periodo.anoReferencia}</td>
|
<td>{periodo.anoReferencia}</td>
|
||||||
<td>
|
<td>
|
||||||
{formatarDataString(periodo.dataInicio)} - {formatarDataString(periodo.dataFim)}
|
{formatarDataString(periodo.dataInicio)} - {formatarDataString(
|
||||||
|
periodo.dataFim
|
||||||
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td class="font-bold">{periodo.diasFerias} dias</td>
|
<td class="font-bold">{periodo.diasFerias} dias</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -1773,9 +1760,8 @@
|
|||||||
{#if periodo.time}
|
{#if periodo.time}
|
||||||
<div
|
<div
|
||||||
class="badge badge-lg font-semibold"
|
class="badge badge-lg font-semibold"
|
||||||
style="background-color: {periodo.time
|
style="background-color: {periodo.time.cor}20; border-color: {periodo
|
||||||
.cor}20; border-color: {periodo.time.cor}; color: {periodo.time
|
.time.cor}; color: {periodo.time.cor}"
|
||||||
.cor}"
|
|
||||||
>
|
>
|
||||||
{periodo.time.nome}
|
{periodo.time.nome}
|
||||||
</div>
|
</div>
|
||||||
@@ -1783,7 +1769,9 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="font-semibold">{periodo.anoReferencia}</td>
|
<td class="font-semibold">{periodo.anoReferencia}</td>
|
||||||
<td class="font-semibold">
|
<td class="font-semibold">
|
||||||
{formatarDataString(periodo.dataInicio)} - {formatarDataString(periodo.dataFim)}
|
{formatarDataString(periodo.dataInicio)} - {formatarDataString(
|
||||||
|
periodo.dataFim
|
||||||
|
)}
|
||||||
</td>
|
</td>
|
||||||
<td class="text-lg font-bold">{periodo.diasFerias}</td>
|
<td class="text-lg font-bold">{periodo.diasFerias}</td>
|
||||||
<td>
|
<td>
|
||||||
@@ -2286,9 +2274,18 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
<div
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
class="modal-backdrop"
|
||||||
<div class="modal-backdrop" onclick={() => (mostrarWizard = false)}></div>
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
onclick={() => (mostrarWizard = false)}
|
||||||
|
onkeydown={(event) => {
|
||||||
|
if (event.key === 'Enter' || event.key === ' ') {
|
||||||
|
event.preventDefault();
|
||||||
|
mostrarWizard = false;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
</dialog>
|
</dialog>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ const config = {
|
|||||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||||
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||||
adapter: adapter(),
|
adapter: adapter(),
|
||||||
|
alias: {
|
||||||
|
'@sgse-app/backend': '../../packages/backend',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -299,9 +299,12 @@ export default defineSchema({
|
|||||||
nome: v.string(),
|
nome: v.string(),
|
||||||
descricao: v.optional(v.string()),
|
descricao: v.optional(v.string()),
|
||||||
gestorId: v.id("usuarios"),
|
gestorId: v.id("usuarios"),
|
||||||
|
gestorSuperiorId: v.optional(v.id("usuarios")),
|
||||||
ativo: v.boolean(),
|
ativo: v.boolean(),
|
||||||
cor: v.optional(v.string()), // Cor para identificação visual
|
cor: v.optional(v.string()), // Cor para identificação visual
|
||||||
}).index("by_gestor", ["gestorId"]),
|
})
|
||||||
|
.index("by_gestor", ["gestorId"])
|
||||||
|
.index("by_gestor_superior", ["gestorSuperiorId"]),
|
||||||
|
|
||||||
timesMembros: defineTable({
|
timesMembros: defineTable({
|
||||||
timeId: v.id("times"),
|
timeId: v.id("times"),
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { v } from "convex/values";
|
import { v } from "convex/values";
|
||||||
import { mutation, query } from "./_generated/server";
|
import { mutation, query } from "./_generated/server";
|
||||||
import { Id, Doc } from "./_generated/dataModel";
|
import { getCurrentUserFunction } from "./auth";
|
||||||
|
|
||||||
// Query: Listar todos os times
|
// Query: Listar todos os times
|
||||||
// Tipo inferido automaticamente pelo Convex
|
// Tipo inferido automaticamente pelo Convex
|
||||||
@@ -127,12 +127,67 @@ export const listarPorGestor = query({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const listarSubordinadosDoGestorAtual = query({
|
||||||
|
args: {},
|
||||||
|
handler: async (ctx) => {
|
||||||
|
const usuario = await getCurrentUserFunction(ctx);
|
||||||
|
if (!usuario) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const timesGestor = await ctx.db
|
||||||
|
.query("times")
|
||||||
|
.withIndex("by_gestor", (q) => q.eq("gestorId", usuario._id))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
const timesComoSuperior = await ctx.db
|
||||||
|
.query("times")
|
||||||
|
.withIndex("by_gestor_superior", (q) => q.eq("gestorSuperiorId", usuario._id))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
const timesMap = new Map(
|
||||||
|
[...timesGestor, ...timesComoSuperior]
|
||||||
|
.filter((time) => time.ativo)
|
||||||
|
.map((time) => [time._id, time])
|
||||||
|
);
|
||||||
|
|
||||||
|
const resultado = [];
|
||||||
|
|
||||||
|
for (const time of timesMap.values()) {
|
||||||
|
const membrosRelacoes = await ctx.db
|
||||||
|
.query("timesMembros")
|
||||||
|
.withIndex("by_time_and_ativo", (q) => q.eq("timeId", time._id).eq("ativo", true))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
const membros = [];
|
||||||
|
for (const rel of membrosRelacoes) {
|
||||||
|
const funcionario = await ctx.db.get(rel.funcionarioId);
|
||||||
|
if (funcionario) {
|
||||||
|
membros.push({
|
||||||
|
relacaoId: rel._id,
|
||||||
|
funcionario,
|
||||||
|
dataEntrada: rel.dataEntrada,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resultado.push({
|
||||||
|
...time,
|
||||||
|
membros,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultado;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Mutation: Criar time
|
// Mutation: Criar time
|
||||||
export const criar = mutation({
|
export const criar = mutation({
|
||||||
args: {
|
args: {
|
||||||
nome: v.string(),
|
nome: v.string(),
|
||||||
descricao: v.optional(v.string()),
|
descricao: v.optional(v.string()),
|
||||||
gestorId: v.id("usuarios"),
|
gestorId: v.id("usuarios"),
|
||||||
|
gestorSuperiorId: v.optional(v.id("usuarios")),
|
||||||
cor: v.optional(v.string()),
|
cor: v.optional(v.string()),
|
||||||
},
|
},
|
||||||
returns: v.id("times"),
|
returns: v.id("times"),
|
||||||
@@ -141,6 +196,7 @@ export const criar = mutation({
|
|||||||
nome: args.nome,
|
nome: args.nome,
|
||||||
descricao: args.descricao,
|
descricao: args.descricao,
|
||||||
gestorId: args.gestorId,
|
gestorId: args.gestorId,
|
||||||
|
gestorSuperiorId: args.gestorSuperiorId ?? args.gestorId,
|
||||||
ativo: true,
|
ativo: true,
|
||||||
cor: args.cor || "#3B82F6",
|
cor: args.cor || "#3B82F6",
|
||||||
});
|
});
|
||||||
@@ -156,12 +212,16 @@ export const atualizar = mutation({
|
|||||||
nome: v.string(),
|
nome: v.string(),
|
||||||
descricao: v.optional(v.string()),
|
descricao: v.optional(v.string()),
|
||||||
gestorId: v.id("usuarios"),
|
gestorId: v.id("usuarios"),
|
||||||
|
gestorSuperiorId: v.optional(v.id("usuarios")),
|
||||||
cor: v.optional(v.string()),
|
cor: v.optional(v.string()),
|
||||||
},
|
},
|
||||||
returns: v.null(),
|
returns: v.null(),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
const { id, ...dados } = args;
|
const { id, ...dados } = args;
|
||||||
await ctx.db.patch(id, dados);
|
await ctx.db.patch(id, {
|
||||||
|
...dados,
|
||||||
|
gestorSuperiorId: dados.gestorSuperiorId ?? dados.gestorId,
|
||||||
|
});
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -185,6 +245,8 @@ export const desativar = mutation({
|
|||||||
ativo: false,
|
ativo: false,
|
||||||
dataSaida: Date.now(),
|
dataSaida: Date.now(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await ctx.db.patch(membro.funcionarioId, { gestorId: undefined });
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -210,6 +272,11 @@ export const adicionarMembro = mutation({
|
|||||||
throw new Error("Funcionário já está em um time ativo");
|
throw new Error("Funcionário já está em um time ativo");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const time = await ctx.db.get(args.timeId);
|
||||||
|
if (!time || !time.ativo) {
|
||||||
|
throw new Error("Time inválido ou inativo");
|
||||||
|
}
|
||||||
|
|
||||||
const membroId = await ctx.db.insert("timesMembros", {
|
const membroId = await ctx.db.insert("timesMembros", {
|
||||||
timeId: args.timeId,
|
timeId: args.timeId,
|
||||||
funcionarioId: args.funcionarioId,
|
funcionarioId: args.funcionarioId,
|
||||||
@@ -217,6 +284,8 @@ export const adicionarMembro = mutation({
|
|||||||
ativo: true,
|
ativo: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await ctx.db.patch(args.funcionarioId, { gestorId: time.gestorId });
|
||||||
|
|
||||||
return membroId;
|
return membroId;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -226,10 +295,16 @@ export const removerMembro = mutation({
|
|||||||
args: { membroId: v.id("timesMembros") },
|
args: { membroId: v.id("timesMembros") },
|
||||||
returns: v.null(),
|
returns: v.null(),
|
||||||
handler: async (ctx, args) => {
|
handler: async (ctx, args) => {
|
||||||
|
const membro = await ctx.db.get(args.membroId);
|
||||||
|
if (!membro) {
|
||||||
|
throw new Error("Membro não encontrado");
|
||||||
|
}
|
||||||
await ctx.db.patch(args.membroId, {
|
await ctx.db.patch(args.membroId, {
|
||||||
ativo: false,
|
ativo: false,
|
||||||
dataSaida: Date.now(),
|
dataSaida: Date.now(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await ctx.db.patch(membro.funcionarioId, { gestorId: undefined });
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -257,6 +332,11 @@ export const transferirMembro = mutation({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Adicionar ao novo time
|
// Adicionar ao novo time
|
||||||
|
const novoTime = await ctx.db.get(args.novoTimeId);
|
||||||
|
if (!novoTime || !novoTime.ativo) {
|
||||||
|
throw new Error("Novo time inválido ou inativo");
|
||||||
|
}
|
||||||
|
|
||||||
await ctx.db.insert("timesMembros", {
|
await ctx.db.insert("timesMembros", {
|
||||||
timeId: args.novoTimeId,
|
timeId: args.novoTimeId,
|
||||||
funcionarioId: args.funcionarioId,
|
funcionarioId: args.funcionarioId,
|
||||||
@@ -264,6 +344,8 @@ export const transferirMembro = mutation({
|
|||||||
ativo: true,
|
ativo: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await ctx.db.patch(args.funcionarioId, { gestorId: novoTime.gestorId });
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user