230 lines
8.1 KiB
Vue
230 lines
8.1 KiB
Vue
<template>
|
|
<div class="nobility-view">
|
|
<StatusBar />
|
|
<div class="nobility-content">
|
|
<h2>{{ $t('falukant.nobility.title') }}</h2>
|
|
<SimpleTabs v-model="activeTab" :tabs="tabs" />
|
|
|
|
<!-- OVERVIEW -->
|
|
<div v-if="activeTab === 'overview'">
|
|
<div class="nobility-section">
|
|
<p>
|
|
<strong>
|
|
{{ $t(`falukant.titles.${gender}.${current.labelTr}`) }}
|
|
</strong>
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ADVANCE -->
|
|
<div v-else-if="activeTab === 'advance'">
|
|
<div class="advance-section" v-if="next && next.labelTr">
|
|
<p>
|
|
{{ $t('falukant.nobility.nextTitle') }}:
|
|
<strong>{{ $t(`falukant.titles.${gender}.${next.labelTr}`) }}</strong>
|
|
</p>
|
|
<ul class="prerequisites" v-if="next.requirements && next.requirements.length > 0">
|
|
<li v-for="req in next.requirements" :key="req.titleId">
|
|
{{ formatRequirement(req.requirementType, req.requirementValue) }}
|
|
</li>
|
|
</ul>
|
|
<button v-if="canAdvance" @click="applyAdvance" class="button" :disabled="isAdvancing">
|
|
<span v-if="!isAdvancing">{{ $t('falukant.nobility.advance.confirm') }}</span>
|
|
<span v-else>{{ $t('falukant.nobility.advance.processing') }}</span>
|
|
</button>
|
|
<p v-else-if="nextAdvanceAt" class="cooldown-message">
|
|
{{ $t('falukant.nobility.cooldown', { date: formatDate(nextAdvanceAt) }) }}
|
|
</p>
|
|
</div>
|
|
<div v-else class="advance-section">
|
|
<p style="color: red;">Fehler: Keine nächste Titel-Information verfügbar. Bitte Seite neu laden.</p>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import StatusBar from '@/components/falukant/StatusBar.vue';
|
|
import SimpleTabs from '@/components/SimpleTabs.vue';
|
|
import apiClient from '@/utils/axios.js';
|
|
import { mapState } from 'vuex';
|
|
|
|
export default {
|
|
name: 'NobilityView',
|
|
components: { StatusBar, SimpleTabs },
|
|
data() {
|
|
return {
|
|
activeTab: 'overview',
|
|
tabs: [
|
|
{ value: 'overview', label: 'falukant.nobility.tabs.overview' },
|
|
{ value: 'advance', label: 'falukant.nobility.tabs.advance' }
|
|
],
|
|
current: { labelTr: '', requirements: [], charactersWithNobleTitle: [] },
|
|
next: { labelTr: '', requirements: [] },
|
|
nextAdvanceAt: null,
|
|
isAdvancing: false
|
|
};
|
|
},
|
|
computed: {
|
|
...mapState(['socket', 'falukantData']),
|
|
gender() {
|
|
return this.current.charactersWithNobleTitle[0]?.gender || 'male';
|
|
},
|
|
canAdvance() {
|
|
if (!this.next) return false;
|
|
if (!this.nextAdvanceAt) return true;
|
|
const now = new Date();
|
|
const nextDate = new Date(this.nextAdvanceAt);
|
|
return nextDate <= now;
|
|
}
|
|
},
|
|
async mounted() {
|
|
await this.loadNobility();
|
|
this.setupSocketEvents();
|
|
},
|
|
beforeUnmount() {
|
|
if (this.socket) {
|
|
this.socket.off('falukantUpdateStatus', this.loadNobility);
|
|
}
|
|
},
|
|
methods: {
|
|
setupSocketEvents() {
|
|
if (this.socket) {
|
|
this.socket.on('falukantUpdateStatus', (data) => {
|
|
this.handleEvent({ event: 'falukantUpdateStatus', ...data });
|
|
});
|
|
} else {
|
|
setTimeout(() => this.setupSocketEvents(), 1000);
|
|
}
|
|
},
|
|
handleEvent(eventData) {
|
|
switch (eventData.event) {
|
|
case 'falukantUpdateStatus':
|
|
this.loadNobility();
|
|
break;
|
|
}
|
|
},
|
|
async loadNobility() {
|
|
try {
|
|
const { data } = await apiClient.get('/api/falukant/nobility');
|
|
this.current = data.current || { labelTr: '', requirements: [], charactersWithNobleTitle: [] };
|
|
this.next = data.next || { labelTr: '', requirements: [] };
|
|
this.nextAdvanceAt = data.nextAdvanceAt || null;
|
|
} catch (err) {
|
|
console.error('Error loading nobility:', err);
|
|
}
|
|
},
|
|
async applyAdvance() {
|
|
if (!this.canAdvance || this.isAdvancing) return;
|
|
this.isAdvancing = true;
|
|
try {
|
|
await apiClient.post('/api/falukant/nobility');
|
|
await this.loadNobility();
|
|
} catch (err) {
|
|
console.error('Error advancing nobility:', err);
|
|
const resp = err?.response;
|
|
if (resp?.status === 412) {
|
|
if (resp.data?.message === 'nobilityTooSoon') {
|
|
const retryAtIso = resp.data?.retryAt;
|
|
const retryStr = retryAtIso ? this.formatDate(retryAtIso) : '';
|
|
const msg = this.$t('falukant.nobility.errors.tooSoon');
|
|
this.$root.$refs.errorDialog?.open(retryStr ? `${msg} — ${this.$t('falukant.nobility.cooldown', { date: retryStr })}` : msg);
|
|
} else if (resp.data?.message === 'nobilityRequirements') {
|
|
const unmet = resp.data?.unmet || [];
|
|
const items = unmet.map(u => this.formatRequirement(u.type, u.required)).join('\n');
|
|
const base = this.$t('falukant.nobility.errors.unmet');
|
|
this.$root.$refs.errorDialog?.open(`${base}\n${items}`);
|
|
} else {
|
|
this.$root.$refs.errorDialog?.open('tr:falukant.nobility.errors.generic');
|
|
}
|
|
} else {
|
|
this.$root.$refs.errorDialog?.open('tr:falukant.nobility.errors.generic');
|
|
}
|
|
} finally {
|
|
this.isAdvancing = false;
|
|
}
|
|
},
|
|
handleDaemonMessage(evt) {
|
|
if (evt.data === 'ping') return;
|
|
const msg = JSON.parse(evt.data);
|
|
if (['nobilityChange', 'moneyChange'].includes(msg.event)) this.loadNobility();
|
|
},
|
|
formatCost(val) {
|
|
return new Intl.NumberFormat(navigator.language, { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(val);
|
|
},
|
|
formatRequirement(type, rawValue) {
|
|
const numericValue = Number(rawValue);
|
|
const amount = ['money', 'cost'].includes(type)
|
|
? this.formatCost(numericValue)
|
|
: rawValue;
|
|
const key = `falukant.nobility.requirement.${type}`;
|
|
const translated = this.$t(key, { amount });
|
|
if (translated && translated !== key) {
|
|
return translated;
|
|
}
|
|
switch (type) {
|
|
case 'money':
|
|
return `Vermögen mindestens ${amount}`;
|
|
case 'cost':
|
|
return `Kosten: ${amount}`;
|
|
case 'branches':
|
|
return `Mindestens ${amount} Niederlassungen`;
|
|
case 'reputation':
|
|
return `Beliebtheit mindestens ${amount}`;
|
|
case 'house_position':
|
|
return `Hausstand mindestens Stufe ${amount}`;
|
|
case 'house_condition':
|
|
return `Hauszustand mindestens ${amount}`;
|
|
case 'office_rank_any':
|
|
return `Höchstes politisches oder kirchliches Amt mindestens Rang ${amount}`;
|
|
case 'lover_count_max':
|
|
return `Höchstens ${amount} Liebhaber oder Mätressen`;
|
|
default:
|
|
return `${type}: ${amount}`;
|
|
}
|
|
},
|
|
formatDate(isoString) {
|
|
const d = new Date(isoString);
|
|
const now = new Date();
|
|
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
const dateOnly = new Date(d.getFullYear(), d.getMonth(), d.getDate());
|
|
|
|
// Wenn das Datum heute ist oder in der Zukunft liegt, zeige auch die Uhrzeit
|
|
if (dateOnly.getTime() >= today.getTime()) {
|
|
return d.toLocaleString(navigator.language, {
|
|
year: 'numeric',
|
|
month: 'numeric',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
});
|
|
}
|
|
// Ansonsten nur das Datum
|
|
return d.toLocaleDateString();
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
h2 { padding-top: 20px; }
|
|
.nobility-section,
|
|
.advance-section {
|
|
border: 1px solid #ccc;
|
|
border-radius: 4px;
|
|
margin: 10px 0;
|
|
padding: 10px;
|
|
}
|
|
.prerequisites {
|
|
list-style: disc inside;
|
|
margin-bottom: 1rem;
|
|
}
|
|
.cooldown-message {
|
|
color: #666;
|
|
font-style: italic;
|
|
margin-top: 1rem;
|
|
}
|
|
</style>
|