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