Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
V
VCCI-News
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Văn Hoàng
VCCI-News
Commits
a45cc1e3
Commit
a45cc1e3
authored
Dec 03, 2025
by
Phạm Quang Bảo
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
update/events page
parent
32cb6417
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
276 additions
and
8 deletions
+276
-8
index.tsx
src/app/(main)/(home)/components/events-calendar/index.tsx
+2
-2
index.tsx
.../(home)/components/events/components/card-event/index.tsx
+1
-2
index.tsx
src/app/(main)/(home)/components/events/index.tsx
+2
-2
index.tsx
src/app/(main)/(home)/components/news/index.tsx
+2
-2
page.tsx
src/app/(main)/[...slug]/page.tsx
+7
-0
EventDetailPage.tsx
src/app/(main)/[...slug]/templates/EventDetailPage.tsx
+163
-0
EventPage.tsx
src/app/(main)/[...slug]/templates/EventPage.tsx
+99
-0
No files found.
src/app/(main)/(home)/components/events-calendar/index.tsx
View file @
a45cc1e3
...
@@ -10,12 +10,12 @@ const EventsCalendar = () => {
...
@@ -10,12 +10,12 @@ const EventsCalendar = () => {
<
h2
className=
"text-[18px] sm:text-[20px] font-bold uppercase text-[#e8c518]"
>
<
h2
className=
"text-[18px] sm:text-[20px] font-bold uppercase text-[#e8c518]"
>
Lịch sự kiện
Lịch sự kiện
</
h2
>
</
h2
>
<
Link
{
/*
<Link
href="#"
href="#"
className="text-[#e8c518] hover:underline text-sm sm:text-base"
className="text-[#e8c518] hover:underline text-sm sm:text-base"
>
>
<ChevronsRight />
<ChevronsRight />
</
Link
>
</Link>
*/
}
</
div
>
</
div
>
<
hr
className=
"border-[#e8c518] mb-4"
/>
<
hr
className=
"border-[#e8c518] mb-4"
/>
<
EventCalendar
/>
<
EventCalendar
/>
...
...
src/app/(main)/(home)/components/events/components/card-event/index.tsx
View file @
a45cc1e3
...
@@ -8,8 +8,7 @@ import ImageNext from "@/components/shared/image-next";
...
@@ -8,8 +8,7 @@ import ImageNext from "@/components/shared/image-next";
function
CardEvent
({
event
}:
{
event
:
EventItem
})
{
function
CardEvent
({
event
}:
{
event
:
EventItem
})
{
return
(
return
(
<
Link
<
Link
// href={`hoat-dong/su-kien/${event.id}`}
href=
{
`/hoat-dong/su-kien/${event.id}`
}
href=
{
`#`
}
className=
'flex flex-row gap-2 mb-2 sm:gap-3 sm:mb-3 p-2 sm:p-3 border border-gray-200 bg-white rounded-md'
className=
'flex flex-row gap-2 mb-2 sm:gap-3 sm:mb-3 p-2 sm:p-3 border border-gray-200 bg-white rounded-md'
>
>
<
ImageNext
<
ImageNext
...
...
src/app/(main)/(home)/components/events/index.tsx
View file @
a45cc1e3
...
@@ -18,7 +18,7 @@ function Events() {
...
@@ -18,7 +18,7 @@ function Events() {
<
h2
className=
"text-[18px] sm:text-[20px] font-bold uppercase text-[#e8c518]"
>
<
h2
className=
"text-[18px] sm:text-[20px] font-bold uppercase text-[#e8c518]"
>
Sự kiện sắp diễn ra
Sự kiện sắp diễn ra
</
h2
>
</
h2
>
<
Link
href=
"
#
"
className=
"text-[#e8c518] text-sm sm:text-base"
>
<
Link
href=
"
/hoat-dong/su-kien
"
className=
"text-[#e8c518] text-sm sm:text-base"
>
<
ChevronsRight
/>
<
ChevronsRight
/>
</
Link
>
</
Link
>
</
div
>
</
div
>
...
@@ -34,7 +34,7 @@ function Events() {
...
@@ -34,7 +34,7 @@ function Events() {
{
data
?.
responseData
.
rows
.
slice
(
0
,
1
).
map
((
event
:
EventItem
)
=>
(
{
data
?.
responseData
.
rows
.
slice
(
0
,
1
).
map
((
event
:
EventItem
)
=>
(
<
Link
<
Link
key=
{
event
.
id
}
key=
{
event
.
id
}
href=
{
`
#
`
}
href=
{
`
/hoat-dong/su-kien/${event.id}
`
}
className=
"flex flex-col w-full md:w-1/2 min-h-[180px] sm:min-h-[220px] gap-3 mb-3 border border-gray-200 bg-white rounded-md p-3"
className=
"flex flex-col w-full md:w-1/2 min-h-[180px] sm:min-h-[220px] gap-3 mb-3 border border-gray-200 bg-white rounded-md p-3"
>
>
<
div
className=
"w-full aspect-3/2 overflow-hidden"
>
<
div
className=
"w-full aspect-3/2 overflow-hidden"
>
...
...
src/app/(main)/(home)/components/news/index.tsx
View file @
a45cc1e3
...
@@ -28,12 +28,12 @@ const News = () => {
...
@@ -28,12 +28,12 @@ const News = () => {
>
>
Tin tức
Tin tức
</
Link
>
</
Link
>
<
Link
{
/*
<Link
href="/thong-tin-truyen-thong/tin-vcci/"
href="/thong-tin-truyen-thong/tin-vcci/"
className="text-[#063e8e] text-sm sm:text-base"
className="text-[#063e8e] text-sm sm:text-base"
>
>
<ChevronsRight />
<ChevronsRight />
</
Link
>
</Link>
*/
}
</
div
>
</
div
>
<
hr
className=
"border-[#063e8e] mb-4"
/>
<
hr
className=
"border-[#063e8e] mb-4"
/>
...
...
src/app/(main)/[...slug]/page.tsx
View file @
a45cc1e3
...
@@ -11,6 +11,8 @@ import ArticlePage from "./templates/ArticlePage";
...
@@ -11,6 +11,8 @@ import ArticlePage from "./templates/ArticlePage";
import
{
Spinner
}
from
"@/components/ui"
;
import
{
Spinner
}
from
"@/components/ui"
;
import
{
useGetNews
}
from
"@/api/endpoints/news"
;
import
{
useGetNews
}
from
"@/api/endpoints/news"
;
import
ArticleDetailPage
from
"./templates/ArticleDetailPage"
;
import
ArticleDetailPage
from
"./templates/ArticleDetailPage"
;
import
EventPage
from
"./templates/EventPage"
;
import
EventDetailPage
from
"./templates/EventDetailPage"
;
export
default
function
DynamicPage
()
{
export
default
function
DynamicPage
()
{
const
params
=
useParams
();
const
params
=
useParams
();
...
@@ -38,6 +40,11 @@ export default function DynamicPage() {
...
@@ -38,6 +40,11 @@ export default function DynamicPage() {
// }, [slug, category, children, router]);
// }, [slug, category, children, router]);
//template
//template
if
(
slug
[
0
]
===
"hoat-dong"
&&
slug
[
1
]
===
"su-kien"
)
{
if
(
slug
.
length
===
2
)
return
<
EventPage
/>;
if
(
slug
.
length
===
3
)
return
<
EventDetailPage
/>;
}
if
(
news
?.
responseData
?.
count
==
0
&&
categoryLoading
)
{
if
(
news
?.
responseData
?.
count
==
0
&&
categoryLoading
)
{
return
(
return
(
<
div
className=
"flex justify-center items-center w-full h-64"
>
<
div
className=
"flex justify-center items-center w-full h-64"
>
...
...
src/app/(main)/[...slug]/templates/EventDetailPage.tsx
0 → 100644
View file @
a45cc1e3
'use client'
;
import
{
useState
}
from
"react"
;
import
Image
from
"next/image"
;
import
{
notFound
,
useParams
}
from
"next/navigation"
;
import
dayjs
from
"dayjs"
;
import
parse
from
"html-react-parser"
;
import
BASE_URL
from
"@/links"
;
import
{
useGetEvents
}
from
"@/api/endpoints/event"
;
import
{
EventApiResponse
}
from
"@/api/types/event"
;
import
{
GetNewsPageConfigResponseType
}
from
"@/api/types/news-page-config"
;
import
{
useGetNewsPageConfigGetHierarchical
}
from
"@/api/endpoints/news-page-config"
;
import
{
Spinner
}
from
"@/components/ui/spinner"
;
import
ListCategory
from
"@/components/base/list-category"
;
import
EventCalendar
from
"@/components/base/event-calendar"
;
import
{
CreditCard
,
MapPin
,
Clock
}
from
"lucide-react"
;
export
default
function
EventDetailPage
()
{
// get url
const
params
=
useParams
();
const
slug
=
Array
.
isArray
(
params
.
slug
)
?
params
.
slug
:
[
params
.
slug
];
const
lastpath
=
slug
[
slug
.
length
-
1
];
// query
const
{
data
:
categoriesPage
}
=
useGetNewsPageConfigGetHierarchical
<
GetNewsPageConfigResponseType
>
({
code
:
`
${
slug
[
0
]}
`
,
});
const
{
data
:
eventsDetail
,
isLoading
}
=
useGetEvents
<
EventApiResponse
>
({
filters
:
`id==
${
lastpath
}
`
,
});
// template
if
(
!
isLoading
&&
(
!
eventsDetail
?.
responseData
?.
rows
||
eventsDetail
.
responseData
.
rows
.
length
===
0
))
{
return
notFound
();
}
return
(
<
div
className=
'container w-full flex justify-center items-center pb-10'
>
{
isLoading
?
(
<
div
className=
"flex justify-center items-center w-full h-64"
>
<
Spinner
/>
</
div
>
)
:
(
<
div
className=
'flex flex-col gap-5 w-full'
>
<
ListCategory
categories=
{
categoriesPage
?.
responseData
?.
children
}
/>
<
div
className=
"grid grid-cols-1 lg:grid-cols-3 gap-5"
>
<
main
className=
"lg:col-span-2 bg-white border rounded-md py-10 px-5 md:px-20"
>
<
div
className=
'pb-5 text-primary text-2xl leading-normal font-medium'
>
{
eventsDetail
?.
responseData
?.
rows
[
0
]?.
name
}
</
div
>
<
hr
className=
"py-2"
/>
{
/* Top summary with image + details */
}
<
div
className=
"flex flex-col md:flex-row gap-6 my-6"
>
<
div
className=
"w-full lg:w-1/2 bg-gray-50 rounded-md overflow-hidden"
>
{
eventsDetail
?.
responseData
?.
rows
[
0
].
image
?
(
<
div
className=
"w-full h-52 relative "
>
<
EventImage
src=
{
`${BASE_URL.imageEndpoint}${eventsDetail?.responseData?.rows[0].image}`
}
alt=
{
eventsDetail
?.
responseData
?.
rows
[
0
]?.
name
||
"image"
}
/>
</
div
>
)
:
(
<
div
className=
"w-full h-52 bg-gray-200"
/>
)
}
</
div
>
<
div
className=
"w-full lg:w-1/2 bg-white border rounded-md p-3 md:p-6"
>
<
div
className=
"flex flex-col gap-3"
>
<
div
className=
"text-sm text-gray-500 flex flex-row items-center gap-2"
>
<
Clock
className=
"h-5 w-5 text-yellow-500"
/>
<
div
>
<
div
className=
"text-sm font-medium text-gray-800"
>
Bắt đầu:
{
eventsDetail
?.
responseData
?.
rows
[
0
].
start_time
?
dayjs
(
eventsDetail
.
responseData
.
rows
[
0
].
start_time
).
format
(
'HH:mm DD/MM/YYYY'
)
:
"-"
}
</
div
>
<
div
className=
"text-sm font-medium text-gray-800"
>
Kết thúc:
{
eventsDetail
?.
responseData
?.
rows
[
0
].
end_time
?
dayjs
(
eventsDetail
.
responseData
.
rows
[
0
].
end_time
).
format
(
'HH:mm DD/MM/YYYY'
)
:
"-"
}
</
div
>
</
div
>
</
div
>
<
div
className=
"text-sm text-gray-500 flex items-center gap-2"
>
<
MapPin
className=
"h-5 w-5 text-blue-600"
/>
<
div
className=
"text-sm font-medium text-gray-800"
>
Địa điểm:
{
eventsDetail
?.
responseData
?.
rows
[
0
]?.
location
??
eventsDetail
?.
responseData
?.
rows
[
0
]?.
province
??
"-"
}
</
div
>
</
div
>
<
div
className=
"text-sm text-gray-500 flex items-center gap-2"
>
<
CreditCard
className=
"h-5 w-5 text-yellow-400"
/>
<
div
className=
"text-sm font-medium text-gray-800"
>
Phí tham dự:
{
eventsDetail
?.
responseData
?.
rows
[
0
]?.
table_cost
?
`${eventsDetail?.responseData?.rows[0]?.table_count
} Bàn : ${eventsDetail?.responseData?.rows[0]?.table_cost.toLocaleString()} đ`
:
"Vui lòng xem chi tiết trong bài"
}
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
{
/* Full description */
}
<
div
className=
"prose tiptap overflow-hidden"
>
{
parse
(
eventsDetail
?.
responseData
?.
rows
[
0
]?.
description
??
""
)
}
</
div
>
</
main
>
{
/* Sidebar */
}
<
aside
className=
"space-y-6"
>
<
EventCalendar
/>
<
div
className=
"bg-white border rounded-md overflow-hidden"
>
<
div
className=
"w-full h-75 relative bg-gray-100"
>
<
Image
src=
"/banner.webp"
alt=
"Quảng cáo"
fill
className=
"object-contain"
/>
</
div
>
</
div
>
</
aside
>
</
div
>
</
div
>
)
}
</
div
>
);
}
// Local small component to safely handle Image src fallback without mutating DOM
type
EventImageProps
=
{
src
:
string
;
alt
?:
string
;
};
function
EventImage
({
src
,
alt
}:
EventImageProps
)
{
const
[
imgSrc
,
setImgSrc
]
=
useState
<
string
>
(
src
);
return
(
<
Image
src=
{
imgSrc
}
alt=
{
alt
??
"image"
}
fill
className=
"object-cover"
onError=
{
()
=>
{
// swap to local fallback file when Next/Image fails to load the provided URL
if
(
imgSrc
!==
"/img-error.png"
)
setImgSrc
(
"/img-error.png"
);
}
}
/>
);
}
\ No newline at end of file
src/app/(main)/[...slug]/templates/EventPage.tsx
0 → 100644
View file @
a45cc1e3
'use client'
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
notFound
,
useParams
,
usePathname
,
useRouter
,
useSearchParams
}
from
"next/navigation"
;
import
{
useGetEvents
}
from
"@/api/endpoints/event"
;
import
{
EventApiResponse
}
from
"@/api/types/event"
;
import
{
GetNewsPageConfigResponseType
}
from
"@/api/types/news-page-config"
;
import
{
useGetNewsPageConfigGetHierarchical
}
from
"@/api/endpoints/news-page-config"
;
import
{
Spinner
}
from
"@/components/ui/spinner"
;
import
ListCategory
from
"@/components/base/list-category"
;
import
CardEvents
from
"@/components/base/card-events"
;
import
{
Pagination
}
from
"@/components/base/pagination"
;
import
ListFilter
from
"@/components/base/list-filter"
;
import
EventCalendar
from
"@/components/base/event-calendar"
;
export
default
function
EventPage
()
{
// get url
const
params
=
useParams
();
const
slug
=
Array
.
isArray
(
params
.
slug
)
?
params
.
slug
:
[
params
.
slug
];
const
searchParams
=
useSearchParams
();
const
router
=
useRouter
();
const
pathname
=
usePathname
();
// states
const
initialPage
=
Number
(
searchParams
.
get
(
"page"
)
??
"1"
);
const
[
submitSearch
,
setSubmitSearch
]
=
useState
(
""
);
const
[
page
,
setPage
]
=
useState
(
initialPage
);
const
pageSize
=
5
;
useEffect
(()
=>
{
const
params
=
new
URLSearchParams
(
searchParams
.
toString
());
if
(
page
>
1
)
{
params
.
set
(
"page"
,
String
(
page
));
}
else
{
params
.
delete
(
"page"
);
}
const
qs
=
params
.
toString
();
router
.
replace
(
qs
?
`
${
pathname
}
?
${
qs
}
`
:
pathname
,
{
scroll
:
false
});
},
[
page
]);
// query
const
{
data
:
categoriesPage
}
=
useGetNewsPageConfigGetHierarchical
<
GetNewsPageConfigResponseType
>
({
code
:
`
${
slug
[
0
]}
`
,
});
const
{
data
:
events
,
isLoading
:
eventsLoading
}
=
useGetEvents
<
EventApiResponse
>
({
filters
:
`name@=
${
submitSearch
?
`title@=
${
submitSearch
}
`
:
""
}
`
,
pageSize
:
String
(
pageSize
),
currentPage
:
String
(
page
),
});
//template
return
(
<>
<
div
className=
"min-h-screen container mx-auto"
>
{
eventsLoading
?
(
<
div
className=
"flex justify-center items-center w-full h-64"
>
<
Spinner
/>
</
div
>
)
:
(
<
div
className=
"w-full flex flex-col gap-5"
>
<
ListCategory
categories=
{
categoriesPage
?.
responseData
?.
children
}
/>
<
div
className=
"grid grid-cols-1 lg:grid-cols-3 gap-6"
>
<
main
className=
"lg:col-span-2 bg-background"
>
<
div
className=
"pb-5 overflow-hidden"
>
{
events
?.
responseData
?.
rows
?.
map
((
item
)
=>
(
<
CardEvents
key=
{
item
.
id
}
event=
{
item
}
link=
{
`su-kien/${item.id}`
}
/>
))
}
<
div
className=
"w-full flex justify-center mt-4"
>
<
Pagination
pageCount=
{
Number
(
events
?.
responseData
?.
totalPages
??
1
)
}
page=
{
Number
(
events
?.
responseData
?.
currentPage
??
page
)
}
onChangePage=
{
setPage
}
onGoToPreviousPage=
{
()
=>
setPage
(
Math
.
max
(
1
,
page
-
1
))
}
onGoToNextPage=
{
()
=>
setPage
(
Math
.
min
(
Number
(
events
?.
responseData
?.
totalPages
??
1
),
page
+
1
))
}
/>
</
div
>
</
div
>
</
main
>
<
aside
className=
"space-y-6"
>
<
ListFilter
onSearch=
{
setSubmitSearch
}
/>
<
EventCalendar
/>
</
aside
>
</
div
>
</
div
>
)
}
</
div
>
</>
);
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment