Neuste Version mit offline storage

This commit is contained in:
Marc Wieland 2025-11-06 15:57:11 +01:00
parent d5fe1fc04c
commit 371d8d9058
16 changed files with 686 additions and 153 deletions

View File

@ -73,15 +73,27 @@
protected override async Task OnInitializedAsync()
{
customers = await CustomerService.GetCustomersAsync();
try
{
customers = await CustomerService.GetCustomersAsync();
Console.WriteLine($"UI: {customers?.Count ?? 0} Kunden empfangen");
}
catch (Exception ex)
{
Console.WriteLine($"UI Fehler: {ex.Message}");
customers = new();
}
// Force UI Update
StateHasChanged();
}
private async Task SelectCustomer(CustomerModel c)
{
private async Task SelectCustomer(CustomerModel c)
{
await State.SetCustomerAsync(c.Name);
await State.SetCustomerAsync(c.Name);
Nav.NavigateTo($"/stations/{c.Id}");
}
Nav.NavigateTo($"/stations/{c.Id}");
}
}

View File

@ -92,33 +92,15 @@
private async Task SaveForm()
{
bool success;
if (filter.Id > 0)
{
// Update vorhandenen Filter
success = await FilterService.UpdateFilterAsync(filter);
}
else
{
// Neuer Filter
success = await FilterService.AddFilterAsync(filter);
}
bool success = await FilterService.SaveFilterAsync(filter);
if (success)
{
showToast = true;
StateHasChanged();
await Task.Delay(3000);
showToast = false;
StateHasChanged();
await JS.InvokeVoidAsync("showToast", "✅ Filter erfolgreich gespeichert!");
await JS.InvokeVoidAsync("showToast", "✅ Offline gespeichert wird synchronisiert!", "success");
NavManager.NavigateTo($"/stations/{filter.CustomerId}/filters/{filter.StationId}");
}
else
{
await JS.InvokeVoidAsync("showToast", "Fehler beim Speichern.", "error");
await JS.InvokeVoidAsync("showToast", "Fehler", "error");
}
}
@ -135,24 +117,23 @@
}
protected override async Task OnInitializedAsync()
{
var uri = NavManager.ToAbsoluteUri(NavManager.Uri);
var query = System.Web.HttpUtility.ParseQueryString(uri.Query);
// StationId setzen
if (int.TryParse(query.Get("stationId"), out int stationId))
{
filter.StationId = stationId;
}
var uri = NavManager.ToAbsoluteUri(NavManager.Uri);
var query = System.Web.HttpUtility.ParseQueryString(uri.Query);
// Falls FilterId vorhanden → bestehenden Filter laden
if (int.TryParse(query.Get("filterId"), out int filterId))
{
var existing = await FilterService.GetFilterByIdAsync(filterId);
if (existing != null)
filter = existing;
if (int.TryParse(query.Get("customerId"), out int customerId))
filter.CustomerId = customerId;
if (int.TryParse(query.Get("stationId"), out int stationId))
filter.StationId = stationId;
if (int.TryParse(query.Get("filterId"), out int filterId))
{
var existing = await FilterService.GetFilterByIdAsync(filterId);
if (existing != null)
filter = existing;
}
}
}

View File

@ -29,7 +29,7 @@
@foreach (var f in filters)
{
<div class="col-12 col-md-6 col-lg-4">
<div class="card border-0 shadow-sm h-100">
<div class="card border-0 shadow-sm filter-card h-100" @onclick="() => OpenFilterForm(f)">
<div class="card-body">
<h5 class="fw-semibold text-primary">
<i class="bi bi-funnel me-1"></i> @f.Bezeichnung
@ -82,6 +82,6 @@
private void OpenFilterForm(FilterModel filter)
{
Nav.NavigateTo($"/filterform?filterId={filter.Id}&stationId={stationId}&customerId={customerId}");
Nav.NavigateTo($"/filterform?filterId={filter.Id}&stationId={filter.StationId}&customerId={customerId}");
}
}

View File

@ -1,6 +1,7 @@
using FilterCair.Client;
using FilterCair.Client.Services;
using FilterCair.Client.Services.API;
using FilterCair.Client.Services.Local;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
@ -31,4 +32,8 @@ builder.Services.AddScoped<CustomerService>();
builder.Services.AddScoped<StationService>();
builder.Services.AddScoped<FilterService>();
builder.Services.AddScoped<CustomerLocalService>();
builder.Services.AddScoped<StationLocalService>();
builder.Services.AddScoped<FilterLocalService>();
await builder.Build().RunAsync();

