Commit 2ce89c19 authored by Phạm Quang Bảo's avatar Phạm Quang Bảo

Merge branch 'tumlumtumla' into feat/home_page

parents 33fcbbfb 6c996526
......@@ -79,7 +79,8 @@ const orvalConfig = async () => {
'UserHistory',
'Approvals',
'News',
'Category'
'Category',
'NewsPageConfig',
]
}
}
......
This diff is collapsed.
......@@ -140,6 +140,7 @@ export * from './putEventsLinkParams';
export * from './putEventsParams';
export * from './putFooterParams';
export * from './putMembershipFeeParams';
export * from './putNewsPageConfigCategoryIdBody';
export * from './putNotificationsMarkAsReadParams';
export * from './putNotificationsParams';
export * from './putOrderPaymentParams';
......
/**
* Generated by orval v8.0.0-rc.0 🍺
* Do not edit manually.
* VCCI
* Coded by Meu TEAM
* OpenAPI spec version: 1.0.0
*/
export type PutNewsPageConfigCategoryIdBody = {
category_ids?: string[];
};
......@@ -41,7 +41,7 @@ const NewsDetailPage = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-5">
{/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-7">
<div className='pb-5 text-blue-900 text-2xl leading-normal font-medium'>
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<div className='flex items-center gap-2 text-sm mb-4'>
......
......@@ -13,6 +13,10 @@ function CardEvent({ event }: { event: EventItem }) {
src={`${BASE_URL.imageEndpoint}${event.image}`}
alt={event.name}
className='w-[100px] md:w-[130px] aspect-3/2 object-cover'
onError={(e) => {
e.currentTarget.onerror = null
e.currentTarget.src = "/fallback.png"
}}
/>
<div className='flex-1'>
<p className='text-[#0056b3] font-bold text-sm line-clamp-2'>
......
......@@ -12,8 +12,13 @@ function CardNews({ news }: { news: NewsAdminItem }) {
<img
src={`${BASE_URL.imageEndpoint}${news.thumbnail}`}
alt={news.title}
className='w-[100px] md:w-[130px] aspect-3/2 object-cover'
className="w-[100px] md:w-[130px] aspect-3/2 object-cover"
onError={(e) => {
e.currentTarget.onerror = null
e.currentTarget.src = "/fallback.png"
}}
/>
<div className='flex-1'>
<p className='text-[#0056b3] font-bold text-sm line-clamp-2'>
{news.title}
......
......@@ -30,7 +30,9 @@ const Page = () => {
const swiperRef = useRef<SwiperType | null>(null)
const { data: categoryData, isLoading: isLoadingCategory } = useGetCategory<GetCategoryAdminResponseType>()
const { data: newsData, isLoading: isLoadingNews } = useGetNews<GetNewsAdminResponseType>()
const { data: newsData, isLoading: isLoadingNews } = useGetNews<GetNewsAdminResponseType>(
{ pageSize: '999' },
)
const { data: eventData, isLoading: isLoadingEvent } = useGetEvents<EventApiResponse>()
// filter category
......@@ -176,6 +178,10 @@ const Page = () => {
src={`${BASE_URL.imageEndpoint}${news.thumbnail}`}
alt={news.title}
className="w-full h-full object-cover"
onError={(e) => {
e.currentTarget.onerror = null
e.currentTarget.src = "/fallback.png"
}}
/>
</div>
......@@ -279,7 +285,10 @@ const Page = () => {
src={`${BASE_URL.imageEndpoint}${event.image}`}
alt={event.name}
className="w-full h-full object-cover"
/>
onError={(e) => {
e.currentTarget.onerror = null
e.currentTarget.src = "/fallback.png"
}} />
</div>
<div className="flex-1">
......
const MenuItem = ({ title, items, link }: { title: string; items: string[]; link?: string }) => (
<div className="group relative">
<a
className="px-3 py-5 text-[16px] font-[600] text-[#124588] hover:text-[#E8C518] transition block"
href={`/${link}`}
>
{title}
</a>
<div className="absolute left-0 top-full hidden group-hover:block bg-[#124588]/98 text-white text-[14px] font-[500] min-w-[220px] shadow-lg">
{items.map((item, i) => (
<div
key={i}
className="px-5 py-3 hover:bg-[#e8c518]/80 cursor-pointer whitespace-nowrap"
>
{item}
</div>
))}
import Link from "next/link";
type MenuItemProps = {
title: string;
link?: string;
items: { title: string; link: string }[];
};
const MenuItem = ({ title, link, items }: MenuItemProps) => {
return (
<div className="group relative">
<Link
href={`/${link || ""}`}
className="px-3 py-5 text-[16px] font-[600] text-[#124588] hover:text-[#E8C518] transition block"
>
{title}
</Link>
{/* Dropdown */}
<div className="absolute left-0 top-full hidden group-hover:block bg-[#124588]/98 text-white text-[14px] font-[500] min-w-[220px] shadow-lg">
{items.map((item, i) => (
<Link
key={i}
href={`/${link}/${item.link}`}
className="block px-5 py-3 hover:bg-[#e8c518]/80 cursor-pointer whitespace-nowrap transition"
>
{item.title}
</Link>
))}
</div>
</div>
</div>
);
);
};
export default MenuItem;
......@@ -14,7 +14,7 @@ import {
import Image from "next/image";
import vietnamMap from "@/assets/vietnam-map-white.png.webp";
function footer() {
function Footer() {
const emailRef = useRef<HTMLInputElement>(null);
const checkBoxRef = useRef<HTMLInputElement>(null);
const [emailError, setEmailError] = useState(false);
......@@ -212,4 +212,4 @@ function footer() {
);
}
export default footer;
export default Footer;
"use client";
import React, { useState } from "react";
import { useRouter } from 'next/navigation'
import { Menu, X, Facebook, Linkedin, Twitter, Youtube } from "lucide-react";
import logo from "@/assets/VCCI-HCM-logo-VN-2025.png";
import Image from "next/image";
import MenuItem from "./MenuItem";
import Link from "next/link";
function Header() {
const [toggleMenu, setToggleMenu] = useState<boolean>(false);
const router = useRouter()
return (
<>
......@@ -25,7 +27,7 @@ function Header() {
className="px-3 py-2 text-[14px] text-white hover:opacity-80"
href="#"
>
sitemap
Sitemap
</a>
<a
className="px-3 py-2 text-[14px] text-white hover:opacity-80"
......@@ -40,6 +42,13 @@ function Header() {
className="bg-white h-12 rounded-sm outline-none px-4 w-64 placeholder:text-sm"
type="text"
placeholder="Tìm kiếm"
onKeyDown={(e) => {
if (e.key === 'Enter') {
const value = (e.currentTarget as HTMLInputElement).value || ''
const encoded = encodeURIComponent(value)
router.push(`/search?q=${encoded}`)
}
}}
/>
<div className="flex gap-2">
{[Facebook, Twitter, Youtube, Linkedin].map((Icon, i) => (
......@@ -56,17 +65,17 @@ function Header() {
</div>
</div>
<div className="sticky top-0 z-50 bg-[#ededed] shadow-md">
<div className="sticky top-0 z-50 bg-[#ededed] shadow-md py-4">
<div className="container m-auto">
<div className="w-full flex justify-between items-center">
{/* Logo */}
<a href="/" className="flex items-center">
<Link href="/">
<Image
className="w-[140px] object-contain lg:ml-0 ml-10"
className="w-[140px] object-contain"
src={logo}
alt="VCCI HCM"
/>
</a>
</Link>
{/* Desktop Menu */}
<nav className="hidden lg:flex items-center">
......@@ -75,35 +84,74 @@ function Header() {
title="Giới thiệu"
link="gioi-thieu"
items={[
"Về VCCI-HCM",
"Chức Năng Và Nhiệm Vụ",
"Sơ Đồ Tổ Chức",
"Dịch Vụ Cung Cấp",
{ title: "Về VCCI-HCM", link: "ve-vcci-hcm" },
{
title: "Chức Năng Và Nhiệm Vụ",
link: "chuc-nang-va-nhiem-vu",
},
{ title: "Sơ Đồ Tổ Chức", link: "so-do-to-chuc" },
{ title: "Dịch Vụ Cung Cấp", link: "dich-vu-cung-cap" },
]}
/>
<MenuItem
title="Hội viên"
link="hoi-vien"
items={[
"Lợi Ích Của Hội Viên VCCI",
"Đăng Ký Hội Viên",
"Kết Nối Hội Viên",
"Tin Hội Viên",
items={[
{
title: "Lợi Ích Của Hội Viên VCCI",
link: "",
},
{ title: "Đăng Ký Hội Viên", link: "dang-ky-hoi-vien" },
{ title: "Kết Nối Hội Viên", link: "ket-noi-hoi-vien" },
{ title: "Tin Hội Viên", link: "tin-hoi-vien" },
]}
/>
<MenuItem title="Hoạt động" items={["Sự Kiện", "Đào Tạo"]} />
<MenuItem
title="Hoạt động"
link="hoat-dong"
items={[
{ title: "Sự Kiện", link: "" },
{ title: "Đào Tạo", link: "dao-tao" },
]}
/>
<MenuItem
title="Xuất Xứ Hàng Hóa"
link="xuat-xu-hang-hoa"
items={[
"Định Nghĩa Chung",
"Mục Đích Của C/O",
"Luật Áp Dụng Về C/O",
"Thủ Tục Cấp C/O",
"Biểu Mẫu C/O Và Cách Khai",
"Phí Và Lệ Phí Cấp C/O",
"Điểm Cấp Và Thời Gian Cấp C/O",
"Thông Tin Liên Hệ",
{
title: "Định Nghĩa Chung",
link: "",
},
{
title: "Mục Đích Của C/O",
link: "muc-dich",
},
{
title: "Luật Áp Dụng Về C/O",
link: "luat-ap-dung",
},
{
title: "Thủ Tục Cấp C/O",
link: "thu-tuc-cap",
},
{
title: "Biểu Mẫu C/O Và Cách Khai",
link: "bieu-mau-c-o-va-cach-khai",
},
{
title: "Phí Và Lệ Phí Cấp C/O",
link: "phi-va-le-phi-cap",
},
{
title: "Điểm Cấp Và Thời Gian Cấp C/O",
link: "diem-cap-va-thoi-gian-cap",
},
{
title: "Thông Tin Liên Hệ",
link: "thong-tin-lien-he",
},
]}
/>
......@@ -150,25 +198,33 @@ function Header() {
<MenuItem
title="Xúc tiến thương mại"
link="xuc-tien-thuong-mai"
items={[
"Hồ Sơ Thị Trường",
"Môi Trường Kinh Doanh",
"Cơ Hội Kinh Doanh",
"Hỗ Trợ Kinh Doanh",
items={[
{ title: "Hồ Sơ Thị Trường", link: "ho-so-thi-truong" },
{
title: "Môi Trường Kinh Doanh",
link: "moi-truong-kinh-doanh",
},
{ title: "Cơ Hội Kinh Doanh", link: "co-hoi-kinh-doanh" },
{ title: "Hỗ Trợ Kinh Doanh", link: "ho-tro-kinh-doanh" },
]}
/>
<MenuItem
title="Thông tin truyền thông"
link="thong-tin-truyen-thong"
items={[
"Tin VCCI",
"Tin Kinh Tế",
"Tin Doanh Nghiệp",
"Chuyên Đề",
"Thông Tin Chính Sách Và Pháp Luật",
"Ấn Phẩm",
"Thư Viện Tài Liệu",
items={[
{ title: "Tin VCCI", link: "tin-vcci" },
{ title: "Tin Kinh Tế", link: "tin-kinh-te" },
{ title: "Tin Doanh Nghiệp", link: "tin-doanh-nghiep" },
{ title: "Chuyên Đề", link: "chuyen-de" },
{
title: "Thông Tin Chính Sách Và Pháp Luật",
link: "thong-tin-chinh-sach-va-phap-luat",
},
{ title: "Ấn Phẩm", link: "an-pham" },
{ title: "Thư Viện Tài Liệu", link: "thu-vien-tai-lieu" },
]}
/>
</nav>
......
......@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main>
......
......@@ -17,7 +17,8 @@ export default function Page() {
const { data: allData } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined,
// filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters:'category@=Chủ đề'
});
return (
<div className="min-h-screen container mx-auto p-4">
......
......@@ -2,7 +2,23 @@
import { NewsItem } from '@app/dai-dien-gioi-chu/lib/types/NewsPage.type';
import Links from '@links/index'
import dayjs from 'dayjs';
import parse from 'html-react-parser'
// Helper: remove <img> tags and extract plain text from HTML
const stripImagesAndHtml = (html?: string) => {
if (!html) return ''
// remove img tags first
const withoutImgs = html.replace(/<img[^>]*>/gi, '')
// use DOMParser on client for robust extraction
if (typeof window !== 'undefined' && typeof DOMParser !== 'undefined') {
try {
const doc = new DOMParser().parseFromString(withoutImgs, 'text/html')
return doc.body.textContent || ''
} catch {
// fallback to regex
}
}
return withoutImgs.replace(/<[^>]*>/g, '')
}
function NewsContent({ news ,link}: { news: NewsItem ,link:string}) {
return (
......@@ -21,14 +37,14 @@ function NewsContent({ news ,link}: { news: NewsItem ,link:string}) {
/>
<div className="flex-1 min-w-0 pl-0 sm:pl-4">
<p className="text-primary font-semibold text-base md:text-lg hover:underline line-clamp-2 wrap-break-word hover:no-underline">
<p className="text-primary font-semibold text-base md:text-lg hover:underline line-clamp-2 wrap-break-word">
{news.title}
</p>
<div className="text-sm my-2 text-[#00AED5]">{dayjs(news.release_at).format('DD/MM/YYYY')}</div>
<div className="text-sm text-[#777] line-clamp-3">
<div className="text-sm prose tiptap">{parse(news.description)}</div>
<div className="text-sm prose tiptap">{stripImagesAndHtml(news.description)}</div>
</div>
</div>
</a>
......
export const SAMPLE_HTML = `
<div class="document">
<h1 style="font-size:18px; font-weight:700; margin-bottom:8px;">Chức năng Đại diện Người sử dụng lao động</h1>
<h1 class="text-primary" style="font-size:20px; font-weight:700; margin-bottom:12px;">Chức năng Đại diện Người sử dụng lao động</h1>
<p>Chức năng Đại diện Người sử dụng lao động (NSDLĐ):</p>
......
......@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main>
......
......@@ -4,23 +4,31 @@ import Image from "next/image";
import ListCategory from "../../components/list-category";
import { OWNER_REPRESENTATIVES_CATEGORIES } from "@constants/categories";
import ListFilter from "../../components/list-filter";
import { useGetNewsId } from '@/api/endpoints/news';
import { useGetNewsId } from "@/api/endpoints/news";
import parse from "html-react-parser";
import { useParams } from 'next/navigation'
import { GetNewsDetailResponseType } from '@lib/types/news-detail-response-data';
import { useParams } from "next/navigation";
import { GetNewsDetailResponseType } from "@lib/types/news-detail-response-data";
// ...existing code...
const Page: React.FC = () => {
const { id } = useParams()
const { data, isLoading } = useGetNewsId<GetNewsDetailResponseType>(id as string)
const { id } = useParams();
const { data, isLoading } = useGetNewsId<GetNewsDetailResponseType>(
id as string
);
return (
<div className="min-h-screen w-full container mx-auto p-4">
<div className="w-full flex flex-col gap-5">
<ListCategory categories={OWNER_REPRESENTATIVES_CATEGORIES} />
<ListCategory categories={OWNER_REPRESENTATIVES_CATEGORIES} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
<div className="pb-5 text-primary text-2xl leading-normal font-medium">
{data?.responseData?.title}
</div>
<hr className="py-2" />
<div className="p-7.5 prose tiptap overflow-hidden">
{parse(data?.responseData?.description ?? "")}
</div>
</main>
{/* Sidebar */}
......
......@@ -10,13 +10,14 @@ import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type";
import { PATHS } from "@constants/paths";
export default function Page() {
const [submitSearch] = useState("");
const [submitSearch,setsubmitSearch] = useState("");
const [page, setPage] = useState(1);
const pageSize = 5;
const { data: allData } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters: submitSearch ? `title @=${submitSearch},category@=Tin liên quan` : 'category@=Tin liên quan',
});
return (
<div className="min-h-screen container mx-auto p-4">
......@@ -45,7 +46,7 @@ export default function Page() {
{/* Sidebar */}
<aside className="space-y-6 order-first lg:order-last">
<ListFilter />
<ListFilter onSearch={setsubmitSearch}/>
<div className="bg-white border rounded-md overflow-hidden hidden lg:block">
<div className="w-full h-56 relative bg-gray-100">
......
......@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main>
......
......@@ -4,7 +4,7 @@ import ListCategory from "@app/dai-dien-gioi-chu/components/list-category";
import { EVENT_CATEGORIES } from "@constants/categories";
import EventFilter from "@app/dai-dien-gioi-chu/components/event-filter";
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import { Pagination} from "@components/base/pagination";
import { Pagination } from "@components/base/pagination";
import Image from "next/image";
import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type";
......@@ -17,19 +17,19 @@ export default function Page() {
const { data: allData } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters: submitSearch ? `title @=${submitSearch}` : 'category @=Đào tạo',
});
return (
<div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5">
<ListCategory categories={EVENT_CATEGORIES} />
<ListCategory categories={EVENT_CATEGORIES} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => (
<NewsContent key={news.id} news={news} link={`${PATHS.event}/dao-tao/${news.id}`}/>
<NewsContent key={news.id} news={news} link={`${PATHS.event}/dao-tao/${news.id}`} />
))}
<div className="w-full flex justify-center mt-4">
......
......@@ -15,7 +15,7 @@ export default function Page() {
return (
<div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5">
<ListCategory categories={EVENT_CATEGORIES} />
<ListCategory categories={EVENT_CATEGORIES} />
</div>
</div>
);
......
......@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main>
......
......@@ -5,7 +5,7 @@ import { EVENT_CATEGORIES } from "@constants/categories";
import EventFilter from "@app/dai-dien-gioi-chu/components/event-filter";
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
// ...existing code...
import { Pagination} from "@components/base/pagination";
import { Pagination } from "@components/base/pagination";
import Image from "next/image";
import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type";
......@@ -18,19 +18,19 @@ export default function Page() {
const { data: allData } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters: submitSearch ? `title @=${submitSearch}` : 'category @=Sự kiện',
});
return (
<div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5">
<ListCategory categories={EVENT_CATEGORIES} />
<ListCategory categories={EVENT_CATEGORIES} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => (
<NewsContent key={news.id} news={news} link={`${PATHS.event}/su-kien/${news.id}`}/>
<NewsContent key={news.id} news={news} link={`${PATHS.event}/su-kien/${news.id}`} />
))}
<div className="w-full flex justify-center mt-4">
......
......@@ -16,7 +16,7 @@ export default function Page() {
const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters: submitSearch ? `title @=${submitSearch}` : `category @=Kết nối hội viên`,
});
return (
<div className="min-h-screen container mx-auto pb-4">
......@@ -30,24 +30,24 @@ export default function Page() {
<CardNews key={news.id} news={news} />
))}
<div className='w-full flex justify-center mt-4'>
<Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() =>
setPage(
Math.min(
Number(allData?.responseData.totalPages ?? 1),
page + 1
)
)
}
/>
</div>
</div>
</main>
<div className='w-full flex justify-center mt-4'>
<Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() =>
setPage(
Math.min(
Number(allData?.responseData.totalPages ?? 1),
page + 1
)
)
}
/>
</div>
</div>
</main>
{/* Sidebar */}
<aside className="space-y-6">
......
......@@ -16,7 +16,7 @@ export default function Page() {
const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters: submitSearch ? `title @=${submitSearch}` : 'category @=Tin hội viên',
})
return (
<div className="min-h-screen container mx-auto pb-4">
......
"use client";
import React, { useState, Suspense } from "react";
import ListCategory from "@app/dai-dien-gioi-chu/components/list-category";
import { OWNER_REPRESENTATIVES_CATEGORIES } from "@constants/categories";
import ListFilter from "@app/dai-dien-gioi-chu/components/list-filter";
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import { Pagination } from "@components/base/pagination";
import Image from "next/image";
import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type";
import { PATHS } from "@constants/paths";
import { useSearchParams } from 'next/navigation'
function SearchContent() {
const [page, setPage] = useState(1);
const searchParams = useSearchParams()
const query = searchParams.get('q') //
const pageSize = 5;
const { data: allData } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: query ? `title @=${query}` : undefined,
});
return (
<div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5">
<div className="border-t border-gray-200 bg-white p-2.5">
<div className="w-full px-4 sm:px-6 lg:px-8">
<div className="py-3">
<h1 className="text-md md:text-lg font-semibold leading-6 text-gray-900">
{" "}
Search Results for: {query}
</h1>
</div>
</div>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => (
<NewsContent
key={news.id}
news={news}
link={`${PATHS.mediaInformation}/tin-vcci/${news.id}`}
/>
))}
<div className="w-full flex justify-center mt-4">
<Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() =>
setPage(
Math.min(
Number(allData?.responseData.totalPages ?? 1),
page + 1
)
)
}
/>
</div>
</div>
</main>
{/* Sidebar */}
<aside className="space-y-6 order-first lg:order-last">
<div className="bg-white border rounded-md overflow-hidden hidden lg:block">
<div className="w-full h-62 relative bg-gray-100">
<Image
src="/banner.webp"
alt="Quảng cáo"
fill
className="object-cover"
/>
</div>
</div>
</aside>
</div>
</div>
</div>
);
}
export default function Page() {
return (
<Suspense fallback={
<div className="min-h-screen container mx-auto p-4 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-[#063e8e] mx-auto"></div>
<p className="mt-4 text-gray-600">Loading search results...</p>
</div>
</div>
}>
<SearchContent />
</Suspense>
);
}
export type SiteMapItem = {
label: string;
url: string;
children?: SiteMapItem[];
};
export const MOCK_SITEMAP: SiteMapItem[] = [
{ label: "Trang chủ", url: "/homepage", children: [] },
{
label: "Giới thiệu",
url: "/gioi-thieu",
children: [
{ label: "Về VCCI-HCM", url: "/gioi-thieu/ve-vcci-hcm", children: [] },
{ label: "Chức năng và nhiệm vụ", url: "/gioi-thieu/chuc-nang-nhiem-vu", children: [] },
{ label: "Sơ đồ tổ chức", url: "/gioi-thieu/so-do-to-chuc", children: [] },
{ label: "Dịch vụ cung cấp", url: "/gioi-thieu/dich-vu-cung-cap", children: [] },
],
},
{
label: "Hội viên",
url: "/hoi-vien",
children: [
{ label: "Lợi ích của hội viên VCCI", url: "/hoi-vien/loi-ich-hoi-vien", children: [] },
{ label: "Đăng ký hội viên", url: "/hoi-vien/dang-ky-hoi-vien", children: [] },
{ label: "Kết nối hội viên", url: "/hoi-vien/ket-noi-hoi-vien", children: [] },
{ label: "Tin hội viên", url: "/hoi-vien/tin-hoi-vien", children: [] },
],
},
{
label: "Hoạt động",
url: "/hoat-dong",
children: [
{ label: "Sự kiện", url: "/hoat-dong/su-kien", children: [] },
{ label: "Đào tạo", url: "/hoat-dong/dao-tao", children: [] },
],
},
{
label: "Xuất xứ hàng hóa",
url: "/xuat-xu-hang-hoa",
children: [
{ label: "Định nghĩa chung", url: "/xuat-xu-hang-hoa", children: [] },
{ label: "Mục đích của C/O", url: "/xuat-xu-hang-hoa/muc-dich-co", children: [] },
{ label: "Luật áp dụng về C/O", url: "/xuat-xu-hang-hoa/luat-ap-dung-co", children: [] },
{ label: "Thủ tục cấp C/O", url: "/xuat-xu-hang-hoa/thu-tuc-cap-co", children: [] },
{ label: "Biểu mẫu C/O và cách khai", url: "/xuat-xu-hang-hoa/bieu-mau-co", children: [] },
{ label: "Phí và lệ phí cấp C/O", url: "/xuat-xu-hang-hoa/phi-le-phi-cap-co", children: [] },
{ label: "Điểm cấp và thời gian cấp C/O", url: "/xuat-xu-hang-hoa/diem-cap-thoi-gian", children: [] },
{ label: "Thông tin liên hệ", url: "/xuat-xu-hang-hoa/lien-he", children: [] },
],
},
{
label: "Đại diện giới chủ",
url: "/dai-dien-gioi-chu",
children: [
{ label: "Chức năng đại diện người sử dụng lao động", url: "/dai-dien-gioi-chu/chuc-nang", children: [] },
{ label: "Sự kiện - tập huấn NSDLĐ", url: "/dai-dien-gioi-chu/su-kien-tap-huan", children: [] },
{ label: "Tin liên quan", url: "/dai-dien-gioi-chu/tin-lien-quan", children: [] },
{ label: "Chủ đề", url: "/dai-dien-gioi-chu/chu-de", children: [] },
],
},
{
label: "Xúc tiến thương mại",
url: "/xuc-tien-thuong-mai",
children: [
{ label: "Hồ sơ thị trường", url: "/xuc-tien-thuong-mai/ho-so-thi-truong", children: [] },
{ label: "Môi trường kinh doanh", url: "/xuc-tien-thuong-mai/doi-song-kinh-doanh", children: [] },
{ label: "Cơ hội kinh doanh", url: "/xuc-tien-thuong-mai/co-hoi-kinh-doanh", children: [] },
{ label: "Hỗ trợ kinh doanh", url: "/xuc-tien-thuong-mai/ho-tro-kinh-doanh", children: [] },
],
},
{
label: "Thông tin truyền thông",
url: "/thong-tin-truyen-thong",
children: [
{ label: "Tin VCCI", url: "/thong-tin-truyen-thong/tin-vcci", children: [] },
{ label: "Tin kinh tế", url: "/thong-tin-truyen-thong/tin-kinh-te", children: [] },
{ label: "Tin doanh nghiệp", url: "/thong-tin-truyen-thong/tin-doanh-nghiep", children: [] },
{ label: "Chuyên đề", url: "/thong-tin-truyen-thong/chuyen-de", children: [] },
{ label: "Thông tin chính sách và pháp luật", url: "/thong-tin-truyen-thong/chinh-sach-phap-luat", children: [] },
{ label: "Ấn phẩm", url: "/thong-tin-truyen-thong/an-pham", children: [] },
{ label: "Thư viện tài liệu", url: "/thong-tin-truyen-thong/thu-vien-tai-lieu", children: [] },
],
},
];
export default MOCK_SITEMAP;
"use client";
import React from "react";
import { MOCK_SITEMAP } from "./_lib/mock-data";
import Link from "next/link";
function SiteMapPage() {
const homepage = MOCK_SITEMAP[0];
const sections = MOCK_SITEMAP.slice(1);
return (
<div className="min-h-screen bg-gray-50 py-12">
<div className="container mx-auto px-4">
<h1 className="text-3xl font-bold text-center mb-12 text-[#063e8e]">
SƠ ĐỒ TRANG WEB
</h1>
{/* Sitemap Structure */}
<div className="relative flex flex-col items-center">
{/* Homepage - Top Level */}
<div className="relative mb-20">
<Link
href={homepage.url}
className="block bg-[#063e8e] text-white px-8 py-4 rounded-lg font-semibold text-center hover:bg-[#0a4fb5] transition shadow-lg min-w-[200px]"
>
{homepage.label.toUpperCase()}
</Link>
{/* Vertical line from homepage down */}
<div className="absolute left-1/2 -translate-x-1/2 top-full h-16 w-0.5 bg-gray-600"></div>
</div>
{/* Main Sections - Second Level */}
<div className="relative w-full max-w-[1400px]">
{/* Horizontal line connecting all sections */}
<div className="absolute top-0 left-[7%] right-[7%] h-0.5 bg-gray-600 z-0"></div>
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-7 gap-6 relative pt-4">
{sections.map((section, idx) => (
<div key={idx} className="relative flex flex-col items-center">
{/* Vertical line from horizontal bar down to section */}
<div className="absolute -top-4 left-1/2 -translate-x-1/2 h-4 w-0.5 bg-gray-600 z-10"></div>
{/* Section Box */}
<div className="relative z-20">
<Link
href={section.url}
className="flex bg-[#063e8e] text-white px-4 py-3 rounded-md font-medium text-center hover:bg-[#0a4fb5] transition shadow-md w-full text-sm min-h-20 items-center justify-center"
>
<span className="leading-tight">{section.label.toUpperCase()}</span>
</Link>
{/* Vertical line from section down to children */}
{section.children && section.children.length > 0 && (
<div className="absolute left-1/2 -translate-x-1/2 top-full h-6 w-0.5 bg-gray-600 z-10"></div>
)}
</div>
{/* Children - Third Level */}
{section.children && section.children.length > 0 && (
<div className="mt-6 flex flex-col gap-3 w-full relative z-20">
{section.children.map((child, childIdx) => (
<div key={childIdx} className="relative">
{/* Vertical line connecting child to parent */}
{childIdx === 0 ? (
// First child connects to the line from parent
<div className="absolute left-1/2 -translate-x-1/2 -top-6 h-6 w-0.5 bg-gray-600"></div>
) : (
// Other children connect to the vertical spine
<div className="absolute left-1/2 -translate-x-1/2 -top-[calc(1.5rem+0.375rem)] bottom-1/2 w-0.5 bg-gray-600"></div>
)}
{/* Horizontal line from spine to child box */}
<div className="absolute left-1/2 top-1/2 -translate-y-1/2 w-1/2 h-0.5 bg-gray-600 -translate-x-full"></div>
<Link
href={child.url}
className="block bg-gray-400 text-white px-3 py-2.5 rounded text-xs font-medium text-center hover:bg-gray-500 transition shadow-sm leading-tight relative z-10"
>
{child.label.toUpperCase()}
</Link>
</div>
))}
</div>
)}
</div>
))}
</div>
</div>
</div>
</div>
<style jsx>{`
@media (max-width: 768px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
`}</style>
</div>
);
}
export default SiteMapPage;
......@@ -4,7 +4,7 @@ import ListCategory from "@app/dai-dien-gioi-chu/components/list-category";
import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories";
import EventFilter from "@app/dai-dien-gioi-chu/components/event-filter";
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import { Pagination} from "@components/base/pagination";
import { Pagination } from "@components/base/pagination";
import Image from "next/image";
import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type";
......@@ -16,12 +16,12 @@ export default function Page() {
const { data: allData } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters: submitSearch ? `title @=${submitSearch}` : 'category @=Ấn phẩm',
});
return (
<div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5">
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
......
......@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main>
......
......@@ -5,7 +5,7 @@ import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories";
// ...existing code...
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import ListFilter from "@app/dai-dien-gioi-chu/components/list-filter";
import { Pagination} from "@components/base/pagination";
import { Pagination } from "@components/base/pagination";
import Image from "next/image";
import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type";
......@@ -17,24 +17,27 @@ export default function Page() {
const { data: allData } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters: submitSearch ? `title @=${submitSearch}` : 'category @=Chuyên đề',
});
return (
<div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5">
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => (
<NewsContent
key={news.id}
news={news}
link={`${PATHS.mediaInformation}/chuyen-de/${news.id}`}
/>
))}
{allData?.responseData.rows.length === 0 ? (
<p className="text-center py-4">Không có dữ liệu</p>
) : (
allData?.responseData.rows.map((news) => (
<NewsContent
key={news.id}
news={news}
link={`${PATHS.mediaInformation}/chuyen-de/${news.id}`}
/>
)))}
<div className="w-full flex justify-center mt-4">
<Pagination
......
......@@ -15,7 +15,7 @@ export default function Page() {
return (
<div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5">
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
</div>
</div>
......
......@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main>
......
......@@ -5,7 +5,7 @@ import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories";
// ...existing code...
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import ListFilter from "@app/dai-dien-gioi-chu/components/list-filter";
import { Pagination} from "@components/base/pagination";
import { Pagination } from "@components/base/pagination";
import Image from "next/image";
import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type";
......@@ -18,24 +18,27 @@ export default function Page() {
const { data: allData } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters: submitSearch ? `title @=${submitSearch}` : 'category @=Thông tin chính sách và pháp luật',
});
return (
<div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5">
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => (
<NewsContent
key={news.id}
news={news}
link={`${PATHS.mediaInformation}/thong-tin-chinh-sach-va-phap-luat/${news.id}`}
/>
))}
{allData?.responseData.rows.length === 0 ? (
<p className="text-center py-4">Không có dữ liệu</p>
) : (
allData?.responseData.rows.map((news) => (
<NewsContent
key={news.id}
news={news}
link={`${PATHS.mediaInformation}/thong-tin-chinh-sach-va-phap-luat/${news.id}`}
/>
)))}
<div className="w-full flex justify-center mt-4">
<Pagination
......
......@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main>
......
......@@ -4,7 +4,7 @@ import ListCategory from "@app/dai-dien-gioi-chu/components/list-category";
import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories";
// ...existing code...
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import { Pagination} from "@components/base/pagination";
import { Pagination } from "@components/base/pagination";
import ListFilter from "@app/dai-dien-gioi-chu/components/list-filter";
import Image from "next/image";
import { useGetNews } from "@api/endpoints/news";
......@@ -18,24 +18,27 @@ export default function Page() {
const { data: allData } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters: submitSearch ? `title @=${submitSearch}` : 'category @=Thư viện tài liệu',
});
return (
<div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5">
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => (
<NewsContent
key={news.id}
news={news}
link={`${PATHS.mediaInformation}/thu-vien-tai-lieu/${news.id}`}
/>
))}
{allData?.responseData.rows.length === 0 ? (
<p className="text-center py-4">Không có dữ liệu</p>
) : (
allData?.responseData.rows.map((news) => (
<NewsContent
key={news.id}
news={news}
link={`${PATHS.mediaInformation}/thu-vien-tai-lieu/${news.id}`}
/>
)))}
<div className="w-full flex justify-center mt-4">
<Pagination
......
......@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main>
......
......@@ -5,7 +5,7 @@ import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories";
// ...existing code...
import ListFilter from "@app/dai-dien-gioi-chu/components/list-filter";
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import { Pagination} from "@components/base/pagination";
import { Pagination } from "@components/base/pagination";
import Image from "next/image";
import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type";
......@@ -18,24 +18,27 @@ export default function Page() {
const { data: allData } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters: submitSearch ? `title @=${submitSearch}` : 'category @=Tin doanh nghiệp',
});
return (
<div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5">
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => (
<NewsContent
key={news.id}
news={news}
link={`${PATHS.mediaInformation}/tin-doanh-nghiep/${news.id}`}
/>
))}
{allData?.responseData.rows.length === 0 ? (
<p className="text-center py-4">Không có dữ liệu</p>
) : (
allData?.responseData.rows.map((news) => (
<NewsContent
key={news.id}
news={news}
link={`${PATHS.mediaInformation}/tin-doanh-nghiep/${news.id}`}
/>
)))}
<div className="w-full flex justify-center mt-4">
<Pagination
......
......@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main>
......
......@@ -4,7 +4,7 @@ import ListCategory from "@app/dai-dien-gioi-chu/components/list-category";
import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories";
// ...existing code...
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import { Pagination} from "@components/base/pagination";
import { Pagination } from "@components/base/pagination";
import Image from "next/image";
import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type";
......@@ -17,24 +17,27 @@ export default function Page() {
const { data: allData } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters: submitSearch ? `title @=${submitSearch}` : 'category @=Tin kinh tế',
});
return (
<div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5">
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => (
<NewsContent
key={news.id}
news={news}
link={`${PATHS.mediaInformation}/tin-kinh-te/${news.id}`}
/>
))}
{allData?.responseData.rows.length === 0 ? (
<p className="text-center py-4">Không có dữ liệu</p>
) : (
allData?.responseData.rows.map((news) => (
<NewsContent
key={news.id}
news={news}
link={`${PATHS.mediaInformation}/tin-kinh-te/${news.id}`}
/>
)))}
<div className="w-full flex justify-center mt-4">
<Pagination
......
......@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main>
......
......@@ -6,13 +6,13 @@ import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories";
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import ListFilter from "@app/dai-dien-gioi-chu/components/list-filter";
import EventCalendar from "@app/dai-dien-gioi-chu/components/event-calendar";
import { Pagination} from "@components/base/pagination";
import { Pagination } from "@components/base/pagination";
import Image from "next/image";
import { useGetNews } from "@api/endpoints/news";
import { PATHS } from "@constants/paths";
import { GetNewsResponseType } from "@api/types/NewsPage.type";
export default function Page() {
const [submitSearch] = useState("");
const [submitSearch, setSubmitSearch] = useState("");
const [page, setPage] = useState(1);
const pageSize = 5;
......@@ -24,19 +24,23 @@ export default function Page() {
return (
<div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5">
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => (
<NewsContent
key={news.id}
news={news}
link={`${PATHS.mediaInformation}/tin-vcci/${news.id}`}
/>
))}
{allData?.responseData.rows.length === 0 ? (
<p className="text-center py-4">Không có dữ liệu</p>
) : (
allData?.responseData.rows.map((news) => (
<NewsContent
key={news.id}
news={news}
link={`${PATHS.mediaInformation}/tin-vcci/${news.id}`}
/>
)))
}
<div className="w-full flex justify-center mt-4">
<Pagination
......@@ -52,8 +56,8 @@ export default function Page() {
{/* Sidebar */}
<aside className="space-y-6">
<ListFilter />
<EventCalendar/>
<ListFilter onSearch={setSubmitSearch} />
<EventCalendar />
<div className="bg-white border rounded-md overflow-hidden">
<div className="w-full h-56 relative bg-gray-100">
<Image
......
......@@ -6,7 +6,7 @@ import Image from "next/image";
export default function page() {
return (
<div className="bg-[#f6f6f6]">
<div className="max-w-[1200px] m-auto flex flex-col gap-5 mb-[50px]">
<div className="container m-auto flex flex-col gap-5 mb-[50px]">
<div className="border-[#e5e7f2] border-[1px]">
<ListCategory />
</div>
......
......@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main>
......
......@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main>
......
......@@ -17,7 +17,8 @@ export default function Page() {
const { data: allData } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined,
// filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters:'category@=Cơ hội kinh doanh'
});
return (
<div className="min-h-screen container mx-auto p-4">
......
......@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main>
......
......@@ -16,7 +16,8 @@ export default function Page() {
const { data: allData } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined,
// filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters:'category@=Hỗ trợ kinh doanh'
});
return (
<div className="min-h-screen container mx-auto p-4">
......
......@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main>
......
......@@ -17,7 +17,8 @@ export default function Page() {
const { data: allData } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined,
// filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters:'category@=Môi trường kinh doanh'
});
return (
<div className="min-h-screen container mx-auto p-4">
......
......@@ -2,8 +2,24 @@
import { NewsItem } from '@app/dai-dien-gioi-chu/lib/types/NewsPage.type';
import Links from '@links/index'
import dayjs from 'dayjs';
import parse from 'html-react-parser'
function NewsContent({ news, link }: { news: NewsItem, link: string }) {
// Helper: remove <img> tags and extract plain text from HTML
const stripImagesAndHtml = (html?: string) => {
if (!html) return ''
// remove img tags first
const withoutImgs = html.replace(/<img[^>]*>/gi, '')
// use DOMParser on client for robust extraction
if (typeof window !== 'undefined' && typeof DOMParser !== 'undefined') {
try {
const doc = new DOMParser().parseFromString(withoutImgs, 'text/html')
return doc.body.textContent || ''
} catch {
// fallback to regex
}
}
return withoutImgs.replace(/<[^>]*>/g, '')
}
function NewsContent({ news ,link}: { news: NewsItem ,link:string}) {
return (
<a
......@@ -14,20 +30,21 @@ function NewsContent({ news, link }: { news: NewsItem, link: string }) {
src={`${Links.imageEndpoint}${news.thumbnail}`}
alt={news.title}
className="w-full sm:w-56 md:w-64 h-40 md:h-36 object-cover shrink-0"
onError={(e) => {
e.currentTarget.src = "/img-error.png"
}}
onError={(e) => {
e.currentTarget.src = "/img-error.png"
}}
/>
<div className="flex-1 min-w-0 pl-0 sm:pl-4">
<p className="text-primary font-semibold text-base md:text-lg line-clamp-2 wrap-break-word hover:no-underline">
<p className="text-primary font-semibold text-base md:text-lg hover:underline line-clamp-2 wrap-break-word">
{news.title}
</p>
<div className="text-sm my-2 text-[#00AED5]">{dayjs(news.release_at).format('DD/MM/YYYY')}</div>
<div className="text-sm text-[#777] line-clamp-3">
<div className="text-sm prose tiptap">{parse(news.description)}</div>
<div className="text-sm prose tiptap">{stripImagesAndHtml(news.description)}</div>
</div>
</div>
</a>
......
......@@ -4,24 +4,47 @@ import { AppEditorContentProps } from './AppEditorContent.type';
import './AppEditorContent.css';
const AppEditorContent: FC<AppEditorContentProps> = ({ value = '', className = '' }) => {
const transform = (node: DOMNode): JSX.Element | null => {
if (node instanceof Element && node.tagName === 'strong') {
const transform = (node: DOMNode): JSX.Element | string | undefined | null => {
// 1. Xử lý Text Node
if (node instanceof Text) {
return node.data;
}
if (!(node instanceof Element)) return undefined;
const tagName = node.tagName.toLowerCase();
// ✅ FIX LỖI: Ép kiểu (as DOMNode) để Type 'ChildNode' khớp với 'DOMNode'
const children = node.children
? node.children.map(child => transform(child as DOMNode))
: [];
// --- LOGIC XỬ LÝ THEO YÊU CẦU ---
// 2. ✅ Xóa thẻ <img>
if (tagName === 'img') {
return <></>;
}
// 3. ✅ Xóa thẻ <a> nhưng giữ lại nội dung
if (tagName === 'a') {
// Trả về children đã được xử lý (làm phẳng thẻ <a>)
return <>{children}</>;
}
// 4. ✅ Xử lý thẻ <strong>
if (tagName === 'strong') {
return (
<strong className="custom-strong">
{node.children && Array.isArray(node.children)
? node.children.map((child, index) => {
if (typeof child === 'string') {
return child;
} else if (child instanceof Text) {
return child.data;
}
return null;
})
: null}
{children}
</strong>
);
}
return null;
// 5. ✅ Render các thẻ HTML khác (Fallback)
// Trả về undefined để thư viện tự động render các thẻ còn lại (p, div, br,...)
return undefined;
};
return (
......@@ -31,4 +54,4 @@ const AppEditorContent: FC<AppEditorContentProps> = ({ value = '', className = '
);
};
export default AppEditorContent;
export default AppEditorContent;
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment