Files
Investidor-V2/resources/js/pages/investment/shared.tsx
T

210 lines
6.8 KiB
TypeScript

import { Head, Link, usePage } from '@inertiajs/react';
import type { ReactNode } from 'react';
import { PortalMenu } from '@/layouts/portal-navigation';
import type { TerritorialSourceEvidence } from '@/components/territorial-source-evidence';
import type {
StructureGeography,
DocumentedLocation,
} from '@/components/structure-geography';
export type Option = {
id: number;
group: string;
code: string;
label: string;
active: boolean;
requires_description: boolean;
sort_order: number;
};
export type Entity = {
id: string;
name: string;
url?: string | null;
dossiers?: { id: string; title: string; source: string }[];
geography?: StructureGeography | null;
};
export type References = {
preparationOptions?: { value: string; label: string; group: string }[];
classification?: import('@/components/opportunity-classification').ClassificationOptions;
authorities?: { id: string; label_pt_mz: string }[];
categories?: string[];
maturityCatalogue?: {
version: string;
stages: Record<string, string>;
legacyVersion: string;
legacyLabels: Record<number, string>;
};
zones?: { id: string; name: string; category: string }[];
options: Option[];
clusters: Entity[];
corridors: Entity[];
projects: Entity[];
locations: Entity[];
sectors: { id: number; name: string; slug: string }[];
};
export type Opportunity = {
id: number;
title: string;
slug: string;
reference: string;
summary: string | null;
description: string | null;
province: string | null;
district: string | null;
sector?: { name: string; slug: string };
estimated_investment_amount: string | null;
estimated_investment_currency: string;
maturity_label: string | null;
maturity_catalogue_version?: string | null;
promoter_name: string | null;
expected_impact: string | null;
financing_modality: string | null;
status: string;
last_validated_at?: string | null;
};
export type Context = {
classification?: import('@/components/opportunity-classification').PublicClassification[];
source_evidence?: TerritorialSourceEvidence[];
opportunity_locations?: DocumentedLocation[];
primary_territory_path?: {
id: string;
name: string;
location_type: string;
}[];
zones?: {
id: string;
name: string;
category: string;
association_type: string;
source_reference: string;
}[];
profile: {
version: number;
is_education: boolean;
business_model: string | null;
requirements: string | null;
risks: string | null;
target_date: string | null;
source_reference: string | null;
} | null;
facets: { id: number; group: string; label: string }[];
clusters: Entity[];
corridors: Entity[];
projects: Entity[];
locations: Entity[];
};
export const input =
'w-full rounded-lg border border-slate-300 bg-white px-3 py-2 text-slate-900 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-emerald-700';
export const button =
'inline-flex items-center justify-center rounded-lg bg-emerald-800 px-4 py-2 font-semibold text-white disabled:opacity-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-emerald-700';
export const card = 'rounded-xl border border-slate-200 bg-white p-5 shadow-sm';
export function Frame({
title,
children,
}: {
title: string;
children: ReactNode;
}) {
const { flash } = usePage<{
flash?: { success?: string; warning?: string };
}>().props;
return (
<div className="min-h-screen bg-slate-50 text-slate-900">
<Head title={title} />
<PortalMenu />
<main className="mx-auto max-w-7xl space-y-6 px-5 py-9">
<h1 className="text-3xl font-bold">{title}</h1>
{flash?.warning && (
<p
role="alert"
className="rounded border border-amber-600 bg-amber-50 p-4 text-amber-950"
>
{flash.warning}
</p>
)}
{flash?.success && (
<p
role="status"
className="rounded border border-emerald-700 bg-emerald-50 p-4"
>
{flash.success}
</p>
)}
{children}
</main>
<footer className="border-t p-6 text-center">
<Link href="/apoio" className="underline">
Apoio ao Investidor
</Link>{' '}
·{' '}
<Link href="/documentos" className="underline">
Documentos
</Link>
</footer>
</div>
);
}
export function Field({
label,
children,
}: {
label: string;
children: ReactNode;
}) {
return (
<label className="block space-y-1 text-sm font-medium">
<span>{label}</span>
{children}
</label>
);
}
export function money(r: Opportunity) {
return r.estimated_investment_amount === null
? 'Montante não indicado'
: `${new Intl.NumberFormat('pt-MZ', { maximumFractionDigits: 2 }).format(Number(r.estimated_investment_amount))} ${r.estimated_investment_currency}`;
}
export async function api(url: string, body?: unknown) {
const token = decodeURIComponent(
document.cookie
.split('; ')
.find((c) => c.startsWith('XSRF-TOKEN='))
?.slice(11) ?? '',
);
const formData =
typeof FormData !== 'undefined' && body instanceof FormData;
const response = await fetch(url, {
method: body === undefined ? 'GET' : 'POST',
credentials: 'same-origin',
headers: {
Accept: 'application/json',
...(formData ? {} : { 'Content-Type': 'application/json' }),
'X-XSRF-TOKEN': token,
},
...(body === undefined
? {}
: { body: formData ? body : JSON.stringify(body) }),
});
const data = await response.json();
if (!response.ok)
throw new Error(
data.message ?? 'Não foi possível concluir a operação.',
);
return data;
}
export const groupLabels: Record<string, string> = {
education: 'Educação',
infrastructure: 'Infraestruturas',
labour: 'Mão de obra',
qualification: 'Qualificações',
support: 'Apoios',
impact: 'Impacto',
partnership: 'Modelo de parceria',
subsector: 'Subsector',
need_category: 'Categoria da necessidade',
priority: 'Prioridade',
unit: 'Unidade',
participation: 'Participação',
investor_profile: 'Perfil do investidor',
need_status: 'Estado da necessidade',
};