View File

@ -1,5 +1,7 @@
using FilterCair.Shared.Models;
using FilterCair.Client.Services.Local;
using System.Net.Http.Json;
using Microsoft.JSInterop;
namespace FilterCair.Client.Services.API
{
@ -7,69 +9,147 @@ namespace FilterCair.Client.Services.API
{
private readonly HttpClient _http;
private readonly IConfiguration _config;
private readonly CustomerLocalService _local;
private readonly IJSRuntime _js;
private string BaseUrl => _config["Api:BaseUrl"] + "/api/Customer";
public CustomerService(HttpClient http, IConfiguration config)
public CustomerService(
HttpClient http,
IConfiguration config,
CustomerLocalService local,
IJSRuntime js)
{
_http = http;
_config = config;
_local = local;
_js = js;
}
// Online-Status prüfen
private async Task<bool> IsOnlineAsync()
{
try
{
return await _js.InvokeAsync<bool>("navigator.onLine");
}
catch
{
return true; // Fallback: versuchen online
}
}
public async Task<List<CustomerModel>> GetCustomersAsync()
{
bool isOnline = await IsOnlineAsync();
Console.WriteLine($"[CustomerService] Online: {isOnline}");
if (!isOnline)
{
Console.WriteLine("Offline → lade aus IndexedDB");
var local = await _local.GetAllAsync();
Console.WriteLine($"IndexedDB liefert: {local?.Count ?? 0} Kunden");
return local ?? new();
}
try
{
var result = await _http.GetFromJsonAsync<List<CustomerModel>>(BaseUrl);
Console.WriteLine($"API liefert: {result?.Count ?? 0} Kunden");
if (result != null && result.Count > 0)
{
await _local.SaveAllAsync(result);
}
return result ?? new();
}
catch (Exception ex)
{
Console.WriteLine($"❌ Fehler beim Laden der Kunden: {ex.Message}");
return new();
Console.WriteLine($"API-Fehler: {ex.Message}");
Console.WriteLine("Fallback auf IndexedDB");
var fallback = await _local.GetAllAsync();
Console.WriteLine($"Fallback liefert: {fallback?.Count ?? 0} Kunden");
return fallback ?? new();
}
}
public async Task<bool> AddCustomerAsync(CustomerModel customer)
{
try
{
var response = await _http.PostAsJsonAsync(BaseUrl, customer);
return response.IsSuccessStatusCode;
}
catch (Exception ex)
{
Console.WriteLine($"❌ Fehler beim Anlegen: {ex.Message}");
return false;
}
}
bool isOnline = await IsOnlineAsync();
public async Task<bool> DeleteCustomerAsync(int id)
{
try
if (isOnline)
{
var response = await _http.DeleteAsync($"{BaseUrl}/{id}");
return response.IsSuccessStatusCode;
}
catch (Exception ex)
{
Console.WriteLine($"❌ Fehler beim Löschen: {ex.Message}");
return false;
try
{
var response = await _http.PostAsJsonAsync(BaseUrl, customer);
if (response.IsSuccessStatusCode)
{
await _local.AddOrUpdateAsync(customer);
return true;
}
}
catch (Exception ex)
{
Console.WriteLine($"Add online fehlgeschlagen: {ex.Message}");
}
}
// Offline: nur lokal speichern
Console.WriteLine("Offline Kunde nur lokal gespeichert");
await _local.AddOrUpdateAsync(customer);
return true; // "Erfolg" im Offline-Modus
}
public async Task<bool> UpdateCustomerAsync(CustomerModel customer)
{
try
bool isOnline = await IsOnlineAsync();
if (isOnline)
{
var response = await _http.PutAsJsonAsync($"{BaseUrl}/{customer.Id}", customer);
return response.IsSuccessStatusCode;
try
{
var response = await _http.PutAsJsonAsync($"{BaseUrl}/{customer.Id}", customer);
if (response.IsSuccessStatusCode)
{
await _local.AddOrUpdateAsync(customer);
return true;
}
}
catch (Exception ex)
{
Console.WriteLine($"Update online fehlgeschlagen: {ex.Message}");
}
}
catch (Exception ex)
Console.WriteLine("Offline Update nur lokal");
await _local.AddOrUpdateAsync(customer);
return true;
}
public async Task<bool> DeleteCustomerAsync(int id)
{
bool isOnline = await IsOnlineAsync();
if (isOnline)
{
Console.WriteLine($"❌ Fehler beim Aktualisieren: {ex.Message}");
return false;
try
{
var response = await _http.DeleteAsync($"{BaseUrl}/{id}");
if (response.IsSuccessStatusCode)
{
await _local.DeleteAsync(id);
return true;
}
}
catch (Exception ex)
{
Console.WriteLine($"Delete online fehlgeschlagen: {ex.Message}");
}
}
Console.WriteLine("Offline Delete nur lokal");
await _local.DeleteAsync(id);
return true;
}
}
}
}

