345 lines
16 KiB
TypeScript
345 lines
16 KiB
TypeScript
import { useState } from 'react';
|
|
import { InventoryNotice, ReceiptProgressTable } from '@/components/inventory';
|
|
import { Panel, controlClass } 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 { quantity } from '@/types/inventory';
|
|
import type { ReceiptProgress } from '@/types/inventory';
|
|
|
|
const progress: ReceiptProgress = {
|
|
state: 'partial',
|
|
has_divergences: true,
|
|
lines: [
|
|
{
|
|
id: 1,
|
|
description: 'Papel A4 de demonstração',
|
|
unit: 'cx',
|
|
purchase_type: 'consumable',
|
|
ordered_milli: 10000,
|
|
received_milli: 6000,
|
|
accepted_milli: 5000,
|
|
rejected_milli: 1000,
|
|
delivery_pending_milli: 4000,
|
|
acceptance_pending_milli: 5000,
|
|
},
|
|
{
|
|
id: 2,
|
|
description: 'Tinteiro de demonstração',
|
|
unit: 'un',
|
|
purchase_type: 'consumable',
|
|
ordered_milli: 3000,
|
|
received_milli: 3000,
|
|
accepted_milli: 3000,
|
|
rejected_milli: 0,
|
|
delivery_pending_milli: 0,
|
|
acceptance_pending_milli: 0,
|
|
},
|
|
],
|
|
};
|
|
const examples = [
|
|
{
|
|
code: 'DEMO-A4',
|
|
name: 'Papel A4 de demonstração',
|
|
unit: 'cx',
|
|
quantity_milli: 5000,
|
|
warehouse: 'Armazém de demonstração',
|
|
receipt: 'REC-DEMO-001',
|
|
note: 'Uma caixa rejeitada por danos; só cinco caixas aceites entraram em stock.',
|
|
},
|
|
{
|
|
code: 'DEMO-TINTA',
|
|
name: 'Tinteiro de demonstração',
|
|
unit: 'un',
|
|
quantity_milli: 3000,
|
|
warehouse: 'Armazém de demonstração',
|
|
receipt: 'REC-DEMO-001',
|
|
note: 'Três unidades recebidas e aceites integralmente.',
|
|
},
|
|
];
|
|
|
|
export default function InventoryDemo({
|
|
screen,
|
|
}: {
|
|
screen: 'receipts' | 'stock';
|
|
}) {
|
|
const [search, setSearch] = useState('');
|
|
const [detail, setDetail] = useState<(typeof examples)[number] | null>(
|
|
null,
|
|
);
|
|
const [quality, setQuality] = useState('reservations');
|
|
const [notice, setNotice] = useState(false);
|
|
const visible = examples.filter((example) =>
|
|
`${example.code} ${example.name}`
|
|
.toLowerCase()
|
|
.includes(search.toLowerCase()),
|
|
);
|
|
return (
|
|
<>
|
|
<InventoryNotice />
|
|
{screen === 'receipts' ? (
|
|
<>
|
|
<Panel
|
|
title="OC-DEMO-001 · Recepção parcial"
|
|
description="Exemplo fictício: seis caixas conferidas, cinco aceites e uma rejeitada. Existem quatro caixas por entregar."
|
|
>
|
|
<ReceiptProgressTable progress={progress} />
|
|
<p className="mt-4 text-sm text-muted-foreground">
|
|
Emitente: Comprador de exemplo · Recepção: Armazém
|
|
de exemplo. São pessoas distintas.
|
|
</p>
|
|
</Panel>
|
|
<Panel
|
|
title="Conferência de qualidade"
|
|
description="Experimente os campos. Este formulário é local, não é enviado e não confirma recepções reais."
|
|
>
|
|
<form
|
|
className="space-y-4"
|
|
onSubmit={(event) => {
|
|
event.preventDefault();
|
|
setNotice(true);
|
|
}}
|
|
>
|
|
<div className="grid gap-4 md:grid-cols-3">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="demo-guide">
|
|
Guia fictícia
|
|
</Label>
|
|
<Input
|
|
id="demo-guide"
|
|
defaultValue="GR-DEMO-001"
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="demo-received">
|
|
Quantidade recebida
|
|
</Label>
|
|
<Input
|
|
id="demo-received"
|
|
type="number"
|
|
min="0.001"
|
|
step="0.001"
|
|
defaultValue="6"
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="demo-quality">
|
|
Qualidade
|
|
</Label>
|
|
<select
|
|
id="demo-quality"
|
|
className={controlClass}
|
|
value={quality}
|
|
onChange={(event) =>
|
|
setQuality(event.target.value)
|
|
}
|
|
>
|
|
<option value="compliant">
|
|
Conforme
|
|
</option>
|
|
<option value="reservations">
|
|
Conforme com reservas
|
|
</option>
|
|
<option value="nonconforming">
|
|
Não conforme
|
|
</option>
|
|
</select>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="demo-accepted">
|
|
Quantidade aceite
|
|
</Label>
|
|
<Input
|
|
id="demo-accepted"
|
|
type="number"
|
|
min="0"
|
|
step="0.001"
|
|
defaultValue="5"
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="demo-location">
|
|
Destino fictício
|
|
</Label>
|
|
<select
|
|
id="demo-location"
|
|
className={controlClass}
|
|
>
|
|
<option>Armazém de demonstração</option>
|
|
</select>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="demo-article">
|
|
Artigo fictício
|
|
</Label>
|
|
<select
|
|
id="demo-article"
|
|
className={controlClass}
|
|
>
|
|
<option>
|
|
DEMO-A4 · Papel A4 de demonstração
|
|
</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="demo-divergence">
|
|
Divergências
|
|
</Label>
|
|
<textarea
|
|
id="demo-divergence"
|
|
className={`${controlClass} h-24 py-3`}
|
|
defaultValue="Uma caixa com danos na embalagem e humidade. Rejeitada e sem entrada em stock."
|
|
/>
|
|
</div>
|
|
<Button>Pré-visualizar conferência fictícia</Button>
|
|
{notice && (
|
|
<p
|
|
role="status"
|
|
className="text-sm text-teal-700 dark:text-teal-400"
|
|
>
|
|
Demonstração local: nenhum dado foi
|
|
guardado. No módulo autenticado, o servidor
|
|
verifica quantidades, segregação e
|
|
duplicações.
|
|
</p>
|
|
)}
|
|
</form>
|
|
</Panel>
|
|
</>
|
|
) : (
|
|
<>
|
|
<Panel
|
|
title="Existências fictícias"
|
|
description="Somente quantidades aceites. Pesquise e abra os detalhes para rever a apresentação."
|
|
>
|
|
<Input
|
|
aria-label="Pesquisar artigos fictícios"
|
|
placeholder="Pesquisar código ou artigo de demonstração"
|
|
value={search}
|
|
onChange={(event) => setSearch(event.target.value)}
|
|
className="mb-5 max-w-sm"
|
|
/>
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full text-left text-sm">
|
|
<thead className="border-b text-muted-foreground">
|
|
<tr>
|
|
<th className="pr-4 pb-3">Artigo</th>
|
|
<th className="pr-4">Armazém</th>
|
|
<th className="pr-4">Em stock</th>
|
|
<th>Histórico</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{visible.map((example) => (
|
|
<tr
|
|
className="border-b last:border-0"
|
|
key={example.code}
|
|
>
|
|
<td className="py-4 pr-4">
|
|
<strong>{example.code}</strong>
|
|
<p className="mt-1 text-muted-foreground">
|
|
{example.name}
|
|
</p>
|
|
</td>
|
|
<td className="pr-4">
|
|
{example.warehouse}
|
|
</td>
|
|
<td className="pr-4">
|
|
{quantity(
|
|
example.quantity_milli,
|
|
)}{' '}
|
|
{example.unit}
|
|
</td>
|
|
<td>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() =>
|
|
setDetail(example)
|
|
}
|
|
>
|
|
Ver movimentos
|
|
</Button>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{visible.length === 0 && (
|
|
<p className="py-5 text-sm text-muted-foreground">
|
|
Nenhum exemplo corresponde à pesquisa.
|
|
</p>
|
|
)}
|
|
</Panel>
|
|
<Panel title="Histórico de entradas fictícias">
|
|
<div className="space-y-3">
|
|
{visible.map((example) => (
|
|
<button
|
|
type="button"
|
|
onClick={() => setDetail(example)}
|
|
key={example.code}
|
|
className="w-full rounded-lg border p-4 text-left text-sm"
|
|
>
|
|
<strong>
|
|
+{quantity(example.quantity_milli)}{' '}
|
|
{example.unit} · {example.code}
|
|
</strong>
|
|
<p className="mt-1 text-muted-foreground">
|
|
{example.receipt} · OC-DEMO-001 · Guia
|
|
GR-DEMO-001
|
|
</p>
|
|
</button>
|
|
))}
|
|
</div>
|
|
<p className="mt-4 text-xs text-muted-foreground">
|
|
Os exemplos não correspondem a existências da
|
|
empresa. Consulte Saídas e inventários para rever os
|
|
novos exemplos fictícios.
|
|
</p>
|
|
</Panel>
|
|
</>
|
|
)}
|
|
<Dialog
|
|
open={detail !== null}
|
|
onOpenChange={(open) => {
|
|
if (!open) setDetail(null);
|
|
}}
|
|
>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
{detail?.code} · Entrada fictícia
|
|
</DialogTitle>
|
|
<DialogDescription>
|
|
Histórico ilustrativo, sem acesso aos registos
|
|
reais.
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
{detail && (
|
|
<div className="space-y-3 text-sm">
|
|
<p>
|
|
{detail.name} · +
|
|
{quantity(detail.quantity_milli)} {detail.unit}
|
|
</p>
|
|
<p>{detail.receipt} · OC-DEMO-001 · GR-DEMO-001</p>
|
|
<p>{detail.note}</p>
|
|
<p>
|
|
Confirmado por Armazém de exemplo, distinto do
|
|
emitente.
|
|
</p>
|
|
</div>
|
|
)}
|
|
</DialogContent>
|
|
</Dialog>
|
|
</>
|
|
);
|
|
}
|