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

fix ui

parent 379eb4cb
......@@ -14,6 +14,36 @@ const weekDays = ["CN", "T2", "T3", "T4", "T5", "T6", "T7"];
const formatDateTime = (value: string) =>
value ? dayjs(value).format("DD/MM/YYYY HH:mm") : "Đang cập nhật";
const getEventDateRange = (item: HomePostItem) => {
const startedAt = item.startedAt ? dayjs(item.startedAt) : null;
const endedAt = item.endedAt ? dayjs(item.endedAt) : null;
if (startedAt && endedAt) {
const dates: string[] = [];
let current = startedAt;
while (current.isBefore(endedAt) || current.isSame(endedAt, "day")) {
dates.push(current.format("YYYY-MM-DD"));
current = current.add(1, "day");
}
return dates;
}
if (startedAt) {
return [startedAt.format("YYYY-MM-DD")];
}
if (endedAt) {
return [endedAt.format("YYYY-MM-DD")];
}
// Fallback to registrationDeadline
if (item.registrationDeadline) {
return [dayjs(item.registrationDeadline).format("YYYY-MM-DD")];
}
return [];
};
const isTrainingEvent = (item: HomePostItem) =>
item.categories.some((category) => {
const key = `${category.name} ${category.slug} ${category.url}`.toLowerCase();
......@@ -39,19 +69,19 @@ function EventsCalendar({
}) {
const today = dayjs();
const todayKey = today.format("YYYY-MM-DD");
const todayMonth = today.month();
const todayYear = today.year();
const [currentMonth, setCurrentMonth] = useState(new Date());
const [selectedDateKey, setSelectedDateKey] = useState<string | null>(null);
const eventCalendarQuery = useEventCalendarPosts(currentMonth);
const monthEvents = eventCalendarQuery.data ?? [];
const monthEvents = useMemo(() => eventCalendarQuery.data ?? [], [eventCalendarQuery.data]);
useEffect(() => {
const viewingCurrentMonth =
currentMonth.getMonth() === todayMonth && currentMonth.getFullYear() === todayYear;
currentMonth.getMonth() === today.month() && currentMonth.getFullYear() === today.year();
const defaultSelectedKey = viewingCurrentMonth ? todayKey : null;
setSelectedDateKey(viewingCurrentMonth ? todayKey : null);
}, [currentMonth, todayKey, todayMonth, todayYear]);
// Initialize selectedDateKey when month changes
useEffect(() => {
setSelectedDateKey(defaultSelectedKey);
}, [defaultSelectedKey]);
const days = useMemo(() => {
const monthStart = startOfMonth(currentMonth);
......@@ -70,17 +100,23 @@ function EventsCalendar({
const map = new Map<string, HomePostItem[]>();
monthEvents.forEach((item) => {
const key = dayjs(item.registrationDeadline).format("YYYY-MM-DD");
const existing = map.get(key) ?? [];
const eventDates = getEventDateRange(item);
eventDates.forEach((dateKey) => {
const existing = map.get(dateKey) ?? [];
if (!existing.some((e) => e.id === item.id)) {
existing.push(item);
}
map.set(
key,
existing.sort(
(left, right) =>
dayjs(left.registrationDeadline).valueOf() - dayjs(right.registrationDeadline).valueOf(),
),
dateKey,
existing.sort((left, right) => {
const leftStart = dayjs(left.startedAt || left.endedAt || left.registrationDeadline).valueOf();
const rightStart = dayjs(right.startedAt || right.endedAt || right.registrationDeadline).valueOf();
return leftStart - rightStart;
}),
);
});
});
return map;
}, [monthEvents]);
......@@ -242,7 +278,7 @@ function EventsCalendar({
</p>
<div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-[11px] text-[#6f84aa]">
<span>Hạn đăng ký: {formatDateTime(item.registrationDeadline)}</span>
<span>Chi phí: {item.participationFee || "Đang cập nhật"}</span>
<span>Chi phí: {item.participationFee || "Miễn phí"}</span>
</div>
<div className="mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-[11px] text-[#6f84aa]">
<span>Địa điểm: {item.location || "Đang cập nhật"}</span>
......@@ -314,7 +350,7 @@ function EventsCalendar({
</Link>
<p>
Hạn đăng ký: {formatDateTime(item.registrationDeadline)} · Chi phí:{" "}
{item.participationFee || "Đang cập nhật"}
{item.participationFee || "Miễn phí"}
</p>
<p>Địa điểm: {item.location || "Đang cập nhật"}</p>
</div>
......
'use client';
'use client';
import ImageNext from "@/components/shared/image-next";
import { useHomePosts } from "@/app/(main)/(home)/lib/use-home-posts";
......@@ -7,7 +7,8 @@ import Link from "next/link";
function Events() {
const { eventPosts, categoryLinks, categoryNames } = useHomePosts();
const eventItems = eventPosts;
// Reverse so newest event is first (featured card)
const eventItems = [...eventPosts].reverse();
const [featuredEvent, ...sideEvents] = eventItems;
const sideSlots = Array.from({ length: 4 }, (_, index) => sideEvents[index] ?? null);
const eventsLink =
......@@ -47,11 +48,35 @@ function Events() {
/>
</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]">
<div className="flex flex-col p-3 pt-2.5">
<h3 className="text-[16px] font-bold uppercase leading-[1.28] text-[#22459b] line-clamp-2 md:text-[18px]">
{featuredEvent.title}
</h3>
<p className="mt-1.5 text-[13px] text-[#90a0bd]">
{(() => {
const rawText = featuredEvent.contentText || featuredEvent.summary || "";
const textOnly = rawText
.replace(/\[caption[^\]]*\].*?\[\/caption\]/gi, "")
.replace(/<figure[^>]*>.*?<\/figure>/gi, "")
.replace(/<img[^>]*>/gi, "")
.replace(/<[^>]+>/g, " ")
.replace(/&nbsp;/g, " ")
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&quot;/g, '"')
.replace(/"/g, '"')
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/–/g, "–")
.replace(/\s+/g, " ")
.trim();
return textOnly.length > 10 ? (
<p className="mt-2 line-clamp-1 text-[13px] leading-normal text-[#5f6f86]">
{textOnly.substring(0, 150)}
</p>
) : null;
})()}
<p className="mt-auto pt-2 text-[13px] text-[#90a0bd]">
{dayjs(
featuredEvent.startedAt || featuredEvent.publishedAt || featuredEvent.createdAt,
).format("DD/MM/YYYY")}
......@@ -87,9 +112,33 @@ function Events() {
</div>
<div className="min-w-0">
<h4 className="line-clamp-2 text-[15px] font-semibold leading-[1.35] text-white">
<h4 className="line-clamp-1 text-[15px] font-semibold leading-[1.35] text-white">
{item.title}
</h4>
{(() => {
const rawText = item.contentText || item.summary || "";
const textOnly = rawText
.replace(/\[caption[^\]]*\].*?\[\/caption\]/gi, "")
.replace(/<figure[^>]*>.*?<\/figure>/gi, "")
.replace(/<img[^>]*>/gi, "")
.replace(/<[^>]+>/g, " ")
.replace(/&nbsp;/g, " ")
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&quot;/g, '"')
.replace(/"/g, '"')
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/–/g, "–")
.replace(/\s+/g, " ")
.trim();
return textOnly.length > 10 ? (
<p className="mt-1 line-clamp-1 text-[12px] text-white/78">
{textOnly.substring(0, 80)}
</p>
) : null;
})()}
<p className="mt-1 text-[12px] text-white/78">
{dayjs(item.startedAt || item.publishedAt || item.createdAt).format(
"DD/MM/YYYY",
......
......@@ -2,7 +2,6 @@
import ImageNext from "@/components/shared/image-next";
import { useHomePosts } from "@/app/(main)/(home)/lib/use-home-posts";
import stripImagesAndHtml from "@/helpers/stripImageAndHtml";
import dayjs from "dayjs";
import Link from "next/link";
import { useMemo, useState } from "react";
......@@ -76,7 +75,7 @@ function News() {
href={featuredArticle.externalLink}
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="aspect-[1.75/1] overflow-hidden">
<div className="aspect-[1.4/1] overflow-hidden">
<ImageNext
src={featuredArticle.thumbnail?.url ?? "/thumbnail.png"}
alt={featuredArticle.thumbnail?.alt || featuredArticle.title}
......@@ -95,8 +94,27 @@ function News() {
{featuredArticle.title}
</h3>
<p className="line-clamp-2 text-[13px] leading-[1.45] text-[#6c7b96]">
{stripImagesAndHtml(featuredArticle.summary)}
<p className="line-clamp-3 text-[13px] leading-[1.45] text-[#6c7b96]">
{(() => {
const rawText = featuredArticle.contentText || featuredArticle.summary || "";
const textOnly = rawText
.replace(/\[caption[^\]]*\].*?\[\/caption\]/gi, "")
.replace(/<figure[^>]*>.*?<\/figure>/gi, "")
.replace(/<img[^>]*>/gi, "")
.replace(/<[^>]+>/g, " ")
.replace(/&nbsp;/g, " ")
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&quot;/g, '"')
.replace(/"/g, '"')
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/–/g, "–")
.replace(/\s+/g, " ")
.trim();
return textOnly || "-";
})()}
</p>
<p className="text-[14px] text-[#8a9bb6]">
......@@ -129,9 +147,33 @@ function News() {
href={news.externalLink}
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]">
<h4 className="line-clamp-1 text-[15px] font-bold leading-[1.28] text-[#21408f]">
{news.title}
</h4>
{(() => {
const rawText = news.contentText || news.summary || "";
const textOnly = rawText
.replace(/\[caption[^\]]*\].*?\[\/caption\]/gi, "")
.replace(/<figure[^>]*>.*?<\/figure>/gi, "")
.replace(/<img[^>]*>/gi, "")
.replace(/<[^>]+>/g, " ")
.replace(/&nbsp;/g, " ")
.replace(/&amp;/g, "&")
.replace(/&lt;/g, "<")
.replace(/&gt;/g, ">")
.replace(/&quot;/g, '"')
.replace(/"/g, '"')
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/–/g, "–")
.replace(/\s+/g, " ")
.trim();
return textOnly.length > 10 ? (
<p className="mt-1.5 line-clamp-2 text-[13px] leading-normal text-[#6c7b96]">
{textOnly}
</p>
) : null;
})()}
<p className="mt-1 text-[13px] text-[#8a9bb6]">
{dayjs(news.publishedAt || news.createdAt).format("DD/MM/YYYY")}
</p>
......
......@@ -42,6 +42,11 @@ type RawHomePost = {
type?: string | null;
categories?: RawHomeCategory[] | null;
thumbnail?: RawHomeThumbnail | null;
content_structure?: {
post_content?: Array<{
content?: string | null;
}> | null;
} | null;
};
type HomeEnvelope<T> = {
......@@ -65,6 +70,7 @@ export type HomePostItem = {
title: string;
externalLink: string;
summary: string;
contentText: string;
createdAt: string;
publishedAt: string;
startedAt: string;
......@@ -262,16 +268,14 @@ function createCategoryPostsQuery(categoryId: string, pageSize: string) {
}
function createEventCalendarQuery(currentMonth: Date) {
const monthStart = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), 1);
const monthEnd = new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 0, 23, 59, 59, 999);
// We'll filter by date on client-side for more flexibility
return new URLSearchParams({
sortField: "registration_deadline",
page: "1",
pageSize: "100",
sortField: "started_at",
sortOrder: "asc",
filters: [
`category.id==(${HOME_CATEGORY_IDS.suKien}|${HOME_CATEGORY_IDS.daoTao})`,
`registration_deadline>=${dayjs(monthStart).format("YYYY-MM-DD HH:mm:ss")}`,
`registration_deadline<=${dayjs(monthEnd).format("YYYY-MM-DD HH:mm:ss")}`,
"is_hidden==false",
"is_active==true",
"type==news",
......@@ -375,6 +379,12 @@ async function fetchHomePostsFromApi() {
title,
externalLink,
summary: String(item.summary ?? item.content ?? ""),
contentText: String(
item.content_structure?.post_content?.[0]?.content ??
item.summary ??
item.content ??
""
),
createdAt: String(item.created_at ?? ""),
publishedAt: String(item.published_at ?? item.release_at ?? item.created_at ?? ""),
startedAt: String(item.started_at ?? ""),
......@@ -567,7 +577,11 @@ export function useEventCalendarPosts(currentMonth: Date) {
const response = await useCustomClient<HomeEnvelope<HomePagedResult<RawHomePost>>>(
`/post?${queryParams.toString()}`,
);
return (response.responseData?.rows ?? []).map((item) => {
const monthStart = new Date(currentMonth.getFullYear(), currentMonth.getMonth(), 1);
const monthEnd = new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1, 0, 23, 59, 59, 999);
const mappedPosts = (response.responseData?.rows ?? []).map((item) => {
const categories = (item.categories ?? [])
.filter((category) => category?.id && category?.name)
.map((category) => ({
......@@ -591,6 +605,12 @@ export function useEventCalendarPosts(currentMonth: Date) {
title,
externalLink,
summary: String(item.summary ?? item.content ?? ""),
contentText: String(
item.content_structure?.post_content?.[0]?.content ??
item.summary ??
item.content ??
""
),
createdAt: String(item.created_at ?? ""),
publishedAt: String(item.published_at ?? item.release_at ?? item.created_at ?? ""),
startedAt: String(item.started_at ?? ""),
......@@ -613,6 +633,41 @@ export function useEventCalendarPosts(currentMonth: Date) {
: null,
} satisfies HomePostItem;
});
// Filter posts that have at least one date (startedAt, endedAt, or registrationDeadline)
// falling within the current month
return mappedPosts.filter((item) => {
const startedAt = item.startedAt ? dayjs(item.startedAt) : null;
const endedAt = item.endedAt ? dayjs(item.endedAt) : null;
const registrationDeadline = item.registrationDeadline ? dayjs(item.registrationDeadline) : null;
// If no dates at all, exclude
if (!startedAt && !endedAt && !registrationDeadline) return false;
const monthStartDay = dayjs(monthStart);
const monthEndDay = dayjs(monthEnd);
// Check if any date falls within the current month
const hasDateInMonth = (date: dayjs.Dayjs | null): boolean => {
return date !== null && !date.isBefore(monthStartDay, "day") && !date.isAfter(monthEndDay, "day");
};
// Check if event overlaps with current month (spans across the month)
const eventStartDate = startedAt || registrationDeadline;
const eventEndDate = endedAt || registrationDeadline || startedAt;
if (eventStartDate && eventEndDate) {
// Event overlaps with month if it starts before month end AND ends after month start
return !eventStartDate.isAfter(monthEndDay, "day") && !eventEndDate.isBefore(monthStartDay, "day");
}
// Fallback: check individual dates
if (startedAt && hasDateInMonth(startedAt)) return true;
if (endedAt && hasDateInMonth(endedAt)) return true;
if (registrationDeadline && hasDateInMonth(registrationDeadline)) return true;
return false;
});
} catch (error) {
// eslint-disable-next-line no-console
console.warn("[useEventCalendarPosts] CMS unavailable, falling back to mock data", error);
......
......@@ -5,10 +5,111 @@ import ImageNext from "@/components/shared/image-next";
import AppEditorContent from "@/components/shared/editor-content";
import ListCategory from "@/components/base/list-category";
import EventsCalendar from "@/app/(main)/(home)/components/events-calendar";
import { Calendar, MapPin, Clock, DollarSign, Users, CreditCard } from "lucide-react";
import { buildDynamicCategoryMenu, findDisplayCategoryForPost } from "./data";
import StructuredPostContent from "./StructuredPostContent";
import type { DynamicCategoryRouteItem, DynamicPostItem } from "./types";
const formatDate = (value: string | null) =>
value ? dayjs(value).format("DD/MM/YYYY") : "";
const formatDateTime = (value: string | null) =>
value ? dayjs(value).format("DD/MM/YYYY HH:mm") : "";
const isEventOrTraining = (post: DynamicPostItem) => {
const eventCategories = ["Sự kiện", "Đào tạo", "su-kien", "dao-tao", "su_kien", "dao_tao"];
return post.categories.some((cat) =>
eventCategories.some(
(key) =>
cat.name.toLowerCase().includes(key.toLowerCase()) ||
(cat.slug && cat.slug.toLowerCase().includes(key.toLowerCase()))
)
);
};
const hasEventInfo = (post: DynamicPostItem) => {
return (
post.started_at ||
post.ended_at ||
post.registration_deadline ||
post.location
);
};
const EventInfoCard = ({ post }: { post: DynamicPostItem }) => {
if (!isEventOrTraining(post) || !hasEventInfo(post)) return null;
const startedAt = post.started_at;
const endedAt = post.ended_at;
const registrationDeadline = post.registration_deadline;
return (
<div className="mt-7 rounded-2xl border border-[#e3ebf8] bg-linear-to-br from-[#f8faff] to-white p-5 shadow-[0_8px_24px_rgba(36,70,156,0.1)]">
<div className="mb-4 flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-[#24469c]">
<Calendar className="h-5 w-5 text-white" />
</div>
<div>
<h3 className="text-lg font-bold text-[#24469c]">Thông tin sự kiện</h3>
<p className="text-xs text-[#7f8eab]">Event Information</p>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
{/* Row 1: Hạn đăng ký | Chi phí */}
{registrationDeadline && (
<div className="rounded-xl bg-white p-3 shadow-sm">
<div className="flex items-center gap-2 text-[#f5a400]">
<Users className="h-4 w-4" />
<span className="text-xs font-medium uppercase tracking-wide">Hạn đăng ký</span>
</div>
<p className="mt-1 text-sm font-semibold text-[#1f3768]">
{formatDateTime(registrationDeadline)}
</p>
</div>
)}
<div className="rounded-xl bg-white p-3 shadow-sm">
<div className="flex items-center gap-2 text-[#24469c]">
<CreditCard className="h-4 w-4" />
<span className="text-xs font-medium uppercase tracking-wide">Chi phí</span>
</div>
<p className="mt-1 text-sm font-semibold text-[#1f3768]">
{post.participation_fee || "Miễn phí"}
</p>
</div>
{/* Row 2: Ngày bắt đầu/kết thúc | Địa điểm */}
<div className="rounded-xl bg-white p-3 shadow-sm">
<div className="flex items-center gap-2 text-[#24469c]">
<Clock className="h-4 w-4" />
<span className="text-xs font-medium uppercase tracking-wide">Thời gian</span>
</div>
<p className="mt-1 text-sm font-semibold text-[#1f3768]">
{startedAt
? endedAt
? `${formatDate(startedAt)} - ${formatDate(endedAt)}`
: formatDate(startedAt)
: endedAt
? formatDate(endedAt)
: "-"}
</p>
</div>
<div className="rounded-xl bg-white p-3 shadow-sm">
<div className="flex items-center gap-2 text-[#e22f5a]">
<MapPin className="h-4 w-4" />
<span className="text-xs font-medium uppercase tracking-wide">Địa điểm</span>
</div>
<p className="mt-1 text-sm font-semibold text-[#1f3768] line-clamp-2">
{post.location || "-"}
</p>
</div>
</div>
</div>
);
};
type ArticleDetailPageProps = {
post: DynamicPostItem;
category: DynamicCategoryRouteItem | null;
......@@ -53,6 +154,8 @@ export default function ArticleDetailPage({
</div>
) : null}
<EventInfoCard post={post} />
<div className="mt-7 rounded-3xl bg-white px-4 py-5 shadow-[0_18px_42px_rgba(17,24,39,0.06)] sm:px-8 sm:py-6 lg:px-10">
<div className="article-detail-content prose tiptap max-w-none overflow-hidden">
<StructuredPostContent post={post} />
......
......@@ -20,6 +20,7 @@ type CategoryListResponse = {
type RawPostCategory = {
id?: string | null;
name?: string | null;
slug?: string | null;
url?: string | null;
type?: string | null;
};
......@@ -56,6 +57,8 @@ type RawPostItem = {
ended_at?: string | null;
expired_at?: string | null;
registration_deadline?: string | null;
location?: string | null;
participation_fee?: string | null;
is_featured?: boolean | null;
is_hidden?: boolean | null;
is_active?: boolean | null;
......@@ -216,6 +219,8 @@ const mapPost = (item: RawPostItem): DynamicPostItem => ({
ended_at: item.ended_at ?? null,
expired_at: item.expired_at ?? null,
registration_deadline: item.registration_deadline ?? null,
location: item.location ?? null,
participation_fee: item.participation_fee ?? null,
is_featured: Boolean(item.is_featured),
is_hidden: Boolean(item.is_hidden),
is_active: item.is_active !== false,
......@@ -227,6 +232,7 @@ const mapPost = (item: RawPostItem): DynamicPostItem => ({
.map((category) => ({
id: String(category.id),
name: String(category.name),
slug: String(category.slug ?? ""),
url: normalizePath(category.url),
type: String(category.type ?? ""),
})),
......
'use client';
import { useMemo, useState } from "react";
import { ArrowDownToLine, Globe2, MapPinned, Newspaper, TrendingUp } from "lucide-react";
import { Globe2, Newspaper, TrendingUp } from "lucide-react";
import ImageNext from "@/components/shared/image-next";
import type { DynamicPostItem } from "../types";
......@@ -35,14 +35,14 @@ const REGION_CONFIGS: RegionConfig[] = [
key: "dong-nam-a",
label: "Đông Nam Á",
title: "Đông Nam Á",
image: "https://vcci-hcm.org.vn/wp-content/uploads/2022/06/Dong-Nam-A-scaled.jpg",
image: "/ho-so-thi-truong/ban-do/Dong-Nam-A-scaled.jpg",
imageAlt: "Bản đồ thị trường Đông Nam Á",
description:
"Khu vực trọng điểm dành cho doanh nghiệp theo dõi cơ hội thương mại, xuất nhập khẩu, chuỗi cung ứng và kết nối đối tác trong ASEAN.",
markets: [
{
name: "Việt Nam",
href: "https://vcci-hcm.org.vn/wp-content/uploads/2022/06/VN-factsheet.pdf",
href: "/ho-so-thi-truong/tai-lieu/VN-factsheet.pdf",
tone: "bg-[#da251d]",
},
{ name: "Lào", href: "#", tone: "bg-[#002868]" },
......@@ -52,7 +52,7 @@ const REGION_CONFIGS: RegionConfig[] = [
{ name: "Malaysia", href: "#", tone: "bg-[#c00]" },
{
name: "Singapore",
href: "https://vcci-hcm.org.vn/wp-content/uploads/2022/06/SINGAPORE-2020.pdf",
href: "/ho-so-thi-truong/tai-lieu/SINGAPORE-2020.pdf",
tone: "bg-[#df0000]",
},
{ name: "Philippines", href: "#", tone: "bg-[#0038a8]" },
......@@ -61,7 +61,7 @@ const REGION_CONFIGS: RegionConfig[] = [
],
featuredDocument: {
title: "Factsheet Việt Nam",
href: "https://vcci-hcm.org.vn/wp-content/uploads/2022/06/VN-factsheet.pdf",
href: "/ho-so-thi-truong/tai-lieu/VN-factsheet.pdf",
description: "Mở tài liệu tham khảo",
},
},
......@@ -69,7 +69,7 @@ const REGION_CONFIGS: RegionConfig[] = [
key: "dong-bac-a",
label: "Đông Bắc Á",
title: "Đông Bắc Á",
image: "https://vcci-hcm.org.vn/wp-content/uploads/2022/06/Dong-Bac-A-scaled.jpg",
image: "/ho-so-thi-truong/ban-do/Dong-Bac-A-scaled.jpg",
imageAlt: "Khu vực Đông Bắc Á",
description:
"Nhóm thị trường phù hợp để doanh nghiệp tiếp cận chuỗi giá trị công nghiệp, công nghệ, logistics và thương mại khu vực Đông Bắc Á.",
......@@ -85,7 +85,7 @@ const REGION_CONFIGS: RegionConfig[] = [
key: "nam-a",
label: "Nam Á",
title: "Nam Á",
image: "https://vcci-hcm.org.vn/wp-content/uploads/2022/06/d60bf053ad586e063749-scaled.jpg",
image: "/ho-so-thi-truong/d60bf053ad586e063749-scaled.jpg",
imageAlt: "Khu vực Nam Á",
description:
"Không gian thị trường giàu tiềm năng với dân số lớn, tốc độ đô thị hóa nhanh và nhu cầu hợp tác thương mại đa ngành.",
......@@ -101,7 +101,7 @@ const REGION_CONFIGS: RegionConfig[] = [
key: "tay-a",
label: "Tây Á",
title: "Tây Á",
image: "https://vcci-hcm.org.vn/wp-content/uploads/2022/06/33a2e0fdbdf67ea827e7-scaled.jpg",
image: "/ho-so-thi-truong/33a2e0fdbdf67ea827e7-scaled.jpg",
imageAlt: "Khu vực Tây Á",
description:
"Thị trường phù hợp với định hướng mở rộng đối tác năng lượng, xây dựng, thương mại dịch vụ và kết nối trung chuyển.",
......@@ -117,7 +117,7 @@ const REGION_CONFIGS: RegionConfig[] = [
key: "bac-my",
label: "Bắc Mỹ",
title: "Bắc Mỹ",
image: "https://vcci-hcm.org.vn/wp-content/uploads/2022/06/Bac-My-1-scaled.jpg",
image: "/ho-so-thi-truong/ban-do/Bac-My-1-scaled.jpg",
imageAlt: "Khu vực Bắc Mỹ",
description:
"Thị trường quy mô lớn, yêu cầu cao về tiêu chuẩn, truy xuất nguồn gốc và chiến lược tiếp cận bài bản.",
......@@ -131,7 +131,7 @@ const REGION_CONFIGS: RegionConfig[] = [
key: "nam-my",
label: "Nam Mỹ",
title: "Nam Mỹ",
image: "https://vcci-hcm.org.vn/wp-content/uploads/2022/06/Nam-My-1-scaled.jpg",
image: "/ho-so-thi-truong/ban-do/Nam-My-1-scaled.jpg",
imageAlt: "Khu vực Nam Mỹ",
description:
"Nhóm thị trường phù hợp để theo dõi nhu cầu hàng tiêu dùng, nông sản, logistics biển và liên kết chuỗi cung ứng mới.",
......@@ -146,7 +146,7 @@ const REGION_CONFIGS: RegionConfig[] = [
key: "chau-au",
label: "Châu Âu",
title: "Châu Âu",
image: "https://vcci-hcm.org.vn/wp-content/uploads/2022/06/Chau-Au-scaled.jpg",
image: "/ho-so-thi-truong/ban-do/Chau-Au-scaled.jpg",
imageAlt: "Khu vực Châu Âu",
description:
"Khu vực trọng tâm cho doanh nghiệp quan tâm đến EVFTA, tiêu chuẩn xanh, phát triển bền vững và thị trường giá trị cao.",
......@@ -162,7 +162,7 @@ const REGION_CONFIGS: RegionConfig[] = [
key: "chau-uc",
label: "Châu Úc",
title: "Châu Úc",
image: "https://vcci-hcm.org.vn/wp-content/uploads/2022/06/Chau-Uc-scaled.jpg",
image: "/ho-so-thi-truong/ban-do/Chau-Uc-scaled.jpg",
imageAlt: "Khu vực Châu Úc",
description:
"Phù hợp với chiến lược tìm hiểu nhu cầu nhập khẩu ổn định, tiêu chuẩn chất lượng cao và hợp tác thương mại dài hạn.",
......@@ -176,7 +176,7 @@ const REGION_CONFIGS: RegionConfig[] = [
key: "chau-phi",
label: "Châu Phi",
title: "Châu Phi",
image: "https://vcci-hcm.org.vn/wp-content/uploads/2022/06/Chau-Phi-1-scaled.jpg",
image: "/ho-so-thi-truong/ban-do/Chau-Phi-1-scaled.jpg",
imageAlt: "Khu vực Châu Phi",
description:
"Khu vực giàu dư địa tiếp cận thị trường mới cho hàng tiêu dùng, nông sản, vật liệu và hợp tác thương mại song phương.",
......
......@@ -131,7 +131,7 @@ export default function MemberBenefitsPage() {
<div>
<div className="flex items-center gap-2 text-[#f5c21b]">
<Phone className="h-4 w-4" />
<span className="font-semibold">Phòng Hội viên và Đào tạo</span>
<span className="font-semibold">Phòng Hội viên Đào tạo và Truyền thông</span>
</div>
<p className="mt-1">C. Thanh Thủy</p>
<p>ĐT: 0903 909 796</p>
......@@ -160,9 +160,7 @@ export default function MemberBenefitsPage() {
<span className="font-semibold">Địa chỉ</span>
</div>
<p className="mt-1">
P. 306, Lầu 3, Tòa nhà VCCI, 171 Võ Thị Sáu,
<br />
Phường Xuân Hoà, TP. HCM
P. 306, Lầu 3, Tòa nhà VCCI, 171 Võ Thị Sáu, Phường Xuân Hoà, TP. HCM
</p>
</div>
</div>
......
......@@ -87,7 +87,7 @@ export default function MemberRegistrationPage({ post }: MemberRegistrationPageP
<div className="mt-4 space-y-1 text-[18px] leading-9 text-[#1f2a44]">
<p className="font-semibold">Phòng Hội viên Đào tạo và Truyền thông:</p>
<p>C. Thúy – ĐD: 0903 909 756</p>
<p>Email: luuthanhthuy72@yahoo.com; hoivien@vcci-hcm.org.vn;</p>
<p>Email: luuthanhthuy72@yahoo.com; hoivien@vcci-hcm.org.vn</p>
<p>Điện thoại: 028. 3932 0611 – Fax: 028. 3932 5472</p>
<p>Địa chỉ: P. 306, Lầu 3, Tòa nhà VCCI, 171 Võ Thị Sáu, Phường Xuân Hòa, TP. Hồ Chí Minh</p>
</div>
......
......@@ -19,6 +19,7 @@ export type DynamicCategoryMenuItem = {
export type DynamicPostCategoryItem = {
id: string;
name: string;
slug: string;
url: string;
type: string;
};
......@@ -63,6 +64,8 @@ export type DynamicPostItem = {
ended_at: string | null;
expired_at: string | null;
registration_deadline: string | null;
location: string | null;
participation_fee: string | null;
is_featured: boolean;
is_hidden: boolean;
is_active: boolean;
......
......@@ -379,12 +379,12 @@ export default function HeaderCategoryPostsPage() {
{
kind: item.is_hidden ? "hidden" : "visible",
label: item.is_hidden
? "B\u00e0i vi\u1ebft \u0111ang \u1ea9n"
: "B\u00e0i vi\u1ebft \u0111ang hi\u1ec3n th\u1ecb",
? "Bài viết đang ẩn"
: "Bài viết đang hiển thị",
},
{
kind: "edit",
label: "Ch\u1ec9nh s\u1eeda b\u00e0i vi\u1ebft",
label: "Chỉnh sửa bài viết",
onClick: () =>
router.push(
`/admin/header-config/${categoryId}/posts/${item.id}?returnTo=${encodeURIComponent(listPath)}`,
......@@ -392,7 +392,7 @@ export default function HeaderCategoryPostsPage() {
},
{
kind: "delete",
label: "X\u00f3a b\u00e0i vi\u1ebft",
label: "Xóa bài viết",
onClick: () => setDeleteTarget(item),
},
]}
......@@ -407,9 +407,8 @@ export default function HeaderCategoryPostsPage() {
{totalPages > 1 && (
<div className="flex items-center justify-between border-t border-[#063e8e]/10 px-4 py-3">
<div className="text-sm text-gray-700">
{"Hi\u1ec3n th\u1ecb"} {(page - 1) * PAGE_SIZE + 1} {"\u0111\u1ebfn"}{" "}
{Math.min(page * PAGE_SIZE, total)} {"c\u1ee7a"}{" "}
{total} {"b\u00e0i vi\u1ebft"}
Hiển thị {(page - 1) * PAGE_SIZE + 1} đến{" "}
{Math.min(page * PAGE_SIZE, total)} của {total} bài viết
</div>
<Pagination page={page} pageCount={totalPages} onChangePage={handlePageChange} />
</div>
......
......@@ -16,6 +16,7 @@ export type HomePostItem = {
title: string;
externalLink: string;
summary: string;
contentText: string;
createdAt: string;
publishedAt: string;
startedAt: string;
......@@ -111,6 +112,7 @@ const buildPost = (params: BuildPostParams): HomePostItem => ({
title: params.title,
externalLink: `/bai-viet/${params.id}`,
summary: params.summary,
contentText: params.content,
createdAt: params.publishedAt,
publishedAt: params.publishedAt,
startedAt: params.startedAt ?? "",
......
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