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:
@@ -13,43 +13,7 @@
|
||||
<div v-if="loading" class="dashboard-widget__state">Laden…</div>
|
||||
<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="newsDataSingleItem">
|
||||
<article class="dashboard-widget__news-single">
|
||||
<a v-if="newsDataSingleItem.link" :href="newsDataSingleItem.link" target="_blank" rel="noopener noreferrer" class="dashboard-widget__news-title">{{ newsDataSingleItem.title || '—' }}</a>
|
||||
<span v-else class="dashboard-widget__title-text">{{ newsDataSingleItem.title || '—' }}</span>
|
||||
<span v-if="newsDataSingleItem.pubDate" class="dashboard-widget__date">{{ formatNewsDate(newsDataSingleItem.pubDate) }}</span>
|
||||
<p v-if="newsDataSingleItem.description" class="dashboard-widget__desc">{{ newsDataSingleItem.description }}</p>
|
||||
</article>
|
||||
</template>
|
||||
<template v-else-if="falukantData">
|
||||
<dl 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>
|
||||
</template>
|
||||
<template v-else-if="dataList.length">
|
||||
<ul class="dashboard-widget__list">
|
||||
<li v-for="(item, i) in dataList" :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>
|
||||
</template>
|
||||
<template v-else>{{ defaultContent }}</template>
|
||||
</slot>
|
||||
<component :is="widgetComponent" :data="data" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -58,16 +22,26 @@
|
||||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import FalukantWidget from './widgets/FalukantWidget.vue';
|
||||
import NewsWidget from './widgets/NewsWidget.vue';
|
||||
import ListWidget from './widgets/ListWidget.vue';
|
||||
|
||||
function getWidgetComponent(endpoint) {
|
||||
if (!endpoint || typeof endpoint !== 'string') return ListWidget;
|
||||
const ep = endpoint.toLowerCase();
|
||||
if (ep.includes('falukant')) return FalukantWidget;
|
||||
if (ep.includes('news')) return NewsWidget;
|
||||
return ListWidget;
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'DashboardWidget',
|
||||
components: { FalukantWidget, NewsWidget, ListWidget },
|
||||
props: {
|
||||
widgetId: { type: String, required: true },
|
||||
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 },
|
||||
/** 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'],
|
||||
@@ -85,66 +59,8 @@ export default {
|
||||
isFalukantWidget() {
|
||||
return this.endpoint && String(this.endpoint).includes('falukant');
|
||||
},
|
||||
newsDataResults() {
|
||||
const d = this.data;
|
||||
if (d && typeof d === 'object' && Array.isArray(d.results)) return d.results;
|
||||
return [];
|
||||
},
|
||||
/** Pro News-Widget wird nur ein Artikel angezeigt (erster der Seite; Counter sorgt für unterschiedliche Seiten). */
|
||||
newsDataSingleItem() {
|
||||
const list = this.newsDataResults;
|
||||
return list.length ? list[0] : null;
|
||||
},
|
||||
falukantData() {
|
||||
const d = this.data;
|
||||
if (d && typeof d === 'object' && 'characterName' in d && 'money' in d) return d;
|
||||
return null;
|
||||
},
|
||||
/** Anzeigename: Adelstitel aus falukant.titles.{gender}.{titleLabelTr} + Name. */
|
||||
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);
|
||||
// Backend gibt Tage zurück (calcAge verwendet differenceInDays)
|
||||
// Wenn < 365 Tage: Tage anzeigen, sonst Jahre
|
||||
return `${numAge} ${this.$t('falukant.overview.metadata.years')}`;
|
||||
},
|
||||
dataList() {
|
||||
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 [];
|
||||
},
|
||||
defaultContent() {
|
||||
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);
|
||||
widgetComponent() {
|
||||
return getWidgetComponent(this.endpoint);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -212,7 +128,7 @@ export default {
|
||||
const res = await apiClient.get(path);
|
||||
this.data = res.data;
|
||||
} catch (e) {
|
||||
this.error = e.response?.data?.message || e.message || 'Fehler beim Laden';
|
||||
this.error = e.response?.data?.error || e.message || 'Fehler beim Laden';
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
@@ -222,8 +138,6 @@ export default {
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
e.dataTransfer.setData('text/plain', this.widgetId);
|
||||
e.dataTransfer.setData('application/x-widget-id', this.widgetId);
|
||||
|
||||
// Erstelle ein visuelles Drag-Ghost-Bild
|
||||
const dragGhost = this.$el.cloneNode(true);
|
||||
dragGhost.style.position = 'absolute';
|
||||
dragGhost.style.top = '-9999px';
|
||||
@@ -232,42 +146,18 @@ export default {
|
||||
dragGhost.style.transform = 'rotate(-2deg)';
|
||||
dragGhost.style.pointerEvents = 'none';
|
||||
document.body.appendChild(dragGhost);
|
||||
|
||||
// Setze das Drag-Image mit Offset, damit es unter dem Cursor erscheint
|
||||
const rect = this.$el.getBoundingClientRect();
|
||||
const offsetX = e.clientX - rect.left;
|
||||
const offsetY = e.clientY - rect.top;
|
||||
e.dataTransfer.setDragImage(dragGhost, offsetX, offsetY);
|
||||
|
||||
// Entferne das Ghost-Element nach kurzer Verzögerung
|
||||
setTimeout(() => {
|
||||
if (dragGhost.parentNode) {
|
||||
dragGhost.parentNode.removeChild(dragGhost);
|
||||
}
|
||||
if (dragGhost.parentNode) dragGhost.parentNode.removeChild(dragGhost);
|
||||
}, 0);
|
||||
|
||||
this.$emit('drag-start', { widgetId: this.widgetId, event: e });
|
||||
},
|
||||
onDragEnd() {
|
||||
this.isDragging = false;
|
||||
this.$emit('drag-end');
|
||||
},
|
||||
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' });
|
||||
},
|
||||
formatMoney(value) {
|
||||
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' });
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -286,6 +176,7 @@ export default {
|
||||
overflow: hidden;
|
||||
transition: opacity 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.dashboard-widget.is-dragging {
|
||||
opacity: 0.4;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
||||
@@ -312,6 +203,7 @@ export default {
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.dashboard-widget__drag-handle:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
@@ -337,6 +229,7 @@ export default {
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.dashboard-widget__error {
|
||||
color: #c92a2a;
|
||||
}
|
||||
@@ -345,81 +238,4 @@ export default {
|
||||
font-size: 0.9rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.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__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__desc {
|
||||
margin: 4px 0 0 0;
|
||||
font-size: 0.85rem;
|
||||
color: #555;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.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>
|
||||
|
||||
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