Files
compras/resources/js/pages/stock/catalogue.tsx
T
nala c6373365bf
tests / ci (push) Canceled after 0s
commit9
2026-10-30 20:02:30 +02:00

201 lines
7.1 KiB
TypeScript

import { Head, Link, useForm } from '@inertiajs/react';
import {
PageTitle,
Panel,
FormErrors,
controlClass,
} from '@/components/procurement';
import { Button } from '@/components/ui/button';
type Entry = {
id: number;
code: string;
name: string;
unit: string;
stock_basis: string;
version: number;
category: string;
purchase_type: string;
published: boolean;
source_current: boolean;
};
function EntryForm({ entry }: { entry: Entry }) {
const form = useForm({
version: entry.version,
stock_basis: entry.stock_basis,
category: entry.category,
purchase_type: entry.purchase_type,
published: entry.published,
reason: '',
});
return (
<form
className="space-y-3"
onSubmit={(event) => {
event.preventDefault();
form.post(`/stock/catalogo/${entry.id}`, {
preserveScroll: true,
});
}}
>
<FormErrors errors={form.errors} />
<label className="grid gap-1">
Categoria
<input
className={controlClass}
required
maxLength={100}
value={form.data.category}
onChange={(event) =>
form.setData('category', event.target.value)
}
/>
</label>
<label className="grid gap-1">
Tipo de compra
<select
className={controlClass}
value={form.data.purchase_type}
onChange={(event) =>
form.setData('purchase_type', event.target.value)
}
>
<option value="consumable">Insumos / matéria-prima</option>
<option value="other">Outro bem</option>
</select>
</label>
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={form.data.published}
onChange={(event) =>
form.setData('published', event.target.checked)
}
/>
Disponível para seleccionar nas requisições da empresa
</label>
<label className="grid gap-1">
Motivo
<textarea
className={controlClass}
required
minLength={10}
maxLength={4000}
value={form.data.reason}
onChange={(event) =>
form.setData('reason', event.target.value)
}
/>
</label>
<Button disabled={form.processing}>
Guardar versão do catálogo
</Button>
</form>
);
}
export default function Catalogue({
records,
filters,
ready,
canManage,
}: {
records: {
data: Entry[];
total: number;
prev_page_url: string | null;
next_page_url: string | null;
};
filters: { search?: string };
ready: boolean;
canManage: boolean;
}) {
const search = useForm({ search: filters.search ?? '' });
return (
<>
<Head title="Catálogo para requisições" />
<div className="mx-auto max-w-5xl space-y-6 p-5 lg:p-8">
<PageTitle
eyebrow="Armazém e compras"
title="Catálogo para requisições"
description="Reutilize o cadastro de artigos. Publique apenas os bens que os solicitantes podem seleccionar, preservando os dados de cada pedido."
/>
<Link className="underline" href="/stock">
Cadastro e existências
</Link>
<Panel title="Pesquisar artigos">
<form
className="flex gap-3"
onSubmit={(event) => {
event.preventDefault();
search.get('/stock/catalogo', {
preserveState: true,
});
}}
>
<input
className={controlClass}
aria-label="Código ou nome"
maxLength={100}
value={search.data.search}
onChange={(event) =>
search.setData('search', event.target.value)
}
/>
<Button>Pesquisar</Button>
</form>
</Panel>
{!ready && (
<p>Instalação do catálogo pendente neste ambiente.</p>
)}
{records.data.map((entry) => (
<Panel
key={`${entry.id}-${entry.version}-${entry.stock_basis}`}
title={`${entry.code} · ${entry.name}`}
>
<p className="mb-3 text-sm">
Unidade: {entry.unit} · versão {entry.version} ·{' '}
{entry.published ? 'Publicado' : 'Não publicado'}
</p>
{!entry.source_current && (
<p className="mb-3 text-sm">
O cadastro mudou. Confira e guarde uma nova
versão antes de novas utilizações.
</p>
)}
{ready && canManage && <EntryForm entry={entry} />}
</Panel>
))}
{!records.total && (
<p>
Sem artigos neste âmbito. Registe o artigo no cadastro
de stock antes de o publicar.
</p>
)}
<div className="flex gap-4">
{records.prev_page_url && (
<Link
className="underline"
href={records.prev_page_url}
>
Anterior
</Link>
)}
{records.next_page_url && (
<Link
className="underline"
href={records.next_page_url}
>
Seguinte
</Link>
)}
</div>
<p className="text-sm">
A publicação não divulga saldos ou movimentos. Equipamentos
e sobressalentes mantêm o seu roteiro técnico; serviços
continuam no preenchimento próprio.
</p>
</div>
</>
);
}