View File

@ -1,5 +1,7 @@
using System.Net.Http.Json;
using FilterCair.Client.Services.Local;
using FilterCair.Shared.Models;
using Microsoft.JSInterop;
using System.Net.Http.Json;
namespace FilterCair.Client.Services.API
{
@ -7,90 +9,105 @@ namespace FilterCair.Client.Services.API
{
private readonly HttpClient _http;
private readonly IConfiguration _config;
private readonly FilterLocalService _local;
private readonly IJSRuntime _js;
private string BaseUrl => _config["Api:BaseUrl"] + "/api/Filter";
public FilterService(HttpClient http, IConfiguration config)
public FilterService(HttpClient http, IConfiguration config, FilterLocalService local, IJSRuntime js)
{
_http = http;
_config = config;
_http = http; _config = config; _local = local; _js = js;
}
private async Task<bool> IsOnlineAsync()
{
try { return await _js.InvokeAsync<bool>("navigator.onLine"); }
catch { return true; }
}
// Alle Filter einer Station laden
public async Task<List<FilterModel>> GetFiltersByStationAsync(int stationId)
{
bool isOnline = await IsOnlineAsync();
if (!isOnline)
{
Console.WriteLine($"Offline → Filter für Station {stationId} aus IndexedDB");
return await _local.GetFiltersAsync(stationId);
}
try
{
var filters = await _http.GetFromJsonAsync<List<FilterModel>>($"{BaseUrl}/byStation/{stationId}");
if (filters != null) await _local.SaveFiltersAsync(stationId, filters);
return filters ?? new();
}
catch (Exception ex)
{
Console.WriteLine($"❌ Fehler beim Laden der Filter: {ex.Message}");
return new();
Console.WriteLine($"API-Fehler → Fallback: {ex.Message}");
return await _local.GetFiltersAsync(stationId);
}
}
// Filter anlegen
public async Task<bool> AddFilterAsync(FilterModel filter)
public async Task<bool> SaveFilterAsync(FilterModel filter)
{
try
bool isOnline = await IsOnlineAsync();
if (isOnline)
{
var response = await _http.PostAsJsonAsync(BaseUrl, filter);
return response.IsSuccessStatusCode;
}
catch (Exception ex)
{
Console.WriteLine($"❌ Fehler beim Erstellen: {ex.Message}");
return false;
try
{
var response = filter.Id > 0
? await _http.PutAsJsonAsync($"{BaseUrl}/{filter.Id}", filter)
: await _http.PostAsJsonAsync(BaseUrl, filter);
if (response.IsSuccessStatusCode)
{
var result = filter.Id > 0 ? filter : await response.Content.ReadFromJsonAsync<FilterModel>();
if (result != null) await _local.SaveFiltersAsync(filter.StationId, new[] { result }.ToList());
return true;
}
}
catch { }
}
// Offline: lokal + Outbox
await _local.SaveFiltersAsync(filter.StationId, new[] { filter }.ToList());
await _local.EnqueueAsync(filter.Id > 0 ? "update" : "add", filter);
return true;
}
// Filter per QR-Code abrufen
public async Task<FilterModel?> GetFilterByQRCodeAsync(string qrCode)
{
try
{
var result = await _http.GetFromJsonAsync<FilterModel>($"{BaseUrl}/byQr/{qrCode}");
return result;
}
catch (Exception ex)
{
Console.WriteLine($"❌ Fehler beim Laden per QR-Code: {ex.Message}");
return null;
}
}
// Einzelnen Filter per ID laden
public async Task<FilterModel?> GetFilterByIdAsync(int id)
{
bool isOnline = await IsOnlineAsync();
if (!isOnline)
{
// Offline: aus IndexedDB laden
var allFilters = await _local.GetFiltersAsync(0); // 0 = alle Stationen ignorieren
return allFilters.FirstOrDefault(f => f.Id == id);
}
try
{
var result = await _http.GetFromJsonAsync<FilterModel>($"{BaseUrl}/{id}");
return result;
var filter = await _http.GetFromJsonAsync<FilterModel>($"{BaseUrl}/{id}");
if (filter != null)
{
await _local.SaveFiltersAsync(filter.StationId, new[] { filter }.ToList());
}
return filter;
}
catch (Exception ex)
{
Console.WriteLine($"❌ Fehler beim Laden des Filters: {ex.Message}");
return null;
Console.WriteLine($"API-Fehler bei GetFilterByIdAsync: {ex.Message}");
// Fallback auf IndexedDB
var localFilters = await _local.GetFiltersAsync(0);
return localFilters.FirstOrDefault(f => f.Id == id);
}
}
// Filter aktualisieren
public async Task<bool> UpdateFilterAsync(FilterModel filter)
{
try
{
var response = await _http.PutAsJsonAsync($"{BaseUrl}/{filter.Id}", filter);
return response.IsSuccessStatusCode;
}
catch (Exception ex)
{
Console.WriteLine($"❌ Fehler beim Aktualisieren: {ex.Message}");
return false;
}
}
}
}

