Files
Investidor-V2/resources/js/pages/management/editor.tsx
T
nala cf003c8623
tests / ci (push) Canceled after 0s
versao nrgocios 4
2026-10-18 15:57:06 +02:00

348 lines
16 KiB
TypeScript

import { Link, useForm } from '@inertiajs/react';
import InvestmentCoordinates from '@/components/investment-coordinates';
import {
TerritoryLevel,
type TerritoryOption,
} from '@/components/investment-territory';
import { useState } from 'react';
import { Management, Field, button, control, states } from './shared';
type Values = Record<string, string | boolean>;
type Entry = Record<string, string | number | boolean | null> & { id: number };
const opportunityFields = {
reference: 'Referência',
title: 'Título',
slug: 'Slug (endereço)',
summary: 'Resumo',
description: 'Descrição',
promoter_name: 'Promotor',
promoter_type: 'Tipo de promotor',
estimated_investment_amount: 'Investimento estimado',
estimated_investment_currency: 'Moeda (código de três letras)',
financing_modality: 'Modalidade de financiamento',
expected_impact: 'Impacto esperado',
};
const sectorFields = {
name: 'Nome',
slug: 'Slug (endereço)',
short_description: 'Descrição curta',
description: 'Descrição',
sort_order: 'Ordem de apresentação',
};
const maturities = [
'Ideia identificada',
'Pré-estudo disponível',
'Estudo de viabilidade em curso',
'Projecto estruturado',
'Pronto para investimento',
'Em negociação ou implementação',
];
export default function Editor({
resource,
record,
sectors,
territoryPath = [],
}: {
resource: 'sectors' | 'opportunities';
record: Entry | null;
sectors: { id: number; name: string }[];
territoryPath?: TerritoryOption[];
}) {
const isSector = resource === 'sectors';
const fields = isSector ? sectorFields : opportunityFields;
const initial: Values = Object.fromEntries(
Object.keys(fields).map((key) => [
key,
String(
record?.[key] ??
(key === 'estimated_investment_currency'
? 'USD'
: key === 'sort_order'
? '0'
: ''),
),
]),
);
if (isSector) {
initial.is_active = Boolean(record?.is_active ?? true);
initial.editorial_published = Boolean(record?.published_at);
} else {
initial.sector_id = String(record?.sector_id ?? '');
initial.maturity_level = String(record?.maturity_level ?? '');
initial.status = String(record?.status ?? 'draft');
initial.is_featured = Boolean(record?.is_featured ?? false);
initial.geography_scope = String(
record?.geography_scope ?? 'unspecified',
);
initial.territory_version = String(record?.territory_version ?? 0);
['province_id', 'district_id', 'admin_post_id', 'locality_id'].forEach(
(field, index) => {
initial[field] = territoryPath[index]?.id ?? '';
},
);
}
const form = useForm<Values>(initial);
const [creationPath, setCreationPath] =
useState<TerritoryOption[]>(territoryPath);
return (
<Management
title={`${record ? 'Editar' : 'Criar'} ${isSector ? 'sector' : 'oportunidade'}`}
>
{!isSector && record && (
<Link
className="underline"
href={`/management/opportunities/${record.id}/context`}
>
Gerir clusters, corredores, projectos, educação e
necessidades
</Link>
)}
<form
className="max-w-3xl space-y-5"
onSubmit={(event) => {
event.preventDefault();
if (record)
form.put(`/management/${resource}/${record.id}`);
else form.post(`/management/${resource}`);
}}
>
<fieldset disabled={form.processing} className="space-y-5">
{!isSector && (
<div className="grid gap-4 md:grid-cols-2">
{(
[
'province_id',
'district_id',
'admin_post_id',
'locality_id',
] as const
).map((key, index) => (
<TerritoryLevel
key={`${key}-${creationPath[index - 1]?.id ?? ''}`}
index={index}
parent={creationPath[index - 1]?.id ?? ''}
selected={creationPath[index]}
endpoint="/management/territory-options"
fieldError={form.errors[key]}
onChange={(value) => {
const path = [
...creationPath.slice(0, index),
...(value ? [value] : []),
];
setCreationPath(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 ?? '',
}));
}}
/>
))}
</div>
)}
{isSector && (
<Field
label="Publicar no catálogo"
error={form.errors.editorial_published}
>
<label className="flex gap-2">
<input
type="checkbox"
checked={Boolean(
form.data.editorial_published,
)}
onChange={(e) =>
form.setData(
'editorial_published',
e.target.checked,
)
}
/>
Publicado na navegação e nos filtros públicos
quando também estiver activo
</label>
<p>
Desmarque para retirar do catálogo, conservando
o cadastro e as relações.
</p>
</Field>
)}
{Object.entries(fields).map(([key, label]) => (
<Field key={key} label={label} error={form.errors[key]}>
{[
'description',
'summary',
'short_description',
'expected_impact',
].includes(key) ? (
<textarea
className={control}
rows={key === 'description' ? 6 : 3}
value={String(form.data[key])}
onChange={(e) =>
form.setData(key, e.target.value)
}
/>
) : (
<input
className={control}
value={String(form.data[key])}
onChange={(e) =>
form.setData(key, e.target.value)
}
/>
)}
</Field>
))}
{!isSector && (
<>
<Field label="Sector" error={form.errors.sector_id}>
<select
className={control}
value={String(form.data.sector_id)}
onChange={(e) =>
form.setData(
'sector_id',
e.target.value,
)
}
>
<option value="">Seleccione</option>
{sectors.map((sector) => (
<option
key={sector.id}
value={sector.id}
>
{sector.name}
</option>
))}
</select>
</Field>
<Field
label="Maturidade"
error={form.errors.maturity_level}
>
<select
className={control}
value={String(form.data.maturity_level)}
onChange={(e) =>
form.setData(
'maturity_level',
e.target.value,
)
}
>
<option value="">Por indicar</option>
{maturities.map((label, index) => (
<option key={label} value={index + 1}>
{label}
</option>
))}
</select>
</Field>
<Field
label="Âmbito geográfico"
error={form.errors.geography_scope}
>
<select
className={control}
value={String(form.data.geography_scope)}
onChange={(e) => {
form.setData(
'geography_scope',
e.target.value,
);
if (
e.target.value === 'national' ||
e.target.value === 'digital'
) {
setCreationPath([]);
form.setData((data) => ({
...data,
geography_scope: e.target.value,
province_id: '',
district_id: '',
admin_post_id: '',
locality_id: '',
}));
}
}}
>
<option value="unspecified">
Não especificado
</option>
<option value="specific">
Localização específica
</option>
<option value="multiple">
Várias localizações
</option>
<option value="national">
Âmbito nacional
</option>
<option value="digital">
Digital/sem instalação definida
</option>
</select>
</Field>
<Field label="Estado" error={form.errors.status}>
<select
className={control}
value={String(form.data.status)}
onChange={(e) =>
form.setData('status', e.target.value)
}
>
{['draft', 'published', 'archived'].map(
(key) => (
<option key={key} value={key}>
{states[key]}
</option>
),
)}
</select>
</Field>
</>
)}
<Field
label={isSector ? 'Activo' : 'Em destaque'}
error={
form.errors[isSector ? 'is_active' : 'is_featured']
}
>
<input
type="checkbox"
checked={Boolean(
form.data[
isSector ? 'is_active' : 'is_featured'
],
)}
onChange={(e) =>
form.setData(
isSector ? 'is_active' : 'is_featured',
e.target.checked,
)
}
/>
</Field>
<div className="flex gap-4">
<button className={button} disabled={form.processing}>
{form.processing ? 'A guardar…' : 'Guardar'}
</button>
<Link
className="p-2 underline"
href={`/management/${resource}`}
>
Cancelar
</Link>
</div>
</fieldset>
</form>
{!isSector && record && <InvestmentCoordinates id={record.id} />}
</Management>
);
}