Commit 5ca290c8 authored by Vũ Đình Nguyên's avatar Vũ Đình Nguyên

Merge branch 'tumlumtumla' into 'develop'

Tumlumtumla

See merge request !9
parents cbfd9322 695425f5
...@@ -79,7 +79,8 @@ const orvalConfig = async () => { ...@@ -79,7 +79,8 @@ const orvalConfig = async () => {
'UserHistory', 'UserHistory',
'Approvals', 'Approvals',
'News', 'News',
'Category' 'Category',
'NewsPageConfig',
] ]
} }
} }
......
This diff is collapsed.
...@@ -140,6 +140,7 @@ export * from './putEventsLinkParams'; ...@@ -140,6 +140,7 @@ export * from './putEventsLinkParams';
export * from './putEventsParams'; export * from './putEventsParams';
export * from './putFooterParams'; export * from './putFooterParams';
export * from './putMembershipFeeParams'; export * from './putMembershipFeeParams';
export * from './putNewsPageConfigCategoryIdBody';
export * from './putNotificationsMarkAsReadParams'; export * from './putNotificationsMarkAsReadParams';
export * from './putNotificationsParams'; export * from './putNotificationsParams';
export * from './putOrderPaymentParams'; export * from './putOrderPaymentParams';
......
/**
* Generated by orval v8.0.0-rc.0 🍺
* Do not edit manually.
* VCCI
* Coded by Meu TEAM
* OpenAPI spec version: 1.0.0
*/
export type PutNewsPageConfigCategoryIdBody = {
category_ids?: string[];
};
import { ResponseType } from '@lib/types/common'
export type EventStatus = { export type EventStatus = {
id: string; id: string;
name: string; name: string;
...@@ -22,7 +23,68 @@ export type EventOrganization = { ...@@ -22,7 +23,68 @@ export type EventOrganization = {
add_info: unknown | null; add_info: unknown | null;
organization: unknown | null; organization: unknown | null;
}; };
// useGetEventsId
export type GetEventsIdQueryResponseType = ResponseType<{
accept_entries: boolean | null
counter_cost: number
counter_count: number
created_at: string
description: string
end_time: string
event_organizations: Array<{
add_info: string | null
created_at: string
guest_image: string | null
guest_name: string | null
id: string
org_counter_count: number | null
org_table_count: number | null
organization: {
address: string
avatar: string | null
club_link: string | null
club_name: string | null
id: string
name: string
org_categories: string[]
org_link: string | null
org_status_id: string | null
organization_products: Array<{
id: string
images: string[]
}>
province: string[]
tax_code: string
users: Array<{
id: string
}>
website: string
} | null
role: 'PARTAKER' | 'MAIN' | 'SUPPORT' | 'SUPPORT_1' | 'SUPPORT_2' | 'SUPPORT_3' | 'GUEST'
status: string | null
}>
host_club: string | null
id: string
image: string
introduction: string | null
location: string
name: string
org_support_titles: string[] | null
province: string
seo_text: string
seo_text_en: string | null
start_time: string
status: string
status_status: {
code: string
id: string
name: string
name_en: string
}
table_cost: number
table_count: number
updated_at: string | null
}>
export type EventItem = { export type EventItem = {
id: string; id: string;
name: string; name: string;
...@@ -71,4 +133,4 @@ export type EventApiResponse = { ...@@ -71,4 +133,4 @@ export type EventApiResponse = {
status: string; status: string;
timeStamp: string; timeStamp: string;
violations: null | unknown; violations: null | unknown;
}; };
\ No newline at end of file
...@@ -41,7 +41,7 @@ const NewsDetailPage = () => { ...@@ -41,7 +41,7 @@ const NewsDetailPage = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-5"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-5">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-7"> <main className="lg:col-span-2 bg-white border rounded-md p-7">
<div className='pb-5 text-blue-900 text-2xl leading-normal font-medium'> <div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title} {data?.responseData?.title}
</div> </div>
<div className='flex items-center gap-2 text-sm mb-4'> <div className='flex items-center gap-2 text-sm mb-4'>
......
...@@ -13,6 +13,10 @@ function CardEvent({ event }: { event: EventItem }) { ...@@ -13,6 +13,10 @@ function CardEvent({ event }: { event: EventItem }) {
src={`${BASE_URL.imageEndpoint}${event.image}`} src={`${BASE_URL.imageEndpoint}${event.image}`}
alt={event.name} alt={event.name}
className='w-[100px] md:w-[130px] aspect-3/2 object-cover' className='w-[100px] md:w-[130px] aspect-3/2 object-cover'
onError={(e) => {
e.currentTarget.onerror = null
e.currentTarget.src = "/fallback.png"
}}
/> />
<div className='flex-1'> <div className='flex-1'>
<p className='text-[#0056b3] font-bold text-sm line-clamp-2'> <p className='text-[#0056b3] font-bold text-sm line-clamp-2'>
......
...@@ -12,8 +12,13 @@ function CardNews({ news }: { news: NewsAdminItem }) { ...@@ -12,8 +12,13 @@ function CardNews({ news }: { news: NewsAdminItem }) {
<img <img
src={`${BASE_URL.imageEndpoint}${news.thumbnail}`} src={`${BASE_URL.imageEndpoint}${news.thumbnail}`}
alt={news.title} alt={news.title}
className='w-[100px] md:w-[130px] aspect-3/2 object-cover' className="w-[100px] md:w-[130px] aspect-3/2 object-cover"
onError={(e) => {
e.currentTarget.onerror = null
e.currentTarget.src = "/fallback.png"
}}
/> />
<div className='flex-1'> <div className='flex-1'>
<p className='text-[#0056b3] font-bold text-sm line-clamp-2'> <p className='text-[#0056b3] font-bold text-sm line-clamp-2'>
{news.title} {news.title}
......
...@@ -30,7 +30,9 @@ const Page = () => { ...@@ -30,7 +30,9 @@ const Page = () => {
const swiperRef = useRef<SwiperType | null>(null) const swiperRef = useRef<SwiperType | null>(null)
const { data: categoryData, isLoading: isLoadingCategory } = useGetCategory<GetCategoryAdminResponseType>() const { data: categoryData, isLoading: isLoadingCategory } = useGetCategory<GetCategoryAdminResponseType>()
const { data: newsData, isLoading: isLoadingNews } = useGetNews<GetNewsAdminResponseType>() const { data: newsData, isLoading: isLoadingNews } = useGetNews<GetNewsAdminResponseType>(
{ pageSize: '999' },
)
const { data: eventData, isLoading: isLoadingEvent } = useGetEvents<EventApiResponse>() const { data: eventData, isLoading: isLoadingEvent } = useGetEvents<EventApiResponse>()
// filter category // filter category
...@@ -178,6 +180,10 @@ const Page = () => { ...@@ -178,6 +180,10 @@ const Page = () => {
src={`${BASE_URL.imageEndpoint}${news.thumbnail}`} src={`${BASE_URL.imageEndpoint}${news.thumbnail}`}
alt={news.title} alt={news.title}
className="w-full h-full object-cover" className="w-full h-full object-cover"
onError={(e) => {
e.currentTarget.onerror = null
e.currentTarget.src = "/fallback.png"
}}
/> />
</div> </div>
...@@ -281,7 +287,10 @@ const Page = () => { ...@@ -281,7 +287,10 @@ const Page = () => {
src={`${BASE_URL.imageEndpoint}${event.image}`} src={`${BASE_URL.imageEndpoint}${event.image}`}
alt={event.name} alt={event.name}
className="w-full h-full object-cover" className="w-full h-full object-cover"
/> onError={(e) => {
e.currentTarget.onerror = null
e.currentTarget.src = "/fallback.png"
}} />
</div> </div>
<div className="flex-1"> <div className="flex-1">
......
...@@ -14,7 +14,7 @@ import { ...@@ -14,7 +14,7 @@ import {
import Image from "next/image"; import Image from "next/image";
import vietnamMap from "@/assets/vietnam-map-white.png.webp"; import vietnamMap from "@/assets/vietnam-map-white.png.webp";
function footer() { function Footer() {
const emailRef = useRef<HTMLInputElement>(null); const emailRef = useRef<HTMLInputElement>(null);
const checkBoxRef = useRef<HTMLInputElement>(null); const checkBoxRef = useRef<HTMLInputElement>(null);
const [emailError, setEmailError] = useState(false); const [emailError, setEmailError] = useState(false);
...@@ -212,4 +212,4 @@ function footer() { ...@@ -212,4 +212,4 @@ function footer() {
); );
} }
export default footer; export default Footer;
"use client"; "use client";
import React, { useState } from "react"; import React, { useState } from "react";
import { useRouter } from "next/navigation";
import { Menu, X, Facebook, Linkedin, Twitter, Youtube } from "lucide-react"; import { Menu, X, Facebook, Linkedin, Twitter, Youtube } from "lucide-react";
import logo from "@/assets/VCCI-HCM-logo-VN-2025.png"; import logo from "@/assets/VCCI-HCM-logo-VN-2025.png";
import Image from "next/image"; import Image from "next/image";
import MenuItem from "./MenuItem"; import MenuItem from "./MenuItem";
import Link from "next/link"; import Link from "next/link";
function Header() { function Header() {
const [toggleMenu, setToggleMenu] = useState<boolean>(false); const [toggleMenu, setToggleMenu] = useState<boolean>(false);
const router = useRouter();
return ( return (
<> <>
<div className="sticky top-0 w-full h-[56px] hidden lg:flex items-center justify-center bg-[#063e8e]"> <div className="sticky top-0 w-full h-14 hidden lg:flex items-center justify-center bg-[#063e8e]">
<div className="container w-full px-4 flex items-center justify-between"> <div className="container w-full px-4 flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-[130px] h-[36px] bg-[#e8c518] flex items-center justify-center border-4 rounded-sm border-[#647792]"> <div className="w-[130px] h-9 bg-[#e8c518] flex items-center justify-center border-4 rounded-sm border-[#647792]">
<a <Link
className="font-[600] text-[14px] text-[#063E8E] hover:text-white transition" className="font-semibold text-[14px] text-primary hover:text-white transition"
href="#" href="https://vccihcm.vn/dang-ky"
> >
Đăng Ký Hội Viên Đăng Ký Hội Viên
</a> </Link>
</div> </div>
<a <Link
className="px-3 py-2 text-[14px] text-white hover:opacity-80" className="px-3 py-2 text-[14px] text-white hover:opacity-80"
href="#" href="/site-map"
> >
sitemap Sitemap
</a> </Link>
<a <Link
className="px-3 py-2 text-[14px] text-white hover:opacity-80" className="px-3 py-2 text-[14px] text-white hover:opacity-80"
href="#" href="https://vccihcm.vn/lien-he"
> >
Liên hệ Liên hệ
</a> </Link>
</div> </div>
<div className="flex items-center gap-8"> <div className="flex items-center gap-8">
...@@ -41,6 +42,14 @@ function Header() { ...@@ -41,6 +42,14 @@ function Header() {
className="bg-white h-12 rounded-sm outline-none px-4 w-64 placeholder:text-sm" className="bg-white h-12 rounded-sm outline-none px-4 w-64 placeholder:text-sm"
type="text" type="text"
placeholder="Tìm kiếm" placeholder="Tìm kiếm"
onKeyDown={(e) => {
if (e.key === "Enter") {
const value =
(e.currentTarget as HTMLInputElement).value || "";
const encoded = encodeURIComponent(value);
router.push(`/search?q=${encoded}`);
}
}}
/> />
<div className="flex gap-2"> <div className="flex gap-2">
{[Facebook, Twitter, Youtube, Linkedin].map((Icon, i) => ( {[Facebook, Twitter, Youtube, Linkedin].map((Icon, i) => (
...@@ -57,17 +66,17 @@ function Header() { ...@@ -57,17 +66,17 @@ function Header() {
</div> </div>
</div> </div>
<div className="sticky top-0 z-50 bg-[#ededed] shadow-md"> <div className="sticky top-0 z-50 bg-[#ededed] shadow-md py-4">
<div className="container m-auto"> <div className="container m-auto">
<div className="w-full flex justify-between items-center"> <div className="w-full flex justify-between items-center">
{/* Logo */} {/* Logo */}
<a href="/" className="flex items-center"> <Link href="/">
<Image <Image
className="w-[140px] object-contain lg:ml-0 ml-10" className="w-[140px] object-contain"
src={logo} src={logo}
alt="VCCI HCM" alt="VCCI HCM"
/> />
</a> </Link>
{/* Desktop Menu */} {/* Desktop Menu */}
<nav className="hidden lg:flex items-center"> <nav className="hidden lg:flex items-center">
...@@ -112,7 +121,7 @@ function Header() { ...@@ -112,7 +121,7 @@ function Header() {
items={[ items={[
{ {
title: "Định Nghĩa Chung", title: "Định Nghĩa Chung",
link: "dinh-nghia-chung", link: "",
}, },
{ {
title: "Mục Đích Của C/O", title: "Mục Đích Của C/O",
......
...@@ -20,6 +20,10 @@ const Page: React.FC = () => { ...@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6"> <main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div> <div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main> </main>
......
...@@ -9,15 +9,17 @@ import Image from "next/image"; ...@@ -9,15 +9,17 @@ import Image from "next/image";
import { useGetNews } from "@api/endpoints/news"; import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type"; import { GetNewsResponseType } from "@api/types/NewsPage.type";
import { PATHS } from "@constants/paths"; import { PATHS } from "@constants/paths";
import { Spinner } from "@components/ui/spinner";
export default function Page() { export default function Page() {
const [submitSearch] = useState(""); const [submitSearch] = useState("");
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const pageSize = 5; const pageSize = 5;
const { data: allData } = useGetNews<GetNewsResponseType>({ const { data: allData,isLoading } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize), pageSize: String(pageSize),
currentPage: String(page), currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined, // filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters:'category@=Chủ đề'
}); });
return ( return (
<div className="min-h-screen container mx-auto p-4"> <div className="min-h-screen container mx-auto p-4">
...@@ -28,26 +30,35 @@ export default function Page() { ...@@ -28,26 +30,35 @@ export default function Page() {
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-background "> <main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden"> <div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => ( {isLoading ? (
<NewsContent key={news.id} news={news} link={`${PATHS.ownerRepresentatives}/chu-de/${news.id}`}/> <div className="flex justify-center items-center py-12">
))} <Spinner className="size-8" />
<span className="ml-2 text-gray-600">Đang tải dữ liệu...</span>
</div>
) : (
<>
{allData?.responseData.rows.map((news) => (
<NewsContent key={news.id} news={news} link={`${PATHS.ownerRepresentatives}/chu-de/${news.id}`}/>
))}
<div className="w-full flex justify-center mt-4"> <div className="w-full flex justify-center mt-4">
<Pagination <Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)} pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)} page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)} onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))} onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() => onGoToNextPage={() =>
setPage( setPage(
Math.min( Math.min(
Number(allData?.responseData.totalPages ?? 1), Number(allData?.responseData.totalPages ?? 1),
page + 1 page + 1
) )
) )
} }
/> />
</div> </div>
</>
)}
</div> </div>
</main> </main>
......
...@@ -2,8 +2,24 @@ ...@@ -2,8 +2,24 @@
import { NewsItem } from '@app/dai-dien-gioi-chu/lib/types/NewsPage.type'; import { NewsItem } from '@app/dai-dien-gioi-chu/lib/types/NewsPage.type';
import Links from '@links/index' import Links from '@links/index'
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import parse from 'html-react-parser' import {EventItem} from "@api/types/event";
function NewsContent({ news ,link}: { news: NewsItem ,link:string}) { // Helper: remove <img> tags and extract plain text from HTML
const stripImagesAndHtml = (html?: string) => {
if (!html) return ''
// remove img tags first
const withoutImgs = html.replace(/<img[^>]*>/gi, '')
// use DOMParser on client for robust extraction
if (typeof window !== 'undefined' && typeof DOMParser !== 'undefined') {
try {
const doc = new DOMParser().parseFromString(withoutImgs, 'text/html')
return doc.body.textContent || ''
} catch {
// fallback to regex
}
}
return withoutImgs.replace(/<[^>]*>/g, '')
}
function NewsContent({ news ,link,event}: { news?: NewsItem ,link:string,event?:EventItem}) {
return ( return (
<a <a
...@@ -11,8 +27,8 @@ function NewsContent({ news ,link}: { news: NewsItem ,link:string}) { ...@@ -11,8 +27,8 @@ function NewsContent({ news ,link}: { news: NewsItem ,link:string}) {
className="flex flex-col hover:no-underline sm:flex-row gap-2 mb-6 bg-white rounded-lg shadow-sm p-4 border items-start min-w-0" className="flex flex-col hover:no-underline sm:flex-row gap-2 mb-6 bg-white rounded-lg shadow-sm p-4 border items-start min-w-0"
> >
<img <img
src={`${Links.imageEndpoint}${news.thumbnail}`} src={`${Links.imageEndpoint}${news?news.thumbnail:event?.image}`}
alt={news.title} alt={news?news.title:event?.name}
className="w-full sm:w-56 md:w-64 h-40 md:h-36 object-cover shrink-0" className="w-full sm:w-56 md:w-64 h-40 md:h-36 object-cover shrink-0"
onError={(e) => { onError={(e) => {
e.currentTarget.src = "/img-error.png" e.currentTarget.src = "/img-error.png"
...@@ -21,14 +37,14 @@ function NewsContent({ news ,link}: { news: NewsItem ,link:string}) { ...@@ -21,14 +37,14 @@ function NewsContent({ news ,link}: { news: NewsItem ,link:string}) {
/> />
<div className="flex-1 min-w-0 pl-0 sm:pl-4"> <div className="flex-1 min-w-0 pl-0 sm:pl-4">
<p className="text-primary font-semibold text-base md:text-lg hover:underline line-clamp-2 wrap-break-word hover:no-underline"> <p className="text-primary font-semibold text-base md:text-lg hover:underline line-clamp-2 wrap-break-word">
{news.title} {news?.title}{event?.name}
</p> </p>
<div className="text-sm my-2 text-[#00AED5]">{dayjs(news.release_at).format('DD/MM/YYYY')}</div> <div className="text-sm my-2 text-[#00AED5]">{dayjs(news?news?.created_at:event?.created_at).format('DD/MM/YYYY')}</div>
<div className="text-sm text-[#777] line-clamp-3"> <div className="text-sm text-[#777] line-clamp-3">
<div className="text-sm prose tiptap">{parse(news.description)}</div> <div className="text-sm prose tiptap">{stripImagesAndHtml(news?news.description:event?.description)}</div>
</div> </div>
</div> </div>
</a> </a>
......
export const SAMPLE_HTML = ` export const SAMPLE_HTML = `
<div class="document"> <div class="document">
<h1 style="font-size:18px; font-weight:700; margin-bottom:8px;">Chức năng Đại diện Người sử dụng lao động</h1> <h1 class="text-primary" style="font-size:20px; font-weight:700; margin-bottom:12px;">Chức năng Đại diện Người sử dụng lao động</h1>
<p>Chức năng Đại diện Người sử dụng lao động (NSDLĐ):</p> <p>Chức năng Đại diện Người sử dụng lao động (NSDLĐ):</p>
......
...@@ -20,6 +20,10 @@ const Page: React.FC = () => { ...@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6"> <main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div> <div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main> </main>
......
...@@ -9,12 +9,13 @@ import Image from "next/image"; ...@@ -9,12 +9,13 @@ import Image from "next/image";
import { useGetNews } from "@api/endpoints/news"; import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type"; import { GetNewsResponseType } from "@api/types/NewsPage.type";
import { PATHS } from "@constants/paths"; import { PATHS } from "@constants/paths";
import { Spinner } from "@components/ui/spinner";
export default function Page() { export default function Page() {
const [submitSearch] = useState(""); const [submitSearch] = useState("");
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const pageSize = 5; const pageSize = 5;
const { data: allData } = useGetNews<GetNewsResponseType>({ const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize), pageSize: String(pageSize),
currentPage: String(page), currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined, filters: submitSearch ? `title @=${submitSearch}` : undefined,
...@@ -28,26 +29,35 @@ export default function Page() { ...@@ -28,26 +29,35 @@ export default function Page() {
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-background "> <main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden"> <div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => ( {isLoading ? (
<NewsContent key={news.id} news={news} link={`${PATHS.ownerRepresentatives}/tap-huan-nsdld/${news.id}`} /> <div className="flex justify-center items-center py-12">
))} <Spinner className="size-8" />
<span className="ml-2 text-gray-600">Đang tải tập huấn NSDLĐ...</span>
</div>
) : (
<>
{allData?.responseData.rows.map((news) => (
<NewsContent key={news.id} news={news} link={`${PATHS.ownerRepresentatives}/tap-huan-nsdld/${news.id}`} />
))}
<div className="w-full flex justify-center mt-4"> <div className="w-full flex justify-center mt-4">
<Pagination <Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)} pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)} page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)} onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))} onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() => onGoToNextPage={() =>
setPage( setPage(
Math.min( Math.min(
Number(allData?.responseData.totalPages ?? 1), Number(allData?.responseData.totalPages ?? 1),
page + 1 page + 1
) )
) )
} }
/> />
</div> </div>
</>
)}
</div> </div>
</main> </main>
......
...@@ -4,23 +4,31 @@ import Image from "next/image"; ...@@ -4,23 +4,31 @@ import Image from "next/image";
import ListCategory from "../../components/list-category"; import ListCategory from "../../components/list-category";
import { OWNER_REPRESENTATIVES_CATEGORIES } from "@constants/categories"; import { OWNER_REPRESENTATIVES_CATEGORIES } from "@constants/categories";
import ListFilter from "../../components/list-filter"; import ListFilter from "../../components/list-filter";
import { useGetNewsId } from '@/api/endpoints/news'; import { useGetNewsId } from "@/api/endpoints/news";
import parse from "html-react-parser"; import parse from "html-react-parser";
import { useParams } from 'next/navigation' import { useParams } from "next/navigation";
import { GetNewsDetailResponseType } from '@lib/types/news-detail-response-data'; import { GetNewsDetailResponseType } from "@lib/types/news-detail-response-data";
// ...existing code... // ...existing code...
const Page: React.FC = () => { const Page: React.FC = () => {
const { id } = useParams() const { id } = useParams();
const { data, isLoading } = useGetNewsId<GetNewsDetailResponseType>(id as string) const { data, isLoading } = useGetNewsId<GetNewsDetailResponseType>(
id as string
);
return ( return (
<div className="min-h-screen w-full container mx-auto p-4"> <div className="min-h-screen w-full container mx-auto p-4">
<div className="w-full flex flex-col gap-5"> <div className="w-full flex flex-col gap-5">
<ListCategory categories={OWNER_REPRESENTATIVES_CATEGORIES} /> <ListCategory categories={OWNER_REPRESENTATIVES_CATEGORIES} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6"> <main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div> <div className="pb-5 text-primary text-2xl leading-normal font-medium">
{data?.responseData?.title}
</div>
<hr className="py-2" />
<div className="p-7.5 prose tiptap overflow-hidden">
{parse(data?.responseData?.description ?? "")}
</div>
</main> </main>
{/* Sidebar */} {/* Sidebar */}
......
...@@ -9,14 +9,16 @@ import Image from "next/image"; ...@@ -9,14 +9,16 @@ import Image from "next/image";
import { useGetNews } from "@api/endpoints/news"; import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type"; import { GetNewsResponseType } from "@api/types/NewsPage.type";
import { PATHS } from "@constants/paths"; import { PATHS } from "@constants/paths";
import { Spinner } from "@components/ui/spinner";
export default function Page() { export default function Page() {
const [submitSearch] = useState(""); const [submitSearch,setsubmitSearch] = useState("");
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const pageSize = 5; const pageSize = 5;
const { data: allData } = useGetNews<GetNewsResponseType>({ const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize), pageSize: String(pageSize),
currentPage: String(page), currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined, filters: submitSearch ? `title @=${submitSearch},category@=Tin liên quan` : 'category@=Tin liên quan',
}); });
return ( return (
<div className="min-h-screen container mx-auto p-4"> <div className="min-h-screen container mx-auto p-4">
...@@ -27,25 +29,34 @@ export default function Page() { ...@@ -27,25 +29,34 @@ export default function Page() {
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-background "> <main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden"> <div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => ( {isLoading ? (
<NewsContent key={news.id} news={news} link={`${PATHS.ownerRepresentatives}/tin-lien-quan/${news.id}`} /> <div className="flex justify-center items-center py-12">
))} <Spinner className="size-8" />
<span className="ml-2 text-gray-600">Đang tải tin liên quan...</span>
</div>
) : (
<>
{allData?.responseData.rows.map((news) => (
<NewsContent key={news.id} news={news} link={`${PATHS.ownerRepresentatives}/tin-lien-quan/${news.id}`} />
))}
<div className="w-full flex justify-center mt-4"> <div className="w-full flex justify-center mt-4">
<Pagination <Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)} pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)} page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)} onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))} onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() => setPage(Math.min(Number(allData?.responseData.totalPages ?? 1), page + 1))} onGoToNextPage={() => setPage(Math.min(Number(allData?.responseData.totalPages ?? 1), page + 1))}
/> />
</div> </div>
</>
)}
</div> </div>
</main> </main>
{/* Sidebar */} {/* Sidebar */}
<aside className="space-y-6 order-first lg:order-last"> <aside className="space-y-6 order-first lg:order-last">
<ListFilter /> <ListFilter onSearch={setsubmitSearch}/>
<div className="bg-white border rounded-md overflow-hidden hidden lg:block"> <div className="bg-white border rounded-md overflow-hidden hidden lg:block">
<div className="w-full h-56 relative bg-gray-100"> <div className="w-full h-56 relative bg-gray-100">
......
...@@ -20,6 +20,10 @@ const Page: React.FC = () => { ...@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6"> <main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div> <div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main> </main>
......
...@@ -4,43 +4,53 @@ import ListCategory from "@app/dai-dien-gioi-chu/components/list-category"; ...@@ -4,43 +4,53 @@ import ListCategory from "@app/dai-dien-gioi-chu/components/list-category";
import { EVENT_CATEGORIES } from "@constants/categories"; import { EVENT_CATEGORIES } from "@constants/categories";
import EventFilter from "@app/dai-dien-gioi-chu/components/event-filter"; import EventFilter from "@app/dai-dien-gioi-chu/components/event-filter";
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news"; import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import { Pagination} from "@components/base/pagination"; import { Pagination } from "@components/base/pagination";
import Image from "next/image"; import Image from "next/image";
import { useGetNews } from "@api/endpoints/news"; import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type"; import { GetNewsResponseType } from "@api/types/NewsPage.type";
import { PATHS } from "@constants/paths"; import { PATHS } from "@constants/paths";
import { Spinner } from "@components/ui/spinner";
export default function Page() { export default function Page() {
const [submitSearch] = useState(""); const [submitSearch] = useState("");
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const pageSize = 5; const pageSize = 5;
const { data: allData } = useGetNews<GetNewsResponseType>({ const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize), pageSize: String(pageSize),
currentPage: String(page), currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined, filters: submitSearch ? `title @=${submitSearch}` : 'category @=Đào tạo',
}); });
return ( return (
<div className="min-h-screen container mx-auto p-4"> <div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5"> <div className="w-full flex flex-col gap-5">
<ListCategory categories={EVENT_CATEGORIES} /> <ListCategory categories={EVENT_CATEGORIES} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-background "> <main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden"> <div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => ( {isLoading ? (
<NewsContent key={news.id} news={news} link={`${PATHS.event}/dao-tao/${news.id}`}/> <div className="flex justify-center items-center py-12">
))} <Spinner className="size-8" />
<span className="ml-2 text-gray-600">Đang tải khóa đào tạo...</span>
</div>
) : (
<>
{allData?.responseData.rows.map((news) => (
<NewsContent key={news.id} news={news} link={`${PATHS.event}/dao-tao/${news.id}`} />
))}
<div className="w-full flex justify-center mt-4"> <div className="w-full flex justify-center mt-4">
<Pagination <Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)} pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)} page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)} onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))} onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() => setPage(Math.min(Number(allData?.responseData.totalPages ?? 1), page + 1))} onGoToNextPage={() => setPage(Math.min(Number(allData?.responseData.totalPages ?? 1), page + 1))}
/> />
</div> </div>
</>
)}
</div> </div>
</main> </main>
......
...@@ -15,7 +15,7 @@ export default function Page() { ...@@ -15,7 +15,7 @@ export default function Page() {
return ( return (
<div className="min-h-screen container mx-auto p-4"> <div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5"> <div className="w-full flex flex-col gap-5">
<ListCategory categories={EVENT_CATEGORIES} /> <ListCategory categories={EVENT_CATEGORIES} />
</div> </div>
</div> </div>
); );
......
...@@ -4,14 +4,16 @@ import Image from "next/image"; ...@@ -4,14 +4,16 @@ import Image from "next/image";
import ListCategory from "@app/dai-dien-gioi-chu/components/list-category"; import ListCategory from "@app/dai-dien-gioi-chu/components/list-category";
import { EVENT_CATEGORIES } from "@constants/categories"; import { EVENT_CATEGORIES } from "@constants/categories";
import ListFilter from "@app/dai-dien-gioi-chu/components/list-filter"; import ListFilter from "@app/dai-dien-gioi-chu/components/list-filter";
import { useGetNewsId } from '@/api/endpoints/news'; import {useGetEventsId} from '@/api/endpoints/event';
import parse from "html-react-parser"; import parse from "html-react-parser";
import { useParams } from 'next/navigation' import { useParams } from 'next/navigation'
import { GetNewsDetailResponseType } from '@lib/types/news-detail-response-data'; import { GetNewsDetailResponseType } from '@lib/types/news-detail-response-data';
import {GetEventsIdQueryResponseType} from '@api/types/event';
import { Spinner } from "@components/ui/spinner";
// ...existing code... // ...existing code...
const Page: React.FC = () => { const Page: React.FC = () => {
const { id } = useParams() const { id } = useParams()
const { data, isLoading } = useGetNewsId<GetNewsDetailResponseType>(id as string) const { data, isLoading } = useGetEventsId<GetEventsIdQueryResponseType>(id as string)
return ( return (
<div className="min-h-screen w-full container mx-auto p-4"> <div className="min-h-screen w-full container mx-auto p-4">
<div className="w-full flex flex-col gap-5"> <div className="w-full flex flex-col gap-5">
...@@ -20,7 +22,20 @@ const Page: React.FC = () => { ...@@ -20,7 +22,20 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6"> <main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div> {isLoading ? (
<div className="flex justify-center items-center py-12">
<Spinner className="size-8" />
<span className="ml-2 text-gray-600">Đang tải chi tiết sự kiện...</span>
</div>
) : (
<>
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.name}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</>
)}
</main> </main>
{/* Sidebar */} {/* Sidebar */}
......
...@@ -5,43 +5,57 @@ import { EVENT_CATEGORIES } from "@constants/categories"; ...@@ -5,43 +5,57 @@ import { EVENT_CATEGORIES } from "@constants/categories";
import EventFilter from "@app/dai-dien-gioi-chu/components/event-filter"; import EventFilter from "@app/dai-dien-gioi-chu/components/event-filter";
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news"; import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
// ...existing code... // ...existing code...
import { Pagination} from "@components/base/pagination"; import { Pagination } from "@components/base/pagination";
import Image from "next/image"; import Image from "next/image";
import { useGetEvents } from '@api/endpoints/event'
import { EventApiResponse } from '@api/types/event'
import { useGetNews } from "@api/endpoints/news"; import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type"; import { GetNewsResponseType } from "@api/types/NewsPage.type";
import { PATHS } from "@constants/paths"; import { PATHS } from "@constants/paths";
import { Spinner } from "@components/ui/spinner";
export default function Page() { export default function Page() {
const [submitSearch] = useState(""); const [submitSearch] = useState("");
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const pageSize = 5; const pageSize = 5;
const { data: allData } = useGetNews<GetNewsResponseType>({ const { data: allData, isLoading } = useGetEvents<EventApiResponse>({
pageSize: String(pageSize), pageSize: String(pageSize),
currentPage: String(page), currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined, sortField: 'start_time',
sortOrder: 'ASC',
filters: submitSearch ? `title @=${submitSearch},start_time>${new Date()}` : `start_time>${new Date()}`,
}); });
return ( return (
<div className="min-h-screen container mx-auto p-4"> <div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5"> <div className="w-full flex flex-col gap-5">
<ListCategory categories={EVENT_CATEGORIES} /> <ListCategory categories={EVENT_CATEGORIES} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-background "> <main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden"> <div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => ( {isLoading ? (
<NewsContent key={news.id} news={news} link={`${PATHS.event}/su-kien/${news.id}`}/> <div className="flex justify-center items-center py-12">
))} <Spinner className="size-8" />
<span className="ml-2 text-gray-600">Đang tải sự kiện...</span>
</div>
) : (
<>
{allData?.responseData.rows.map((event) => (
<NewsContent key={event.id} event={event} link={`${PATHS.event}/su-kien/${event.id}`} />
))}
<div className="w-full flex justify-center mt-4"> <div className="w-full flex justify-center mt-4">
<Pagination <Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)} pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)} page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)} onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))} onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() => setPage(Math.min(Number(allData?.responseData.totalPages ?? 1), page + 1))} onGoToNextPage={() => setPage(Math.min(Number(allData?.responseData.totalPages ?? 1), page + 1))}
/> />
</div> </div>
</>
)}
</div> </div>
</main> </main>
......
...@@ -7,6 +7,7 @@ import { Pagination } from "@components/base/pagination"; ...@@ -7,6 +7,7 @@ import { Pagination } from "@components/base/pagination";
import Image from "next/image"; import Image from "next/image";
import { useGetNews } from "@api/endpoints/news"; import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type"; import { GetNewsResponseType } from "@api/types/NewsPage.type";
import { Spinner } from "@components/ui/spinner";
export default function Page() { export default function Page() {
const [submitSearch] = useState(""); const [submitSearch] = useState("");
...@@ -16,7 +17,7 @@ export default function Page() { ...@@ -16,7 +17,7 @@ export default function Page() {
const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({ const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize), pageSize: String(pageSize),
currentPage: String(page), currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined, filters: submitSearch ? `title @=${submitSearch}` : `category @=Kết nối hội viên`,
}); });
return ( return (
<div className="min-h-screen container mx-auto pb-4"> <div className="min-h-screen container mx-auto pb-4">
...@@ -26,28 +27,37 @@ export default function Page() { ...@@ -26,28 +27,37 @@ export default function Page() {
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-background"> <main className="lg:col-span-2 bg-background">
<div className="pb-5 overflow-hidden"> <div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => ( {isLoading ? (
<CardNews key={news.id} news={news} /> <div className="flex justify-center items-center py-12">
))} <Spinner className="size-8" />
<span className="ml-2 text-gray-600">Đang tải dữ liệu kết nối hội viên...</span>
</div>
) : (
<>
{allData?.responseData.rows.map((news) => (
<CardNews key={news.id} news={news} />
))}
<div className='w-full flex justify-center mt-4'> <div className='w-full flex justify-center mt-4'>
<Pagination <Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)} pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)} page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)} onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))} onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() => onGoToNextPage={() =>
setPage( setPage(
Math.min( Math.min(
Number(allData?.responseData.totalPages ?? 1), Number(allData?.responseData.totalPages ?? 1),
page + 1 page + 1
) )
) )
} }
/> />
</div> </div>
</div> </>
</main> )}
</div>
</main>
{/* Sidebar */} {/* Sidebar */}
<aside className="space-y-6"> <aside className="space-y-6">
......
...@@ -7,6 +7,7 @@ import { Pagination } from '@components/base/pagination' ...@@ -7,6 +7,7 @@ import { Pagination } from '@components/base/pagination'
import Image from "next/image"; import Image from "next/image";
import { useGetNews } from '@api/endpoints/news' import { useGetNews } from '@api/endpoints/news'
import { GetNewsResponseType } from '@api/types/NewsPage.type' import { GetNewsResponseType } from '@api/types/NewsPage.type'
import { Spinner } from "@components/ui/spinner";
export default function Page() { export default function Page() {
const [submitSearch] = useState('') const [submitSearch] = useState('')
...@@ -16,7 +17,7 @@ export default function Page() { ...@@ -16,7 +17,7 @@ export default function Page() {
const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({ const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize), pageSize: String(pageSize),
currentPage: String(page), currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined, filters: submitSearch ? `title @=${submitSearch}` : 'category @=Tin hội viên',
}) })
return ( return (
<div className="min-h-screen container mx-auto pb-4"> <div className="min-h-screen container mx-auto pb-4">
...@@ -26,26 +27,35 @@ export default function Page() { ...@@ -26,26 +27,35 @@ export default function Page() {
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-background"> <main className="lg:col-span-2 bg-background">
<div className='pb-5 overflow-hidden'> <div className='pb-5 overflow-hidden'>
{allData?.responseData.rows.map((news) => ( {isLoading ? (
<CardNews key={news.id} news={news} /> <div className="flex justify-center items-center py-12">
))} <Spinner className="size-8" />
<span className="ml-2 text-gray-600">Đang tải tin hội viên...</span>
</div>
) : (
<>
{allData?.responseData.rows.map((news) => (
<CardNews key={news.id} news={news} />
))}
<div className='w-full flex justify-center mt-4'> <div className='w-full flex justify-center mt-4'>
<Pagination <Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)} pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)} page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)} onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))} onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() => onGoToNextPage={() =>
setPage( setPage(
Math.min( Math.min(
Number(allData?.responseData.totalPages ?? 1), Number(allData?.responseData.totalPages ?? 1),
page + 1 page + 1
) )
) )
} }
/> />
</div> </div>
</>
)}
</div> </div>
</main> </main>
......
"use client";
import React, { useState, Suspense } from "react";
import ListCategory from "@app/dai-dien-gioi-chu/components/list-category";
import { OWNER_REPRESENTATIVES_CATEGORIES } from "@constants/categories";
import ListFilter from "@app/dai-dien-gioi-chu/components/list-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";
import { Spinner } from "@components/ui/spinner";
import { PATHS } from "@constants/paths";
import { useSearchParams } from 'next/navigation'
function SearchContent() {
const [page, setPage] = useState(1);
const searchParams = useSearchParams()
const query = searchParams.get('q') //
const pageSize = 5;
const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize),
currentPage: String(page),
filters: query ? `title @=${query}` : undefined,
});
return (
<div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5">
<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">
<h1 className="text-md md:text-lg font-semibold leading-6 text-gray-900">
{" "}
Search Results for: {query}
</h1>
</div>
</div>
</div>
<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">
{isLoading ? (
<div className="flex justify-center items-center py-12">
<Spinner className="size-8" />
<span className="ml-2 text-gray-600">Đang tìm kiếm...</span>
</div>
) : (
<>
{allData?.responseData.rows.map((news) => (
<NewsContent
key={news.id}
news={news}
link={`${PATHS.mediaInformation}/tin-vcci/${news.id}`}
/>
))}
<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 order-first lg:order-last">
<div className="bg-white border rounded-md overflow-hidden hidden lg:block">
<div className="w-full h-62 relative bg-gray-100">
<Image
src="/banner.webp"
alt="Quảng cáo"
fill
className="object-cover"
/>
</div>
</div>
</aside>
</div>
</div>
</div>
);
}
export default function Page() {
return (
<Suspense fallback={
<div className="min-h-screen container mx-auto p-4 flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-[#063e8e] mx-auto"></div>
<p className="mt-4 text-gray-600">Loading search results...</p>
</div>
</div>
}>
<SearchContent />
</Suspense>
);
}
export type SiteMapItem = {
label: string;
url: string;
children?: SiteMapItem[];
};
export const MOCK_SITEMAP: SiteMapItem[] = [
{ label: "Trang chủ", url: "/homepage", children: [] },
{
label: "Giới thiệu",
url: "/gioi-thieu",
children: [
{ label: "Về VCCI-HCM", url: "/gioi-thieu/ve-vcci-hcm", children: [] },
{ label: "Chức năng và nhiệm vụ", url: "/gioi-thieu/chuc-nang-nhiem-vu", children: [] },
{ label: "Sơ đồ tổ chức", url: "/gioi-thieu/so-do-to-chuc", children: [] },
{ label: "Dịch vụ cung cấp", url: "/gioi-thieu/dich-vu-cung-cap", children: [] },
],
},
{
label: "Hội viên",
url: "/hoi-vien",
children: [
{ label: "Lợi ích của hội viên VCCI", url: "/hoi-vien/loi-ich-hoi-vien", children: [] },
{ label: "Đăng ký hội viên", url: "/hoi-vien/dang-ky-hoi-vien", children: [] },
{ label: "Kết nối hội viên", url: "/hoi-vien/ket-noi-hoi-vien", children: [] },
{ label: "Tin hội viên", url: "/hoi-vien/tin-hoi-vien", children: [] },
],
},
{
label: "Hoạt động",
url: "/hoat-dong",
children: [
{ label: "Sự kiện", url: "/hoat-dong/su-kien", children: [] },
{ label: "Đào tạo", url: "/hoat-dong/dao-tao", children: [] },
],
},
{
label: "Xuất xứ hàng hóa",
url: "/xuat-xu-hang-hoa",
children: [
{ label: "Định nghĩa chung", url: "/xuat-xu-hang-hoa", children: [] },
{ label: "Mục đích của C/O", url: "/xuat-xu-hang-hoa/muc-dich-co", children: [] },
{ label: "Luật áp dụng về C/O", url: "/xuat-xu-hang-hoa/luat-ap-dung-co", children: [] },
{ label: "Thủ tục cấp C/O", url: "/xuat-xu-hang-hoa/thu-tuc-cap-co", children: [] },
{ label: "Biểu mẫu C/O và cách khai", url: "/xuat-xu-hang-hoa/bieu-mau-co", children: [] },
{ label: "Phí và lệ phí cấp C/O", url: "/xuat-xu-hang-hoa/phi-le-phi-cap-co", children: [] },
{ label: "Điểm cấp và thời gian cấp C/O", url: "/xuat-xu-hang-hoa/diem-cap-thoi-gian", children: [] },
{ label: "Thông tin liên hệ", url: "/xuat-xu-hang-hoa/lien-he", children: [] },
],
},
{
label: "Đại diện giới chủ",
url: "/dai-dien-gioi-chu",
children: [
{ label: "Chức năng đại diện người sử dụng lao động", url: "/dai-dien-gioi-chu/chuc-nang", children: [] },
{ label: "Sự kiện - tập huấn NSDLĐ", url: "/dai-dien-gioi-chu/su-kien-tap-huan", children: [] },
{ label: "Tin liên quan", url: "/dai-dien-gioi-chu/tin-lien-quan", children: [] },
{ label: "Chủ đề", url: "/dai-dien-gioi-chu/chu-de", children: [] },
],
},
{
label: "Xúc tiến thương mại",
url: "/xuc-tien-thuong-mai",
children: [
{ label: "Hồ sơ thị trường", url: "/xuc-tien-thuong-mai/ho-so-thi-truong", children: [] },
{ label: "Môi trường kinh doanh", url: "/xuc-tien-thuong-mai/doi-song-kinh-doanh", children: [] },
{ label: "Cơ hội kinh doanh", url: "/xuc-tien-thuong-mai/co-hoi-kinh-doanh", children: [] },
{ label: "Hỗ trợ kinh doanh", url: "/xuc-tien-thuong-mai/ho-tro-kinh-doanh", children: [] },
],
},
{
label: "Thông tin truyền thông",
url: "/thong-tin-truyen-thong",
children: [
{ label: "Tin VCCI", url: "/thong-tin-truyen-thong/tin-vcci", children: [] },
{ label: "Tin kinh tế", url: "/thong-tin-truyen-thong/tin-kinh-te", children: [] },
{ label: "Tin doanh nghiệp", url: "/thong-tin-truyen-thong/tin-doanh-nghiep", children: [] },
{ label: "Chuyên đề", url: "/thong-tin-truyen-thong/chuyen-de", children: [] },
{ label: "Thông tin chính sách và pháp luật", url: "/thong-tin-truyen-thong/chinh-sach-phap-luat", children: [] },
{ label: "Ấn phẩm", url: "/thong-tin-truyen-thong/an-pham", children: [] },
{ label: "Thư viện tài liệu", url: "/thong-tin-truyen-thong/thu-vien-tai-lieu", children: [] },
],
},
];
export default MOCK_SITEMAP;
"use client";
import React from "react";
import { MOCK_SITEMAP } from "./_lib/mock-data";
import Link from "next/link";
function SiteMapPage() {
const homepage = MOCK_SITEMAP[0];
const sections = MOCK_SITEMAP.slice(1);
return (
<div className="min-h-screen bg-gray-50 py-12">
<div className="container mx-auto px-4">
<h1 className="text-3xl font-bold text-center mb-12 text-[#063e8e]">
SƠ ĐỒ TRANG WEB
</h1>
{/* Sitemap Structure */}
<div className="relative flex flex-col items-center">
{/* Homepage - Top Level */}
<div className="relative mb-20">
<Link
href={homepage.url}
className="block bg-[#063e8e] text-white px-8 py-4 rounded-lg font-semibold text-center hover:bg-[#0a4fb5] transition shadow-lg min-w-[200px]"
>
{homepage.label.toUpperCase()}
</Link>
{/* Vertical line from homepage down */}
<div className="absolute left-1/2 -translate-x-1/2 top-full h-16 w-0.5 bg-gray-600"></div>
</div>
{/* Main Sections - Second Level */}
<div className="relative w-full max-w-[1400px]">
{/* Horizontal line connecting all sections */}
<div className="absolute top-0 left-[7%] right-[7%] h-0.5 bg-gray-600 z-0"></div>
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-7 gap-6 relative pt-4">
{sections.map((section, idx) => (
<div key={idx} className="relative flex flex-col items-center">
{/* Vertical line from horizontal bar down to section */}
<div className="absolute -top-4 left-1/2 -translate-x-1/2 h-4 w-0.5 bg-gray-600 z-10"></div>
{/* Section Box */}
<div className="relative z-20">
<Link
href={section.url}
className="flex bg-[#063e8e] text-white px-4 py-3 rounded-md font-medium text-center hover:bg-[#0a4fb5] transition shadow-md w-full text-sm min-h-20 items-center justify-center"
>
<span className="leading-tight">{section.label.toUpperCase()}</span>
</Link>
{/* Vertical line from section down to children */}
{section.children && section.children.length > 0 && (
<div className="absolute left-1/2 -translate-x-1/2 top-full h-6 w-0.5 bg-gray-600 z-10"></div>
)}
</div>
{/* Children - Third Level */}
{section.children && section.children.length > 0 && (
<div className="mt-6 flex flex-col gap-3 w-full relative z-20">
{section.children.map((child, childIdx) => (
<div key={childIdx} className="relative">
{/* Vertical line connecting child to parent */}
{childIdx === 0 ? (
// First child connects to the line from parent
<div className="absolute left-1/2 -translate-x-1/2 -top-6 h-6 w-0.5 bg-gray-600"></div>
) : (
// Other children connect to the vertical spine
<div className="absolute left-1/2 -translate-x-1/2 -top-[calc(1.5rem+0.375rem)] bottom-1/2 w-0.5 bg-gray-600"></div>
)}
{/* Horizontal line from spine to child box */}
<div className="absolute left-1/2 top-1/2 -translate-y-1/2 w-1/2 h-0.5 bg-gray-600 -translate-x-full"></div>
<Link
href={child.url}
className="block bg-gray-400 text-white px-3 py-2.5 rounded text-xs font-medium text-center hover:bg-gray-500 transition shadow-sm leading-tight relative z-10"
>
{child.label.toUpperCase()}
</Link>
</div>
))}
</div>
)}
</div>
))}
</div>
</div>
</div>
</div>
<style jsx>{`
@media (max-width: 768px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
`}</style>
</div>
);
}
export default SiteMapPage;
...@@ -20,6 +20,10 @@ const Page: React.FC = () => { ...@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6"> <main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div> <div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main> </main>
......
...@@ -5,46 +5,58 @@ import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories"; ...@@ -5,46 +5,58 @@ import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories";
// ...existing code... // ...existing code...
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news"; import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import ListFilter from "@app/dai-dien-gioi-chu/components/list-filter"; import ListFilter from "@app/dai-dien-gioi-chu/components/list-filter";
import { Pagination} from "@components/base/pagination"; import { Pagination } from "@components/base/pagination";
import Image from "next/image"; import Image from "next/image";
import { useGetNews } from "@api/endpoints/news"; import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type"; import { GetNewsResponseType } from "@api/types/NewsPage.type";
import { PATHS } from "@constants/paths"; import { PATHS } from "@constants/paths";
import { Spinner } from "@components/ui/spinner";
export default function Page() { export default function Page() {
const [submitSearch] = useState(""); const [submitSearch] = useState("");
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const pageSize = 5; const pageSize = 5;
const { data: allData } = useGetNews<GetNewsResponseType>({ const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize), pageSize: String(pageSize),
currentPage: String(page), currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined, filters: submitSearch ? `title @=${submitSearch},category @=Chuyên đề` : 'category @=Chuyên đề',
}); });
return ( return (
<div className="min-h-screen container mx-auto p-4"> <div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5"> <div className="w-full flex flex-col gap-5">
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} /> <ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-background "> <main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden"> <div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => ( {isLoading ? (
<NewsContent <div className="flex justify-center items-center py-12">
key={news.id} <Spinner className="size-8" />
news={news} <span className="ml-2 text-gray-600">Đang tải chuyên đề...</span>
link={`${PATHS.mediaInformation}/chuyen-de/${news.id}`} </div>
/> ) : allData?.responseData.rows.length === 0 ? (
))} <p className="text-center py-4">Không có dữ liệu</p>
) : (
<>
{allData?.responseData.rows.map((news) => (
<NewsContent
key={news.id}
news={news}
link={`${PATHS.mediaInformation}/chuyen-de/${news.id}`}
/>
))}
<div className="w-full flex justify-center mt-4"> <div className="w-full flex justify-center mt-4">
<Pagination <Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)} pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)} page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)} onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))} onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() => setPage(Math.min(Number(allData?.responseData.totalPages ?? 1), page + 1))} onGoToNextPage={() => setPage(Math.min(Number(allData?.responseData.totalPages ?? 1), page + 1))}
/> />
</div> </div>
</>
)}
</div> </div>
</main> </main>
......
...@@ -15,7 +15,7 @@ export default function Page() { ...@@ -15,7 +15,7 @@ export default function Page() {
return ( return (
<div className="min-h-screen container mx-auto p-4"> <div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5"> <div className="w-full flex flex-col gap-5">
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} /> <ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
</div> </div>
</div> </div>
......
...@@ -20,6 +20,10 @@ const Page: React.FC = () => { ...@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6"> <main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div> <div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main> </main>
......
...@@ -5,7 +5,7 @@ import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories"; ...@@ -5,7 +5,7 @@ import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories";
// ...existing code... // ...existing code...
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news"; import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import ListFilter from "@app/dai-dien-gioi-chu/components/list-filter"; import ListFilter from "@app/dai-dien-gioi-chu/components/list-filter";
import { Pagination} from "@components/base/pagination"; import { Pagination } from "@components/base/pagination";
import Image from "next/image"; import Image from "next/image";
import { useGetNews } from "@api/endpoints/news"; import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type"; import { GetNewsResponseType } from "@api/types/NewsPage.type";
...@@ -15,27 +15,30 @@ export default function Page() { ...@@ -15,27 +15,30 @@ export default function Page() {
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const pageSize = 5; const pageSize = 5;
const { data: allData } = useGetNews<GetNewsResponseType>({ const { data: allData,isLoading } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize), pageSize: String(pageSize),
currentPage: String(page), currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined, filters: submitSearch ? `title @=${submitSearch},category @=Thông tin chính sách và pháp luật` : 'category @=Thông tin chính sách và pháp luật',
}); });
return ( return (
<div className="min-h-screen container mx-auto p-4"> <div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5"> <div className="w-full flex flex-col gap-5">
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} /> <ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-background "> <main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden"> <div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => ( {allData?.responseData.rows.length === 0 ? (
<NewsContent <p className="text-center py-4">Không có dữ liệu</p>
key={news.id} ) : (
news={news} allData?.responseData.rows.map((news) => (
link={`${PATHS.mediaInformation}/thong-tin-chinh-sach-va-phap-luat/${news.id}`} <NewsContent
/> key={news.id}
))} news={news}
link={`${PATHS.mediaInformation}/thong-tin-chinh-sach-va-phap-luat/${news.id}`}
/>
)))}
<div className="w-full flex justify-center mt-4"> <div className="w-full flex justify-center mt-4">
<Pagination <Pagination
......
...@@ -20,6 +20,10 @@ const Page: React.FC = () => { ...@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6"> <main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div> <div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main> </main>
......
...@@ -4,48 +4,60 @@ import ListCategory from "@app/dai-dien-gioi-chu/components/list-category"; ...@@ -4,48 +4,60 @@ import ListCategory from "@app/dai-dien-gioi-chu/components/list-category";
import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories"; import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories";
// ...existing code... // ...existing code...
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news"; import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import { Pagination} from "@components/base/pagination"; import { Pagination } from "@components/base/pagination";
import ListFilter from "@app/dai-dien-gioi-chu/components/list-filter"; import ListFilter from "@app/dai-dien-gioi-chu/components/list-filter";
import Image from "next/image"; import Image from "next/image";
import { useGetNews } from "@api/endpoints/news"; import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type"; import { GetNewsResponseType } from "@api/types/NewsPage.type";
import { PATHS } from "@constants/paths"; import { PATHS } from "@constants/paths";
import { Spinner } from "@components/ui/spinner";
export default function Page() { export default function Page() {
const [submitSearch] = useState(""); const [submitSearch] = useState("");
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const pageSize = 5; const pageSize = 5;
const { data: allData } = useGetNews<GetNewsResponseType>({ const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize), pageSize: String(pageSize),
currentPage: String(page), currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined, filters: submitSearch ? `title @=${submitSearch},category @=Thư viện tài liệu'` : 'category @=Thư viện tài liệu',
}); });
return ( return (
<div className="min-h-screen container mx-auto p-4"> <div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5"> <div className="w-full flex flex-col gap-5">
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} /> <ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-background "> <main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden"> <div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => ( {isLoading ? (
<NewsContent <div className="flex justify-center items-center py-12">
key={news.id} <Spinner className="size-8" />
news={news} <span className="ml-2 text-gray-600">Đang tải thư viện tài liệu...</span>
link={`${PATHS.mediaInformation}/thu-vien-tai-lieu/${news.id}`} </div>
/> ) : allData?.responseData.rows.length === 0 ? (
))} <p className="text-center py-4">Không có dữ liệu</p>
) : (
<>
{allData?.responseData.rows.map((news) => (
<NewsContent
key={news.id}
news={news}
link={`${PATHS.mediaInformation}/thu-vien-tai-lieu/${news.id}`}
/>
))}
<div className="w-full flex justify-center mt-4"> <div className="w-full flex justify-center mt-4">
<Pagination <Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)} pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)} page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)} onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))} onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() => setPage(Math.min(Number(allData?.responseData.totalPages ?? 1), page + 1))} onGoToNextPage={() => setPage(Math.min(Number(allData?.responseData.totalPages ?? 1), page + 1))}
/> />
</div> </div>
</>
)}
</div> </div>
</main> </main>
......
...@@ -20,6 +20,10 @@ const Page: React.FC = () => { ...@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6"> <main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div> <div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main> </main>
......
...@@ -5,47 +5,59 @@ import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories"; ...@@ -5,47 +5,59 @@ import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories";
// ...existing code... // ...existing code...
import ListFilter from "@app/dai-dien-gioi-chu/components/list-filter"; import ListFilter from "@app/dai-dien-gioi-chu/components/list-filter";
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news"; import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import { Pagination} from "@components/base/pagination"; import { Pagination } from "@components/base/pagination";
import Image from "next/image"; import Image from "next/image";
import { useGetNews } from "@api/endpoints/news"; import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type"; import { GetNewsResponseType } from "@api/types/NewsPage.type";
import { PATHS } from "@constants/paths"; import { PATHS } from "@constants/paths";
import { Spinner } from "@components/ui/spinner";
export default function Page() { export default function Page() {
const [submitSearch] = useState(""); const [submitSearch] = useState("");
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const pageSize = 5; const pageSize = 5;
const { data: allData } = useGetNews<GetNewsResponseType>({ const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize), pageSize: String(pageSize),
currentPage: String(page), currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined, filters: submitSearch ? `title @=${submitSearch},category @=Tin doanh nghiệp` : 'category @=Tin doanh nghiệp',
}); });
return ( return (
<div className="min-h-screen container mx-auto p-4"> <div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5"> <div className="w-full flex flex-col gap-5">
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} /> <ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-background "> <main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden"> <div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => ( {isLoading ? (
<NewsContent <div className="flex justify-center items-center py-12">
key={news.id} <Spinner className="size-8" />
news={news} <span className="ml-2 text-gray-600">Đang tải tin doanh nghiệp...</span>
link={`${PATHS.mediaInformation}/tin-doanh-nghiep/${news.id}`} </div>
/> ) : allData?.responseData.rows.length === 0 ? (
))} <p className="text-center py-4">Không có dữ liệu</p>
) : (
<>
{allData?.responseData.rows.map((news) => (
<NewsContent
key={news.id}
news={news}
link={`${PATHS.mediaInformation}/tin-doanh-nghiep/${news.id}`}
/>
))}
<div className="w-full flex justify-center mt-4"> <div className="w-full flex justify-center mt-4">
<Pagination <Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)} pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)} page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)} onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))} onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() => setPage(Math.min(Number(allData?.responseData.totalPages ?? 1), page + 1))} onGoToNextPage={() => setPage(Math.min(Number(allData?.responseData.totalPages ?? 1), page + 1))}
/> />
</div> </div>
</>
)}
</div> </div>
</main> </main>
......
...@@ -20,6 +20,10 @@ const Page: React.FC = () => { ...@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6"> <main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div> <div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main> </main>
......
...@@ -4,47 +4,59 @@ import ListCategory from "@app/dai-dien-gioi-chu/components/list-category"; ...@@ -4,47 +4,59 @@ import ListCategory from "@app/dai-dien-gioi-chu/components/list-category";
import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories"; import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories";
// ...existing code... // ...existing code...
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news"; import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import { Pagination} from "@components/base/pagination"; import { Pagination } from "@components/base/pagination";
import Image from "next/image"; import Image from "next/image";
import { useGetNews } from "@api/endpoints/news"; import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type"; import { GetNewsResponseType } from "@api/types/NewsPage.type";
import { PATHS } from "@constants/paths"; import { PATHS } from "@constants/paths";
import { Spinner } from "@components/ui/spinner";
export default function Page() { export default function Page() {
const [submitSearch] = useState(""); const [submitSearch] = useState("");
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const pageSize = 5; const pageSize = 5;
const { data: allData } = useGetNews<GetNewsResponseType>({ const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize), pageSize: String(pageSize),
currentPage: String(page), currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined, filters: submitSearch ? `title @=${submitSearch},category @=Tin kinh tế` : 'category @=Tin kinh tế',
}); });
return ( return (
<div className="min-h-screen container mx-auto p-4"> <div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5"> <div className="w-full flex flex-col gap-5">
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} /> <ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-background "> <main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden"> <div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => ( {isLoading ? (
<NewsContent <div className="flex justify-center items-center py-12">
key={news.id} <Spinner className="size-8" />
news={news} <span className="ml-2 text-gray-600">Đang tải tin kinh tế...</span>
link={`${PATHS.mediaInformation}/tin-kinh-te/${news.id}`} </div>
/> ) : allData?.responseData.rows.length === 0 ? (
))} <p className="text-center py-4">Không có dữ liệu</p>
) : (
<>
{allData?.responseData.rows.map((news) => (
<NewsContent
key={news.id}
news={news}
link={`${PATHS.mediaInformation}/tin-kinh-te/${news.id}`}
/>
))}
<div className="w-full flex justify-center mt-4"> <div className="w-full flex justify-center mt-4">
<Pagination <Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)} pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)} page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)} onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))} onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() => setPage(Math.min(Number(allData?.responseData.totalPages ?? 1), page + 1))} onGoToNextPage={() => setPage(Math.min(Number(allData?.responseData.totalPages ?? 1), page + 1))}
/> />
</div> </div>
</>
)}
</div> </div>
</main> </main>
......
...@@ -20,6 +20,10 @@ const Page: React.FC = () => { ...@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6"> <main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div> <div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</main> </main>
......
...@@ -6,17 +6,18 @@ import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories"; ...@@ -6,17 +6,18 @@ import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories";
import NewsContent from "@app/dai-dien-gioi-chu/components/card-news"; import NewsContent from "@app/dai-dien-gioi-chu/components/card-news";
import ListFilter from "@app/dai-dien-gioi-chu/components/list-filter"; 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 "@app/dai-dien-gioi-chu/components/event-calendar";
import { Pagination} from "@components/base/pagination"; import { Pagination } from "@components/base/pagination";
import Image from "next/image"; import Image from "next/image";
import { useGetNews } from "@api/endpoints/news"; import { useGetNews } from "@api/endpoints/news";
import { PATHS } from "@constants/paths"; import { PATHS } from "@constants/paths";
import { GetNewsResponseType } from "@api/types/NewsPage.type"; import { GetNewsResponseType } from "@api/types/NewsPage.type";
import { Spinner } from "@components/ui/spinner";
export default function Page() { export default function Page() {
const [submitSearch] = useState(""); const [submitSearch, setSubmitSearch] = useState("");
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const pageSize = 5; const pageSize = 5;
const { data: allData } = useGetNews<GetNewsResponseType>({ const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize), pageSize: String(pageSize),
currentPage: String(page), currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined, filters: submitSearch ? `title @=${submitSearch}` : undefined,
...@@ -24,36 +25,47 @@ export default function Page() { ...@@ -24,36 +25,47 @@ export default function Page() {
return ( return (
<div className="min-h-screen container mx-auto p-4"> <div className="min-h-screen container mx-auto p-4">
<div className="w-full flex flex-col gap-5"> <div className="w-full flex flex-col gap-5">
<ListCategory categories={MEDIA_INFORMATION_CATEGORIES} /> <ListCategory categories={MEDIA_INFORMATION_CATEGORIES} />
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-background "> <main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden"> <div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => ( {isLoading ? (
<NewsContent <div className="flex justify-center items-center py-12">
key={news.id} <Spinner className="size-8" />
news={news} <span className="ml-2 text-gray-600">Đang tải tin VCCI...</span>
link={`${PATHS.mediaInformation}/tin-vcci/${news.id}`} </div>
/> ) : allData?.responseData.rows.length === 0 ? (
))} <p className="text-center py-4">Không có dữ liệu</p>
) : (
<>
{allData?.responseData.rows.map((news) => (
<NewsContent
key={news.id}
news={news}
link={`${PATHS.mediaInformation}/tin-vcci/${news.id}`}
/>
))}
<div className="w-full flex justify-center mt-4"> <div className="w-full flex justify-center mt-4">
<Pagination <Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)} pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)} page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)} onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))} onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() => setPage(Math.min(Number(allData?.responseData.totalPages ?? 1), page + 1))} onGoToNextPage={() => setPage(Math.min(Number(allData?.responseData.totalPages ?? 1), page + 1))}
/> />
</div> </div>
</>
)}
</div> </div>
</main> </main>
{/* Sidebar */} {/* Sidebar */}
<aside className="space-y-6"> <aside className="space-y-6">
<ListFilter /> <ListFilter onSearch={setSubmitSearch} />
<EventCalendar/> <EventCalendar />
<div className="bg-white border rounded-md overflow-hidden"> <div className="bg-white border rounded-md overflow-hidden">
<div className="w-full h-56 relative bg-gray-100"> <div className="w-full h-56 relative bg-gray-100">
<Image <Image
......
...@@ -6,7 +6,7 @@ import Image from "next/image"; ...@@ -6,7 +6,7 @@ import Image from "next/image";
export default function page() { export default function page() {
return ( return (
<div className="bg-[#f6f6f6]"> <div className="bg-[#f6f6f6]">
<div className="max-w-[1200px] m-auto flex flex-col gap-5 mb-[50px]"> <div className="container m-auto flex flex-col gap-5 mb-[50px]">
<div className="border-[#e5e7f2] border-[1px]"> <div className="border-[#e5e7f2] border-[1px]">
<ListCategory /> <ListCategory />
</div> </div>
......
...@@ -8,6 +8,7 @@ import { useGetNewsId } from '@/api/endpoints/news'; ...@@ -8,6 +8,7 @@ import { useGetNewsId } from '@/api/endpoints/news';
import parse from "html-react-parser"; import parse from "html-react-parser";
import { useParams } from 'next/navigation' import { useParams } from 'next/navigation'
import { GetNewsDetailResponseType } from '@lib/types/news-detail-response-data'; import { GetNewsDetailResponseType } from '@lib/types/news-detail-response-data';
import { Spinner } from "@components/ui/spinner";
// ...existing code... // ...existing code...
const Page: React.FC = () => { const Page: React.FC = () => {
const { id } = useParams() const { id } = useParams()
...@@ -20,7 +21,20 @@ const Page: React.FC = () => { ...@@ -20,7 +21,20 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6"> <main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div> {isLoading ? (
<div className="flex justify-center items-center py-12">
<Spinner className="size-8" />
<span className="ml-2 text-gray-600">Đang tải nội dung...</span>
</div>
) : (
<>
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</>
)}
</main> </main>
{/* Sidebar */} {/* Sidebar */}
......
...@@ -8,6 +8,7 @@ import { useGetNewsId } from '@/api/endpoints/news'; ...@@ -8,6 +8,7 @@ import { useGetNewsId } from '@/api/endpoints/news';
import parse from "html-react-parser"; import parse from "html-react-parser";
import { useParams } from 'next/navigation' import { useParams } from 'next/navigation'
import { GetNewsDetailResponseType } from '@lib/types/news-detail-response-data'; import { GetNewsDetailResponseType } from '@lib/types/news-detail-response-data';
import { Spinner } from "@components/ui/spinner";
// ...existing code... // ...existing code...
const Page: React.FC = () => { const Page: React.FC = () => {
const { id } = useParams() const { id } = useParams()
...@@ -20,7 +21,22 @@ const Page: React.FC = () => { ...@@ -20,7 +21,22 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6"> <main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div> {isLoading ? (
<div className="flex justify-center items-center py-12">
<Spinner className="size-8" />
<span className="ml-2 text-gray-600">Đang tải chi tiết cơ hội kinh doanh...</span>
</div>
) : data?.responseData ? (
<>
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</>
) : (
<p className="text-center py-4">Không có dữ liệu</p>
)}
</main> </main>
{/* Sidebar */} {/* Sidebar */}
......
...@@ -9,15 +9,17 @@ import Image from "next/image"; ...@@ -9,15 +9,17 @@ import Image from "next/image";
import { useGetNews } from "@api/endpoints/news"; import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type"; import { GetNewsResponseType } from "@api/types/NewsPage.type";
import { PATHS } from "@constants/paths"; import { PATHS } from "@constants/paths";
import { Spinner } from "@components/ui/spinner";
export default function Page() { export default function Page() {
const [submitSearch] = useState(""); const [submitSearch] = useState("");
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const pageSize = 5; const pageSize = 5;
const { data: allData } = useGetNews<GetNewsResponseType>({ const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize), pageSize: String(pageSize),
currentPage: String(page), currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined, // filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters:'category@=Cơ hội kinh doanh'
}); });
return ( return (
<div className="min-h-screen container mx-auto p-4"> <div className="min-h-screen container mx-auto p-4">
...@@ -28,26 +30,37 @@ export default function Page() { ...@@ -28,26 +30,37 @@ export default function Page() {
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-background "> <main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden"> <div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => ( {isLoading ? (
<NewsContent key={news.id} news={news} link={`${PATHS.tradePromotion}/${news.id}`} /> <div className="flex justify-center items-center py-12">
))} <Spinner className="size-8" />
<span className="ml-2 text-gray-600">Đang tải cơ hội kinh doanh...</span>
</div>
) : allData?.responseData.rows.length === 0 ? (
<p className="text-center py-4">Không có dữ liệu</p>
) : (
<>
{allData?.responseData.rows.map((news) => (
<NewsContent key={news.id} news={news} link={`${PATHS.tradePromotion}/${news.id}`} />
))}
<div className="w-full flex justify-center mt-4"> <div className="w-full flex justify-center mt-4">
<Pagination <Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)} pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)} page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)} onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))} onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() => onGoToNextPage={() =>
setPage( setPage(
Math.min( Math.min(
Number(allData?.responseData.totalPages ?? 1), Number(allData?.responseData.totalPages ?? 1),
page + 1 page + 1
) )
) )
} }
/> />
</div> </div>
</>
)}
</div> </div>
</main> </main>
......
...@@ -8,6 +8,7 @@ import { useGetNewsId } from '@/api/endpoints/news'; ...@@ -8,6 +8,7 @@ import { useGetNewsId } from '@/api/endpoints/news';
import parse from "html-react-parser"; import parse from "html-react-parser";
import { useParams } from 'next/navigation' import { useParams } from 'next/navigation'
import { GetNewsDetailResponseType } from '@lib/types/news-detail-response-data'; import { GetNewsDetailResponseType } from '@lib/types/news-detail-response-data';
import { Spinner } from "@components/ui/spinner";
// ...existing code... // ...existing code...
const Page: React.FC = () => { const Page: React.FC = () => {
const { id } = useParams() const { id } = useParams()
...@@ -20,7 +21,22 @@ const Page: React.FC = () => { ...@@ -20,7 +21,22 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6"> <main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div> {isLoading ? (
<div className="flex justify-center items-center py-12">
<Spinner className="size-8" />
<span className="ml-2 text-gray-600">Đang tải chi tiết hỗ trợ kinh doanh...</span>
</div>
) : data?.responseData ? (
<>
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</>
) : (
<p className="text-center py-4">Không có dữ liệu</p>
)}
</main> </main>
{/* Sidebar */} {/* Sidebar */}
......
...@@ -8,15 +8,17 @@ import Image from "next/image"; ...@@ -8,15 +8,17 @@ import Image from "next/image";
import { useGetNews } from "@api/endpoints/news"; import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type"; import { GetNewsResponseType } from "@api/types/NewsPage.type";
import { PATHS } from "@constants/paths"; import { PATHS } from "@constants/paths";
import { Spinner } from "@components/ui/spinner";
export default function Page() { export default function Page() {
const [submitSearch] = useState(""); const [submitSearch] = useState("");
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const pageSize = 5; const pageSize = 5;
const { data: allData } = useGetNews<GetNewsResponseType>({ const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize), pageSize: String(pageSize),
currentPage: String(page), currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined, // filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters:'category@=Hỗ trợ kinh doanh'
}); });
return ( return (
<div className="min-h-screen container mx-auto p-4"> <div className="min-h-screen container mx-auto p-4">
...@@ -27,30 +29,41 @@ export default function Page() { ...@@ -27,30 +29,41 @@ export default function Page() {
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-background "> <main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden"> <div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => ( {isLoading ? (
<NewsContent <div className="flex justify-center items-center py-12">
key={news.id} <Spinner className="size-8" />
news={news} <span className="ml-2 text-gray-600">Đang tải hỗ trợ kinh doanh...</span>
link={`${PATHS.tradePromotion}/${news.id}`} </div>
/> ) : allData?.responseData.rows.length === 0 ? (
))} <p className="text-center py-4">Không có dữ liệu</p>
) : (
<>
{allData?.responseData.rows.map((news) => (
<NewsContent
key={news.id}
news={news}
link={`${PATHS.tradePromotion}/${news.id}`}
/>
))}
<div className="w-full flex justify-center mt-4"> <div className="w-full flex justify-center mt-4">
<Pagination <Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)} pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)} page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)} onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))} onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() => onGoToNextPage={() =>
setPage( setPage(
Math.min( Math.min(
Number(allData?.responseData.totalPages ?? 1), Number(allData?.responseData.totalPages ?? 1),
page + 1 page + 1
) )
) )
} }
/> />
</div> </div>
</>
)}
</div> </div>
</main> </main>
......
...@@ -8,6 +8,7 @@ import { useGetNewsId } from '@/api/endpoints/news'; ...@@ -8,6 +8,7 @@ import { useGetNewsId } from '@/api/endpoints/news';
import parse from "html-react-parser"; import parse from "html-react-parser";
import { useParams } from 'next/navigation' import { useParams } from 'next/navigation'
import { GetNewsDetailResponseType } from '@lib/types/news-detail-response-data'; import { GetNewsDetailResponseType } from '@lib/types/news-detail-response-data';
import { Spinner } from "@components/ui/spinner";
// ...existing code... // ...existing code...
const Page: React.FC = () => { const Page: React.FC = () => {
const { id } = useParams() const { id } = useParams()
...@@ -20,7 +21,22 @@ const Page: React.FC = () => { ...@@ -20,7 +21,22 @@ const Page: React.FC = () => {
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-white border rounded-md p-6"> <main className="lg:col-span-2 bg-white border rounded-md p-6">
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div> {isLoading ? (
<div className="flex justify-center items-center py-12">
<Spinner className="size-8" />
<span className="ml-2 text-gray-600">Đang tải chi tiết môi trường kinh doanh...</span>
</div>
) : data?.responseData ? (
<>
<div className='pb-5 text-primary text-2xl leading-normal font-medium'>
{data?.responseData?.title}
</div>
<hr className="py-2"/>
<div className="p-7.5 prose tiptap overflow-hidden">{parse(data?.responseData?.description ?? '')}</div>
</>
) : (
<p className="text-center py-4">Không có dữ liệu</p>
)}
</main> </main>
{/* Sidebar */} {/* Sidebar */}
......
...@@ -9,15 +9,17 @@ import EventCalendar from "@app/dai-dien-gioi-chu/components/event-calendar"; ...@@ -9,15 +9,17 @@ import EventCalendar from "@app/dai-dien-gioi-chu/components/event-calendar";
import { useGetNews } from "@api/endpoints/news"; import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type"; import { GetNewsResponseType } from "@api/types/NewsPage.type";
import { PATHS } from "@constants/paths"; import { PATHS } from "@constants/paths";
import { Spinner } from "@components/ui/spinner";
export default function Page() { export default function Page() {
const [submitSearch] = useState(""); const [submitSearch] = useState("");
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const pageSize = 5; const pageSize = 5;
const { data: allData } = useGetNews<GetNewsResponseType>({ const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize), pageSize: String(pageSize),
currentPage: String(page), currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined, // filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters:'category@=Môi trường kinh doanh'
}); });
return ( return (
<div className="min-h-screen container mx-auto p-4"> <div className="min-h-screen container mx-auto p-4">
...@@ -28,30 +30,41 @@ export default function Page() { ...@@ -28,30 +30,41 @@ export default function Page() {
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-background "> <main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden"> <div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => ( {isLoading ? (
<NewsContent <div className="flex justify-center items-center py-12">
key={news.id} <Spinner className="size-8" />
news={news} <span className="ml-2 text-gray-600">Đang tải môi trường kinh doanh...</span>
link={`${PATHS.tradePromotion}/${news.id}`} </div>
/> ) : allData?.responseData.rows.length === 0 ? (
))} <p className="text-center py-4">Không có dữ liệu</p>
) : (
<>
{allData?.responseData.rows.map((news) => (
<NewsContent
key={news.id}
news={news}
link={`${PATHS.tradePromotion}/${news.id}`}
/>
))}
<div className="w-full flex justify-center mt-4"> <div className="w-full flex justify-center mt-4">
<Pagination <Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)} pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)} page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)} onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))} onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() => onGoToNextPage={() =>
setPage( setPage(
Math.min( Math.min(
Number(allData?.responseData.totalPages ?? 1), Number(allData?.responseData.totalPages ?? 1),
page + 1 page + 1
) )
) )
} }
/> />
</div> </div>
</>
)}
</div> </div>
</main> </main>
......
...@@ -8,12 +8,13 @@ import Image from "next/image"; ...@@ -8,12 +8,13 @@ import Image from "next/image";
import { useGetNews } from "@api/endpoints/news"; import { useGetNews } from "@api/endpoints/news";
import { GetNewsResponseType } from "@api/types/NewsPage.type"; import { GetNewsResponseType } from "@api/types/NewsPage.type";
import { PATHS } from "@constants/paths"; import { PATHS } from "@constants/paths";
import { Spinner } from "@components/ui/spinner";
export default function Page() { export default function Page() {
const [submitSearch] = useState(""); const [submitSearch] = useState("");
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const pageSize = 5; const pageSize = 5;
const { data: allData } = useGetNews<GetNewsResponseType>({ const { data: allData, isLoading } = useGetNews<GetNewsResponseType>({
pageSize: String(pageSize), pageSize: String(pageSize),
currentPage: String(page), currentPage: String(page),
filters: submitSearch ? `title @=${submitSearch}` : undefined, filters: submitSearch ? `title @=${submitSearch}` : undefined,
...@@ -27,26 +28,35 @@ export default function Page() { ...@@ -27,26 +28,35 @@ export default function Page() {
{/* Main content */} {/* Main content */}
<main className="lg:col-span-2 bg-background "> <main className="lg:col-span-2 bg-background ">
<div className="pb-5 overflow-hidden"> <div className="pb-5 overflow-hidden">
{allData?.responseData.rows.map((news) => ( {isLoading ? (
<NewsContent key={news.id} news={news} link={`${PATHS.tradePromotion}/${news.id}`} /> <div className="flex justify-center items-center py-12">
))} <Spinner className="size-8" />
<span className="ml-2 text-gray-600">Đang tải tin xúc tiến thương mại...</span>
</div>
) : (
<>
{allData?.responseData.rows.map((news) => (
<NewsContent key={news.id} news={news} link={`${PATHS.tradePromotion}/${news.id}`} />
))}
<div className="w-full flex justify-center mt-4"> <div className="w-full flex justify-center mt-4">
<Pagination <Pagination
pageCount={Number(allData?.responseData.totalPages ?? 1)} pageCount={Number(allData?.responseData.totalPages ?? 1)}
page={Number(allData?.responseData.currentPage ?? page)} page={Number(allData?.responseData.currentPage ?? page)}
onChangePage={(p) => setPage(p)} onChangePage={(p) => setPage(p)}
onGoToPreviousPage={() => setPage(Math.max(1, page - 1))} onGoToPreviousPage={() => setPage(Math.max(1, page - 1))}
onGoToNextPage={() => onGoToNextPage={() =>
setPage( setPage(
Math.min( Math.min(
Number(allData?.responseData.totalPages ?? 1), Number(allData?.responseData.totalPages ?? 1),
page + 1 page + 1
) )
) )
} }
/> />
</div> </div>
</>
)}
</div> </div>
</main> </main>
......
...@@ -2,8 +2,24 @@ ...@@ -2,8 +2,24 @@
import { NewsItem } from '@app/dai-dien-gioi-chu/lib/types/NewsPage.type'; import { NewsItem } from '@app/dai-dien-gioi-chu/lib/types/NewsPage.type';
import Links from '@links/index' import Links from '@links/index'
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import parse from 'html-react-parser'
function NewsContent({ news, link }: { news: NewsItem, link: string }) { // Helper: remove <img> tags and extract plain text from HTML
const stripImagesAndHtml = (html?: string) => {
if (!html) return ''
// remove img tags first
const withoutImgs = html.replace(/<img[^>]*>/gi, '')
// use DOMParser on client for robust extraction
if (typeof window !== 'undefined' && typeof DOMParser !== 'undefined') {
try {
const doc = new DOMParser().parseFromString(withoutImgs, 'text/html')
return doc.body.textContent || ''
} catch {
// fallback to regex
}
}
return withoutImgs.replace(/<[^>]*>/g, '')
}
function NewsContent({ news ,link}: { news: NewsItem ,link:string}) {
return ( return (
<a <a
...@@ -14,20 +30,21 @@ function NewsContent({ news, link }: { news: NewsItem, link: string }) { ...@@ -14,20 +30,21 @@ function NewsContent({ news, link }: { news: NewsItem, link: string }) {
src={`${Links.imageEndpoint}${news.thumbnail}`} src={`${Links.imageEndpoint}${news.thumbnail}`}
alt={news.title} alt={news.title}
className="w-full sm:w-56 md:w-64 h-40 md:h-36 object-cover shrink-0" className="w-full sm:w-56 md:w-64 h-40 md:h-36 object-cover shrink-0"
onError={(e) => { onError={(e) => {
e.currentTarget.src = "/img-error.png" e.currentTarget.src = "/img-error.png"
}} }}
/> />
<div className="flex-1 min-w-0 pl-0 sm:pl-4"> <div className="flex-1 min-w-0 pl-0 sm:pl-4">
<p className="text-primary font-semibold text-base md:text-lg line-clamp-2 wrap-break-word hover:no-underline"> <p className="text-primary font-semibold text-base md:text-lg hover:underline line-clamp-2 wrap-break-word">
{news.title} {news.title}
</p> </p>
<div className="text-sm my-2 text-[#00AED5]">{dayjs(news.release_at).format('DD/MM/YYYY')}</div> <div className="text-sm my-2 text-[#00AED5]">{dayjs(news.release_at).format('DD/MM/YYYY')}</div>
<div className="text-sm text-[#777] line-clamp-3"> <div className="text-sm text-[#777] line-clamp-3">
<div className="text-sm prose tiptap">{parse(news.description)}</div> <div className="text-sm prose tiptap">{stripImagesAndHtml(news.description)}</div>
</div> </div>
</div> </div>
</a> </a>
......
...@@ -4,24 +4,47 @@ import { AppEditorContentProps } from './AppEditorContent.type'; ...@@ -4,24 +4,47 @@ import { AppEditorContentProps } from './AppEditorContent.type';
import './AppEditorContent.css'; import './AppEditorContent.css';
const AppEditorContent: FC<AppEditorContentProps> = ({ value = '', className = '' }) => { const AppEditorContent: FC<AppEditorContentProps> = ({ value = '', className = '' }) => {
const transform = (node: DOMNode): JSX.Element | null => { const transform = (node: DOMNode): JSX.Element | string | undefined | null => {
if (node instanceof Element && node.tagName === 'strong') {
// 1. Xử lý Text Node
if (node instanceof Text) {
return node.data;
}
if (!(node instanceof Element)) return undefined;
const tagName = node.tagName.toLowerCase();
// ✅ FIX LỖI: Ép kiểu (as DOMNode) để Type 'ChildNode' khớp với 'DOMNode'
const children = node.children
? node.children.map(child => transform(child as DOMNode))
: [];
// --- LOGIC XỬ LÝ THEO YÊU CẦU ---
// 2. ✅ Xóa thẻ <img>
if (tagName === 'img') {
return <></>;
}
// 3. ✅ Xóa thẻ <a> nhưng giữ lại nội dung
if (tagName === 'a') {
// Trả về children đã được xử lý (làm phẳng thẻ <a>)
return <>{children}</>;
}
// 4. ✅ Xử lý thẻ <strong>
if (tagName === 'strong') {
return ( return (
<strong className="custom-strong"> <strong className="custom-strong">
{node.children && Array.isArray(node.children) {children}
? node.children.map((child, index) => {
if (typeof child === 'string') {
return child;
} else if (child instanceof Text) {
return child.data;
}
return null;
})
: null}
</strong> </strong>
); );
} }
return null;
// 5. ✅ Render các thẻ HTML khác (Fallback)
// Trả về undefined để thư viện tự động render các thẻ còn lại (p, div, br,...)
return undefined;
}; };
return ( return (
...@@ -31,4 +54,4 @@ const AppEditorContent: FC<AppEditorContentProps> = ({ value = '', className = ' ...@@ -31,4 +54,4 @@ const AppEditorContent: FC<AppEditorContentProps> = ({ value = '', className = '
); );
}; };
export default AppEditorContent; export default AppEditorContent;
\ No newline at end of file
import { Spinner } from "@/components/ui/spinner";
import { cn } from "@/lib/utils";
interface LoadingStateProps {
message?: string;
size?: "sm" | "md" | "lg";
className?: string;
showMessage?: boolean;
}
export function LoadingState({
message = "Đang tải dữ liệu...",
size = "md",
className,
showMessage = true
}: LoadingStateProps) {
const sizeClasses = {
sm: "size-4",
md: "size-6",
lg: "size-8"
};
const paddingClasses = {
sm: "py-6",
md: "py-8",
lg: "py-12"
};
return (
<div className={cn("flex justify-center items-center", paddingClasses[size], className)}>
<Spinner className={sizeClasses[size]} />
{showMessage && (
<span className="ml-2 text-gray-600">{message}</span>
)}
</div>
);
}
// Các preset thường dùng
export const LoadingPresets = {
News: () => <LoadingState message="Đang tải tin tức..." />,
Search: () => <LoadingState message="Đang tìm kiếm..." />,
Content: () => <LoadingState message="Đang tải nội dung..." />,
Data: () => <LoadingState message="Đang tải dữ liệu..." />,
Members: () => <LoadingState message="Đang tải thông tin hội viên..." />,
Events: () => <LoadingState message="Đang tải sự kiện..." />,
Small: (message?: string) => <LoadingState size="sm" message={message} />,
Large: (message?: string) => <LoadingState size="lg" message={message} />,
};
\ No newline at end of file
// Query response type
interface ResponseType<T = any> {
data: any
message: string | null
message_en: string | null
responseData: T
status: 'fail' | 'success'
statusCode: number
timeStamp: string
violations: Array<{
code: number
message: string
action: Array<{
location: string
msg: {
en: string
vi: string
}
path: string
value: string
}>
}> | null
}
// Infinite query statuses type
interface InfiniteQueryStatusesType {
isFetchingInitialPage: boolean
isFetchingNextPage: boolean
isEmptyData: boolean
isError?: boolean
}
export type { ResponseType, InfiniteQueryStatusesType }
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