Implement job hierarchy and region depth calculations in FalukantService; enhance PoliticsView with own position highlighting

- Added a job hierarchy mapping to determine positions based on their rank.
- Introduced asynchronous region depth calculations to determine the hierarchy of regions.
- Updated the mapping of office data to include job hierarchy levels and region depths.
- Enhanced the PoliticsView to highlight the user's own positions with a distinct style.
- Implemented a method to load the user's character ID for position comparison.
This commit is contained in:
Torsten Schulz (local)
2026-01-16 16:25:22 +01:00
parent d3629a8a09
commit 59c05b3628
2 changed files with 129 additions and 5 deletions

View File

@@ -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 }); const o = office.get({ plain: true });
// Enddatum der Amtszeit berechnen: Start = createdAt, Dauer = termLength Jahre // Enddatum der Amtszeit berechnen: Start = createdAt, Dauer = termLength Jahre
@@ -4953,20 +5008,64 @@ class FalukantService extends BaseService {
name: o.type?.name name: o.type?.name
}, },
region: { region: {
id: o.region?.id,
name: o.region?.name, name: o.region?.name,
regionType: o.region?.regionType regionType: o.region?.regionType
? { labelTr: o.region.regionType.labelTr } ? { labelTr: o.region.regionType.labelTr }
: undefined : undefined,
depth: regionDepths.get(o.region?.id) || 0
}, },
character: o.holder character: o.holder
? { ? {
id: o.holder.id,
definedFirstName: o.holder.definedFirstName, definedFirstName: o.holder.definedFirstName,
definedLastName: o.holder.definedLastName, definedLastName: o.holder.definedLastName,
nobleTitle: o.holder.nobleTitle, nobleTitle: o.holder.nobleTitle,
gender: o.holder.gender gender: o.holder.gender
} }
: null, : 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
}
}; };
}); });
} }

View File

@@ -23,7 +23,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="pos in currentPositions" :key="pos.id"> <tr v-for="pos in currentPositions" :key="pos.id" :class="{ 'own-position': isOwnPosition(pos) }">
<td>{{ $t(`falukant.politics.offices.${pos.officeType.name}`) }}</td> <td>{{ $t(`falukant.politics.offices.${pos.officeType.name}`) }}</td>
<td>{{ pos.region.name }}</td> <td>{{ pos.region.name }}</td>
<td> <td>
@@ -193,6 +193,7 @@ export default {
elections: [], elections: [],
selectedCandidates: {}, selectedCandidates: {},
selectedApplications: [], selectedApplications: [],
ownCharacterId: null,
loading: { loading: {
current: false, current: false,
openPolitics: false, openPolitics: false,
@@ -209,7 +210,8 @@ export default {
return this.elections.some(e => !e.voted); return this.elections.some(e => !e.voted);
} }
}, },
mounted() { async mounted() {
await this.loadOwnCharacterId();
this.loadCurrentPositions(); this.loadCurrentPositions();
}, },
methods: { 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() { async submitApplications() {
try { try {
const response = await apiClient.post( const response = await apiClient.post(
@@ -411,6 +431,11 @@ h2 {
border: 1px solid #ddd; border: 1px solid #ddd;
} }
.politics-table tbody tr.own-position {
background-color: #e0e0e0;
font-weight: bold;
}
.loading { .loading {
text-align: center; text-align: center;
font-style: italic; font-style: italic;