Add news widget functionality: Integrate newsRouter for fetching news data, update initializeWidgetTypes to include news endpoint, and enhance DashboardWidget component to display news articles with pagination support. Update LoggedInView to manage widget request counters for unique endpoint handling.

This commit is contained in:
Torsten Schulz (local)
2026-01-29 17:20:06 +01:00
parent 62d8cd7b05
commit e8c6f6ffb9
7 changed files with 188 additions and 14 deletions

View File

@@ -14,19 +14,29 @@
<div v-else-if="error" class="dashboard-widget__state dashboard-widget__error">{{ error }}</div>
<div v-else class="dashboard-widget__body">
<slot :data="data">
<template v-if="falukantData">
<template v-else-if="newsDataResults.length">
<ul class="dashboard-widget__list">
<li v-for="(item, i) in newsDataResults" :key="item.article_id || i" class="dashboard-widget__list-item">
<a v-if="item.link" :href="item.link" target="_blank" rel="noopener noreferrer" class="dashboard-widget__news-title">{{ item.title || '' }}</a>
<span v-else class="dashboard-widget__title-text">{{ item.title || '—' }}</span>
<span v-if="item.pubDate" class="dashboard-widget__date">{{ formatNewsDate(item.pubDate) }}</span>
<p v-if="item.description" class="dashboard-widget__desc">{{ item.description }}</p>
</li>
</ul>
</template>
<template v-else-if="falukantData">
<dl class="dashboard-widget__falukant">
<dt>Name</dt>
<dt>{{ $t('falukant.overview.metadata.name') }}</dt>
<dd>{{ falukantData.characterName }}</dd>
<dt>Geschlecht</dt>
<dd>{{ falukantData.gender ?? '—' }}</dd>
<dt>Alter</dt>
<dd>{{ falukantData.age != null ? falukantData.age + ' Tage' : '—' }}</dd>
<dt>Geld</dt>
<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>Ungelesene Nachrichten</dt>
<dt>{{ $t('falukant.messages.title') }}</dt>
<dd>{{ falukantData.unreadNotificationsCount }}</dd>
<dt>Kinder</dt>
<dt>{{ $t('falukant.statusbar.children') }}</dt>
<dd>{{ falukantData.childrenCount }}</dd>
</dl>
</template>
@@ -57,7 +67,9 @@ export default {
title: { type: String, required: true },
endpoint: { type: String, required: true },
/** Wenn true, wird nicht automatisch geladen (z. B. bei Bearbeitung). */
pauseFetch: { type: Boolean, default: false }
pauseFetch: { type: Boolean, default: false },
/** Wievieltes Widget dieser Art (0, 1, 2, …), wird als &counter=N ans EP gehängt (z. B. für News-Pagination). */
requestCounter: { type: Number, default: undefined }
},
emits: ['drag-start', 'drag-end'],
data() {
@@ -69,11 +81,29 @@ export default {
};
},
computed: {
newsDataResults() {
const d = this.data;
if (d && typeof d === 'object' && Array.isArray(d.results)) return d.results;
return [];
},
falukantData() {
const d = this.data;
if (d && typeof d === 'object' && 'characterName' in d && 'money' in d) return d;
return null;
},
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 days = this.falukantData?.age;
if (days == null) return '—';
const years = Math.floor(Number(days) / 365);
return `${years} ${this.$t('admin.createNPCs.years')}`;
},
dataList() {
if (!Array.isArray(this.data) || this.data.length === 0) return [];
const first = this.data[0];
@@ -94,6 +124,7 @@ export default {
},
watch: {
endpoint: { handler: 'fetchData', immediate: false },
requestCounter: { handler: 'fetchData', immediate: false },
pauseFetch: { handler(now) { if (!now) this.fetchData(); } }
},
mounted() {
@@ -105,9 +136,13 @@ export default {
this.loading = true;
this.error = null;
try {
const path = this.endpoint.startsWith('/') ? this.endpoint : `/${this.endpoint}`;
const url = path.startsWith('/api') ? path : `/api${path}`;
const res = await apiClient.get(url);
let path = this.endpoint.startsWith('/') ? this.endpoint : `/${this.endpoint}`;
path = path.startsWith('/api') ? path : `/api${path}`;
if (this.requestCounter !== undefined && this.requestCounter !== null) {
const sep = path.includes('?') ? '&' : '?';
path = `${path}${sep}counter=${Number(this.requestCounter)}`;
}
const res = await apiClient.get(path);
this.data = res.data;
} catch (e) {
this.error = e.response?.data?.message || e.message || 'Fehler beim Laden';
@@ -136,6 +171,12 @@ export default {
const n = Number(value);
if (Number.isNaN(n)) return '—';
return n.toLocaleString('de-DE');
},
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' });
}
}
};
@@ -235,6 +276,15 @@ export default {
color: #333;
}
.dashboard-widget__news-title {
font-weight: 600;
color: #0d6efd;
text-decoration: none;
}
.dashboard-widget__news-title:hover {
text-decoration: underline;
}
.dashboard-widget__desc {
margin: 4px 0 0 0;
font-size: 0.85rem;
@@ -261,4 +311,14 @@ export default {
margin: 0;
font-size: 0.9rem;
}
.dashboard-widget__falukant dd.falukant-gender {
color: #0d6efd;
font-weight: 600;
}
.dashboard-widget__falukant dd.falukant-age {
color: #198754;
font-weight: 600;
}
</style>

View File

@@ -64,6 +64,7 @@
:widget-id="w.id"
:title="w.title"
:endpoint="w.endpoint"
:request-counter="widgetRequestCounter(index)"
@drag-start="() => (draggedIndex = index)"
@drag-end="() => (draggedIndex = null)"
/>
@@ -143,6 +144,16 @@ export default {
},
methods: {
...mapActions(['logout']),
/** Counter für EP: wievieltes Widget mit gleichem Endpoint (0, 1, 2, …), damit z. B. News nicht doppelt. */
widgetRequestCounter(index) {
const endpoint = this.widgets[index]?.endpoint;
if (endpoint == null) return undefined;
let count = 0;
for (let i = 0; i < index; i++) {
if (this.widgets[i].endpoint === endpoint) count++;
}
return count;
},
handleLogout() {
this.logout();
},