From 53eecb6f83ff75f1f3c75d68cee5e46d114ac8d1 Mon Sep 17 00:00:00 2001 From: Luca <102960244+Luca-Timo@users.noreply.github.com> Date: Mon, 11 May 2026 20:56:50 +0200 Subject: [PATCH] feat(email-templates): group Templates UI by category with core sub-sections --- frontend/src/i18n/locales/de.json | 9 +- frontend/src/i18n/locales/en.json | 9 +- frontend/src/i18n/locales/fr.json | 9 +- frontend/src/i18n/locales/nl.json | 9 +- frontend/src/i18n/locales/pt.json | 9 +- frontend/src/i18n/locales/ru.json | 9 +- frontend/src/pages/admin/EmailConfigPage.tsx | 174 ++++++++++++------- 7 files changed, 164 insertions(+), 64 deletions(-) diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index 6be29353..24e0e666 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -2235,7 +2235,14 @@ "billing": "Rechnungen" }, "featureOff": "Funktion aus", - "featureOffTooltip": "Die zugehörige Funktion ist derzeit deaktiviert. Du kannst die Vorlage trotzdem bearbeiten — sie wird verwendet, sobald die Funktion wieder aktiviert ist." + "featureOffTooltip": "Die zugehörige Funktion ist derzeit deaktiviert. Du kannst die Vorlage trotzdem bearbeiten — sie wird verwendet, sobald die Funktion wieder aktiviert ist.", + "subcategories": { + "gallery": "Galerien", + "admin": "Admin-Konten", + "backup": "Backup & Wiederherstellung", + "system": "System-Updates", + "other": "Sonstige" + } }, "cms": { "title": "CMS-Seiten", diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 2f12b31a..0749bd25 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -1889,7 +1889,14 @@ "billing": "Billing" }, "featureOff": "Feature off", - "featureOffTooltip": "The feature this template belongs to is currently disabled. You can still edit the template — it will be used once the feature is re-enabled." + "featureOffTooltip": "The feature this template belongs to is currently disabled. You can still edit the template — it will be used once the feature is re-enabled.", + "subcategories": { + "gallery": "Galleries", + "admin": "Admin accounts", + "backup": "Backup & restore", + "system": "System updates", + "other": "Other" + } }, "cms": { "title": "CMS Pages", diff --git a/frontend/src/i18n/locales/fr.json b/frontend/src/i18n/locales/fr.json index 136ed739..adb3d696 100644 --- a/frontend/src/i18n/locales/fr.json +++ b/frontend/src/i18n/locales/fr.json @@ -1835,7 +1835,14 @@ "billing": "Facturation" }, "featureOff": "Fonction désactivée", - "featureOffTooltip": "La fonctionnalité à laquelle appartient ce modèle est actuellement désactivée. Vous pouvez toujours modifier le modèle — il sera utilisé une fois la fonctionnalité réactivée." + "featureOffTooltip": "La fonctionnalité à laquelle appartient ce modèle est actuellement désactivée. Vous pouvez toujours modifier le modèle — il sera utilisé une fois la fonctionnalité réactivée.", + "subcategories": { + "gallery": "Galeries", + "admin": "Comptes administrateurs", + "backup": "Sauvegarde & restauration", + "system": "Mises à jour système", + "other": "Autres" + } }, "cms": { "title": "Pages CMS", diff --git a/frontend/src/i18n/locales/nl.json b/frontend/src/i18n/locales/nl.json index 3a6d59b3..cd2e67c9 100644 --- a/frontend/src/i18n/locales/nl.json +++ b/frontend/src/i18n/locales/nl.json @@ -1889,7 +1889,14 @@ "billing": "Facturatie" }, "featureOff": "Functie uit", - "featureOffTooltip": "De functie waar deze sjabloon bij hoort, is momenteel uitgeschakeld. U kunt de sjabloon nog steeds bewerken — deze wordt gebruikt zodra de functie weer is ingeschakeld." + "featureOffTooltip": "De functie waar deze sjabloon bij hoort, is momenteel uitgeschakeld. U kunt de sjabloon nog steeds bewerken — deze wordt gebruikt zodra de functie weer is ingeschakeld.", + "subcategories": { + "gallery": "Galerijen", + "admin": "Adminaccounts", + "backup": "Back-up & herstel", + "system": "Systeemupdates", + "other": "Overige" + } }, "cms": { "title": "CMS-pagina's", diff --git a/frontend/src/i18n/locales/pt.json b/frontend/src/i18n/locales/pt.json index 58b99a8d..ac104c16 100644 --- a/frontend/src/i18n/locales/pt.json +++ b/frontend/src/i18n/locales/pt.json @@ -1914,7 +1914,14 @@ "billing": "Faturamento" }, "featureOff": "Recurso desativado", - "featureOffTooltip": "O recurso ao qual este modelo pertence está atualmente desativado. Você ainda pode editar o modelo — ele será usado assim que o recurso for reativado." + "featureOffTooltip": "O recurso ao qual este modelo pertence está atualmente desativado. Você ainda pode editar o modelo — ele será usado assim que o recurso for reativado.", + "subcategories": { + "gallery": "Galerias", + "admin": "Contas de administrador", + "backup": "Backup & restauração", + "system": "Atualizações do sistema", + "other": "Outros" + } }, "cms": { "title": "Páginas CMS", diff --git a/frontend/src/i18n/locales/ru.json b/frontend/src/i18n/locales/ru.json index e8f5744a..666e72c3 100644 --- a/frontend/src/i18n/locales/ru.json +++ b/frontend/src/i18n/locales/ru.json @@ -1939,7 +1939,14 @@ "billing": "Счета" }, "featureOff": "Функция выключена", - "featureOffTooltip": "Связанная функция в данный момент отключена. Вы по-прежнему можете редактировать шаблон — он будет использоваться после повторного включения функции." + "featureOffTooltip": "Связанная функция в данный момент отключена. Вы по-прежнему можете редактировать шаблон — он будет использоваться после повторного включения функции.", + "subcategories": { + "gallery": "Галереи", + "admin": "Учётные записи администраторов", + "backup": "Резервное копирование и восстановление", + "system": "Системные обновления", + "other": "Прочее" + } }, "cms": { "title": "Страницы CMS", diff --git a/frontend/src/pages/admin/EmailConfigPage.tsx b/frontend/src/pages/admin/EmailConfigPage.tsx index aa39412f..cdba818f 100644 --- a/frontend/src/pages/admin/EmailConfigPage.tsx +++ b/frontend/src/pages/admin/EmailConfigPage.tsx @@ -27,11 +27,12 @@ import { SUPPORTED_LANGUAGES } from "../../components/common/LanguageSelector.ts import { useFeatureFlags, type FeatureKey } from '../../contexts/FeatureFlagsContext'; /** - * Template categorisation (migration 098). Order matters — sidebar - * sections render in this sequence. Empty categories are hidden - * automatically. New categories: add the key here, give it an i18n - * label below, set `feature_flag` on templates that should chip - * out when the matching flag is off. No other UI changes required. + * Template categorisation (migration 098). Sidebar sections render + * in this order. Empty categories are hidden automatically. New + * categories: add the key here, give it an i18n label + * (email.categories.), set `feature_flag` on templates that + * should chip out when the matching flag is off. No other UI + * changes required. */ const CATEGORY_ORDER: readonly string[] = [ 'core', @@ -41,6 +42,20 @@ const CATEGORY_ORDER: readonly string[] = [ 'billing', ] as const; +/** + * Sub-categorisation inside `core` (which carries 14 templates and + * deserves its own internal headers). Order is the render sequence. + * Templates whose subcategory isn't in this list fall through to a + * trailing "other" bucket so a forward-compat row never disappears. + * Other top-level categories are flat (no sub-sections) for now. + */ +const CORE_SUBCATEGORY_ORDER: readonly string[] = [ + 'gallery', + 'admin', + 'backup', + 'system', +] as const; + const defaultTemplateKeys = [ { key: 'gallery_created', @@ -741,70 +756,113 @@ export const EmailConfigPage: React.FC = () => { editable, just chip-tagged so the admin knows the feature is dormant. */} {(() => { - const grouped: Record = {}; + // 1. Bucket templates by top-level category (forward-compat: + // unknown categories fall into 'core'). + const byCategory: Record = {}; for (const template of templates) { const cat = CATEGORY_ORDER.includes(template.category || 'core') ? (template.category || 'core') : 'core'; - (grouped[cat] = grouped[cat] || []).push(template); + (byCategory[cat] = byCategory[cat] || []).push(template); } - const visibleSections = CATEGORY_ORDER.filter((c) => grouped[c]?.length); + const visibleCategories = CATEGORY_ORDER.filter((c) => byCategory[c]?.length); + + // 2. Renders a single template button. Pulled out so the + // flat path and the sub-category path share it. + const renderTemplate = (template: EmailTemplate) => { + const templateInfo = defaultTemplateKeys.find((t) => t.key === template.template_key); + const translationCount = getTranslationCount(template); + const enTranslation = template.translations?.en; + const featureOff = template.feature_flag + ? featureFlags[template.feature_flag as FeatureKey] === false + : false; + return ( + + ); + }; return (
- {visibleSections.map((category) => ( -
-

- {t(`email.categories.${category}`, category)} -

-
- {grouped[category].map((template) => { - const templateInfo = defaultTemplateKeys.find((t) => t.key === template.template_key); - const translationCount = getTranslationCount(template); - const enTranslation = template.translations?.en; - const featureOff = template.feature_flag - ? featureFlags[template.feature_flag as FeatureKey] === false - : false; - return ( - - ); - })} + ))} +
+
+ ); + } + return ( +
+

+ {t(`email.categories.${category}`, category)} +

+
+ {byCategory[category].map(renderTemplate)} +
-
- ))} + ); + })} ); })()}