From f008610b2623a8413fd5f5bd1ac23420bfab84c9 Mon Sep 17 00:00:00 2001 From: killer-cf Date: Thu, 18 Dec 2025 12:02:57 -0300 Subject: [PATCH] refactor: enhance pedidos UI by integrating reusable components for layout and styling, improving code maintainability and user experience across various pages --- .../lib/components/layout/Breadcrumbs.svelte | 29 + .../lib/components/layout/PageHeader.svelte | 50 + .../lib/components/layout/PageShell.svelte | 16 + .../src/lib/components/ui/EmptyState.svelte | 34 + .../src/lib/components/ui/GlassCard.svelte | 22 + .../src/lib/components/ui/TableCard.svelte | 18 + .../routes/(dashboard)/pedidos/+page.svelte | 354 +++---- .../(dashboard)/pedidos/[id]/+page.svelte | 865 ++++++++++-------- .../(dashboard)/pedidos/aceite/+page.svelte | 69 +- .../pedidos/minhas-analises/+page.svelte | 145 +-- .../(dashboard)/pedidos/novo/+page.svelte | 345 ++++--- .../pedidos/planejamento/+page.svelte | 253 ++--- .../pedidos/planejamento/[id]/+page.svelte | 385 ++++---- 13 files changed, 1430 insertions(+), 1155 deletions(-) create mode 100644 apps/web/src/lib/components/layout/Breadcrumbs.svelte create mode 100644 apps/web/src/lib/components/layout/PageHeader.svelte create mode 100644 apps/web/src/lib/components/layout/PageShell.svelte create mode 100644 apps/web/src/lib/components/ui/EmptyState.svelte create mode 100644 apps/web/src/lib/components/ui/GlassCard.svelte create mode 100644 apps/web/src/lib/components/ui/TableCard.svelte diff --git a/apps/web/src/lib/components/layout/Breadcrumbs.svelte b/apps/web/src/lib/components/layout/Breadcrumbs.svelte new file mode 100644 index 0000000..03b3f4e --- /dev/null +++ b/apps/web/src/lib/components/layout/Breadcrumbs.svelte @@ -0,0 +1,29 @@ + + +
+ +
+ + diff --git a/apps/web/src/lib/components/layout/PageHeader.svelte b/apps/web/src/lib/components/layout/PageHeader.svelte new file mode 100644 index 0000000..b42b09a --- /dev/null +++ b/apps/web/src/lib/components/layout/PageHeader.svelte @@ -0,0 +1,50 @@ + + +
+
+
+ {#if icon} +
+
+ {@render icon()} +
+
+ {/if} + +
+

{title}

+ {#if subtitle} +

{subtitle}

+ {/if} +
+
+ + {#if actions} +
+ {@render actions()} +
+ {/if} +
+
+ + diff --git a/apps/web/src/lib/components/layout/PageShell.svelte b/apps/web/src/lib/components/layout/PageShell.svelte new file mode 100644 index 0000000..b45727d --- /dev/null +++ b/apps/web/src/lib/components/layout/PageShell.svelte @@ -0,0 +1,16 @@ + + +
+ {@render children?.()} +
+ + diff --git a/apps/web/src/lib/components/ui/EmptyState.svelte b/apps/web/src/lib/components/ui/EmptyState.svelte new file mode 100644 index 0000000..bf6edb3 --- /dev/null +++ b/apps/web/src/lib/components/ui/EmptyState.svelte @@ -0,0 +1,34 @@ + + +
+ {#if icon} +
+
+ {@render icon()} +
+
+ {/if} + +

{title}

+ {#if description} +

{description}

+ {/if} +
+ + diff --git a/apps/web/src/lib/components/ui/GlassCard.svelte b/apps/web/src/lib/components/ui/GlassCard.svelte new file mode 100644 index 0000000..1e441eb --- /dev/null +++ b/apps/web/src/lib/components/ui/GlassCard.svelte @@ -0,0 +1,22 @@ + + +
+
+ {@render children?.()} +
+
diff --git a/apps/web/src/lib/components/ui/TableCard.svelte b/apps/web/src/lib/components/ui/TableCard.svelte new file mode 100644 index 0000000..d551ba5 --- /dev/null +++ b/apps/web/src/lib/components/ui/TableCard.svelte @@ -0,0 +1,18 @@ + + + +
+ {@render children?.()} +
+
diff --git a/apps/web/src/routes/(dashboard)/pedidos/+page.svelte b/apps/web/src/routes/(dashboard)/pedidos/+page.svelte index 0352f70..909e707 100644 --- a/apps/web/src/routes/(dashboard)/pedidos/+page.svelte +++ b/apps/web/src/routes/(dashboard)/pedidos/+page.svelte @@ -3,9 +3,14 @@ import type { Id } from '@sgse-app/backend/convex/_generated/dataModel'; import { exportarRelatorioPedidosXLSX } from '$lib/utils/pedidos/relatorioPedidosExcel'; import { gerarRelatorioPedidosPDF } from '$lib/utils/pedidos/relatorioPedidosPDF'; + import Breadcrumbs from '$lib/components/layout/Breadcrumbs.svelte'; + import PageHeader from '$lib/components/layout/PageHeader.svelte'; + import PageShell from '$lib/components/layout/PageShell.svelte'; + import GlassCard from '$lib/components/ui/GlassCard.svelte'; + import TableCard from '$lib/components/ui/TableCard.svelte'; import { useConvexClient, useQuery } from 'convex-svelte'; import { endOfDay, startOfDay } from 'date-fns'; - import { Eye, Plus } from 'lucide-svelte'; + import { Eye, FileText, Plus } from 'lucide-svelte'; import { resolve } from '$app/paths'; const client = useConvexClient(); @@ -149,22 +154,22 @@ } } - function getStatusColor(status: string) { + function getStatusBadgeClass(status: string) { switch (status) { case 'em_rascunho': - return 'bg-gray-100 text-gray-800'; + return 'badge-ghost'; case 'aguardando_aceite': - return 'bg-yellow-100 text-yellow-800'; + return 'badge-warning'; case 'em_analise': - return 'bg-blue-100 text-blue-800'; + return 'badge-info'; case 'precisa_ajustes': - return 'bg-orange-100 text-orange-800'; + return 'badge-secondary'; case 'concluido': - return 'bg-green-100 text-green-800'; + return 'badge-success'; case 'cancelado': - return 'bg-red-100 text-red-800'; + return 'badge-error'; default: - return 'bg-gray-100 text-gray-800'; + return 'badge-ghost'; } } @@ -173,148 +178,150 @@ } -
-
-

Pedidos

-
+ + + + + {#snippet icon()} + + {/snippet} + + {#snippet actions()} - + Novo Pedido -
-
+ {/snippet} + -
-
-
- - -
+ +
+
+
+ + +
-
- - -
+
+ + +
-
- - -
+
+ + +
-
- - -
+
+ + +
-
- - -
+
+ + +
-
-
Status
-
- {#each statusOptions as s (s.value)} - - {/each} +
+
+ Status +
+
+ {#each statusOptions as s (s.value)} + + {/each} +
-
-
- +
+
{pedidos.length} resultado(s)
+ +
-
+
{:else}
- SEI: {pedido.numeroSei || 'sem número SEI'} +

+ SEI: {pedido.numeroSei || 'sem número SEI'} +

{#if pedido.status === 'em_rascunho' || pedido.status === 'precisa_ajustes'} {/if}
@@ -1216,167 +1237,162 @@
{:else}
- DFD: {pedido.numeroDfd || 'sem número DFD'} + DFD: {pedido.numeroDfd || 'sem número DFD'} {#if pedido.status === 'em_rascunho' || pedido.status === 'precisa_ajustes'} {/if}
{/if}
- + + {formatStatus(pedido.status)} - - {#if !pedido.numeroSei} -

- ⚠️ Este pedido não possui número SEI. Um número SEI deve ser informado antes de enviar - para aceite. -

- {/if} - {#if !pedido.numeroDfd} -

- ⚠️ Este pedido não possui número DFD. Um número DFD deve ser informado antes de enviar - para aceite. -

- {/if} - {#if pedido.status === 'precisa_ajustes' && pedido.descricaoAjuste} -
-

- Ajustes Solicitados: -

-

{pedido.descricaoAjuste}

-
- {/if} -
+ {#if !pedido.numeroSei} +
+ + ⚠️ Este pedido não possui número SEI. Um número SEI deve ser informado antes de + enviar para aceite. + +
+ {/if} + {#if !pedido.numeroDfd} +
+ + ⚠️ Este pedido não possui número DFD. Um número DFD deve ser informado antes de + enviar para aceite. + +
+ {/if} + {#if pedido.status === 'precisa_ajustes' && pedido.descricaoAjuste} +
+ + + + Ajustes solicitados: + {pedido.descricaoAjuste} + + +
+ {/if} +
-
- {#if permissions?.canSendToAcceptance} - - {/if} +
+ {#if permissions?.canSendToAcceptance} + + {/if} - {#if permissions?.canStartAnalysis} - - {/if} + {#if permissions?.canStartAnalysis} + + {/if} - {#if permissions?.canConclude} - - {/if} + {#if permissions?.canConclude} + + {/if} - {#if permissions?.canRequestAdjustments} - - {/if} + {#if permissions?.canRequestAdjustments} + + {/if} - {#if permissions?.canCompleteAdjustments} - - {/if} + {#if permissions?.canCompleteAdjustments} + + {/if} - {#if permissions?.canCancel} - - {/if} -
-
+ {#if permissions?.canCancel} + + {/if} + + -
-
+ +

Documentos do Pedido

{#if showAddPedidoDocumento} -
+
- +
- + { const f = e.currentTarget.files?.[0] ?? null; @@ -1394,7 +1410,7 @@ pedidoDocumentoDescricao = ''; pedidoDocumentoFile = null; }} - class="rounded bg-gray-200 px-3 py-2 text-sm text-gray-700 hover:bg-gray-300" + class="btn btn-ghost btn-sm" disabled={salvandoPedidoDocumento} > Cancelar @@ -1403,12 +1419,12 @@ type="button" onclick={handleAddPedidoDocumento} disabled={salvandoPedidoDocumento} - class="flex items-center gap-2 rounded bg-blue-600 px-3 py-2 text-sm text-white hover:bg-blue-700 disabled:opacity-50" + class="btn btn-primary btn-sm gap-2" > {#if salvandoPedidoDocumento} - + {:else} - + {/if} Anexar @@ -1418,52 +1434,67 @@
{#if pedidoDocumentos.length === 0} -

Nenhum documento anexado ao pedido.

+

Nenhum documento anexado ao pedido.

{:else}
- - +
+ - - - - - - + + + + + + - + {#each pedidoDocumentos as doc (doc._id)} - - - + + - - - + + - @@ -1473,44 +1504,55 @@ {/if} - + {#if requests.length > 0} -
+
-

Solicitações Pendentes

+

Solicitações Pendentes

-
DescriçãoArquivoTamanhoEnviado porDataAçõesDescriçãoArquivoTamanhoEnviado porDataAções
{doc.descricao} - {doc.nome} +
{doc.descricao} + {doc.nome} {#if doc.origemSolicitacaoId} - (origem: solicitação) + + (origem: solicitação) {/if} {formatBytes(doc.tamanho)}{doc.criadoPorNome ?? 'Desconhecido'} + {formatBytes(doc.tamanho)}{doc.criadoPorNome ?? 'Desconhecido'} {new Date(doc.criadoEm).toLocaleString('pt-BR')} +
- +
+ - - - - + + + + - + {#each requests as req (req._id)} {@const data = parseRequestData(req.dados)} - - + - - + -
TipoSolicitanteDetalhesAçõesTipoSolicitanteDetalhesAções
+
{#if req.tipo === 'adicao'} - Adição + Adição {:else if req.tipo === 'alteracao_quantidade'} - Alteração Qtd + Alteração Qtd {:else if req.tipo === 'exclusao'} - Exclusão + Exclusão {:else if req.tipo === 'alteracao_detalhes'} - {describeChangedDetails(data)} + {describeChangedDetails(data)} {/if} {req.solicitadoPorNome} + {req.solicitadoPorNome} {#if req.tipo === 'adicao'} {getObjetoName(data.objetoId)} - {data.quantidade}x ({data.modalidade}) {:else if req.tipo === 'alteracao_quantidade'} @@ -1537,7 +1579,7 @@ {/if} {/if} +
{#if req.tipo === 'adicao'} {@const canAddDoc = @@ -1545,44 +1587,44 @@ {/if} {#if permissions?.canManageRequests} {:else} - Aguardando Análise {/if} @@ -1594,35 +1636,32 @@
-
+ {/if} -
-
+ +

Itens do Pedido

{#if pedido.status === 'em_rascunho' || pedido.status === 'precisa_ajustes' || pedido.status === 'em_analise' || pedido.status === 'aguardando_aceite'} - {/if}
{#if showAddItem} -
+
- +
- +
- + (newItem.valorEstimado = maskCurrencyBRL(e.currentTarget.value))} - class="w-full rounded-md border-gray-300 text-sm shadow-sm" + class="input input-bordered focus:input-primary input-sm w-full" placeholder="R$ 0,00" />
{#if newItem.objetoId && permissions?.canEditAta}
- + {#each acoes as a (a._id)} @@ -1708,15 +1747,19 @@
@@ -1726,12 +1769,10 @@
{#if hasSelection}
- + {selectedCount} -
{/if} {#each groupedItems as group (group.name)} -
+
Adicionado por: {group.name}
- - +
+ {#if (pedido.status === 'em_rascunho' || pedido.status === 'precisa_ajustes') && currentFuncionarioId && group.items[0]?.adicionadoPor === currentFuncionarioId} {/if} - - - - - - + {#each group.items as item (item._id)} - + {#if (pedido.status === 'em_rascunho' || pedido.status === 'precisa_ajustes') && currentFuncionarioId && item.adicionadoPor === currentFuncionarioId} - {/if} - - + - - - - - - @@ -1993,111 +2028,122 @@ {/each} {#if items.length === 0} -
Nenhum item adicionado.
+
Nenhum item adicionado.
{:else} -
-
+
+
Total Geral: R$ {totalGeral.toFixed(2).replace('.', ',')}
{/if}
-
+ {#if showDetailsModal && selectedObjeto} -
-
+
{ const checked = e.currentTarget.checked; for (const groupItem of group.items) { @@ -1795,56 +1832,54 @@ ObjetoQtdValor Est.ModalidadeAçãoAta Total Ações
+ toggleItemSelection(item._id)} aria-label={`Selecionar item ${getObjetoName(item.objetoId)}`} /> {getObjetoName(item.objetoId)} + {getObjetoName(item.objetoId)} {#if pedido.status === 'em_rascunho' || pedido.status === 'precisa_ajustes' || pedido.status === 'em_analise' || pedido.status === 'aguardando_aceite'} handleUpdateQuantity(item._id, parseInt(e.currentTarget.value) || 1)} - class="w-20 rounded border px-2 py-1 text-sm" + class="input input-bordered input-sm w-20" /> {:else} {item.quantidade} {/if} + {#if pedido.status === 'em_rascunho' || pedido.status === 'precisa_ajustes' || pedido.status === 'em_analise' || pedido.status === 'aguardando_aceite'} setEditingField( @@ -1877,10 +1912,10 @@ {maskCurrencyBRL(item.valorEstimado) || 'R$ 0,00'} {/if} + {#if permissions?.canEditModalidade} + {#if pedido.status === 'em_rascunho' || pedido.status === 'precisa_ajustes' || pedido.status === 'em_analise' || pedido.status === 'aguardando_aceite'} + {#if permissions?.canEditAta} + R$ {calculateItemTotal(item.valorEstimado, item.quantidade) .toFixed(2) .replace('.', ',')} + {#if pedido.status === 'em_rascunho' || pedido.status === 'precisa_ajustes' || pedido.status === 'em_analise' || pedido.status === 'aguardando_aceite'} {/if}
- +
+ - - - - - - + + + + + + - + {#each solicitacaoDocs as doc (doc._id)} - - - + + - - - + + - @@ -2350,50 +2411,48 @@ {/if} -
- +
+ {/if} {#if showRequestAdjustmentsModal} -
-
-

Solicitar Ajustes

-

+

{/if} @@ -2406,4 +2465,4 @@ onConfirm={confirmModal.onConfirm} onClose={() => (confirmModal.open = false)} /> -
+ diff --git a/apps/web/src/routes/(dashboard)/pedidos/aceite/+page.svelte b/apps/web/src/routes/(dashboard)/pedidos/aceite/+page.svelte index 5929f60..9942e25 100644 --- a/apps/web/src/routes/(dashboard)/pedidos/aceite/+page.svelte +++ b/apps/web/src/routes/(dashboard)/pedidos/aceite/+page.svelte @@ -4,6 +4,12 @@ import type { Id } from '@sgse-app/backend/convex/_generated/dataModel'; import { CheckCircle, Clock, FileText, User } from 'lucide-svelte'; import { toast } from 'svelte-sonner'; + import { resolve } from '$app/paths'; + import Breadcrumbs from '$lib/components/layout/Breadcrumbs.svelte'; + import PageHeader from '$lib/components/layout/PageHeader.svelte'; + import PageShell from '$lib/components/layout/PageShell.svelte'; + import GlassCard from '$lib/components/ui/GlassCard.svelte'; + import EmptyState from '$lib/components/ui/EmptyState.svelte'; const client = useConvexClient(); const ordersQuery = useQuery(api.pedidos.listForAcceptance, {}); @@ -27,42 +33,44 @@ } -
-
-
-

Pedidos para Aceite

-

- Lista de pedidos aguardando análise do setor de compras. -

-
-
+ + - {#if ordersQuery.isLoading} -
- {#each Array(3) as _, i (i)} -
- {/each} -
+ + {#snippet icon()} + + {/snippet} + + +
+ + {#if ordersQuery.isLoading} +
+ +
{:else if ordersQuery.error}
Erro ao carregar pedidos: {ordersQuery.error.message}
{:else if !ordersQuery.data || ordersQuery.data.length === 0} -
-
- -
-

Tudo em dia!

-

Não há pedidos aguardando aceite no momento.

-
+ + {#snippet icon()} + + {/snippet} + {:else}
{#each ordersQuery.data as pedido (pedido._id)} -
+
@@ -90,7 +98,7 @@
- + Ver Detalhes @@ -108,8 +116,9 @@
-
+
{/each}
{/if} -
+
+
diff --git a/apps/web/src/routes/(dashboard)/pedidos/minhas-analises/+page.svelte b/apps/web/src/routes/(dashboard)/pedidos/minhas-analises/+page.svelte index c059ea1..5d75e22 100644 --- a/apps/web/src/routes/(dashboard)/pedidos/minhas-analises/+page.svelte +++ b/apps/web/src/routes/(dashboard)/pedidos/minhas-analises/+page.svelte @@ -2,82 +2,91 @@ import { useQuery } from 'convex-svelte'; import { api } from '@sgse-app/backend/convex/_generated/api'; import { ClipboardList, Clock, FileText, User, Search } from 'lucide-svelte'; + import { resolve } from '$app/paths'; + import Breadcrumbs from '$lib/components/layout/Breadcrumbs.svelte'; + import PageHeader from '$lib/components/layout/PageHeader.svelte'; + import PageShell from '$lib/components/layout/PageShell.svelte'; + import GlassCard from '$lib/components/ui/GlassCard.svelte'; + import EmptyState from '$lib/components/ui/EmptyState.svelte'; const ordersQuery = useQuery(api.pedidos.listMyAnalysis, {}); -
-
-
-

Minhas Análises

-

Pedidos que você aceitou e está analisando.

-
-
+ + - {#if ordersQuery.isLoading} -
- {#each Array(3) as _, i (i)} -
- {/each} -
- {:else if ordersQuery.error} -
- Erro ao carregar análises: {ordersQuery.error.message} -
- {:else if !ordersQuery.data || ordersQuery.data.length === 0} -
-
- + + {#snippet icon()} + + {/snippet} + + +
+ {#if ordersQuery.isLoading} +
+
-

Nenhuma análise em andamento

-

- Você não possui pedidos sob sua responsabilidade no momento. Vá para "Pedidos para Aceite" - para pegar novos pedidos. -

-
- {:else} -
- {#each ordersQuery.data as pedido (pedido._id)} -
-
-
-
- - - Em Análise - - - #{pedido._id.slice(-6)} - -
-

- {pedido.numeroSei ? `SEI: ${pedido.numeroSei}` : 'Sem número SEI'} -

-
-
- - Criado por: {pedido.criadoPorNome} + {:else if ordersQuery.error} +
+ Erro ao carregar análises: {ordersQuery.error.message} +
+ {:else if !ordersQuery.data || ordersQuery.data.length === 0} + + {#snippet icon()} + + {/snippet} + + {:else} +
+ {#each ordersQuery.data as pedido (pedido._id)} + +
+
+
+ + + Em Análise + + + #{pedido._id.slice(-6)} +
-
- - Aceito em: {new Date(pedido.atualizadoEm).toLocaleDateString()} +

+ {pedido.numeroSei ? `SEI: ${pedido.numeroSei}` : 'Sem número SEI'} +

+
+
+ + Criado por: {pedido.criadoPorNome} +
+
+ + Aceito em: {new Date(pedido.atualizadoEm).toLocaleDateString()} +
-
- -
-
- {/each} -
- {/if} -
+ + {/each} +
+ {/if} +
+ diff --git a/apps/web/src/routes/(dashboard)/pedidos/novo/+page.svelte b/apps/web/src/routes/(dashboard)/pedidos/novo/+page.svelte index 73a8a4a..31a5e13 100644 --- a/apps/web/src/routes/(dashboard)/pedidos/novo/+page.svelte +++ b/apps/web/src/routes/(dashboard)/pedidos/novo/+page.svelte @@ -2,7 +2,12 @@ import { api } from '@sgse-app/backend/convex/_generated/api'; import type { Doc, Id } from '@sgse-app/backend/convex/_generated/dataModel'; import { useConvexClient, useQuery } from 'convex-svelte'; - import { Plus, Trash2, X, Info } from 'lucide-svelte'; + import Breadcrumbs from '$lib/components/layout/Breadcrumbs.svelte'; + import PageHeader from '$lib/components/layout/PageHeader.svelte'; + import PageShell from '$lib/components/layout/PageShell.svelte'; + import EmptyState from '$lib/components/ui/EmptyState.svelte'; + import GlassCard from '$lib/components/ui/GlassCard.svelte'; + import { Info, Plus, Trash2, X } from 'lucide-svelte'; import { goto } from '$app/navigation'; import { resolve } from '$app/paths'; @@ -254,71 +259,81 @@ } -
-

Novo Pedido

+ + + + + {#snippet icon()} + + {/snippet} +
{#if error} -
-

Erro

-

{error}

+
+ {error}
{/if}
- -
-

Informações Básicas

-
-
+ - -
-

Adicionar Objetos ao Pedido

+ +

Adicionar Objetos ao Pedido

-
-
DescriçãoArquivoTamanhoEnviado porDataAçõesDescriçãoArquivoTamanhoEnviado porDataAções
{doc.descricao} - {doc.nome} +
{doc.descricao} + {doc.nome} {formatBytes(doc.tamanho)}{doc.criadoPorNome ?? 'Desconhecido'} + {formatBytes(doc.tamanho)}{doc.criadoPorNome ?? 'Desconhecido'} {new Date(doc.criadoEm).toLocaleString('pt-BR')} + {#if canAddDocsToSelectedRequest} {/if}
- + +
+ - - Título - - - Data - - - Responsável - - - Ação - - - Status - - Ações - - - {#each planejamentos as p (p._id)} - - - - - - - - - {/each} + {#if planejamentos.length === 0} - + + {:else} + {#each planejamentos as p (p._id)} + + + + + + + + + {/each} {/if}
TítuloDataResponsávelAçãoStatus Ações
{p.titulo}{formatDateYMD(p.data)}{p.responsavelNome}{p.acaoNome || '-'} - - {formatStatus(p.status)} - - - - - Abrir - -
Nenhum planejamento encontrado. +
+

Nenhum planejamento encontrado

+

Clique em “Novo planejamento” para criar o primeiro.

+
+
{p.titulo}{formatDateYMD(p.data)}{p.responsavelNome}{p.acaoNome || '-'} + + {formatStatus(p.status)} + + + + + Abrir + +
-
+ {/if} {#if showCreate} -
-
+