merge: preserve current main catalogue and restore accepted fiscal manual history

This commit is contained in:
2026-10-26 16:07:28 +02:00
10 changed files with 298 additions and 124 deletions
@@ -13,9 +13,12 @@ export type EducationalArticle = {
export default function EducationalArticleCard({
article,
headingLevel = 3,
}: {
article: EducationalArticle;
headingLevel?: 3 | 4;
}) {
const Heading = headingLevel === 3 ? 'h3' : 'h4';
const focus =
'focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-emerald-800';
return (
@@ -34,11 +37,11 @@ export default function EducationalArticleCard({
<p className="text-sm font-medium text-emerald-800">
Guia sectorial
</p>
<h3 className="text-2xl leading-tight font-bold">
<Heading className="text-2xl leading-tight font-bold">
<Link href={article.href} className={focus}>
{article.title}
</Link>
</h3>
</Heading>
{article.summary && (
<p className="text-base leading-relaxed text-slate-700">
{article.summary}
+112
View File
@@ -0,0 +1,112 @@
import { Link } from '@inertiajs/react';
import SectorEntry, { type SectorEntryData } from '@/components/sector-entry';
export type SectorDetailData = {
sector: {
id: number;
name: string;
slug: string;
description: string | null;
short_description: string | null;
};
sectorEntry?: SectorEntryData | null;
opportunities?: {
title: string;
slug: string;
province: string | null;
summary: string | null;
}[];
};
const button =
'inline-block rounded-lg bg-emerald-800 px-5 py-3 font-semibold text-white disabled:opacity-50';
export default function SectorContent({
sector,
sectorEntry,
opportunities,
headingLevel = 2,
}: SectorDetailData & { headingLevel?: 2 | 3 }) {
const Heading = headingLevel === 2 ? 'h2' : 'h3';
return (
<div className="space-y-5">
<p className="whitespace-pre-line">
{sector.description ||
sector.short_description ||
'Descrição sectorial em preparação.'}
</p>
{!sectorEntry && (
<Link
className={button}
href={'/investimentos/iniciar?source=sector:' + sector.id}
>
Iniciar ou continuar uma ideia neste sector
</Link>
)}
{sectorEntry && (
<SectorEntry
headingLevel={headingLevel}
data={{
...sectorEntry,
start_href: sectorEntry.start_href
? '/investimentos/iniciar?source=sector:' +
sector.id
: null,
}}
/>
)}
<Heading className="text-xl font-bold">
Oportunidades no sector
</Heading>
{opportunities?.map((o) => (
<article key={o.slug} className="rounded border bg-white p-5">
<Link
className="text-lg font-bold underline"
href={`/oportunidades/${o.slug}`}
>
{o.title}
</Link>
<p>{o.province}</p>
<p>{o.summary}</p>
<ul aria-label="Localizações associadas">
{sectorEntry?.locations
.filter(
(l) => l.href === `/oportunidades/${o.slug}`,
)
.filter(
(l, i, rows) =>
rows.findIndex(
(other) =>
JSON.stringify(other.location) ===
JSON.stringify(l.location),
) === i,
)
.map((l, i) => (
<li key={i}>
{typeof l.location.name === 'string'
? l.location.name
: 'Consultar localização e fonte na oportunidade'}
</li>
))}
</ul>
</article>
))}
{!opportunities?.length && (
<p>Sem oportunidades publicadas neste sector.</p>
)}
<Link
className={button}
href={`/oportunidades?sector=${sector.slug}`}
>
Pesquisar todas as oportunidades do sector
</Link>
<p>
<Link href="/como-investir" className="underline">
Preparar o investimento
</Link>{' '}
·{' '}
<Link href="/apoio" className="underline">
Pedir apoio sectorial
</Link>
</p>
</div>
);
}
+18 -5
View File
@@ -24,7 +24,14 @@ export type SectorEntryData = {
specialization_href: string | null;
content_notice: string | null;
};
export default function SectorEntry({ data }: { data: SectorEntryData }) {
export default function SectorEntry({
data,
headingLevel = 2,
}: {
data: SectorEntryData;
headingLevel?: 2 | 3;
}) {
const Heading = headingLevel === 2 ? 'h2' : 'h3';
return (
<section
className="space-y-5"
@@ -64,7 +71,9 @@ export default function SectorEntry({ data }: { data: SectorEntryData }) {
perfil e a modalidade já autorizados; se ainda não tem perfil,
pode preparar a ideia e pedir o perfil de promotor.
</p>
<h2 className="text-xl font-bold">Descubra onde investir</h2>
<Heading className="text-xl font-bold">
Descubra onde investir
</Heading>
{data.content_notice && <p>{data.content_notice}</p>}
{!data.articles.length && (
<p>
@@ -74,7 +83,11 @@ export default function SectorEntry({ data }: { data: SectorEntryData }) {
)}
<div className="grid gap-4 md:grid-cols-2">
{data.articles.map((a) => (
<EducationalArticleCard key={a.href} article={a} />
<EducationalArticleCard
key={a.href}
article={a}
headingLevel={headingLevel === 2 ? 3 : 4}
/>
))}
</div>
{data.specialization_href && (
@@ -87,9 +100,9 @@ export default function SectorEntry({ data }: { data: SectorEntryData }) {
dependências.
</p>
)}
<h2 className="text-xl font-bold">
<Heading className="text-xl font-bold">
Projetos âncora, clusters e território
</h2>
</Heading>
{data.relations.length ? (
<ul>
{data.relations.map((r) => (
+47 -110
View File
@@ -12,7 +12,9 @@ import { ProvinceSelect } from '@/components/investment-territory';
import { SimulationForm } from '@/components/simulation-form';
import { InvestmentRoadmap } from '@/components/investment-roadmap';
import { JourneyDocumentResources } from '@/components/journey-document-resources';
import SectorEntry, { type SectorEntryData } from '@/components/sector-entry';
import SectorContent, {
type SectorDetailData,
} from '@/components/sector-content';
import CountrySelect, { type Country } from '@/components/country-select';
const input =
@@ -68,7 +70,8 @@ type Page<T> = {
total: number;
};
type Props = {
sectorEntry?: SectorEntryData;
sectorEntry?: SectorDetailData['sectorEntry'];
sectorDetails?: SectorDetailData[];
countries?: Country[];
section: string;
filters?: Record<string, string | string[]>;
@@ -80,19 +83,8 @@ type Props = {
url?: string;
specializations?: { id: number; name: string; url: string }[];
}[];
sector?: {
id: number;
name: string;
slug: string;
description: string;
short_description: string;
};
opportunities?: {
title: string;
slug: string;
province: string;
summary: string;
}[];
sector?: SectorDetailData['sector'];
opportunities?: SectorDetailData['opportunities'];
resources?: Page<Resource>;
categories?: string[];
topics?: string[];
@@ -511,8 +503,9 @@ export default function Services(p: Props) {
)}
{p.section === 'sectors' && (
<section
id="sectores-indice"
aria-label="Todos os sectores de investimento"
className="space-y-6"
className="scroll-mt-24 space-y-6"
>
<p className="text-slate-600">
Explore o potencial estratégico de cada sector em
@@ -525,9 +518,41 @@ export default function Services(p: Props) {
description: sector.short_description,
opportunitiesCount: sector.opportunities_count,
specializations: sector.specializations,
url: sector.url,
url: p.sectorDetails?.length
? `#sector-${sector.slug}`
: sector.url,
}))}
/>
{p.sectorDetails?.map((detail) => (
<section
id={`sector-${detail.sector.slug}`}
key={detail.sector.id}
aria-labelledby={`sector-heading-${detail.sector.slug}`}
className="scroll-mt-24 space-y-5 border-t border-slate-200 pt-6"
>
<div className="flex flex-wrap items-center justify-between gap-3">
<h2
id={`sector-heading-${detail.sector.slug}`}
className="text-2xl font-bold"
>
{detail.sector.name}
</h2>
<Link
href={`/sectores/${detail.sector.slug}`}
className="inline-flex min-h-11 items-center text-sm font-semibold underline"
>
Abrir página do sector
</Link>
</div>
<SectorContent {...detail} headingLevel={3} />
<a
href="#sectores-indice"
className="inline-flex min-h-11 items-center text-sm font-semibold underline"
>
Voltar à lista de sectores
</a>
</section>
))}
{!p.sectors?.length && (
<p>Não existem sectores publicados.</p>
)}
@@ -539,99 +564,11 @@ export default function Services(p: Props) {
</section>
)}
{p.section === 'sector' && p.sector && (
<>
<p className="whitespace-pre-line">
{p.sector.description ||
p.sector.short_description ||
'Descrição sectorial em preparação.'}
</p>
{!p.sectorEntry && (
<Link
className={button}
href={
'/investimentos/iniciar?source=sector:' +
p.sector.id
}
>
Iniciar ou continuar uma ideia neste sector
</Link>
)}
{p.sectorEntry && (
<SectorEntry
data={{
...p.sectorEntry,
start_href: p.sectorEntry.start_href
? '/investimentos/iniciar?source=sector:' +
p.sector.id
: null,
}}
/>
)}
<h2 className="text-xl font-bold">
Oportunidades no sector
</h2>
{p.opportunities?.map((o) => (
<article
key={o.slug}
className="rounded border bg-white p-5"
>
<Link
className="text-lg font-bold underline"
href={`/oportunidades/${o.slug}`}
>
{o.title}
</Link>
<p>{o.province}</p>
<p>{o.summary}</p>
<ul aria-label="Localizações associadas">
{p.sectorEntry?.locations
.filter(
(l) =>
l.href ===
`/oportunidades/${o.slug}`,
)
.filter(
(l, i, rows) =>
rows.findIndex(
(other) =>
JSON.stringify(
other.location,
) ===
JSON.stringify(
l.location,
),
) === i,
)
.map((l, i) => (
<li key={i}>
{typeof l.location.name ===
'string'
? l.location.name
: 'Consultar localização e fonte na oportunidade'}
</li>
))}
</ul>
</article>
))}
{!p.opportunities?.length && (
<p>Sem oportunidades publicadas neste sector.</p>
)}
<Link
className={button}
href={`/oportunidades?sector=${p.sector.slug}`}
>
Pesquisar todas as oportunidades do sector
</Link>
<p>
<Link href="/como-investir" className="underline">
Preparar o investimento
</Link>{' '}
·{' '}
<Link href="/apoio" className="underline">
Pedir apoio sectorial
</Link>
</p>
</>
<SectorContent
sector={p.sector}
sectorEntry={p.sectorEntry}
opportunities={p.opportunities}
/>
)}
{['procedures', 'documents', 'legislation'].includes(
p.section,
+1 -1
View File
@@ -15,7 +15,7 @@ export default function WhyInvest() {
<div className="-mx-2 flow-root">
<article
aria-label="Investir em Moçambique"
className="rounded-lg bg-[#D0E2FF] p-2 lg:float-left lg:w-2/3"
className="bg-white p-2 lg:float-left lg:w-2/3"
>
<div className="space-y-3 text-base leading-6 text-slate-700">
<p>