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
e57f97aa
Commit
e57f97aa
authored
Nov 12, 2025
by
Văn Hoàng
Browse files
Options
Browse Files
Download
Plain Diff
[tag]0.1-vcci
parents
ea03e458
bae5e371
Pipeline
#43899
passed with stages
in 4 minutes and 30 seconds
Changes
6
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
163 additions
and
92 deletions
+163
-92
news.ts
src/api/types/news.ts
+1
-0
index.tsx
src/app/(main)/(home)/components/card-news/index.tsx
+1
-1
page.tsx
src/app/(main)/(home)/page.tsx
+76
-58
index.tsx
src/app/(main)/[...slug]/components/event-detail/index.tsx
+38
-0
index.tsx
src/app/(main)/[...slug]/components/news-detail/index.tsx
+24
-0
page.tsx
src/app/(main)/[...slug]/page.tsx
+23
-33
No files found.
src/api/types/news.ts
View file @
e57f97aa
...
...
@@ -17,6 +17,7 @@ export interface NewsItem {
name
:
string
static_link
:
string
static_link_en
:
string
code
:
string
}
}
...
...
src/app/(main)/(home)/components/card-news/index.tsx
View file @
e57f97aa
...
...
@@ -6,7 +6,7 @@ import AppEditorContent from "@/components/shared/editor-content";
function
CardNews
({
news
}:
{
news
:
NewsItem
})
{
return
(
<
a
href=
{
`${news.
page_config.static_link}/${news.id
}`
}
href=
{
`${news.
external_link
}`
}
className=
"flex flex-row gap-2 mb-2 sm:gap-3 sm:mb-3"
>
<
img
...
...
src/app/(main)/(home)/page.tsx
View file @
e57f97aa
...
...
@@ -33,11 +33,10 @@ const Page = () => {
const
swiperRef
=
useRef
<
SwiperType
|
null
>
(
null
);
// query
const
{
data
:
categoryData
,
isLoading
:
isLoadingCategory
}
=
useGetCategory
<
GetCategoryAdminResponseType
>
();
const
{
data
:
newsData
,
isLoading
:
isLoadingNews
}
=
useGetNews
<
GetNewsResponseType
>
(
{
pageSize
:
'5'
,
filters
:
tab
===
"all"
?
``
:
`
category
@=
${
tab
}
`
,
filters
:
tab
===
"all"
?
``
:
`
page_config.code
@=
${
tab
}
`
,
}
);
...
...
@@ -49,17 +48,25 @@ const Page = () => {
const
{
data
:
businessOpportunities
,
isLoading
:
isLoadingBusinessOpportunities
}
=
useGetNews
<
GetNewsResponseType
>
(
{
pageSize
:
'5'
,
filters
:
`
category @=Cơ hội kinh
doanh`
,
filters
:
`
page_config.code @=co-hoi-kinh-
doanh`
,
}
);
const
{
data
:
policyAndLegalInformation
,
isLoading
:
isLoadingPolicyAndLegalInformation
}
=
useGetNews
<
GetNewsResponseType
>
(
{
pageSize
:
'5'
,
filters
:
`
category @=Thông tin chính sách và pháp luậ
t`
,
filters
:
`
page_config.code @=phap-lua
t`
,
}
);
const
{
data
:
eventData
,
isLoading
:
isLoadingEvent
}
=
useGetEvents
<
EventApiResponse
>
();
const
now
=
new
Date
();
const
eventDataFiltered
=
eventData
?.
responseData
.
rows
.
filter
(
event
=>
{
const
eventTime
=
new
Date
(
event
.
start_time
);
return
eventTime
>
now
;
});
// helpers
const
stripImagesAndHtml
=
(
html
?:
string
)
=>
{
if
(
!
html
)
return
''
...
...
@@ -108,7 +115,7 @@ const Page = () => {
];
return
(
(
isLoadingBusinessOpportunities
||
isLoadingPolicyAndLegalInformation
||
isLoading
Category
||
isLoading
Event
)
?
(
(
isLoadingBusinessOpportunities
||
isLoadingPolicyAndLegalInformation
||
isLoadingEvent
)
?
(
<
div
className=
"container w-full h-[80vh] flex justify-center items-center"
>
<
Spinner
/>
</
div
>
...
...
@@ -170,7 +177,7 @@ const Page = () => {
{
newsAll
?.
responseData
?.
rows
.
map
((
news
)
=>
(
<
SwiperSlide
key=
{
news
.
id
}
>
<
a
href=
{
`${news.
page_config.static_link}/${news.id
}`
}
href=
{
`${news.
external_link
}`
}
className=
"relative block bg-white shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300"
>
<
img
...
...
@@ -225,7 +232,7 @@ const Page = () => {
.
map
((
news
:
NewsItem
)
=>
(
<
a
key=
{
news
.
id
}
href=
{
`${news.
page_config.static_link}/${news.id
}`
}
href=
{
`${news.
external_link
}`
}
className=
"flex flex-col w-full md:w-1/2 min-h-[180px] sm:min-h-[220px] gap-3 mb-3 bg-white"
>
<
div
className=
"w-full aspect-3/2 overflow-hidden"
>
...
...
@@ -263,20 +270,33 @@ const Page = () => {
>
Tất cả
</
button
>
{
categoryData
?.
responseData
.
rows
.
slice
(
0
,
3
)
.
map
((
category
)
=>
(
<
button
key=
{
category
.
id
}
className=
{
`flex-1 py-[3px] text-[14px] transition-colors cursor-pointer ${category.name === tab
? "bg-[#d3d3d3] text-[#063e8e] font-semibold"
: "border-gray-300 text-[#363636] bg-[#e8e8e8] hover:bg-[#e8e8e8] hover:text-[#063e8e] font-semibold"
}`
}
onClick=
{
()
=>
setTab
(
category
.
name
)
}
>
{
category
.
name
}
</
button
>
))
}
<
button
className=
{
`flex-1 py-[3px] text-[14px] transition-colors cursor-pointer ${`
tin
-
vcci
` === tab
? "bg-[#d3d3d3] text-[#063e8e] font-semibold"
: "border-gray-300 text-[#363636] bg-[#e8e8e8] hover:bg-[#e8e8e8] hover:text-[#063e8e] font-semibold"
}`
}
onClick=
{
()
=>
setTab
(
`tin-vcci`
)
}
>
Tin VCCI
</
button
>
<
button
className=
{
`flex-1 py-[3px] text-[14px] transition-colors cursor-pointer ${`
tin
-
kinh
-
te
` === tab
? "bg-[#d3d3d3] text-[#063e8e] font-semibold"
: "border-gray-300 text-[#363636] bg-[#e8e8e8] hover:bg-[#e8e8e8] hover:text-[#063e8e] font-semibold"
}`
}
onClick=
{
()
=>
setTab
(
`tin-kinh-te`
)
}
>
Tin Kinh Tế
</
button
>
<
button
className=
{
`flex-1 py-[3px] text-[14px] transition-colors cursor-pointer ${`
chuyen
-
de
` === tab
? "bg-[#d3d3d3] text-[#063e8e] font-semibold"
: "border-gray-300 text-[#363636] bg-[#e8e8e8] hover:bg-[#e8e8e8] hover:text-[#063e8e] font-semibold"
}`
}
onClick=
{
()
=>
setTab
(
`chuyen-de`
)
}
>
Chuyên Đề
</
button
>
</
div
>
{
newsData
?.
responseData
?.
rows
.
slice
(
0
,
4
).
map
((
news
)
=>
(
...
...
@@ -347,39 +367,37 @@ const Page = () => {
<
hr
className=
"border-[#e8c518] mb-4"
/>
<
div
className=
"flex flex-col md:flex-row gap-5"
>
{
eventData
?.
responseData
.
rows
.
slice
(
0
,
1
)
.
map
((
event
:
EventItem
)
=>
(
<
a
key=
{
event
.
id
}
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"
>
<
div
className=
"w-full aspect-3/2 overflow-hidden"
>
<
img
src=
{
`${BASE_URL.imageEndpoint}${event.image}`
}
alt=
{
event
.
name
}
className=
"w-full h-full object-cover"
onError=
{
(
e
)
=>
{
(
e
.
target
as
HTMLImageElement
).
src
=
"/img-error.png"
;
}
}
/>
</
div
>
{
eventDataFiltered
?.
slice
(
0
,
1
).
map
((
event
:
EventItem
)
=>
(
<
a
key=
{
event
.
id
}
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"
>
<
div
className=
"w-full aspect-3/2 overflow-hidden"
>
<
img
src=
{
`${BASE_URL.imageEndpoint}${event.image}`
}
alt=
{
event
.
name
}
className=
"w-full h-full object-cover"
onError=
{
(
e
)
=>
{
(
e
.
target
as
HTMLImageElement
).
src
=
"/img-error.png"
;
}
}
/>
</
div
>
<
div
className=
"flex-1"
>
<
p
className=
"text-[#0056b3] font-bold text-xl line-clamp-2"
>
{
event
.
name
}
</
p
>
<
p
className=
"text-gray-500 text-sm my-1"
>
{
dayjs
(
event
.
start_time
).
format
(
"DD/MM/YYYY"
)
}
</
p
>
<
p
className=
"line-clamp-3"
>
{
stripImagesAndHtml
(
event
.
description
)
}
</
p
>
</
div
>
</
a
>
))
}
<
div
className=
"flex-1"
>
<
p
className=
"text-[#0056b3] font-bold text-xl line-clamp-2"
>
{
event
.
name
}
</
p
>
<
p
className=
"text-gray-500 text-sm my-1"
>
{
dayjs
(
event
.
start_time
).
format
(
"DD/MM/YYYY"
)
}
</
p
>
<
p
className=
"line-clamp-3"
>
{
stripImagesAndHtml
(
event
.
description
)
}
</
p
>
</
div
>
</
a
>
))
}
<
div
className=
"w-full md:w-1/2"
>
{
eventData
?.
responseData
.
rows
.
slice
(
0
,
4
).
map
((
event
)
=>
(
{
eventData
Filtered
?
.
slice
(
0
,
4
).
map
((
event
)
=>
(
<
CardEvent
key=
{
event
.
id
}
event=
{
event
}
/>
))
}
</
div
>
...
...
@@ -416,13 +434,13 @@ const Page = () => {
<
div
className=
"flex-1"
>
<
div
className=
"flex justify-between items-center"
>
<
a
href=
"/xuc-tien-thuong-mai/co-hoi
-kinh-doanh
/"
href=
"/xuc-tien-thuong-mai/co-hoi/"
className=
"text-[18px] sm:text-[20px] font-bold uppercase text-[#063e8e]"
>
Cơ hội kinh doanh
</
a
>
<
a
href=
"/xuc-tien-thuong-mai/co-hoi
-kinh-doanh
/"
href=
"/xuc-tien-thuong-mai/co-hoi/"
className=
"text-[#063e8e] text-sm sm:text-base"
>
<
ChevronsRight
/>
...
...
@@ -433,7 +451,7 @@ const Page = () => {
{
businessOpportunities
?.
responseData
.
rows
.
slice
(
0
,
1
)
.
map
((
news
:
NewsItem
)
=>
(
<
a
key=
{
news
.
id
}
href=
{
`${news.
page_config.static_link}/${news.id
}`
}
>
<
a
key=
{
news
.
id
}
href=
{
`${news.
external_link
}`
}
>
<
div
className=
"w-full aspect-3/2 relative overflow-hidden mb-5"
>
<
img
src=
{
`${BASE_URL.imageEndpoint}${news.thumbnail}`
}
...
...
@@ -461,13 +479,13 @@ const Page = () => {
<
div
className=
"flex-1"
>
<
div
className=
"flex justify-between items-center"
>
<
a
href=
"/thong-tin-truyen-thong/
thong-tin-chinh-sach-va-
phap-luat"
href=
"/thong-tin-truyen-thong/phap-luat"
className=
"text-[18px] sm:text-[20px] font-bold uppercase text-[#063e8e]"
>
Chính sách
&
pháp luật
</
a
>
<
a
href=
"/thong-tin-truyen-thong/
thong-tin-chinh-sach-va-
phap-luat"
href=
"/thong-tin-truyen-thong/phap-luat"
className=
"text-[#063e8e] text-sm sm:text-base"
>
<
ChevronsRight
/>
...
...
@@ -478,7 +496,7 @@ const Page = () => {
{
policyAndLegalInformation
?.
responseData
.
rows
.
slice
(
0
,
1
)
.
map
((
news
:
NewsItem
)
=>
(
<
a
key=
{
news
.
id
}
href=
{
`${news.
page_config.static_link}/${news.id
}`
}
>
<
a
key=
{
news
.
id
}
href=
{
`${news.
external_link
}`
}
>
<
div
className=
"w-full aspect-3/2 relative overflow-hidden mb-5"
>
<
img
src=
{
`${BASE_URL.imageEndpoint}${news.thumbnail}`
}
...
...
src/app/(main)/[...slug]/components/event-detail/index.tsx
0 → 100644
View file @
e57f97aa
"use client"
;
import
parse
from
"html-react-parser"
;
import
dayjs
from
"dayjs"
;
import
{
Spinner
}
from
"@/components/ui"
;
import
{
useGetNewsId
}
from
"@/api/endpoints/news"
;
import
{
GetNewsDetailResponseType
}
from
"./../../page.type"
;
interface
EventDetailProps
{
id
?:
string
;
}
export
default
function
EventDetail
({
id
}:
EventDetailProps
)
{
if
(
!
id
)
return
null
;
const
{
data
:
eventDetail
,
isLoading
}
=
useGetNewsId
<
GetNewsDetailResponseType
>
(
id
);
if
(
isLoading
)
{
return
(
<
div
className=
"flex justify-center py-6"
>
<
Spinner
/>
</
div
>
);
}
const
event
=
eventDetail
?.
responseData
;
if
(
!
event
)
return
null
;
return
(
<
div
>
<
h1
className=
"text-2xl font-medium text-primary"
>
{
event
.
title
}
</
h1
>
<
div
className=
"text-sm text-blue-700 mb-4"
>
{
dayjs
(
event
.
created_at
).
format
(
"DD/MM/YYYY"
)
}
</
div
>
<
div
className=
"prose tiptap"
>
{
parse
(
event
.
description
??
""
)
}
</
div
>
</
div
>
);
}
src/app/(main)/[...slug]/components/news-detail/index.tsx
0 → 100644
View file @
e57f97aa
"use client"
;
import
parse
from
"html-react-parser"
;
import
dayjs
from
"dayjs"
;
import
{
GetNewsResponseType
}
from
"@/api/types/news"
;
interface
NewsDetailProps
{
data
:
GetNewsResponseType
;
}
export
default
function
NewsDetail
({
data
}:
NewsDetailProps
)
{
const
news
=
data
?.
responseData
?.
rows
?.[
0
];
if
(
!
news
)
return
null
;
return
(
<
div
>
<
h1
className=
"text-2xl font-medium text-primary"
>
{
news
.
title
}
</
h1
>
<
div
className=
"text-sm text-blue-700 mb-4"
>
{
dayjs
(
news
.
created_at
).
format
(
"DD/MM/YYYY"
)
}
</
div
>
<
div
className=
"prose tiptap"
>
{
parse
(
news
.
description
??
""
)
}
</
div
>
</
div
>
);
}
src/app/(main)/[...slug]/page.tsx
View file @
e57f97aa
...
...
@@ -8,17 +8,17 @@ import { ListFilter } from "@/components/base/list-filter";
import
EventCalendar
from
"@/components/base/event-calendar"
;
import
ListCategory
from
"@/components/base/list-category"
;
import
CardNews
from
"@/components/base/card-news"
;
import
Image
from
"next/image"
;
import
parse
from
"html-react-parser"
;
import
dayjs
from
"dayjs"
;
// API hooks
import
{
useGetNews
,
useGetNewsId
}
from
"@/api/endpoints/news"
;
import
{
useGetNews
}
from
"@/api/endpoints/news"
;
import
{
GetNewsResponseType
}
from
"@/api/types/news"
;
import
{
GetNewsDetailResponseType
}
from
"./page.type"
;
import
{
useGetNewsPageConfigGetHierarchical
}
from
"@/api/endpoints/news-page-config"
;
import
{
GetNewsPageConfigResponseType
}
from
"@/api/types/news-page-config"
;
// Component con
import
NewsDetail
from
"./components/news-detail"
;
import
EventDetail
from
"./components/event-detail"
;
export
default
function
DynamicPage
()
{
const
params
=
useParams
();
const
slugArray
=
Array
.
isArray
(
params
.
slug
)
?
params
.
slug
:
[
params
.
slug
];
...
...
@@ -30,19 +30,19 @@ export default function DynamicPage() {
const
[
page
,
setPage
]
=
useState
(
1
);
const
pageSize
=
5
;
// quer
y
// quer
ies
const
{
data
:
categoriesPage
}
=
useGetNewsPageConfigGetHierarchical
<
GetNewsPageConfigResponseType
>
({
code
:
`
${
slugArray
[
0
]}
`
,
code
:
slugArray
[
0
]
,
});
const
{
data
:
newsDetail
,
isLoading
:
isLoadingDetail
}
=
useGetNews
<
GetNewsResponseType
>
({
filters
:
`page_config.static_link==/
${
url
}
`
+
`
,external_link@=
${
lastPart
}
`
,
filters
:
`page_config.static_link==/
${
url
}
,external_link@=
${
lastPart
}
`
,
});
const
{
data
:
news
,
isLoading
}
=
useGetNews
<
GetNewsResponseType
>
({
pageSize
:
String
(
pageSize
),
currentPage
:
String
(
page
),
filters
:
`page_config.static_link==/
${
url
}
`
+
(
submitSearch
?
`,title@=
${
submitSearch
}
`
:
""
)
,
filters
:
`page_config.static_link==/
${
url
}
${
submitSearch
?
`,title@=
${
submitSearch
}
`
:
""
}
`
,
});
if
(
isLoadingDetail
)
{
...
...
@@ -53,29 +53,23 @@ export default function DynamicPage() {
);
}
// detail news page
// check UUID
const
isUUID
=
/^
[
0-9a-fA-F
]{8}
-
[
0-9a-fA-F
]{4}
-
[
0-9a-fA-F
]{4}
-
[
0-9a-fA-F
]{4}
-
[
0-9a-fA-F
]{12}
$/
.
test
(
lastPart
as
string
);
const
id
=
isUUID
?
lastPart
:
undefined
;
// detail page condition
const
isDetailPage
=
newsDetail
?.
responseData
?.
rows
?.
length
===
1
;
if
(
isDetailPage
)
{
if
(
isDetailPage
||
id
)
{
return
(
<
div
className=
'container w-full flex justify-center items-center pb-10'
>
<
div
className=
'flex flex-col gap-5 w-full'
>
<
div
className=
"container w-full flex justify-center items-center pb-10"
>
<
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 p-8"
>
<
div
className=
'pb-5 text-primary text-2xl leading-normal font-medium'
>
{
newsDetail
?.
responseData
?.
rows
[
0
].
title
}
</
div
>
<
div
className=
'flex items-center gap-2 text-sm mb-4'
>
<
span
className=
'text-base text-blue-700'
>
{
dayjs
(
newsDetail
?.
responseData
?.
rows
[
0
].
created_at
).
format
(
'DD/MM/YYYY'
)
}
</
span
>
</
div
>
<
hr
className=
"my-5"
/>
<
div
className=
'flex-1 text-app-grey text-base overflow-hidden'
>
<
div
className=
"prose tiptap overflow-hidden"
>
{
parse
(
newsDetail
?.
responseData
?.
rows
[
0
].
description
??
''
)
}
</
div
>
</
div
>
{
isDetailPage
?
<
NewsDetail
data=
{
newsDetail
}
/>
:
<
EventDetail
id=
{
id
}
/>
}
</
main
>
<
aside
className=
"space-y-6"
>
<
EventCalendar
/>
...
...
@@ -104,11 +98,7 @@ export default function DynamicPage() {
)
:
(
<>
{
news
?.
responseData
.
rows
.
map
((
item
)
=>
(
<
CardNews
key=
{
item
.
id
}
news=
{
item
}
link=
{
`${item.external_link}`
}
/>
<
CardNews
key=
{
item
.
id
}
news=
{
item
}
link=
{
`${item.external_link}`
}
/>
))
}
<
div
className=
"w-full flex justify-center mt-4"
>
<
Pagination
...
...
@@ -138,4 +128,4 @@ export default function DynamicPage() {
</
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