872 lines
46 KiB
TypeScript
872 lines
46 KiB
TypeScript
import { Head, router } from '@inertiajs/react';
|
|
import { useState } from 'react';
|
|
import {
|
|
ArrowRight,
|
|
ClipboardList,
|
|
Eye,
|
|
FileText,
|
|
LayoutGrid,
|
|
Palette,
|
|
Scale,
|
|
Search,
|
|
ShieldCheck,
|
|
Users,
|
|
PackageCheck,
|
|
Warehouse,
|
|
} from 'lucide-react';
|
|
import { PageTitle, Panel, Status } from '@/components/procurement';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from '@/components/ui/dialog';
|
|
import { money } from '@/types/procurement';
|
|
import { previewPaths } from '@/lib/preview-navigation';
|
|
import InventoryDemo from './inventory-demo';
|
|
import { OperationsDemo } from '../stock/operations';
|
|
import { EquipmentDemo } from '../equipment';
|
|
|
|
// Static examples only: this page never loads or submits business data.
|
|
const requests = [
|
|
{
|
|
number: 'DEMO-001',
|
|
title: 'Material de escritório',
|
|
department: 'Administração',
|
|
status: 'approved',
|
|
amount: 1856000,
|
|
author: 'Utilizador de exemplo A',
|
|
},
|
|
{
|
|
number: 'DEMO-002',
|
|
title: 'Peças para manutenção',
|
|
department: 'Produção',
|
|
status: 'pending',
|
|
amount: 6264000,
|
|
author: 'Utilizador de exemplo B',
|
|
},
|
|
{
|
|
number: 'DEMO-003',
|
|
title: 'Equipamento informático',
|
|
department: 'Tecnologia',
|
|
status: 'draft',
|
|
amount: 13920000,
|
|
author: 'Utilizador de exemplo C',
|
|
},
|
|
];
|
|
const suppliers = [
|
|
{
|
|
name: 'Fornecedor de demonstração A',
|
|
category: 'Material de escritório',
|
|
status: 'Activo',
|
|
review: '2027-01-15',
|
|
contact: 'contacto-a@example.invalid',
|
|
},
|
|
{
|
|
name: 'Fornecedor de demonstração B',
|
|
category: 'Equipamentos e peças',
|
|
status: 'Pendente de validação',
|
|
review: '2027-02-20',
|
|
contact: 'contacto-b@example.invalid',
|
|
},
|
|
{
|
|
name: 'Fornecedor de demonstração C',
|
|
category: 'Tecnologia',
|
|
status: 'Activo',
|
|
review: '2027-03-10',
|
|
contact: 'contacto-c@example.invalid',
|
|
},
|
|
];
|
|
const quotes = [
|
|
{
|
|
supplier: 'Fornecedor de demonstração A',
|
|
amount: 1600000,
|
|
days: 7,
|
|
score: '8,65',
|
|
evaluations: 2,
|
|
},
|
|
{
|
|
supplier: 'Fornecedor de demonstração B',
|
|
amount: 1750000,
|
|
days: 10,
|
|
score: 'Pendente',
|
|
evaluations: 1,
|
|
},
|
|
{
|
|
supplier: 'Fornecedor de demonstração C',
|
|
amount: 1680000,
|
|
days: 8,
|
|
score: '8,12',
|
|
evaluations: 2,
|
|
},
|
|
];
|
|
const navigation = [
|
|
{ id: 'overview', title: 'Visão geral', icon: LayoutGrid },
|
|
{ id: 'requests', title: 'Requisições', icon: ClipboardList },
|
|
{ id: 'suppliers', title: 'Fornecedores e anexos', icon: Users },
|
|
{ id: 'quotes', title: 'Cotações', icon: Scale },
|
|
{ id: 'conflicts', title: 'Conflitos de interesses', icon: ShieldCheck },
|
|
{ id: 'orders', title: 'Ordens de compra', icon: FileText },
|
|
{ id: 'receipts', title: 'Recepção e qualidade', icon: PackageCheck },
|
|
{ id: 'stock', title: 'Stock e movimentos', icon: Warehouse },
|
|
{ id: 'operations', title: 'Saídas e inventários', icon: Warehouse },
|
|
{ id: 'equipment', title: 'Equipamentos e peças', icon: Warehouse },
|
|
{ id: 'invoices', title: 'Facturas e conferência', icon: FileText },
|
|
{ id: 'bank', title: 'Dados bancários', icon: ShieldCheck },
|
|
{ id: 'payments', title: 'Pagamentos externos', icon: FileText },
|
|
{ id: 'reinforcements', title: 'Reforços orçamentais', icon: FileText },
|
|
{ id: 'investment', title: 'Regras de investimento', icon: ShieldCheck },
|
|
{ id: 'appearance', title: 'Cores e componentes', icon: Palette },
|
|
];
|
|
type Detail = { title: string; subtitle: string; rows: [string, string][] };
|
|
|
|
export default function Preview({
|
|
initialScreen = 'overview',
|
|
}: {
|
|
initialScreen?: string;
|
|
}) {
|
|
const screen = initialScreen;
|
|
const [search, setSearch] = useState('');
|
|
|
|
const [detail, setDetail] = useState<Detail | null>(null);
|
|
const title = navigation.find((n) => n.id === screen)?.title ?? 'Consulta';
|
|
function navigate(id: string) {
|
|
setSearch('');
|
|
|
|
router.get(previewPaths[id] ?? '/consulta');
|
|
}
|
|
const filteredRequests = requests.filter((r) =>
|
|
`${r.number} ${r.title} ${r.department}`
|
|
.toLowerCase()
|
|
.includes(search.toLowerCase()),
|
|
);
|
|
const requestTable = (
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full text-left text-sm">
|
|
<thead className="border-b text-xs text-muted-foreground">
|
|
<tr>
|
|
<th className="pr-4 pb-3">Requisição</th>
|
|
<th className="pr-4">Departamento</th>
|
|
<th className="pr-4">Estado</th>
|
|
<th className="pr-4 text-right">Total com IVA</th>
|
|
<th>
|
|
<span className="sr-only">Consultar</span>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{filteredRequests.map((r) => (
|
|
<tr key={r.number} className="border-b last:border-0">
|
|
<td className="py-5 pr-4">
|
|
<strong>{r.number}</strong>
|
|
<p className="mt-1 text-xs text-muted-foreground">
|
|
{r.title}
|
|
</p>
|
|
</td>
|
|
<td className="pr-4">{r.department}</td>
|
|
<td className="pr-4">
|
|
<Status value={r.status} />
|
|
</td>
|
|
<td className="pr-4 text-right whitespace-nowrap">
|
|
{money(r.amount)}
|
|
</td>
|
|
<td>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={() =>
|
|
setDetail({
|
|
title: r.number,
|
|
subtitle: r.title,
|
|
rows: [
|
|
['Solicitante', r.author],
|
|
['Departamento', r.department],
|
|
[
|
|
'Total com IVA',
|
|
money(r.amount),
|
|
],
|
|
[
|
|
'Finalidade',
|
|
'Exemplo fictício para consulta visual.',
|
|
],
|
|
],
|
|
})
|
|
}
|
|
>
|
|
<Eye className="size-4" />
|
|
<span className="sr-only">
|
|
Ver {r.number}
|
|
</span>
|
|
</Button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
{!filteredRequests.length && (
|
|
<p className="py-8 text-center text-muted-foreground">
|
|
Nenhum exemplo corresponde à pesquisa.
|
|
</p>
|
|
)}
|
|
</div>
|
|
);
|
|
return (
|
|
<>
|
|
<Head title="Consulta livre" />
|
|
<div className="min-h-screen bg-muted/25 text-foreground">
|
|
<div>
|
|
<div className="mx-auto max-w-7xl px-5 pt-5 text-sm text-muted-foreground">
|
|
Consulta / {title}
|
|
</div>
|
|
<div className="mx-auto flex max-w-7xl flex-col gap-6 p-5 lg:p-8">
|
|
<div className="flex flex-wrap items-center justify-between gap-2 rounded-lg border border-teal-200 bg-teal-50 px-4 py-3 text-sm text-teal-900 dark:border-teal-900 dark:bg-teal-950 dark:text-teal-100">
|
|
<span className="flex items-center gap-2">
|
|
<Eye className="size-4" />
|
|
<strong>Entrada livre para consulta</strong>
|
|
</span>
|
|
<span>
|
|
Exemplos fictícios · sem gravação de dados
|
|
</span>
|
|
</div>
|
|
<PageTitle
|
|
eyebrow="SGC · Demonstração"
|
|
title={title}
|
|
description="Explore os exemplos para verificar a organização, os ecrãs, as cores e a navegação. Os dados apresentados não pertencem à sua empresa."
|
|
/>
|
|
{screen === 'overview' && (
|
|
<>
|
|
<div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
|
|
{[
|
|
[
|
|
'Requisições de exemplo',
|
|
'3',
|
|
'Uma aprovada, uma pendente e um rascunho',
|
|
],
|
|
[
|
|
'A aguardar decisão',
|
|
'1',
|
|
'Exemplo de aprovação por departamento',
|
|
],
|
|
[
|
|
'Fornecedores de exemplo',
|
|
'3',
|
|
'Com estados de validação diferentes',
|
|
],
|
|
[
|
|
'Valor aprovado de exemplo',
|
|
money(1856000),
|
|
'Estimativa em MZN, com IVA',
|
|
],
|
|
].map(([label, value, note]) => (
|
|
<div
|
|
key={label}
|
|
className="rounded-xl border bg-card p-5 shadow-xs"
|
|
>
|
|
<p className="text-sm text-muted-foreground">
|
|
{label}
|
|
</p>
|
|
<strong className="mt-5 block text-2xl tracking-tight">
|
|
{value}
|
|
</strong>
|
|
<p className="mt-2 text-xs text-muted-foreground">
|
|
{note}
|
|
</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<Panel
|
|
title="Requisições recentes"
|
|
description="Clique no ícone de consulta para abrir os detalhes do exemplo."
|
|
>
|
|
{requestTable}
|
|
</Panel>
|
|
<div className="grid gap-4 md:grid-cols-3">
|
|
{[
|
|
[
|
|
'suppliers',
|
|
'Ficha e documentos',
|
|
'Consultar identificação, revisão e anexos.',
|
|
],
|
|
[
|
|
'quotes',
|
|
'Comparação de propostas',
|
|
'Comparar preços, prazos e avaliações.',
|
|
],
|
|
[
|
|
'conflicts',
|
|
'Transparência nas decisões',
|
|
'Consultar exemplos de declarações pessoais.',
|
|
],
|
|
].map(([id, label, note]) => (
|
|
<button
|
|
key={id}
|
|
onClick={() => navigate(id)}
|
|
className="rounded-xl border bg-card p-5 text-left shadow-xs hover:border-teal-500"
|
|
>
|
|
<strong className="flex items-center justify-between text-sm">
|
|
{label}
|
|
<ArrowRight className="size-4 text-teal-700" />
|
|
</strong>
|
|
<p className="mt-3 text-sm text-muted-foreground">
|
|
{note}
|
|
</p>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</>
|
|
)}
|
|
{screen === 'requests' && (
|
|
<Panel
|
|
title="Pedidos de compra"
|
|
description="Pesquisa e detalhe disponíveis nesta demonstração."
|
|
>
|
|
<div className="relative mb-5">
|
|
<Search className="absolute top-3 left-3 size-4 text-muted-foreground" />
|
|
<Input
|
|
className="pl-10"
|
|
aria-label="Pesquisar exemplos"
|
|
placeholder="Pesquisar referência, descrição ou departamento"
|
|
value={search}
|
|
onChange={(e) =>
|
|
setSearch(e.target.value)
|
|
}
|
|
/>
|
|
</div>
|
|
{requestTable}
|
|
</Panel>
|
|
)}
|
|
{screen === 'suppliers' && (
|
|
<div className="grid gap-5 lg:grid-cols-3">
|
|
{suppliers.map((s) => (
|
|
<Panel
|
|
key={s.name}
|
|
title={s.name}
|
|
description={s.category}
|
|
>
|
|
<div className="space-y-4 text-sm">
|
|
<span
|
|
className={`inline-block rounded-full px-3 py-1 text-xs ${s.status === 'Activo' ? 'bg-teal-50 text-teal-800' : 'bg-amber-50 text-amber-900'}`}
|
|
>
|
|
{s.status}
|
|
</span>
|
|
<p>
|
|
<span className="block text-xs text-muted-foreground">
|
|
Próxima revisão
|
|
</span>
|
|
{s.review}
|
|
</p>
|
|
<p className="break-all">
|
|
<span className="block text-xs text-muted-foreground">
|
|
Contacto fictício
|
|
</span>
|
|
{s.contact}
|
|
</p>
|
|
<Button
|
|
variant="outline"
|
|
className="w-full"
|
|
onClick={() =>
|
|
setDetail({
|
|
title: s.name,
|
|
subtitle:
|
|
'Ficha de demonstração · dados fictícios',
|
|
rows: [
|
|
[
|
|
'Categoria',
|
|
s.category,
|
|
],
|
|
[
|
|
'Estado',
|
|
s.status,
|
|
],
|
|
[
|
|
'Próxima revisão',
|
|
s.review,
|
|
],
|
|
[
|
|
'Documentos de exemplo',
|
|
'Alvará.pdf · Declaração.pdf (sem ficheiros reais)',
|
|
],
|
|
[
|
|
'Controlo',
|
|
'Alterações e novos anexos exigem nova validação por Finanças.',
|
|
],
|
|
],
|
|
})
|
|
}
|
|
>
|
|
Consultar ficha
|
|
</Button>
|
|
</div>
|
|
</Panel>
|
|
))}
|
|
</div>
|
|
)}
|
|
{screen === 'quotes' && (
|
|
<Panel
|
|
title="Matriz de avaliação · DEMO-001"
|
|
description="Valores ilustrativos. Preço 40% · qualidade 20% · prazo 15% · histórico 15% · pagamento 10%."
|
|
>
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full text-left text-sm">
|
|
<thead className="border-b text-xs text-muted-foreground">
|
|
<tr>
|
|
{[
|
|
'Fornecedor',
|
|
'Preço sem IVA',
|
|
'Prazo',
|
|
'Avaliações',
|
|
'Pontuação',
|
|
].map((h) => (
|
|
<th
|
|
key={h}
|
|
className="pr-4 pb-3"
|
|
>
|
|
{h}
|
|
</th>
|
|
))}
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{quotes.map((q) => (
|
|
<tr
|
|
className="border-b last:border-0"
|
|
key={q.supplier}
|
|
>
|
|
<td className="py-5 pr-4">
|
|
{q.supplier}
|
|
</td>
|
|
<td className="pr-4 whitespace-nowrap">
|
|
{money(q.amount)}
|
|
</td>
|
|
<td className="pr-4">
|
|
{q.days} dias
|
|
</td>
|
|
<td className="pr-4">
|
|
{q.evaluations} / 2
|
|
</td>
|
|
<td className="pr-4 font-semibold">
|
|
{q.score}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<p className="mt-5 rounded-lg bg-amber-50 p-4 text-sm text-amber-900">
|
|
Exemplo de comparação incompleta: uma das
|
|
propostas ainda não tem duas avaliações. A
|
|
consulta não permite adjudicar.
|
|
</p>
|
|
</Panel>
|
|
)}
|
|
{screen === 'conflicts' && (
|
|
<Panel
|
|
title="Declarações pessoais"
|
|
description="Exemplo fictício de um conflito por fornecedor, com histórico preservado."
|
|
>
|
|
<div className="rounded-lg border p-5">
|
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
<strong>
|
|
Fornecedor de demonstração B
|
|
</strong>
|
|
<span className="rounded-full bg-rose-50 px-3 py-1 text-xs text-rose-800">
|
|
Conflito declarado
|
|
</span>
|
|
</div>
|
|
<dl className="mt-5 grid gap-5 text-sm sm:grid-cols-2">
|
|
<div>
|
|
<dt className="text-muted-foreground">
|
|
Autor
|
|
</dt>
|
|
<dd>Utilizador de exemplo A</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-muted-foreground">
|
|
Âmbito
|
|
</dt>
|
|
<dd>
|
|
Fornecedor em todos os processos
|
|
</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-muted-foreground">
|
|
Fundamento fictício
|
|
</dt>
|
|
<dd>
|
|
Relação pessoal com o
|
|
fornecedor.
|
|
</dd>
|
|
</div>
|
|
<div>
|
|
<dt className="text-muted-foreground">
|
|
Efeito
|
|
</dt>
|
|
<dd>
|
|
Participação impedida nos
|
|
processos afectados.
|
|
</dd>
|
|
</div>
|
|
</dl>
|
|
<p className="mt-5 border-t pt-4 text-xs text-muted-foreground">
|
|
Uma declaração de ausência não anula um
|
|
conflito registado. A política de
|
|
levantamento continua por definir.
|
|
</p>
|
|
</div>
|
|
</Panel>
|
|
)}
|
|
{screen === 'orders' && (
|
|
<Panel
|
|
title="Adjudicação e ordens de compra"
|
|
description="Etapa em desenvolvimento no projecto. Este ecrã é apenas uma ilustração, não uma ordem emitida."
|
|
>
|
|
<div className="rounded-lg border p-6">
|
|
<div className="flex flex-wrap justify-between gap-4">
|
|
<div>
|
|
<p className="text-xs tracking-widest text-teal-700 uppercase">
|
|
Exemplo visual
|
|
</p>
|
|
<h2 className="mt-2 text-xl font-semibold">
|
|
Ordem DEMO-OC-001
|
|
</h2>
|
|
</div>
|
|
<span className="text-sm text-muted-foreground">
|
|
Sem validade comercial
|
|
</span>
|
|
</div>
|
|
<p className="mt-6 text-sm">
|
|
Fornecedor de demonstração A · Material
|
|
de escritório
|
|
</p>
|
|
<div className="mt-5 grid gap-4 border-y py-5 text-sm sm:grid-cols-3">
|
|
<p>
|
|
Sem IVA
|
|
<br />
|
|
<strong>{money(1600000)}</strong>
|
|
</p>
|
|
<p>
|
|
IVA ilustrativo
|
|
<br />
|
|
<strong>{money(256000)}</strong>
|
|
</p>
|
|
<p>
|
|
Total
|
|
<br />
|
|
<strong>{money(1856000)}</strong>
|
|
</p>
|
|
</div>
|
|
<p className="mt-5 text-sm text-muted-foreground">
|
|
A emissão real depende das cotações,
|
|
avaliações, aprovações e verificações de
|
|
conflito no servidor.
|
|
</p>
|
|
</div>
|
|
</Panel>
|
|
)}
|
|
{(screen === 'receipts' || screen === 'stock') && (
|
|
<InventoryDemo key={screen} screen={screen} />
|
|
)}
|
|
{screen === 'operations' && <OperationsDemo />}
|
|
{screen === 'equipment' && <EquipmentDemo />}
|
|
{screen === 'investment' && (
|
|
<Panel
|
|
title="Regra de investimento · exemplo de circuito"
|
|
description="Sem limites predefinidos, consultas ou gravações de negócio."
|
|
>
|
|
<div className="space-y-3 text-sm">
|
|
<p>
|
|
A empresa escolhe o tecto por processo,
|
|
a base com/sem IVA e as datas. Dois
|
|
administradores distintos do proponente
|
|
aprovam a versão.
|
|
</p>
|
|
<p>
|
|
Equipamento exige também validação
|
|
técnica, Finanças para qualquer valor e
|
|
reserva em dotação de investimento. O
|
|
tecto aplica-se ao total do processo que
|
|
contenha equipamento.
|
|
</p>
|
|
<p>
|
|
Sem regra vigente, ou acima do tecto, a
|
|
adjudicação fica bloqueada. A ordem
|
|
conserva a versão aplicada; alterações
|
|
posteriores não reescrevem documentos
|
|
emitidos.
|
|
</p>
|
|
<p>
|
|
Aprovação adicional, agregação por
|
|
projecto e detecção automática de
|
|
fraccionamento não fazem parte desta
|
|
etapa.
|
|
</p>
|
|
</div>
|
|
</Panel>
|
|
)}
|
|
{screen === 'reinforcements' && (
|
|
<Panel
|
|
title="Reforço orçamental · exemplo fictício"
|
|
description="Valores ilustrativos; cada empresa define as suas dotações. Sem consultas ou gravações de negócio."
|
|
>
|
|
<div className="space-y-3 text-sm">
|
|
<p>
|
|
Dotação inicial fictícia de 1.000,00
|
|
MZN, com 300,00 MZN utilizados:
|
|
disponível de 700,00 MZN.
|
|
</p>
|
|
<p>
|
|
Um reforço proposto de 400,00 MZN fica
|
|
pendente. Só depois da aprovação por
|
|
outra pessoa a dotação acumulada passa a
|
|
1.400,00 MZN e o disponível a 1.100,00
|
|
MZN.
|
|
</p>
|
|
<p>
|
|
O valor inicial, a base de IVA, o centro
|
|
de custo e o exercício são preservados.
|
|
Repetir uma aprovação não duplica a
|
|
verba. Rejeições ficam no histórico sem
|
|
alterar o saldo.
|
|
</p>
|
|
</div>
|
|
</Panel>
|
|
)}
|
|
{screen === 'payments' && (
|
|
<Panel
|
|
title="Pagamento externo · exemplo fictício"
|
|
description="Sem registos reais, contas bancárias ou execução de transferências."
|
|
>
|
|
<div className="space-y-3 text-sm">
|
|
<p>
|
|
Factura fictícia de 1.000,00 MZN. Um
|
|
pagamento externo de 600,00 MZN é
|
|
registado com referência e evidência.
|
|
</p>
|
|
<p>
|
|
Enquanto pendente de conferência, o
|
|
saldo continua em 1.000,00 MZN. Após
|
|
confirmação por outra pessoa de
|
|
Finanças, fica em 400,00 MZN.
|
|
</p>
|
|
<p>
|
|
A versão bancária e as duas validações
|
|
são novamente verificadas. Repetir a
|
|
confirmação não duplica a liquidação; o
|
|
orçamento não é consumido outra vez.
|
|
</p>
|
|
<p>
|
|
Os valores são apenas exemplos. Cada
|
|
empresa tem as suas facturas e
|
|
montantes; o SGC não efectua a operação
|
|
no banco.
|
|
</p>
|
|
</div>
|
|
</Panel>
|
|
)}
|
|
{screen === 'bank' && (
|
|
<Panel
|
|
title="Dupla validação bancária · exemplo fictício"
|
|
description="Sem dados de contas, consultas ou gravações de negócio."
|
|
>
|
|
<div className="space-y-3 text-sm">
|
|
<p>
|
|
Procurement propõe uma versão com
|
|
evidência. Finanças A confere e aprova;
|
|
Finanças B confere de forma
|
|
independente.
|
|
</p>
|
|
<p>
|
|
Uma aprovação: pendente. Duas pessoas
|
|
distintas: duplamente validado. Repetir
|
|
a decisão da mesma pessoa não conta duas
|
|
vezes.
|
|
</p>
|
|
<p>
|
|
Nova versão: disponibilidade anterior
|
|
substituída e duas novas validações
|
|
obrigatórias. Rejeição, conflito ou
|
|
perda de elegibilidade bloqueiam o
|
|
estado.
|
|
</p>
|
|
<p>
|
|
Cada empresa mantém os seus fornecedores
|
|
e dados. Não autoriza transferências nem
|
|
confirma titularidade junto do banco.
|
|
</p>
|
|
</div>
|
|
</Panel>
|
|
)}
|
|
{screen === 'invoices' && (
|
|
<Panel
|
|
title="Factura parcial · exemplo fictício"
|
|
description="Sem consultas ou gravações de negócio. Os valores são apenas exemplos; cada empresa define as suas dotações e base de IVA."
|
|
>
|
|
<p className="mb-4 text-sm">
|
|
FT-DEMO-01 · Fornecedor fictício ·
|
|
OC-DEMO-01 · Validada por uma segunda pessoa
|
|
de Finanças.
|
|
</p>
|
|
<dl className="grid gap-3 text-sm sm:grid-cols-2">
|
|
<div>
|
|
<dt>Quantidade autorizada e aceite</dt>
|
|
<dd>10 unidades</dd>
|
|
</div>
|
|
<div>
|
|
<dt>Quantidade facturada</dt>
|
|
<dd>6 unidades</dd>
|
|
</div>
|
|
<div>
|
|
<dt>Valor desta factura</dt>
|
|
<dd>600,00 MZN (exemplo sem IVA)</dd>
|
|
</div>
|
|
<div>
|
|
<dt>
|
|
Compromisso antes da conferência
|
|
</dt>
|
|
<dd>1.000,00 MZN</dd>
|
|
</div>
|
|
<div>
|
|
<dt>Consumo após validação</dt>
|
|
<dd>600,00 MZN</dd>
|
|
</div>
|
|
<div>
|
|
<dt>Compromisso remanescente</dt>
|
|
<dd>400,00 MZN</dd>
|
|
</div>
|
|
</dl>
|
|
<p className="mt-4 text-sm">
|
|
O disponível não aumenta: o compromisso
|
|
converte-se em consumo. Preço, unidade, IVA
|
|
e quantidades são novamente conferidos no
|
|
servidor. Sem compromisso aprovado, a
|
|
factura permanece pendente. Não autoriza
|
|
pagamentos.
|
|
</p>
|
|
</Panel>
|
|
)}
|
|
{screen === 'appearance' && (
|
|
<>
|
|
<Panel
|
|
title="Paleta e estados"
|
|
description="Componentes e estilos partilhados com a aplicação para apoiar a revisão visual."
|
|
>
|
|
<div className="grid gap-4 sm:grid-cols-4">
|
|
{[
|
|
['Principal', 'bg-teal-700'],
|
|
['Aprovação', 'bg-teal-100'],
|
|
['Atenção', 'bg-amber-100'],
|
|
['Impedimento', 'bg-rose-100'],
|
|
].map(([label, color]) => (
|
|
<div key={label}>
|
|
<div
|
|
className={`h-20 rounded-lg border ${color}`}
|
|
/>
|
|
<p className="mt-2 text-sm">
|
|
{label}
|
|
</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className="mt-6 flex flex-wrap gap-3">
|
|
<Status value="draft" />
|
|
<Status value="pending" />
|
|
<Status value="approved" />
|
|
<Status value="rejected" />
|
|
</div>
|
|
</Panel>
|
|
<Panel
|
|
title="Campos e botões"
|
|
description="Os campos abaixo são apenas locais e não são enviados ou guardados."
|
|
>
|
|
<div className="grid gap-5 sm:grid-cols-2">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="sample-title">
|
|
Descrição de exemplo
|
|
</Label>
|
|
<Input
|
|
id="sample-title"
|
|
placeholder="Escreva para experimentar o campo"
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="sample-disabled">
|
|
Campo apenas de consulta
|
|
</Label>
|
|
<Input
|
|
id="sample-disabled"
|
|
disabled
|
|
value="Valor de demonstração"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="mt-6 flex flex-wrap gap-3">
|
|
<Button
|
|
onClick={() =>
|
|
setDetail({
|
|
title: 'Exemplo de diálogo',
|
|
subtitle:
|
|
'Sem alteração de dados',
|
|
rows: [
|
|
[
|
|
'Acção',
|
|
'Este botão apenas demonstra a apresentação de uma confirmação.',
|
|
],
|
|
],
|
|
})
|
|
}
|
|
>
|
|
Experimentar diálogo
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => navigate('overview')}
|
|
>
|
|
Voltar à visão geral
|
|
</Button>
|
|
<Button disabled>
|
|
Acção indisponível
|
|
</Button>
|
|
</div>
|
|
</Panel>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<Dialog
|
|
open={detail !== null}
|
|
onOpenChange={(open) => {
|
|
if (!open) setDetail(null);
|
|
}}
|
|
>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>{detail?.title}</DialogTitle>
|
|
<DialogDescription>
|
|
{detail?.subtitle}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<dl className="space-y-4">
|
|
{detail?.rows.map(([label, value]) => (
|
|
<div key={label}>
|
|
<dt className="text-xs text-muted-foreground">
|
|
{label}
|
|
</dt>
|
|
<dd className="mt-1 text-sm">{value}</dd>
|
|
</div>
|
|
))}
|
|
</dl>
|
|
<p className="border-t pt-4 text-xs text-muted-foreground">
|
|
Consulta livre · dados fictícios · nenhuma operação de
|
|
compra é efectuada.
|
|
</p>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</>
|
|
);
|
|
}
|