525 lines
26 KiB
TypeScript
525 lines
26 KiB
TypeScript
import { Link } from '@inertiajs/react';
|
|
import { useState } from 'react';
|
|
import {
|
|
usePartnershipEdit,
|
|
type PartnershipEditProps,
|
|
} from '@/lib/partnership-edit';
|
|
import PartnershipEditComparison from '@/components/partnership-edit-comparison';
|
|
import {
|
|
Frame,
|
|
Field,
|
|
Errors,
|
|
control,
|
|
button,
|
|
type Catalogues,
|
|
} from './shared';
|
|
type Props = Catalogues & PartnershipEditProps;
|
|
function Editor(p: Props) {
|
|
const edit = usePartnershipEdit(p);
|
|
const form = edit.form;
|
|
const [place, setPlace] = useState('');
|
|
const [role, setRole] = useState('primary');
|
|
const [source, setSource] = useState('Declaração do proponente');
|
|
const [origin, setOrigin] = useState('');
|
|
return (
|
|
<Frame title={p.record ? 'Editar parceria' : 'Preparar parceria'}>
|
|
<p className="mb-5">
|
|
Guarde uma ideia e retome-a no seu painel. Empresa constituída,
|
|
NUIT, documentos e verificação não são exigidos para preparar o
|
|
rascunho. As informações desconhecidas ficam pendentes.
|
|
</p>
|
|
<form
|
|
className="space-y-5"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
edit.save();
|
|
}}
|
|
>
|
|
<Errors errors={form.errors} />
|
|
<PartnershipEditComparison editor={edit} catalogues={p} />
|
|
<fieldset
|
|
className="space-y-5"
|
|
disabled={!edit.editable || edit.invalid}
|
|
>
|
|
<legend className="sr-only">Conteúdo da parceria</legend>
|
|
<Field label="Título">
|
|
<input
|
|
className={control}
|
|
value={form.data.title}
|
|
onChange={(e) =>
|
|
edit.setData('title', e.target.value)
|
|
}
|
|
/>
|
|
</Field>
|
|
<div className="grid gap-5 md:grid-cols-2">
|
|
<Field label="Organização que representa (opcional)">
|
|
<select
|
|
className={control}
|
|
value={form.data.organization_id}
|
|
onChange={(e) =>
|
|
edit.setData(
|
|
'organization_id',
|
|
e.target.value,
|
|
)
|
|
}
|
|
>
|
|
<option value="">
|
|
Sem organização indicada
|
|
</option>
|
|
{form.data.organization_id &&
|
|
!p.organizations.some(
|
|
(organization) =>
|
|
String(organization.id) ===
|
|
form.data.organization_id,
|
|
) && (
|
|
<option
|
|
value={form.data.organization_id}
|
|
>
|
|
Organização anteriormente indicada —
|
|
fora da lista actual
|
|
</option>
|
|
)}
|
|
{p.organizations.map((x) => (
|
|
<option key={x.id} value={x.id}>
|
|
{x.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<p className="text-sm">
|
|
A autoria mantém-se. Sem organização ligada, só
|
|
o proponente originalmente registado pode editar
|
|
esta proposta.
|
|
</p>
|
|
</Field>
|
|
<Field label="Oferta ou procura">
|
|
<select
|
|
className={control}
|
|
value={form.data.kind}
|
|
onChange={(e) =>
|
|
edit.setData('kind', e.target.value)
|
|
}
|
|
>
|
|
<option value="seeking">Procura</option>
|
|
<option value="offering">Oferta</option>
|
|
</select>
|
|
</Field>
|
|
<Field label="Tipo de parceria">
|
|
<select
|
|
className={control}
|
|
value={form.data.partnership_type}
|
|
onChange={(e) =>
|
|
edit.setData(
|
|
'partnership_type',
|
|
e.target.value,
|
|
)
|
|
}
|
|
>
|
|
<option value="">Por definir</option>
|
|
{form.data.partnership_type &&
|
|
!Object.hasOwn(
|
|
p.types,
|
|
form.data.partnership_type,
|
|
) && (
|
|
<option
|
|
value={form.data.partnership_type}
|
|
>
|
|
Tipo anteriormente indicado — fora
|
|
da configuração actual
|
|
</option>
|
|
)}
|
|
{Object.entries(p.types).map(([k, v]) => (
|
|
<option key={k} value={k}>
|
|
{v}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</Field>
|
|
<Field label="Setor">
|
|
<select
|
|
className={control}
|
|
value={form.data.sector_id}
|
|
onChange={(e) =>
|
|
edit.setData('sector_id', e.target.value)
|
|
}
|
|
>
|
|
<option value="">Por definir</option>
|
|
{form.data.sector_id &&
|
|
!p.sectors.some(
|
|
(sector) =>
|
|
String(sector.id) ===
|
|
form.data.sector_id,
|
|
) && (
|
|
<option value={form.data.sector_id}>
|
|
Sector anteriormente indicado — fora
|
|
da lista actual
|
|
</option>
|
|
)}
|
|
{p.sectors.map((x) => (
|
|
<option key={x.id} value={x.id}>
|
|
{x.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</Field>
|
|
</div>
|
|
<Field label="Resumo público">
|
|
<textarea
|
|
className={control}
|
|
value={form.data.payload.summary}
|
|
onChange={(e) =>
|
|
edit.setData('payload', (previous) => ({
|
|
...previous,
|
|
summary: e.target.value,
|
|
}))
|
|
}
|
|
/>
|
|
</Field>
|
|
<Field label="O que oferece">
|
|
<textarea
|
|
className={control}
|
|
value={form.data.offer}
|
|
onChange={(e) =>
|
|
edit.setData('offer', e.target.value)
|
|
}
|
|
/>
|
|
</Field>
|
|
<Field label="O que procura">
|
|
<textarea
|
|
className={control}
|
|
value={form.data.seeking}
|
|
onChange={(e) =>
|
|
edit.setData('seeking', e.target.value)
|
|
}
|
|
/>
|
|
</Field>
|
|
<Field label="Requisitos e critérios desejáveis">
|
|
<textarea
|
|
className={control}
|
|
value={form.data.payload.requirements}
|
|
onChange={(e) =>
|
|
edit.setData('payload', (previous) => ({
|
|
...previous,
|
|
requirements: e.target.value,
|
|
}))
|
|
}
|
|
/>
|
|
</Field>
|
|
<Field label="Disponibilidade dos recursos ou da colaboração (opcional)">
|
|
<textarea
|
|
className={control}
|
|
maxLength={1000}
|
|
value={form.data.payload.availability}
|
|
onChange={(event) =>
|
|
edit.setData('payload', (previous) => ({
|
|
...previous,
|
|
availability: event.target.value,
|
|
}))
|
|
}
|
|
/>
|
|
<p className="text-sm">
|
|
Indique quando e em que condições os recursos ou a
|
|
colaboração estarão disponíveis. O prazo de recepção
|
|
de interesses é indicado separadamente.
|
|
</p>
|
|
</Field>
|
|
<fieldset className="space-y-2 rounded border p-4">
|
|
<legend>
|
|
Critérios obrigatórios expressos da proposta
|
|
</legend>
|
|
<p>
|
|
Marque apenas condições necessárias. Critérios
|
|
desejáveis servem a pontuação. Informação
|
|
desconhecida exige esclarecimento e não constitui
|
|
incumprimento comprovado.
|
|
</p>
|
|
{Object.entries(p.criteria).map(([k, v]) => (
|
|
<label className="block" key={k}>
|
|
<input
|
|
type="checkbox"
|
|
checked={form.data.payload.mandatory.includes(
|
|
k,
|
|
)}
|
|
onChange={(e) =>
|
|
edit.setData('payload', (previous) => ({
|
|
...previous,
|
|
mandatory: e.target.checked
|
|
? [...previous.mandatory, k]
|
|
: previous.mandatory.filter(
|
|
(x) => x !== k,
|
|
),
|
|
}))
|
|
}
|
|
/>{' '}
|
|
{v}
|
|
</label>
|
|
))}
|
|
</fieldset>
|
|
{form.data.payload.mandatory
|
|
.filter((code) => !Object.hasOwn(p.criteria, code))
|
|
.map((code) => (
|
|
<p className="rounded border p-3" key={code}>
|
|
Critério anteriormente indicado — fora da
|
|
configuração actual: {code}. Retirá-lo da
|
|
preparação altera apenas a próxima gravação; o
|
|
histórico mantém-se.{' '}
|
|
<button
|
|
type="button"
|
|
className="underline"
|
|
disabled={!edit.editable || edit.invalid}
|
|
onClick={() =>
|
|
edit.setData('payload', (previous) => ({
|
|
...previous,
|
|
mandatory:
|
|
previous.mandatory.filter(
|
|
(value) => value !== code,
|
|
),
|
|
}))
|
|
}
|
|
>
|
|
Retirar da preparação
|
|
</button>
|
|
</p>
|
|
))}
|
|
<div className="grid gap-4 md:grid-cols-2">
|
|
<Field label="Prazo para receber manifestações de interesse (opcional)">
|
|
<input
|
|
type="date"
|
|
className={control}
|
|
value={form.data.deadline}
|
|
onChange={(e) =>
|
|
edit.setData('deadline', e.target.value)
|
|
}
|
|
/>
|
|
</Field>
|
|
<Field label="Visibilidade do conteúdo detalhado">
|
|
<select
|
|
className={control}
|
|
value={form.data.payload.confidentiality}
|
|
onChange={(e) =>
|
|
edit.setData('payload', (previous) => ({
|
|
...previous,
|
|
confidentiality: e.target.value,
|
|
}))
|
|
}
|
|
>
|
|
<option value="public">
|
|
Público após moderação
|
|
</option>
|
|
<option value="private">
|
|
Resumo público; detalhes privados
|
|
</option>
|
|
</select>
|
|
</Field>
|
|
</div>
|
|
<section className="space-y-4 rounded border p-5">
|
|
<h2 className="font-bold">
|
|
Localizações e abrangência
|
|
</h2>
|
|
<p>
|
|
A localização de uma origem fica como contexto. Só
|
|
acrescente uma localização própria com fonte; não se
|
|
deduz por proximidade ou pertença a cluster.
|
|
</p>
|
|
<div className="grid gap-3 md:grid-cols-3">
|
|
<Field label="Localização do cadastro">
|
|
<select
|
|
className={control}
|
|
value={place}
|
|
onChange={(e) => setPlace(e.target.value)}
|
|
>
|
|
<option value="">
|
|
Escolher território
|
|
</option>
|
|
{p.territories.map((x) => (
|
|
<option key={x.id} value={x.id}>
|
|
{x.name} · {x.location_type}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</Field>
|
|
<Field label="Papel">
|
|
<select
|
|
className={control}
|
|
value={role}
|
|
onChange={(e) => setRole(e.target.value)}
|
|
>
|
|
<option value="primary">Principal</option>
|
|
<option value="operating">
|
|
Área de atuação
|
|
</option>
|
|
<option value="coverage">
|
|
Abrangência
|
|
</option>
|
|
</select>
|
|
</Field>
|
|
<Field label="Fonte da localização">
|
|
<input
|
|
className={control}
|
|
value={source}
|
|
onChange={(e) => setSource(e.target.value)}
|
|
/>
|
|
</Field>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
className={button}
|
|
disabled={!place || !source}
|
|
onClick={() => {
|
|
edit.setData('locations', (previous) =>
|
|
previous.some(
|
|
(x) =>
|
|
x.geo_location_id === place &&
|
|
x.role === role,
|
|
)
|
|
? previous
|
|
: [
|
|
...previous,
|
|
{
|
|
geo_location_id: place,
|
|
role,
|
|
source_reference: source,
|
|
},
|
|
],
|
|
);
|
|
}}
|
|
>
|
|
Acrescentar localização
|
|
</button>
|
|
{form.data.locations.map((l, i) => (
|
|
<p key={i}>
|
|
{
|
|
p.territories.find(
|
|
(t) => t.id === l.geo_location_id,
|
|
)?.name
|
|
}{' '}
|
|
· {l.role} · {l.source_reference}{' '}
|
|
<button
|
|
type="button"
|
|
className="underline"
|
|
onClick={() =>
|
|
edit.setData('locations', (previous) =>
|
|
previous.filter(
|
|
(item) =>
|
|
item.geo_location_id !==
|
|
l.geo_location_id ||
|
|
item.role !== l.role,
|
|
),
|
|
)
|
|
}
|
|
>
|
|
Remover
|
|
</button>
|
|
</p>
|
|
))}
|
|
</section>
|
|
<section className="space-y-3 rounded border p-5">
|
|
<h2 className="font-bold">Registos de origem</h2>
|
|
<Field label="Adicionar origem existente">
|
|
<select
|
|
className={control}
|
|
value={origin}
|
|
onChange={(e) => setOrigin(e.target.value)}
|
|
>
|
|
<option value="">
|
|
Escolher empresa, investimento, projeto,
|
|
cluster, zona ou ficha
|
|
</option>
|
|
{p.sources?.map((x) => (
|
|
<option
|
|
key={x.type + ':' + x.target_id}
|
|
value={x.type + ':' + x.target_id}
|
|
>
|
|
{x.title}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</Field>
|
|
<button
|
|
type="button"
|
|
className={button}
|
|
disabled={!origin}
|
|
onClick={() => {
|
|
const [type, target_id] = origin.split(':');
|
|
edit.setData('origins', (previous) =>
|
|
previous.some(
|
|
(item) =>
|
|
item.type === type &&
|
|
item.target_id === target_id,
|
|
)
|
|
? previous
|
|
: [...previous, { type, target_id }],
|
|
);
|
|
}}
|
|
>
|
|
Ligar origem
|
|
</button>
|
|
{form.data.origins.map((o, i) => (
|
|
<p key={i}>
|
|
{p.sources?.find(
|
|
(x) =>
|
|
x.type === o.type &&
|
|
x.target_id === o.target_id,
|
|
)?.title ?? 'Origem documental ligada'}{' '}
|
|
<button
|
|
type="button"
|
|
className="underline"
|
|
onClick={() =>
|
|
edit.setData('origins', (previous) =>
|
|
previous.filter(
|
|
(item) =>
|
|
item.type !== o.type ||
|
|
item.target_id !==
|
|
o.target_id,
|
|
),
|
|
)
|
|
}
|
|
>
|
|
Remover
|
|
</button>
|
|
</p>
|
|
))}
|
|
</section>
|
|
<label className="block">
|
|
<input
|
|
type="checkbox"
|
|
checked={form.data.payload.consent}
|
|
onChange={(e) =>
|
|
edit.setData('payload', (previous) => ({
|
|
...previous,
|
|
consent: e.target.checked,
|
|
}))
|
|
}
|
|
/>{' '}
|
|
Autorizo submeter o conteúdo indicado para divulgação no
|
|
âmbito escolhido. Não confirma verificação ou
|
|
certificação.
|
|
</label>
|
|
<div className="flex flex-wrap gap-4">
|
|
<button
|
|
className={button}
|
|
disabled={form.processing || edit.blocked}
|
|
>
|
|
{edit.retryingCreation
|
|
? 'Recuperar gravação anterior'
|
|
: edit.current?.status === 'published'
|
|
? 'Guardar revisão em rascunho'
|
|
: 'Guardar rascunho'}
|
|
</button>
|
|
{p.record && (
|
|
<Link
|
|
className="underline"
|
|
href={'/parcerias/' + p.record.id}
|
|
>
|
|
Pré-visualizar e submeter
|
|
</Link>
|
|
)}
|
|
</div>
|
|
</fieldset>
|
|
</form>
|
|
</Frame>
|
|
);
|
|
}
|
|
export default function Page(p: Props) {
|
|
return <Editor key={p.editorActorId ?? 'legacy-editor'} {...p} />;
|
|
}
|