Refactor dashboard widget management: Update dashboardService to handle user-specific widget configurations with create and update logic. Enhance LoggedInView to support adding the same widget type and display error messages for save operations. Ensure effective endpoint handling for widgets and improve UI interactions.

This commit is contained in:
Torsten Schulz (local)
2026-01-30 07:31:38 +01:00
parent 39ac149430
commit 4779a6e4af
4 changed files with 114 additions and 48 deletions

View File

@@ -28,6 +28,14 @@
{{ wt.label }}
</option>
</select>
<button
v-if="selectedWidgetTypeId"
type="button"
class="btn-add-again"
@click="addSameWidgetType"
>
Nochmal hinzufügen
</button>
</div>
<button type="button" class="btn-done" @click="doneEditing">
Fertig
@@ -42,6 +50,12 @@
>
{{ loadError }}
</div>
<div
v-if="saveError"
class="dashboard-message dashboard-error"
>
{{ saveError }}
</div>
<div
v-else
class="dashboard-grid"
@@ -60,7 +74,7 @@
v-if="!editMode"
:widget-id="w.id"
:title="w.title"
:endpoint="w.endpoint"
:endpoint="effectiveEndpoint(w)"
:request-counter="widgetRequestCounter(index)"
@drag-start="() => (draggedIndex = index)"
@drag-end="() => (draggedIndex = null)"
@@ -73,12 +87,6 @@
placeholder="Titel"
class="widget-edit-input"
/>
<input
v-model="w.endpoint"
type="text"
placeholder="Endpoint (z. B. /api/termine)"
class="widget-edit-input widget-edit-endpoint"
/>
</div>
<button
type="button"
@@ -129,6 +137,7 @@ export default {
selectedWidgetTypeId: '',
loading: true,
loadError: null,
saveError: null,
editMode: false,
draggedIndex: null,
dragOverIndex: null
@@ -139,13 +148,19 @@ export default {
this.loadAvailableWidgets();
},
methods: {
/** Endpoint aus Widget-Typ (anhand gespeichertem endpoint gematcht), sonst w.endpoint. */
effectiveEndpoint(w) {
if (!w?.endpoint) return '';
const t = this.availableWidgets.find(wt => wt.endpoint === w.endpoint);
return t ? t.endpoint : w.endpoint;
},
/** 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;
const endpoint = this.effectiveEndpoint(this.widgets[index]);
if (!endpoint) return undefined;
let count = 0;
for (let i = 0; i < index; i++) {
if (this.widgets[i].endpoint === endpoint) count++;
if (this.effectiveEndpoint(this.widgets[i]) === endpoint) count++;
}
return count;
},
@@ -177,33 +192,52 @@ export default {
}
},
async saveConfig() {
this.saveError = null;
try {
await apiClient.put('/api/dashboard/config', { widgets: this.widgets });
const payload = this.widgets.map(w => ({
id: w.id,
title: w.title,
endpoint: this.effectiveEndpoint(w)
}));
await apiClient.put('/api/dashboard/config', { widgets: payload });
} catch (e) {
console.error('Dashboard speichern fehlgeschlagen:', e);
this.saveError = e.response?.data?.error || e.message || 'Dashboard konnte nicht gespeichert werden.';
}
},
onSelectWidgetType() {
addWidgetFromType(wt) {
this.widgets.push({
id: generateId(),
title: wt.label,
endpoint: wt.endpoint
});
},
async onSelectWidgetType() {
const id = this.selectedWidgetTypeId;
if (!id) return;
const wt = this.widgetTypeOptions.find(w => String(w.id) === String(id));
if (wt) {
this.widgets.push({
id: generateId(),
title: wt.label,
endpoint: wt.endpoint
});
this.saveConfig();
this.addWidgetFromType(wt);
await this.saveConfig();
}
this.selectedWidgetTypeId = '';
},
removeWidget(index) {
this.widgets.splice(index, 1);
this.saveConfig();
async addSameWidgetType() {
const id = this.selectedWidgetTypeId;
if (!id) return;
const wt = this.widgetTypeOptions.find(w => String(w.id) === String(id));
if (wt) {
this.addWidgetFromType(wt);
await this.saveConfig();
}
},
doneEditing() {
async removeWidget(index) {
this.widgets.splice(index, 1);
await this.saveConfig();
},
async doneEditing() {
this.editMode = false;
this.saveConfig();
await this.saveConfig();
},
setDropTarget(index) {
this.dragOverIndex = index;
@@ -214,7 +248,7 @@ export default {
onGridDragover() {
this.dragOverIndex = this.widgets.length;
},
onGridDrop() {
async onGridDrop() {
if (this.draggedIndex == null) return;
const from = this.draggedIndex;
const to = this.widgets.length;
@@ -227,9 +261,9 @@ export default {
this.widgets.splice(to, 0, item);
this.draggedIndex = null;
this.dragOverIndex = null;
this.saveConfig();
await this.saveConfig();
},
onDrop(toIndex) {
async onDrop(toIndex) {
if (this.draggedIndex == null) return;
const from = this.draggedIndex;
const to = toIndex;
@@ -242,7 +276,7 @@ export default {
this.widgets.splice(to, 0, item);
this.draggedIndex = null;
this.dragOverIndex = null;
this.saveConfig();
await this.saveConfig();
}
}
};
@@ -297,6 +331,22 @@ export default {
.widget-add-row {
display: flex;
align-items: center;
gap: 10px;
}
.btn-add-again {
padding: 8px 14px;
border-radius: 4px;
border: 1px solid var(--color-text-secondary);
background: #fff;
color: var(--color-text-primary);
font-size: 0.9rem;
cursor: pointer;
}
.btn-add-again:hover {
background: var(--color-primary-orange-light);
border-color: var(--color-primary-orange);
}
.widget-type-select {
@@ -361,11 +411,6 @@ export default {
font-size: 0.9rem;
}
.widget-edit-endpoint {
font-family: monospace;
font-size: 0.85rem;
}
.btn-remove {
align-self: flex-start;
padding: 6px 12px;