feat(cms): enhance content editors with dynamic UI configs and validation

Implement a more flexible, configuration-driven approach for CMS editors across accreditation, admissions, and partnerships modules.
- Move UI labels, help texts, and field limits from hardcoded views to `editorUi` configurations in config files.
- Add server-side and client-side validation to prevent duplicate category tabs in accreditation and partnerships editors.
- Refactor partnership and admission views to dynamically render tabs and fields based on the provided configuration.
- Update field length constraints and default values across multiple content editors to better align with frontend requirements.
- Improve the admissions calculator editor with dynamic field configurations and default value fallbacks.
This commit is contained in:
Tống Thành Đạt
2026-04-22 18:06:03 +07:00
parent a44d005c13
commit a39c336dd4
19 changed files with 790 additions and 228 deletions
+77 -18
View File
@@ -20,6 +20,39 @@ module.exports = {
previewPath: "/admissions",
dataFile: "admissions",
imageType: "admissions",
editorUi: {
keyDates: {
tableLabel: "Key dates table",
tableHelpText: "Manage the table directly by adding or removing columns and rows.",
addColumnLabel: "Add Column",
addRowLabel: "Add Row",
columnPlaceholder: "Column name",
emptyRowsText: "No rows yet.",
actionsLabel: "Actions",
columnLabelMaxLength: 40,
cellMaxLength: 60,
},
calculator: {
ctaLabel: "Primary button",
ctaHelpText: "This button appears at the bottom of the calculator card.",
optionsLabel: "Calculator options",
optionsHelpText: "Each option has its own pricing labels, amount, note, and icon. Open the edit page to update the option details.",
optionsEmptyText: "No calculator options yet.",
addOptionLabel: "Add calculator option",
maxOptions: 3,
limitHelpText: "You can add up to 3 calculator options.",
defaultOption: {
paceLabel: "Target Pace",
minPaceLabel: "Relaxed",
maxPaceLabel: "Accelerated",
resultLabel: "Estimated Monthly Payment",
monthlyAmount: "299",
monthlySuffix: "/mo",
noteIcon: "fa-bolt",
note: "",
},
},
},
tabs: [
{
key: "hero",
@@ -102,8 +135,8 @@ module.exports = {
label: "Tuition",
icon: "fas fa-chart-column",
schema: object("tuition", "Tuition", [
text("title", "Section title", { maxLength: 60 }),
text("chartTitle", "Chart title", { maxLength: 60 }),
text("title", "Section title", { maxLength: 45 }),
text("chartTitle", "Chart title", { maxLength: 40 }),
textarea("chartDescription", "Chart description", {
maxLength: 140,
rows: 3,
@@ -112,14 +145,14 @@ module.exports = {
"series",
"Chart series",
[
text("label", "Series label", { maxLength: 40 }),
text("label", "Series label", { maxLength: 20 }),
{ key: "color", label: "Series color", type: "color" },
objectList(
"points",
"Data points",
[
text("time", "Time label", {
maxLength: 30,
maxLength: 14,
placeholder: "Year 1",
}),
{
@@ -170,18 +203,44 @@ module.exports = {
objectList(
"options",
"Calculator options",
[
hidden("id"),
text("label", "Option label", { maxLength: 12 }),
text("paceLabel", "Pace label", { maxLength: 30 }),
text("minPaceLabel", "Minimum pace label", { maxLength: 20 }),
text("maxPaceLabel", "Maximum pace label", { maxLength: 20 }),
text("resultLabel", "Result label", { maxLength: 40 }),
text("monthlyAmount", "Monthly amount", { maxLength: 20 }),
text("monthlySuffix", "Monthly suffix", { maxLength: 10 }),
icon("noteIcon", "Note icon"),
text("note", "Note text", { maxLength: 60 }),
],
[
hidden("id"),
text("label", "Option label", {
maxLength: 12,
helpText: "This label appears in the calculator option switcher.",
}),
text("paceLabel", "Pace label", {
maxLength: 20,
helpText: "This appears above the pace slider.",
}),
text("minPaceLabel", "Minimum pace label", {
maxLength: 7,
helpText: "This appears on the left side of the pace slider.",
}),
text("maxPaceLabel", "Maximum pace label", {
maxLength: 7,
helpText: "This appears on the right side of the pace slider.",
}),
text("resultLabel", "Result label", {
maxLength: 40,
helpText: "This label appears above the calculated amount.",
}),
text("monthlyAmount", "Monthly amount", {
maxLength: 20,
helpText: "Enter digits only. The currency symbol is added on the website automatically.",
}),
text("monthlySuffix", "Monthly suffix", {
maxLength: 10,
helpText: "Example: /mo",
}),
icon("noteIcon", "Note icon", {
helpText: "Choose the icon shown beside the note.",
}),
text("note", "Note text", {
maxLength: 60,
helpText: "This short note appears under the amount.",
}),
],
{
itemLabel: "Option",
sortable: true,
@@ -210,8 +269,8 @@ module.exports = {
"items",
"Scholarship items",
[
text("title", "Title", { maxLength: 50 }),
text("amount", "Amount", { maxLength: 24 }),
text("title", "Title", { maxLength: 40 }),
text("amount", "Amount", { maxLength: 12 }),
textarea("description", "Description", {
maxLength: 160,
rows: 3,