feat(web): add khu-cong-nghiep, chuyen-nhuong, and reports pages

Add three new frontend page sections:
- Industrial parks (khu-cong-nghiep): listing, detail, filter bar
- Transfer listings (chuyen-nhuong): search, category tabs, detail
- AI reports dashboard: list, create, viewer with TOC

Includes components, API clients, hooks, server helpers, i18n keys,
navigation links in public and dashboard layouts, and lint fixes.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Ho Ngoc Hai
2026-04-16 09:07:45 +07:00
parent 62a8842193
commit 7ce651fce5
30 changed files with 2874 additions and 1 deletions

View File

@@ -0,0 +1,41 @@
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { KhuCongNghiepDetailClient } from '@/components/khu-cong-nghiep/khu-cong-nghiep-detail-client';
import { fetchIndustrialParkBySlug } from '@/lib/khu-cong-nghiep-server';
interface PageProps {
params: Promise<{ slug: string; locale: string }>;
}
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const { slug } = await params;
const park = await fetchIndustrialParkBySlug(slug);
if (!park) return { title: 'Không tìm thấy khu công nghiệp' };
const description = park.description?.slice(0, 160) ??
`${park.name} — KCN tại ${park.province}, diện tích ${park.totalAreaHa} ha, tỷ lệ lấp đầy ${park.occupancyRate}%`;
return {
title: `${park.name} — Khu Công Nghiệp ${park.province}`,
description,
openGraph: {
title: park.name,
description,
images: park.media
?.filter((m) => m.type === 'image')
.slice(0, 1)
.map((m) => ({ url: m.url })) ?? [],
},
};
}
export default async function KhuCongNghiepDetailPage({ params }: PageProps) {
const { slug } = await params;
const park = await fetchIndustrialParkBySlug(slug);
if (!park) {
notFound();
}
return <KhuCongNghiepDetailClient park={park} />;
}