348 lines
16 KiB
TypeScript
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>
|
|
);
|
|
}
|