- @foreach (var f in filters)
- {
-
-
OpenFilterForm(f)">
-
-
- @f.Bezeichnung
-
-
- Typ: @f.Typ
-
-
- Seriennr.: @f.Seriennummer
-
-
- Zustand: @f.Zustand
-
-
- Letzte Wartung:
- @(f.LetzteWartung?.ToString("dd.MM.yyyy") ?? "–")
-
-
-
+ }
+
+ }
+
+
+ @if (pendingFilters.Any())
+ {
+
+ Offen (@pendingFilters.Count)
+
+
+ @foreach (var f in pendingFilters)
+ {
+
+
OpenFilterForm(f)">
+
+
+ @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