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
2ce89c19
Commit
2ce89c19
authored
Nov 05, 2025
by
Phạm Quang Bảo
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'tumlumtumla' into feat/home_page
parents
33fcbbfb
6c996526
Changes
53
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
53 changed files
with
1671 additions
and
198 deletions
+1671
-198
orval.config.ts
orval.config.ts
+2
-1
fallback.png
public/fallback.png
+0
-0
news-page-config.ts
src/api/endpoints/news-page-config.ts
+930
-0
index.ts
src/api/models/index.ts
+1
-0
putNewsPageConfigCategoryIdBody.ts
src/api/models/putNewsPageConfigCategoryIdBody.ts
+11
-0
page.tsx
src/app/(main)/(home)/[id]/page.tsx
+1
-1
index.tsx
src/app/(main)/(home)/components/card-event/index.tsx
+4
-0
index.tsx
src/app/(main)/(home)/components/card-news/index.tsx
+6
-1
page.tsx
src/app/(main)/(home)/page.tsx
+11
-2
MenuItem.tsx
src/app/(main)/_lib/layout/MenuItem.tsx
+32
-19
footer.tsx
src/app/(main)/_lib/layout/footer.tsx
+2
-2
header.tsx
src/app/(main)/_lib/layout/header.tsx
+93
-37
page.tsx
src/app/(main)/dai-dien-gioi-chu/chu-de/[id]/page.tsx
+4
-0
page.tsx
src/app/(main)/dai-dien-gioi-chu/chu-de/page.tsx
+2
-1
index.tsx
...p/(main)/dai-dien-gioi-chu/components/card-news/index.tsx
+19
-3
sampleHtml.ts
src/app/(main)/dai-dien-gioi-chu/lib/sampleHtml.ts
+1
-1
page.tsx
...app/(main)/dai-dien-gioi-chu/tap-huan-nsdld/[id]/page.tsx
+4
-0
page.tsx
src/app/(main)/dai-dien-gioi-chu/tin-lien-quan/[id]/page.tsx
+15
-7
page.tsx
src/app/(main)/dai-dien-gioi-chu/tin-lien-quan/page.tsx
+4
-3
page.tsx
src/app/(main)/hoat-dong/dao-tao/[id]/page.tsx
+4
-0
page.tsx
src/app/(main)/hoat-dong/dao-tao/page.tsx
+4
-4
page.tsx
src/app/(main)/hoat-dong/page.tsx
+1
-1
page.tsx
src/app/(main)/hoat-dong/su-kien/[id]/page.tsx
+4
-0
page.tsx
src/app/(main)/hoat-dong/su-kien/page.tsx
+4
-4
page.tsx
src/app/(main)/hoi-vien/ket-noi-hoi-vien/page.tsx
+19
-19
page.tsx
src/app/(main)/hoi-vien/tin-hoi-vien/page.tsx
+1
-1
page.tsx
src/app/(main)/search/page.tsx
+102
-0
mock-data.ts
src/app/(main)/site-map/_lib/mock-data.ts
+94
-0
page.tsx
src/app/(main)/site-map/page.tsx
+103
-0
page.tsx
src/app/(main)/thong-tin-truyen-thong/an-pham/page.tsx
+3
-3
page.tsx
...app/(main)/thong-tin-truyen-thong/chuyen-de/[id]/page.tsx
+4
-0
page.tsx
src/app/(main)/thong-tin-truyen-thong/chuyen-de/page.tsx
+13
-10
page.tsx
src/app/(main)/thong-tin-truyen-thong/page.tsx
+1
-1
page.tsx
...yen-thong/thong-tin-chinh-sach-va-phap-luat/[id]/page.tsx
+4
-0
page.tsx
...n-truyen-thong/thong-tin-chinh-sach-va-phap-luat/page.tsx
+13
-10
page.tsx
...n)/thong-tin-truyen-thong/thu-vien-tai-lieu/[id]/page.tsx
+4
-0
page.tsx
.../(main)/thong-tin-truyen-thong/thu-vien-tai-lieu/page.tsx
+13
-10
page.tsx
...in)/thong-tin-truyen-thong/tin-doanh-nghiep/[id]/page.tsx
+4
-0
page.tsx
...p/(main)/thong-tin-truyen-thong/tin-doanh-nghiep/page.tsx
+13
-10
page.tsx
...p/(main)/thong-tin-truyen-thong/tin-kinh-te/[id]/page.tsx
+4
-0
page.tsx
src/app/(main)/thong-tin-truyen-thong/tin-kinh-te/page.tsx
+13
-10
page.tsx
src/app/(main)/thong-tin-truyen-thong/tin-vcci/[id]/page.tsx
+4
-0
page.tsx
src/app/(main)/thong-tin-truyen-thong/tin-vcci/page.tsx
+16
-12
page.tsx
src/app/(main)/xuat-xu-hang-hoa/page.tsx
+1
-1
page.tsx
src/app/(main)/xuc-tien-thuong-mai/[id]/page.tsx
+4
-0
page.tsx
...main)/xuc-tien-thuong-mai/co-hoi-kinh-doanh/[id]/page.tsx
+4
-0
page.tsx
...app/(main)/xuc-tien-thuong-mai/co-hoi-kinh-doanh/page.tsx
+2
-1
page.tsx
...main)/xuc-tien-thuong-mai/ho-tro-kinh-doanh/[id]/page.tsx
+4
-0
page.tsx
...app/(main)/xuc-tien-thuong-mai/ho-tro-kinh-doanh/page.tsx
+2
-1
page.tsx
...)/xuc-tien-thuong-mai/moi-truong-kinh-doanh/[id]/page.tsx
+4
-0
page.tsx
...(main)/xuc-tien-thuong-mai/moi-truong-kinh-doanh/page.tsx
+2
-1
index.tsx
src/components/base/card-news/index.tsx
+24
-7
AppEditorContent.tsx
src/components/shared/editor-content/AppEditorContent.tsx
+37
-14
No files found.
orval.config.ts
View file @
2ce89c19
...
...
@@ -79,7 +79,8 @@ const orvalConfig = async () => {
'UserHistory'
,
'Approvals'
,
'News'
,
'Category'
'Category'
,
'NewsPageConfig'
,
]
}
}
...
...
public/fallback.png
0 → 100644
View file @
2ce89c19
498 Bytes
src/api/endpoints/news-page-config.ts
0 → 100644
View file @
2ce89c19
This diff is collapsed.
Click to expand it.
src/api/models/index.ts
View file @
2ce89c19
...
...
@@ -140,6 +140,7 @@ export * from './putEventsLinkParams';
export
*
from
'./putEventsParams'
;
export
*
from
'./putFooterParams'
;
export
*
from
'./putMembershipFeeParams'
;
export
*
from
'./putNewsPageConfigCategoryIdBody'
;
export
*
from
'./putNotificationsMarkAsReadParams'
;
export
*
from
'./putNotificationsParams'
;
export
*
from
'./putOrderPaymentParams'
;
...
...
src/api/models/putNewsPageConfigCategoryIdBody.ts
0 → 100644
View file @
2ce89c19
/**
* 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
[];
};
src/app/(main)/(home)/[id]/page.tsx
View file @
2ce89c19
...
...
@@ -41,7 +41,7 @@ const NewsDetailPage = () => {
<
div
className=
"grid grid-cols-1 lg:grid-cols-3 gap-5"
>
{
/* Main content */
}
<
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
}
</
div
>
<
div
className=
'flex items-center gap-2 text-sm mb-4'
>
...
...
src/app/(main)/(home)/components/card-event/index.tsx
View file @
2ce89c19
...
...
@@ -13,6 +13,10 @@ function CardEvent({ event }: { event: EventItem }) {
src=
{
`${BASE_URL.imageEndpoint}${event.image}`
}
alt=
{
event
.
name
}
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'
>
<
p
className=
'text-[#0056b3] font-bold text-sm line-clamp-2'
>
...
...
src/app/(main)/(home)/components/card-news/index.tsx
View file @
2ce89c19
...
...
@@ -12,8 +12,13 @@ function CardNews({ news }: { news: NewsAdminItem }) {
<
img
src=
{
`${BASE_URL.imageEndpoint}${news.thumbnail}`
}
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'
>
<
p
className=
'text-[#0056b3] font-bold text-sm line-clamp-2'
>
{
news
.
title
}
...
...
src/app/(main)/(home)/page.tsx
View file @
2ce89c19
...
...
@@ -30,7 +30,9 @@ const Page = () => {
const
swiperRef
=
useRef
<
SwiperType
|
null
>
(
null
)
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
>
()
// filter category
...
...
@@ -176,6 +178,10 @@ const Page = () => {
src=
{
`${BASE_URL.imageEndpoint}${news.thumbnail}`
}
alt=
{
news
.
title
}
className=
"w-full h-full object-cover"
onError=
{
(
e
)
=>
{
e
.
currentTarget
.
onerror
=
null
e
.
currentTarget
.
src
=
"/fallback.png"
}
}
/>
</
div
>
...
...
@@ -279,7 +285,10 @@ const Page = () => {
src=
{
`${BASE_URL.imageEndpoint}${event.image}`
}
alt=
{
event
.
name
}
className=
"w-full h-full object-cover"
/>
onError=
{
(
e
)
=>
{
e
.
currentTarget
.
onerror
=
null
e
.
currentTarget
.
src
=
"/fallback.png"
}
}
/>
</
div
>
<
div
className=
"flex-1"
>
...
...
src/app/(main)/_lib/layout/MenuItem.tsx
View file @
2ce89c19
const
MenuItem
=
({
title
,
items
,
link
}:
{
title
:
string
;
items
:
string
[];
link
?:
string
})
=>
(
<
div
className=
"group relative"
>
<
a
className=
"px-3 py-5 text-[16px] font-[600] text-[#124588] hover:text-[#E8C518] transition block"
href=
{
`/${link}`
}
>
{
title
}
</
a
>
<
div
className=
"absolute left-0 top-full hidden group-hover:block bg-[#124588]/98 text-white text-[14px] font-[500] min-w-[220px] shadow-lg"
>
{
items
.
map
((
item
,
i
)
=>
(
<
div
key=
{
i
}
className=
"px-5 py-3 hover:bg-[#e8c518]/80 cursor-pointer whitespace-nowrap"
>
{
item
}
</
div
>
))
}
import
Link
from
"next/link"
;
type
MenuItemProps
=
{
title
:
string
;
link
?:
string
;
items
:
{
title
:
string
;
link
:
string
}[];
};
const
MenuItem
=
({
title
,
link
,
items
}:
MenuItemProps
)
=>
{
return
(
<
div
className=
"group relative"
>
<
Link
href=
{
`/${link || ""}`
}
className=
"px-3 py-5 text-[16px] font-[600] text-[#124588] hover:text-[#E8C518] transition block"
>
{
title
}
</
Link
>
{
/* Dropdown */
}
<
div
className=
"absolute left-0 top-full hidden group-hover:block bg-[#124588]/98 text-white text-[14px] font-[500] min-w-[220px] shadow-lg"
>
{
items
.
map
((
item
,
i
)
=>
(
<
Link
key=
{
i
}
href=
{
`/${link}/${item.link}`
}
className=
"block px-5 py-3 hover:bg-[#e8c518]/80 cursor-pointer whitespace-nowrap transition"
>
{
item
.
title
}
</
Link
>
))
}
</
div
>
</
div
>
</
div
>
)
;
);
}
;
export
default
MenuItem
;
src/app/(main)/_lib/layout/footer.tsx
View file @
2ce89c19
...
...
@@ -14,7 +14,7 @@ import {
import
Image
from
"next/image"
;
import
vietnamMap
from
"@/assets/vietnam-map-white.png.webp"
;
function
f
ooter
()
{
function
F
ooter
()
{
const
emailRef
=
useRef
<
HTMLInputElement
>
(
null
);
const
checkBoxRef
=
useRef
<
HTMLInputElement
>
(
null
);
const
[
emailError
,
setEmailError
]
=
useState
(
false
);
...
...
@@ -212,4 +212,4 @@ function footer() {
);
}
export
default
f
ooter
;
export
default
F
ooter
;
src/app/(main)/_lib/layout/header.tsx
View file @
2ce89c19
"use client"
;
import
React
,
{
useState
}
from
"react"
;
import
{
useRouter
}
from
'next/navigation'
import
{
Menu
,
X
,
Facebook
,
Linkedin
,
Twitter
,
Youtube
}
from
"lucide-react"
;
import
logo
from
"@/assets/VCCI-HCM-logo-VN-2025.png"
;
import
Image
from
"next/image"
;
import
MenuItem
from
"./MenuItem"
;
import
Link
from
"next/link"
;
function
Header
()
{
const
[
toggleMenu
,
setToggleMenu
]
=
useState
<
boolean
>
(
false
);
const
router
=
useRouter
()
return
(
<>
...
...
@@ -25,7 +27,7 @@ function Header() {
className=
"px-3 py-2 text-[14px] text-white hover:opacity-80"
href=
"#"
>
s
itemap
S
itemap
</
a
>
<
a
className=
"px-3 py-2 text-[14px] text-white hover:opacity-80"
...
...
@@ -40,6 +42,13 @@ function Header() {
className=
"bg-white h-12 rounded-sm outline-none px-4 w-64 placeholder:text-sm"
type=
"text"
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"
>
{
[
Facebook
,
Twitter
,
Youtube
,
Linkedin
].
map
((
Icon
,
i
)
=>
(
...
...
@@ -56,17 +65,17 @@ function Header() {
</
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=
"w-full flex justify-between items-center"
>
{
/* Logo */
}
<
a
href=
"/"
className=
"flex items-center
"
>
<
Link
href=
"/
"
>
<
Image
className=
"w-[140px] object-contain
lg:ml-0 ml-10
"
className=
"w-[140px] object-contain"
src=
{
logo
}
alt=
"VCCI HCM"
/>
</
a
>
</
Link
>
{
/* Desktop Menu */
}
<
nav
className=
"hidden lg:flex items-center"
>
...
...
@@ -75,35 +84,74 @@ function Header() {
title=
"Giới thiệu"
link=
"gioi-thieu"
items=
{
[
"Về VCCI-HCM"
,
"Chức Năng Và Nhiệm Vụ"
,
"Sơ Đồ Tổ Chức"
,
"Dịch Vụ Cung Cấp"
,
{
title
:
"Về VCCI-HCM"
,
link
:
"ve-vcci-hcm"
},
{
title
:
"Chức Năng Và Nhiệm Vụ"
,
link
:
"chuc-nang-va-nhiem-vu"
,
},
{
title
:
"Sơ Đồ Tổ Chức"
,
link
:
"so-do-to-chuc"
},
{
title
:
"Dịch Vụ Cung Cấp"
,
link
:
"dich-vu-cung-cap"
},
]
}
/>
<
MenuItem
title=
"Hội viên"
link=
"hoi-vien"
items=
{
[
"Lợi Ích Của Hội Viên VCCI"
,
"Đăng Ký Hội Viên"
,
"Kết Nối Hội Viên"
,
"Tin Hội Viên"
,
items=
{
[
{
title
:
"Lợi Ích Của Hội Viên VCCI"
,
link
:
""
,
},
{
title
:
"Đăng Ký Hội Viên"
,
link
:
"dang-ky-hoi-vien"
},
{
title
:
"Kết Nối Hội Viên"
,
link
:
"ket-noi-hoi-vien"
},
{
title
:
"Tin Hội Viên"
,
link
:
"tin-hoi-vien"
},
]
}
/>
<
MenuItem
title=
"Hoạt động"
items=
{
[
"Sự Kiện"
,
"Đào Tạo"
]
}
/>
<
MenuItem
title=
"Hoạt động"
link=
"hoat-dong"
items=
{
[
{
title
:
"Sự Kiện"
,
link
:
""
},
{
title
:
"Đào Tạo"
,
link
:
"dao-tao"
},
]
}
/>
<
MenuItem
title=
"Xuất Xứ Hàng Hóa"
link=
"xuat-xu-hang-hoa"
items=
{
[
"Định Nghĩa Chung"
,
"Mục Đích Của C/O"
,
"Luật Áp Dụng Về C/O"
,
"Thủ Tục Cấp C/O"
,
"Biểu Mẫu C/O Và Cách Khai"
,
"Phí Và Lệ Phí Cấp C/O"
,
"Điểm Cấp Và Thời Gian Cấp C/O"
,
"Thông Tin Liên Hệ"
,
{
title
:
"Định Nghĩa Chung"
,
link
:
""
,
},
{
title
:
"Mục Đích Của C/O"
,
link
:
"muc-dich"
,
},
{
title
:
"Luật Áp Dụng Về C/O"
,
link
:
"luat-ap-dung"
,
},
{
title
:
"Thủ Tục Cấp C/O"
,
link
:
"thu-tuc-cap"
,
},
{
title
:
"Biểu Mẫu C/O Và Cách Khai"
,
link
:
"bieu-mau-c-o-va-cach-khai"
,
},
{
title
:
"Phí Và Lệ Phí Cấp C/O"
,
link
:
"phi-va-le-phi-cap"
,
},
{
title
:
"Điểm Cấp Và Thời Gian Cấp C/O"
,
link
:
"diem-cap-va-thoi-gian-cap"
,
},
{
title
:
"Thông Tin Liên Hệ"
,
link
:
"thong-tin-lien-he"
,
},
]
}
/>
...
...
@@ -150,25 +198,33 @@ function Header() {
<
MenuItem
title=
"Xúc tiến thương mại"
link=
"xuc-tien-thuong-mai"
items=
{
[
"Hồ Sơ Thị Trường"
,
"Môi Trường Kinh Doanh"
,
"Cơ Hội Kinh Doanh"
,
"Hỗ Trợ Kinh Doanh"
,
items=
{
[
{
title
:
"Hồ Sơ Thị Trường"
,
link
:
"ho-so-thi-truong"
},
{
title
:
"Môi Trường Kinh Doanh"
,
link
:
"moi-truong-kinh-doanh"
,
},
{
title
:
"Cơ Hội Kinh Doanh"
,
link
:
"co-hoi-kinh-doanh"
},
{
title
:
"Hỗ Trợ Kinh Doanh"
,
link
:
"ho-tro-kinh-doanh"
},
]
}
/>
<
MenuItem
title=
"Thông tin truyền thông"
link=
"thong-tin-truyen-thong"
items=
{
[
"Tin VCCI"
,
"Tin Kinh Tế"
,
"Tin Doanh Nghiệp"
,
"Chuyên Đề"
,
"Thông Tin Chính Sách Và Pháp Luật"
,
"Ấn Phẩm"
,
"Thư Viện Tài Liệu"
,
items=
{
[
{
title
:
"Tin VCCI"
,
link
:
"tin-vcci"
},
{
title
:
"Tin Kinh Tế"
,
link
:
"tin-kinh-te"
},
{
title
:
"Tin Doanh Nghiệp"
,
link
:
"tin-doanh-nghiep"
},
{
title
:
"Chuyên Đề"
,
link
:
"chuyen-de"
},
{
title
:
"Thông Tin Chính Sách Và Pháp Luật"
,
link
:
"thong-tin-chinh-sach-va-phap-luat"
,
},
{
title
:
"Ấn Phẩm"
,
link
:
"an-pham"
},
{
title
:
"Thư Viện Tài Liệu"
,
link
:
"thu-vien-tai-lieu"
},
]
}
/>
</
nav
>
...
...
src/app/(main)/dai-dien-gioi-chu/chu-de/[id]/page.tsx
View file @
2ce89c19
...
...
@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<
div
className=
"grid grid-cols-1 lg:grid-cols-3 gap-6"
>
{
/* Main content */
}
<
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
>
</
main
>
...
...
src/app/(main)/dai-dien-gioi-chu/chu-de/page.tsx
View file @
2ce89c19
...
...
@@ -17,7 +17,8 @@ export default function Page() {
const
{
data
:
allData
}
=
useGetNews
<
GetNewsResponseType
>
({
pageSize
:
String
(
pageSize
),
currentPage
:
String
(
page
),
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
undefined
,
// filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters
:
'category@=Chủ đề'
});
return
(
<
div
className=
"min-h-screen container mx-auto p-4"
>
...
...
src/app/(main)/dai-dien-gioi-chu/components/card-news/index.tsx
View file @
2ce89c19
...
...
@@ -2,7 +2,23 @@
import
{
NewsItem
}
from
'@app/dai-dien-gioi-chu/lib/types/NewsPage.type'
;
import
Links
from
'@links/index'
import
dayjs
from
'dayjs'
;
import
parse
from
'html-react-parser'
// 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
(
...
...
@@ -21,14 +37,14 @@ function NewsContent({ news ,link}: { news: NewsItem ,link:string}) {
/>
<
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
}
</
p
>
<
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 prose tiptap"
>
{
parse
(
news
.
description
)
}
</
div
>
<
div
className=
"text-sm prose tiptap"
>
{
stripImagesAndHtml
(
news
.
description
)
}
</
div
>
</
div
>
</
div
>
</
a
>
...
...
src/app/(main)/dai-dien-gioi-chu/lib/sampleHtml.ts
View file @
2ce89c19
export
const
SAMPLE_HTML
=
`
<div class="document">
<h1
style="font-size:18px; font-weight:700; margin-bottom:8
px;">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:12
px;">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>
...
...
src/app/(main)/dai-dien-gioi-chu/tap-huan-nsdld/[id]/page.tsx
View file @
2ce89c19
...
...
@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<
div
className=
"grid grid-cols-1 lg:grid-cols-3 gap-6"
>
{
/* Main content */
}
<
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
>
</
main
>
...
...
src/app/(main)/dai-dien-gioi-chu/tin-lien-quan/[id]/page.tsx
View file @
2ce89c19
...
...
@@ -4,23 +4,31 @@ import Image from "next/image";
import
ListCategory
from
"../../components/list-category"
;
import
{
OWNER_REPRESENTATIVES_CATEGORIES
}
from
"@constants/categories"
;
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
{
useParams
}
from
'next/navigation'
import
{
GetNewsDetailResponseType
}
from
'@lib/types/news-detail-response-data'
;
import
{
useParams
}
from
"next/navigation"
;
import
{
GetNewsDetailResponseType
}
from
"@lib/types/news-detail-response-data"
;
// ...existing code...
const
Page
:
React
.
FC
=
()
=>
{
const
{
id
}
=
useParams
()
const
{
data
,
isLoading
}
=
useGetNewsId
<
GetNewsDetailResponseType
>
(
id
as
string
)
const
{
id
}
=
useParams
();
const
{
data
,
isLoading
}
=
useGetNewsId
<
GetNewsDetailResponseType
>
(
id
as
string
);
return
(
<
div
className=
"min-h-screen w-full container mx-auto p-4"
>
<
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"
>
{
/* Main content */
}
<
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
>
{
/* Sidebar */
}
...
...
src/app/(main)/dai-dien-gioi-chu/tin-lien-quan/page.tsx
View file @
2ce89c19
...
...
@@ -10,13 +10,14 @@ import { useGetNews } from "@api/endpoints/news";
import
{
GetNewsResponseType
}
from
"@api/types/NewsPage.type"
;
import
{
PATHS
}
from
"@constants/paths"
;
export
default
function
Page
()
{
const
[
submitSearch
]
=
useState
(
""
);
const
[
submitSearch
,
setsubmitSearch
]
=
useState
(
""
);
const
[
page
,
setPage
]
=
useState
(
1
);
const
pageSize
=
5
;
const
{
data
:
allData
}
=
useGetNews
<
GetNewsResponseType
>
({
pageSize
:
String
(
pageSize
),
currentPage
:
String
(
page
),
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
undefined
,
filters
:
submitSearch
?
`title @=
${
submitSearch
}
,category@=Tin liên quan`
:
'category@=Tin liên quan'
,
});
return
(
<
div
className=
"min-h-screen container mx-auto p-4"
>
...
...
@@ -45,7 +46,7 @@ export default function Page() {
{
/* Sidebar */
}
<
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=
"w-full h-56 relative bg-gray-100"
>
...
...
src/app/(main)/hoat-dong/dao-tao/[id]/page.tsx
View file @
2ce89c19
...
...
@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<
div
className=
"grid grid-cols-1 lg:grid-cols-3 gap-6"
>
{
/* Main content */
}
<
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
>
</
main
>
...
...
src/app/(main)/hoat-dong/dao-tao/page.tsx
View file @
2ce89c19
...
...
@@ -4,7 +4,7 @@ import ListCategory from "@app/dai-dien-gioi-chu/components/list-category";
import
{
EVENT_CATEGORIES
}
from
"@constants/categories"
;
import
EventFilter
from
"@app/dai-dien-gioi-chu/components/event-filter"
;
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
{
useGetNews
}
from
"@api/endpoints/news"
;
import
{
GetNewsResponseType
}
from
"@api/types/NewsPage.type"
;
...
...
@@ -17,19 +17,19 @@ export default function Page() {
const
{
data
:
allData
}
=
useGetNews
<
GetNewsResponseType
>
({
pageSize
:
String
(
pageSize
),
currentPage
:
String
(
page
),
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
undefined
,
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
'category @=Đào tạo'
,
});
return
(
<
div
className=
"min-h-screen container mx-auto p-4"
>
<
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"
>
{
/* Main content */
}
<
main
className=
"lg:col-span-2 bg-background "
>
<
div
className=
"pb-5 overflow-hidden"
>
{
allData
?.
responseData
.
rows
.
map
((
news
)
=>
(
<
NewsContent
key=
{
news
.
id
}
news=
{
news
}
link=
{
`${PATHS.event}/dao-tao/${news.id}`
}
/>
<
NewsContent
key=
{
news
.
id
}
news=
{
news
}
link=
{
`${PATHS.event}/dao-tao/${news.id}`
}
/>
))
}
<
div
className=
"w-full flex justify-center mt-4"
>
...
...
src/app/(main)/hoat-dong/page.tsx
View file @
2ce89c19
...
...
@@ -15,7 +15,7 @@ export default function Page() {
return
(
<
div
className=
"min-h-screen container mx-auto p-4"
>
<
div
className=
"w-full flex flex-col gap-5"
>
<
ListCategory
categories=
{
EVENT_CATEGORIES
}
/>
<
ListCategory
categories=
{
EVENT_CATEGORIES
}
/>
</
div
>
</
div
>
);
...
...
src/app/(main)/hoat-dong/su-kien/[id]/page.tsx
View file @
2ce89c19
...
...
@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<
div
className=
"grid grid-cols-1 lg:grid-cols-3 gap-6"
>
{
/* Main content */
}
<
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
>
</
main
>
...
...
src/app/(main)/hoat-dong/su-kien/page.tsx
View file @
2ce89c19
...
...
@@ -5,7 +5,7 @@ import { EVENT_CATEGORIES } from "@constants/categories";
import
EventFilter
from
"@app/dai-dien-gioi-chu/components/event-filter"
;
import
NewsContent
from
"@app/dai-dien-gioi-chu/components/card-news"
;
// ...existing code...
import
{
Pagination
}
from
"@components/base/pagination"
;
import
{
Pagination
}
from
"@components/base/pagination"
;
import
Image
from
"next/image"
;
import
{
useGetNews
}
from
"@api/endpoints/news"
;
import
{
GetNewsResponseType
}
from
"@api/types/NewsPage.type"
;
...
...
@@ -18,19 +18,19 @@ export default function Page() {
const
{
data
:
allData
}
=
useGetNews
<
GetNewsResponseType
>
({
pageSize
:
String
(
pageSize
),
currentPage
:
String
(
page
),
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
undefined
,
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
'category @=Sự kiện'
,
});
return
(
<
div
className=
"min-h-screen container mx-auto p-4"
>
<
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"
>
{
/* Main content */
}
<
main
className=
"lg:col-span-2 bg-background "
>
<
div
className=
"pb-5 overflow-hidden"
>
{
allData
?.
responseData
.
rows
.
map
((
news
)
=>
(
<
NewsContent
key=
{
news
.
id
}
news=
{
news
}
link=
{
`${PATHS.event}/su-kien/${news.id}`
}
/>
<
NewsContent
key=
{
news
.
id
}
news=
{
news
}
link=
{
`${PATHS.event}/su-kien/${news.id}`
}
/>
))
}
<
div
className=
"w-full flex justify-center mt-4"
>
...
...
src/app/(main)/hoi-vien/ket-noi-hoi-vien/page.tsx
View file @
2ce89c19
...
...
@@ -16,7 +16,7 @@ export default function Page() {
const
{
data
:
allData
,
isLoading
}
=
useGetNews
<
GetNewsResponseType
>
({
pageSize
:
String
(
pageSize
),
currentPage
:
String
(
page
),
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
undefined
,
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
`category @=Kết nối hội viên`
,
});
return
(
<
div
className=
"min-h-screen container mx-auto pb-4"
>
...
...
@@ -30,24 +30,24 @@ export default function Page() {
<
CardNews
key=
{
news
.
id
}
news=
{
news
}
/>
))
}
<
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
>
<
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"
>
...
...
src/app/(main)/hoi-vien/tin-hoi-vien/page.tsx
View file @
2ce89c19
...
...
@@ -16,7 +16,7 @@ export default function Page() {
const
{
data
:
allData
,
isLoading
}
=
useGetNews
<
GetNewsResponseType
>
({
pageSize
:
String
(
pageSize
),
currentPage
:
String
(
page
),
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
undefined
,
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
'category @=Tin hội viên'
,
})
return
(
<
div
className=
"min-h-screen container mx-auto pb-4"
>
...
...
src/app/(main)/search/page.tsx
0 → 100644
View file @
2ce89c19
"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
{
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
}
=
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"
>
{
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
>
);
}
src/app/(main)/site-map/_lib/mock-data.ts
0 → 100644
View file @
2ce89c19
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
;
src/app/(main)/site-map/page.tsx
0 → 100644
View file @
2ce89c19
"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
;
src/app/(main)/thong-tin-truyen-thong/an-pham/page.tsx
View file @
2ce89c19
...
...
@@ -4,7 +4,7 @@ import ListCategory from "@app/dai-dien-gioi-chu/components/list-category";
import
{
MEDIA_INFORMATION_CATEGORIES
}
from
"@constants/categories"
;
import
EventFilter
from
"@app/dai-dien-gioi-chu/components/event-filter"
;
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
{
useGetNews
}
from
"@api/endpoints/news"
;
import
{
GetNewsResponseType
}
from
"@api/types/NewsPage.type"
;
...
...
@@ -16,12 +16,12 @@ export default function Page() {
const
{
data
:
allData
}
=
useGetNews
<
GetNewsResponseType
>
({
pageSize
:
String
(
pageSize
),
currentPage
:
String
(
page
),
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
undefined
,
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
'category @=Ấn phẩm'
,
});
return
(
<
div
className=
"min-h-screen container mx-auto p-4"
>
<
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"
>
{
/* Main content */
}
...
...
src/app/(main)/thong-tin-truyen-thong/chuyen-de/[id]/page.tsx
View file @
2ce89c19
...
...
@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<
div
className=
"grid grid-cols-1 lg:grid-cols-3 gap-6"
>
{
/* Main content */
}
<
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
>
</
main
>
...
...
src/app/(main)/thong-tin-truyen-thong/chuyen-de/page.tsx
View file @
2ce89c19
...
...
@@ -5,7 +5,7 @@ import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories";
// ...existing code...
import
NewsContent
from
"@app/dai-dien-gioi-chu/components/card-news"
;
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
{
useGetNews
}
from
"@api/endpoints/news"
;
import
{
GetNewsResponseType
}
from
"@api/types/NewsPage.type"
;
...
...
@@ -17,24 +17,27 @@ export default function Page() {
const
{
data
:
allData
}
=
useGetNews
<
GetNewsResponseType
>
({
pageSize
:
String
(
pageSize
),
currentPage
:
String
(
page
),
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
undefined
,
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
'category @=Chuyên đề'
,
});
return
(
<
div
className=
"min-h-screen container mx-auto p-4"
>
<
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"
>
{
/* Main content */
}
<
main
className=
"lg:col-span-2 bg-background "
>
<
div
className=
"pb-5 overflow-hidden"
>
{
allData
?.
responseData
.
rows
.
map
((
news
)
=>
(
<
NewsContent
key=
{
news
.
id
}
news=
{
news
}
link=
{
`${PATHS.mediaInformation}/chuyen-de/${news.id}`
}
/>
))
}
{
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"
>
<
Pagination
...
...
src/app/(main)/thong-tin-truyen-thong/page.tsx
View file @
2ce89c19
...
...
@@ -15,7 +15,7 @@ export default function Page() {
return
(
<
div
className=
"min-h-screen container mx-auto p-4"
>
<
div
className=
"w-full flex flex-col gap-5"
>
<
ListCategory
categories=
{
MEDIA_INFORMATION_CATEGORIES
}
/>
<
ListCategory
categories=
{
MEDIA_INFORMATION_CATEGORIES
}
/>
</
div
>
</
div
>
...
...
src/app/(main)/thong-tin-truyen-thong/thong-tin-chinh-sach-va-phap-luat/[id]/page.tsx
View file @
2ce89c19
...
...
@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<
div
className=
"grid grid-cols-1 lg:grid-cols-3 gap-6"
>
{
/* Main content */
}
<
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
>
</
main
>
...
...
src/app/(main)/thong-tin-truyen-thong/thong-tin-chinh-sach-va-phap-luat/page.tsx
View file @
2ce89c19
...
...
@@ -5,7 +5,7 @@ import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories";
// ...existing code...
import
NewsContent
from
"@app/dai-dien-gioi-chu/components/card-news"
;
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
{
useGetNews
}
from
"@api/endpoints/news"
;
import
{
GetNewsResponseType
}
from
"@api/types/NewsPage.type"
;
...
...
@@ -18,24 +18,27 @@ export default function Page() {
const
{
data
:
allData
}
=
useGetNews
<
GetNewsResponseType
>
({
pageSize
:
String
(
pageSize
),
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'
,
});
return
(
<
div
className=
"min-h-screen container mx-auto p-4"
>
<
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"
>
{
/* Main content */
}
<
main
className=
"lg:col-span-2 bg-background "
>
<
div
className=
"pb-5 overflow-hidden"
>
{
allData
?.
responseData
.
rows
.
map
((
news
)
=>
(
<
NewsContent
key=
{
news
.
id
}
news=
{
news
}
link=
{
`${PATHS.mediaInformation}/thong-tin-chinh-sach-va-phap-luat/${news.id}`
}
/>
))
}
{
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}/thong-tin-chinh-sach-va-phap-luat/${news.id}`
}
/>
)))
}
<
div
className=
"w-full flex justify-center mt-4"
>
<
Pagination
...
...
src/app/(main)/thong-tin-truyen-thong/thu-vien-tai-lieu/[id]/page.tsx
View file @
2ce89c19
...
...
@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<
div
className=
"grid grid-cols-1 lg:grid-cols-3 gap-6"
>
{
/* Main content */
}
<
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
>
</
main
>
...
...
src/app/(main)/thong-tin-truyen-thong/thu-vien-tai-lieu/page.tsx
View file @
2ce89c19
...
...
@@ -4,7 +4,7 @@ import ListCategory from "@app/dai-dien-gioi-chu/components/list-category";
import
{
MEDIA_INFORMATION_CATEGORIES
}
from
"@constants/categories"
;
// ...existing code...
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
Image
from
"next/image"
;
import
{
useGetNews
}
from
"@api/endpoints/news"
;
...
...
@@ -18,24 +18,27 @@ export default function Page() {
const
{
data
:
allData
}
=
useGetNews
<
GetNewsResponseType
>
({
pageSize
:
String
(
pageSize
),
currentPage
:
String
(
page
),
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
undefined
,
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
'category @=Thư viện tài liệu'
,
});
return
(
<
div
className=
"min-h-screen container mx-auto p-4"
>
<
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"
>
{
/* Main content */
}
<
main
className=
"lg:col-span-2 bg-background "
>
<
div
className=
"pb-5 overflow-hidden"
>
{
allData
?.
responseData
.
rows
.
map
((
news
)
=>
(
<
NewsContent
key=
{
news
.
id
}
news=
{
news
}
link=
{
`${PATHS.mediaInformation}/thu-vien-tai-lieu/${news.id}`
}
/>
))
}
{
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"
>
<
Pagination
...
...
src/app/(main)/thong-tin-truyen-thong/tin-doanh-nghiep/[id]/page.tsx
View file @
2ce89c19
...
...
@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<
div
className=
"grid grid-cols-1 lg:grid-cols-3 gap-6"
>
{
/* Main content */
}
<
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
>
</
main
>
...
...
src/app/(main)/thong-tin-truyen-thong/tin-doanh-nghiep/page.tsx
View file @
2ce89c19
...
...
@@ -5,7 +5,7 @@ import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories";
// ...existing code...
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
{
Pagination
}
from
"@components/base/pagination"
;
import
Image
from
"next/image"
;
import
{
useGetNews
}
from
"@api/endpoints/news"
;
import
{
GetNewsResponseType
}
from
"@api/types/NewsPage.type"
;
...
...
@@ -18,24 +18,27 @@ export default function Page() {
const
{
data
:
allData
}
=
useGetNews
<
GetNewsResponseType
>
({
pageSize
:
String
(
pageSize
),
currentPage
:
String
(
page
),
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
undefined
,
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
'category @=Tin doanh nghiệp'
,
});
return
(
<
div
className=
"min-h-screen container mx-auto p-4"
>
<
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"
>
{
/* Main content */
}
<
main
className=
"lg:col-span-2 bg-background "
>
<
div
className=
"pb-5 overflow-hidden"
>
{
allData
?.
responseData
.
rows
.
map
((
news
)
=>
(
<
NewsContent
key=
{
news
.
id
}
news=
{
news
}
link=
{
`${PATHS.mediaInformation}/tin-doanh-nghiep/${news.id}`
}
/>
))
}
{
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"
>
<
Pagination
...
...
src/app/(main)/thong-tin-truyen-thong/tin-kinh-te/[id]/page.tsx
View file @
2ce89c19
...
...
@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<
div
className=
"grid grid-cols-1 lg:grid-cols-3 gap-6"
>
{
/* Main content */
}
<
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
>
</
main
>
...
...
src/app/(main)/thong-tin-truyen-thong/tin-kinh-te/page.tsx
View file @
2ce89c19
...
...
@@ -4,7 +4,7 @@ import ListCategory from "@app/dai-dien-gioi-chu/components/list-category";
import
{
MEDIA_INFORMATION_CATEGORIES
}
from
"@constants/categories"
;
// ...existing code...
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
{
useGetNews
}
from
"@api/endpoints/news"
;
import
{
GetNewsResponseType
}
from
"@api/types/NewsPage.type"
;
...
...
@@ -17,24 +17,27 @@ export default function Page() {
const
{
data
:
allData
}
=
useGetNews
<
GetNewsResponseType
>
({
pageSize
:
String
(
pageSize
),
currentPage
:
String
(
page
),
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
undefined
,
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
'category @=Tin kinh tế'
,
});
return
(
<
div
className=
"min-h-screen container mx-auto p-4"
>
<
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"
>
{
/* Main content */
}
<
main
className=
"lg:col-span-2 bg-background "
>
<
div
className=
"pb-5 overflow-hidden"
>
{
allData
?.
responseData
.
rows
.
map
((
news
)
=>
(
<
NewsContent
key=
{
news
.
id
}
news=
{
news
}
link=
{
`${PATHS.mediaInformation}/tin-kinh-te/${news.id}`
}
/>
))
}
{
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"
>
<
Pagination
...
...
src/app/(main)/thong-tin-truyen-thong/tin-vcci/[id]/page.tsx
View file @
2ce89c19
...
...
@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<
div
className=
"grid grid-cols-1 lg:grid-cols-3 gap-6"
>
{
/* Main content */
}
<
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
>
</
main
>
...
...
src/app/(main)/thong-tin-truyen-thong/tin-vcci/page.tsx
View file @
2ce89c19
...
...
@@ -6,13 +6,13 @@ import { MEDIA_INFORMATION_CATEGORIES } from "@constants/categories";
import
NewsContent
from
"@app/dai-dien-gioi-chu/components/card-news"
;
import
ListFilter
from
"@app/dai-dien-gioi-chu/components/list-filter"
;
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
{
useGetNews
}
from
"@api/endpoints/news"
;
import
{
PATHS
}
from
"@constants/paths"
;
import
{
GetNewsResponseType
}
from
"@api/types/NewsPage.type"
;
export
default
function
Page
()
{
const
[
submitSearch
]
=
useState
(
""
);
const
[
submitSearch
,
setSubmitSearch
]
=
useState
(
""
);
const
[
page
,
setPage
]
=
useState
(
1
);
const
pageSize
=
5
;
...
...
@@ -24,19 +24,23 @@ export default function Page() {
return
(
<
div
className=
"min-h-screen container mx-auto p-4"
>
<
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"
>
{
/* Main content */
}
<
main
className=
"lg:col-span-2 bg-background "
>
<
div
className=
"pb-5 overflow-hidden"
>
{
allData
?.
responseData
.
rows
.
map
((
news
)
=>
(
<
NewsContent
key=
{
news
.
id
}
news=
{
news
}
link=
{
`${PATHS.mediaInformation}/tin-vcci/${news.id}`
}
/>
))
}
{
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"
>
<
Pagination
...
...
@@ -52,8 +56,8 @@ export default function Page() {
{
/* Sidebar */
}
<
aside
className=
"space-y-6"
>
<
ListFilter
/>
<
EventCalendar
/>
<
ListFilter
onSearch=
{
setSubmitSearch
}
/>
<
EventCalendar
/>
<
div
className=
"bg-white border rounded-md overflow-hidden"
>
<
div
className=
"w-full h-56 relative bg-gray-100"
>
<
Image
...
...
src/app/(main)/xuat-xu-hang-hoa/page.tsx
View file @
2ce89c19
...
...
@@ -6,7 +6,7 @@ import Image from "next/image";
export
default
function
page
()
{
return
(
<
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]"
>
<
ListCategory
/>
</
div
>
...
...
src/app/(main)/xuc-tien-thuong-mai/[id]/page.tsx
View file @
2ce89c19
...
...
@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<
div
className=
"grid grid-cols-1 lg:grid-cols-3 gap-6"
>
{
/* Main content */
}
<
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
>
</
main
>
...
...
src/app/(main)/xuc-tien-thuong-mai/co-hoi-kinh-doanh/[id]/page.tsx
View file @
2ce89c19
...
...
@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<
div
className=
"grid grid-cols-1 lg:grid-cols-3 gap-6"
>
{
/* Main content */
}
<
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
>
</
main
>
...
...
src/app/(main)/xuc-tien-thuong-mai/co-hoi-kinh-doanh/page.tsx
View file @
2ce89c19
...
...
@@ -17,7 +17,8 @@ export default function Page() {
const
{
data
:
allData
}
=
useGetNews
<
GetNewsResponseType
>
({
pageSize
:
String
(
pageSize
),
currentPage
:
String
(
page
),
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
undefined
,
// filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters
:
'category@=Cơ hội kinh doanh'
});
return
(
<
div
className=
"min-h-screen container mx-auto p-4"
>
...
...
src/app/(main)/xuc-tien-thuong-mai/ho-tro-kinh-doanh/[id]/page.tsx
View file @
2ce89c19
...
...
@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<
div
className=
"grid grid-cols-1 lg:grid-cols-3 gap-6"
>
{
/* Main content */
}
<
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
>
</
main
>
...
...
src/app/(main)/xuc-tien-thuong-mai/ho-tro-kinh-doanh/page.tsx
View file @
2ce89c19
...
...
@@ -16,7 +16,8 @@ export default function Page() {
const
{
data
:
allData
}
=
useGetNews
<
GetNewsResponseType
>
({
pageSize
:
String
(
pageSize
),
currentPage
:
String
(
page
),
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
undefined
,
// filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters
:
'category@=Hỗ trợ kinh doanh'
});
return
(
<
div
className=
"min-h-screen container mx-auto p-4"
>
...
...
src/app/(main)/xuc-tien-thuong-mai/moi-truong-kinh-doanh/[id]/page.tsx
View file @
2ce89c19
...
...
@@ -20,6 +20,10 @@ const Page: React.FC = () => {
<
div
className=
"grid grid-cols-1 lg:grid-cols-3 gap-6"
>
{
/* Main content */
}
<
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
>
</
main
>
...
...
src/app/(main)/xuc-tien-thuong-mai/moi-truong-kinh-doanh/page.tsx
View file @
2ce89c19
...
...
@@ -17,7 +17,8 @@ export default function Page() {
const
{
data
:
allData
}
=
useGetNews
<
GetNewsResponseType
>
({
pageSize
:
String
(
pageSize
),
currentPage
:
String
(
page
),
filters
:
submitSearch
?
`title @=
${
submitSearch
}
`
:
undefined
,
// filters: submitSearch ? `title @=${submitSearch}` : undefined,
filters
:
'category@=Môi trường kinh doanh'
});
return
(
<
div
className=
"min-h-screen container mx-auto p-4"
>
...
...
src/components/base/card-news/index.tsx
View file @
2ce89c19
...
...
@@ -2,8 +2,24 @@
import
{
NewsItem
}
from
'@app/dai-dien-gioi-chu/lib/types/NewsPage.type'
;
import
Links
from
'@links/index'
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
(
<
a
...
...
@@ -14,20 +30,21 @@ function NewsContent({ news, link }: { news: NewsItem, link: string }) {
src=
{
`${Links.imageEndpoint}${news.thumbnail}`
}
alt=
{
news
.
title
}
className=
"w-full sm:w-56 md:w-64 h-40 md:h-36 object-cover shrink-0"
onError=
{
(
e
)
=>
{
e
.
currentTarget
.
src
=
"/img-error.png"
}
}
onError=
{
(
e
)
=>
{
e
.
currentTarget
.
src
=
"/img-error.png"
}
}
/>
<
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
}
</
p
>
<
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 prose tiptap"
>
{
parse
(
news
.
description
)
}
</
div
>
<
div
className=
"text-sm prose tiptap"
>
{
stripImagesAndHtml
(
news
.
description
)
}
</
div
>
</
div
>
</
div
>
</
a
>
...
...
src/components/shared/editor-content/AppEditorContent.tsx
View file @
2ce89c19
...
...
@@ -4,24 +4,47 @@ import { AppEditorContentProps } from './AppEditorContent.type';
import
'./AppEditorContent.css'
;
const
AppEditorContent
:
FC
<
AppEditorContentProps
>
=
({
value
=
''
,
className
=
''
})
=>
{
const
transform
=
(
node
:
DOMNode
):
JSX
.
Element
|
null
=>
{
if
(
node
instanceof
Element
&&
node
.
tagName
===
'strong'
)
{
const
transform
=
(
node
:
DOMNode
):
JSX
.
Element
|
string
|
undefined
|
null
=>
{
// 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
(
<
strong
className=
"custom-strong"
>
{
node
.
children
&&
Array
.
isArray
(
node
.
children
)
?
node
.
children
.
map
((
child
,
index
)
=>
{
if
(
typeof
child
===
'string'
)
{
return
child
;
}
else
if
(
child
instanceof
Text
)
{
return
child
.
data
;
}
return
null
;
})
:
null
}
{
children
}
</
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
(
...
...
@@ -31,4 +54,4 @@ const AppEditorContent: FC<AppEditorContentProps> = ({ value = '', className = '
);
};
export
default
AppEditorContent
;
export
default
AppEditorContent
;
\ 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