|
|
|
|
@@ -3,7 +3,7 @@
|
|
|
|
|
import type { Doc, Id } from '@sgse-app/backend/convex/_generated/dataModel';
|
|
|
|
|
import { useConvexClient, useQuery } from 'convex-svelte';
|
|
|
|
|
import { resolve } from '$app/paths';
|
|
|
|
|
import { ClipboardList, Plus, CheckCircle, XCircle, Package, Filter, User, Building2, Calendar, FileText, Search, Hash } from 'lucide-svelte';
|
|
|
|
|
import { ClipboardList, Plus, CheckCircle, XCircle, Package, Filter, User, Building2, Calendar, FileText, Search, Hash, Eye, AlertTriangle } from 'lucide-svelte';
|
|
|
|
|
import ErrorModal from '$lib/components/ErrorModal.svelte';
|
|
|
|
|
|
|
|
|
|
const client = useConvexClient();
|
|
|
|
|
@@ -172,27 +172,177 @@
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function aprovarRequisicao(id: Id<'requisicoesMaterial'>) {
|
|
|
|
|
let requisicaoParaAprovar = $state<Id<'requisicoesMaterial'> | null>(null);
|
|
|
|
|
let showModalAprovar = $state(false);
|
|
|
|
|
let problemasEstoqueAprovar = $state<Array<{
|
|
|
|
|
materialNome: string;
|
|
|
|
|
quantidadeSolicitada: number;
|
|
|
|
|
estoqueAtual: number;
|
|
|
|
|
falta: number;
|
|
|
|
|
}>>([]);
|
|
|
|
|
let processandoAprovar = $state(false);
|
|
|
|
|
|
|
|
|
|
function abrirModalAprovar(id: Id<'requisicoesMaterial'>) {
|
|
|
|
|
requisicaoParaAprovar = id;
|
|
|
|
|
problemasEstoqueAprovar = [];
|
|
|
|
|
showModalAprovar = true;
|
|
|
|
|
processandoAprovar = false;
|
|
|
|
|
|
|
|
|
|
// Verificar estoque quando abrir o modal
|
|
|
|
|
client.query(api.almoxarifado.verificarEstoqueRequisicao, { id }).then((verificacao) => {
|
|
|
|
|
if (!verificacao.temEstoqueSuficiente) {
|
|
|
|
|
problemasEstoqueAprovar = verificacao.problemasEstoque;
|
|
|
|
|
}
|
|
|
|
|
}).catch(() => {
|
|
|
|
|
// Ignorar erros na verificação prévia
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function fecharModalAprovar() {
|
|
|
|
|
showModalAprovar = false;
|
|
|
|
|
requisicaoParaAprovar = null;
|
|
|
|
|
problemasEstoqueAprovar = [];
|
|
|
|
|
processandoAprovar = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function confirmarAprovar() {
|
|
|
|
|
if (!requisicaoParaAprovar) return;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await client.mutation(api.almoxarifado.aprovarRequisicao, { id });
|
|
|
|
|
processandoAprovar = true;
|
|
|
|
|
await client.mutation(api.almoxarifado.aprovarRequisicao, { id: requisicaoParaAprovar });
|
|
|
|
|
mostrarMensagem('success', 'Requisição aprovada com sucesso!');
|
|
|
|
|
fecharModalAprovar();
|
|
|
|
|
} catch (error: unknown) {
|
|
|
|
|
const message = error instanceof Error ? error.message : 'Erro ao aprovar requisição';
|
|
|
|
|
mostrarMensagem('error', message);
|
|
|
|
|
} finally {
|
|
|
|
|
processandoAprovar = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function atenderRequisicao(id: Id<'requisicoesMaterial'>) {
|
|
|
|
|
if (!confirm('Tem certeza que deseja atender esta requisição? Isso registrará as saídas de material.')) {
|
|
|
|
|
let requisicaoParaVisualizar = $state<Id<'requisicoesMaterial'> | null>(null);
|
|
|
|
|
let showModalVisualizar = $state(false);
|
|
|
|
|
let requisicaoDetalhes = $state<any>(null);
|
|
|
|
|
let carregandoDetalhes = $state(false);
|
|
|
|
|
let erroDetalhes = $state<string | null>(null);
|
|
|
|
|
|
|
|
|
|
// Usar $effect para buscar dados quando requisicaoParaVisualizar muda
|
|
|
|
|
$effect(() => {
|
|
|
|
|
const id = requisicaoParaVisualizar;
|
|
|
|
|
|
|
|
|
|
if (!id) {
|
|
|
|
|
requisicaoDetalhes = null;
|
|
|
|
|
carregandoDetalhes = false;
|
|
|
|
|
erroDetalhes = null;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
carregandoDetalhes = true;
|
|
|
|
|
erroDetalhes = null;
|
|
|
|
|
requisicaoDetalhes = null;
|
|
|
|
|
|
|
|
|
|
client.query(api.almoxarifado.obterRequisicao, { id })
|
|
|
|
|
.then((data) => {
|
|
|
|
|
// Verificar se o ID ainda é o mesmo (evitar race conditions)
|
|
|
|
|
if (requisicaoParaVisualizar === id) {
|
|
|
|
|
requisicaoDetalhes = data;
|
|
|
|
|
carregandoDetalhes = false;
|
|
|
|
|
erroDetalhes = null;
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch((error) => {
|
|
|
|
|
// Verificar se o ID ainda é o mesmo (evitar race conditions)
|
|
|
|
|
if (requisicaoParaVisualizar === id) {
|
|
|
|
|
erroDetalhes = error?.message || String(error);
|
|
|
|
|
carregandoDetalhes = false;
|
|
|
|
|
requisicaoDetalhes = null;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
function abrirModalVisualizar(id: Id<'requisicoesMaterial'>) {
|
|
|
|
|
requisicaoParaVisualizar = id;
|
|
|
|
|
showModalVisualizar = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function fecharModalVisualizar() {
|
|
|
|
|
showModalVisualizar = false;
|
|
|
|
|
requisicaoParaVisualizar = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let requisicaoParaReprovar = $state<Id<'requisicoesMaterial'> | null>(null);
|
|
|
|
|
let motivoReprovacao = $state('');
|
|
|
|
|
let showModalReprovar = $state(false);
|
|
|
|
|
let processandoReprovar = $state(false);
|
|
|
|
|
|
|
|
|
|
function abrirModalReprovar(id: Id<'requisicoesMaterial'>) {
|
|
|
|
|
requisicaoParaReprovar = id;
|
|
|
|
|
motivoReprovacao = '';
|
|
|
|
|
showModalReprovar = true;
|
|
|
|
|
processandoReprovar = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function fecharModalReprovar() {
|
|
|
|
|
showModalReprovar = false;
|
|
|
|
|
requisicaoParaReprovar = null;
|
|
|
|
|
motivoReprovacao = '';
|
|
|
|
|
processandoReprovar = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function confirmarReprovar() {
|
|
|
|
|
if (!requisicaoParaReprovar) return;
|
|
|
|
|
|
|
|
|
|
if (!motivoReprovacao.trim()) {
|
|
|
|
|
mostrarMensagem('error', 'É necessário informar o motivo da reprovação');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await client.mutation(api.almoxarifado.atenderRequisicao, { id });
|
|
|
|
|
processandoReprovar = true;
|
|
|
|
|
await client.mutation(api.almoxarifado.reprovarRequisicao, {
|
|
|
|
|
id: requisicaoParaReprovar,
|
|
|
|
|
motivoReprovacao: motivoReprovacao.trim()
|
|
|
|
|
});
|
|
|
|
|
mostrarMensagem('success', 'Requisição reprovada com sucesso!');
|
|
|
|
|
fecharModalReprovar();
|
|
|
|
|
} catch (error: unknown) {
|
|
|
|
|
const message = error instanceof Error ? error.message : 'Erro ao reprovar requisição';
|
|
|
|
|
mostrarMensagem('error', message);
|
|
|
|
|
} finally {
|
|
|
|
|
processandoReprovar = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let requisicaoParaAtender = $state<Id<'requisicoesMaterial'> | null>(null);
|
|
|
|
|
let showModalAtender = $state(false);
|
|
|
|
|
let processandoAtender = $state(false);
|
|
|
|
|
|
|
|
|
|
function abrirModalAtender(id: Id<'requisicoesMaterial'>) {
|
|
|
|
|
requisicaoParaAtender = id;
|
|
|
|
|
showModalAtender = true;
|
|
|
|
|
processandoAtender = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function fecharModalAtender() {
|
|
|
|
|
showModalAtender = false;
|
|
|
|
|
requisicaoParaAtender = null;
|
|
|
|
|
processandoAtender = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function confirmarAtender() {
|
|
|
|
|
if (!requisicaoParaAtender) return;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
processandoAtender = true;
|
|
|
|
|
await client.mutation(api.almoxarifado.atenderRequisicao, { id: requisicaoParaAtender });
|
|
|
|
|
mostrarMensagem('success', 'Requisição atendida com sucesso!');
|
|
|
|
|
fecharModalAtender();
|
|
|
|
|
} catch (error: unknown) {
|
|
|
|
|
const message = error instanceof Error ? error.message : 'Erro ao atender requisição';
|
|
|
|
|
mostrarMensagem('error', message);
|
|
|
|
|
} finally {
|
|
|
|
|
processandoAtender = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -426,28 +576,50 @@
|
|
|
|
|
<span class="badge badge-ghost">{setor?.nome || 'Carregando...'}</span>
|
|
|
|
|
</td>
|
|
|
|
|
<td>
|
|
|
|
|
<span class="badge {getStatusBadge(requisicao.status)} badge-lg">
|
|
|
|
|
{getStatusLabel(requisicao.status)}
|
|
|
|
|
</span>
|
|
|
|
|
<div class="flex flex-col gap-1">
|
|
|
|
|
<span class="badge {getStatusBadge(requisicao.status)} badge-lg">
|
|
|
|
|
{getStatusLabel(requisicao.status)}
|
|
|
|
|
</span>
|
|
|
|
|
{#if requisicao.status === 'rejeitada' && requisicao.motivoReprovacao}
|
|
|
|
|
<div class="text-xs text-error mt-1 max-w-xs truncate" title={requisicao.motivoReprovacao}>
|
|
|
|
|
<strong>Motivo:</strong> {requisicao.motivoReprovacao}
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
<td>
|
|
|
|
|
<span class="text-sm">{new Date(requisicao.criadoEm).toLocaleDateString('pt-BR')}</span>
|
|
|
|
|
</td>
|
|
|
|
|
<td>
|
|
|
|
|
<div class="flex gap-2">
|
|
|
|
|
<button
|
|
|
|
|
class="btn btn-sm btn-ghost transition-all"
|
|
|
|
|
onclick={() => abrirModalVisualizar(requisicao._id)}
|
|
|
|
|
title="Visualizar requisição"
|
|
|
|
|
>
|
|
|
|
|
<Eye class="h-4 w-4" />
|
|
|
|
|
</button>
|
|
|
|
|
{#if requisicao.status === 'pendente'}
|
|
|
|
|
<button
|
|
|
|
|
class="btn btn-sm btn-success transition-all"
|
|
|
|
|
onclick={() => aprovarRequisicao(requisicao._id)}
|
|
|
|
|
onclick={() => abrirModalAprovar(requisicao._id)}
|
|
|
|
|
title="Aprovar requisição"
|
|
|
|
|
>
|
|
|
|
|
<CheckCircle class="h-4 w-4" />
|
|
|
|
|
Aprovar
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
class="btn btn-sm btn-error transition-all"
|
|
|
|
|
onclick={() => abrirModalReprovar(requisicao._id)}
|
|
|
|
|
title="Reprovar requisição"
|
|
|
|
|
>
|
|
|
|
|
<XCircle class="h-4 w-4" />
|
|
|
|
|
Reprovar
|
|
|
|
|
</button>
|
|
|
|
|
{:else if requisicao.status === 'aprovada'}
|
|
|
|
|
<button
|
|
|
|
|
class="btn btn-sm btn-primary transition-all"
|
|
|
|
|
onclick={() => atenderRequisicao(requisicao._id)}
|
|
|
|
|
onclick={() => abrirModalAtender(requisicao._id)}
|
|
|
|
|
title="Atender requisição"
|
|
|
|
|
>
|
|
|
|
|
<Package class="h-4 w-4" />
|
|
|
|
|
@@ -695,6 +867,423 @@
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
<!-- Modal Aprovar Requisição -->
|
|
|
|
|
{#if showModalAprovar}
|
|
|
|
|
<div class="modal modal-open backdrop-blur-sm">
|
|
|
|
|
<div class="modal-box max-w-2xl border border-base-300 shadow-2xl">
|
|
|
|
|
<div class="mb-6 flex items-center gap-4 border-b-2 border-success/20 pb-4">
|
|
|
|
|
<div class="rounded-2xl bg-success/20 p-3">
|
|
|
|
|
<CheckCircle class="h-8 w-8 text-success" strokeWidth={2.5} />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
<h3 class="text-2xl font-bold text-base-content">Aprovar Requisição</h3>
|
|
|
|
|
<p class="text-base-content/70 mt-1">Confirmar aprovação da requisição</p>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="btn btn-ghost btn-sm" onclick={fecharModalAprovar} disabled={processandoAprovar}>
|
|
|
|
|
<XCircle class="h-5 w-5" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{#if problemasEstoqueAprovar.length > 0}
|
|
|
|
|
<div class="mb-6">
|
|
|
|
|
<div class="alert alert-warning border-warning/30 bg-warning/10">
|
|
|
|
|
<AlertTriangle class="h-5 w-5 text-warning" />
|
|
|
|
|
<div>
|
|
|
|
|
<h4 class="font-bold text-base-content">Estoque Insuficiente</h4>
|
|
|
|
|
<p class="text-sm text-base-content/80 mt-1">
|
|
|
|
|
A requisição contém materiais com estoque insuficiente:
|
|
|
|
|
</p>
|
|
|
|
|
<ul class="list-disc list-inside mt-2 text-sm text-base-content/80">
|
|
|
|
|
{#each problemasEstoqueAprovar as problema}
|
|
|
|
|
<li>
|
|
|
|
|
{problema.materialNome}: solicitado {problema.quantidadeSolicitada}, disponível {problema.estoqueAtual} (faltam {problema.falta})
|
|
|
|
|
</li>
|
|
|
|
|
{/each}
|
|
|
|
|
</ul>
|
|
|
|
|
<p class="text-sm text-base-content/80 mt-2">
|
|
|
|
|
A aprovação permitirá atender apenas a quantidade disponível em estoque.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{:else}
|
|
|
|
|
<div class="mb-6">
|
|
|
|
|
<div class="alert alert-info border-info/30 bg-info/10">
|
|
|
|
|
<CheckCircle class="h-5 w-5 text-info" />
|
|
|
|
|
<div>
|
|
|
|
|
<h4 class="font-bold text-base-content">Confirmar Aprovação</h4>
|
|
|
|
|
<p class="text-sm text-base-content/80 mt-1">
|
|
|
|
|
A requisição será aprovada e ficará disponível para atendimento.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
<div class="modal-action gap-3 border-t-2 border-base-300 pt-6">
|
|
|
|
|
<button
|
|
|
|
|
class="btn btn-ghost btn-lg min-w-[140px]"
|
|
|
|
|
onclick={fecharModalAprovar}
|
|
|
|
|
disabled={processandoAprovar}
|
|
|
|
|
>
|
|
|
|
|
Cancelar
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
class="btn btn-success btn-lg min-w-[200px] shadow-lg hover:shadow-xl"
|
|
|
|
|
onclick={confirmarAprovar}
|
|
|
|
|
disabled={processandoAprovar}
|
|
|
|
|
>
|
|
|
|
|
{#if processandoAprovar}
|
|
|
|
|
<span class="loading loading-spinner loading-sm"></span>
|
|
|
|
|
Aprovando...
|
|
|
|
|
{:else}
|
|
|
|
|
<CheckCircle class="h-5 w-5" />
|
|
|
|
|
Confirmar Aprovação
|
|
|
|
|
{/if}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
<!-- Modal Reprovar Requisição -->
|
|
|
|
|
{#if showModalReprovar}
|
|
|
|
|
<div class="modal modal-open backdrop-blur-sm">
|
|
|
|
|
<div class="modal-box max-w-2xl border border-base-300 shadow-2xl">
|
|
|
|
|
<div class="mb-6 flex items-center gap-4 border-b-2 border-error/20 pb-4">
|
|
|
|
|
<div class="rounded-2xl bg-error/20 p-3">
|
|
|
|
|
<XCircle class="h-8 w-8 text-error" strokeWidth={2.5} />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
<h3 class="text-2xl font-bold text-base-content">Reprovar Requisição</h3>
|
|
|
|
|
<p class="text-base-content/70 mt-1">Informe o motivo da reprovação</p>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="btn btn-ghost btn-sm" onclick={fecharModalReprovar} disabled={processandoReprovar}>
|
|
|
|
|
<XCircle class="h-5 w-5" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="mb-6">
|
|
|
|
|
<div class="alert alert-warning border-warning/30 bg-warning/10 mb-4">
|
|
|
|
|
<AlertTriangle class="h-5 w-5 text-warning" />
|
|
|
|
|
<div>
|
|
|
|
|
<h4 class="font-bold text-base-content">Confirmar Reprovação</h4>
|
|
|
|
|
<p class="text-sm text-base-content/80 mt-1">
|
|
|
|
|
O motivo informado será registrado e poderá ser visualizado pelo solicitante da requisição.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-control">
|
|
|
|
|
<label class="label pb-2">
|
|
|
|
|
<span class="label-text font-semibold flex items-center gap-2">
|
|
|
|
|
<FileText class="h-4 w-4" />
|
|
|
|
|
Motivo da Reprovação <span class="text-error">*</span>
|
|
|
|
|
</span>
|
|
|
|
|
</label>
|
|
|
|
|
<textarea
|
|
|
|
|
class="textarea textarea-bordered w-full focus:textarea-error transition-colors h-32"
|
|
|
|
|
placeholder="Descreva o motivo da reprovação desta requisição..."
|
|
|
|
|
bind:value={motivoReprovacao}
|
|
|
|
|
rows="5"
|
|
|
|
|
disabled={processandoReprovar}
|
|
|
|
|
></textarea>
|
|
|
|
|
<label class="label pt-1">
|
|
|
|
|
<span class="label-text-alt text-base-content/60">Este motivo será registrado e poderá ser visualizado pelo solicitante</span>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="modal-action gap-3 border-t-2 border-base-300 pt-6">
|
|
|
|
|
<button
|
|
|
|
|
class="btn btn-ghost btn-lg min-w-[140px]"
|
|
|
|
|
onclick={fecharModalReprovar}
|
|
|
|
|
disabled={processandoReprovar}
|
|
|
|
|
>
|
|
|
|
|
Cancelar
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
class="btn btn-error btn-lg min-w-[200px] shadow-lg hover:shadow-xl"
|
|
|
|
|
onclick={confirmarReprovar}
|
|
|
|
|
disabled={!motivoReprovacao.trim() || processandoReprovar}
|
|
|
|
|
>
|
|
|
|
|
{#if processandoReprovar}
|
|
|
|
|
<span class="loading loading-spinner loading-sm"></span>
|
|
|
|
|
Reprovando...
|
|
|
|
|
{:else}
|
|
|
|
|
<XCircle class="h-5 w-5" />
|
|
|
|
|
Confirmar Reprovação
|
|
|
|
|
{/if}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
<!-- Modal Visualizar Requisição -->
|
|
|
|
|
{#if showModalVisualizar}
|
|
|
|
|
<div class="modal modal-open backdrop-blur-sm">
|
|
|
|
|
<div class="modal-box max-w-4xl border border-base-300 shadow-2xl">
|
|
|
|
|
<div class="mb-6 flex items-center gap-4 border-b-2 border-primary/20 pb-4">
|
|
|
|
|
<div class="rounded-2xl bg-primary/20 p-3">
|
|
|
|
|
<Eye class="h-8 w-8 text-primary" strokeWidth={2.5} />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
<h3 class="text-2xl font-bold text-base-content">Detalhes da Requisição</h3>
|
|
|
|
|
{#if carregandoDetalhes}
|
|
|
|
|
<p class="text-base-content/70 mt-1">Carregando...</p>
|
|
|
|
|
{:else if erroDetalhes}
|
|
|
|
|
<p class="text-base-content/70 mt-1 text-error">Erro: {erroDetalhes}</p>
|
|
|
|
|
{:else if requisicaoDetalhes}
|
|
|
|
|
<p class="text-base-content/70 mt-1">Número: <span class="font-mono font-bold text-primary">{requisicaoDetalhes.numero}</span></p>
|
|
|
|
|
{:else}
|
|
|
|
|
<p class="text-base-content/70 mt-1">Carregando...</p>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
<button class="btn btn-ghost btn-sm" onclick={fecharModalVisualizar}>
|
|
|
|
|
<XCircle class="h-5 w-5" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{#if carregandoDetalhes}
|
|
|
|
|
<div class="flex items-center justify-center py-16">
|
|
|
|
|
<span class="loading loading-spinner loading-lg"></span>
|
|
|
|
|
</div>
|
|
|
|
|
{:else if erroDetalhes}
|
|
|
|
|
<div class="alert alert-error">
|
|
|
|
|
<AlertTriangle class="h-5 w-5" />
|
|
|
|
|
<div>
|
|
|
|
|
<h4 class="font-bold">Erro ao carregar detalhes</h4>
|
|
|
|
|
<p class="text-sm">{erroDetalhes}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{:else if requisicaoDetalhes}
|
|
|
|
|
{@const solicitanteDetalhes = funcionariosQuery?.data?.find(f => f._id === requisicaoDetalhes.solicitanteId)}
|
|
|
|
|
{@const setorDetalhes = setoresQuery?.data?.find(s => s._id === requisicaoDetalhes.setorId)}
|
|
|
|
|
|
|
|
|
|
<!-- Informações Básicas -->
|
|
|
|
|
<div class="mb-6">
|
|
|
|
|
<h4 class="mb-4 text-lg font-bold flex items-center gap-2">
|
|
|
|
|
<FileText class="h-5 w-5" />
|
|
|
|
|
Informações Básicas
|
|
|
|
|
</h4>
|
|
|
|
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
|
|
|
<div class="card bg-base-200">
|
|
|
|
|
<div class="card-body p-4">
|
|
|
|
|
<div class="text-sm text-base-content/70">Solicitante</div>
|
|
|
|
|
<div class="font-semibold">{solicitanteDetalhes?.nome || 'Carregando...'}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="card bg-base-200">
|
|
|
|
|
<div class="card-body p-4">
|
|
|
|
|
<div class="text-sm text-base-content/70">Setor</div>
|
|
|
|
|
<div class="font-semibold">{setorDetalhes?.nome || 'Carregando...'}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="card bg-base-200">
|
|
|
|
|
<div class="card-body p-4">
|
|
|
|
|
<div class="text-sm text-base-content/70">Status</div>
|
|
|
|
|
<span class="badge {getStatusBadge(requisicaoDetalhes.status)} badge-lg">
|
|
|
|
|
{getStatusLabel(requisicaoDetalhes.status)}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="card bg-base-200">
|
|
|
|
|
<div class="card-body p-4">
|
|
|
|
|
<div class="text-sm text-base-content/70">Data de Criação</div>
|
|
|
|
|
<div class="font-semibold">{new Date(requisicaoDetalhes.criadoEm).toLocaleString('pt-BR')}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{#if requisicaoDetalhes.aprovadoPor}
|
|
|
|
|
{@const aprovador = funcionariosQuery.data?.find(f => f._id === requisicaoDetalhes.aprovadoPor)}
|
|
|
|
|
<div class="card bg-base-200">
|
|
|
|
|
<div class="card-body p-4">
|
|
|
|
|
<div class="text-sm text-base-content/70">Aprovado por</div>
|
|
|
|
|
<div class="font-semibold">{aprovador?.nome || 'Carregando...'}</div>
|
|
|
|
|
{#if requisicaoDetalhes.dataAprovacao}
|
|
|
|
|
<div class="text-xs text-base-content/60 mt-1">
|
|
|
|
|
{new Date(requisicaoDetalhes.dataAprovacao).toLocaleString('pt-BR')}
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
{#if requisicaoDetalhes.reprovadoPor}
|
|
|
|
|
{@const reprovador = funcionariosQuery.data?.find(f => f._id === requisicaoDetalhes.reprovadoPor)}
|
|
|
|
|
<div class="card bg-base-200">
|
|
|
|
|
<div class="card-body p-4">
|
|
|
|
|
<div class="text-sm text-base-content/70">Reprovado por</div>
|
|
|
|
|
<div class="font-semibold">{reprovador?.nome || 'Carregando...'}</div>
|
|
|
|
|
{#if requisicaoDetalhes.dataReprovacao}
|
|
|
|
|
<div class="text-xs text-base-content/60 mt-1">
|
|
|
|
|
{new Date(requisicaoDetalhes.dataReprovacao).toLocaleString('pt-BR')}
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
{#if requisicaoDetalhes.motivoReprovacao}
|
|
|
|
|
<div class="mt-2 text-sm">
|
|
|
|
|
<div class="text-xs text-base-content/70">Motivo:</div>
|
|
|
|
|
<div class="text-error">{requisicaoDetalhes.motivoReprovacao}</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Itens da Requisição -->
|
|
|
|
|
<div class="mb-6">
|
|
|
|
|
<h4 class="mb-4 text-lg font-bold flex items-center gap-2">
|
|
|
|
|
<Package class="h-5 w-5" />
|
|
|
|
|
Itens da Requisição
|
|
|
|
|
</h4>
|
|
|
|
|
{#if requisicaoDetalhes.itens && requisicaoDetalhes.itens.length > 0}
|
|
|
|
|
<div class="overflow-x-auto rounded-lg border border-base-300">
|
|
|
|
|
<table class="table table-zebra">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr class="bg-base-200">
|
|
|
|
|
<th class="font-bold text-base-content">Material</th>
|
|
|
|
|
<th class="font-bold text-base-content">Quantidade Solicitada</th>
|
|
|
|
|
<th class="font-bold text-base-content">Quantidade Atendida</th>
|
|
|
|
|
<th class="font-bold text-base-content">Observações</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
{#each requisicaoDetalhes.itens as item}
|
|
|
|
|
{@const material = materiaisQuery.data?.find(m => m._id === item.materialId)}
|
|
|
|
|
<tr class="hover:bg-base-200/50 transition-colors">
|
|
|
|
|
<td>
|
|
|
|
|
<div class="font-medium">{material?.nome || 'Carregando...'}</div>
|
|
|
|
|
{#if material?.codigo}
|
|
|
|
|
<div class="text-xs text-base-content/50 font-mono">{material.codigo}</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</td>
|
|
|
|
|
<td>
|
|
|
|
|
<span class="font-semibold">{item.quantidadeSolicitada}</span>
|
|
|
|
|
<span class="text-sm text-base-content/60 ml-1">{material?.unidadeMedida || ''}</span>
|
|
|
|
|
</td>
|
|
|
|
|
<td>
|
|
|
|
|
{#if item.quantidadeAtendida !== undefined}
|
|
|
|
|
<span class="font-semibold">{item.quantidadeAtendida}</span>
|
|
|
|
|
<span class="text-sm text-base-content/60 ml-1">{material?.unidadeMedida || ''}</span>
|
|
|
|
|
{:else}
|
|
|
|
|
<span class="text-base-content/50">-</span>
|
|
|
|
|
{/if}
|
|
|
|
|
</td>
|
|
|
|
|
<td>
|
|
|
|
|
{#if item.observacoes}
|
|
|
|
|
<div class="text-sm">{item.observacoes}</div>
|
|
|
|
|
{:else}
|
|
|
|
|
<span class="text-base-content/50">-</span>
|
|
|
|
|
{/if}
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
{/each}
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
{:else}
|
|
|
|
|
<div class="alert alert-info">
|
|
|
|
|
<Package class="h-5 w-5" />
|
|
|
|
|
<span>Nenhum item encontrado nesta requisição</span>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Observações Gerais -->
|
|
|
|
|
{#if requisicaoDetalhes.observacoes}
|
|
|
|
|
<div class="mb-6">
|
|
|
|
|
<h4 class="mb-4 text-lg font-bold flex items-center gap-2">
|
|
|
|
|
<FileText class="h-5 w-5" />
|
|
|
|
|
Observações Gerais
|
|
|
|
|
</h4>
|
|
|
|
|
<div class="card bg-base-200">
|
|
|
|
|
<div class="card-body p-4">
|
|
|
|
|
<p class="text-sm whitespace-pre-wrap">{requisicaoDetalhes.observacoes}</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
<div class="modal-action">
|
|
|
|
|
<button class="btn btn-ghost" onclick={fecharModalVisualizar}>
|
|
|
|
|
Fechar
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
{:else}
|
|
|
|
|
<div class="flex items-center justify-center py-16">
|
|
|
|
|
<span class="loading loading-spinner loading-lg"></span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="modal-action">
|
|
|
|
|
<button class="btn btn-ghost" onclick={fecharModalVisualizar}>
|
|
|
|
|
Fechar
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
<!-- Modal Atender Requisição -->
|
|
|
|
|
{#if showModalAtender}
|
|
|
|
|
<div class="modal modal-open backdrop-blur-sm">
|
|
|
|
|
<div class="modal-box max-w-2xl border border-base-300 shadow-2xl">
|
|
|
|
|
<div class="mb-6 flex items-center gap-4 border-b-2 border-primary/20 pb-4">
|
|
|
|
|
<div class="rounded-2xl bg-primary/20 p-3">
|
|
|
|
|
<Package class="h-8 w-8 text-primary" strokeWidth={2.5} />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex-1">
|
|
|
|
|
<h3 class="text-2xl font-bold text-base-content">Atender Requisição</h3>
|
|
|
|
|
<p class="text-base-content/70 mt-1">Registrar saídas de material</p>
|
|
|
|
|
</div>
|
|
|
|
|
<button class="btn btn-ghost btn-sm" onclick={fecharModalAtender} disabled={processandoAtender}>
|
|
|
|
|
<XCircle class="h-5 w-5" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="mb-6">
|
|
|
|
|
<div class="alert alert-info border-info/30 bg-info/10">
|
|
|
|
|
<FileText class="h-5 w-5 text-info" />
|
|
|
|
|
<div>
|
|
|
|
|
<h4 class="font-bold text-base-content">Confirmar Atendimento</h4>
|
|
|
|
|
<p class="text-sm text-base-content/80 mt-1">
|
|
|
|
|
Ao confirmar, serão registradas as saídas de material do estoque conforme os itens da requisição.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="mb-6">
|
|
|
|
|
<p class="text-base-content">
|
|
|
|
|
Tem certeza que deseja atender esta requisição? Isso registrará as saídas de material no estoque.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="modal-action gap-3 border-t-2 border-base-300 pt-6">
|
|
|
|
|
<button
|
|
|
|
|
class="btn btn-ghost btn-lg min-w-[140px]"
|
|
|
|
|
onclick={fecharModalAtender}
|
|
|
|
|
disabled={processandoAtender}
|
|
|
|
|
>
|
|
|
|
|
Cancelar
|
|
|
|
|
</button>
|
|
|
|
|
<button
|
|
|
|
|
class="btn btn-primary btn-lg min-w-[200px] shadow-lg hover:shadow-xl"
|
|
|
|
|
onclick={confirmarAtender}
|
|
|
|
|
disabled={processandoAtender}
|
|
|
|
|
>
|
|
|
|
|
{#if processandoAtender}
|
|
|
|
|
<span class="loading loading-spinner loading-sm"></span>
|
|
|
|
|
Atendendo...
|
|
|
|
|
{:else}
|
|
|
|
|
<Package class="h-5 w-5" />
|
|
|
|
|
Confirmar Atendimento
|
|
|
|
|
{/if}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|