Files
Investidor-V2/resources/js/pages/education/analysis.tsx
T
nala 1106509116
tests / ci (push) Canceled after 0s
vrsao 2
2026-10-08 14:02:01 +02:00

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>
);
}