Commit 695425f5 authored by Vũ Đình Nguyên's avatar Vũ Đình Nguyên

Merge branch 'develop' into 'tumlumtumla'

# Conflicts:
#   src/app/(main)/_lib/layout/header.tsx
#   src/app/(main)/thong-tin-truyen-thong/an-pham/page.tsx
parents 74a04b88 cbfd9322
......@@ -164,20 +164,34 @@ function Header() {
</a>
<div className="absolute left-0 top-full hidden group-hover:block bg-[#124588]/98 text-white text-[14px] font-[500] min-w-[280px] shadow-lg">
<div className="flex flex-col">
<div className="px-5 py-3 hover:bg-[#e8c518]/80 cursor-pointer">
<Link
href={
"/dai-dien-gioi-chu/chuc-nang-dai-dien-nguoi-su-dung-lao-dong"
}
className="px-5 py-3 hover:bg-[#e8c518]/80 cursor-pointer"
>
Chức Năng Đại Diện Người Sử Dụng Lao Động
</div>
<div className="px-5 py-3 hover:bg-[#e8c518]/80 cursor-pointer">
</Link>
<Link
href={"/dai-dien-gioi-chu/tap-huan-nsdld"}
className="px-5 py-3 hover:bg-[#e8c518]/80 cursor-pointer"
>
Sự Kiện – Tập Huấn NSDLĐ
</div>
<div className="px-5 py-3 hover:bg-[#e8c518]/80 cursor-pointer">
</Link>
<Link
href={"/dai-dien-gioi-chu/tin-lien-quan"}
className="px-5 py-3 hover:bg-[#e8c518]/80 cursor-pointer"
>
Tin Liên Quan
</div>
</Link>
<div className="relative group/submenu">
<div className="px-5 py-3 hover:bg-[#e8c518]/80 cursor-pointer flex justify-between items-center">
<Link
href={"/dai-dien-gioi-chu/chu-de"}
className="px-5 py-3 hover:bg-[#e8c518]/80 cursor-pointer flex justify-between items-center"
>
Chủ Đề <span className="ml-2 text-xs"></span>
</div>
</Link>
<div className="absolute left-full top-0 hidden group-hover/submenu:block bg-[#124588]/98 text-white text-[14px] font-[500] min-w-[220px] shadow-lg">
<div className="px-5 py-3 hover:bg-[#e8c518]/80 cursor-pointer">
Quan Hệ Lao Động
......
"use client";
import ListCategory from "@/components/base/list-category";
import { MEDIA_INFORMATION_CATEGORIES } from "@/constants/categories";
import Image from "next/image";
import Link from "next/link";
import { notFound, useParams } from "next/navigation";
import Calendar from "../components/calendar";
const publications = [
{
id: "huong-dan-dau-tu-2024",
title: "Cẩm nang Hướng dẫn đầu tư kinh doanh tại Việt Nam",
date: "18/10/2023",
link: "https://vcci-hcm.org.vn/wp-content/uploads/2023/10/Doing-Business-in-Vietnam-2023_Upload.pdf",
title_link: "“DOING BUSINESS IN VIETNAM 2023”",
img: "/an-pham/A-Guide-2023_Cover-725x1024.webp",
},
{
id: "connections-2022-2023",
title: "Danh bạ Hội viên CONNECTIONS 2022-2023",
date: "19/01/2023",
link: "https://vcci-hcm.org.vn/wp-content/uploads/2022/12/Danh-ba-HV-Connections_2022-2023.pdf",
title_link: "“DANH BẠ HỘI VIÊN CONNECTIONS 2022-2023”",
img: "/an-pham/Trang-bia_Connections_2022-2023-725x1024.jpg.webp",
},
{
id: "chuyen-doi-so",
title: "Chuyển đổi số – Động lực phục hồi và phát triển kinh tế",
date: "19/01/2023", // ← Sửa: 119 → 19
link: "https://vcci-hcm.org.vn/wp-content/uploads/2022/12/CHUYEN-DOI-SO-2022_Final_19.12.2022.pdf",
title_link: "“CHUYỂN ĐỔI SỐ – ĐỘNG LỰC PHỤC HỒI VÀ PHÁT TRIỂN KINH TẾ”",
img: "/an-pham/Trang-bia_Chuyen-doi-so_2022-750x1024.webp",
},
{
id: "huong-dan-dau-tu-2021",
title: "Cẩm nang Hướng dẫn đầu tư kinh doanh tại Việt Nam 2021",
date: "14/03/2022",
link: "https://vcci-hcm.org.vn/wp-content/uploads/2023/10/Doing-Business-in-Vietnam-2023_Upload.pdf",
title_link: "“DOING BUSINESS IN VIETNAM 2021”",
img: "/an-pham/doing-in-business-cover-1-1.webp",
},
{
id: "ban-tin-quy-4-2020",
title: "Bản tin Quý IV năm 2020",
date: "04/01/2021",
link: "https://vcci-hcm.org.vn/wp-content/uploads/2021/01/No3-092020_VCCI-NEWS-FINAL.pdf",
title_link: "Bản Tin Quý IV năm 2020",
img: "/an-pham/bia-ban-tin-quy-4-1.webp",
},
{
id: "ban-tin-quy-1-2020",
title: "Bản tin Quý I năm 2020",
date: "16/07/2020",
link: "https://vcci-hcm.org.vn/wp-content/uploads/2020/07/VCCI-NEWS-012020_XUAN.pdf",
title_link: "Bản tin Quý I năm 2020",
img: "/an-pham/bantintet-1.webp", // ← Sửa: iimg → img
},
];
// ĐÚNG: Không async, params là object
export default function PublicationDetail() {
const params = useParams(); // Dùng hook
const id = params.id as string; // Ép kiểu an toàn
const publication = publications.find((p) => p.id === id);
if (!publication) return notFound();
return (
<div className="bg-[#f6f6f6] min-h-screen">
<div className="max-w-[1200px] mx-auto flex flex-col gap-5 mb-[50px]">
<div className="border-[#e5e7f2] border-[1px]">
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
</div>
<div className="w-full flex gap-5 flex-wrap">
<div className="lg:w-[calc(65%-10px)] w-full border-[#e5e7f2] border-[1px] bg-white p-[30px] flex flex-col gap-[15px]">
<h1 className="text-[22px] font-semibold text-[#003366]">
{publication.title}
</h1>
<p className="text-[#00AED5] text-sm">{publication.date}</p>
<hr />
<p className="text-[16px] text-[#363636]">
Tải về ấn phẩm:{" "}
<Link
href={publication.link}
target="_blank"
className="text-[#0073e6] hover:text-[#e8c518]"
>
{publication.title_link}
</Link>
</p>
<div className="flex justify-center">
<Link href={publication.link} target="_blank">
<Image
src={publication.img}
alt={publication.title}
width={416}
height={566}
className="rounded-lg transition-all duration-300"
/>
</Link>
</div>
</div>
<div className="lg:w-[calc(35%-10px)] w-full">
<Calendar />
<div className="relative w-full mt-4 h-[300px] aspect-video rounded-lg overflow-hidden">
<Image
src="/banner.webp"
alt="Quảng cáo"
fill
className="object-contain"
/>
</div>
</div>
</div>
</div>
</div>
);
}
"use client";
import React, { useState, useMemo } from "react";
import {
format,
startOfMonth,
endOfMonth,
eachDayOfInterval,
isSameMonth,
isSameDay,
addMonths,
subMonths,
} from "date-fns";
import { ArrowLeft, ArrowRight, ChevronLeft, ChevronRight } from "lucide-react";
import { vi } from "date-fns/locale";
interface Event {
date: Date;
title: string;
type: "event" | "training";
description?: string;
}
export default function Calendar() {
const [currentMonth, setCurrentMonth] = useState(new Date());
const today = new Date();
// Dữ liệu mẫu
const events: Event[] = [
{
date: new Date(2025, 10, 1),
title: "Đào tạo nội bộ",
type: "training",
description: "Khóa học kỹ năng mềm",
},
{
date: new Date(2025, 10, 3),
title: "Họp cổ đông",
type: "event",
description: "Báo cáo Q3",
},
{
date: new Date(2025, 10, 13),
title: "Đào tạo kỹ thuật",
type: "training",
description: "React Advanced",
},
{
date: new Date(2025, 10, 14),
title: "Đào tạo an toàn",
type: "training",
description: "An toàn lao động",
},
{
date: new Date(2025, 10, 20),
title: "Hội thảo thuế",
type: "event",
description:
"Cập nhật luật thuế thu nhập doanh nghiệp số 67/2025/QH15...",
},
{
date: new Date(2025, 10, 28),
title: "Sự kiện nội bộ",
type: "event",
description: "Team building",
},
];
const monthStart = startOfMonth(currentMonth);
const monthEnd = endOfMonth(currentMonth);
const monthDays = eachDayOfInterval({ start: monthStart, end: monthEnd });
const firstDayOfWeek = monthStart.getDay(); // 0 = CN, 1 = T2...
const startDate = new Date(monthStart);
startDate.setDate(startDate.getDate() - firstDayOfWeek);
const days = [];
for (let i = 0; i < 42; i++) {
const day = new Date(startDate);
day.setDate(startDate.getDate() + i);
days.push(day);
}
const getEventForDay = (date: Date) =>
events.filter((e) => isSameDay(e.date, date));
const formatMonthTitle = () => {
return `THÁNG ${format(currentMonth, "M/yyyy")}`.toUpperCase();
};
return (
<>
<div className="w-full mx-auto bg-white rounded-lg p-4 ">
{/* Header */}
<div className="flex items-center justify-between mb-4 px-3">
<h2 className="text-[15px] font-bold text-[#063E8E]">
{formatMonthTitle()}
</h2>
<div className="flex gap-3">
<button
onClick={() => setCurrentMonth(subMonths(currentMonth, 1))}
className="p-2 cursor-pointer rounded-full group border-3 border-[#363636] hover:border-[#063e8e] transition"
>
<ArrowLeft className="group-hover:text-[#e8c518] text-[#363636] w-5 h-5" />
</button>
<button
onClick={() => setCurrentMonth(addMonths(currentMonth, 1))}
className="p-2 cursor-pointer rounded-full group border-3 border-[#363636] hover:border-[#063e8e] transition"
>
<ArrowRight className="group-hover:text-[#e8c518] text-[#363636] w-5 h-5" />
</button>
</div>
</div>
{/* Days of week */}
<div className="grid grid-cols-7 text-center text-sm font-medium text-gray-600 mb-1">
{["CN", "T2", "T3", "T4", "T5", "T6", "T7"].map((day) => (
<div key={day} className="py-2 text-[15px] text-[#063E8E]">
{day}
</div>
))}
</div>
{/* Calendar grid */}
<div className="grid grid-cols-7 gap-1 text-sm">
{days.map((day, idx) => {
const dayEvents = getEventForDay(day);
const isCurrentMonth = isSameMonth(day, currentMonth);
const isToday = isSameDay(day, today);
const hasEvent = dayEvents.some((e) => e.type === "event");
const hasTraining = dayEvents.some((e) => e.type === "training");
return (
<div
key={idx}
className={`
relative group aspect-square flex items-center justify-center rounded-full
${!isCurrentMonth ? "text-[#A4A4A4]" : "text-[#333333]"}
${hasEvent || hasTraining ? "text-white" : "text-[#333333]"}
${isToday ? "text-red-600 font-bold" : ""}
hover:bg-gray-50 transition
`}
>
<span className="relative z-10">{format(day, "d")}</span>
{/* Event/Training dots */}
{(hasEvent || hasTraining) && (
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
<div className="flex">
{hasEvent && (
<div className="w-10 h-10 bg-blue-600 rounded-full"></div>
)}
{hasTraining && (
<div className="w-10 h-10 bg-yellow-500 rounded-full"></div>
)}
</div>
</div>
)}
{/* Tooltip on hover */}
{dayEvents.length > 0 && (
<div
className="absolute top-full left-1/2 -translate-x-1/2 mt-2
w-64 p-3 bg-gray-900 text-white text-xs rounded-lg
shadow-xl opacity-0 pointer-events-none
group-hover:opacity-90 transition-opacity z-50"
>
<div className="space-y-2">
{dayEvents.map((event, i) => (
<div
key={i}
className="border-b border-gray-700 border-opacity-20 last:border-0 pb-2 last:pb-0"
>
<div className="flex items-center gap-2">
<div
className={`w-3 h-3 rounded-full ${
event.type === "event"
? "bg-blue-400"
: "bg-yellow-400"
}`}
></div>
<span className="font-medium">{event.title}</span>
</div>
{event.description && (
<p className="text-gray-300 mt-1 text-xs">
{event.description}
</p>
)}
</div>
))}
</div>
{/* Mũi tên nhọn HƯỚNG LÊN (chỉ vào ngày) */}
<div
className="absolute bottom-full left-1/2 -translate-x-1/2 -mb-1
w-0 h-0
border-l-8 border-l-transparent
border-r-8 border-r-transparent
border-b-8 border-b-gray-900"
></div>
</div>
)}
</div>
);
})}
</div>
{/* Legend */}
<div className="flex justify-center gap-6 mt-4 text-xs">
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-blue-600 rounded-full"></div>
<span>Sự kiện</span>
</div>
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-yellow-500 rounded-full"></div>
<span>Đào tạo</span>
</div>
</div>
</div>
</>
);
}
"use client";
import React, { useState } from "react";
import Image from "next/image";
import Link from "next/link";
import { Pagination } from "@components/base/pagination";
import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type";
const publications = [
{
id: "huong-dan-dau-tu-2024",
title: "Cẩm nang Hướng dẫn đầu tư kinh doanh tại Việt Nam",
img: "/an-pham/A-Guide-2023_Cover-725x1024.webp",
},
{
id: "connections-2022-2023",
title: "Danh bạ Hội viên CONNECTIONS 2022-2023",
img: "/an-pham/Trang-bia_Connections_2022-2023-725x1024.jpg.webp",
},
{
id: "chuyen-doi-so",
title: "Chuyển đổi số – Động lực phục hồi và phát triển kinh tế",
img: "/an-pham/Trang-bia_Chuyen-doi-so_2022-750x1024.webp",
},
{
id: "huong-dan-dau-tu-2021",
title: "Cẩm nang Hướng dẫn đầu tư kinh doanh tại Việt Nam 2021",
img: "/an-pham/doing-in-business-cover-1-1.webp",
},
{
id: "ban-tin-quy-4-2020",
title: "Bản tin Quý IV năm 2020",
img: "/an-pham/bia-ban-tin-quy-4-1.webp",
},
{
id: "ban-tin-quy-1-2020",
title: "Bản tin Quý I năm 2020",
img: "/an-pham/bantintet-1.webp",
},
];
export default function PublicationList() {
const [page, setPage] = useState(1);
const pageSize = 5;
const { data: allData } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
});
return (
<div className="lg:w-[calc(65%-10px)] w-full flex flex-col gap-[15px]">
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-8">
{publications.map((pub) => (
<Link
href={`/thong-tin-truyen-thong/an-pham/${pub.id}`}
key={pub.id}
className="flex flex-col items-center text-center h-full max-h-[342px] bg-white group"
>
<div className="w-full max-w-[260px] aspect-[3/4] overflow-hidden rounded-lg">
<Image
src={pub.img}
alt={pub.title}
width={300}
height={400}
className="object-contain w-full h-full transition-transform duration-300 group-hover:scale-105"
/>
</div>
<h3 className="mt-3 text-[15px] font-semibold text-[#124588] group-hover:text-[#E8C518] leading-snug">
{pub.title}
</h3>
</Link>
))}
</div>
<div className="w-full flex justify-center mt-4">
<Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() =>
setPage(
Math.min(Number(allData?.responseData.totalPages ?? 1), page + 1)
)
}
/>
</div>
</div>
);
}
"use client";
import React, { useState } from "react";
import ListCategory from "@app/dai-dien-gioi-chu/components/list-category";
import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories";
import ListCategory from "@/components/base/list-category";
import { MEDIA_INFORMATION_CATEGORIES } from "@/constants/categories";
import EventFilter from "@app/dai-dien-gioi-chu/components/event-filter";
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import { Pagination } from "@components/base/pagination";
import Image from "next/image";
import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type";
export default function Page() {
const [submitSearch] = useState("");
const [page, setPage] = useState(1);
import PublicationList from "./components/publicationList";
const pageSize = 5;
const { data: allData } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : 'category @=Ấn phẩm',
});
export default function Page() {
return (
<div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5">
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
<main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden">
{/* {allData?.responseData.rows.map((news) => (
<NewsContent key={news.id} news={news} />
))} */}
<div className="w-full flex justify-center mt-4">
<Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() => setPage(Math.min(Number(allData?.responseData.totalPages ?? 1), page + 1))}
/>
</div>
</div>
</main>
{/* Sidebar */}
<aside className="space-y-6">
<div className="bg-[#f6f6f6]">
<div className="max-w-[1200px] m-auto flex flex-col gap-5 mb-[50px]">
<div className="border-[#e5e7f2] border-[1px]">
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
</div>
<div className="w-full flex gap-5 flex-wrap">
<PublicationList />
<div className="lg:w-[calc(35%-10px)] w-full">
<EventFilter />
<div className="bg-white border rounded-md overflow-hidden">
<div className="w-full h-56 relative bg-gray-100">
<Image
src="/banner.webp"
alt="Quảng cáo"
fill
className="object-cover"
/>
</div>
<div className="relative w-full mt-4 h-[300px] aspect-video rounded-lg overflow-hidden">
<Image
src="/banner.webp"
alt="Quảng cáo"
fill
className="object-contain"
/>
</div>
</aside>
</div>
</div>
</div>
</div>
......
"use client"
"use client";
import { usePathname } from "next/navigation"
import React from "react"
import { MenuItem } from '../menu-category'
import { usePathname } from "next/navigation";
import React from "react";
import { MenuItem } from "../menu-category";
// Local Menu shape compatible with MenuItem
type Menu = {
id: string | number
name: string
link?: string
}
id: string | number;
name: string;
link?: string;
};
type Category = {
title: string
href: string
}
title: string;
href: string;
};
// Default categories removed — component now accepts `categories` via props.
const ListCategory: React.FC<{ categories?: Category[] }> = ({ categories = [] }) => {
const pathname = usePathname() || ""
const ListCategory: React.FC<{ categories?: Category[] }> = ({
categories = [],
}) => {
const pathname = usePathname() || "";
const isActive = (href: string) => {
// treat the base path as active for nested routes as well
if (href === "/gioi-thieu") return pathname === href || pathname.startsWith(href + "/")
return pathname === href
}
const isActive = (href: string) => {
// treat the base path as active for nested routes as well
// if (href === "/gioi-thieu") return pathname === href || pathname.startsWith(href + "/")
return pathname === href;
};
return (
<div className="border-t border-gray-200 bg-white p-2.5">
<div className="w-full px-4 sm:px-6 lg:px-8">
<div className="py-3">
<div className="flex flex-wrap items-center max-w-full overflow-x-auto">
{categories.map((c) => {
const menu: Menu = { id: c.href, name: c.title, link: c.href }
const active = isActive(c.href)
return (
<div key={c.href} className="shrink-0">
<MenuItem menu={menu} active={active} />
</div>
)
})}
</div>
return (
<div className="border-t border-gray-200 bg-white p-2.5">
<div className="w-full px-4 sm:px-6 lg:px-8">
<div className="py-3">
<div className="flex flex-wrap items-center max-w-full overflow-x-auto">
{categories.map((c) => {
const menu: Menu = { id: c.href, name: c.title, link: c.href };
const active = isActive(c.href);
return (
<div key={c.href} className="shrink-0">
<MenuItem menu={menu} active={active} />
</div>
</div>
);
})}
</div>
</div>
)
}
</div>
</div>
);
};
export default ListCategory
\ No newline at end of file
export default ListCategory;
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