Commit 2d0a0602 authored by Lê Bảo Hồng Đức's avatar Lê Bảo Hồng Đức

Merge branch 'feat/duck' into 'develop-news'

re-design ui home client

See merge request !53
parents bee60fa0 c03ea16b
......@@ -5,6 +5,7 @@ import { Swiper, SwiperSlide } from "swiper/react";
import { Autoplay } from "swiper/modules";
import { Swiper as SwiperType } from "swiper/types";
import { useRef } from "react";
import "swiper/css";
const Banner = () => {
const swiperRef = useRef<SwiperType | null>(null);
......@@ -16,6 +17,7 @@ const Banner = () => {
loop
slidesPerView={1}
onSwiper={(s) => (swiperRef.current = s)}
className="w-full overflow-hidden"
>
<SwiperSlide>
<ImageNext
......
import { useGetNews } from "@/api/endpoints/news";
import { GetNewsResponseType, NewsItem } from "@/api/types/news";
import ImageNext from "@/components/shared/image-next";
import { Spinner } from "@/components/ui/spinner";
import { ChevronsRight } from "lucide-react";
'use client';
import {
type AdminNewsItem,
getAdminNewsSeed,
} from "@/mockdata/admin-news";
import dayjs from "dayjs";
import { ChevronRight } from "lucide-react";
import Link from "next/link";
import BASE_URL from "@/links/index";
import CardNews from "./components/card-news";
const businessOpportunities = () => {
const { data, isLoading } = useGetNews<GetNewsResponseType>(
{
pageSize: '5',
filters: `page_config.code @=co-hoi-kinh-doanh`,
}
const businessItems = getAdminNewsSeed()
.filter(
(item) =>
item.type === "tintuc" &&
!item.is_hidden &&
(item.category_ids.includes("cat-business-opportunity") ||
item.tagsearch_values.some((tag) => tag.toLowerCase().includes("cơ hội kinh doanh"))),
)
.sort(
(left, right) =>
new Date(right.published_at || right.created_at).getTime() -
new Date(left.published_at || left.created_at).getTime(),
);
function formatPublishDate(item: AdminNewsItem) {
return dayjs(item.published_at || item.created_at).format("DD/MM/YYYY");
}
function BusinessOpportunities() {
const [featuredItem, ...listItems] = businessItems;
if (!featuredItem) return null;
return (
<div className="flex-1">
<div className="flex justify-between items-center">
<section className="flex-1">
<div className="mb-4 flex items-center justify-between gap-3">
<div>
<h2 className="text-[28px] font-extrabold uppercase tracking-tight text-[#24469c] md:text-[34px]">
Cơ hội kinh doanh
</h2>
<div className="mt-2.5 h-[4px] w-[40px] rounded-full bg-[#f7b500]" />
</div>
<Link
href="/xuc-tien-thuong-mai/co-hoi/"
className="text-[18px] sm:text-[20px] font-bold uppercase text-[#063e8e]"
className="text-[#24469c] transition-colors hover:text-[#1b55a1]"
>
Cơ hội kinh doanh
<ChevronRight className="h-5 w-5" />
</Link>
</div>
<div className="space-y-3">
<Link
href="/xuc-tien-thuong-mai/co-hoi/"
className="text-[#063e8e] text-sm sm:text-base"
className="block rounded-[18px] bg-[#f5f7fb] px-4 py-3.5 transition-colors hover:bg-[#eef3fb]"
>
<ChevronsRight />
<h3 className="line-clamp-2 text-[16px] font-bold leading-[1.45] text-[#264798] md:text-[17px]">
{featuredItem.title}
</h3>
<p className="mt-2 text-[13px] text-[#9aa8c1]">{formatPublishDate(featuredItem)}</p>
</Link>
</div>
<hr className="border-[#063e8e] mb-4" />
<div className="pt-2">
{isLoading ? (
<div className="container w-full h-[80vh] flex justify-center items-center">
<Spinner />
</div>
) : (
<>
{data?.responseData.rows
.slice(0, 1)
.map((news: NewsItem) => (
<Link key={news.id} href={`${news.external_link}`}>
<div className="w-full aspect-3/2 relative overflow-hidden mb-5">
<ImageNext
src={`${BASE_URL.imageEndpoint}${news.thumbnail}`}
alt={news.title}
width={600}
height={400}
sizes="(max-width:768px) 100vw,50vw"
className="w-full h-full object-cover"
/>
<div className="absolute bg-white opacity-80 bottom-5 left-5 right-5 p-5">
<p className="text-[#063e8e] font-semibold text-sm sm:text-base z-10 line-clamp-3">
{news.title}
</p>
</div>
<div className="space-y-2.5">
{listItems.slice(0, 3).map((item) => (
<Link
key={item.id}
href="/xuc-tien-thuong-mai/co-hoi/"
className="flex gap-3 rounded-[14px] px-0.5 py-1 transition-colors hover:bg-[#f8fafe]"
>
<span className="mt-1 h-[40px] w-[2px] shrink-0 rounded-full bg-[#f7b500]" />
<div className="min-w-0">
<h4 className="line-clamp-2 text-[15px] leading-[1.45] text-[#264798]">
{item.title}
</h4>
<p className="mt-1.5 text-[13px] text-[#9aa8c1]">{formatPublishDate(item)}</p>
</div>
</Link>
))}
{data?.responseData.rows.slice(0, 3).map((news) => (
<CardNews key={news.id} news={news} />
))}
</>
)}
</div>
</div>
</section>
);
};
}
export default businessOpportunities;
export default BusinessOpportunities;
import EventCalendar from "@/components/base/event-calendar"
import { ChevronsRight } from "lucide-react"
import Link from "next/link"
'use client';
import {
type AdminNewsItem,
getAdminNewsSeed,
} from "@/mockdata/admin-news";
import { addMonths, format, getDay, startOfMonth, subMonths } from "date-fns";
import dayjs from "dayjs";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { useMemo, useState } from "react";
const weekDays = ["CN", "T2", "T3", "T4", "T5", "T6", "T7"];
const eventItems = getAdminNewsSeed()
.filter(
(item) =>
item.type === "tintuc" &&
!item.is_hidden &&
item.started_at,
)
.sort(
(left, right) =>
new Date(left.started_at).getTime() - new Date(right.started_at).getTime(),
);
function isTrainingEvent(item: AdminNewsItem) {
return item.tagsearch_values.some((tag) => tag.toLowerCase().includes("đào tạo"));
}
function EventsCalendar() {
const firstEventDate = eventItems[0]?.started_at
? new Date(eventItems[0].started_at)
: new Date("2026-11-01T00:00:00");
const [currentMonth, setCurrentMonth] = useState(
new Date(firstEventDate.getFullYear(), firstEventDate.getMonth(), 1),
);
const monthEvents = useMemo(
() =>
eventItems.filter((item) => {
const date = new Date(item.started_at);
return (
date.getMonth() === currentMonth.getMonth() &&
date.getFullYear() === currentMonth.getFullYear()
);
}),
[currentMonth],
);
const days = useMemo(() => {
const monthStart = startOfMonth(currentMonth);
const startWeekDay = getDay(monthStart);
const start = new Date(monthStart);
start.setDate(monthStart.getDate() - startWeekDay);
return Array.from({ length: 35 }, (_, index) => {
const day = new Date(start);
day.setDate(start.getDate() + index);
return day;
});
}, [currentMonth]);
const eventMap = useMemo(() => {
const map = new Map<string, AdminNewsItem[]>();
monthEvents.forEach((item) => {
const key = dayjs(item.started_at).format("YYYY-MM-DD");
const existing = map.get(key) ?? [];
existing.push(item);
map.set(key, existing);
});
return map;
}, [monthEvents]);
const highlightedEvent = monthEvents[0];
const EventsCalendar = () => {
return (
<div className="bg-[#063e8e] w-full lg:w-[30%] p-5">
<aside>
<div className="flex justify-between items-center">
<h2 className="text-[18px] sm:text-[20px] font-bold uppercase text-[#e8c518]">
<aside className="w-full rounded-[28px] bg-white p-4 text-[#24469c] shadow-[0_18px_38px_rgba(16,61,130,0.16)] md:p-5 xl:w-[28%] xl:min-w-[320px]">
<div className="flex items-start justify-between gap-3">
<div>
<h2 className="text-[28px] font-extrabold uppercase tracking-tight md:text-[34px]">
Lịch sự kiện
</h2>
<Link
href="/hoat-dong/su-kien"
className="text-[#e8c518] hover:underline text-sm sm:text-base"
<p className="mt-1.5 text-[12px] uppercase tracking-[0.28em] text-[#7f8eab]">
{`THÁNG ${format(currentMonth, "MM/yyyy")}`}
</p>
</div>
<div className="flex gap-2">
<button
type="button"
onClick={() => setCurrentMonth(subMonths(currentMonth, 1))}
className="flex h-8 w-8 items-center justify-center rounded-full border border-[#dbe4f2] text-[#7f8eab] transition-colors hover:border-[#24469c] hover:text-[#24469c]"
>
<ChevronsRight />
</Link>
<ChevronLeft className="h-4 w-4" />
</button>
<button
type="button"
onClick={() => setCurrentMonth(addMonths(currentMonth, 1))}
className="flex h-8 w-8 items-center justify-center rounded-full border border-[#dbe4f2] text-[#7f8eab] transition-colors hover:border-[#24469c] hover:text-[#24469c]"
>
<ChevronRight className="h-4 w-4" />
</button>
</div>
<hr className="border-[#e8c518] mb-4" />
<EventCalendar />
</aside>
</div>
)
<div className="mt-3 h-[4px] w-[60px] rounded-full bg-[#f7b500]" />
<div className="mt-4 border-t border-[#ebf0f8] pt-3.5">
<div className="grid grid-cols-7 gap-y-2.5 text-center text-[11px] font-semibold uppercase text-[#9aabc6]">
{weekDays.map((day) => (
<div key={day}>{day}</div>
))}
</div>
<div className="mt-2.5 grid grid-cols-7 gap-y-2.5 text-center text-[13px] text-[#5e7090]">
{days.map((day) => {
const key = dayjs(day).format("YYYY-MM-DD");
const items = eventMap.get(key) ?? [];
const inMonth = day.getMonth() === currentMonth.getMonth();
const hasTraining = items.some((item) => isTrainingEvent(item));
const hasEvent = items.length > 0 && !hasTraining;
return (
<div key={key} className="relative flex items-center justify-center">
<span
className={`relative flex h-7 w-7 items-center justify-center rounded-full ${
!inMonth
? "text-[#c9d2e2]"
: hasTraining
? "bg-[#ffbc11] font-semibold text-[#163b73]"
: hasEvent
? "bg-[#1e3f9a] font-semibold text-white"
: ""
}`}
>
{format(day, "d")}
</span>
{items.length > 0 && !hasTraining && inMonth ? (
<span className="absolute bottom-[-5px] h-1.5 w-1.5 rounded-full bg-[#1e3f9a]" />
) : null}
{items.length > 0 && hasTraining && inMonth ? (
<span className="absolute bottom-[-5px] h-1.5 w-1.5 rounded-full bg-[#ffbc11]" />
) : null}
</div>
);
})}
</div>
</div>
<div className="mt-4 flex items-center gap-5 text-[12px] font-medium text-[#45608f]">
<div className="flex items-center gap-2">
<span className="h-2.5 w-2.5 rounded-full bg-[#1e3f9a]" />
<span>Sự kiện</span>
</div>
<div className="flex items-center gap-2">
<span className="h-2.5 w-2.5 rounded-full bg-[#ffbc11]" />
<span>Đào tạo</span>
</div>
</div>
{highlightedEvent ? (
<div className="mt-4 rounded-[16px] bg-[#f7f9fd] p-3.5 text-[12px] leading-5 text-[#3d547f]">
<div className="flex items-start gap-3">
<span
className={`mt-1 h-2.5 w-2.5 rounded-full ${
isTrainingEvent(highlightedEvent) ? "bg-[#ffbc11]" : "bg-[#1e3f9a]"
}`}
/>
<p className="line-clamp-3">{highlightedEvent.title}</p>
</div>
</div>
) : null}
</aside>
);
}
export default EventsCalendar
\ No newline at end of file
export default EventsCalendar;
import { useGetEvents } from "@/api/endpoints/event";
import { EventApiResponse, EventItem } from "@/api/types/event";
'use client';
import ImageNext from "@/components/shared/image-next";
import { Spinner } from "@/components/ui/spinner";
import { ChevronsRight } from "lucide-react"
import Link from "next/link"
import BASE_URL from "@/links/index";
import CardEvent from "./components/card-event";
import stripImagesAndHtml from "@/helpers/stripImageAndHtml";
import {
type AdminNewsItem,
getAdminNewsSeed,
} from "@/mockdata/admin-news";
import dayjs from "dayjs";
import Link from "next/link";
const eventItems = getAdminNewsSeed()
.filter(
(item) =>
item.type === "tintuc" &&
item.header_category_id === "activity-events" &&
!item.is_hidden &&
item.started_at,
)
.sort(
(left, right) =>
new Date(left.started_at).getTime() - new Date(right.started_at).getTime(),
);
function formatEventDate(item: AdminNewsItem) {
return dayjs(item.started_at || item.published_at || item.created_at).format("DD/MM/YYYY");
}
function Events() {
const { data, isLoading } = useGetEvents<EventApiResponse>();
const [featuredEvent, ...sideEvents] = eventItems;
if (!featuredEvent) return null;
return (
<div className="flex-1 bg-[#063e8e] p-5">
<div className="flex justify-between items-center">
<h2 className="text-[18px] sm:text-[20px] font-bold uppercase text-[#e8c518]">
<div className="flex-1 rounded-[28px] bg-linear-to-br from-[#14488f] to-[#2d67bf] p-4 text-white shadow-[0_18px_38px_rgba(16,61,130,0.24)] md:p-5">
<div className="mb-4 flex items-start justify-between gap-4">
<div>
<h2 className="text-[28px] font-extrabold uppercase tracking-tight text-white md:text-[34px]">
Sự kiện sắp diễn ra
</h2>
<Link href="/hoat-dong/su-kien" className="text-[#e8c518] text-sm sm:text-base">
<ChevronsRight />
<div className="mt-2.5 h-[4px] w-[60px] rounded-full bg-[#f7b500]" />
</div>
<Link
href="/hoat-dong/su-kien"
className="pt-1.5 text-sm font-semibold text-[#ffd34f] transition-colors hover:text-white"
>
Xem sự kiện
</Link>
</div>
<hr className="border-[#e8c518] mb-4" />
<div className="flex flex-col md:flex-row gap-5">
{isLoading ? (
<div className="flex flex-col justify-center items-center w-full min-h-[180px] sm:min-h-[220px] p-3">
<Spinner />
<div className="grid items-stretch gap-3 xl:grid-cols-[minmax(0,1.02fr)_minmax(270px,0.98fr)]">
<Link
href="/hoat-dong/su-kien"
className="flex h-full flex-col overflow-hidden rounded-[22px] bg-white text-[#20408f] shadow-[0_14px_28px_rgba(10,39,95,0.18)]"
>
<div className="h-[220px] overflow-hidden md:h-[235px] xl:h-[248px]">
<ImageNext
src={featuredEvent.thumbnail?.url ?? "/thumbnail.png"}
alt={featuredEvent.thumbnail?.alt || featuredEvent.title}
width={720}
height={520}
className="h-full w-full object-cover"
/>
</div>
<div className="p-3 pt-2.5">
<h3 className="line-clamp-2 text-[16px] font-extrabold uppercase leading-[1.28] text-[#22459b] md:text-[18px]">
{featuredEvent.title}
</h3>
<p className="mt-1.5 text-[13px] text-[#90a0bd]">{formatEventDate(featuredEvent)}</p>
</div>
) : (
<>
{data?.responseData.rows.slice(0, 1).map((event: EventItem) => (
</Link>
<div className="flex h-full flex-col gap-3">
{sideEvents.slice(0, 4).map((item) => (
<Link
key={event.id}
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"
key={item.id}
href="/hoat-dong/su-kien"
className="flex flex-1 items-center gap-3 rounded-[18px] bg-white/10 p-2.5 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.08)] backdrop-blur-sm transition-colors hover:bg-white/14"
>
<div className="w-full aspect-3/2 overflow-hidden">
<div className="h-[64px] w-[64px] shrink-0 overflow-hidden rounded-[12px]">
<ImageNext
src={`${BASE_URL.imageEndpoint}${event.image}`}
alt={event.name}
width={600}
height={400}
sizes="(max-width:768px) 100vw,50vw"
className="w-full h-full object-cover"
src={item.thumbnail?.url ?? "/thumbnail.png"}
alt={item.thumbnail?.alt || item.title}
width={160}
height={160}
className="h-full w-full object-cover"
/>
</div>
<div className="flex-1">
<p className="text-[#0056b3] font-bold text-xl line-clamp-2">
{event.name}
</p>
<p className="text-gray-500 text-sm my-1">
{dayjs(event.start_time).format("DD/MM/YYYY")}
</p>
<p className="line-clamp-3 text-justify">{stripImagesAndHtml(event.description)}</p>
<div className="min-w-0">
<h4 className="line-clamp-2 text-[15px] font-semibold leading-[1.35] text-white">
{item.title}
</h4>
<p className="mt-1 text-[12px] text-white/78">{formatEventDate(item)}</p>
</div>
</Link>
))}
<div className="w-full md:w-1/2">
{data?.responseData.rows.slice(0, 4).map((event) => (
<CardEvent key={event.id} event={event} />
))}
</div>
</>
)}
</div>
</div>
)
);
}
export default Events
\ No newline at end of file
export default Events;
'use client';
import { useGetNews } from "@/api/endpoints/news";
import { GetNewsResponseType } from "@/api/types/news";
import ImageNext from "@/components/shared/image-next";
import {
type AdminNewsItem,
getAdminNewsSeed,
} from "@/mockdata/admin-news";
import { getHeaderCategorySeed } from "@/mockdata/header-config";
import dayjs from "dayjs";
import { ChevronRight, Mail, Phone } from "lucide-react";
import Link from "next/link";
import { Swiper, SwiperSlide } from "swiper/react";
import { Autoplay, Grid } from "swiper/modules";
import BASE_URL from "@/links/index";
import { Spinner } from "@/components/ui/spinner";
const FALLBACK_CATEGORY_LINK = "/hoat-dong/tin-tuc";
const headerCategoryMap = new Map(
getHeaderCategorySeed().map((item) => [item.id, item.static_link]),
);
const FEATURED_OVERVIEW_LINK =
headerCategoryMap.get("activity-news") ?? FALLBACK_CATEGORY_LINK;
function getFeaturedNewsItems(items: AdminNewsItem[]) {
return items
.filter(
(item) =>
item.type === "tintuc" &&
item.is_featured &&
!item.is_hidden &&
Boolean(item.thumbnail?.url),
)
.slice(0, 3);
}
function getNewsLink(item: AdminNewsItem) {
return headerCategoryMap.get(item.header_category_id) ?? FALLBACK_CATEGORY_LINK;
}
function getBadgeLabel(item: AdminNewsItem) {
if (item.header_category_id === "activity-events") return "Sự kiện";
const firstTag = item.tagsearch_values.find(Boolean);
if (firstTag) return firstTag;
return "Tin VCCI";
}
const featuredNewsItems = getFeaturedNewsItems(getAdminNewsSeed());
function FeaturedNews() {
const { data, isLoading } = useGetNews<GetNewsResponseType>(
{
pageSize: '10',
}
);
const [primaryItem, ...secondaryItems] = featuredNewsItems;
if (!primaryItem) return null;
return (
<section>
<div className="flex items-center justify-center py-8 px-4">
<div className="flex items-center w-full max-w-4xl">
<div className="flex-1 h-px bg-linear-to-r from-transparent via-gray-300 to-gray-400"></div>
<h1 className="px-6 text-[20px] sm:text-[24px] md:text-[28px] uppercase font-bold text-[#063e8e] whitespace-nowrap">
Tin Nổi Bật
</h1>
<div className="flex-1 h-px bg-linear-to-l from-transparent via-gray-300 to-gray-400"></div>
</div>
</div>
{isLoading ? (
<div className="flex justify-center items-center w-full h-64">
<Spinner />
</div>
) : (
<Swiper
modules={[Autoplay]}
autoplay={{ delay: 4000, disableOnInteraction: false }}
loop
breakpoints={{
0: { slidesPerView: 1.1, spaceBetween: 10 },
640: { slidesPerView: 2, spaceBetween: 16 },
1024: { slidesPerView: 3, spaceBetween: 24 },
}}
className="pb-5"
<section className="py-8 md:py-10">
<div className="w-full">
<div className="mb-8 flex items-start justify-between gap-4">
<div>
<h2 className="text-[28px] font-extrabold uppercase tracking-tight text-[#24469c] md:text-[34px]">
Tin nổi bật
</h2>
<div className="mt-3 h-[5px] w-[68px] rounded-full bg-[#f7b500]" />
</div>
<Link
href={FEATURED_OVERVIEW_LINK}
className="inline-flex items-center gap-2 pt-2 text-base font-semibold text-[#2b56c0] transition-colors hover:text-[#173f9f]"
>
{data?.responseData?.rows.map((news) => (
<SwiperSlide key={news.id}>
<span>Xem tất cả</span>
<ChevronRight className="h-4 w-4" />
</Link>
</div>
<div className="grid gap-5 xl:grid-cols-[minmax(0,1.14fr)_minmax(0,0.96fr)]">
<Link
href={`${news.external_link}`}
className="relative block bg-white shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300"
href={getNewsLink(primaryItem)}
className="group relative block min-h-[260px] overflow-hidden rounded-[24px] bg-[#0d2f5f] shadow-[0_18px_38px_rgba(28,52,120,0.22)] md:min-h-[320px] xl:min-h-[350px]"
>
<div className="relative h-full min-h-[260px] md:min-h-[320px] xl:min-h-[350px]">
<ImageNext
src={`${BASE_URL.imageEndpoint}${news.thumbnail}`}
alt={news.title}
src={primaryItem.thumbnail?.url ?? "/thumbnail.png"}
alt={primaryItem.thumbnail?.alt || primaryItem.title}
width={1200}
height={800}
className="absolute inset-0 h-full w-full object-cover transition-transform duration-500 group-hover:scale-[1.04]"
/>
<div className="absolute inset-0 bg-linear-to-t from-[#26356d] via-[#53669b]/34 to-transparent" />
<div className="relative flex h-full flex-col justify-end p-4 md:p-5">
<span className="mb-2 inline-flex w-fit rounded-[10px] bg-[#ffc400] px-3 py-1 text-sm font-bold text-[#1d3f90]">
{getBadgeLabel(primaryItem)}
</span>
<h3 className="max-w-3xl text-[20px] font-bold leading-[1.28] text-white md:text-[28px] xl:text-[32px]">
{primaryItem.title}
</h3>
<p className="mt-2 text-base font-medium text-white/78 md:text-[17px]">
{dayjs(primaryItem.published_at || primaryItem.created_at).format("DD/MM/YYYY")}
</p>
</div>
</div>
</Link>
<div className="grid gap-4">
<div className="grid gap-4 md:grid-cols-2">
{secondaryItems.map((item) => (
<Link
key={item.id}
href={getNewsLink(item)}
className="group relative block min-h-[195px] overflow-hidden rounded-[20px] bg-[#27447f] shadow-[0_16px_32px_rgba(28,52,120,0.2)] md:min-h-[205px] xl:min-h-[215px]"
>
<div className="relative h-full min-h-[195px] md:min-h-[205px] xl:min-h-[215px]">
<ImageNext
src={item.thumbnail?.url ?? "/thumbnail.png"}
alt={item.thumbnail?.alt || item.title}
width={600}
height={400}
sizes="(max-width:640px) 100vw,(max-width:1024px) 50vw,33vw"
className="w-full aspect-3/2 sm:h-56 md:h-64 object-cover"
height={420}
className="absolute inset-0 h-full w-full object-cover transition-transform duration-500 group-hover:scale-[1.05]"
/>
<div className="absolute bottom-0 left-0 right-0 h-20 md:h-24 bg-linear-to-t from-black/80 to-transparent flex items-center justify-center p-3">
<p className="text-white text-center font-semibold line-clamp-2 text-sm sm:text-base leading-snug">
{news.title}
<div className="absolute inset-0 bg-linear-to-t from-[#5a6796] via-[#405083]/34 to-transparent" />
<div className="relative flex h-full flex-col justify-end p-3.5">
<span className="mb-2 inline-flex w-fit rounded-[10px] bg-[#ffc400] px-3 py-1 text-sm font-bold text-[#1d3f90]">
{getBadgeLabel(item)}
</span>
<h4 className="line-clamp-2 text-[16px] font-bold leading-[1.32] text-white md:text-[17px]">
{item.title}
</h4>
<p className="mt-1.5 text-[15px] font-medium text-white/78 md:text-base">
{dayjs(item.published_at || item.created_at).format("DD/MM/YYYY")}
</p>
</div>
</div>
</Link>
</SwiperSlide>
))}
</Swiper>
)}
</div>
<div className="overflow-hidden rounded-[28px] bg-linear-to-r from-[#214b95] to-[#2b66bb] px-5 py-5 text-white shadow-[0_18px_38px_rgba(28,52,120,0.2)] md:px-7">
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
<div>
<p className="text-[12px] uppercase tracking-[0.4em] text-white/80">
Quảng bá & tiếp cận
</p>
<h3 className="mt-2 max-w-[520px] text-[20px] font-extrabold uppercase leading-[1.02] xl:text-[28px]">
Cộng đồng doanh nghiệp
</h3>
</div>
<div className="w-full max-w-60 rounded-[999px] bg-white px-4 py-3 text-[#173f88] shadow-[0_10px_24px_rgba(8,25,74,0.12)]">
<div className="flex items-center gap-3 text-sm font-medium">
<Mail className="h-5 w-5 shrink-0" />
<span>info@vcci-hcm.org.vn</span>
</div>
<div className="mt-2 flex items-center gap-3 text-sm font-medium">
<Phone className="h-5 w-5 shrink-0" />
<span>+84-28-3932 5171</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
}
......
'use client';
import ImageNext from "@/components/shared/image-next";
import memberImages from "@/constants/memberImages";
import { ChevronsRight } from "lucide-react";
import {
getAdminNewsSeed,
} from "@/mockdata/admin-news";
import Link from "next/link";
import { Autoplay } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
import "swiper/css/grid";
import { GetNewsResponseType, NewsItem } from "@/api/types/news";
import BASE_URL from "@/links/index";
import { useGetNews } from "@/api/endpoints/news";
import { Spinner } from "@/components/ui/spinner";
const Members = () => {
const { data, isLoading } = useGetNews<GetNewsResponseType>(
{
filters: `page_config.code @=ket-noi-hoi-vien`,
}
const memberConnectionItems = getAdminNewsSeed()
.filter(
(item) =>
item.type === "tintuc" &&
!item.is_hidden &&
(item.category_ids.includes("cat-member-connection") ||
item.tagsearch_values.some((tag) => tag.toLowerCase().includes("kết nối hội viên"))),
)
.sort(
(left, right) =>
new Date(right.published_at || right.created_at).getTime() -
new Date(left.published_at || left.created_at).getTime(),
);
function Members() {
const featuredConnection = memberConnectionItems[0];
return (
<section className="flex flex-col lg:flex-row gap-5 pb-10 mb-0">
{/* LEFT: HỘI VIÊN TIÊU BIỂU */}
<aside className="w-full lg:w-1/3 flex-1 bg-[#e8c518] p-5">
<div className="flex justify-between items-center mb-3">
<h2 className="text-xl font-bold uppercase text-[#063e8e]">
<section className="flex flex-col gap-5 pb-8 xl:flex-row xl:items-stretch">
<aside className="flex-1 rounded-[22px] bg-[#f7b500] p-4 shadow-[0_18px_34px_rgba(247,181,0,0.18)] md:p-5">
<div className="flex items-start justify-between gap-3">
<div>
<h2 className="text-[28px] font-extrabold uppercase tracking-tight text-[#20449a] md:text-[34px]">
Hội viên tiêu biểu
</h2>
<div className="mt-2.5 h-[4px] w-[40px] rounded-full bg-white" />
</div>
<Link
href="/danh-ba-hoi-vien"
className="text-[#063e8e] hover:underline text-sm font-medium"
className="pt-1 text-sm font-semibold text-[#1e2f5e] transition-colors hover:text-[#20449a]"
>
<ChevronsRight />
Xem thêm
</Link>
</div>
<hr className="border-[#063e8e] mb-5" />
<div className="mt-4 border-t border-[#e7aa00] pt-5" />
<Swiper
modules={[Autoplay]}
autoplay={{ delay: 4000, disableOnInteraction: false }}
loop
slidesPerView={3}
spaceBetween={16}
breakpoints={{
0: { slidesPerView: 2, spaceBetween: 10 },
640: { slidesPerView: 3, spaceBetween: 16 },
1024: { slidesPerView: 3, spaceBetween: 24 },
}}
className="partner-swiper"
<div className="grid gap-4 sm:grid-cols-3">
{memberImages.slice(0, 3).map((src, index) => (
<div
key={src}
className="rounded-[24px] bg-white p-[7px] shadow-[0_10px_22px_rgba(158,114,0,0.16)]"
>
{memberImages.map((src, i) => (
<SwiperSlide key={i}>
<div className="flex justify-center items-center bg-white rounded-lg shadow p-3">
<div className="flex aspect-[1.06/1] items-center justify-center overflow-hidden rounded-[18px] bg-[#f4f7fb] px-4 py-5">
<ImageNext
src={src}
alt={`member-${i}`}
width={160}
height={160}
sizes="(max-width:640px) 25vw,(max-width:1024px) 15vw,10vw"
className="object-contain w-full h-full"
alt={`Hội viên tiêu biểu ${index + 1}`}
width={320}
height={220}
className="max-h-full max-w-full object-contain"
/>
</div>
</SwiperSlide>
</div>
))}
</Swiper>
</div>
</aside>
{/* RIGHT: KẾT NỐI HỘI VIÊN */}
{isLoading ? (
<div className="flex justify-center items-center w-full h-64">
<Spinner />
</div>
) : (
<aside className="w-full lg:w-[30%] py-5">
<div className="flex justify-between items-center mb-3">
<h2 className="text-xl font-bold uppercase text-[#063e8e]">
<aside className="w-full xl:w-[31%] xl:min-w-[320px]">
<div className="mb-4 flex items-start justify-between gap-3">
<div>
<h2 className="text-[28px] font-extrabold uppercase tracking-tight text-[#24469c] md:text-[34px]">
Kết nối hội viên
</h2>
<div className="mt-2.5 h-[4px] w-[40px] rounded-full bg-[#f7b500]" />
</div>
<hr className="border-[#063e8e] mb-5" />
<Swiper
modules={[Autoplay]}
autoplay={{ delay: 4000, disableOnInteraction: false }}
loop
className="partner-swiper"
</div>
{featuredConnection ? (
<Link
href="/danh-ba-hoi-vien"
className="block overflow-hidden rounded-[20px] shadow-[0_16px_32px_rgba(31,59,124,0.12)]"
>
{data?.responseData.rows.map((news: NewsItem) => (
<SwiperSlide key={news.id}>
<a href={`${news.external_link}`}>
<div className="w-full aspect-3/2 relative overflow-hidden mb-5">
<div className="aspect-[1.25/1] overflow-hidden rounded-[20px]">
<ImageNext
src={`${BASE_URL.imageEndpoint}${news.thumbnail}`}
alt={news.title}
width={600}
height={400}
sizes="(max-width:768px) 100vw,50vw"
className="w-full h-full object-cover"
src={featuredConnection.thumbnail?.url ?? "/thumbnail.png"}
alt={featuredConnection.thumbnail?.alt || featuredConnection.title}
width={520}
height={420}
className="h-full w-full object-cover"
/>
<div className="absolute bg-white opacity-90 bottom-5 left-5 right-5 p-5">
<p className="text-[#063e8e] font-semibold text-sm sm:text-base z-10 line-clamp-3">
{news.title}
</p>
</div>
</div>
</a>
</SwiperSlide>
))}
</Swiper>
</Link>
) : null}
</aside>
)}
</section>
);
};
}
export default Members;
import { useGetNews } from "@/api/endpoints/news";
import { GetNewsResponseType, NewsItem } from "@/api/types/news";
'use client';
import ImageNext from "@/components/shared/image-next";
import Link from "next/link";
import BASE_URL from "@/links/index";
import { ChevronsRight } from "lucide-react";
import { useState } from "react";
import stripImagesAndHtml from "@/helpers/stripImageAndHtml";
import CardNews from "./components/card-news";
import { Spinner } from "@/components/ui/spinner";
import {
type AdminNewsItem,
getAdminNewsSeed,
} from "@/mockdata/admin-news";
import dayjs from "dayjs";
import Link from "next/link";
import { useMemo, useState } from "react";
const News = () => {
const [tab, setTab] = useState("all");
const tabs = [
{ id: "all", label: "Tất cả" },
{ id: "tin-vcci", label: "Tin VCCI" },
{ id: "tin-kinh-te", label: "Tin Kinh Tế" },
{ id: "chuyen-de", label: "Chuyên Đề" },
];
const allNewsItems = getAdminNewsSeed().filter(
(item) => item.type === "tintuc" && !item.is_hidden,
);
function getTabLabel(item: AdminNewsItem) {
const tags = item.tagsearch_values.map((tag) => tag.toLowerCase());
if (tags.some((tag) => tag.includes("kinh tế") || tag.includes("vĩ mô"))) {
return "Tin Kinh Tế";
}
if (tags.some((tag) => tag.includes("chuyên đề") || tag.includes("cẩm nang"))) {
return "Chuyên Đề";
}
return "Tin VCCI";
}
function matchesTab(item: AdminNewsItem, tab: string) {
if (tab === "all") return true;
const tags = item.tagsearch_values.map((value) => value.toLowerCase());
if (tab === "tin-vcci") {
return tags.some((tag) => tag.includes("tin vcci") || tag.includes("hợp tác"));
}
if (tab === "tin-kinh-te") {
return tags.some((tag) => tag.includes("kinh tế") || tag.includes("vĩ mô"));
}
const { data: newsSpecial, isLoading: isLoadingSpecial } = useGetNews<GetNewsResponseType>({ pageSize: '1' });
const { data: newsFilters, isLoading: isLoadingFilters } = useGetNews<GetNewsResponseType>(
{
pageSize: '5',
filters: tab === "all" ? `` : `page_config.code @=${tab}`,
if (tab === "chuyen-de") {
return tags.some((tag) => tag.includes("chuyên đề") || tag.includes("cẩm nang"));
}
return true;
}
function News() {
const [tab, setTab] = useState("all");
const filteredItems = useMemo(
() => allNewsItems.filter((item) => matchesTab(item, tab)),
[tab],
);
const featuredArticle = filteredItems[0] ?? allNewsItems[0];
const listArticles = filteredItems.slice(1, 5);
if (!featuredArticle) return null;
return (
<div className="flex-1">
<div className="flex justify-between items-center">
<Link
href="/thong-tin-truyen-thong/tin-vcci/"
className="text-[18px] sm:text-[20px] font-semibold uppercase text-[#063e8e]"
>
<div className="mb-6 flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
<div>
<h2 className="text-[28px] font-extrabold uppercase tracking-tight text-[#24469c] md:text-[34px]">
Tin tức
</Link>
<Link
href="/thong-tin-truyen-thong/tin-vcci/"
className="text-[#063e8e] text-sm sm:text-base"
>
<ChevronsRight />
</Link>
</h2>
<div className="mt-3 h-[5px] w-[68px] rounded-full bg-[#f7b500]" />
</div>
<hr className="border-[#063e8e] mb-4" />
<div className="flex flex-col md:flex-row gap-5">
{isLoadingSpecial ? (
<div className="flex justify-center items-center flex-col w-full md:w-1/2 min-h-[180px] sm:min-h-[220px] gap-3 mb-3">
<Spinner />
<div className="flex flex-wrap gap-3 xl:justify-end">
{tabs.map((item) => {
const active = item.id === tab;
return (
<button
key={item.id}
type="button"
onClick={() => setTab(item.id)}
className={`rounded-full px-5 py-2.5 text-[14px] font-semibold transition-all ${
active
? "bg-[#1f5ba9] text-white shadow-[0_10px_20px_rgba(31,91,169,0.18)]"
: "bg-[#f4f7fb] text-[#7f8eab] hover:bg-[#eaf0f8]"
}`}
>
{item.label}
</button>
);
})}
</div>
</div>
) : (
newsSpecial?.responseData.rows
.slice(0, 1)
.map((news: NewsItem) => (
<div className="grid gap-4 xl:grid-cols-[minmax(0,1.02fr)_minmax(320px,0.98fr)]">
<div>
<Link
key={news.id}
href={`${news.external_link}`}
className="flex flex-col w-full md:w-1/2 min-h-[180px] sm:min-h-[220px] gap-3 mb-3 bg-white"
href="/hoat-dong/tin-tuc"
className="block h-full overflow-hidden rounded-[22px] border border-[#dbe4f2] bg-white shadow-[0_8px_24px_rgba(31,59,124,0.08)]"
>
<div className="w-full aspect-3/2 overflow-hidden">
<div className="aspect-[1.75/1] overflow-hidden">
<ImageNext
src={`${BASE_URL.imageEndpoint}${news.thumbnail}`}
alt={news.title}
width={600}
height={400}
sizes="(max-width:768px) 100vw,50vw"
className="w-full h-full object-cover"
src={featuredArticle.thumbnail?.url ?? "/thumbnail.png"}
alt={featuredArticle.thumbnail?.alt || featuredArticle.title}
width={720}
height={580}
className="h-full w-full object-cover"
/>
</div>
<div className="flex-1 p-5 pt-1">
<p className="text-[#063E8E] font-bold pb-2 text-xl line-clamp-2">
{news.title}
<div className="space-y-1.5 p-3">
<span className="inline-flex text-[14px] font-bold text-[#e2a500]">
{getTabLabel(featuredArticle)}
</span>
<h3 className="line-clamp-2 text-[16px] font-bold leading-[1.28] text-[#20408f] md:text-[17px]">
{featuredArticle.title}
</h3>
<p className="line-clamp-2 text-[13px] leading-[1.45] text-[#6c7b96]">
{stripImagesAndHtml(featuredArticle.summary)}
</p>
<p className="text-[14px] text-[#8a9bb6]">
{dayjs(featuredArticle.published_at || featuredArticle.created_at).format("DD/MM/YYYY")}
</p>
<p className="line-clamp-4 text-justify">{stripImagesAndHtml(news.description)}</p>
</div>
</Link>
))
)}
<div className="w-full md:w-1/2">
<div className="flex flex-wrap gap-2 sm:gap-3 mb-5">
<button
className={`flex-1 py-[3px] text-sm transition-colors cursor-pointer ${tab === "all"
? " bg-[#d3d3d3] text-[#063e8e] font-semibold"
: "border-gray-300 text-[#363636] bg-[#e8e8e8] hover:bg-[#e8e8e8] hover:text-[#063e8e] font-semibold"
}`}
onClick={() => setTab("all")}>
Tất cả
</button>
<button
className={`flex-1 py-[3px] text-[14px] transition-colors cursor-pointer ${`tin-vcci` === tab
? "bg-[#d3d3d3] text-[#063e8e] font-semibold"
: "border-gray-300 text-[#363636] bg-[#e8e8e8] hover:bg-[#e8e8e8] hover:text-[#063e8e] font-semibold"
}`}
onClick={() => setTab("tin-vcci")}
>
Tin VCCI
</button>
<button
className={`flex-1 py-[3px] text-[14px] transition-colors cursor-pointer ${`tin-kinh-te` === tab
? "bg-[#d3d3d3] text-[#063e8e] font-semibold"
: "border-gray-300 text-[#363636] bg-[#e8e8e8] hover:bg-[#e8e8e8] hover:text-[#063e8e] font-semibold"
}`}
onClick={() => setTab("tin-kinh-te")}
>
Tin Kinh Tế
</button>
<button
className={`flex-1 py-[3px] text-[14px] transition-colors cursor-pointer ${`chuyen-de` === tab
? "bg-[#d3d3d3] text-[#063e8e] font-semibold"
: "border-gray-300 text-[#363636] bg-[#e8e8e8] hover:bg-[#e8e8e8] hover:text-[#063e8e] font-semibold"
}`}
onClick={() => setTab("chuyen-de")}
>
Chuyên Đề
</button>
</div>
{isLoadingFilters ? (
<div className="flex justify-center py-10">
<Spinner />
<div className="xl:flex xl:h-full xl:flex-col">
<div className="space-y-3 xl:flex xl:flex-1 xl:flex-col">
{listArticles.map((news) => (
<Link
key={news.id}
href="/hoat-dong/tin-tuc"
className="block rounded-[18px] border border-[#dbe4f2] bg-white px-4 py-2.5 shadow-[0_8px_24px_rgba(31,59,124,0.08)] transition-all hover:-translate-y-0.5 hover:shadow-[0_14px_28px_rgba(31,59,124,0.12)] xl:flex-1"
>
<h4 className="line-clamp-2 text-[15px] font-bold leading-[1.28] text-[#21408f]">
{news.title}
</h4>
<p className="mt-1 text-[13px] text-[#8a9bb6]">
{dayjs(news.published_at || news.created_at).format("DD/MM/YYYY")}
</p>
</Link>
))}
</div>
) : (
newsFilters?.responseData?.rows.slice(0, 4).map((news) => (
<CardNews key={news.id} news={news} />
))
)}
</div>
</div>
</div>
......
import { useGetNews } from "@/api/endpoints/news";
import { GetNewsResponseType, NewsItem } from "@/api/types/news";
import ImageNext from "@/components/shared/image-next";
import { Spinner } from "@/components/ui/spinner";
import { ChevronsRight } from "lucide-react";
'use client';
import {
type AdminNewsItem,
getAdminNewsSeed,
} from "@/mockdata/admin-news";
import dayjs from "dayjs";
import { ChevronRight } from "lucide-react";
import Link from "next/link";
import BASE_URL from "@/links/index";
import CardNews from "./card-news";
const PolicyAndLaws = () => {
const { data, isLoading } = useGetNews<GetNewsResponseType>(
{
pageSize: '5',
filters: `page_config.code @=phap-luat`,
}
const policyItems = getAdminNewsSeed()
.filter(
(item) =>
item.type === "tintuc" &&
!item.is_hidden &&
(item.category_ids.includes("cat-policy-law") ||
item.category_ids.includes("cat-policy") ||
item.tagsearch_values.some((tag) => {
const normalized = tag.toLowerCase();
return normalized.includes("chính sách") || normalized.includes("pháp luật");
})),
)
.sort(
(left, right) =>
new Date(right.published_at || right.created_at).getTime() -
new Date(left.published_at || left.created_at).getTime(),
);
function formatPublishDate(item: AdminNewsItem) {
return dayjs(item.published_at || item.created_at).format("DD/MM/YYYY");
}
function PolicyAndLaws() {
const [featuredItem, ...listItems] = policyItems;
if (!featuredItem) return null;
return (
<div className="flex-1">
<div className="flex justify-between items-center">
<section className="flex-1">
<div className="mb-4 flex items-center justify-between gap-3">
<div>
<h2 className="text-[28px] font-extrabold uppercase tracking-tight text-[#24469c] md:text-[34px]">
Chính sách & pháp luật
</h2>
<div className="mt-2.5 h-[4px] w-[40px] rounded-full bg-[#f7b500]" />
</div>
<Link
href="/thong-tin-truyen-thong/phap-luat"
className="text-[18px] sm:text-[20px] font-bold uppercase text-[#063e8e]"
className="text-[#24469c] transition-colors hover:text-[#1b55a1]"
>
Chính sách & pháp luật
<ChevronRight className="h-5 w-5" />
</Link>
</div>
<div className="space-y-2.5">
{[featuredItem, ...listItems.slice(0, 2)].map((item, index) => (
<Link
key={item.id}
href="/thong-tin-truyen-thong/phap-luat"
className="text-[#063e8e] text-sm sm:text-base"
className={`flex gap-3 rounded-[14px] px-0.5 py-1 transition-colors hover:bg-[#f8fafe] ${
index === 0 ? "pt-0.5" : ""
}`}
>
<ChevronsRight />
</Link>
</div>
<hr className="border-[#063e8e] mb-4" />
<div className="pt-2">
{isLoading ? (
<div className="container w-full h-[80vh] flex justify-center items-center">
<Spinner />
</div>
) : (
<>
{data?.responseData.rows
.slice(0, 1)
.map((news: NewsItem) => (
<Link key={news.id} href={`${news.external_link}`}>
<div className="w-full aspect-3/2 relative overflow-hidden mb-5">
<ImageNext
src={`${BASE_URL.imageEndpoint}${news.thumbnail}`}
alt={news.title}
width={600}
height={400}
sizes="(max-width:768px) 100vw,50vw"
className="w-full h-full object-cover"
/>
<div className="absolute bg-white opacity-80 bottom-5 left-5 right-5 p-5">
<p className="text-[#063e8e] font-semibold text-sm sm:text-base z-10 line-clamp-3">
{news.title}
</p>
</div>
<span className="mt-1 h-[40px] w-[2px] shrink-0 rounded-full bg-[#f7b500]" />
<div className="min-w-0">
<h3 className="line-clamp-2 text-[15px] leading-[1.45] text-[#264798] md:text-[16px]">
{item.title}
</h3>
<p className="mt-1.5 text-[13px] text-[#9aa8c1]">{formatPublishDate(item)}</p>
</div>
</Link>
))}
{data?.responseData.rows.slice(0, 3).map((news) => (
<CardNews key={news.id} news={news} />
))}
</>
)}
</div>
</div>
</section>
);
}
......
'use client';
import ImageNext from "@/components/shared/image-next";
import Link from "next/link";
const quickLinks = [
{
href: "https://vcci-hcm.org.vn/lien-ket-nhanh/doanh-nghiep-kien-nghi-ve-chinh-sach-va-phap-luat/",
label: "Doanh nghiệp kiến nghị về chính sách và pháp luật",
},
{
href: "https://vcci-hcm.org.vn/lien-ket-nhanh/cam-nang-huong-dan-dau-tu-kinh-doanh-tai-viet-nam-2023/",
label: "Cẩm nang hướng dẫn đầu tư kinh doanh tại Việt Nam",
},
];
function QuickLinks() {
return (
<aside className="w-full lg:w-[30%]">
<div className="flex justify-between items-center">
<h2 className="text-[18px] sm:text-[20px] font-semibold uppercase text-[#063e8e]">
<aside className="w-full xl:grid xl:w-[32%] xl:grid-rows-[0.74fr_0.88fr] xl:gap-4">
<div className="rounded-[22px] border border-[#dbe4f2] bg-white p-4 shadow-[0_8px_24px_rgba(31,59,124,0.08)] xl:h-full">
<h2 className="text-[28px] md:text-[34px] font-extrabold uppercase tracking-tight text-[#24469c]">
Liên kết nhanh
</h2>
</div>
<hr className="border-[#063e8e] mb-4" />
<div className="space-y-2 text-[#063e8e] text-sm md:text-base pb-10">
<div>
<div className="mt-3 h-[5px] w-[68px] rounded-full bg-[#f7b500]" />
<div className="mt-4 space-y-2.5">
{quickLinks.map((item) => (
<Link
className="text-[#363636]"
href="https://vcci-hcm.org.vn/lien-ket-nhanh/cam-nang-huong-dan-dau-tu-kinh-doanh-tai-viet-nam-2023/"
key={item.href}
href={item.href}
className="flex items-start gap-3 text-[15px] leading-[1.32] text-[#556684] transition-colors hover:text-[#21408f]"
>
🔗 Cẩm nang hướng dẫn đầu tư kinh doanh tại Việt Nam
<span className="mt-1 text-[#e2a500]"></span>
<span>{item.label}</span>
</Link>
))}
</div>
</div>
<div>
<Link
className="text-[#363636]"
href="https://vcci-hcm.org.vn/lien-ket-nhanh/doanh-nghiep-kien-nghi-ve-chinh-sach-va-phap-luat/"
href="https://hardwaretools.com.vn/"
className="mt-4 block overflow-hidden rounded-[28px] shadow-[0_12px_28px_rgba(31,59,124,0.14)] xl:mt-0 xl:h-full"
>
🔗 Doanh nghiệp kiến nghị về chính sách và pháp luật
</Link>
</div>
</div>
<div>
<Link href="https://hardwaretools.com.vn/">
<div className="aspect-[1.55/1] overflow-hidden xl:h-full xl:aspect-auto">
<ImageNext
src="/home/20-2048x1365.webp"
alt="banner"
alt="Liên kết nhanh"
width={2048}
height={1365}
className="h-full w-full object-cover"
/>
</Link>
</div>
</Link>
</aside>
);
}
......
'use client';
import ImageNext from "@/components/shared/image-next";
import partnerImages from "@/constants/partnerImages";
import { ChevronsRight } from "lucide-react";
import { ChevronRight, Play } from "lucide-react";
import Link from "next/link";
import { Autoplay, Grid } from "swiper/modules";
import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";
import "swiper/css/grid";
const videos = [
{
embedSrc: "https://www.youtube.com/embed/J0Iz0iGuAXY",
title: "VCCI-HCM 2025 IN REVIEW (ENGLISH VERSION)",
thumbnail:
"https://img.youtube.com/vi/J0Iz0iGuAXY/hqdefault.jpg",
},
{
embedSrc: "https://www.youtube.com/embed/_OnnGWv2ehM",
title: "Hội nghị Hội viên VCCI - Gala Mừng Xuân 2026",
thumbnail:
"https://img.youtube.com/vi/_OnnGWv2ehM/hqdefault.jpg",
},
];
const VideoAndPartners = () => {
function VideoAndPartners() {
return (
<section className="flex flex-col lg:flex-row gap-5 pb-10">
{/* LEFT: VIDEO */}
<div className="flex flex-col flex-1">
<div className="flex justify-between items-center mb-3">
<h2 className="text-xl font-bold uppercase text-[#063e8e]">Video</h2>
<section className="flex flex-col gap-6 pb-10 xl:flex-row xl:items-start">
<div className="flex-1">
<div className="mb-5 flex items-start justify-between gap-3">
<div>
<h2 className="text-[28px] font-extrabold uppercase tracking-tight text-[#24469c] md:text-[34px]">
Video
</h2>
<div className="mt-2.5 h-[4px] w-[40px] rounded-full bg-[#f7b500]" />
</div>
<Link
href="/video"
className="text-[#063e8e] hover:underline text-sm font-medium"
className="pt-1 text-[#24469c] transition-colors hover:text-[#1b55a1]"
>
<ChevronsRight />
<ChevronRight className="h-5 w-5" />
</Link>
</div>
<hr className="border-[#063e8e] mb-5" />
<div className="flex flex-col md:flex-row gap-4 md:gap-6">
{[
{
src: "https://www.youtube.com/embed/J0Iz0iGuAXY",
title: "VCCI-HCM 2024 IN REVIEW (ENGLISH VERSION)",
},
{
src: "https://www.youtube.com/embed/_OnnGWv2ehM",
title: "Hội nghị Hội viên VCCI - Gala Mừng Xuân Ất Tỵ 2025",
},
].map((video, i) => (
<div key={i} className="w-full md:w-1/2">
<div className="aspect-video rounded-lg overflow-hidden shadow">
<iframe
className="w-full h-full font-bold"
src={video.src}
title={video.title}
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerPolicy="strict-origin-when-cross-origin"
allowFullScreen
<div className="grid gap-4 md:grid-cols-2">
{videos.map((video) => (
<a
key={video.embedSrc}
href={video.embedSrc.replace("/embed/", "/watch?v=")}
target="_blank"
rel="noreferrer"
className="overflow-hidden rounded-[16px] border border-[#e5ebf4] bg-white shadow-[0_10px_22px_rgba(31,59,124,0.08)] transition-all hover:-translate-y-0.5 hover:shadow-[0_16px_30px_rgba(31,59,124,0.12)]"
>
<div className="group relative aspect-[1.95/1] overflow-hidden">
<ImageNext
src={video.thumbnail}
alt={video.title}
width={640}
height={440}
className="h-full w-full object-cover transition-transform duration-500 group-hover:scale-[1.03]"
/>
<div className="absolute inset-0 bg-black/18" />
<div className="absolute inset-0 flex items-center justify-center">
<span className="flex h-12 w-12 items-center justify-center rounded-full bg-white/92 text-[#2a4ea3] shadow-[0_10px_26px_rgba(0,0,0,0.18)]">
<Play className="ml-1 h-5 w-5 fill-current" />
</span>
</div>
</div>
<p className="mt-2 text-sm text-gray-700 font-medium">
<div className="px-4 py-2.5">
<p className="line-clamp-2 text-[14px] font-semibold leading-[1.32] text-[#264798] md:text-[15px]">
{video.title}
</p>
</div>
</a>
))}
</div>
</div>
{/* RIGHT: ĐỐI TÁC */}
<aside className="w-full lg:w-[30%]">
<div className="flex justify-between items-center mb-3">
<h2 className="text-xl font-bold uppercase text-[#063e8e]">
<aside className="w-full xl:w-[43%]">
<div className="mb-5 flex items-start justify-between gap-3">
<div>
<h2 className="text-[28px] font-extrabold uppercase tracking-tight text-[#24469c] md:text-[34px]">
Đối tác
</h2>
<div className="mt-2.5 h-[4px] w-[40px] rounded-full bg-[#f7b500]" />
</div>
</div>
<hr className="border-[#063e8e] mb-5" />
<div className="pb-10">
<Swiper
modules={[Autoplay, Grid]}
autoplay={{ delay: 4000, disableOnInteraction: false }}
loop
grid={{ rows: 2, fill: "row" }}
slidesPerView={3}
spaceBetween={16}
breakpoints={{
0: { slidesPerView: 3, spaceBetween: 10, grid: { rows: 2 } },
640: { slidesPerView: 3, spaceBetween: 16, grid: { rows: 2 } },
1024: { slidesPerView: 3, spaceBetween: 24, grid: { rows: 2 } },
}}
className="partner-swiper"
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3">
{partnerImages.slice(0, 6).map((src, index) => (
<div
key={src}
className="flex h-[96px] items-center justify-center rounded-[14px] border border-[#edf1f7] bg-white px-5 py-4 shadow-[0_8px_20px_rgba(31,59,124,0.05)]"
>
{partnerImages.map((src, i) => (
<SwiperSlide key={i}>
<div className="flex justify-center items-center bg-white rounded-lg shadow p-3 w-[120px] h-[120px]">
<ImageNext
src={src}
alt={`partner-${i}`}
width={120}
height={120}
className="object-contain w-full h-full"
alt={`Đối tác ${index + 1}`}
width={140}
height={72}
className="max-h-full w-full object-contain"
/>
</div>
</SwiperSlide>
))}
</Swiper>
</div>
</aside>
</section>
);
};
}
export default VideoAndPartners;
'use client'
import Link from 'next/link'
import ImageNext from '@/components/shared/image-next';
import FeaturedNews from "./components/featured-news";
import QuickLinks from "./components/quick-links";
import News from "./components/news";
......@@ -18,9 +16,9 @@ const Page = () => {
<div>
<Banner />
{/* 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-6">
<FeaturedNews />
<div>
{/* <div>
<Link href="https://hardwaretools.com.vn/">
<ImageNext
src="/home/Standard-Banner-1-2024.png.webp"
......@@ -29,21 +27,21 @@ const Page = () => {
height={720}
/>
</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 pb-16 gap-5 mb-0">
<News />
<QuickLinks />
</section >
<section className="flex flex-col lg:flex-row gap-5 pb-10 mb-0" >
<section className="flex flex-col gap-5 xl:flex-row xl:items-stretch" >
<Events />
<EventsCalendar />
</section >
<div className="flex flex-col lg:flex-row gap-5 pb-10 mb-0" >
<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">
<ImageNext
src="/home/Standard-Banner-1-2024.png.webp"
......@@ -52,7 +50,7 @@ const Page = () => {
height={720}
/>
</Link>
</div>
</div> */}
<section className="flex flex-col md:flex-row gap-5 pt-8">
<BusinessOpportunities />
......@@ -60,7 +58,7 @@ const Page = () => {
</section>
</div>
<div className="w-full lg:w-[30%] justify-center items-start flex">
{/* <div className="w-full lg:w-[30%] justify-center items-start flex">
<Link href="https://smartgara.ecaraid.com/">
<ImageNext
src="/home/eCarAid_web_banner_600x400.webp"
......@@ -69,7 +67,7 @@ const Page = () => {
height={400}
/>
</Link>
</div>
</div> */}
</div >
<Members />
......
This diff is collapsed.
This diff is collapsed.
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