Neuste Version mit offline storage
This commit is contained in:
parent
d5fe1fc04c
commit
371d8d9058
@ -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}");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
68
FilterCair.Client/Services/Local/CustomerLocalService.cs
Normal file
68
FilterCair.Client/Services/Local/CustomerLocalService.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
54
FilterCair.Client/Services/Local/FilterLocalService.cs
Normal file
54
FilterCair.Client/Services/Local/FilterLocalService.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
43
FilterCair.Client/Services/Local/StationLocalService.cs
Normal file
43
FilterCair.Client/Services/Local/StationLocalService.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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>
|
||||
|
||||
2
FilterCair.Client/wwwroot/js/dexie.min.js
vendored
Normal file
2
FilterCair.Client/wwwroot/js/dexie.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
229
FilterCair.Client/wwwroot/js/filterdb.js
Normal file
229
FilterCair.Client/wwwroot/js/filterdb.js
Normal 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;
|
||||
2
FilterCair.Client/wwwroot/js/onlineStatus.js
Normal file
2
FilterCair.Client/wwwroot/js/onlineStatus.js
Normal file
@ -0,0 +1,2 @@
|
||||
window.navigator = window.navigator || {};
|
||||
window.navigator.onLine = () => navigator.onLine;
|
||||
@ -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;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user