Commit 709ff6db authored by Phạm Quang Bảo's avatar Phạm Quang Bảo

update

parent 97a4dfb1
...@@ -17,10 +17,11 @@ const Members = () => { ...@@ -17,10 +17,11 @@ const Members = () => {
filters: `page_config.code @=ket-noi-hoi-vien`, filters: `page_config.code @=ket-noi-hoi-vien`,
} }
); );
return ( return (
<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">
{/* LEFT: HỘI VIÊN TIÊU BIỂU */} {/* LEFT: HỘI VIÊN TIÊU BIỂU */}
<aside className="w-full lg:w-1/3 flex-1 bg-[#e8c518] p-5"> <aside className="w-full flex-1 bg-[#e8c518] p-5">
<div className="flex justify-between items-center mb-3"> <div className="flex justify-between items-center mb-3">
<h2 className="text-xl font-bold uppercase text-[#063e8e]"> <h2 className="text-xl font-bold uppercase text-[#063e8e]">
Hội viên tiêu biểu Hội viên tiêu biểu
...@@ -39,7 +40,7 @@ const Members = () => { ...@@ -39,7 +40,7 @@ const Members = () => {
modules={[Autoplay]} modules={[Autoplay]}
autoplay={{ delay: 4000, disableOnInteraction: false }} autoplay={{ delay: 4000, disableOnInteraction: false }}
loop loop
slidesPerView={3} slidesPerView={6}
spaceBetween={16} spaceBetween={16}
breakpoints={{ breakpoints={{
0: { slidesPerView: 2, spaceBetween: 10 }, 0: { slidesPerView: 2, spaceBetween: 10 },
...@@ -66,7 +67,7 @@ const Members = () => { ...@@ -66,7 +67,7 @@ const Members = () => {
</aside> </aside>
{/* RIGHT: KẾT NỐI HỘI VIÊN */} {/* RIGHT: KẾT NỐI HỘI VIÊN */}
{isLoading ? ( {/* {isLoading ? (
<div className="flex justify-center items-center w-full h-64"> <div className="flex justify-center items-center w-full h-64">
<Spinner /> <Spinner />
</div> </div>
...@@ -107,7 +108,7 @@ const Members = () => { ...@@ -107,7 +108,7 @@ const Members = () => {
))} ))}
</Swiper> </Swiper>
</aside> </aside>
)} )} */}
</section> </section>
); );
}; };
......
...@@ -67,7 +67,7 @@ const News = () => { ...@@ -67,7 +67,7 @@ const News = () => {
))} ))}
<div className="w-full md:w-1/2"> <div className="w-full md:w-1/2">
<div className="flex flex-wrap gap-2 sm:gap-3 mb-5"> {/* <div className="flex flex-wrap gap-2 sm:gap-3 mb-5">
<button <button
className={`flex-1 py-[3px] text-sm transition-colors cursor-pointer ${tab === "all" className={`flex-1 py-[3px] text-sm transition-colors cursor-pointer ${tab === "all"
? " bg-[#d3d3d3] text-[#063e8e] font-semibold" ? " bg-[#d3d3d3] text-[#063e8e] font-semibold"
...@@ -103,9 +103,9 @@ const News = () => { ...@@ -103,9 +103,9 @@ const News = () => {
> >
Chuyên Đề Chuyên Đề
</button> </button>
</div> </div> */}
{newsFilters?.responseData?.rows.slice(0, 4).map((news) => ( {newsFilters?.responseData?.rows.slice(0, 5).map((news) => (
<CardNews key={news.id} news={news} /> <CardNews key={news.id} news={news} />
))} ))}
</div> </div>
......
...@@ -42,7 +42,7 @@ const Page = () => { ...@@ -42,7 +42,7 @@ const Page = () => {
<EventsCalendar /> <EventsCalendar />
</section > </section >
<div className="flex flex-col lg:flex-row gap-5 pb-10 mb-0" > {/* <div className="flex flex-col lg:flex-row gap-5 pb-10 mb-0" >
<div className="flex flex-col flex-1"> <div className="flex flex-col flex-1">
<div> <div>
<Link href="https://vcci-hcm.org.vn/wp-content/uploads/2022/11/MEDIA-KIT_VCCI-HCM-2022-Final.pdf"> <Link href="https://vcci-hcm.org.vn/wp-content/uploads/2022/11/MEDIA-KIT_VCCI-HCM-2022-Final.pdf">
...@@ -71,12 +71,12 @@ const Page = () => { ...@@ -71,12 +71,12 @@ const Page = () => {
/> />
</Link> </Link>
</div> </div>
</div > </div> */}
<Members /> <Members />
<VideoAndPartners /> <VideoAndPartners />
</div> </div>
</div> </div >
); );
}; };
......
"use client"; "use client";
import { useEffect } from "react"; import { notFound, useParams } from "next/navigation";
import { notFound, useParams, useRouter } 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";
// templates // templates
import InformationPage from "./templates/InformationPage"; import InformationPage from "./templates/InformationPage";
import ArticlePage from "./templates/ArticlePage"; import ArticlePage from "./templates/ArticlePage";
import ArticleDetailPage from "./templates/ArticleDetailPage";
import EventPage from "./templates/EventPage";
import EventDetailPage from "./templates/EventDetailPage";
import { Spinner } from "@/components/ui"; import { Spinner } from "@/components/ui";
import { useGetNews } from "@/api/endpoints/news";
import ArticleDetailPage from "./templates/ArticleDetailPage";
export default function DynamicPage() { export default function DynamicPage() {
const params = useParams(); const params = useParams();
const router = useRouter();
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: category, isLoading, isError } = useGetNewsPageConfigGetHierarchical<GetNewsPageConfigResponseType>({
static_link: `/${path}`, static_link: `/${path}`,
}); });
const children = category?.responseData?.children || []; const data = useGetNews<GetNewsResponseType>(
// redirect to first child if has children { filters: `external_link==/${lastThree}` }
useEffect(() => { );
if (!category) return;
if (slug.length === 1 && children.length > 0) { // const children = category?.responseData?.children || [];
const firstChild = children[0]; // // redirect to first child if has children
if (firstChild?.static_link) { // useEffect(() => {
router.push(firstChild.static_link); // if (!category) return;
} // if (slug.length === 1 && children.length > 0) {
} // const firstChild = children[0];
}, [slug, category, children, router]); // if (firstChild?.static_link) {
// router.push(firstChild.static_link);
// }
// }
// }, [slug, category, children, router]);
//template //template
if (slug.length === 1 && children.length > 0) { // if (isLoading) {
return null; // return (
} // <div className="flex justify-center items-center w-full h-64">
// <Spinner />
if (slug[0] === "hoat-dong" && slug[1] === "su-kien") { // </div>
if (slug.length === 2) return <EventPage isError={isError} isLoading={isLoading} />; // );
if (slug.length === 3) return <EventDetailPage />; // }
}
if (slug.length === 2) {
return category?.responseData?.is_article ? <ArticlePage isError={isError} isLoading={isLoading} /> : <InformationPage isError={isError} isLoading={isLoading} />;
}
if (slug.length === 3) {
return <ArticleDetailPage />;
}
// not found page // not found page
if (isLoading) { // if (isError) {
return ( // return notFound();
<div className="flex justify-center items-center w-full h-64"> // }
<Spinner />
</div> // default
); return (data?.data?.responseData?.rows.length !== 0 ? <ArticleDetailPage /> : (
} category?.responseData?.is_article ?
<ArticlePage isError={isError} isLoading={isLoading} /> :
if (isError) { <InformationPage isError={isError} isLoading={isLoading} />
return notFound(); ));
}
} }
'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({ isError, isLoading }: { isError: boolean, isLoading: boolean }) {
// 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
if (isLoading) return (
<div className="flex justify-center items-center w-full h-64">
<Spinner />
</div>
);
if (isError) return notFound();
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
...@@ -7,10 +7,8 @@ import { useParams } from "next/dist/client/components/navigation"; ...@@ -7,10 +7,8 @@ import { useParams } from "next/dist/client/components/navigation";
import { Spinner } from "@/components/ui/spinner"; 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 dayjs from "dayjs";
import parse from "html-react-parser"; import parse from "html-react-parser";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
import { is } from "date-fns/locale";
export default function InformationPage({ isError, isLoading }: { isError: boolean, isLoading: boolean }) { export default function InformationPage({ isError, isLoading }: { isError: boolean, isLoading: boolean }) {
// get url // get url
......
...@@ -33,7 +33,6 @@ const CardNews = ({ news, link }: { news: NewsItem, link: string }) => { ...@@ -33,7 +33,6 @@ const CardNews = ({ news, link }: { news: NewsItem, link: string }) => {
onError={(e) => { onError={(e) => {
e.currentTarget.src = "/img-error.png" e.currentTarget.src = "/img-error.png"
}} }}
/> />
<div className="flex-1 min-w-0 pl-0 sm:pl-4"> <div className="flex-1 min-w-0 pl-0 sm:pl-4">
......
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