From 73a8ec0e76e3af991f8ce51379bf5a335528ae50 Mon Sep 17 00:00:00 2001 From: Marc Wieland Date: Thu, 6 Nov 2025 21:45:46 +0100 Subject: [PATCH] Neuste fixes --- FilterCair.Client/Layout/MainLayout.razor | 67 ++++++++++++- FilterCair.Client/Pages/Filters.razor | 94 ++++++++++++------ FilterCair.Client/Pages/Home.razor | 4 - .../Services/API/FilterService.cs | 2 +- .../Services/Local/FilterLocalService.cs | 6 +- FilterCair.Client/wwwroot/css/app.css | 49 ++++++++++ FilterCair.Client/wwwroot/js/filterdb.js | 96 +++++++++---------- 7 files changed, 228 insertions(+), 90 deletions(-) diff --git a/FilterCair.Client/Layout/MainLayout.razor b/FilterCair.Client/Layout/MainLayout.razor index 0ad9ff3..9f7b3f4 100644 --- a/FilterCair.Client/Layout/MainLayout.razor +++ b/FilterCair.Client/Layout/MainLayout.razor @@ -3,13 +3,17 @@ @using Microsoft.AspNetCore.Components.WebAssembly.Authentication @inject FilterCair.Client.Services.AppState State +@inject IJSRuntime JS +
+ } + + + @if (pendingFilters.Any()) + { +
+ Offen (@pendingFilters.Count) +
+
+ @foreach (var f in pendingFilters) + { +
+
+
+
+ @f.Bezeichnung +
+

Zustand: @f.Zustand

+

+ Noch nicht bearbeitet +

+
+ +
+
+ } +
+ } } diff --git a/FilterCair.Client/Pages/Home.razor b/FilterCair.Client/Pages/Home.razor index adce32e..bfd7469 100644 --- a/FilterCair.Client/Pages/Home.razor +++ b/FilterCair.Client/Pages/Home.razor @@ -11,10 +11,6 @@

FÜÜS - Filter Überprüf- und Überwachungssoftware

