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
1334e92b
Commit
1334e92b
authored
May 20, 2026
by
Lê Bảo Hồng Đức
☄
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix
parent
d9a52db6
Changes
13
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
1018 additions
and
1 deletion
+1018
-1
orval.config.ts
orval.config.ts
+1
-0
banner.ts
src/api/endpoints/banner.ts
+759
-0
deleteBannerId200.ts
src/api/models/deleteBannerId200.ts
+11
-0
deleteBannerId200AllOf.ts
src/api/models/deleteBannerId200AllOf.ts
+11
-0
getBannerId200.ts
src/api/models/getBannerId200.ts
+11
-0
getBannerId200AllOf.ts
src/api/models/getBannerId200AllOf.ts
+12
-0
getBannerParams.ts
src/api/models/getBannerParams.ts
+37
-0
index.ts
src/api/models/index.ts
+9
-0
postBanner200.ts
src/api/models/postBanner200.ts
+11
-0
postBanner200AllOf.ts
src/api/models/postBanner200AllOf.ts
+12
-0
putBannerId200.ts
src/api/models/putBannerId200.ts
+11
-0
putBannerId200AllOf.ts
src/api/models/putBannerId200AllOf.ts
+12
-0
page.tsx
src/app/admin/base-config/page.tsx
+121
-1
No files found.
orval.config.ts
View file @
1334e92b
...
...
@@ -127,6 +127,7 @@ const orvalConfig = async () => {
'NewsletterSubscription'
,
'SiteInformation'
,
'Logo'
,
'Banner'
,
],
},
},
...
...
src/api/endpoints/banner.ts
0 → 100644
View file @
1334e92b
This diff is collapsed.
Click to expand it.
src/api/models/deleteBannerId200.ts
0 → 100644
View file @
1334e92b
/**
* Generated by orval v8.0.0-rc.0 🍺
* Do not edit manually.
* Backend Template
* Coded by Meu TEAM
* OpenAPI spec version: 1.0.0
*/
import
type
{
ApiResponse
}
from
'./apiResponse'
;
import
type
{
DeleteBannerId200AllOf
}
from
'./deleteBannerId200AllOf'
;
export
type
DeleteBannerId200
=
ApiResponse
&
DeleteBannerId200AllOf
;
src/api/models/deleteBannerId200AllOf.ts
0 → 100644
View file @
1334e92b
/**
* Generated by orval v8.0.0-rc.0 🍺
* Do not edit manually.
* Backend Template
* Coded by Meu TEAM
* OpenAPI spec version: 1.0.0
*/
export
type
DeleteBannerId200AllOf
=
{
responseData
?:
boolean
;
};
src/api/models/getBannerId200.ts
0 → 100644
View file @
1334e92b
/**
* Generated by orval v8.0.0-rc.0 🍺
* Do not edit manually.
* Backend Template
* Coded by Meu TEAM
* OpenAPI spec version: 1.0.0
*/
import
type
{
ApiResponse
}
from
'./apiResponse'
;
import
type
{
GetBannerId200AllOf
}
from
'./getBannerId200AllOf'
;
export
type
GetBannerId200
=
ApiResponse
&
GetBannerId200AllOf
;
src/api/models/getBannerId200AllOf.ts
0 → 100644
View file @
1334e92b
/**
* Generated by orval v8.0.0-rc.0 🍺
* Do not edit manually.
* Backend Template
* Coded by Meu TEAM
* OpenAPI spec version: 1.0.0
*/
import
type
{
Banner
}
from
'./banner'
;
export
type
GetBannerId200AllOf
=
{
responseData
?:
Banner
;
};
src/api/models/getBannerParams.ts
0 → 100644
View file @
1334e92b
/**
* Generated by orval v8.0.0-rc.0 🍺
* Do not edit manually.
* Backend Template
* Coded by Meu TEAM
* OpenAPI spec version: 1.0.0
*/
import
type
{
FiltersParameter
}
from
'./filtersParameter'
;
import
type
{
SortFieldParameter
}
from
'./sortFieldParameter'
;
import
type
{
SortOrderParameter
}
from
'./sortOrderParameter'
;
import
type
{
PageParameter
}
from
'./pageParameter'
;
import
type
{
PageSizeParameter
}
from
'./pageSizeParameter'
;
export
type
GetBannerParams
=
{
/**
* filter, visit https://www.npmjs.com/package/sequelize-api-paginate for syntax
*/
filters
?:
FiltersParameter
;
/**
* sortField, visit https://www.npmjs.com/package/sequelize-api-paginate for syntax
*/
sortField
?:
SortFieldParameter
;
/**
* sort order, visit https://www.npmjs.com/package/sequelize-api-paginate for syntax
*/
sortOrder
?:
SortOrderParameter
;
/**
* page, visit https://www.npmjs.com/package/sequelize-api-paginate for syntax
* @minimum 1
*/
page
?:
PageParameter
;
/**
* pageSize, visit https://www.npmjs.com/package/sequelize-api-paginate for syntax
* @minimum 1
*/
pageSize
?:
PageSizeParameter
;
};
src/api/models/index.ts
View file @
1334e92b
...
...
@@ -45,6 +45,8 @@ export * from './deleteApiV10CategoryId200AllOf';
export
*
from
'./deleteApiV10ContactId200'
;
export
*
from
'./deleteApiV10ContactId200AllOf'
;
export
*
from
'./deleteApprovalParams'
;
export
*
from
'./deleteBannerId200'
;
export
*
from
'./deleteBannerId200AllOf'
;
export
*
from
'./deleteCategoryId200'
;
export
*
from
'./deleteCategoryId200AllOf'
;
export
*
from
'./deleteConfigParams'
;
...
...
@@ -106,6 +108,9 @@ export * from './getApiV10VideoId200';
export
*
from
'./getApiV10VideoId200AllOf'
;
export
*
from
'./getApiV10VideoParams'
;
export
*
from
'./getApprovalParams'
;
export
*
from
'./getBannerId200'
;
export
*
from
'./getBannerId200AllOf'
;
export
*
from
'./getBannerParams'
;
export
*
from
'./getCategoryId200'
;
export
*
from
'./getCategoryId200AllOf'
;
export
*
from
'./getCategoryParams'
;
...
...
@@ -266,6 +271,8 @@ export * from './postApiV10PageConfigBody';
export
*
from
'./postApiV10Video200'
;
export
*
from
'./postApiV10Video200AllOf'
;
export
*
from
'./postAuthForgotPasswordBody'
;
export
*
from
'./postBanner200'
;
export
*
from
'./postBanner200AllOf'
;
export
*
from
'./postCategory'
;
export
*
from
'./postCategory200'
;
export
*
from
'./postCategory200AllOf'
;
...
...
@@ -314,6 +321,8 @@ export * from './putApiV10SiteInformation200';
export
*
from
'./putApiV10SiteInformation200AllOf'
;
export
*
from
'./putAuthAssignBusinessInterestBody'
;
export
*
from
'./putAuthUpdatePasswordBody'
;
export
*
from
'./putBannerId200'
;
export
*
from
'./putBannerId200AllOf'
;
export
*
from
'./putCategoryId200'
;
export
*
from
'./putCategoryId200AllOf'
;
export
*
from
'./putConfigParams'
;
...
...
src/api/models/postBanner200.ts
0 → 100644
View file @
1334e92b
/**
* Generated by orval v8.0.0-rc.0 🍺
* Do not edit manually.
* Backend Template
* Coded by Meu TEAM
* OpenAPI spec version: 1.0.0
*/
import
type
{
ApiResponse
}
from
'./apiResponse'
;
import
type
{
PostBanner200AllOf
}
from
'./postBanner200AllOf'
;
export
type
PostBanner200
=
ApiResponse
&
PostBanner200AllOf
;
src/api/models/postBanner200AllOf.ts
0 → 100644
View file @
1334e92b
/**
* Generated by orval v8.0.0-rc.0 🍺
* Do not edit manually.
* Backend Template
* Coded by Meu TEAM
* OpenAPI spec version: 1.0.0
*/
import
type
{
Banner
}
from
'./banner'
;
export
type
PostBanner200AllOf
=
{
responseData
?:
Banner
;
};
src/api/models/putBannerId200.ts
0 → 100644
View file @
1334e92b
/**
* Generated by orval v8.0.0-rc.0 🍺
* Do not edit manually.
* Backend Template
* Coded by Meu TEAM
* OpenAPI spec version: 1.0.0
*/
import
type
{
ApiResponse
}
from
'./apiResponse'
;
import
type
{
PutBannerId200AllOf
}
from
'./putBannerId200AllOf'
;
export
type
PutBannerId200
=
ApiResponse
&
PutBannerId200AllOf
;
src/api/models/putBannerId200AllOf.ts
0 → 100644
View file @
1334e92b
/**
* Generated by orval v8.0.0-rc.0 🍺
* Do not edit manually.
* Backend Template
* Coded by Meu TEAM
* OpenAPI spec version: 1.0.0
*/
import
type
{
Banner
}
from
'./banner'
;
export
type
PutBannerId200AllOf
=
{
responseData
?:
Banner
;
};
src/app/admin/base-config/page.tsx
View file @
1334e92b
...
...
@@ -44,7 +44,10 @@ import {
putSiteInformation
,
}
from
"@/api/endpoints/site-information"
;
import
{
deleteLogoId
,
postLogo
,
putLogoId
}
from
"@/api/endpoints/logo"
;
import
{
deleteBannerId
,
getBanner
,
postBanner
,
putBannerId
}
from
"@/api/endpoints/banner"
;
import
type
{
Banner
,
BannerMutate
,
SiteInformationBranch
,
SiteInformationBranchMutate
,
SiteInformationData
,
...
...
@@ -52,6 +55,7 @@ import type {
SiteInformationSocialMutate
,
}
from
"@/api/models"
;
import
type
{
AdminMediaItem
}
from
"@/mockdata/admin-news"
;
import
{
fetchCmsFileById
,
toAdminMediaItem
}
from
"@/lib/api/files"
;
import
{
type
BaseConfigBannerItem
,
type
BaseConfigBranchItem
,
...
...
@@ -83,6 +87,13 @@ type LogoMediaItem = AdminMediaItem & {
logoId
?:
string
;
};
type
PageEnvelope
<
T
>
=
{
rows
?:
T
[];
count
?:
number
;
page
?:
number
;
pageSize
?:
number
;
};
type
ConfigItemForm
=
{
name
:
string
;
imageId
:
string
;
...
...
@@ -158,6 +169,27 @@ function mapConfigSocialToApi(social: BaseConfigSocialItem): SiteInformationSoci
};
}
function
mapApiBannerToConfig
(
banner
:
Banner
):
BaseConfigBannerItem
{
return
{
id
:
banner
.
id
??
createBaseConfigItemId
(
"banner"
),
name
:
banner
.
banner_name
??
""
,
imageId
:
banner
.
file_id
??
""
,
isActive
:
banner
.
status
!==
"INACTIVE"
,
displayTimeSeconds
:
banner
.
display_time
??
5
,
sortOrder
:
banner
.
display_order
??
1
,
};
}
function
mapConfigBannerToApi
(
banner
:
BaseConfigBannerItem
):
BannerMutate
{
return
{
banner_name
:
banner
.
name
.
trim
(),
file_id
:
banner
.
imageId
,
display_order
:
banner
.
sortOrder
,
display_time
:
Math
.
max
(
1
,
banner
.
displayTimeSeconds
||
1
),
status
:
banner
.
isActive
?
"ACTIVE"
:
"INACTIVE"
,
};
}
function
mapSiteLogoToConfig
(
siteInformation
:
SiteInformationData
):
{
logo
:
BaseConfigLogoItem
|
null
;
media
:
LogoMediaItem
|
null
;
...
...
@@ -493,6 +525,56 @@ export default function AdminBaseConfigPage() {
void
loadSiteInformation
();
const
loadBanners
=
async
()
=>
{
try
{
const
response
=
await
getBanner
({
page
:
1
,
pageSize
:
100
,
sortField
:
"display_order"
,
sortOrder
:
"asc"
,
});
const
pageData
=
getEnvelopeData
<
PageEnvelope
<
Banner
>>
(
response
);
const
bannerRows
=
pageData
?.
rows
??
[];
const
mediaRows
=
await
Promise
.
all
(
bannerRows
.
map
((
banner
)
=>
banner
.
file_id
)
.
filter
((
fileId
):
fileId
is
string
=>
Boolean
(
fileId
))
.
map
(
async
(
fileId
)
=>
{
const
file
=
await
fetchCmsFileById
(
fileId
).
catch
(()
=>
null
);
return
file
?
toAdminMediaItem
(
file
)
:
null
;
}),
);
if
(
!
mounted
)
return
;
const
nextBanners
=
bannerRows
.
map
(
mapApiBannerToConfig
);
setMediaItems
((
previous
)
=>
{
const
nextMap
=
new
Map
(
previous
.
map
((
entry
)
=>
[
entry
.
id
,
entry
]));
mediaRows
.
forEach
((
item
)
=>
{
if
(
item
)
nextMap
.
set
(
item
.
id
,
item
);
});
return
Array
.
from
(
nextMap
.
values
());
});
setConfig
((
previous
)
=>
previous
?
{
...
previous
,
banners
:
nextBanners
,
}
:
previous
,
);
setCurrentBannerIndex
(
0
);
}
catch
(
error
)
{
console
.
error
(
error
);
if
(
mounted
)
{
toast
.
error
(
"Không thể tải danh sách banner"
);
}
}
};
void
loadBanners
();
return
()
=>
{
mounted
=
false
;
};
...
...
@@ -609,8 +691,45 @@ export default function AdminBaseConfigPage() {
return
;
}
const
bannerDraft
:
BaseConfigBannerItem
=
{
id
:
editingItemId
||
createBaseConfigItemId
(
"banner"
),
name
:
trimmedName
,
imageId
:
itemForm
.
imageId
,
isActive
:
itemForm
.
isActive
,
displayTimeSeconds
:
itemForm
.
displayTimeSeconds
,
sortOrder
:
itemForm
.
sortOrder
,
};
try
{
const
response
=
editingItemId
?
await
putBannerId
(
editingItemId
,
mapConfigBannerToApi
(
bannerDraft
))
:
await
postBanner
(
mapConfigBannerToApi
(
bannerDraft
));
const
savedBanner
=
getEnvelopeData
<
Banner
>
(
response
);
const
nextBanner
=
savedBanner
?
mapApiBannerToConfig
(
savedBanner
)
:
bannerDraft
;
const
nextConfig
=
cloneBaseConfigData
(
config
);
if
(
editingItemId
)
{
nextConfig
.
banners
=
nextConfig
.
banners
.
map
((
item
)
=>
item
.
id
===
editingItemId
?
nextBanner
:
item
,
);
}
else
{
nextConfig
.
banners
.
push
(
nextBanner
);
setCurrentBannerIndex
(
Math
.
max
(
nextConfig
.
banners
.
length
-
1
,
0
));
}
setConfig
(
nextConfig
);
setSavingItem
(
false
);
setItemDialogOpen
(
false
);
toast
.
success
(
"Đã lưu cấu hình banner"
);
}
catch
(
error
)
{
console
.
error
(
error
);
setSavingItem
(
false
);
toast
.
error
(
"Không thể lưu cấu hình banner"
);
}
return
;
const
nextConfig
=
cloneBaseConfigData
(
config
!
);
if
(
editingItemId
)
{
nextConfig
.
banners
=
nextConfig
.
banners
.
map
((
item
)
=>
item
.
id
===
editingItemId
...
...
@@ -663,6 +782,7 @@ export default function AdminBaseConfigPage() {
return
;
}
}
else
{
await
deleteBannerId
(
deleteTarget
.
id
);
nextConfig
.
banners
=
nextConfig
.
banners
.
filter
((
item
)
=>
item
.
id
!==
deleteTarget
.
id
);
setCurrentBannerIndex
((
previous
)
=>
Math
.
max
(
0
,
Math
.
min
(
previous
,
nextConfig
.
banners
.
length
-
1
)),
...
...
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