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 });
// 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
}
};
});
}

View File

@@ -23,7 +23,7 @@
</tr>
</thead>
<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>{{ pos.region.name }}</td>
<td>
@@ -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;