Files
Investidor-V2/resources/js/pages/structures/editor.tsx
T
nala e65cb26901
tests / ci (push) Canceled after 0s
varias alteracoes
2026-10-06 14:50:12 +02:00

345 lines
17 KiB
TypeScript

import { Link, router, useForm } from "@inertiajs/react";
import InvestmentTerritory, { TerritoryFields } from "@/components/investment-territory";
import { Management, Field, button, control } from "../management/shared";
import { titles, statuses, stages, roles } from "./types";
import type { Structure, StructureRecord, Chain } from "./types";
import ValueNetwork from "./value-network";
import type { ChainLink, Gap } from "./value-network";
function ChainForm({ clusterId, chain }: { clusterId: string; chain?: Chain }) {
const form = useForm({
code: chain?.code ?? "",
name: chain?.name ?? "",
stage: chain?.stage ?? "input",
});
return (
<form
className="space-y-3 rounded border p-4"
onSubmit={(e) => {
e.preventDefault();
const url = `/management/clusters/${clusterId}/chains`;
if (chain) form.put(`${url}/${chain.id}`, { preserveScroll: true });
else
form.post(url, {
preserveScroll: true,
onSuccess: () => form.reset(),
});
}}
>
<h3 className="font-semibold">{chain ? chain.name : "Nova etapa"}</h3>
<Field label="Código da etapa" error={form.errors.code}>
<input
className={control}
value={form.data.code}
onChange={(e) => form.setData("code", e.target.value)}
/>
</Field>
<Field label="Nome da etapa" error={form.errors.name}>
<input
className={control}
value={form.data.name}
onChange={(e) => form.setData("name", e.target.value)}
/>
</Field>
<Field label="Fase da cadeia" error={form.errors.stage}>
<select
className={control}
value={form.data.stage}
onChange={(e) => form.setData("stage", e.target.value)}
>
{Object.entries(stages).map(([value, label]) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
</Field>
<button className={button} disabled={form.processing}>
Guardar etapa
</button>
</form>
);
}
export default function Editor({
structure,
record,
projects,
viewerId,
chains,
memberships,
chainLinks,
gaps,
}: {
structure: Structure;
record: StructureRecord | null;
projects: { id: string; name: string; code: string }[];
viewerId: number;
chains: Chain[];
chainLinks: ChainLink[];
gaps: Gap[];
memberships: {
id: string;
project_id: string;
name: string;
role: string;
association_type: string;
source_reference: string | null;
evidence_reference: string | null;
valid_from: string | null;
valid_until: string | null;
validation_status: string;
version_no: number;
editor_id: number | null;
}[];
}) {
const isProject = structure === "projects";
const isCluster = structure === "clusters";
const isCorridor = structure === "corridors";
const singular = isProject ? "projecto" : isCluster ? "cluster" : "corredor";
const form = useForm({
code: record?.code ?? "",
name: record?.name ?? "",
status: record?.status ?? (isProject ? "draft" : isCluster ? "planned" : "proposed"),
sector: record?.sector ?? "",
description: record?.description ?? "",
budget_amount: record?.budget_amount ?? "",
budget_currency: record?.budget_currency ?? "",
start_date: record?.start_date?.slice(0, 10) ?? "",
end_date: record?.end_date?.slice(0, 10) ?? "",
province_id: "",
district_id: "",
admin_post_id: "",
locality_id: "",
});
const association = useForm({ project_id: "", version_no: 0, role: "complementary", association_type: "potential", source_reference: "", evidence_reference: "", valid_from: "", valid_until: "" });
return (
<Management title={`${record ? "Editar" : "Criar"} ${singular}`}>
<Link className="underline" href={`/management/${structure}`}>
Voltar a {titles[structure].toLowerCase()}
</Link>
<p>Registo interno de gestão. O estado activo não publica este conteúdo no portal.</p>
{!record && (
<TerritoryFields
endpoint="/management/territory-options"
onChange={(path) =>
form.setData((data) => ({
...data,
province_id: path[0]?.id ?? "",
district_id: path[1]?.id ?? "",
admin_post_id: path[2]?.id ?? "",
locality_id: path[3]?.id ?? "",
}))
}
/>
)}
<form
className="max-w-3xl space-y-4"
onSubmit={(e) => {
e.preventDefault();
if (record) form.put(`/management/${structure}/${record.id}`);
else form.post(`/management/${structure}`);
}}
>
<Field label="Código" error={form.errors.code}>
<input
className={control}
value={form.data.code}
onChange={(e) => form.setData("code", e.target.value)}
/>
</Field>
<Field label="Nome" error={form.errors.name}>
<input
className={control}
value={form.data.name}
onChange={(e) => form.setData("name", e.target.value)}
/>
</Field>
<Field label="Estado" error={form.errors.status}>
<select
className={control}
value={form.data.status}
onChange={(e) => form.setData("status", e.target.value)}
>
{Object.entries(statuses[structure]).map(([value, label]) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
</Field>
{isCluster && (
<Field label="Sector do cluster" error={form.errors.sector}>
<input
className={control}
value={form.data.sector}
onChange={(e) => form.setData("sector", e.target.value)}
/>
</Field>
)}
{isProject && (
<>
<Field label="Descrição" error={form.errors.description}>
<textarea
className={control}
rows={5}
value={form.data.description}
onChange={(e) => form.setData("description", e.target.value)}
/>
</Field>
<Field
label="Orçamento (use ponto para os decimais)"
error={form.errors.budget_amount}
>
<input
inputMode="decimal"
className={control}
value={form.data.budget_amount}
onChange={(e) => form.setData("budget_amount", e.target.value)}
/>
</Field>
<Field
label="Moeda (código de três letras)"
error={form.errors.budget_currency}
>
<input
maxLength={3}
className={control}
value={form.data.budget_currency}
onChange={(e) =>
form.setData("budget_currency", e.target.value.toUpperCase())
}
/>
</Field>
<Field label="Data de início" error={form.errors.start_date}>
<input
type="date"
className={control}
value={form.data.start_date}
onChange={(e) => form.setData("start_date", e.target.value)}
/>
</Field>
<Field label="Data de conclusão" error={form.errors.end_date}>
<input
type="date"
className={control}
value={form.data.end_date}
onChange={(e) => form.setData("end_date", e.target.value)}
/>
</Field>
</>
)}
{isCorridor && (
<Field label="Descrição" error={form.errors.description}>
<textarea
className={control}
rows={5}
value={form.data.description}
onChange={(e) => form.setData("description", e.target.value)}
/>
</Field>
)}
<button className={button} disabled={form.processing}>
Guardar
</button>
</form>
{isCluster && record && (
<>
<section className="max-w-3xl space-y-4">
<h2 className="text-xl font-semibold">Projectos associados</h2>
{memberships.map((member) => (
<p key={member.id}>
<Link
className="underline"
href={`/management/projects/${member.project_id}/edit`}
>
{member.name}
</Link>{" "}
· {roles[member.role]}
</p>
))}
{memberships.map((member) => (
<div key={`${member.id}-governance`} className="text-sm">
{member.validation_status} - {member.association_type} - {member.source_reference ?? "Sem fonte"} - {member.evidence_reference ?? "Sem evidencia"} - {member.valid_from ?? "Sem inicio"} - {member.valid_until ?? "Sem termo"}
{member.validation_status === "pending" && member.editor_id !== viewerId && (
<button className={button} type="button" onClick={() => router.post(`/management/structure-relations/cluster-project/${member.id}/validate`, { version_no: member.version_no }, { preserveScroll: true })}>Validar associacao</button>
)}
</div>
))}
{!memberships.length && <p>Ainda não existem projectos associados.</p>}
<form
className="space-y-3"
onSubmit={(e) => {
e.preventDefault();
association.post(`/management/clusters/${record.id}/projects`, {
preserveScroll: true,
});
}}
>
<Field label="Projecto" error={association.errors.project_id}>
<select
className={control}
value={association.data.project_id}
onChange={(e) =>
association.setData((data) => ({ ...data, project_id: e.target.value,
version_no: memberships.find((member) => member.project_id === e.target.value)?.version_no ?? 0 }))
}
>
<option value="">Seleccione</option>
{projects.map((project) => (
<option key={project.id} value={project.id}>
{project.code} — {project.name}
</option>
))}
</select>
</Field>
<Field label="Papel no cluster" error={association.errors.role}>
<select
className={control}
value={association.data.role}
onChange={(e) => association.setData("role", e.target.value)}
>
{Object.entries(roles).map(([value, label]) => (
<option key={value} value={value}>
{label}
</option>
))}
</select>
</Field>
<Field label="Tipo de associacao" error={association.errors.association_type}>
<select className={control} value={association.data.association_type} onChange={(e) => association.setData("association_type", e.target.value)}>
<option value="official">Oficial</option><option value="influence">Influencia</option><option value="proximity">Proximidade</option><option value="potential">Potencial</option><option value="historical">Historica</option>
</select>
</Field>
<Field label="Referencia da fonte" error={association.errors.source_reference}><textarea className={control} value={association.data.source_reference} onChange={(e) => association.setData("source_reference", e.target.value)} /></Field>
<Field label="Referencia da evidencia" error={association.errors.evidence_reference}><input className={control} value={association.data.evidence_reference} onChange={(e) => association.setData("evidence_reference", e.target.value)} /></Field>
<Field label="Vigente desde" error={association.errors.valid_from}><input className={control} type="date" value={association.data.valid_from} onChange={(e) => association.setData("valid_from", e.target.value)} /></Field>
<Field label="Vigente ate" error={association.errors.valid_until}><input className={control} type="date" value={association.data.valid_until} onChange={(e) => association.setData("valid_until", e.target.value)} /></Field>
<button className={button} disabled={association.processing}>
Guardar associação
</button>
<p className="text-sm">
Seleccionar um projecto já associado permite actualizar o seu papel.
</p>
</form>
</section>
<section className="max-w-3xl space-y-4">
<h2 className="text-xl font-semibold">Etapas da cadeia de valor</h2>
{chains.map((chain) => (
<ChainForm key={chain.id} clusterId={record.id} chain={chain} />
))}
<ChainForm clusterId={record.id} />
</section>
<ValueNetwork
clusterId={record.id}
chains={chains}
links={chainLinks}
gaps={gaps}
/>
</>
)}
{record && <InvestmentTerritory kind={structure} id={record.id} />}
</Management>
);
}