Files
yourpart3/frontend/src/components/widgets/FalukantWidget.vue

195 lines
6.7 KiB
Vue

<template>
<dl v-if="falukantData" class="dashboard-widget__falukant">
<dt>{{ $t('falukant.overview.metadata.name') }}</dt>
<dd>{{ falukantDisplayName }}</dd>
<dt>{{ $t('falukant.create.gender') }}</dt>
<dd class="falukant-gender">{{ falukantGenderLabel }}</dd>
<dt>{{ $t('falukant.overview.metadata.age') }}</dt>
<dd class="falukant-age">{{ falukantAgeLabel }}</dd>
<dt>{{ $t('falukant.overview.metadata.money') }}</dt>
<dd>{{ formatMoney(falukantData.money) }}</dd>
<dt>{{ $t('falukant.messages.title') }}</dt>
<dd>{{ falukantData.unreadNotificationsCount }}</dd>
<dt>{{ $t('falukant.statusbar.children') }}</dt>
<dd>{{ falukantData.childrenCount }}</dd>
<template v-if="falukantData.debtorsPrison?.active">
<dt>{{ $t('falukant.bank.debtorsPrison.titlePrison') }}</dt>
<dd class="falukant-debt" :class="{ 'falukant-debt--warning': !falukantData.debtorsPrison?.inDebtorsPrison }">
{{ falukantData.debtorsPrison?.inDebtorsPrison
? $t('falukant.bank.debtorsPrison.titlePrison')
: $t('falukant.bank.debtorsPrison.titleWarning') }}
</dd>
</template>
</dl>
<span v-else></span>
</template>
<script>
export default {
name: 'FalukantWidget',
props: {
data: { type: Object, default: null }
},
computed: {
falukantData() {
// normalize incoming API payload: accept both camelCase and snake_case
const raw = this.data;
if (!raw || typeof raw !== 'object') return null;
const pick = (obj, camel, snake) => {
if (camel in obj && obj[camel] !== undefined) return obj[camel];
if (snake in obj && obj[snake] !== undefined) return obj[snake];
return undefined;
};
const normalized = {
characterName: pick(raw, 'characterName', 'character_name') ?? pick(raw, 'nameWithoutTitle', 'name_without_title'),
nameWithoutTitle: pick(raw, 'nameWithoutTitle', 'name_without_title'),
titleLabelTr: pick(raw, 'titleLabelTr', 'title_label_tr'),
gender: pick(raw, 'gender', 'gender'),
age: pick(raw, 'age', 'age'),
money: pick(raw, 'money', 'money'),
unreadNotificationsCount: pick(raw, 'unreadNotificationsCount', 'unread_notifications_count'),
childrenCount: pick(raw, 'childrenCount', 'children_count'),
debtorsPrison: pick(raw, 'debtorsPrison', 'debtors_prison'),
// keep all original keys as fallback for any other usage
...raw
};
// sanity: require at least a name and money
if ((normalized.characterName || normalized.nameWithoutTitle) && (normalized.money !== undefined)) return normalized;
return null;
},
falukantDisplayName() {
const d = this.falukantData;
if (!d) return '—';
const titleKey = d.titleLabelTr;
const gender = d.gender;
const nameWithoutTitle = d.nameWithoutTitle ?? d.characterName;
if (titleKey && gender) {
const key = `falukant.titles.${gender}.${titleKey}`;
const translatedTitle = this.$t(key);
if (translatedTitle !== key) return `${translatedTitle} ${nameWithoutTitle}`.trim();
}
return d.characterName || nameWithoutTitle || '—';
},
falukantGenderLabel() {
const g = this.falukantData?.gender;
if (g == null || g === '') return '—';
// Altersabhängige, (auf Wunsch) altertümlichere Bezeichnungen
const years = this._ageYearsFromWidgetValue(this.falukantData?.age);
const group = years == null ? null : this._getAgeGroupKey(years);
if (group && (g === 'female' || g === 'male')) {
const key = `falukant.genderAge.${g}.${group}`;
const t = this.$t(key);
if (t !== key) return t;
}
// Fallback auf vorhandene Übersetzungen
const key = `falukant.create.${g}`;
const t = this.$t(key);
return t === key ? this.$t(`general.gender.${g}`) || g : t;
},
falukantAgeLabel() {
const ageValue = this.falukantData?.age;
if (ageValue == null) return '—';
const years = this._ageYearsFromWidgetValue(ageValue);
if (years == null) return '—';
return `${years} ${this.$t('falukant.overview.metadata.years')}`;
}
},
methods: {
/**
* Backend liefert für Falukant das Alter als (Spiel-)Tage.
* In diesem Spiel entspricht 1 (Spiel-)Tag einem Jahr, damit die Alterung spielbar schnell ist.
*
* Daher ist der übergebene Tageswert direkt das Alter in (Spiel-)Jahren.
*/
_ageYearsFromWidgetValue(ageValue) {
const n = Number(ageValue);
if (Number.isNaN(n)) return null;
// Spiel-Zeit: 1 Tag = 1 Jahr
const years = Math.floor(n);
return Number.isFinite(years) ? years : null;
},
_getAgeGroupKey(age) {
const a = Number(age);
if (Number.isNaN(a)) return null;
// Pro Sprache konfigurierbare Schwellenwerte aus i18n.
// Format: "key:maxAge|key2:maxAge2|..." (maxAge exklusiv, letzte Gruppe sollte hoch gesetzt sein)
// Achtung: vue-i18n kann Strings mit `|` als Plural/Choice-Format interpretieren.
// Dann würde `$t(...)` nur bis zum ersten `|` liefern (z.B. "toddler:4").
// Deshalb lesen wir den Rohwert direkt aus den registrierten Messages.
const msgAgeGroups = this?.$i18n?.messages?.[this?.$i18n?.locale]?.falukant?.genderAge?.ageGroups;
const raw = typeof msgAgeGroups === 'string' ? msgAgeGroups : this.$t('falukant.genderAge.ageGroups');
const parsed = typeof raw === 'string' ? raw : '';
const rules = parsed.split('|')
.map(part => part.trim())
.filter(Boolean)
.map(part => {
const [key, num] = part.split(':').map(s => (s ?? '').trim());
return { key, max: Number(num) };
})
.filter(r => r.key && !Number.isNaN(r.max))
.sort((x, y) => x.max - y.max);
for (const r of rules) {
if (a < r.max) return r.key;
}
// Fallback, falls Konfig kaputt ist
return 'adult';
},
formatMoney(value) {
const n = Number(value);
if (Number.isNaN(n)) return '—';
return n.toLocaleString('de-DE');
}
}
};
</script>
<style scoped>
.dashboard-widget__falukant {
margin: 0;
display: grid;
grid-template-columns: auto 1fr;
gap: 4px 16px;
align-items: baseline;
}
.dashboard-widget__falukant dt {
margin: 0;
font-weight: 600;
color: #555;
font-size: 0.85rem;
}
.dashboard-widget__falukant dd {
margin: 0;
font-size: 0.9rem;
}
.dashboard-widget__falukant dd.falukant-gender {
color: var(--color-text-secondary);
font-weight: 600;
}
.dashboard-widget__falukant dd.falukant-age {
color: #198754;
font-weight: 600;
}
.dashboard-widget__falukant dd.falukant-debt {
color: #8b2f23;
font-weight: 700;
}
.dashboard-widget__falukant dd.falukant-debt--warning {
color: #9a5a08;
}
</style>