feat(cms): enhance content editors and implement automatic ID generation

Improve the CMS administration interface across multiple pages (Accreditation, Admissions, History, Partnerships, and Policies) with a focus on usability and data integrity.
Key changes include:
- Implement `ensureUniqueIds` utility to automatically generate and maintain unique slugs for content items, removing the need for manual ID entry in the UI.
- Refactor the Admissions calculator to support detailed per-option editing via a new dedicated view and routes.
- Replace basic datalists with a custom, searchable icon combobox component for better visual selection.
- Update `_renderSingletonPageView` to handle active tab persistence via query parameters.
- Streamline editor configurations by removing redundant fields and improving help text.
- Enhance the Admissions "Key Dates" editor with a dynamic table interface for managing columns and rows.
- Normalize data payloads in controllers to ensure consistent API responses and internal linking.
This commit is contained in:
Tống Thành Đạt
2026-04-21 12:37:19 +07:00
parent 122df1a46e
commit 5d0fae6d51
30 changed files with 2174 additions and 477 deletions
+10 -10
View File
@@ -14,7 +14,7 @@ const {
module.exports = {
key: "history",
title: "History Management",
subtitle: "Edit content displayed on the history page",
subtitle: "Manage the content for the history page",
routeBase: "/admin/history",
apiPath: "/api/history",
previewPath: "/about/history",
@@ -29,7 +29,10 @@ module.exports = {
icon("icon", "Highlight icon"),
text("text", "Message", { maxLength: 110 }),
text("linkLabel", "Link label", { maxLength: 30 }),
url("href", "Link URL", { maxLength: 255 }),
url("href", "Link URL", {
maxLength: 255,
helpText: "Use an anchor or website path only. Examples: #milestones, /about/history, /contact",
}),
]),
},
{
@@ -47,14 +50,11 @@ module.exports = {
label: "Filter Controls",
icon: "fas fa-filter",
schema: object("filters", "Filter controls", [
text("yearLabel", "Year filter label", { maxLength: 30 }),
text("categoryLabel", "Category filter label", { maxLength: 30 }),
text("buttonLabel", "Apply button label", { maxLength: 30 }),
stringList("yearOptions", "Year options", {
itemLabel: "Year option",
maxLength: 30,
sortable: true,
helpText: "Reorder to control the dropdown order shown to users.",
helpText: "Reorder these options to change the order in the Year range dropdown.",
}),
stringList("categoryOptions", "Category options", {
itemLabel: "Category option",
@@ -68,13 +68,13 @@ module.exports = {
label: "Timeline",
icon: "fas fa-clock-rotate-left",
schema: object("timeline", "Timeline", [
text("loadMoreLabel", "Load more button label", { maxLength: 40 }),
objectList(
"items",
"Milestones",
[
text("id", "Milestone key", { maxLength: 60 }),
text("year", "Year", { maxLength: 10 }),
text("year", "Year", {
maxLength: 30,
}),
combobox("yearRange", "Year range", {
maxLength: 30,
optionsPath: "filters.yearOptions",
@@ -90,7 +90,7 @@ module.exports = {
rows: 4,
}),
image("image", "Milestone image", {
imageHint: "Recommended 436x190 px",
imageHint: "Recommended 436x190 px, 16:9 aspect ratio.",
helpText: "Wide image used inside the milestone card.",
}),
text("imageAlt", "Image alt text", { maxLength: 120 }),