944 lines
40 KiB
TypeScript
944 lines
40 KiB
TypeScript
import { Link, router, useForm } from '@inertiajs/react';
|
|
import { useState } from 'react';
|
|
import type { ReactNode } from 'react';
|
|
import { portalRequest, PortalApiError } from '@/lib/portal-api';
|
|
import { Frame, card, button, input } from '@/pages/investment/shared';
|
|
import type { Criterion } from './shared';
|
|
|
|
type Choice = { id: string | number; name?: string; title?: string };
|
|
type WindowDraft = {
|
|
code: string;
|
|
title: string;
|
|
mode: string;
|
|
start_on: string;
|
|
end_on: string;
|
|
coverage: string;
|
|
coverage_text: string;
|
|
territory_ids: string[];
|
|
source_url: string;
|
|
source_reference: string;
|
|
checked_on: string;
|
|
application_url: string;
|
|
criteria: Criterion[];
|
|
};
|
|
type Draft = {
|
|
title: string;
|
|
kind: string;
|
|
summary: string;
|
|
manager_name: string;
|
|
manager_organization_id: string;
|
|
source_url: string;
|
|
source_reference: string;
|
|
source_checked_on: string;
|
|
source_published_on: string;
|
|
source_status: string;
|
|
coverage: string;
|
|
coverage_text: string;
|
|
territory_ids: string[];
|
|
sectors_scope: string;
|
|
sectors_text: string;
|
|
sector_ids: number[];
|
|
resource_ids: number[];
|
|
criteria: Criterion[];
|
|
windows: WindowDraft[];
|
|
};
|
|
type RecordData = {
|
|
id: string;
|
|
code: string;
|
|
version: number;
|
|
draft: Draft;
|
|
published: boolean;
|
|
};
|
|
type Plan = { version: number; hash: string; payload: Draft };
|
|
const empty: Draft = {
|
|
title: '',
|
|
kind: 'fund',
|
|
summary: '',
|
|
manager_name: '',
|
|
manager_organization_id: '',
|
|
source_url: '',
|
|
source_reference: '',
|
|
source_checked_on: '',
|
|
source_published_on: '',
|
|
source_status: 'unknown',
|
|
coverage: 'unknown',
|
|
coverage_text: '',
|
|
territory_ids: [],
|
|
sectors_scope: 'unknown',
|
|
sectors_text: '',
|
|
sector_ids: [],
|
|
resource_ids: [],
|
|
criteria: [],
|
|
windows: [],
|
|
};
|
|
const newCode = () => crypto.randomUUID();
|
|
function Field({ label, children }: { label: string; children: ReactNode }) {
|
|
return (
|
|
<label className="my-3 block">
|
|
<span className="block font-medium">{label}</span>
|
|
{children}
|
|
</label>
|
|
);
|
|
}
|
|
function Multi({
|
|
label,
|
|
options,
|
|
values,
|
|
onChange,
|
|
}: {
|
|
label: string;
|
|
options: Choice[];
|
|
values: (string | number)[];
|
|
onChange: (ids: string[]) => void;
|
|
}) {
|
|
return (
|
|
<Field label={label}>
|
|
<select
|
|
className={input}
|
|
multiple
|
|
value={values.map(String)}
|
|
onChange={(e) =>
|
|
onChange(
|
|
Array.from(
|
|
e.target.selectedOptions,
|
|
(option) => option.value,
|
|
),
|
|
)
|
|
}
|
|
>
|
|
{options.map((option) => (
|
|
<option key={option.id} value={option.id}>
|
|
{option.name ?? option.title}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<span className="text-sm">Pode seleccionar mais de uma opção.</span>
|
|
</Field>
|
|
);
|
|
}
|
|
function Criteria({
|
|
value,
|
|
onChange,
|
|
}: {
|
|
value: Criterion[];
|
|
onChange: (criteria: Criterion[]) => void;
|
|
}) {
|
|
const update = (index: number, patch: Partial<Criterion>) =>
|
|
onChange(
|
|
value.map((item, i) =>
|
|
i === index ? { ...item, ...patch } : item,
|
|
),
|
|
);
|
|
return (
|
|
<section>
|
|
<h3 className="font-semibold">Critérios documentados</h3>
|
|
{value.map((item, index) => (
|
|
<fieldset className={card} key={item.code}>
|
|
<legend>Critério {index + 1}</legend>
|
|
<Field label="Pergunta">
|
|
<input
|
|
className={input}
|
|
value={item.question ?? ''}
|
|
maxLength={500}
|
|
onChange={(e) =>
|
|
update(index, { question: e.target.value })
|
|
}
|
|
/>
|
|
</Field>
|
|
<Field label="Aplica-se a">
|
|
<select
|
|
className={input}
|
|
value={item.applies_to}
|
|
onChange={(e) =>
|
|
update(index, {
|
|
applies_to: e.target
|
|
.value as Criterion['applies_to'],
|
|
})
|
|
}
|
|
>
|
|
<option value="all">Todos os candidatos</option>
|
|
<option value="individual">
|
|
Pessoas singulares
|
|
</option>
|
|
<option value="entity">Entidades</option>
|
|
</select>
|
|
</Field>
|
|
<Field label="Resposta exigida pela fonte">
|
|
<select
|
|
className={input}
|
|
value={item.expected}
|
|
onChange={(e) =>
|
|
update(index, {
|
|
expected: e.target.value as 'yes' | 'no',
|
|
})
|
|
}
|
|
>
|
|
<option value="yes">Sim</option>
|
|
<option value="no">Não</option>
|
|
</select>
|
|
</Field>
|
|
<Field label="Explicação da condição">
|
|
<textarea
|
|
className={input}
|
|
value={item.explanation ?? ''}
|
|
maxLength={2000}
|
|
onChange={(e) =>
|
|
update(index, { explanation: e.target.value })
|
|
}
|
|
/>
|
|
</Field>
|
|
<Field label="Referência exacta na fonte">
|
|
<input
|
|
className={input}
|
|
value={item.source_reference ?? ''}
|
|
maxLength={1500}
|
|
onChange={(e) =>
|
|
update(index, {
|
|
source_reference: e.target.value,
|
|
})
|
|
}
|
|
/>
|
|
</Field>
|
|
<button
|
|
type="button"
|
|
className="underline"
|
|
onClick={() =>
|
|
onChange(value.filter((_, i) => i !== index))
|
|
}
|
|
>
|
|
Remover critério
|
|
</button>
|
|
</fieldset>
|
|
))}
|
|
<button
|
|
type="button"
|
|
className="underline"
|
|
disabled={value.length >= 30}
|
|
onClick={() =>
|
|
onChange([
|
|
...value,
|
|
{
|
|
code: newCode(),
|
|
question: '',
|
|
applies_to: 'all',
|
|
expected: 'yes',
|
|
source_reference: '',
|
|
explanation: '',
|
|
},
|
|
])
|
|
}
|
|
>
|
|
Acrescentar critério
|
|
</button>
|
|
</section>
|
|
);
|
|
}
|
|
function Editor({
|
|
record,
|
|
references,
|
|
kinds,
|
|
windowModes,
|
|
history,
|
|
}: {
|
|
record: RecordData | null;
|
|
references: Record<
|
|
'organizations' | 'sectors' | 'territories' | 'documents',
|
|
Choice[]
|
|
>;
|
|
kinds: Record<string, string>;
|
|
windowModes: Record<string, string>;
|
|
history: { version: number; reason: string; published_at: string }[];
|
|
}) {
|
|
const form = useForm({
|
|
version: record?.version ?? 0,
|
|
code: record?.code ?? newCode(),
|
|
reason: '',
|
|
draft: { ...empty, ...record?.draft },
|
|
});
|
|
const [plan, setPlan] = useState<Plan | null>(null);
|
|
const [error, setError] = useState('');
|
|
const [busy, setBusy] = useState(false);
|
|
const [confirmed, setConfirmed] = useState(false);
|
|
const draft = form.data.draft;
|
|
function change(patch: Partial<Draft>) {
|
|
form.setData('draft', { ...draft, ...patch });
|
|
setPlan(null);
|
|
setConfirmed(false);
|
|
}
|
|
function text(
|
|
key: {
|
|
[K in keyof Draft]: Draft[K] extends string ? K : never;
|
|
}[keyof Draft],
|
|
label: string,
|
|
type = 'text',
|
|
) {
|
|
return (
|
|
<Field label={label}>
|
|
<input
|
|
type={type}
|
|
className={input}
|
|
value={draft[key] ?? ''}
|
|
onChange={(e) => change({ [key]: e.target.value })}
|
|
/>
|
|
</Field>
|
|
);
|
|
}
|
|
function windowChange(index: number, patch: Partial<WindowDraft>) {
|
|
change({
|
|
windows: draft.windows.map((item, i) =>
|
|
i === index ? { ...item, ...patch } : item,
|
|
),
|
|
});
|
|
}
|
|
async function preview() {
|
|
if (
|
|
!record ||
|
|
JSON.stringify(draft) !==
|
|
JSON.stringify({ ...empty, ...record?.draft })
|
|
) {
|
|
setError('Guarde o rascunho antes de conferir a prévia.');
|
|
return;
|
|
}
|
|
setBusy(true);
|
|
setError('');
|
|
setPlan(null);
|
|
setConfirmed(false);
|
|
try {
|
|
setPlan(
|
|
await portalRequest<Plan>(
|
|
`/management/fundos/${record.id}/previa`,
|
|
{ method: 'POST' },
|
|
),
|
|
);
|
|
} catch (failure) {
|
|
setError(
|
|
failure instanceof PortalApiError
|
|
? Object.values(failure.errors).flat().join(' ') ||
|
|
failure.message
|
|
: 'Não foi possível preparar a prévia.',
|
|
);
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
}
|
|
return (
|
|
<Frame
|
|
title={
|
|
record ? 'Editar fundo ou programa' : 'Novo fundo ou programa'
|
|
}
|
|
>
|
|
<Link className="underline" href="/management/fundos">
|
|
Voltar à gestão
|
|
</Link>
|
|
<p>
|
|
Guarde as condições efectivamente documentadas. Uma chamada
|
|
local ou encerrada não altera, por si, a cobertura ou existência
|
|
do programa.
|
|
</p>
|
|
<form
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
const url =
|
|
'/management/fundos' + (record ? `/${record.id}` : '');
|
|
if (record) form.put(url);
|
|
else form.post(url);
|
|
}}
|
|
>
|
|
<section className={card}>
|
|
{text('title', 'Nome do programa')}
|
|
<Field label="Tipo">
|
|
<select
|
|
className={input}
|
|
value={draft.kind}
|
|
onChange={(e) => change({ kind: e.target.value })}
|
|
>
|
|
{Object.entries(kinds).map(([key, label]) => (
|
|
<option key={key} value={key}>
|
|
{label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</Field>
|
|
<Field label="Resumo">
|
|
<textarea
|
|
className={input}
|
|
value={draft.summary ?? ''}
|
|
maxLength={4000}
|
|
onChange={(e) =>
|
|
change({ summary: e.target.value })
|
|
}
|
|
/>
|
|
</Field>
|
|
<Field label="Entidade gestora do cadastro">
|
|
<select
|
|
className={input}
|
|
value={draft.manager_organization_id ?? ''}
|
|
onChange={(e) =>
|
|
change({
|
|
manager_organization_id: e.target.value,
|
|
})
|
|
}
|
|
>
|
|
<option value="">Identificar pela fonte</option>
|
|
{references.organizations.map((item) => (
|
|
<option key={item.id} value={item.id}>
|
|
{item.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</Field>
|
|
{!draft.manager_organization_id &&
|
|
text('manager_name', 'Gestor indicado pela fonte')}
|
|
{text('source_url', 'Endereço da fonte', 'url')}
|
|
{text('source_reference', 'Documento, edição e referência')}
|
|
{text(
|
|
'source_checked_on',
|
|
'Data de consulta da fonte',
|
|
'date',
|
|
)}
|
|
{text(
|
|
'source_published_on',
|
|
'Data de publicação da fonte, se conhecida',
|
|
'date',
|
|
)}
|
|
<Field label="Situação da fonte">
|
|
<select
|
|
className={input}
|
|
value={draft.source_status}
|
|
onChange={(e) =>
|
|
change({ source_status: e.target.value })
|
|
}
|
|
>
|
|
<option value="unknown">Por identificar</option>
|
|
<option value="identified">
|
|
Fonte efectiva identificada
|
|
</option>
|
|
<option value="example">
|
|
Conteúdo explicitamente demonstrativo
|
|
</option>
|
|
</select>
|
|
</Field>
|
|
</section>
|
|
<section className={card}>
|
|
<Field label="Cobertura do programa">
|
|
<select
|
|
className={input}
|
|
value={draft.coverage}
|
|
onChange={(e) =>
|
|
change({
|
|
coverage: e.target.value,
|
|
territory_ids: [],
|
|
})
|
|
}
|
|
>
|
|
<option value="unknown">Por determinar</option>
|
|
<option value="national">Nacional</option>
|
|
<option value="territorial">Territorial</option>
|
|
</select>
|
|
</Field>
|
|
{draft.coverage === 'territorial' && (
|
|
<Multi
|
|
label="Locais abrangidos"
|
|
options={references.territories}
|
|
values={draft.territory_ids}
|
|
onChange={(territory_ids) =>
|
|
change({ territory_ids })
|
|
}
|
|
/>
|
|
)}
|
|
{text('coverage_text', 'Explicação da cobertura')}
|
|
<Field label="Sectores abrangidos">
|
|
<select
|
|
className={input}
|
|
value={draft.sectors_scope}
|
|
onChange={(e) =>
|
|
change({
|
|
sectors_scope: e.target.value,
|
|
sector_ids: [],
|
|
})
|
|
}
|
|
>
|
|
<option value="unknown">Por determinar</option>
|
|
<option value="all">Todos, segundo a fonte</option>
|
|
<option value="selected">
|
|
Sectores documentados
|
|
</option>
|
|
</select>
|
|
</Field>
|
|
{draft.sectors_scope === 'selected' && (
|
|
<Multi
|
|
label="Seleccionar sectores"
|
|
options={references.sectors}
|
|
values={draft.sector_ids}
|
|
onChange={(ids) =>
|
|
change({ sector_ids: ids.map(Number) })
|
|
}
|
|
/>
|
|
)}
|
|
{text('sectors_text', 'Explicação do âmbito sectorial')}
|
|
<Multi
|
|
label="Documentos da biblioteca"
|
|
options={references.documents}
|
|
values={draft.resource_ids}
|
|
onChange={(ids) =>
|
|
change({ resource_ids: ids.map(Number) })
|
|
}
|
|
/>
|
|
<Criteria
|
|
value={draft.criteria}
|
|
onChange={(criteria) => change({ criteria })}
|
|
/>
|
|
</section>
|
|
<section className={card}>
|
|
<h2 className="text-xl font-semibold">
|
|
Janelas de candidatura
|
|
</h2>
|
|
{draft.windows.map((window, index) => (
|
|
<fieldset key={window.code} className={card}>
|
|
<legend>Janela {index + 1}</legend>
|
|
{(
|
|
[
|
|
'title',
|
|
'source_url',
|
|
'source_reference',
|
|
'checked_on',
|
|
'application_url',
|
|
'coverage_text',
|
|
] as const
|
|
).map((key) => (
|
|
<Field
|
|
key={key}
|
|
label={
|
|
{
|
|
title: 'Nome da chamada',
|
|
source_url: 'Endereço da fonte',
|
|
source_reference:
|
|
'Referência na fonte',
|
|
checked_on: 'Data de consulta',
|
|
application_url:
|
|
'Canal de candidatura, se documentado',
|
|
coverage_text:
|
|
'Explicação da cobertura desta chamada',
|
|
}[key]
|
|
}
|
|
>
|
|
<input
|
|
className={input}
|
|
type={
|
|
key === 'checked_on'
|
|
? 'date'
|
|
: key.endsWith('url')
|
|
? 'url'
|
|
: 'text'
|
|
}
|
|
value={window[key] ?? ''}
|
|
onChange={(e) =>
|
|
windowChange(index, {
|
|
[key]: e.target.value,
|
|
})
|
|
}
|
|
/>
|
|
</Field>
|
|
))}
|
|
<Field label="Estado documentado">
|
|
<select
|
|
className={input}
|
|
value={window.mode}
|
|
onChange={(e) =>
|
|
windowChange(index, {
|
|
mode: e.target.value,
|
|
...(['rolling', 'unknown'].includes(
|
|
e.target.value,
|
|
)
|
|
? { start_on: '', end_on: '' }
|
|
: {}),
|
|
})
|
|
}
|
|
>
|
|
{Object.entries(windowModes).map(
|
|
([key, label]) => (
|
|
<option key={key} value={key}>
|
|
{label}
|
|
</option>
|
|
),
|
|
)}
|
|
</select>
|
|
</Field>
|
|
{['dated', 'closed'].includes(window.mode) && (
|
|
<>
|
|
{(['start_on', 'end_on'] as const).map(
|
|
(key) => (
|
|
<Field
|
|
key={key}
|
|
label={
|
|
key === 'start_on'
|
|
? 'Data de abertura'
|
|
: 'Data-limite'
|
|
}
|
|
>
|
|
<input
|
|
className={input}
|
|
type="date"
|
|
value={window[key] ?? ''}
|
|
onChange={(e) =>
|
|
windowChange(index, {
|
|
[key]: e.target
|
|
.value,
|
|
})
|
|
}
|
|
/>
|
|
</Field>
|
|
),
|
|
)}
|
|
</>
|
|
)}
|
|
<Field label="Cobertura da chamada">
|
|
<select
|
|
className={input}
|
|
value={window.coverage}
|
|
onChange={(e) =>
|
|
windowChange(index, {
|
|
coverage: e.target.value,
|
|
territory_ids: [],
|
|
})
|
|
}
|
|
>
|
|
<option value="programme">
|
|
A mesma do programa
|
|
</option>
|
|
<option value="unknown">
|
|
Por determinar
|
|
</option>
|
|
<option value="national">Nacional</option>
|
|
<option value="territorial">
|
|
Territorial
|
|
</option>
|
|
</select>
|
|
</Field>
|
|
{window.coverage === 'territorial' && (
|
|
<Multi
|
|
label="Locais da chamada"
|
|
options={references.territories}
|
|
values={window.territory_ids ?? []}
|
|
onChange={(territory_ids) =>
|
|
windowChange(index, { territory_ids })
|
|
}
|
|
/>
|
|
)}
|
|
<Criteria
|
|
value={window.criteria ?? []}
|
|
onChange={(criteria) =>
|
|
windowChange(index, { criteria })
|
|
}
|
|
/>
|
|
<button
|
|
className="underline"
|
|
type="button"
|
|
onClick={() =>
|
|
change({
|
|
windows: draft.windows.filter(
|
|
(_, i) => i !== index,
|
|
),
|
|
})
|
|
}
|
|
>
|
|
Remover janela
|
|
</button>
|
|
</fieldset>
|
|
))}
|
|
<button
|
|
className="underline"
|
|
type="button"
|
|
disabled={draft.windows.length >= 20}
|
|
onClick={() =>
|
|
change({
|
|
windows: [
|
|
...draft.windows,
|
|
{
|
|
code: newCode(),
|
|
title: '',
|
|
mode: 'unknown',
|
|
start_on: '',
|
|
end_on: '',
|
|
coverage: 'programme',
|
|
coverage_text: '',
|
|
territory_ids: [],
|
|
source_url: '',
|
|
source_reference: '',
|
|
checked_on: '',
|
|
application_url: '',
|
|
criteria: [],
|
|
},
|
|
],
|
|
})
|
|
}
|
|
>
|
|
Acrescentar janela
|
|
</button>
|
|
</section>
|
|
<Field label="Motivo da alteração">
|
|
<textarea
|
|
className={input}
|
|
value={form.data.reason}
|
|
minLength={20}
|
|
maxLength={2000}
|
|
onChange={(e) => form.setData('reason', e.target.value)}
|
|
required
|
|
/>
|
|
</Field>
|
|
{Object.values(form.errors).map((message, index) => (
|
|
<p role="alert" key={index}>
|
|
{message}
|
|
</p>
|
|
))}
|
|
<button className={button} disabled={form.processing || busy}>
|
|
Guardar rascunho
|
|
</button>
|
|
</form>
|
|
{record && (
|
|
<section className={card}>
|
|
<h2>Publicação</h2>
|
|
<p>
|
|
A publicação cria uma versão pública; não altera
|
|
interesses já guardados, não abre candidaturas nem
|
|
activa cálculos fiscais.
|
|
</p>
|
|
<button
|
|
className={button}
|
|
disabled={busy || form.processing}
|
|
onClick={() => void preview()}
|
|
>
|
|
Conferir prévia de publicação
|
|
</button>
|
|
{error && <p role="alert">{error}</p>}
|
|
{plan && (
|
|
<div>
|
|
<h3 className="font-semibold">
|
|
{plan.payload.title}
|
|
</h3>
|
|
<p>{plan.payload.summary}</p>
|
|
<p>
|
|
Gestor: {plan.payload.manager_name}. Fonte:{' '}
|
|
{plan.payload.source_reference}.
|
|
</p>
|
|
<p>
|
|
{plan.payload.criteria.length} critérios gerais;{' '}
|
|
{plan.payload.windows.length} janelas.
|
|
</p>
|
|
<p>
|
|
<a
|
|
className="underline"
|
|
href={plan.payload.source_url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
Abrir fonte conferida
|
|
</a>{' '}
|
|
— consulta em {plan.payload.source_checked_on}.
|
|
</p>
|
|
<p>
|
|
Cobertura: {plan.payload.coverage};{' '}
|
|
{plan.payload.coverage_text}
|
|
</p>
|
|
<p>
|
|
{references.territories
|
|
.filter((item) =>
|
|
plan.payload.territory_ids.includes(
|
|
String(item.id),
|
|
),
|
|
)
|
|
.map((item) => item.name)
|
|
.join(', ')}
|
|
</p>
|
|
<p>
|
|
Sectores:{' '}
|
|
{plan.payload.sectors_text ||
|
|
(plan.payload.sectors_scope === 'all'
|
|
? 'Todos segundo a fonte'
|
|
: plan.payload.sectors_scope ===
|
|
'unknown'
|
|
? 'Por determinar'
|
|
: references.sectors
|
|
.filter((item) =>
|
|
plan.payload.sector_ids.includes(
|
|
Number(item.id),
|
|
),
|
|
)
|
|
.map((item) => item.name)
|
|
.join(', '))}
|
|
</p>
|
|
<ul>
|
|
{plan.payload.criteria.map((item) => (
|
|
<li key={item.code}>
|
|
<strong>{item.question}</strong> —{' '}
|
|
{item.expected === 'yes'
|
|
? 'Sim'
|
|
: 'Não'}
|
|
;{' '}
|
|
{item.applies_to === 'all'
|
|
? 'todos os candidatos'
|
|
: item.applies_to === 'entity'
|
|
? 'entidades'
|
|
: 'pessoas singulares'}
|
|
. {item.explanation} Fonte:{' '}
|
|
{item.source_reference}.
|
|
</li>
|
|
))}
|
|
</ul>
|
|
{plan.payload.windows.map((window) => (
|
|
<article key={window.code} className={card}>
|
|
<h4 className="font-semibold">
|
|
{window.title}
|
|
</h4>
|
|
<p>
|
|
{windowModes[window.mode]};{' '}
|
|
{window.start_on ||
|
|
'Abertura sem data indicada'}
|
|
;{' '}
|
|
{window.end_on ||
|
|
'Sem data-limite indicada'}
|
|
.
|
|
</p>
|
|
<p>{window.coverage_text}</p>
|
|
<p>
|
|
{references.territories
|
|
.filter((item) =>
|
|
(
|
|
window.territory_ids ?? []
|
|
).includes(String(item.id)),
|
|
)
|
|
.map((item) => item.name)
|
|
.join(', ')}
|
|
</p>
|
|
<a
|
|
className="underline"
|
|
href={window.source_url}
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
{window.source_reference}
|
|
</a>
|
|
<p>Consulta: {window.checked_on}</p>
|
|
{window.application_url && (
|
|
<p>
|
|
Canal indicado:{' '}
|
|
{window.application_url}
|
|
</p>
|
|
)}
|
|
<ul>
|
|
{window.criteria.map((item) => (
|
|
<li key={item.code}>
|
|
{item.question} —{' '}
|
|
{item.expected === 'yes'
|
|
? 'Sim'
|
|
: 'Não'}
|
|
; {item.explanation} Fonte:{' '}
|
|
{item.source_reference}.
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</article>
|
|
))}
|
|
<ul>
|
|
{references.documents
|
|
.filter((item) =>
|
|
plan.payload.resource_ids.includes(
|
|
Number(item.id),
|
|
),
|
|
)
|
|
.map((item) => (
|
|
<li key={item.id}>
|
|
Documento: {item.title}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
<label className="block">
|
|
<input
|
|
type="checkbox"
|
|
checked={confirmed}
|
|
onChange={(e) =>
|
|
setConfirmed(e.target.checked)
|
|
}
|
|
/>{' '}
|
|
Confirmei as condições, fontes e efeito da
|
|
publicação.
|
|
</label>
|
|
<button
|
|
className={button}
|
|
disabled={
|
|
!confirmed ||
|
|
form.processing ||
|
|
form.data.reason.length < 20
|
|
}
|
|
onClick={() =>
|
|
router.post(
|
|
`/management/fundos/${record.id}/publicar`,
|
|
{
|
|
version: plan.version,
|
|
hash: plan.hash,
|
|
reason: form.data.reason,
|
|
confirm: true,
|
|
},
|
|
)
|
|
}
|
|
>
|
|
Publicar versão conferida
|
|
</button>
|
|
</div>
|
|
)}
|
|
{record.published && (
|
|
<button
|
|
className="ml-3 underline"
|
|
disabled={
|
|
form.data.reason.length < 20 ||
|
|
JSON.stringify(draft) !==
|
|
JSON.stringify({
|
|
...empty,
|
|
...record?.draft,
|
|
})
|
|
}
|
|
onClick={() =>
|
|
router.post(
|
|
`/management/fundos/${record.id}/retirar`,
|
|
{
|
|
version: record.version,
|
|
reason: form.data.reason,
|
|
},
|
|
)
|
|
}
|
|
>
|
|
Retirar publicação
|
|
</button>
|
|
)}
|
|
</section>
|
|
)}
|
|
{history.length > 0 && (
|
|
<section className={card}>
|
|
<h2>Histórico de publicação</h2>
|
|
<ul>
|
|
{history.map((item) => (
|
|
<li key={item.version}>
|
|
Versão {item.version} — {item.published_at}:{' '}
|
|
{item.reason}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</section>
|
|
)}
|
|
</Frame>
|
|
);
|
|
}
|
|
|
|
export default function Edit(props: Parameters<typeof Editor>[0]) {
|
|
return (
|
|
<Editor
|
|
key={
|
|
props.record
|
|
? props.record.id + ':' + props.record.version
|
|
: 'new'
|
|
}
|
|
{...props}
|
|
/>
|
|
);
|
|
}
|