View File

@ -1,5 +1,8 @@
using System.Net.Http.Json;
using FilterCair.Client.Services.Local;
using FilterCair.Shared.Models;
using Microsoft.JSInterop;
using System.Net.Http.Json;
namespace FilterCair.Client.Services.API
{
@ -7,42 +10,48 @@ namespace FilterCair.Client.Services.API
{
private readonly HttpClient _http;
private readonly IConfiguration _config;
private readonly StationLocalService _local;
private readonly IJSRuntime _js;
private string BaseUrl => _config["Api:BaseUrl"] + "/api/Station";
public StationService(HttpClient http, IConfiguration config)
public StationService(HttpClient http, IConfiguration config, StationLocalService local, IJSRuntime js)
{
_http = http;
_config = config;
_local = local;
_js = js;
}
private async Task<bool> IsOnlineAsync()
{
try { return await _js.InvokeAsync<bool>("navigator.onLine"); }
catch { return true; }
}
// 🔹 Alle Stationen eines Kunden laden
public async Task<List<StationModel>> GetStationsByCustomerAsync(int customerId)
{
bool isOnline = await IsOnlineAsync();
if (!isOnline)
{
Console.WriteLine($"Offline → Stationen für Kunde {customerId} aus IndexedDB");
return await _local.GetStationsAsync(customerId);
}
try
{
var stations = await _http.GetFromJsonAsync<List<StationModel>>($"{BaseUrl}/byCustomer/{customerId}");
if (stations != null && stations.Count > 0)
{
await _local.SaveStationsAsync(customerId, stations);
}
return stations ?? new();
}
catch (Exception ex)
{
Console.WriteLine($"❌ Fehler beim Laden der Stationen: {ex.Message}");
return new();
}
}
// 🔹 Neue Station anlegen
public async Task<bool> AddStationAsync(StationModel station)
{
try
{
var response = await _http.PostAsJsonAsync(BaseUrl, station);
return response.IsSuccessStatusCode;
}
catch (Exception ex)
{
Console.WriteLine($"❌ Fehler beim Erstellen: {ex.Message}");
return false;
Console.WriteLine($"API-Fehler → Fallback auf IndexedDB: {ex.Message}");
return await _local.GetStationsAsync(customerId);
}
}
}
}
}

View File

@ -0,0 +1,68 @@
using FilterCair.Shared.Models;
using Microsoft.JSInterop;
using System.Text.Json;
namespace FilterCair.Client.Services.Local
{
public class CustomerLocalService
{
private readonly IJSRuntime _js;
public CustomerLocalService(IJSRuntime js)
{
_js = js;
}
public async Task SaveAllAsync(List<CustomerModel> customers)
{
var json = JsonSerializer.Serialize(customers, JsonOptions);
try
{
await _js.InvokeVoidAsync("filterDb.saveAll", json);
}
catch (Exception ex)
{
Console.WriteLine($"filterDb.saveAll fehlgeschlagen: {ex.Message}");
}
}
public async Task<List<CustomerModel>> GetAllAsync()
{
try
{
Console.WriteLine("Lese aus IndexedDB...");
var json = await _js.InvokeAsync<string>("filterDb.getAll");
Console.WriteLine($"JS liefert JSON: {json?.Substring(0, Math.Min(100, json.Length))}...");
if (string.IsNullOrEmpty(json) || json == "[]")
return new();
var result = JsonSerializer.Deserialize<List<CustomerModel>>(json, JsonOptions);
Console.WriteLine($"Deserialisiert: {result?.Count ?? 0} Kunden");
return result ?? new();
}
catch (Exception ex)
{
Console.WriteLine($"IndexedDB Fehler: {ex.Message}");
return new();
}
}
public async Task AddOrUpdateAsync(CustomerModel customer)
{
var json = JsonSerializer.Serialize(customer, JsonOptions);
await _js.InvokeVoidAsync("filterDb.put", json);
}
public async Task DeleteAsync(int id)
{
await _js.InvokeVoidAsync("filterDb.delete", id);
}
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
}
}

