749 lines
39 KiB
TypeScript
749 lines
39 KiB
TypeScript
import { Head, Link } from '@inertiajs/react';
|
|
import { useState } from 'react';
|
|
import {
|
|
ArrowRight,
|
|
Building2,
|
|
ClipboardList,
|
|
Eye,
|
|
FileText,
|
|
LayoutGrid,
|
|
LockKeyhole,
|
|
Menu,
|
|
Palette,
|
|
Scale,
|
|
Search,
|
|
ShieldCheck,
|
|
Users,
|
|
} 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';
|
|
|
|
// 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: 'appearance', title: 'Cores e componentes', icon: Palette },
|
|
];
|
|
type Detail = { title: string; subtitle: string; rows: [string, string][] };
|
|
export default function Preview() {
|
|
const [screen, setScreen] = useState('overview');
|
|
const [search, setSearch] = useState('');
|
|
const [menu, setMenu] = useState(false);
|
|
const [detail, setDetail] = useState<Detail | null>(null);
|
|
const title = navigation.find((n) => n.id === screen)?.title ?? 'Consulta';
|
|
function navigate(id: string) {
|
|
setScreen(id);
|
|
setSearch('');
|
|
setMenu(false);
|
|
}
|
|
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">
|
|
<aside
|
|
className={`${menu ? 'flex' : 'hidden'} fixed inset-y-0 left-0 z-30 w-64 flex-col border-r bg-background p-5 lg:flex`}
|
|
>
|
|
<Link
|
|
href="/consulta"
|
|
className="mb-8 flex items-center gap-3 text-xl font-semibold"
|
|
>
|
|
<span className="grid size-9 place-items-center rounded-xl bg-teal-700 text-sm text-white">
|
|
PF
|
|
</span>
|
|
ProcuraFlow
|
|
</Link>
|
|
<div className="mb-7 rounded-xl border bg-muted/30 p-4">
|
|
<p className="flex items-center gap-2 text-sm font-semibold">
|
|
<Building2 className="size-4" />
|
|
Empresa de demonstração
|
|
</p>
|
|
<p className="mt-2 text-xs text-muted-foreground">
|
|
Consulta livre · sem autenticação
|
|
</p>
|
|
</div>
|
|
<nav className="space-y-2">
|
|
{navigation.map((n) => (
|
|
<button
|
|
type="button"
|
|
key={n.id}
|
|
onClick={() => navigate(n.id)}
|
|
aria-current={
|
|
screen === n.id ? 'page' : undefined
|
|
}
|
|
className={`flex w-full items-center gap-3 rounded-lg px-3 py-3 text-left text-sm transition-colors ${screen === n.id ? 'bg-teal-50 font-medium text-teal-800 dark:bg-teal-950 dark:text-teal-200' : 'text-muted-foreground hover:bg-muted'}`}
|
|
>
|
|
<n.icon className="size-4" />
|
|
{n.title}
|
|
</button>
|
|
))}
|
|
</nav>
|
|
<p className="mt-auto pt-8 text-xs leading-5 text-muted-foreground">
|
|
Dados fictícios. A navegação e a pesquisa permitem
|
|
avaliar a apresentação; não executam compras.
|
|
</p>
|
|
</aside>
|
|
{menu && (
|
|
<button
|
|
className="fixed inset-0 z-20 bg-black/25 lg:hidden"
|
|
aria-label="Fechar menu"
|
|
onClick={() => setMenu(false)}
|
|
/>
|
|
)}
|
|
<div className="lg:ml-64">
|
|
<header className="flex min-h-20 items-center justify-between gap-3 border-b bg-background px-5 lg:px-8">
|
|
<div className="flex items-center gap-3">
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="lg:hidden"
|
|
aria-label="Abrir menu"
|
|
onClick={() => setMenu(!menu)}
|
|
>
|
|
<Menu className="size-5" />
|
|
</Button>
|
|
<span className="text-sm text-muted-foreground">
|
|
Consulta visual <span className="mx-2">/</span>
|
|
<strong className="font-medium text-foreground">
|
|
{title}
|
|
</strong>
|
|
</span>
|
|
</div>
|
|
<Button asChild variant="outline">
|
|
<Link href="/login">
|
|
<LockKeyhole className="size-4" />
|
|
Entrar na empresa
|
|
</Link>
|
|
</Button>
|
|
</header>
|
|
<main 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="ProcuraFlow · 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 === '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>
|
|
</>
|
|
)}
|
|
<footer className="py-4 text-xs text-muted-foreground">
|
|
ProcuraFlow · Consulta pública de exemplos · Os
|
|
processos reais permanecem no acesso autenticado.
|
|
</footer>
|
|
</main>
|
|
</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>
|
|
</>
|
|
);
|
|
}
|