From 62d8cd7b05585a41bb8b491f76159e33c9d34b91 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Thu, 29 Jan 2026 17:03:32 +0100 Subject: [PATCH] Add dashboard widget functionality: Implement getDashboardWidget method in FalukantService to retrieve compact user data for the dashboard. Update FalukantController and router to expose the new endpoint, and enhance DashboardWidget component to display user-specific information including character name, gender, age, money, unread notifications, and children count. --- backend/controllers/falukantController.js | 1 + backend/routers/falukantRouter.js | 1 + backend/services/falukantService.js | 54 +++++++++++++++++++++ backend/utils/initializeWidgetTypes.js | 3 +- frontend/src/components/DashboardWidget.vue | 48 +++++++++++++++++- 5 files changed, 105 insertions(+), 2 deletions(-) diff --git a/backend/controllers/falukantController.js b/backend/controllers/falukantController.js index 89b3f61..b6ddb7f 100644 --- a/backend/controllers/falukantController.js +++ b/backend/controllers/falukantController.js @@ -231,6 +231,7 @@ class FalukantController { this.getNotifications = this._wrapWithUser((userId) => this.service.getNotifications(userId)); this.getAllNotifications = this._wrapWithUser((userId, req) => this.service.getAllNotifications(userId, req.query.page, req.query.size)); this.markNotificationsShown = this._wrapWithUser((userId) => this.service.markNotificationsShown(userId), { successStatus: 202 }); + this.getDashboardWidget = this._wrapWithUser((userId) => this.service.getDashboardWidget(userId)); this.getUndergroundTargets = this._wrapWithUser((userId) => this.service.getPoliticalOfficeHolders(userId)); this.searchUsers = this._wrapWithUser((userId, req) => { diff --git a/backend/routers/falukantRouter.js b/backend/routers/falukantRouter.js index 2272d05..a85f309 100644 --- a/backend/routers/falukantRouter.js +++ b/backend/routers/falukantRouter.js @@ -11,6 +11,7 @@ router.get('/character/affect', falukantController.getCharacterAffect); router.get('/name/randomfirstname/:gender', falukantController.randomFirstName); router.get('/name/randomlastname', falukantController.randomLastName); router.get('/info', falukantController.getInfo); +router.get('/dashboard-widget', falukantController.getDashboardWidget); router.get('/branches/types', falukantController.getBranchTypes); router.get('/branches/:branch', falukantController.getBranch); router.get('/branches', falukantController.getBranches); diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index 12c34b2..c512545 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -4748,6 +4748,60 @@ ORDER BY r.id`, return { updated: count }; } + /** + * Kompakte Daten für das Dashboard-Widget (Charakter-Name, Geschlecht, Alter, Geld, ungelesene Nachrichten, Kinder). + * @param {string} hashedUserId + * @returns {Promise<{ characterName: string, gender: string|null, age: number|null, money: number, unreadNotificationsCount: number, childrenCount: number }>} + */ + async getDashboardWidget(hashedUserId) { + const falukantUser = await FalukantUser.findOne({ + include: [ + { model: User, as: 'user', attributes: [], where: { hashedId: hashedUserId } }, + { + model: FalukantCharacter, + as: 'character', + attributes: ['id', 'birthdate', 'gender'], + include: [ + { model: FalukantPredefineFirstname, as: 'definedFirstName', attributes: ['name'] }, + { model: FalukantPredefineLastname, as: 'definedLastName', attributes: ['name'] }, + { model: TitleOfNobility, as: 'nobleTitle', attributes: ['labelTr'] } + ] + } + ], + attributes: ['id', 'money'] + }); + if (!falukantUser || !falukantUser.character) { + throw new Error('No Falukant character found for this user'); + } + const character = falukantUser.character; + const firstName = character.definedFirstName?.name ?? ''; + const lastName = character.definedLastName?.name ?? ''; + const title = character.nobleTitle?.labelTr ?? ''; + const characterName = [title, firstName, lastName].filter(Boolean).join(' ') || '—'; + const age = character.birthdate ? calcAge(character.birthdate) : null; + + const [unreadNotificationsCount, childrenCount] = await Promise.all([ + Notification.count({ where: { userId: falukantUser.id, shown: false } }), + ChildRelation.count({ + where: { + [Op.or]: [ + { fatherCharacterId: character.id }, + { motherCharacterId: character.id } + ] + } + }) + ]); + + return { + characterName, + gender: character.gender ?? null, + age, + money: Number(falukantUser.money ?? 0), + unreadNotificationsCount, + childrenCount + }; + } + async getPoliticalOfficeHolders(hashedUserId) { const user = await getFalukantUserOrFail(hashedUserId); const character = await FalukantCharacter.findOne({ diff --git a/backend/utils/initializeWidgetTypes.js b/backend/utils/initializeWidgetTypes.js index 5ae1fb0..ae60bc9 100644 --- a/backend/utils/initializeWidgetTypes.js +++ b/backend/utils/initializeWidgetTypes.js @@ -1,7 +1,8 @@ import WidgetType from '../models/type/widget_type.js'; const DEFAULT_WIDGET_TYPES = [ - { label: 'Termine', endpoint: '/api/termine', description: 'Bevorstehende Termine', orderId: 1 } + { label: 'Termine', endpoint: '/api/termine', description: 'Bevorstehende Termine', orderId: 1 }, + { label: 'Falukant', endpoint: '/api/falukant/dashboard-widget', description: 'Charakter, Geld, Nachrichten, Kinder', orderId: 2 } ]; /** diff --git a/frontend/src/components/DashboardWidget.vue b/frontend/src/components/DashboardWidget.vue index 096e441..f2a1cae 100644 --- a/frontend/src/components/DashboardWidget.vue +++ b/frontend/src/components/DashboardWidget.vue @@ -14,7 +14,23 @@
{{ error }}
-