Commit 8da75a01 authored by Văn Hoàng's avatar Văn Hoàng

Merge branch 'update/dynamic_page' into 'develop-hiea'

fix/dynamic_page and route

See merge request !44
parents 638751c0 36bdedc1
...@@ -15,6 +15,12 @@ const nextConfig: NextConfig = { ...@@ -15,6 +15,12 @@ const nextConfig: NextConfig = {
port: "", port: "",
pathname: "/wp-content/uploads/**", pathname: "/wp-content/uploads/**",
}, },
{
protocol: "http",
hostname: "103.72.98.149",
port: "7041",
pathname: "/images/**",
},
], ],
}, },
}; };
......
...@@ -44,8 +44,8 @@ const orvalConfig = async () => { ...@@ -44,8 +44,8 @@ const orvalConfig = async () => {
usePrefetch: true, usePrefetch: true,
// useSuspenseQuery: true, // useSuspenseQuery: true,
options: { options: {
retry: 3, retry: 2,
retryDelay: 1000, retryDelay: 500,
} }
}, },
mutator: { mutator: {
......
...@@ -72,6 +72,7 @@ ...@@ -72,6 +72,7 @@
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"axios": "^1.13.1", "axios": "^1.13.1",
"baseline-browser-mapping": "^2.9.0",
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "16.0.1", "eslint-config-next": "16.0.1",
"orval": "8.0.0-rc.0", "orval": "8.0.0-rc.0",
...@@ -79,4 +80,4 @@ ...@@ -79,4 +80,4 @@
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5" "typescript": "^5"
} }
} }
\ No newline at end of file
...@@ -189,6 +189,9 @@ importers: ...@@ -189,6 +189,9 @@ importers:
axios: axios:
specifier: ^1.13.1 specifier: ^1.13.1
version: 1.13.1 version: 1.13.1
baseline-browser-mapping:
specifier: ^2.9.0
version: 2.9.0
eslint: eslint:
specifier: ^9 specifier: ^9
version: 9.38.0(jiti@2.6.1) version: 9.38.0(jiti@2.6.1)
...@@ -1986,8 +1989,8 @@ packages: ...@@ -1986,8 +1989,8 @@ packages:
balanced-match@1.0.2: balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
baseline-browser-mapping@2.8.20: baseline-browser-mapping@2.9.0:
resolution: {integrity: sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==} resolution: {integrity: sha512-Mh++g+2LPfzZToywfE1BUzvZbfOY52Nil0rn9H1CPC5DJ7fX+Vir7nToBeoiSbB1zTNeGYbELEvJESujgGrzXw==}
hasBin: true hasBin: true
brace-expansion@1.1.12: brace-expansion@1.1.12:
...@@ -5793,7 +5796,7 @@ snapshots: ...@@ -5793,7 +5796,7 @@ snapshots:
balanced-match@1.0.2: {} balanced-match@1.0.2: {}
baseline-browser-mapping@2.8.20: {} baseline-browser-mapping@2.9.0: {}
brace-expansion@1.1.12: brace-expansion@1.1.12:
dependencies: dependencies:
...@@ -5810,7 +5813,7 @@ snapshots: ...@@ -5810,7 +5813,7 @@ snapshots:
browserslist@4.27.0: browserslist@4.27.0:
dependencies: dependencies:
baseline-browser-mapping: 2.8.20 baseline-browser-mapping: 2.9.0
caniuse-lite: 1.0.30001751 caniuse-lite: 1.0.30001751
electron-to-chromium: 1.5.243 electron-to-chromium: 1.5.243
node-releases: 2.0.26 node-releases: 2.0.26
......
...@@ -17,16 +17,16 @@ const Banner = () => { ...@@ -17,16 +17,16 @@ const Banner = () => {
slidesPerView={1} slidesPerView={1}
onSwiper={(s) => (swiperRef.current = s)} onSwiper={(s) => (swiperRef.current = s)}
> >
<SwiperSlide> {/* <SwiperSlide>
<ImageNext <ImageNext
src="https://vcci-hcm.org.vn/wp-content/uploads/2025/10/1.1.-Hero-Banner-CEO-2025-Bi-Sai-Nam-2025-Nhe-2560x720-Px.jpg.webp" src="/1.png"
alt="Banner" alt="Banner"
width={2560} width={2560}
height={720} height={720}
sizes="100vw" sizes="100vw"
className="w-full h-[200px] sm:h-[300px] md:h-[400px] lg:h-[500px] object-cover" className="w-full h-[200px] sm:h-[300px] md:h-[400px] lg:h-[500px] object-cover"
/> />
</SwiperSlide> </SwiperSlide> */}
<SwiperSlide> <SwiperSlide>
<ImageNext <ImageNext
src="https://vcci-hcm.org.vn/wp-content/uploads/2022/07/Landscape-HCM_3-01.png" src="https://vcci-hcm.org.vn/wp-content/uploads/2022/07/Landscape-HCM_3-01.png"
......
...@@ -10,12 +10,12 @@ const EventsCalendar = () => { ...@@ -10,12 +10,12 @@ const EventsCalendar = () => {
<h2 className="text-[18px] sm:text-[20px] font-bold uppercase text-[#e8c518]"> <h2 className="text-[18px] sm:text-[20px] font-bold uppercase text-[#e8c518]">
Lịch sự kiện Lịch sự kiện
</h2> </h2>
<Link {/* <Link
href="/hoat-dong/su-kien" href="#"
className="text-[#e8c518] hover:underline text-sm sm:text-base" className="text-[#e8c518] hover:underline text-sm sm:text-base"
> >
<ChevronsRight /> <ChevronsRight />
</Link> </Link> */}
</div> </div>
<hr className="border-[#e8c518] mb-4" /> <hr className="border-[#e8c518] mb-4" />
<EventCalendar /> <EventCalendar />
......
...@@ -8,8 +8,7 @@ import ImageNext from "@/components/shared/image-next"; ...@@ -8,8 +8,7 @@ import ImageNext from "@/components/shared/image-next";
function CardEvent({ event }: { event: EventItem }) { function CardEvent({ event }: { event: EventItem }) {
return ( return (
<Link <Link
// href={`hoat-dong/su-kien/${event.id}`} href={`/hoat-dong/su-kien/${event.id}`}
href={`#`}
className='flex flex-row gap-2 mb-2 sm:gap-3 sm:mb-3 p-2 sm:p-3 border border-gray-200 bg-white rounded-md' className='flex flex-row gap-2 mb-2 sm:gap-3 sm:mb-3 p-2 sm:p-3 border border-gray-200 bg-white rounded-md'
> >
<ImageNext <ImageNext
......
...@@ -18,7 +18,7 @@ function Events() { ...@@ -18,7 +18,7 @@ function Events() {
<h2 className="text-[18px] sm:text-[20px] font-bold uppercase text-[#e8c518]"> <h2 className="text-[18px] sm:text-[20px] font-bold uppercase text-[#e8c518]">
Sự kiện sắp diễn ra Sự kiện sắp diễn ra
</h2> </h2>
<Link href="#" className="text-[#e8c518] text-sm sm:text-base"> <Link href="/hoat-dong/su-kien" className="text-[#e8c518] text-sm sm:text-base">
<ChevronsRight /> <ChevronsRight />
</Link> </Link>
</div> </div>
...@@ -34,7 +34,7 @@ function Events() { ...@@ -34,7 +34,7 @@ function Events() {
{data?.responseData.rows.slice(0, 1).map((event: EventItem) => ( {data?.responseData.rows.slice(0, 1).map((event: EventItem) => (
<Link <Link
key={event.id} key={event.id}
href={`#`} href={`/hoat-dong/su-kien/${event.id}`}
className="flex flex-col w-full md:w-1/2 min-h-[180px] sm:min-h-[220px] gap-3 mb-3 border border-gray-200 bg-white rounded-md p-3" className="flex flex-col w-full md:w-1/2 min-h-[180px] sm:min-h-[220px] gap-3 mb-3 border border-gray-200 bg-white rounded-md p-3"
> >
<div className="w-full aspect-3/2 overflow-hidden"> <div className="w-full aspect-3/2 overflow-hidden">
......
...@@ -27,7 +27,7 @@ const Members = () => { ...@@ -27,7 +27,7 @@ const Members = () => {
Hội viên tiêu biểu Hội viên tiêu biểu
</h2> </h2>
<Link <Link
href="/danh-ba-hoi-vien" href="#"
className="text-[#063e8e] hover:underline text-sm font-medium" className="text-[#063e8e] hover:underline text-sm font-medium"
> >
<ChevronsRight /> <ChevronsRight />
...@@ -43,9 +43,9 @@ const Members = () => { ...@@ -43,9 +43,9 @@ const Members = () => {
slidesPerView={6} slidesPerView={6}
spaceBetween={16} spaceBetween={16}
breakpoints={{ breakpoints={{
0: { slidesPerView: 2, spaceBetween: 10 }, 0: { slidesPerView: 3, spaceBetween: 10 },
640: { slidesPerView: 3, spaceBetween: 16 }, 640: { slidesPerView: 4, spaceBetween: 16 },
1024: { slidesPerView: 3, spaceBetween: 24 }, 1024: { slidesPerView: 6, spaceBetween: 24 },
}} }}
className="partner-swiper" className="partner-swiper"
> >
......
...@@ -28,12 +28,12 @@ const News = () => { ...@@ -28,12 +28,12 @@ const News = () => {
> >
Tin tức Tin tức
</Link> </Link>
<Link {/* <Link
href="/thong-tin-truyen-thong/tin-vcci/" href="/thong-tin-truyen-thong/tin-vcci/"
className="text-[#063e8e] text-sm sm:text-base" className="text-[#063e8e] text-sm sm:text-base"
> >
<ChevronsRight /> <ChevronsRight />
</Link> </Link> */}
</div> </div>
<hr className="border-[#063e8e] mb-4" /> <hr className="border-[#063e8e] mb-4" />
......
...@@ -14,7 +14,7 @@ function QuickLinks() { ...@@ -14,7 +14,7 @@ function QuickLinks() {
<div> <div>
<Link <Link
className="text-[#363636]" className="text-[#363636]"
href="https://vcci-hcm.org.vn/lien-ket-nhanh/cam-nang-huong-dan-dau-tu-kinh-doanh-tai-viet-nam-2023/" href="#"
> >
🔗 Cẩm nang hướng dẫn đầu tư kinh doanh tại Việt Nam 🔗 Cẩm nang hướng dẫn đầu tư kinh doanh tại Việt Nam
</Link> </Link>
...@@ -22,7 +22,7 @@ function QuickLinks() { ...@@ -22,7 +22,7 @@ function QuickLinks() {
<div> <div>
<Link <Link
className="text-[#363636]" className="text-[#363636]"
href="https://vcci-hcm.org.vn/lien-ket-nhanh/doanh-nghiep-kien-nghi-ve-chinh-sach-va-phap-luat/" href="#"
> >
🔗 Doanh nghiệp kiến nghị về chính sách và pháp luật 🔗 Doanh nghiệp kiến nghị về chính sách và pháp luật
</Link> </Link>
......
...@@ -29,12 +29,12 @@ const VideoAndPartners = () => { ...@@ -29,12 +29,12 @@ const VideoAndPartners = () => {
<div className="flex flex-col md:flex-row gap-4 md:gap-6"> <div className="flex flex-col md:flex-row gap-4 md:gap-6">
{[ {[
{ {
src: "https://www.youtube.com/embed/J0Iz0iGuAXY", src: "https://www.youtube.com/embed/U35yP_yH1dA",
title: "VCCI-HCM 2024 IN REVIEW (ENGLISH VERSION)", title: "Chào mừng đến với MeU Solutions – Biến giấc mơ chuyển đổi số của Daonh nghiệp bạn thành hiện thực 🚀",
}, },
{ {
src: "https://www.youtube.com/embed/_OnnGWv2ehM", src: "https://www.youtube.com/embed/yQaLiPMemcg",
title: "Hội nghị Hội viên VCCI - Gala Mừng Xuân Ất Tỵ 2025", title: "MEU SOLUTIONS - 9 Năm Đồng Hành Cùng Công Nghệ Việt",
}, },
].map((video, i) => ( ].map((video, i) => (
<div key={i} className="w-full md:w-1/2"> <div key={i} className="w-full md:w-1/2">
...@@ -76,9 +76,9 @@ const VideoAndPartners = () => { ...@@ -76,9 +76,9 @@ const VideoAndPartners = () => {
slidesPerView={3} slidesPerView={3}
spaceBetween={16} spaceBetween={16}
breakpoints={{ breakpoints={{
0: { slidesPerView: 3, spaceBetween: 10, grid: { rows: 1 } }, 0: { slidesPerView: 3, spaceBetween: 10, grid: { rows: 2 } },
640: { slidesPerView: 3, spaceBetween: 16 }, 640: { slidesPerView: 3, spaceBetween: 16, grid: { rows: 2 } },
1024: { slidesPerView: 3, spaceBetween: 24 }, 1024: { slidesPerView: 3, spaceBetween: 24, grid: { rows: 2 } },
}} }}
className="partner-swiper" className="partner-swiper"
> >
......
...@@ -21,7 +21,7 @@ const Page = () => { ...@@ -21,7 +21,7 @@ const Page = () => {
{/* contents */} {/* contents */}
<div className="container mx-auto px-3 sm:px-6 lg:px-10 space-y-12"> <div className="container mx-auto px-3 sm:px-6 lg:px-10 space-y-12">
<FeaturedNews /> <FeaturedNews />
<div> {/* <div>
<Link href="https://hardwaretools.com.vn/"> <Link href="https://hardwaretools.com.vn/">
<ImageNext <ImageNext
src="/home/Standard-Banner-1-2024.png.webp" src="/home/Standard-Banner-1-2024.png.webp"
...@@ -30,7 +30,7 @@ const Page = () => { ...@@ -30,7 +30,7 @@ const Page = () => {
height={720} height={720}
/> />
</Link> </Link>
</div> </div> */}
<section className="flex flex-col lg:flex-row gap-5 pb-10 mb-0"> <section className="flex flex-col lg:flex-row gap-5 pb-10 mb-0">
<News /> <News />
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
import { notFound, useParams } from "next/navigation"; import { notFound, useParams } from "next/navigation";
import { useGetNewsPageConfigGetHierarchical } from "@/api/endpoints/news-page-config"; import { useGetNewsPageConfigGetHierarchical } from "@/api/endpoints/news-page-config";
import { GetNewsPageConfigResponseType } from "@/api/types/news-page-config"; import { GetNewsPageConfigResponseType } from "@/api/types/news-page-config";
import { GetNewsResponseType, NewsResponseData } from "@/api/types/news"; import { GetNewsResponseType } from "@/api/types/news";
// templates // templates
import InformationPage from "./templates/InformationPage"; import InformationPage from "./templates/InformationPage";
...@@ -11,24 +11,24 @@ import ArticlePage from "./templates/ArticlePage"; ...@@ -11,24 +11,24 @@ import ArticlePage from "./templates/ArticlePage";
import { Spinner } from "@/components/ui"; import { Spinner } from "@/components/ui";
import { useGetNews } from "@/api/endpoints/news"; import { useGetNews } from "@/api/endpoints/news";
import ArticleDetailPage from "./templates/ArticleDetailPage"; import ArticleDetailPage from "./templates/ArticleDetailPage";
import EventPage from "./templates/EventPage";
import EventDetailPage from "./templates/EventDetailPage";
export default function DynamicPage() { export default function DynamicPage() {
const params = useParams(); const params = useParams();
const slug = Array.isArray(params.slug) ? params.slug : [params.slug]; const slug = Array.isArray(params.slug) ? params.slug : [params.slug];
const path = slug.join("/"); const path = slug.join("/");
const lastThree = slug.slice(-3).join('/');
// query // query
const { data: category, isLoading, isError } = useGetNewsPageConfigGetHierarchical<GetNewsPageConfigResponseType>({ const { data: news } = useGetNews<GetNewsResponseType>(
{ filters: `external_link==/${path}` }
);
const { data: category, isLoading: categoryLoading, isError } = useGetNewsPageConfigGetHierarchical<GetNewsPageConfigResponseType>({
static_link: `/${path}`, static_link: `/${path}`,
}); });
const data = useGetNews<GetNewsResponseType>(
{ filters: `external_link==/${lastThree}` }
);
// const children = category?.responseData?.children || [];
// // redirect to first child if has children // // redirect to first child if has children
// const children = category?.responseData?.children || [];
// useEffect(() => { // useEffect(() => {
// if (!category) return; // if (!category) return;
// if (slug.length === 1 && children.length > 0) { // if (slug.length === 1 && children.length > 0) {
...@@ -40,23 +40,32 @@ export default function DynamicPage() { ...@@ -40,23 +40,32 @@ export default function DynamicPage() {
// }, [slug, category, children, router]); // }, [slug, category, children, router]);
//template //template
// if (isLoading) { if (slug[0] === "hoat-dong" && slug[1] === "su-kien") {
// return ( if (slug.length === 2) return <EventPage />;
// <div className="flex justify-center items-center w-full h-64"> if (slug.length === 3) return <EventDetailPage />;
// <Spinner /> }
// </div>
// ); if (news?.responseData?.count == 0 && categoryLoading) {
// } return (
<div className="flex justify-center items-center w-full h-64">
// not found page <Spinner />
// if (isError) { </div>
// return notFound(); );
// } }
// default if (news && news?.responseData.rows.length !== 0) {
return (data?.data?.responseData?.rows.length !== 0 ? <ArticleDetailPage /> : ( return <ArticleDetailPage data={news} />;
category?.responseData?.is_article ? }
<ArticlePage isError={isError} isLoading={isLoading} /> :
<InformationPage isError={isError} isLoading={isLoading} /> else if (category?.responseData.is_article == true) {
)); return <ArticlePage />;
}
else if (category?.responseData.is_article == false) {
return <InformationPage />;
}
else if (isError) {
return notFound();
}
} }
...@@ -4,64 +4,53 @@ import { GetNewsPageConfigResponseType } from "@/api/types/news-page-config"; ...@@ -4,64 +4,53 @@ import { GetNewsPageConfigResponseType } from "@/api/types/news-page-config";
import { useGetNewsPageConfigGetHierarchical } from "@/api/endpoints/news-page-config"; import { useGetNewsPageConfigGetHierarchical } from "@/api/endpoints/news-page-config";
import ListCategory from "@/components/base/list-category"; import ListCategory from "@/components/base/list-category";
import { useParams } from "next/dist/client/components/navigation"; import { useParams } from "next/dist/client/components/navigation";
import { useGetNews } from "@/api/endpoints/news";
import { GetNewsResponseType } from "@/api/types/news"; import { GetNewsResponseType } from "@/api/types/news";
import EventCalendar from "@/components/base/event-calendar"; import EventCalendar from "@/components/base/event-calendar";
import dayjs from "dayjs"; import dayjs from "dayjs";
import parse from "html-react-parser"; import parse from "html-react-parser";
import { Spinner } from "@/components/ui"; import { Spinner } from "@/components/ui";
import { notFound } from "next/navigation";
export default function ArticleDetailPage() { export default function ArticleDetailPage({ data }: { data: GetNewsResponseType }) {
// get url
const params = useParams(); const params = useParams();
const slug = Array.isArray(params.slug) ? params.slug : [params.slug]; const slug = Array.isArray(params.slug) ? params.slug : [params.slug];
const path = slug.join("/");
//query //query
const { data: categoriesPage } = useGetNewsPageConfigGetHierarchical<GetNewsPageConfigResponseType>({ const { data: category } = useGetNewsPageConfigGetHierarchical<GetNewsPageConfigResponseType>({
code: slug[0], code: slug[0],
}); });
const { data, isLoading } = useGetNews<GetNewsResponseType>({
filters: `external_link==/${path}`,
});
const children = category?.responseData?.children ?? [];
// template // template
if (!isLoading && (!data?.responseData?.rows || data.responseData.rows.length === 0)) {
return notFound();
}
return ( return (
<div className='container w-full flex justify-center items-center pb-10'> <div className='container w-full flex justify-center items-center pb-10'>
{isLoading ? ( <div className='flex flex-col gap-5 w-full'>
<div className='flex justify-center items-center w-full h-64'> {children.length !== 0 ? (
<Spinner /> <ListCategory categories={children} />
</div> ) : (
) : ( <br />
<div className='flex flex-col gap-5 w-full'> )}
<ListCategory categories={categoriesPage?.responseData?.children} /> <div className="grid grid-cols-1 lg:grid-cols-3 gap-5">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-5"> <main className="lg:col-span-2 bg-white border rounded-md p-8">
<main className="lg:col-span-2 bg-white border rounded-md p-8"> <div className='pb-5 text-primary text-2xl leading-normal font-medium'>
<div className='pb-5 text-primary text-2xl leading-normal font-medium'> {data?.responseData?.rows[0]?.title}
{data?.responseData?.rows[0]?.title} </div>
</div> <div className='flex items-center gap-2 text-sm mb-4'>
<div className='flex items-center gap-2 text-sm mb-4'> <span className='text-base text-blue-700'>
<span className='text-base text-blue-700'> {dayjs(data?.responseData?.rows[0]?.created_at).format('DD/MM/YYYY')}
{dayjs(data?.responseData?.rows[0]?.created_at).format('DD/MM/YYYY')} </span>
</span> </div>
</div> <hr className="my-5" />
<hr className="my-5" /> <div className='flex-1 text-app-grey text-base overflow-hidden'>
<div className='flex-1 text-app-grey text-base overflow-hidden'> <div className="prose tiptap overflow-hidden">
<div className="prose tiptap overflow-hidden"> {parse(data?.responseData?.rows[0]?.description ?? '')}
{parse(data?.responseData?.rows[0]?.description ?? '')}
</div>
</div> </div>
</main> </div>
<aside className="space-y-6"> </main>
<EventCalendar /> <aside className="space-y-6">
</aside> <EventCalendar />
</div> </aside>
</div> </div>
)} </div>
</div> </div>
); );
} }
\ No newline at end of file
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
import { GetNewsPageConfigResponseType } from "@/api/types/news-page-config"; import { GetNewsPageConfigResponseType } from "@/api/types/news-page-config";
import { useGetNewsPageConfigGetHierarchical } from "@/api/endpoints/news-page-config"; import { useGetNewsPageConfigGetHierarchical } from "@/api/endpoints/news-page-config";
import ListCategory from "@/components/base/list-category"; import ListCategory from "@/components/base/list-category";
import { useParams, useSearchParams, useRouter, usePathname, notFound } from "next/navigation"; import { useParams, useSearchParams, useRouter, usePathname } from "next/navigation";
import { useGetNews } from "@/api/endpoints/news"; import { useGetNews } from "@/api/endpoints/news";
import { GetNewsResponseType } from "@/api/types/news"; import { GetNewsResponseType } from "@/api/types/news";
import CardNews from "@/components/base/card-news"; import CardNews from "@/components/base/card-news";
...@@ -13,7 +13,7 @@ import EventCalendar from "@/components/base/event-calendar"; ...@@ -13,7 +13,7 @@ import EventCalendar from "@/components/base/event-calendar";
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { Spinner } from "@/components/ui"; import { Spinner } from "@/components/ui";
export default function ArticlePage({ isError, isLoading }: { isError: boolean, isLoading: boolean }) { export default function ArticlePage() {
// get url // get url
const params = useParams(); const params = useParams();
const slug = Array.isArray(params.slug) ? params.slug : [params.slug]; const slug = Array.isArray(params.slug) ? params.slug : [params.slug];
...@@ -41,7 +41,7 @@ export default function ArticlePage({ isError, isLoading }: { isError: boolean, ...@@ -41,7 +41,7 @@ export default function ArticlePage({ isError, isLoading }: { isError: boolean,
}, [page]); }, [page]);
// query // query
const { data: categoriesPage } = useGetNewsPageConfigGetHierarchical<GetNewsPageConfigResponseType>({ const { data: category } = useGetNewsPageConfigGetHierarchical<GetNewsPageConfigResponseType>({
code: slug[0], code: slug[0],
}); });
...@@ -51,14 +51,8 @@ export default function ArticlePage({ isError, isLoading }: { isError: boolean, ...@@ -51,14 +51,8 @@ export default function ArticlePage({ isError, isLoading }: { isError: boolean,
currentPage: String(page), currentPage: String(page),
}); });
const children = category?.responseData?.children ?? [];
//template //template
if (isLoading) return (
<div className="flex justify-center items-center w-full h-64">
<Spinner />
</div>
);
if (isError) return notFound();
return ( return (
<div className="min-h-screen container mx-auto"> <div className="min-h-screen container mx-auto">
{articlesLoading ? ( {articlesLoading ? (
...@@ -67,7 +61,11 @@ export default function ArticlePage({ isError, isLoading }: { isError: boolean, ...@@ -67,7 +61,11 @@ export default function ArticlePage({ isError, isLoading }: { isError: boolean,
</div> </div>
) : ( ) : (
<div className="w-full flex flex-col gap-5"> <div className="w-full flex flex-col gap-5">
<ListCategory categories={categoriesPage?.responseData?.children} /> {children.length !== 0 ? (
<ListCategory categories={children} />
) : (
<br />
)}
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<main className="lg:col-span-2 bg-background"> <main className="lg:col-span-2 bg-background">
<div className="pb-5 overflow-hidden"> <div className="pb-5 overflow-hidden">
......
'use client';
import { useState } from "react";
import Image from "next/image";
import { notFound, useParams } from "next/navigation";
import dayjs from "dayjs";
import parse from "html-react-parser";
import BASE_URL from "@/links";
import { useGetEvents } from "@/api/endpoints/event";
import { EventApiResponse } from "@/api/types/event";
import { GetNewsPageConfigResponseType } from "@/api/types/news-page-config";
import { useGetNewsPageConfigGetHierarchical } from "@/api/endpoints/news-page-config";
import { Spinner } from "@/components/ui/spinner";
import ListCategory from "@/components/base/list-category";
import EventCalendar from "@/components/base/event-calendar";
import { CreditCard, MapPin, Clock } from "lucide-react";
export default function EventDetailPage() {
// get url
const params = useParams();
const slug = Array.isArray(params.slug) ? params.slug : [params.slug];
const lastpath = slug[slug.length - 1];
// query
const { data: categoriesPage } = useGetNewsPageConfigGetHierarchical<GetNewsPageConfigResponseType>({
code: `${slug[0]}`,
});
const { data: eventsDetail, isLoading } = useGetEvents<EventApiResponse>({
filters: `id==${lastpath}`,
});
// template
if (!isLoading && (!eventsDetail?.responseData?.rows || eventsDetail.responseData.rows.length === 0)) {
return notFound();
}
return (
<div className='container w-full flex justify-center items-center pb-10'>
{isLoading ? (
<div className="flex justify-center items-center w-full h-64">
<Spinner />
</div>
) : (
<div className='flex flex-col gap-5 w-full'>
<ListCategory categories={categoriesPage?.responseData?.children} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-5">
<main className="lg:col-span-2 bg-white border rounded-md py-10 px-5 md:px-20">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{eventsDetail?.responseData?.rows[0]?.name}
</div>
<hr className="py-2" />
{/* Top summary with image + details */}
<div className="flex flex-col md:flex-row gap-6 my-6">
<div className="w-full lg:w-1/2 bg-gray-50 rounded-md overflow-hidden">
{eventsDetail?.responseData?.rows[0].image ? (
<div className="w-full h-52 relative ">
<EventImage
src={`${BASE_URL.imageEndpoint}${eventsDetail?.responseData?.rows[0].image}`}
alt={eventsDetail?.responseData?.rows[0]?.name || "image"}
/>
</div>
) : (
<div className="w-full h-52 bg-gray-200" />
)}
</div>
<div className="w-full lg:w-1/2 bg-white border rounded-md p-3 md:p-6">
<div className="flex flex-col gap-3">
<div className="text-sm text-gray-500 flex flex-row items-center gap-2">
<Clock className="h-5 w-5 text-yellow-500" />
<div>
<div className="text-sm font-medium text-gray-800">
Bắt đầu: {eventsDetail?.responseData?.rows[0].start_time
? dayjs(
eventsDetail.responseData.rows[0].start_time
).format('HH:mm DD/MM/YYYY')
: "-"}
</div>
<div className="text-sm font-medium text-gray-800">
Kết thúc: {eventsDetail?.responseData?.rows[0].end_time
? dayjs(
eventsDetail.responseData.rows[0].end_time
).format('HH:mm DD/MM/YYYY')
: "-"}
</div>
</div>
</div>
<div className="text-sm text-gray-500 flex items-center gap-2">
<MapPin className="h-5 w-5 text-blue-600" />
<div className="text-sm font-medium text-gray-800">
Địa điểm: {eventsDetail?.responseData?.rows[0]?.location ??
eventsDetail?.responseData?.rows[0]?.province ??
"-"}
</div>
</div>
<div className="text-sm text-gray-500 flex items-center gap-2">
<CreditCard className="h-5 w-5 text-yellow-400" />
<div className="text-sm font-medium text-gray-800">
Phí tham dự: {eventsDetail?.responseData?.rows[0]?.table_cost
? `${eventsDetail?.responseData?.rows[0]?.table_count
} Bàn : ${eventsDetail?.responseData?.rows[0]?.table_cost.toLocaleString()} đ`
: "Vui lòng xem chi tiết trong bài"}
</div>
</div>
</div>
</div>
</div>
{/* Full description */}
<div className="prose tiptap overflow-hidden">
{parse(eventsDetail?.responseData?.rows[0]?.description ?? "")}
</div>
</main>
{/* Sidebar */}
<aside className="space-y-6">
<EventCalendar />
<div className="bg-white border rounded-md overflow-hidden">
<div className="w-full h-75 relative bg-gray-100">
<Image
src="/banner.webp"
alt="Quảng cáo"
fill
className="object-contain"
/>
</div>
</div>
</aside>
</div>
</div>
)}
</div >
);
}
// Local small component to safely handle Image src fallback without mutating DOM
type EventImageProps = {
src: string;
alt?: string;
};
function EventImage({ src, alt }: EventImageProps) {
const [imgSrc, setImgSrc] = useState<string>(src);
return (
<Image
src={imgSrc}
alt={alt ?? "image"}
fill
className="object-cover"
onError={() => {
// swap to local fallback file when Next/Image fails to load the provided URL
if (imgSrc !== "/img-error.png") setImgSrc("/img-error.png");
}}
/>
);
}
\ No newline at end of file
'use client';
import { useEffect, useState } from "react";
import { notFound, useParams, usePathname, useRouter, useSearchParams } from "next/navigation";
import { useGetEvents } from "@/api/endpoints/event";
import { EventApiResponse } from "@/api/types/event";
import { GetNewsPageConfigResponseType } from "@/api/types/news-page-config";
import { useGetNewsPageConfigGetHierarchical } from "@/api/endpoints/news-page-config";
import { Spinner } from "@/components/ui/spinner";
import ListCategory from "@/components/base/list-category";
import CardEvents from "@/components/base/card-events";
import { Pagination } from "@/components/base/pagination";
import ListFilter from "@/components/base/list-filter";
import EventCalendar from "@/components/base/event-calendar";
export default function EventPage() {
// get url
const params = useParams();
const slug = Array.isArray(params.slug) ? params.slug : [params.slug];
const searchParams = useSearchParams();
const router = useRouter();
const pathname = usePathname();
// states
const initialPage = Number(searchParams.get("page") ?? "1");
const [submitSearch, setSubmitSearch] = useState("");
const [page, setPage] = useState(initialPage);
const pageSize = 5;
useEffect(() => {
const params = new URLSearchParams(searchParams.toString());
if (page > 1) {
params.set("page", String(page));
} else {
params.delete("page");
}
const qs = params.toString();
router.replace(qs ? `${pathname}?${qs}` : pathname, { scroll: false });
}, [page]);
// query
const { data: categoriesPage } = useGetNewsPageConfigGetHierarchical<GetNewsPageConfigResponseType>({
code: `${slug[0]}`,
});
const { data: events, isLoading: eventsLoading } = useGetEvents<EventApiResponse>({
filters: `name@=${submitSearch ? `title@=${submitSearch}` : ""}`,
pageSize: String(pageSize),
currentPage: String(page),
});
//template
return (
<>
<div className="min-h-screen container mx-auto">
{eventsLoading ? (
<div className="flex justify-center items-center w-full h-64">
<Spinner />
</div>
) : (
<div className="w-full flex flex-col gap-5">
<ListCategory categories={categoriesPage?.responseData?.children} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
<main className="lg:col-span-2 bg-background">
<div className="pb-5 overflow-hidden">
{events?.responseData?.rows?.map((item) => (
<CardEvents
key={item.id}
event={item}
link={`su-kien/${item.id}`}
/>
))}
<div className="w-full flex justify-center mt-4">
<Pagination
pageCount={Number(events?.responseData?.totalPages ?? 1)}
page={Number(events?.responseData?.currentPage ?? page)}
onChangePage={setPage}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() =>
setPage(Math.min(Number(events?.responseData?.totalPages ?? 1), page + 1))
}
/>
</div>
</div>
</main>
<aside className="space-y-6">
<ListFilter onSearch={setSubmitSearch} />
<EventCalendar />
</aside>
</div>
</div>
)}
</div>
</>
);
}
\ No newline at end of file
...@@ -8,16 +8,15 @@ import { Spinner } from "@/components/ui/spinner"; ...@@ -8,16 +8,15 @@ import { Spinner } from "@/components/ui/spinner";
import { GetNewsResponseType } from "@/api/types/news"; import { GetNewsResponseType } from "@/api/types/news";
import { useGetNews } from "@/api/endpoints/news"; import { useGetNews } from "@/api/endpoints/news";
import parse from "html-react-parser"; import parse from "html-react-parser";
import { notFound } from "next/navigation";
export default function InformationPage({ isError, isLoading }: { isError: boolean, isLoading: boolean }) { export default function InformationPage() {
// get url // get url
const params = useParams(); const params = useParams();
const slug = Array.isArray(params.slug) ? params.slug : [params.slug]; const slug = Array.isArray(params.slug) ? params.slug : [params.slug];
const path = slug.join("/"); const path = slug.join("/");
// query // query
const { data: categoryPage } = useGetNewsPageConfigGetHierarchical<GetNewsPageConfigResponseType>({ const { data: category } = useGetNewsPageConfigGetHierarchical<GetNewsPageConfigResponseType>({
static_link: `/${slug[0]}`, static_link: `/${slug[0]}`,
}); });
...@@ -25,14 +24,8 @@ export default function InformationPage({ isError, isLoading }: { isError: boole ...@@ -25,14 +24,8 @@ export default function InformationPage({ isError, isLoading }: { isError: boole
filters: `page_config.static_link==/${path}`, filters: `page_config.static_link==/${path}`,
}); });
const children = category?.responseData?.children ?? [];
//template //template
if (isLoading) return (
<div className="flex justify-center items-center w-full h-64">
<Spinner />
</div>
);
if (isError) return notFound();
return ( return (
<div className='container w-full flex justify-center items-center pb-10'> <div className='container w-full flex justify-center items-center pb-10'>
{informationLoading ? ( {informationLoading ? (
...@@ -41,7 +34,11 @@ export default function InformationPage({ isError, isLoading }: { isError: boole ...@@ -41,7 +34,11 @@ export default function InformationPage({ isError, isLoading }: { isError: boole
</div> </div>
) : ( ) : (
<div className='flex flex-col gap-5 w-full'> <div className='flex flex-col gap-5 w-full'>
<ListCategory categories={categoryPage?.responseData?.children} /> {children.length !== 0 ? (
<ListCategory categories={children} />
) : (
<br />
)}
<main className=" bg-white border rounded-md py-10 px-5 md:px-20 lg:px-20"> <main className=" bg-white border rounded-md py-10 px-5 md:px-20 lg:px-20">
<div className='text-primary text-2xl leading-normal font-bold'> <div className='text-primary text-2xl leading-normal font-bold'>
{information?.responseData?.rows[0]?.title} {information?.responseData?.rows[0]?.title}
......
...@@ -111,23 +111,22 @@ function Footer() { ...@@ -111,23 +111,22 @@ function Footer() {
</h2> </h2>
<div className="h-0.5 w-14 bg-[#063e8e] mx-0"></div> <div className="h-0.5 w-14 bg-[#063e8e] mx-0"></div>
<div className="w-full overflow-hidden rounded-md"> <div className="w-full overflow-hidden rounded-md">
<iframe <iframe src="https://www.facebook.com/plugins/page.php?href=https%3A%2F%2Fwww.facebook.com%2Fmeusolutions&tabs&width=340&height=130&small_header=false&adapt_container_width=true&hide_cover=false&show_facepile=true&appId"
className="w-full sm:h-[140px]" width="340"
src="https://www.facebook.com/plugins/page.php?href=https%3A%2F%2Fwww.facebook.com%2FVCCIHCMC%3Fref%3Dembed_page&tabs=&width=340&height=130&small_header=false&adapt_container_width=true&hide_cover=false&show_facepile=false" height="130"
style={{ border: "none", overflow: "hidden" }} className="border:none;overflow:hidden"
scrolling="no" scrolling="no" frameBorder="0"
frameBorder="0"
allowFullScreen={true} allowFullScreen={true}
allow="autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share" allow="autoplay; clipboard-write; encrypted-media; picture-in-picture; web-share">
></iframe> </iframe>
</div> </div>
<div className="flex gap-3 justify-start"> <div className="flex gap-3 justify-start">
{[ {[
{ icon: <Facebook size={20} />, link: "https://www.facebook.com/VCCIHCMC/" }, { icon: <Facebook size={20} />, link: "#" },
{ icon: <Twitter size={20} />, link: "https://twitter.com/VCCI_HCM" }, { icon: <Twitter size={20} />, link: "#" },
{ icon: <Youtube size={20} />, link: "https://www.youtube.com/user/VCCIHCMC" }, { icon: <Youtube size={20} />, link: "#" },
{ icon: <Linkedin size={20} />, link: "https://www.linkedin.com/company/vietnam-chamber-of-commerce-and-industry-ho-chi-minh-city-branch-vcci-hcm-?trk=biz-companies-cym" }, { icon: <Linkedin size={20} />, link: "#" },
].map((s, i) => ( ].map((s, i) => (
<a <a
key={i} key={i}
......
...@@ -30,13 +30,13 @@ function Header() { ...@@ -30,13 +30,13 @@ function Header() {
</div> </div>
<Link <Link
className="px-3 py-2 text-[14px] text-white hover:opacity-80" className="px-3 py-2 text-[14px] text-white hover:opacity-80"
href="/site-map" href="#"
> >
Sitemap Sitemap
</Link> </Link>
<Link <Link
className="px-3 py-2 text-[14px] text-white hover:opacity-80" className="px-3 py-2 text-[14px] text-white hover:opacity-80"
href="https://vccihcm.vn/lien-he" href="/lien-he"
> >
Liên hệ Liên hệ
</Link> </Link>
...@@ -58,28 +58,28 @@ function Header() { ...@@ -58,28 +58,28 @@ function Header() {
/> />
<div className="flex gap-2"> <div className="flex gap-2">
<a <a
href="https://www.facebook.com/VCCIHCMC/" href="#"
target="_blank" target="_blank"
className="bg-white size-7 rounded-full flex items-center justify-center text-[#063e8e] hover:opacity-80 transition" className="bg-white size-7 rounded-full flex items-center justify-center text-[#063e8e] hover:opacity-80 transition"
> >
<Facebook size={16} /> <Facebook size={16} />
</a> </a>
<a <a
href="https://twitter.com/VCCI_HCM" href="#"
target="_blank" target="_blank"
className="bg-white size-7 rounded-full flex items-center justify-center text-[#063e8e] hover:opacity-80 transition" className="bg-white size-7 rounded-full flex items-center justify-center text-[#063e8e] hover:opacity-80 transition"
> >
<Twitter size={16} /> <Twitter size={16} />
</a> </a>
<a <a
href="https://www.youtube.com/user/VCCIHCMC" href="#"
target="_blank" target="_blank"
className="bg-white size-7 rounded-full flex items-center justify-center text-[#063e8e] hover:opacity-80 transition" className="bg-white size-7 rounded-full flex items-center justify-center text-[#063e8e] hover:opacity-80 transition"
> >
<Youtube size={16} /> <Youtube size={16} />
</a> </a>
<a <a
href="https://www.linkedin.com/company/vietnam-chamber-of-commerce-and-industry-ho-chi-minh-city-branch-vcci-hcm-?trk=biz-companies-cym" href="#"
target="_blank" target="_blank"
className="bg-white size-7 rounded-full flex items-center justify-center text-[#063e8e] hover:opacity-80 transition" className="bg-white size-7 rounded-full flex items-center justify-center text-[#063e8e] hover:opacity-80 transition"
> >
......
const links = { const links = {
analyticsGoogle: 'G-C9TEK9BS4C', analyticsGoogle: 'G-C9TEK9BS4C',
apiEndpoint: `https://hiea.meu-solutions.com/api/v1.0`, apiEndpoint: `https://hiea.meu-solutions.com/api/v1.0`,
imageEndpoint: `https://hiea.meu-solutions.com`, imageEndpoint: `http://103.72.98.149:7041`,
siteURL: 'https://hiea-news.meu-solutions.com', siteURL: 'https://hiea-news.meu-solutions.com',
} }
export default links export default links
\ 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