302 lines
13 KiB
TypeScript
302 lines
13 KiB
TypeScript
import { Link, useForm } from '@inertiajs/react';
|
|
import { useState } from 'react';
|
|
import { Management, Field, control, button } from '../management/shared';
|
|
import {
|
|
TerritoryFields,
|
|
type TerritoryOption,
|
|
} from '@/components/investment-territory';
|
|
export default function Analysis({
|
|
demand,
|
|
candidates,
|
|
eligible,
|
|
gap,
|
|
proposal,
|
|
sectors,
|
|
territoryPath,
|
|
}: {
|
|
demand: {
|
|
id: string;
|
|
version_no: number;
|
|
quantity: number;
|
|
qualification: string;
|
|
geo_location_id: string;
|
|
};
|
|
candidates: {
|
|
id: string;
|
|
quantity: number;
|
|
period_start: string;
|
|
period_end: string;
|
|
source: string;
|
|
}[];
|
|
eligible: boolean;
|
|
gap: { state: string; people: number | null };
|
|
proposal: {
|
|
opportunity_id: number;
|
|
demand_version: number;
|
|
intervention: string;
|
|
equipment_needs: string | null;
|
|
trainer_needs: string | null;
|
|
funding_plan: string | null;
|
|
risks: string;
|
|
source: string;
|
|
} | null;
|
|
sectors: { id: number; name: string }[];
|
|
territoryPath: TerritoryOption[];
|
|
}) {
|
|
const [selectedTerritory, setSelectedTerritory] = useState(territoryPath);
|
|
const form = useForm({
|
|
demand_version: demand.version_no,
|
|
sector_id: '',
|
|
title: '',
|
|
intervention: '',
|
|
equipment_needs: '',
|
|
trainer_needs: '',
|
|
funding_plan: '',
|
|
risks: '',
|
|
source: '',
|
|
currency: 'MZN',
|
|
province_id: territoryPath[0]?.id ?? '',
|
|
district_id: territoryPath[1]?.id ?? '',
|
|
admin_post_id: territoryPath[2]?.id ?? '',
|
|
locality_id: territoryPath[3]?.id ?? '',
|
|
});
|
|
return (
|
|
<Management title="Análise assistida de competências">
|
|
<p>
|
|
Procura: {demand.quantity} trabalhadores ·{' '}
|
|
{demand.qualification}
|
|
</p>
|
|
<p>
|
|
Localização da procura:{' '}
|
|
{territoryPath.map((territory) => territory.name).join(' · ') ||
|
|
'Não indicada'}
|
|
</p>
|
|
<p className="rounded border p-4">
|
|
{gap.state === 'expired'
|
|
? 'Fonte expirada. Actualize a declaração antes de analisar.'
|
|
: !eligible
|
|
? 'A procura exige revisão independente antes da análise.'
|
|
: 'Lacuna quantitativa não determinada: falta evidência de trabalhadores qualificados e disponíveis. Vagas em cursos não são trabalhadores disponíveis.'}
|
|
</p>
|
|
<h2 className="text-xl font-semibold">
|
|
Oferta potencialmente compatível
|
|
</h2>
|
|
<p>
|
|
Critérios: mesma ocupação, competência principal, território e
|
|
texto de qualificação; fonte vigente e revisão interna;
|
|
conclusão até ao início da necessidade. Esta correspondência não
|
|
comprova equivalência curricular, acreditação, conclusão ou
|
|
contratação.
|
|
</p>
|
|
{candidates.map((c) => (
|
|
<article className="rounded border p-4" key={c.id}>
|
|
<p>
|
|
{c.quantity} vagas · {c.period_start} a {c.period_end}
|
|
</p>
|
|
<p>Fonte: {c.source}</p>
|
|
</article>
|
|
))}
|
|
{!candidates.length && (
|
|
<p>
|
|
Não há oferta que satisfaça estes critérios. Isto não prova
|
|
ausência de instituições no território.
|
|
</p>
|
|
)}
|
|
<p>
|
|
As capacidades não são somadas nem alocadas: podem partilhar
|
|
formadores, instalações ou equipamentos.
|
|
</p>
|
|
<section className="space-y-4 rounded border p-4">
|
|
<h2 className="text-xl font-semibold">
|
|
Preparação do investimento educativo
|
|
</h2>
|
|
<p>
|
|
A ficha permanece em rascunho até à validação institucional
|
|
da lacuna e da resposta formativa. Não garante emprego ou
|
|
financiamento.
|
|
</p>
|
|
{proposal ? (
|
|
<>
|
|
{proposal.demand_version !== demand.version_no && (
|
|
<p role="alert">
|
|
A procura foi alterada desde a criação da ficha.
|
|
É necessária reconciliação.
|
|
</p>
|
|
)}
|
|
<p>{proposal.intervention}</p>
|
|
<p>
|
|
Equipamentos:{' '}
|
|
{proposal.equipment_needs ||
|
|
'Pendente de levantamento'}
|
|
</p>
|
|
<p>
|
|
Formadores:{' '}
|
|
{proposal.trainer_needs ||
|
|
'Pendente de levantamento'}
|
|
</p>
|
|
<p>
|
|
Financiamento:{' '}
|
|
{proposal.funding_plan ||
|
|
'Pendente de levantamento'}
|
|
</p>
|
|
<p>Riscos: {proposal.risks}</p>
|
|
<p>Fonte: {proposal.source}</p>
|
|
<Link
|
|
className="underline"
|
|
href={`/management/opportunities/${proposal.opportunity_id}/edit`}
|
|
>
|
|
Consultar rascunho no catálogo de oportunidades
|
|
</Link>
|
|
</>
|
|
) : eligible ? (
|
|
<form
|
|
className="grid gap-4 md:grid-cols-2"
|
|
onSubmit={(e) => {
|
|
e.preventDefault();
|
|
form.transform((data) => ({
|
|
...data,
|
|
demand_version: demand.version_no,
|
|
}));
|
|
form.post(
|
|
`/management/education/demands/${demand.id}/investment`,
|
|
);
|
|
}}
|
|
>
|
|
<div className="space-y-2 md:col-span-2">
|
|
<p className="font-medium">
|
|
Localização da oportunidade educativa
|
|
</p>
|
|
<p className="text-muted-foreground text-sm">
|
|
Inicialmente preenchida com a localização da
|
|
procura. Confirme ou ajuste cada nível
|
|
territorial.
|
|
</p>
|
|
<TerritoryFields
|
|
initial={selectedTerritory}
|
|
endpoint="/management/territory-options"
|
|
onChange={(path) => {
|
|
setSelectedTerritory(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 ?? '',
|
|
}));
|
|
}}
|
|
/>
|
|
{Object.entries(form.errors)
|
|
.filter(([key]) => key.endsWith('_id'))
|
|
.map(([key, error]) => (
|
|
<p role="alert" key={key}>
|
|
{error}
|
|
</p>
|
|
))}
|
|
</div>
|
|
<Field label="Sector">
|
|
<select
|
|
required
|
|
className={control}
|
|
value={form.data.sector_id}
|
|
onChange={(e) =>
|
|
form.setData('sector_id', e.target.value)
|
|
}
|
|
>
|
|
<option value="">Seleccionar sector</option>
|
|
{sectors.map((s) => (
|
|
<option value={s.id} key={s.id}>
|
|
{s.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</Field>
|
|
<Field label="Título">
|
|
<input
|
|
required
|
|
maxLength={200}
|
|
className={control}
|
|
value={form.data.title}
|
|
onChange={(e) =>
|
|
form.setData('title', e.target.value)
|
|
}
|
|
/>
|
|
</Field>
|
|
{(
|
|
[
|
|
'intervention',
|
|
'equipment_needs',
|
|
'trainer_needs',
|
|
'funding_plan',
|
|
'risks',
|
|
'source',
|
|
] as const
|
|
).map((key) => (
|
|
<Field
|
|
key={key}
|
|
label={
|
|
{
|
|
intervention:
|
|
'Resposta formativa proposta',
|
|
equipment_needs:
|
|
'Necessidades de equipamentos (opcional)',
|
|
trainer_needs:
|
|
'Necessidades de formadores (opcional)',
|
|
funding_plan:
|
|
'Financiamento e sustentabilidade (opcional)',
|
|
risks: 'Riscos e limitações',
|
|
source: 'Fonte e responsável pela proposta',
|
|
}[key]
|
|
}
|
|
>
|
|
<textarea
|
|
required={[
|
|
'intervention',
|
|
'risks',
|
|
'source',
|
|
].includes(key)}
|
|
className={control}
|
|
value={form.data[key]}
|
|
onChange={(e) =>
|
|
form.setData(key, e.target.value)
|
|
}
|
|
/>
|
|
</Field>
|
|
))}
|
|
<Field label="Moeda de referência (montante ainda não estimado)">
|
|
<select
|
|
className={control}
|
|
value={form.data.currency}
|
|
onChange={(e) =>
|
|
form.setData('currency', e.target.value)
|
|
}
|
|
>
|
|
{['MZN', 'USD', 'EUR', 'ZAR'].map((c) => (
|
|
<option key={c}>{c}</option>
|
|
))}
|
|
</select>
|
|
</Field>
|
|
<div>
|
|
{Object.entries(form.errors).map(([key, value]) => (
|
|
<p role="alert" key={key}>
|
|
{value}
|
|
</p>
|
|
))}
|
|
<button
|
|
className={button}
|
|
disabled={form.processing}
|
|
>
|
|
Guardar ficha em rascunho
|
|
</button>
|
|
</div>
|
|
</form>
|
|
) : (
|
|
<p>
|
|
Reveja a procura e a validade da fonte antes de preparar
|
|
a ficha.
|
|
</p>
|
|
)}
|
|
</section>
|
|
</Management>
|
|
);
|
|
}
|