Refactor DashboardWidget to use dynamic widget components: Replace static slot content with a dynamic component rendering based on the endpoint prop. This change simplifies the widget structure and enhances flexibility by allowing different widget types to be displayed. Additionally, update error handling to provide more specific error messages.
This commit is contained in:
98
frontend/src/components/widgets/FalukantWidget.vue
Normal file
98
frontend/src/components/widgets/FalukantWidget.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<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>
|
||||
</dl>
|
||||
<span v-else>—</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'FalukantWidget',
|
||||
props: {
|
||||
data: { type: Object, default: null }
|
||||
},
|
||||
computed: {
|
||||
falukantData() {
|
||||
const d = this.data;
|
||||
if (d && typeof d === 'object' && 'characterName' in d && 'money' in d) return d;
|
||||
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 '—';
|
||||
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 numAge = Number(ageValue);
|
||||
return `${numAge} ${this.$t('falukant.overview.metadata.years')}`;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
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;
|
||||
}
|
||||
</style>
|
||||
92
frontend/src/components/widgets/ListWidget.vue
Normal file
92
frontend/src/components/widgets/ListWidget.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<ul v-if="items.length" class="dashboard-widget__list">
|
||||
<li
|
||||
v-for="(item, i) in items"
|
||||
:key="i"
|
||||
class="dashboard-widget__list-item"
|
||||
>
|
||||
<span v-if="item.datum" class="dashboard-widget__date">{{ formatDatum(item.datum) }}</span>
|
||||
<span v-if="item.titel" class="dashboard-widget__title-text">{{ item.titel }}</span>
|
||||
<span v-else-if="item.label" class="dashboard-widget__title-text">{{ item.label }}</span>
|
||||
<p v-if="item.beschreibung" class="dashboard-widget__desc">{{ item.beschreibung }}</p>
|
||||
</li>
|
||||
</ul>
|
||||
<span v-else>{{ fallbackText }}</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ListWidget',
|
||||
props: {
|
||||
data: { type: [Array, Object], default: null }
|
||||
},
|
||||
computed: {
|
||||
items() {
|
||||
if (!Array.isArray(this.data) || this.data.length === 0) return [];
|
||||
const first = this.data[0];
|
||||
if (first !== null && typeof first === 'object') return this.data;
|
||||
return [];
|
||||
},
|
||||
fallbackText() {
|
||||
if (this.data == null) return '';
|
||||
if (Array.isArray(this.data)) {
|
||||
return this.data.length === 0 ? 'Keine Einträge' : `(${this.data.length} Einträge)`;
|
||||
}
|
||||
if (typeof this.data === 'object') {
|
||||
const keys = Object.keys(this.data);
|
||||
return keys.length === 0 ? '—' : `(${keys.length} Felder)`;
|
||||
}
|
||||
return String(this.data);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatDatum(dateStr) {
|
||||
if (!dateStr) return '';
|
||||
const d = new Date(dateStr);
|
||||
if (Number.isNaN(d.getTime())) return String(dateStr);
|
||||
return d.toLocaleDateString('de-DE', {
|
||||
weekday: 'short',
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dashboard-widget__list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.dashboard-widget__list-item {
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.dashboard-widget__list-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.dashboard-widget__date {
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
color: #666;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.dashboard-widget__title-text {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.dashboard-widget__desc {
|
||||
margin: 4px 0 0 0;
|
||||
font-size: 0.85rem;
|
||||
color: #555;
|
||||
line-height: 1.4;
|
||||
}
|
||||
</style>
|
||||
85
frontend/src/components/widgets/NewsWidget.vue
Normal file
85
frontend/src/components/widgets/NewsWidget.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<article v-if="article" class="dashboard-widget__news-single">
|
||||
<a
|
||||
v-if="article.link"
|
||||
:href="article.link"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="dashboard-widget__news-title"
|
||||
>
|
||||
{{ article.title || '—' }}
|
||||
</a>
|
||||
<span v-else class="dashboard-widget__title-text">{{ article.title || '—' }}</span>
|
||||
<span v-if="article.pubDate" class="dashboard-widget__date">{{ formatNewsDate(article.pubDate) }}</span>
|
||||
<p v-if="article.description" class="dashboard-widget__desc">{{ article.description }}</p>
|
||||
</article>
|
||||
<span v-else>—</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'NewsWidget',
|
||||
props: {
|
||||
data: { type: Object, default: null }
|
||||
},
|
||||
computed: {
|
||||
article() {
|
||||
const d = this.data;
|
||||
if (d && typeof d === 'object' && Array.isArray(d.results) && d.results.length > 0) {
|
||||
return d.results[0];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatNewsDate(dateStr) {
|
||||
if (!dateStr) return '';
|
||||
const d = new Date(dateStr);
|
||||
if (Number.isNaN(d.getTime())) return String(dateStr);
|
||||
return d.toLocaleDateString('de-DE', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.dashboard-widget__news-single {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.dashboard-widget__news-title {
|
||||
font-weight: 600;
|
||||
color: var(--color-primary-orange);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.dashboard-widget__news-title:hover {
|
||||
text-decoration: underline;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.dashboard-widget__title-text {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.dashboard-widget__date {
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
color: #666;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.dashboard-widget__desc {
|
||||
margin: 4px 0 0 0;
|
||||
font-size: 0.85rem;
|
||||
color: #555;
|
||||
line-height: 1.4;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user