Commit 4b74d1b8 authored by Phạm Quang Bảo's avatar Phạm Quang Bảo

Merge branch 'develop' into fix/home_page

parents a99fbda4 75098072
......@@ -15,6 +15,7 @@ import { useParams } from 'next/navigation'
import ListCategory from '@/components/base/list-category'
import { MEDIA_INFORMATION_CATEGORIES } from '@/constants/categories'
import EventCalendar from '@/components/base/event-calendar'
import parse from "html-react-parser";
// import { t } from 'i18next'
// Component
......@@ -52,7 +53,7 @@ const NewsDetailPage = () => {
<hr />
</div>
<div className='flex-1 text-app-grey text-base overflow-hidden'>
<AppEditorContent value={data?.responseData?.description ?? ''} />
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</div>
</main>
{/* Sidebar */}
......
'use client'
import React from "react";
import ListCategory from "../components/list-category";
import EventCalendar from "../components/event-calendar";
import EventCalendar from '@/components/base/event-calendar';
const Page = () => {
return (
......
'use client'
import React from "react";
import ListCategory from "./components/list-category";
import EventCalendar from "./components/event-calendar";
import EventCalendar from "@/components/base/event-calendar";
const Page = () => {
return (
......
......@@ -4,7 +4,7 @@ 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";
import EventCalendar from "@/components/base/event-calendar";
const publications = [
{
......@@ -106,7 +106,7 @@ export default function PublicationDetail() {
</div>
<div className="lg:w-[calc(35%-10px)] w-full">
<Calendar />
<EventCalendar />
<div className="relative w-full mt-4 h-[300px] aspect-video rounded-lg overflow-hidden">
<Image
src="/banner.webp"
......
"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>
</>
);
}
......@@ -5,7 +5,7 @@ import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories";
// ...existing code...
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import ListFilter from "@app/dai-dien-gioi-chu/components/list-filter";
import EventCalendar from "@app/dai-dien-gioi-chu/components/event-calendar";
import EventCalendar from "@/components/base/event-calendar";
import { Pagination } from "@components/base/pagination";
import Image from "next/image";
import { useGetNews } from "@api/endpoints/news";
......
import React from "react";
import Image from "next/image";
import ListCategory from "../components/list-category";
import Calendar from "../components/Calendar";
import EventCalendar from "@/components/base/event-calendar";
export default function page() {
return (
......@@ -122,7 +122,7 @@ export default function page() {
</div>
</div>
<div className="lg:w-[calc(35%-10px)] w-full ">
<Calendar />
<EventCalendar />
<div className="relative w-full mt-4 h-[300px] aspect-video rounded-lg overflow-hidden">
<Image
src="/banner.webp"
......
"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>
</>
);
}
import React from "react";
import Image from "next/image";
import ListCategory from "../components/list-category";
import Calendar from "../components/Calendar";
import EventCalendar from "@/components/base/event-calendar";
export default function page() {
return (
......@@ -137,7 +137,7 @@ export default function page() {
</div>
</div>
<div className="lg:w-[calc(35%-10px)] w-full ">
<Calendar />
<EventCalendar />
<div className="relative w-full mt-4 h-[300px] aspect-video rounded-lg overflow-hidden">
<Image
src="/banner.webp"
......
import React from "react";
import Image from "next/image";
import ListCategory from "../components/list-category";
import Calendar from "../components/Calendar";
import EventCalendar from "@/components/base/event-calendar";
export default function page() {
return (
......@@ -269,7 +269,7 @@ export default function page() {
</div>
</div>
<div className="lg:w-[calc(35%-10px)] w-full ">
<Calendar />
<EventCalendar />
<div className="relative w-full mt-4 h-[300px] aspect-video rounded-lg overflow-hidden">
<Image
src="/banner.webp"
......
import React from "react";
import Image from "next/image";
import ListCategory from "../components/list-category";
import Calendar from "../components/Calendar";
import EventCalendar from "@/components/base/event-calendar";
export default function page() {
return (
......@@ -98,7 +98,7 @@ export default function page() {
</div>
</div>
<div className="lg:w-[calc(35%-10px)] w-full ">
<Calendar />
<EventCalendar />
<div className="relative w-full mt-4 h-[300px] aspect-video rounded-lg overflow-hidden">
<Image
src="/banner.webp"
......
import React from "react";
import ListCategory from "./components/list-category";
import Calendar from "./components/Calendar";
import EventCalendar from "@/components/base/event-calendar";
import Image from "next/image";
export default function page() {
......@@ -202,7 +202,7 @@ export default function page() {
</div>
</div>
<div className="lg:w-[calc(35%-10px)] w-full ">
<Calendar />
<EventCalendar />
<div className="relative w-full mt-4 h-[300px] aspect-video rounded-lg overflow-hidden">
<Image
src="/banner.webp"
......
import React from "react";
import Image from "next/image";
import ListCategory from "../components/list-category";
import Calendar from "../components/Calendar";
import EventCalendar from "@/components/base/event-calendar";
export default function page() {
return (
......@@ -57,7 +57,7 @@ export default function page() {
</div>
</div>
<div className="lg:w-[calc(35%-10px)] w-full ">
<Calendar />
<EventCalendar />
<div className="relative w-full mt-4 h-[300px] aspect-video rounded-lg overflow-hidden">
<Image
src="/banner.webp"
......
import React from "react";
import Image from "next/image";
import ListCategory from "../components/list-category";
import Calendar from "../components/Calendar";
import EventCalendar from "@/components/base/event-calendar";
export default function page() {
return (
......@@ -154,7 +154,7 @@ export default function page() {
</div>
</div>
<div className="lg:w-[calc(35%-10px)] w-full ">
<Calendar />
<EventCalendar />
<div className="relative w-full mt-4 h-[300px] aspect-video rounded-lg overflow-hidden">
<Image
src="/banner.webp"
......
import React from "react";
import Image from "next/image";
import ListCategory from "../components/list-category";
import Calendar from "../components/Calendar";
import EventCalendar from "@/components/base/event-calendar";
export default function page() {
return (
......@@ -313,7 +313,7 @@ export default function page() {
</div>
</div>
<div className="lg:w-[calc(35%-10px)] w-full ">
<Calendar />
<EventCalendar />
<div className="relative w-full mt-4 h-[300px] aspect-video rounded-lg overflow-hidden">
<Image
src="/banner.webp"
......
......@@ -3,7 +3,7 @@ import React, { useState } from "react";
import ListCategory from "@app/dai-dien-gioi-chu/components/list-category";
import { TRADE_PROMOTION_CATEGORIES } from "@constants/categories";
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import EventCalendar from "@app/dai-dien-gioi-chu/components/event-calendar";
import EventCalendar from "@/components/base/event-calendar";
import { Pagination } from "@components/base/pagination";
import Image from "next/image";
import { useGetNews } from "@api/endpoints/news";
......@@ -19,12 +19,12 @@ export default function Page() {
pageSize: String(pageSize),
currentPage: String(page),
// filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters:'category@=Cơ hội kinh doanh'
filters: 'category@=Cơ hội kinh doanh'
});
return (
<div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5">
<ListCategory categories={TRADE_PROMOTION_CATEGORIES} />
<ListCategory categories={TRADE_PROMOTION_CATEGORIES} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */}
......
......@@ -5,7 +5,7 @@ import { TRADE_PROMOTION_CATEGORIES } from "@constants/categories";
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import { Pagination } from "@components/base/pagination";
import Image from "next/image";
import EventCalendar from "@app/dai-dien-gioi-chu/components/event-calendar";
import EventCalendar from "@/components/base/event-calendar";
import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type";
import { PATHS } from "@constants/paths";
......@@ -19,7 +19,7 @@ export default function Page() {
pageSize: String(pageSize),
currentPage: String(page),
// filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters:'category@=Môi trường kinh doanh'
filters: 'category@=Môi trường kinh doanh'
});
return (
<div className="min-h-screen container mx-auto p-4">
......
"use client";
import React, { useState } from "react";
import { ArrowRight, ArrowLeft } from "lucide-react";
import { useState, useMemo } from "react";
import {
format,
startOfMonth,
endOfMonth,
eachDayOfInterval,
isSameMonth,
isSameDay,
addMonths,
subMonths,
} from "date-fns";
import { ArrowLeft, ArrowRight } from "lucide-react";
import { useGetEvents } from "@/api/endpoints/event";
import { EventApiResponse } from "@/api/types/event";
export default function EventCalendar() {
const mockCalendar = {
month: 10,
year: 2025,
highlighted: [6, 9, 12],
};
const [month, setMonth] = useState<number>(mockCalendar.month);
const [year, setYear] = useState<number>(mockCalendar.year);
const [currentMonth, setCurrentMonth] = useState(new Date());
const today = new Date();
// Fetch event data from API
const { data } = useGetEvents<EventApiResponse>();
const events = data?.responseData.rows ?? [];
// Calculate the days to display in the current month grid
const days = useMemo(() => {
const start = startOfMonth(currentMonth);
const end = endOfMonth(currentMonth);
const firstDayOfWeek = start.getDay(); // Sunday = 0
const gridStart = new Date(start);
gridStart.setDate(start.getDate() - firstDayOfWeek);
const result: Date[] = [];
for (let i = 0; i < 42; i++) {
const day = new Date(gridStart);
day.setDate(gridStart.getDate() + i);
result.push(day);
}
return result;
}, [currentMonth]);
// Helper: get all events for a specific day
const getEventForDay = (date: Date) =>
events.filter((e) => isSameDay(new Date(e.start_time), date));
// Month title formatting (Vietnamese)
const formatMonthTitle = () => `THÁNG ${format(currentMonth, "M/yyyy")}`.toUpperCase();
return (
<div className="bg-white border rounded-md p-4">
<div className="flex items-center justify-between mb-3">
<div className="text-sm font-medium">
THÁNG {month}/{year}
</div>
<div className="flex items-center gap-2">
<div className="group">
<button
aria-label="Tháng trước"
onClick={() => {
// prev month
if (month === 1) {
setMonth(12);
setYear((y) => y - 1);
} else {
setMonth((m) => m - 1);
}
}}
className="group-hover:border-primary p-1 h-10 w-10 flex items-center justify-center rounded-full border-2 border-[#363636] "
>
<ArrowLeft
size={24}
className="group-hover:text-muted-foreground text-[#363636]"
/>
</button>
</div>
<div className="group">
<button
aria-label="Tháng sau"
onClick={() => {
// next month
if (month === 12) {
setMonth(1);
setYear((y) => y + 1);
} else {
setMonth((m) => m + 1);
}
}}
className="p-1 group-hover:border-primary h-10 w-10 flex items-center justify-center rounded-full border-2 border-[#363636]"
>
<ArrowRight
size={24}
className="group-hover:text-muted-foreground"
/>
</button>
</div>
<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>
{/* Month navigation buttons */}
<div className="flex gap-3">
<button
onClick={() => setCurrentMonth(subMonths(currentMonth, 1))}
className="p-2 rounded-full group border 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 rounded-full group border border-[#363636] hover:border-[#063e8e] transition"
>
<ArrowRight className="group-hover:text-[#e8c518] text-[#363636] w-5 h-5" />
</button>
</div>
</div>
<div className="grid grid-cols-7 gap-1 text-center text-xs">
{["CN", "T2", "T3", "T4", "T5", "T6", "T7"].map((d) => (
<div key={d} className="text-gray-400 py-1">
{d}
{/* ===== DAYS OF WEEK ===== */}
<div className="grid grid-cols-7 text-center text-sm font-medium mb-1">
{["CN", "T2", "T3", "T4", "T5", "T6", "T7"].map((day) => (
<div key={day} className="py-2 text-[15px] text-[#063E8E]">
{day}
</div>
))}
{(() => {
const totalDays = new Date(year, month, 0).getDate();
const firstDayIndex = new Date(year, month - 1, 1).getDay(); // 0 (Sun) - 6 (Sat)
// previous month total days
const prevMonthTotalDays = new Date(year, month - 1, 0).getDate();
const totalCells = firstDayIndex + totalDays;
const trailingCount = (7 - (totalCells % 7)) % 7;
</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);
// Example condition: color days that have "B2B" events
const hasB2BEvent = dayEvents.some((e) => e.type === "B2B");
return (
<>
{Array.from({ length: firstDayIndex }).map((_, i) => {
const dayNum = prevMonthTotalDays - (firstDayIndex - 1) + i;
return (
<div key={`prev-${i}`} className="py-2 text-sm text-gray-300">
{dayNum}
</div>
);
})}
<div
key={idx}
className={`
relative group aspect-square flex items-center justify-center rounded-full
${!isCurrentMonth ? "text-gray-400" : ""}
${hasB2BEvent ? "bg-blue-600 text-white" : "text-[#333333]"}
${isToday ? "text-red-600 font-bold" : ""}
transition
`}
>
{/* Day number */}
<span className="relative z-10">{format(day, "d")}</span>
{Array.from({ length: totalDays }, (_, i) => i + 1).map((day) => {
const isHighlighted = mockCalendar.highlighted.includes(day);
return (
<div
key={day}
className={`py-2 rounded-full w-10 h-10 flex flex-col justify-center items-center text-sm ${
isHighlighted
? "bg-yellow-500 text-white"
: "text-gray-700"
}`}
>
{day}
{/* Tooltip showing event details 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 bg-blue-400" />
<span className="font-medium">{event.name}</span>
</div>
</div>
))}
</div>
);
})}
{Array.from({ length: trailingCount }).map((_, i) => (
<div key={`next-${i}`} className="py-2 text-sm text-gray-300">
{i + 1}
{/* Tooltip arrow */}
<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>
{/* ===== 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" />
<span>Sự kiện</span>
</div>
</div>
</div>
);
......
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