449 lines
19 KiB
TypeScript
449 lines
19 KiB
TypeScript
import { createOperationKey } from '@/lib/operation-key';
|
|
import { Link, useForm } from '@inertiajs/react';
|
|
import { useState } from 'react';
|
|
import {
|
|
Frame,
|
|
Field,
|
|
Errors,
|
|
control,
|
|
button,
|
|
type Partnership,
|
|
type Catalogues,
|
|
} from './shared';
|
|
type Location = {
|
|
geo_location_id: string;
|
|
role: string;
|
|
source_reference: string;
|
|
};
|
|
type Origin = { type: string; target_id: string };
|
|
type Props = Catalogues & {
|
|
record: Partnership | null;
|
|
locations: Location[];
|
|
origins: Origin[];
|
|
};
|
|
function Editor(p: Props) {
|
|
const form = useForm({
|
|
client_key: createOperationKey(),
|
|
version: p.record?.version ?? null,
|
|
title: p.record?.title ?? '',
|
|
offer: p.record?.offer ?? '',
|
|
seeking: p.record?.seeking ?? '',
|
|
organization_id: p.record?.organization_id ?? '',
|
|
sector_id: p.record?.sector_id?.toString() ?? '',
|
|
kind: p.record?.kind ?? 'seeking',
|
|
partnership_type: p.record?.partnership_type ?? '',
|
|
deadline: p.record?.deadline ?? '',
|
|
payload: {
|
|
summary: p.record?.payload.summary ?? '',
|
|
requirements: p.record?.payload.requirements ?? '',
|
|
mandatory: p.record?.payload.mandatory ?? ([] as string[]),
|
|
confidentiality: p.record?.payload.confidentiality ?? 'public',
|
|
consent: p.record?.payload.consent ?? false,
|
|
},
|
|
locations: p.locations.map((x) => ({
|
|
geo_location_id: x.geo_location_id,
|
|
role: x.role,
|
|
source_reference: x.source_reference,
|
|
})),
|
|
origins: p.origins.map((x) => ({
|
|
type: x.type,
|
|
target_id: x.target_id,
|
|
})),
|
|
});
|
|
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();
|
|
if (p.record) form.put('/parcerias/' + p.record.id);
|
|
else form.post('/parcerias/rascunhos');
|
|
}}
|
|
>
|
|
<Errors errors={form.errors} />
|
|
<Field label="Título">
|
|
<input
|
|
className={control}
|
|
value={form.data.title}
|
|
onChange={(e) => form.setData('title', e.target.value)}
|
|
/>
|
|
</Field>
|
|
<div className="grid gap-5 md:grid-cols-2">
|
|
<Field label="Participação">
|
|
<select
|
|
className={control}
|
|
value={form.data.organization_id}
|
|
onChange={(e) =>
|
|
form.setData('organization_id', e.target.value)
|
|
}
|
|
>
|
|
<option value="">
|
|
Ideia pessoal — sem organização indicada
|
|
</option>
|
|
{p.organizations.map((x) => (
|
|
<option key={x.id} value={x.id}>
|
|
{x.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</Field>
|
|
<Field label="Oferta ou procura">
|
|
<select
|
|
className={control}
|
|
value={form.data.kind}
|
|
onChange={(e) =>
|
|
form.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) =>
|
|
form.setData('partnership_type', e.target.value)
|
|
}
|
|
>
|
|
<option value="">Por definir</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) =>
|
|
form.setData('sector_id', e.target.value)
|
|
}
|
|
>
|
|
<option value="">Por definir</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) =>
|
|
form.setData('payload', {
|
|
...form.data.payload,
|
|
summary: e.target.value,
|
|
})
|
|
}
|
|
/>
|
|
</Field>
|
|
<Field label="O que oferece">
|
|
<textarea
|
|
className={control}
|
|
value={form.data.offer}
|
|
onChange={(e) => form.setData('offer', e.target.value)}
|
|
/>
|
|
</Field>
|
|
<Field label="O que procura">
|
|
<textarea
|
|
className={control}
|
|
value={form.data.seeking}
|
|
onChange={(e) =>
|
|
form.setData('seeking', e.target.value)
|
|
}
|
|
/>
|
|
</Field>
|
|
<Field label="Requisitos e critérios desejáveis">
|
|
<textarea
|
|
className={control}
|
|
value={form.data.payload.requirements}
|
|
onChange={(e) =>
|
|
form.setData('payload', {
|
|
...form.data.payload,
|
|
requirements: e.target.value,
|
|
})
|
|
}
|
|
/>
|
|
</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) =>
|
|
form.setData('payload', {
|
|
...form.data.payload,
|
|
mandatory: e.target.checked
|
|
? [
|
|
...form.data.payload
|
|
.mandatory,
|
|
k,
|
|
]
|
|
: form.data.payload.mandatory.filter(
|
|
(x) => x !== k,
|
|
),
|
|
})
|
|
}
|
|
/>{' '}
|
|
{v}
|
|
</label>
|
|
))}
|
|
</fieldset>
|
|
<div className="grid gap-4 md:grid-cols-2">
|
|
<Field label="Prazo para interesses">
|
|
<input
|
|
type="date"
|
|
className={control}
|
|
value={form.data.deadline}
|
|
onChange={(e) =>
|
|
form.setData('deadline', e.target.value)
|
|
}
|
|
/>
|
|
</Field>
|
|
<Field label="Visibilidade do conteúdo detalhado">
|
|
<select
|
|
className={control}
|
|
value={form.data.payload.confidentiality}
|
|
onChange={(e) =>
|
|
form.setData('payload', {
|
|
...form.data.payload,
|
|
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={() => {
|
|
if (
|
|
!form.data.locations.some(
|
|
(x) =>
|
|
x.geo_location_id === place &&
|
|
x.role === role,
|
|
)
|
|
)
|
|
form.setData('locations', [
|
|
...form.data.locations,
|
|
{
|
|
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={() =>
|
|
form.setData(
|
|
'locations',
|
|
form.data.locations.filter(
|
|
(_, j) => j !== i,
|
|
),
|
|
)
|
|
}
|
|
>
|
|
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(':');
|
|
if (
|
|
!form.data.origins.some(
|
|
(x) =>
|
|
x.type === type &&
|
|
x.target_id === target_id,
|
|
)
|
|
)
|
|
form.setData('origins', [
|
|
...form.data.origins,
|
|
{ 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={() =>
|
|
form.setData(
|
|
'origins',
|
|
form.data.origins.filter(
|
|
(_, j) => j !== i,
|
|
),
|
|
)
|
|
}
|
|
>
|
|
Remover
|
|
</button>
|
|
</p>
|
|
))}
|
|
</section>
|
|
<label className="block">
|
|
<input
|
|
type="checkbox"
|
|
checked={form.data.payload.consent}
|
|
onChange={(e) =>
|
|
form.setData('payload', {
|
|
...form.data.payload,
|
|
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}>
|
|
Guardar rascunho
|
|
</button>
|
|
{p.record && (
|
|
<Link
|
|
className="underline"
|
|
href={'/parcerias/' + p.record.id}
|
|
>
|
|
Pré-visualizar e submeter
|
|
</Link>
|
|
)}
|
|
</div>
|
|
</form>
|
|
</Frame>
|
|
);
|
|
}
|
|
export default function Page(p: Props) {
|
|
return (
|
|
<Editor
|
|
key={`${p.record?.id ?? 'new'}:${p.record?.version ?? 0}`}
|
|
{...p}
|
|
/>
|
|
);
|
|
}
|