@page "/"
@using System.Diagnostics
@using TrafagSalesExporter.Services
@inject IDashboardPageService DashboardPageActions
@inject ExportOrchestrationService Orchestrator
@inject TimerBackgroundService TimerService
@inject ISnackbar Snackbar
@inject IUiTextService UiText
@implements IDisposable
@T("Export Dashboard", "Export dashboard")
@T("Export Dashboard", "Export dashboard")
@T("Alle exportieren", "Export all")
@T("Zentrale Datei neu erzeugen", "Rebuild consolidated file")
@if (TimerService.NextRun < DateTime.MaxValue)
{
@(string.Format(T("Naechster automatischer Lauf: {0}", "Next automatic run: {0}"), TimerService.NextRun.ToString("dd.MM.yyyy HH:mm")))
}
else
{
@T("Timer deaktiviert", "Timer disabled")
}
@T("Land", "Country")
TSC
@T("Schema", "Schema")
@T("Server", "Server")
@T("Status", "Status")
@T("Live-Status", "Live status")
@T("Zeilen", "Rows")
@T("Letzter Lauf", "Last run")
@T("Dauer", "Duration")
@T("Aktion", "Action")
@context.Land
@context.TSC
@context.Schema
@context.ServerName
@if (Orchestrator.IsExporting(context.SiteId))
{
@Orchestrator.GetExportStatus(context.SiteId)
}
else if (context.LastStatus == "OK")
{
}
else if (context.LastStatus == "Error")
{
}
else
{
-
}
@if (!string.IsNullOrWhiteSpace(context.LiveMessage))
{
@context.LiveMessage
}
else
{
-
}
@(context.RowCount > 0 ? context.RowCount.ToString("N0") : "-")
@(context.LastRun.HasValue ? context.LastRun.Value.ToString("dd.MM.yyyy HH:mm:ss") : "-")
@(context.DurationSeconds > 0 ? $"{context.DurationSeconds:F1}s" : "-")
Export
@T("Excel oeffnen", "Open Excel")
@T("Zentrale Datei", "Consolidated file")
@T("Datei", "File")
Pfad
Letzte Änderung
@T("Status", "Status")
@T("Aktion", "Action")
@context.Label
@context.DisplayPath
@(context.LastModified.HasValue ? context.LastModified.Value.ToString("dd.MM.yyyy HH:mm:ss") : "-")
@if (Orchestrator.IsConsolidatedExporting())
{
@Orchestrator.GetConsolidatedExportStatus()
}
else
{
-
}
@T("Excel oeffnen", "Open Excel")
@T("Keine zentrale Excel-Datei gefunden.", "No consolidated Excel file found.")
@code {
private List _dashboardRows = new();
private List _consolidatedRows = new();
private bool _loading = true;
private bool _anyRunning;
private CancellationTokenSource? _pollingCts;
protected override async Task OnInitializedAsync()
{
Orchestrator.OnExportStatusChanged += HandleStatusChanged;
await LoadDataAsync();
}
private async Task LoadDataAsync()
{
_loading = true;
var state = await DashboardPageActions.LoadAsync();
_dashboardRows = state.DashboardRows;
_consolidatedRows = state.ConsolidatedRows;
_anyRunning = _dashboardRows.Any(r => Orchestrator.IsExporting(r.SiteId)) || Orchestrator.IsConsolidatedExporting();
_loading = false;
}
private async Task ExportAll()
{
_anyRunning = true;
await LoadDataAsync();
StartPolling();
_ = Task.Run(async () =>
{
try
{
await Orchestrator.ExportAllAsync();
await InvokeAsync(() =>
Snackbar.Add(T("Export fuer alle Standorte beendet", "Export completed for all sites"), Severity.Success));
}
catch (Exception ex)
{
await InvokeAsync(() =>
Snackbar.Add(string.Format(T("Export fuer alle Standorte fehlgeschlagen: {0}", "Export for all sites failed: {0}"), FormatException(ex)), Severity.Error));
}
finally
{
await InvokeAsync(async () =>
{
await LoadDataAsync();
StateHasChanged();
});
}
});
Snackbar.Add(T("Export fuer alle Standorte gestartet", "Export started for all sites"), Severity.Info);
}
private async Task ExportConsolidatedOnly()
{
_anyRunning = true;
await LoadDataAsync();
StartPolling();
_ = Task.Run(async () =>
{
try
{
var filePath = await Orchestrator.ExportConsolidatedOnlyAsync();
if (!string.IsNullOrWhiteSpace(filePath))
{
await InvokeAsync(() =>
Snackbar.Add(string.Format(T("Zentrale Datei erzeugt: {0}", "Consolidated file created: {0}"), filePath), Severity.Success));
}
else
{
await InvokeAsync(() =>
Snackbar.Add(T("Zentrale Datei konnte nicht erzeugt werden. Details stehen in den Logs.", "Consolidated file could not be created. Details are in the logs."), Severity.Warning));
}
}
catch (Exception ex)
{
await InvokeAsync(() =>
Snackbar.Add(string.Format(T("Zentrale Datei fehlgeschlagen: {0}", "Consolidated file failed: {0}"), FormatException(ex)), Severity.Error));
}
finally
{
await InvokeAsync(async () =>
{
await LoadDataAsync();
StateHasChanged();
});
}
});
Snackbar.Add(T("Zentrale Datei wird erzeugt", "Building consolidated file"), Severity.Info);
}
private void ExportSingle(int siteId)
{
_anyRunning = true;
_ = InvokeAsync(async () => await LoadDataAsync());
StartPolling();
_ = Task.Run(async () =>
{
try
{
var result = await Orchestrator.ExportSiteByIdAsync(siteId);
if (result?.Log.Status == "OK" && !string.IsNullOrWhiteSpace(result.FilePath))
{
await InvokeAsync(() =>
Snackbar.Add(string.Format(T("Export gespeichert: {0}", "Export saved: {0}"), result.FilePath), Severity.Success));
}
else if (result?.Log.Status == "Error" && !string.IsNullOrWhiteSpace(result.Log.ErrorMessage))
{
await InvokeAsync(() =>
Snackbar.Add(string.Format(T("Export fehlgeschlagen: {0}", "Export failed: {0}"), result.Log.ErrorMessage), Severity.Error));
}
}
catch (Exception ex)
{
await InvokeAsync(() =>
Snackbar.Add(string.Format(T("Export fehlgeschlagen: {0}", "Export failed: {0}"), FormatException(ex)), Severity.Error));
}
finally
{
await InvokeAsync(async () =>
{
await LoadDataAsync();
StateHasChanged();
});
}
});
Snackbar.Add(T("Export gestartet", "Export started"), Severity.Info);
}
private async void HandleStatusChanged()
{
await InvokeAsync(async () =>
{
_anyRunning = _dashboardRows.Any(r => Orchestrator.IsExporting(r.SiteId)) || Orchestrator.IsConsolidatedExporting() || _dashboardRows.Count == 0;
if (_anyRunning)
{
StartPolling();
await RefreshLiveDataAsync();
StateHasChanged();
return;
}
StopPolling();
await LoadDataAsync();
StateHasChanged();
});
}
public void Dispose()
{
StopPolling();
Orchestrator.OnExportStatusChanged -= HandleStatusChanged;
}
private void OpenExportFile(DashboardRow row)
{
OpenFile(row.FilePath);
}
private void OpenFile(string filePath)
{
if (string.IsNullOrWhiteSpace(filePath) || !File.Exists(filePath))
{
Snackbar.Add(T("Exportdatei nicht gefunden.", "Export file not found."), Severity.Warning);
return;
}
try
{
Process.Start(new ProcessStartInfo
{
FileName = filePath,
UseShellExecute = true
});
}
catch (Exception ex)
{
Snackbar.Add(string.Format(T("Datei konnte nicht geoeffnet werden: {0}", "Could not open file: {0}"), ex.Message), Severity.Error);
}
}
private void StartPolling()
{
if (_pollingCts is not null && !_pollingCts.IsCancellationRequested)
return;
_pollingCts = new CancellationTokenSource();
_ = PollDashboardAsync(_pollingCts.Token);
}
private void StopPolling()
{
_pollingCts?.Cancel();
_pollingCts?.Dispose();
_pollingCts = null;
}
private async Task PollDashboardAsync(CancellationToken cancellationToken)
{
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(3));
try
{
while (await timer.WaitForNextTickAsync(cancellationToken))
{
var anyRunning = _dashboardRows.Any(r => Orchestrator.IsExporting(r.SiteId)) || Orchestrator.IsConsolidatedExporting();
if (!anyRunning)
{
await InvokeAsync(async () =>
{
_anyRunning = false;
await LoadDataAsync();
StateHasChanged();
});
StopPolling();
break;
}
await InvokeAsync(async () =>
{
_anyRunning = true;
await RefreshLiveDataAsync();
StateHasChanged();
});
}
}
catch (OperationCanceledException)
{
}
}
private Task RefreshLiveDataAsync()
{
foreach (var row in _dashboardRows)
{
if (!Orchestrator.IsExporting(row.SiteId))
continue;
row.LiveMessage = Orchestrator.GetExportStatus(row.SiteId);
row.LiveDetails = string.Empty;
}
_anyRunning = _dashboardRows.Any(r => Orchestrator.IsExporting(r.SiteId)) || Orchestrator.IsConsolidatedExporting();
return Task.CompletedTask;
}
private static string FormatException(Exception ex)
=> ex.InnerException is null ? ex.Message : $"{ex.Message} Inner: {ex.InnerException.Message}";
}
@code {
private string T(string german, string english) => UiText.Text(german, english);
}