From 49713d957d4c16601a2b35e354c957828cd28a84 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Thu, 2 Apr 2026 15:19:08 +0200 Subject: [PATCH] feat(dashboard): enhance widget availability and initialization - Updated `getAvailableWidgets` method in `DashboardService` to merge default widget types with database entries, ensuring immediate visibility of new widgets post-deployment. - Introduced `DASHBOARD_WIDGET_TYPE_DEFAULTS` in `initializeWidgetTypes` for canonical widget types, facilitating API merging when database entries are absent. - Modified `StatusBar.vue` to utilize a computed property for quick access children, improving menu item handling and visibility based on user context. - Enhanced `LoggedInView.vue` to dynamically return localized widget labels, improving user experience with accurate translations. --- backend/services/dashboardService.js | 54 ++++++++++++++++--- backend/sql/seed_dashboard_widget_types.sql | 22 ++++++++ backend/utils/initializeWidgetTypes.js | 8 ++- .../src/components/falukant/StatusBar.vue | 24 ++++++--- frontend/src/views/home/LoggedInView.vue | 7 ++- 5 files changed, 95 insertions(+), 20 deletions(-) create mode 100644 backend/sql/seed_dashboard_widget_types.sql diff --git a/backend/services/dashboardService.js b/backend/services/dashboardService.js index b3277ec..ee68b78 100644 --- a/backend/services/dashboardService.js +++ b/backend/services/dashboardService.js @@ -1,24 +1,62 @@ import BaseService from './BaseService.js'; import UserDashboard from '../models/community/user_dashboard.js'; import WidgetType from '../models/type/widget_type.js'; +import { DASHBOARD_WIDGET_TYPE_DEFAULTS } from '../utils/initializeWidgetTypes.js'; class DashboardService extends BaseService { /** * Liste aller möglichen (verfügbaren) Widget-Typen. - * @returns {Promise>} + * Merge: Code-Defaults (immer) + DB-Zeilen; fehlende Defaults erscheinen mit synthetischer id, + * damit neue Widgets nach Deploy sofort unter „Widget hinzufügen“ sichtbar sind. + * @returns {Promise>} */ async getAvailableWidgets() { const rows = await WidgetType.findAll({ order: [['orderId', 'ASC'], ['id', 'ASC']], attributes: ['id', 'label', 'endpoint', 'description', 'orderId'] }); - return rows.map(r => ({ - id: r.id, - label: r.label, - endpoint: r.endpoint, - description: r.description ?? null, - orderId: r.orderId - })); + const dbByEndpoint = new Map(rows.map((r) => [r.endpoint, r])); + const defaultEndpoints = new Set(DASHBOARD_WIDGET_TYPE_DEFAULTS.map((d) => d.endpoint)); + + const sortedDefaults = [...DASHBOARD_WIDGET_TYPE_DEFAULTS].sort( + (a, b) => (a.orderId || 0) - (b.orderId || 0) + ); + + const ordered = []; + for (const def of sortedDefaults) { + const existing = dbByEndpoint.get(def.endpoint); + if (existing) { + ordered.push({ + id: existing.id, + label: existing.label, + endpoint: existing.endpoint, + description: existing.description ?? null, + orderId: existing.orderId + }); + } else { + ordered.push({ + id: `ep:${def.endpoint}`, + label: def.label, + endpoint: def.endpoint, + description: def.description ?? null, + orderId: def.orderId ?? 0 + }); + } + } + + for (const r of rows) { + if (!defaultEndpoints.has(r.endpoint)) { + ordered.push({ + id: r.id, + label: r.label, + endpoint: r.endpoint, + description: r.description ?? null, + orderId: r.orderId + }); + } + } + + return ordered; } /** diff --git a/backend/sql/seed_dashboard_widget_types.sql b/backend/sql/seed_dashboard_widget_types.sql new file mode 100644 index 0000000..f3a9dad --- /dev/null +++ b/backend/sql/seed_dashboard_widget_types.sql @@ -0,0 +1,22 @@ +-- Dashboard-Widget-Typen (type.widget_type) +-- Für Produktion ohne initializeWidgetTypes / ohne Deploy-Sync. +-- Idempotent: pro endpoint nur einfügen, wenn noch keine Zeile existiert. +-- +-- Muss mit backend/utils/initializeWidgetTypes.js (DASHBOARD_WIDGET_TYPE_DEFAULTS) +-- übereinstimmen – bei neuen Widgets beides anpassen. + +INSERT INTO type.widget_type (label, endpoint, description, order_id) +SELECT v.label, v.endpoint, v.description, v.order_id +FROM ( + VALUES + ('Termine'::text, '/api/termine'::text, 'Bevorstehende Termine'::text, 1), + ('Falukant', '/api/falukant/dashboard-widget', 'Charakter, Geld, Nachrichten, Kinder', 2), + ('News', '/api/news?language=de&category=top', 'Nachrichten (newsdata.io), Counter für Pagination', 3), + ('Geburtstage', '/api/calendar/widget/birthdays', 'Nächste Geburtstage von Freunden', 4), + ('Nächste Termine', '/api/calendar/widget/upcoming', 'Anstehende Kalendertermine', 5), + ('Kalender', '/api/calendar/widget/mini', 'Mini-Kalenderansicht', 6), + ('Sprachkurse', '/api/vocab/dashboard-widget', 'Vokabelkurse, aktuelle Lektion, Sprung zur Lektion', 7) +) AS v(label, endpoint, description, order_id) +WHERE NOT EXISTS ( + SELECT 1 FROM type.widget_type wt WHERE wt.endpoint = v.endpoint +); diff --git a/backend/utils/initializeWidgetTypes.js b/backend/utils/initializeWidgetTypes.js index d803008..a907afe 100644 --- a/backend/utils/initializeWidgetTypes.js +++ b/backend/utils/initializeWidgetTypes.js @@ -4,7 +4,8 @@ import WidgetType from '../models/type/widget_type.js'; * Default rows for type.widget_type. Labels/descriptions are German for DB/admin readability; * the web app maps known `endpoint` values to localized strings (see LoggedInView / home.dashboard.widgetLabels). */ -const DEFAULT_WIDGET_TYPES = [ +/** Kanonische Widget-Typen (auch für API-Merge, falls DB noch keine Zeile hat) */ +export const DASHBOARD_WIDGET_TYPE_DEFAULTS = [ { label: 'Termine', endpoint: '/api/termine', description: 'Bevorstehende Termine', orderId: 1 }, { label: 'Falukant', endpoint: '/api/falukant/dashboard-widget', description: 'Charakter, Geld, Nachrichten, Kinder', orderId: 2 }, { label: 'News', endpoint: '/api/news?language=de&category=top', description: 'Nachrichten (newsdata.io), Counter für Pagination', orderId: 3 }, @@ -17,9 +18,12 @@ const DEFAULT_WIDGET_TYPES = [ /** * Stellt die Standard-Widget-Typen in type.widget_type bereit. * Idempotent: vorhandene Einträge werden nicht verändert. + * + * Produktion ohne Sync/Init: manuell backend/sql/seed_dashboard_widget_types.sql ausführen + * (muss dieselben Endpoints wie DASHBOARD_WIDGET_TYPE_DEFAULTS enthalten). */ const initializeWidgetTypes = async () => { - for (const row of DEFAULT_WIDGET_TYPES) { + for (const row of DASHBOARD_WIDGET_TYPE_DEFAULTS) { await WidgetType.findOrCreate({ where: { endpoint: row.endpoint }, defaults: { diff --git a/frontend/src/components/falukant/StatusBar.vue b/frontend/src/components/falukant/StatusBar.vue index 66b54d6..502a689 100644 --- a/frontend/src/components/falukant/StatusBar.vue +++ b/frontend/src/components/falukant/StatusBar.vue @@ -38,11 +38,11 @@ class="statusbar-section statusbar-section--nav" >
-