diff --git a/backend/services/falukantService.js b/backend/services/falukantService.js index 90f64b4..42c1132 100644 --- a/backend/services/falukantService.js +++ b/backend/services/falukantService.js @@ -4933,7 +4933,62 @@ class FalukantService extends BaseService { ] }); - return offices.map(office => { + // Job-Hierarchie-Ebene (höhere Zahl = höhere Position) + const jobHierarchy = { + 'assessor': 1, + 'councillor': 2, + 'council': 3, + 'beadle': 4, + 'town-clerk': 4, + 'mayor': 5, + 'master-builder': 6, + 'village-major': 7, + 'judge': 8, + 'bailif': 9, + 'taxman': 10, + 'sheriff': 11, + 'consultant': 12, + 'treasurer': 13, + 'hangman': 12, + 'territorial-council': 13, + 'territorial-council-speaker': 14, + 'ruler-consultant': 15, + 'state-administrator': 16, + 'super-state-administrator': 17, + 'governor': 18, + 'ministry-helper': 19, + 'minister': 20, + 'chancellor': 21 + }; + + // Region-Hierarchie-Tiefe berechnen (0 = oberste Parent, höhere Zahl = tiefer) + const regionDepths = new Map(); + const calculateRegionDepth = async (regionId) => { + if (regionDepths.has(regionId)) { + return regionDepths.get(regionId); + } + let depth = 0; + let currentId = regionId; + while (currentId !== null) { + const region = await RegionData.findByPk(currentId, { + attributes: ['parentId'] + }); + if (region && region.parentId) { + depth++; + currentId = region.parentId; + } else { + break; + } + } + regionDepths.set(regionId, depth); + return depth; + }; + + // Alle Region-Tiefen parallel berechnen + const uniqueRegionIds = [...new Set(offices.map(o => o.regionId))]; + await Promise.all(uniqueRegionIds.map(id => calculateRegionDepth(id))); + + const mapped = offices.map(office => { const o = office.get({ plain: true }); // Enddatum der Amtszeit berechnen: Start = createdAt, Dauer = termLength Jahre @@ -4953,20 +5008,64 @@ class FalukantService extends BaseService { name: o.type?.name }, region: { + id: o.region?.id, name: o.region?.name, regionType: o.region?.regionType ? { labelTr: o.region.regionType.labelTr } - : undefined + : undefined, + depth: regionDepths.get(o.region?.id) || 0 }, character: o.holder ? { + id: o.holder.id, definedFirstName: o.holder.definedFirstName, definedLastName: o.holder.definedLastName, nobleTitle: o.holder.nobleTitle, gender: o.holder.gender } : null, - termEnds + termEnds, + jobHierarchyLevel: jobHierarchy[o.type?.name] || 0 + }; + }); + + // Sortierung: 1. Region-Tiefe (aufsteigend, oberste Parent zuerst), 2. Job-Hierarchie (aufsteigend), 3. TermEnds (aufsteigend, frühere zuerst), 4. Vorname, 5. Nachname + mapped.sort((a, b) => { + // 1. Region-Tiefe (aufsteigend) + if (a.region.depth !== b.region.depth) { + return a.region.depth - b.region.depth; + } + // 2. Job-Hierarchie (aufsteigend) + if (a.jobHierarchyLevel !== b.jobHierarchyLevel) { + return a.jobHierarchyLevel - b.jobHierarchyLevel; + } + // 3. TermEnds (aufsteigend, frühere zuerst) + const termA = a.termEnds ? new Date(a.termEnds).getTime() : Infinity; + const termB = b.termEnds ? new Date(b.termEnds).getTime() : Infinity; + if (termA !== termB) { + return termA - termB; + } + // 4. Vorname + const firstNameA = a.character?.definedFirstName?.name || ''; + const firstNameB = b.character?.definedFirstName?.name || ''; + if (firstNameA !== firstNameB) { + return firstNameA.localeCompare(firstNameB); + } + // 5. Nachname + const lastNameA = a.character?.definedLastName?.name || ''; + const lastNameB = b.character?.definedLastName?.name || ''; + return lastNameA.localeCompare(lastNameB); + }); + + // Entferne temporäre Felder vor der Rückgabe + return mapped.map(({ jobHierarchyLevel, ...rest }) => { + const { depth, id, ...regionRest } = rest.region; + return { + ...rest, + region: { + name: regionRest.name, + regionType: regionRest.regionType + } }; }); } diff --git a/frontend/src/views/falukant/PoliticsView.vue b/frontend/src/views/falukant/PoliticsView.vue index 2303524..de2214d 100644 --- a/frontend/src/views/falukant/PoliticsView.vue +++ b/frontend/src/views/falukant/PoliticsView.vue @@ -23,7 +23,7 @@ - + {{ $t(`falukant.politics.offices.${pos.officeType.name}`) }} {{ pos.region.name }} @@ -193,6 +193,7 @@ export default { elections: [], selectedCandidates: {}, selectedApplications: [], + ownCharacterId: null, loading: { current: false, openPolitics: false, @@ -209,7 +210,8 @@ export default { return this.elections.some(e => !e.voted); } }, - mounted() { + async mounted() { + await this.loadOwnCharacterId(); this.loadCurrentPositions(); }, methods: { @@ -330,6 +332,24 @@ export default { }); }, + async loadOwnCharacterId() { + try { + const { data } = await apiClient.get('/api/falukant/info'); + if (data.character && data.character.id) { + this.ownCharacterId = data.character.id; + } + } catch (err) { + console.error('Error loading own character ID', err); + } + }, + + isOwnPosition(pos) { + if (!this.ownCharacterId || !pos.character) { + return false; + } + return pos.character.id === this.ownCharacterId; + }, + async submitApplications() { try { const response = await apiClient.post( @@ -411,6 +431,11 @@ h2 { border: 1px solid #ddd; } +.politics-table tbody tr.own-position { + background-color: #e0e0e0; + font-weight: bold; +} + .loading { text-align: center; font-style: italic;