Files
Investidor-V2/resources/js/pages/structures/editor.tsx
T
nala 250ac35fd1
tests / ci (push) Canceled after 0s
first commit
2026-09-28 10:01:09 +02:00

354 lines
14 KiB
TypeScript

import { Link, useForm } from '@inertiajs/react';
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,
chains,
memberships,
chainLinks,
gaps,
}: {
structure: Structure;
record: StructureRecord | null;
projects: { id: string; name: string; code: string }[];
chains: Chain[];
chainLinks: ChainLink[];
gaps: Gap[];
memberships: {
id: string;
project_id: string;
name: string;
role: string;
}[];
}) {
const isProject = structure === 'projects';
const form = useForm({
code: record?.code ?? '',
name: record?.name ?? '',
status: record?.status ?? (isProject ? 'draft' : 'planned'),
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) ?? '',
});
const association = useForm({ project_id: '', role: 'complementary' });
return (
<Management
title={`${record ? 'Editar' : 'Criar'} ${isProject ? 'projecto' : 'cluster'}`}
>
<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>
<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>
{!isProject && (
<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>
</>
)}
<button className={button} disabled={form.processing}>
Guardar
</button>
</form>
{!isProject && 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.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(
'project_id',
e.target.value,
)
}
>
<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>
<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}
/>
</>
)}
</Management>
);
}