- - @(isOnline ? "🟢 Online" : "🔴 Offline") - diff --git a/FilterCair.Client/Services/API/FilterService.cs b/FilterCair.Client/Services/API/FilterService.cs index adacfe8..beed510 100644 --- a/FilterCair.Client/Services/API/FilterService.cs +++ b/FilterCair.Client/Services/API/FilterService.cs @@ -73,7 +73,7 @@ namespace FilterCair.Client.Services.API // Offline: lokal + Outbox await _local.SaveFiltersAsync(filter.StationId, new[] { filter }.ToList()); - await _local.EnqueueAsync(filter.Id > 0 ? "update" : "add", filter); + await _local.EnqueueAsync(filter.Id > 0 ? "update" : "add", filter.Id, filter); return true; } diff --git a/FilterCair.Client/Services/Local/FilterLocalService.cs b/FilterCair.Client/Services/Local/FilterLocalService.cs index b7c6c1b..9ed7870 100644 --- a/FilterCair.Client/Services/Local/FilterLocalService.cs +++ b/FilterCair.Client/Services/Local/FilterLocalService.cs @@ -39,10 +39,10 @@ namespace FilterCair.Client.Services.Local } } - public async Task EnqueueAsync(string action, FilterModel filter) + public async Task EnqueueAsync(string action, int entityId, FilterModel payload) { - var payload = JsonSerializer.Serialize(filter, JsonOptions); - await _js.InvokeVoidAsync("filterDb.enqueue", action, filter.Id, payload); + var payloadJson = JsonSerializer.Serialize(payload, JsonOptions); + await _js.InvokeVoidAsync("filterDb.enqueue", action, entityId, payloadJson); } private static readonly JsonSerializerOptions JsonOptions = new() diff --git a/FilterCair.Client/wwwroot/css/app.css b/FilterCair.Client/wwwroot/css/app.css index 25fcba6..c7464d9 100644 --- a/FilterCair.Client/wwwroot/css/app.css +++ b/FilterCair.Client/wwwroot/css/app.css @@ -300,4 +300,53 @@ code { transform: translateY(-8px); box-shadow: 0 12px 24px rgba(0,0,0,0.15) !important; border-color: #0d6efd !important; +} + + + +.status-dot { + font-size: 18px; + font-weight: bold; + animation: pulse 2s infinite; + position: relative; + top: -1px; +} + + .status-dot.online { + color: #28a745; + } + + .status-dot.offline { + color: #dc3545; + animation: blink 1.5s infinite; + } + + +@keyframes pulse { + 0% { + opacity: 0.7; + transform: scale(1); + } + + 50% { + opacity: 1; + transform: scale(1.15); + } + + 100% { + opacity: 0.7; + transform: scale(1); + } +} + +@keyframes blink { + 0%, 100% { + opacity: 1; + transform: scale(1); + } + + 50% { + opacity: 0.4; + transform: scale(0.9); + } } \ No newline at end of file diff --git a/FilterCair.Client/wwwroot/js/filterdb.js b/FilterCair.Client/wwwroot/js/filterdb.js index 53c3fdd..59a3bdc 100644 --- a/FilterCair.Client/wwwroot/js/filterdb.js +++ b/FilterCair.Client/wwwroot/js/filterdb.js @@ -1,21 +1,19 @@ // wwwroot/js/filterdb.js - class FilterCairDB extends Dexie { constructor() { super('FilterOfflineDB'); this.version(4).stores({ customers: 'id, name, standort, email, ansprechpartner', stations: 'id, customerId, name, standort', - filters: 'id, stationId, bezzeichnung, typ', - outbox: '++id, action, entity, payload, timestamp' + filters: 'id, stationId, bezzeichnung, typ', + outbox: '++id, action, entityId, payload, timestamp' }); this.customers = this.table('customers'); this.stations = this.table('stations'); - this.filters = this.table('filters'); + this.filters = this.table('filters'); this.outbox = this.table('outbox'); } } - const db = new FilterCairDB(); window.filterDb = { @@ -35,7 +33,7 @@ window.filterDb = { getAll: async () => { try { const customers = await db.customers.toArray(); - console.log('IndexedDB Inhalt:', customers); + console.log('IndexedDB Inhalt:', customers); return JSON.stringify(customers); } catch (err) { console.error('getAll Fehler:', err); @@ -67,9 +65,7 @@ window.filterDb = { saveStations: async (customerId, stationsJson) => { try { const stations = JSON.parse(stationsJson); - // Alte Stationen dieses Kunden löschen await db.stations.where('customerId').equals(customerId).delete(); - // Neue speichern await db.stations.bulkPut(stations); console.log(`IndexedDB: ${stations.length} Stationen für Kunde ${customerId} gespeichert`); return true; @@ -79,7 +75,6 @@ window.filterDb = { } }, - // NEU: Stationen eines Kunden lesen getStations: async (customerId) => { try { const stations = await db.stations.where('customerId').equals(customerId).toArray(); @@ -106,7 +101,6 @@ window.filterDb = { } }, - // NEU: Filter einer Station lesen getFilters: async (stationId) => { try { const filters = await db.filters.where('stationId').equals(stationId).toArray(); @@ -127,16 +121,16 @@ window.filterDb = { } }, - // NEU: Outbox – Änderung speichern enqueue: async (action, entityId, payload) => { try { + const payloadJson = typeof payload === 'string' ? payload : JSON.stringify(payload); await db.outbox.add({ action, - entityId, - payload: JSON.stringify(payload), + entityId: entityId || 0, + payload: payloadJson, timestamp: Date.now() }); - console.log(`Outbox: ${action} für ID ${entityId}`); + console.log(`Outbox: ${action} für ID ${entityId || 'neu'}`); return true; } catch (err) { console.error('enqueue Fehler:', err); @@ -144,7 +138,6 @@ window.filterDb = { } }, - // NEU: Outbox syncen syncOutbox: async () => { if (!navigator.onLine) return false; @@ -153,14 +146,14 @@ window.filterDb = { if (pending.length === 0) return true; console.log(`Sync: ${pending.length} Änderungen`); + const baseUrl = '/api/Filter'; for (const item of pending) { let success = false; - const url = '/api/Filter'; try { if (item.action === 'add') { - const res = await fetch(url, { + const res = await fetch(baseUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: item.payload @@ -168,62 +161,67 @@ window.filterDb = { success = res.ok; if (success) { const newFilter = await res.json(); - await db.filters.put(newFilter); // ID aktualisieren + await db.filters.put(newFilter); + if (item.entityId === 0) { + await db.outbox.delete(item.id); + } } - } - else if (item.action === 'update') { + } else if (item.action === 'update') { const payload = JSON.parse(item.payload); - const res = await fetch(`${url}/${payload.id}`, { + const id = payload.id || item.entityId; + if (!id) { + console.error('Update ohne ID!', item); + await db.outbox.delete(item.id); + continue; + } + + const res = await fetch(`${baseUrl}/${id}`, { method: 'PUT', - headers: { 'Content-Type': 'application/json' }, + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }, body: item.payload }); - success = res.ok; - if (success) { - await db.filters.put(payload); - } - } - if (success) { + if (!res.ok) { + console.error(`PUT ${id} fehlgeschlagen: ${res.status} ${res.statusText}`); + continue; // Nicht löschen – später erneut + } + + success = true; + await db.filters.put(payload); await db.outbox.delete(item.id); + console.log(`Update erfolgreich: ID ${id}`); } } catch (err) { console.error(`Sync Fehler bei ${item.action}:`, err); } + + if (success) { + // Nur bei Erfolg löschen – bereits oben + } } - // Nach Sync: alle Filter neu laden - await window.filterDb.refreshAllFilters(); + // KEIN refreshAllFilters! API hat kein GET /api/Filter + console.log('Sync abgeschlossen – keine globale Aktualisierung'); return true; + } catch (err) { console.error('syncOutbox Fehler:', err); return false; } }, - // NEU: Alle Filter vom Server neu laden - refreshAllFilters: async () => { - try { - const res = await fetch('/api/Filter'); - if (res.ok) { - const filters = await res.json(); - await db.filters.clear(); - await db.filters.bulkPut(filters); - console.log(`Refresh: ${filters.length} Filter aktualisiert`); - } - } catch (err) { - console.error('refreshAllFilters Fehler:', err); - } - }, - - // NEU: Anzahl offene Änderungen getPendingCount: async () => { return await db.outbox.count(); } - - }; -// Online-Status für C# +// SIGNAL: filterDb ist vollständig geladen und bereit +window.filterDbReady = true; +console.log('filterDb bereit!'); + +// Online-Status für C# (falls benötigt) window.navigator = window.navigator || {}; window.navigator.onLine = () => navigator.onLine; \ No newline at end of file