Files
Investidor-V2/resources/js/components/public-sector-cards.tsx
T

198 lines
8.4 KiB
TypeScript

import { ArrowRight } from 'lucide-react';
import { useState } from 'react';
import {
HOME_SECTION,
HOME_SECTION_HEADING,
PORTAL_MORE_LINK,
} from '@/lib/portal-presentation';
export type PublicSector = {
slug: string;
name: string;
description: string | null;
opportunitiesCount: number;
url?: string;
specializations?: { id: number; name: string; url: string }[];
};
const images: Record<string, string> = {
agronegocio: 'agronegocio',
turismo: 'turismo',
infraestruturas: 'infraestruturas',
'economia-azul': 'economia-azul',
'mercado-financeiro': 'servicos-financeiros',
'servicos-financeiros': 'servicos-financeiros',
fintech: 'tic-economia-digital',
mineracao: 'mineracao',
energia: 'energia',
'energia-especifica': 'energia',
imobiliario: 'outros-sectores',
'bens-de-consumo-e-franquias': 'industria',
'bens-de-consumo-franquias': 'industria',
'tecnologias-de-informacao': 'tic-economia-digital',
'tic-economia-digital': 'tic-economia-digital',
industria: 'industria',
'logistica-transportes': 'logistica-transportes',
};
const imageDescriptions: Record<string, string> = {
agronegocio: 'Campos agrícolas e instalações de transformação de produtos.',
turismo: 'Alojamento turístico junto à costa e à vegetação tropical.',
infraestruturas: 'Ponte rodoviária sobre um rio, ligando áreas urbanas.',
'economia-azul': 'Actividades de pesca e aquacultura numa zona costeira.',
'servicos-financeiros':
'Edifícios empresariais e serviços num espaço urbano.',
mineracao:
'Exploração mineira a céu aberto com instalações de processamento.',
energia:
'Painéis solares e infra-estruturas de transporte de electricidade.',
industria:
'Unidade industrial com área de armazenagem e movimentação de mercadorias.',
'tic-economia-digital':
'Infra-estruturas de telecomunicações num ambiente urbano.',
'logistica-transportes':
'Terminal portuário com contentores e transporte ferroviário.',
'outros-sectores': 'Espaço empresarial e edifícios urbanos ao fundo.',
};
function SectorImage({ slug, preview }: { slug: string; preview: boolean }) {
const [failed, setFailed] = useState(0);
const image = failed
? 'outros-sectores'
: (images[slug] ?? 'outros-sectores');
const base = `/images/sectors/${image}`;
if (failed > 1) return <span className="absolute inset-0 bg-slate-800" />;
return (
<img
src={`${base}.webp`}
srcSet={`${base}-480.webp 480w, ${base}.webp 960w`}
sizes={
preview
? '(min-width: 1152px) 266px, (min-width: 1024px) 25vw, 50vw'
: '(min-width: 1152px) 357px, (min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw'
}
width={960}
height={540}
alt={imageDescriptions[image]}
loading="lazy"
decoding="async"
className="h-full w-full object-cover transition-transform duration-500 motion-safe:group-hover:scale-105"
onError={() => setFailed((current) => Math.min(current + 1, 2))}
/>
);
}
export function PublicSectorCards({
sectors,
preview = false,
}: {
sectors: PublicSector[];
preview?: boolean;
}) {
return (
<div
className={
preview
? 'grid grid-cols-2 gap-4 lg:grid-cols-4'
: 'grid gap-5 sm:grid-cols-2 lg:grid-cols-3'
}
>
{sectors.map((sector) => {
const href =
sector.url ??
`/sectores/${sector.slug.split('/').map(encodeURIComponent).join('/')}`;
const titleId = `public-sector-${sector.slug.replaceAll('/', '-')}`;
return (
<article
key={sector.slug}
className="group flex min-w-0 flex-col overflow-hidden rounded-xl border border-slate-200 bg-white shadow-sm transition-colors hover:border-[#ed1c24]"
>
<a
href={href}
aria-labelledby={titleId}
className="relative block h-40 shrink-0 overflow-hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-[#ed1c24] sm:h-44"
>
<SectorImage slug={sector.slug} preview={preview} />
<span
aria-hidden="true"
className="absolute inset-0 bg-linear-to-t from-black/85 via-black/20 to-transparent"
/>
<h3
id={titleId}
className="absolute right-3 bottom-3 left-3 text-sm leading-5 font-bold wrap-break-word text-white drop-shadow-lg min-[375px]:text-base sm:text-lg sm:leading-6"
>
{sector.name}
</h3>
</a>
<div className="flex flex-1 flex-col gap-2 p-3 sm:p-4">
{sector.description && (
<p className="line-clamp-3 text-sm leading-5 wrap-break-word text-slate-600">
{sector.description}
</p>
)}
{sector.specializations?.map((child) => (
<a
key={child.id}
href={child.url}
className="text-xs font-semibold text-slate-700 underline underline-offset-2 focus-visible:outline-2 focus-visible:outline-[#ed1c24]"
>
Especialização:{' '}
{child.name.replace(
'Mercado Financeiro — ',
'',
)}
</a>
))}
<div className="mt-auto pt-1">
<p className="text-xs leading-5 text-slate-500">
{sector.opportunitiesCount} oportunidade
{sector.opportunitiesCount === 1 ? '' : 's'}
</p>
<a
href={href}
aria-label={`Ler mais sobre ${sector.name}`}
className="inline-flex min-h-11 items-center gap-1.5 text-xs font-bold text-[#ed1c24] hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#ed1c24]"
>
Ler mais{' '}
<ArrowRight size={14} aria-hidden="true" />
</a>
</div>
</div>
</article>
);
})}
</div>
);
}
export function HomepageSectors({ sectors }: { sectors: PublicSector[] }) {
return (
<section
aria-labelledby="home-sectors-heading"
className={HOME_SECTION}
>
<h2 id="home-sectors-heading" className={HOME_SECTION_HEADING}>
Sectores de investimento
</h2>
<p className="mt-2 max-w-2xl text-slate-600">
Explore as áreas prioritárias do programa de investimento
nacional.
</p>
<div className="mt-5">
<PublicSectorCards sectors={sectors.slice(0, 4)} preview />
{!sectors.length && (
<p className="text-slate-600">
Não existem sectores publicados.
</p>
)}
</div>
<div className="mt-5 flex justify-center">
<a href="/sectores" className={PORTAL_MORE_LINK}>
Ver mais sectores{' '}
<ArrowRight size={14} aria-hidden="true" />
</a>
</div>
</section>
);
}