View File

@ -0,0 +1,54 @@
using FilterCair.Shared.Models;
using Microsoft.JSInterop;
using System.Text.Json;
namespace FilterCair.Client.Services.Local
{
// Services/Local/FilterLocalService.cs
public class FilterLocalService
{
private readonly IJSRuntime _js;
public FilterLocalService(IJSRuntime js) => _js = js;
public async Task SaveFiltersAsync(int stationId, List<FilterModel> filters)
{
var json = JsonSerializer.Serialize(filters, JsonOptions);
await _js.InvokeVoidAsync("filterDb.saveFilters", stationId, json);
}
public async Task<List<FilterModel>> GetFiltersAsync(int stationId)
{
try
{
if (stationId == 0)
{
// Alle Filter laden
var json = await _js.InvokeAsync<string>("filterDb.getAllFilters");
return JsonSerializer.Deserialize<List<FilterModel>>(json, JsonOptions) ?? new();
}
else
{
var json = await _js.InvokeAsync<string>("filterDb.getFilters", stationId);
return JsonSerializer.Deserialize<List<FilterModel>>(json, JsonOptions) ?? new();
}
}
catch (Exception ex)
{
Console.WriteLine($"GetFiltersAsync Fehler: {ex.Message}");
return new();
}
}
public async Task EnqueueAsync(string action, FilterModel filter)
{
var payload = JsonSerializer.Serialize(filter, JsonOptions);
await _js.InvokeVoidAsync("filterDb.enqueue", action, filter.Id, payload);
}
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
}
}

View File

@ -0,0 +1,43 @@
// Services/Local/StationLocalService.cs
using FilterCair.Shared.Models;
using Microsoft.JSInterop;
using System.Text.Json;
namespace FilterCair.Client.Services.Local
{
public class StationLocalService
{
private readonly IJSRuntime _js;
public StationLocalService(IJSRuntime js)
{
_js = js;
}
public async Task SaveStationsAsync(int customerId, List<StationModel> stations)
{
var json = JsonSerializer.Serialize(stations, JsonOptions);
await _js.InvokeVoidAsync("filterDb.saveStations", customerId, json);
}
public async Task<List<StationModel>> GetStationsAsync(int customerId)
{
try
{
var json = await _js.InvokeAsync<string>("filterDb.getStations", customerId);
return JsonSerializer.Deserialize<List<StationModel>>(json, JsonOptions) ?? new();
}
catch (Exception ex)
{
Console.WriteLine($"StationLocalService Fehler: {ex.Message}");
return new();
}
}
private static readonly JsonSerializerOptions JsonOptions = new()
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
}
}

View File

@ -274,3 +274,30 @@ code {
margin: 0.5rem auto;
}
}
.station-card {
transition: all 0.3s ease;
cursor: pointer;
border: 1px solid rgba(0,0,0,0.05) !important;
}
.station-card:hover {
transform: translateY(-8px);
box-shadow: 0 12px 24px rgba(0,0,0,0.15) !important;
border-color: #198754 !important;
}
/* app.css */
.filter-card {
transition: all 0.3s ease;
cursor: pointer;
border: 1px solid rgba(0,0,0,0.05) !important;
}
.filter-card:hover {
transform: translateY(-8px);
box-shadow: 0 12px 24px rgba(0,0,0,0.15) !important;
border-color: #0d6efd !important;
}

View File

