219 lines
8.6 KiB
TypeScript
219 lines
8.6 KiB
TypeScript
import { useForm } from '@inertiajs/react';
|
|
|
|
export type TraceabilityPlan = {
|
|
hash: string;
|
|
code_matches: boolean;
|
|
backup_ready: boolean;
|
|
applied: boolean;
|
|
reversed: boolean;
|
|
history: {
|
|
id: string;
|
|
action: string;
|
|
actor_user_id: number;
|
|
occurred_at: string;
|
|
reason: string | null;
|
|
}[];
|
|
plan: {
|
|
target: { database: string; port: number };
|
|
rows: {
|
|
key: string;
|
|
reference: string;
|
|
association: string;
|
|
role: string;
|
|
source_reference: string | null;
|
|
source_quote: string | null;
|
|
supports: string;
|
|
traceability: string;
|
|
existing_validation: string | null;
|
|
effect: string;
|
|
}[];
|
|
updates: {
|
|
id: string;
|
|
reference: string;
|
|
current: {
|
|
source_reference: string | null;
|
|
evidence_reference: string | null;
|
|
};
|
|
values: { source_reference: string; evidence_reference: string };
|
|
source_quote: string;
|
|
effect: string;
|
|
}[];
|
|
pending: { reference: string; reason: string }[];
|
|
};
|
|
};
|
|
export default function TerritorialTraceabilityPlan({
|
|
preview,
|
|
}: {
|
|
preview: TraceabilityPlan;
|
|
}) {
|
|
const form = useForm({
|
|
hash: preview.hash,
|
|
operation: 'traceability',
|
|
reverse: false,
|
|
reason: '',
|
|
confirm_reverse: false,
|
|
});
|
|
return (
|
|
<section className="space-y-4 rounded border p-5">
|
|
<h2 className="text-xl font-bold">
|
|
V2 — aplicar rastreabilidade territorial
|
|
</h2>
|
|
<p>
|
|
Destino: {preview.plan.target.database}:
|
|
{preview.plan.target.port}. Fontes oficiais identificadas são
|
|
aceites; não se repete validação da proveniência. Estados
|
|
existentes são preservados.
|
|
</p>
|
|
<p>
|
|
{preview.plan.rows.length} associações/fontes;{' '}
|
|
{preview.plan.updates.length} atualizações de referências.
|
|
Nenhuma nova implantação ou relação por proximidade.
|
|
</p>
|
|
<p role="status">
|
|
Backup/restauro/reversão:{' '}
|
|
{preview.backup_ready
|
|
? 'verificados (validade 24 horas)'
|
|
: 'pendentes ou expirados'}{' '}
|
|
· Código:{' '}
|
|
{preview.code_matches
|
|
? 'corresponde ao plano'
|
|
: 'plano deve ser atualizado'}{' '}
|
|
· Aplicação:{' '}
|
|
{preview.applied
|
|
? 'aplicada'
|
|
: preview.reversed
|
|
? 'revertida; novo plano necessário'
|
|
: 'não aplicada'}
|
|
</p>
|
|
<details>
|
|
<summary>Histórico de aplicação e reversão</summary>
|
|
{preview.history.map((event) => (
|
|
<p key={event.id}>
|
|
{event.occurred_at} · {event.action} · conta{' '}
|
|
{event.actor_user_id} · Motivo:{' '}
|
|
{event.reason ?? 'não registado no evento original'}
|
|
</p>
|
|
))}
|
|
</details>
|
|
<form
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
form.transform(() => ({
|
|
hash: preview.hash,
|
|
operation: 'traceability',
|
|
reverse: preview.applied,
|
|
reason: form.data.reason,
|
|
confirm_reverse: form.data.confirm_reverse,
|
|
}));
|
|
form.post('/management/relationship-repair', {
|
|
preserveScroll: true,
|
|
});
|
|
}}
|
|
>
|
|
{preview.applied && (
|
|
<fieldset className="space-y-3 rounded border border-red-600 p-4">
|
|
<legend>
|
|
Reversão — restitui as referências anteriores
|
|
</legend>
|
|
<label className="block">
|
|
Motivo da reversão
|
|
<textarea
|
|
className="block w-full rounded border p-2"
|
|
value={form.data.reason}
|
|
maxLength={1000}
|
|
required
|
|
onChange={(event) =>
|
|
form.setData('reason', event.target.value)
|
|
}
|
|
/>
|
|
</label>
|
|
<label className="block">
|
|
<input
|
|
type="checkbox"
|
|
checked={form.data.confirm_reverse}
|
|
onChange={(event) =>
|
|
form.setData(
|
|
'confirm_reverse',
|
|
event.target.checked,
|
|
)
|
|
}
|
|
/>{' '}
|
|
Confirmo que pretendo reverter este incremento
|
|
</label>
|
|
</fieldset>
|
|
)}
|
|
<button
|
|
disabled={
|
|
form.processing ||
|
|
!preview.backup_ready ||
|
|
!preview.code_matches ||
|
|
preview.reversed ||
|
|
(preview.applied &&
|
|
(!form.data.confirm_reverse ||
|
|
!form.data.reason.trim()))
|
|
}
|
|
className="rounded bg-emerald-800 px-4 py-3 text-white disabled:opacity-50"
|
|
>
|
|
{preview.applied
|
|
? 'Reverter este incremento de rastreabilidade'
|
|
: 'Aplicar rastreabilidade territorial V2'}
|
|
</button>
|
|
{Object.values(form.errors).map((error) => (
|
|
<p role="alert" key={error}>
|
|
{error}
|
|
</p>
|
|
))}
|
|
</form>
|
|
<h3 className="font-bold">Alterações aos campos existentes</h3>
|
|
{preview.plan.updates.map((update) => (
|
|
<article className="border-t py-3" key={update.id}>
|
|
<h4 className="font-bold">{update.reference}</h4>
|
|
<p>
|
|
Referência atual:{' '}
|
|
{update.current.source_reference ?? 'não preenchida'};
|
|
evidência atual:{' '}
|
|
{update.current.evidence_reference ?? 'não preenchida'}.
|
|
</p>
|
|
<p>
|
|
Proposta: {update.values.source_reference}; dossier{' '}
|
|
{update.values.evidence_reference}.
|
|
</p>
|
|
<p>Fundamento: {update.source_quote}</p>
|
|
<p>{update.effect}</p>
|
|
</article>
|
|
))}
|
|
<h3 className="font-bold">Fundamento por associação</h3>
|
|
{preview.plan.rows.map((row) => (
|
|
<article className="border-t py-3" key={row.key}>
|
|
<h3 className="font-bold">
|
|
{row.reference} — {row.association} ({row.role})
|
|
</h3>
|
|
<p>
|
|
Atual:{' '}
|
|
{row.existing_validation ??
|
|
'sem campo de validação nesta associação'}
|
|
; estado preservado.
|
|
</p>
|
|
<p>
|
|
Proposta: ligar a referência e o fundamento. Fonte:{' '}
|
|
{row.source_reference ?? 'por recuperar no acervo'}
|
|
</p>
|
|
<p>Citação/âmbito: {row.source_quote ?? row.supports}</p>
|
|
<p>
|
|
Rastreabilidade: {row.traceability} · Efeito:{' '}
|
|
{row.effect}
|
|
</p>
|
|
</article>
|
|
))}
|
|
<h3 className="font-bold">
|
|
Pendências de rastreabilidade (não invalidam dados)
|
|
</h3>
|
|
{preview.plan.pending.map((row, i) => (
|
|
<p key={i}>
|
|
{row.reference}: {row.reason}
|
|
</p>
|
|
))}
|
|
</section>
|
|
);
|
|
}
|