diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index 5f0598e..fdea1dd 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -2472,7 +2472,112 @@ class FalukantService extends BaseService { } async getPoliticsOverview(hashedUserId) { + // Liefert alle aktuell besetzten Ämter im eigenen Gebiet inklusive + // Inhaber und berechnetem Enddatum der Amtszeit. + const user = await getFalukantUserOrFail(hashedUserId); + // Charakter des Users bestimmen (Region ist dort hinterlegt) + const character = await FalukantCharacter.findOne({ + where: { userId: user.id }, + attributes: ['id', 'regionId'] + }); + if (!character) { + return []; + } + + // Alle relevanten Regionen (Region + Eltern) laden + const relevantRegionIds = await this.getRegionAndParentIds(character.regionId); + + // Aktuell besetzte Ämter in diesen Regionen laden + const offices = await PoliticalOffice.findAll({ + where: { + regionId: { + [Op.in]: relevantRegionIds + } + }, + include: [ + { + model: PoliticalOfficeType, + as: 'type', + attributes: ['name', 'termLength'] + }, + { + model: RegionData, + as: 'region', + attributes: ['name'], + include: [ + { + model: RegionType, + as: 'regionType', + attributes: ['labelTr'] + } + ] + }, + { + model: FalukantCharacter, + as: 'holder', + attributes: ['id', 'gender'], + include: [ + { + model: FalukantPredefineFirstname, + as: 'definedFirstName', + attributes: ['name'] + }, + { + model: FalukantPredefineLastname, + as: 'definedLastName', + attributes: ['name'] + }, + { + model: TitleOfNobility, + as: 'nobleTitle', + attributes: ['labelTr'] + } + ] + } + ], + order: [ + [{ model: PoliticalOfficeType, as: 'type' }, 'name', 'ASC'], + [{ model: RegionData, as: 'region' }, 'name', 'ASC'] + ] + }); + + return offices.map(office => { + const o = office.get({ plain: true }); + + // Enddatum der Amtszeit berechnen: Start = createdAt, Dauer = termLength Jahre + let termEnds = null; + if (o.createdAt && o.type && typeof o.type.termLength === 'number') { + const start = new Date(o.createdAt); + if (!Number.isNaN(start.getTime())) { + const end = new Date(start); + end.setFullYear(end.getFullYear() + o.type.termLength); + termEnds = end; + } + } + + return { + id: o.id, + officeType: { + name: o.type?.name + }, + region: { + name: o.region?.name, + regionType: o.region?.regionType + ? { labelTr: o.region.regionType.labelTr } + : undefined + }, + character: o.holder + ? { + definedFirstName: o.holder.definedFirstName, + definedLastName: o.holder.definedLastName, + nobleTitle: o.holder.nobleTitle, + gender: o.holder.gender + } + : null, + termEnds + }; + }); } async getOpenPolitics(hashedUserId) { @@ -2696,9 +2801,14 @@ class FalukantService extends BaseService { endDate: h.endDate })); + const alreadyApplied = (e.candidates || []).some( + c => c.characterId === characterId + ); + return { ...e, - history: matchingHistory + history: matchingHistory, + alreadyApplied }; }); return result; diff --git a/frontend/src/i18n/locales/de/falukant.json b/frontend/src/i18n/locales/de/falukant.json index 7fd2244..548c48d 100644 --- a/frontend/src/i18n/locales/de/falukant.json +++ b/frontend/src/i18n/locales/de/falukant.json @@ -640,7 +640,8 @@ "region": "Region", "date": "Datum", "candidacy": "Kandidatur", - "none": "Keine offenen Positionen." + "none": "Keine offenen Positionen.", + "apply": "Für ausgewählte Positionen kandidieren" }, "upcoming": { "office": "Amt", diff --git a/frontend/src/views/falukant/PoliticsView.vue b/frontend/src/views/falukant/PoliticsView.vue index 504469d..4ff4a3f 100644 --- a/frontend/src/views/falukant/PoliticsView.vue +++ b/frontend/src/views/falukant/PoliticsView.vue @@ -18,6 +18,7 @@ {{ $t('falukant.politics.current.office') }} {{ $t('falukant.politics.current.region') }} {{ $t('falukant.politics.current.holder') }} + {{ $t('falukant.politics.current.termEnds') }} @@ -31,9 +32,15 @@ + + + {{ formatDate(pos.termEnds) }} + + + - {{ $t('falukant.politics.current.none') }} + {{ $t('falukant.politics.current.none') }} @@ -60,8 +67,13 @@ {{ formatDate(e.date) }} - + @@ -222,7 +234,11 @@ export default { try { const { data } = await apiClient.get('/api/falukant/politics/open'); this.openPolitics = data; - this.selectedApplications = []; + // Bereits beworbene Positionen vorselektieren, damit die Checkbox + // sichtbar markiert bleibt. + this.selectedApplications = data + .filter(e => e.alreadyApplied) + .map(e => e.id); } catch (err) { console.error('Error loading open politics', err); } finally {