@ -44,6 +44,9 @@
<script src="js/jsQR.js"></script>
<script src="js/filtercair.js"></script>
<script src="js/toastcontainer.js"></script>
<script src="https://unpkg.com/dexie@3.2.4/dist/dexie.min.js"></script>
<script src="js/filterdb.js"></script>
<script src="js/onlineStatus.js"></script>
<script src="_framework/blazor.webassembly.js"></script>
<script src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationService.js"></script>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,229 @@
// 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'
});
this.customers = this.table('customers');
this.stations = this.table('stations');
this.filters = this.table('filters');
this.outbox = this.table('outbox');
}
}
const db = new FilterCairDB();
window.filterDb = {
saveAll: async (customersJson) => {
try {
const customers = JSON.parse(customersJson);
await db.customers.clear();
await db.customers.bulkPut(customers);
console.log(`IndexedDB: ${customers.length} Kunden gespeichert`);
return true;
} catch (err) {
console.error('saveAll Fehler:', err);
return false;
}
},
getAll: async () => {
try {
const customers = await db.customers.toArray();
console.log('IndexedDB Inhalt:', customers);
return JSON.stringify(customers);
} catch (err) {
console.error('getAll Fehler:', err);
return '[]';
}
},
put: async (customerJson) => {
try {
const customer = JSON.parse(customerJson);
await db.customers.put(customer);
return true;
} catch (err) {
console.error('put Fehler:', err);
return false;
}
},
delete: async (id) => {
try {
await db.customers.delete(id);
return true;
} catch (err) {
console.error('delete Fehler:', err);
return false;
}
},
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;
} catch (err) {
console.error('saveStations Fehler:', err);
return false;
}
},
// NEU: Stationen eines Kunden lesen
getStations: async (customerId) => {
try {
const stations = await db.stations.where('customerId').equals(customerId).toArray();
return JSON.stringify(stations);
} catch (err) {
console.error('getStations Fehler:', err);
return '[]';
}
},
saveFilters: async (stationId, filtersJson) => {
try {
const filters = JSON.parse(filtersJson);
await db.filters.where('stationId').equals(stationId).delete();
await db.filters.bulkPut(filters.map(f => ({
...f,
einbaudatum: f.einbaudatum ? new Date(f.einbaudatum) : null,
letzteWartung: f.letzteWartung ? new Date(f.letzteWartung) : null
})));
return true;
} catch (err) {
console.error('saveFilters Fehler:', err);
return false;
}
},
// NEU: Filter einer Station lesen
getFilters: async (stationId) => {
try {
const filters = await db.filters.where('stationId').equals(stationId).toArray();
return JSON.stringify(filters);
} catch (err) {
console.error('getFilters Fehler:', err);
return '[]';
}
},
getAllFilters: async () => {
try {
const filters = await db.filters.toArray();
return JSON.stringify(filters);
} catch (err) {
console.error('getAllFilters Fehler:', err);
return '[]';
}
},
// NEU: Outbox Änderung speichern
enqueue: async (action, entityId, payload) => {
try {
await db.outbox.add({
action,
entityId,
payload: JSON.stringify(payload),
timestamp: Date.now()
});
console.log(`Outbox: ${action} für ID ${entityId}`);
return true;
} catch (err) {
console.error('enqueue Fehler:', err);
return false;
}
},
// NEU: Outbox syncen
syncOutbox: async () => {
if (!navigator.onLine) return false;
try {
const pending = await db.outbox.orderBy('timestamp').toArray();
if (pending.length === 0) return true;
console.log(`Sync: ${pending.length} Änderungen`);
for (const item of pending) {
let success = false;
const url = '/api/Filter';
try {
if (item.action === 'add') {
const res = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: item.payload
});
success = res.ok;
if (success) {
const newFilter = await res.json();
await db.filters.put(newFilter); // ID aktualisieren
}
}
else if (item.action === 'update') {
const payload = JSON.parse(item.payload);
const res = await fetch(`${url}/${payload.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: item.payload
});
success = res.ok;
if (success) {
await db.filters.put(payload);
}
}
if (success) {
await db.outbox.delete(item.id);
}
} catch (err) {
console.error(`Sync Fehler bei ${item.action}:`, err);
}
}
// Nach Sync: alle Filter neu laden
await window.filterDb.refreshAllFilters();
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#
window.navigator = window.navigator || {};
window.navigator.onLine = () => navigator.onLine;

View File

@ -0,0 +1,2 @@
window.navigator = window.navigator || {};
window.navigator.onLine = () => navigator.onLine;

View File

@ -11,6 +11,7 @@ namespace FilterCair.Shared.Models
{
public int Id { get; set; }
public int StationId { get; set; }
public int CustomerId { get; set; }
public string Bezeichnung { get; set; } = string.Empty;
public string Typ { get; set; } = string.Empty;
public string Seriennummer { get; set; } = string.Empty;