Add published HR KPI workflow fixes
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
@inject IAdminAccessService AdminAccess
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IUiTextService UiText
|
||||
@inject ILogger<AdminAccessPanel> Logger
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
<MudPaper Class="pa-4 mb-4" Elevation="1" Style="max-width:520px;">
|
||||
<MudStack Spacing="3">
|
||||
@@ -14,12 +16,20 @@
|
||||
@T("Admin-Zugang ist noch nicht konfiguriert.", "Admin access is not configured yet.")
|
||||
</MudAlert>
|
||||
}
|
||||
<MudTextField @bind-Value="_username" Label="@T("Name", "Name")" Disabled="@(!AdminAccess.IsConfigured)" />
|
||||
<MudTextField @bind-Value="_password" Label="@T("Passwort", "Password")" InputType="InputType.Password" Disabled="@(!AdminAccess.IsConfigured)" />
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="Unlock"
|
||||
StartIcon="@Icons.Material.Filled.LockOpen" Disabled="@(!AdminAccess.IsConfigured)">
|
||||
@T("Admin entsperren", "Unlock admin")
|
||||
</MudButton>
|
||||
<form method="post" action="@AccessUrl">
|
||||
<input type="hidden" name="returnUrl" value="@Navigation.Uri" />
|
||||
<MudStack Spacing="3">
|
||||
<MudTextField T="string" Name="username" Label="@T("Name", "Name")" Disabled="@(!AdminAccess.IsConfigured)" />
|
||||
<MudTextField T="string" Name="password" Label="@T("Passwort", "Password")" InputType="InputType.Password" Disabled="@(!AdminAccess.IsConfigured)" />
|
||||
<button type="submit" class="mud-button-root mud-button mud-button-filled mud-button-filled-primary mud-button-filled-size-medium mud-ripple">
|
||||
@T("Admin entsperren", "Unlock admin")
|
||||
</button>
|
||||
</MudStack>
|
||||
</form>
|
||||
<MudText Typo="Typo.caption">
|
||||
@T("Server-Klicks", "Server clicks"): @_unlockClickCount |
|
||||
@T("Konfiguriert", "Configured"): @(AdminAccess.IsConfigured ? "JA" : "NEIN")
|
||||
</MudText>
|
||||
<MudDivider />
|
||||
<MudExpansionPanels Elevation="0">
|
||||
<MudExpansionPanel Text="@T("Passwort ändern", "Change password")" Icon="@Icons.Material.Filled.Password">
|
||||
@@ -45,9 +55,19 @@
|
||||
private string? _currentPassword;
|
||||
private string? _newPassword;
|
||||
private string? _newPasswordRepeat;
|
||||
private int _unlockClickCount;
|
||||
private string AccessUrl => new Uri(new Uri(Navigation.BaseUri), "access/admin").ToString();
|
||||
|
||||
private void Unlock()
|
||||
{
|
||||
_unlockClickCount++;
|
||||
Logger.LogInformation(
|
||||
"Admin unlock button handler reached. ClickCount={ClickCount}, IsConfigured={IsConfigured}, UsernameLength={UsernameLength}, PasswordLength={PasswordLength}",
|
||||
_unlockClickCount,
|
||||
AdminAccess.IsConfigured,
|
||||
_username?.Length ?? 0,
|
||||
_password?.Length ?? 0);
|
||||
|
||||
if (!AdminAccess.TryUnlock(_username ?? string.Empty, _password ?? string.Empty))
|
||||
{
|
||||
Snackbar.Add(T("Admin-Anmeldung fehlgeschlagen.", "Admin sign-in failed."), Severity.Error);
|
||||
|
||||
@@ -12,7 +12,14 @@
|
||||
</head>
|
||||
<body>
|
||||
<Routes @rendermode="@Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer" />
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
<script src="@($"{BaseHref}_framework/blazor.web.js")" autostart="false"></script>
|
||||
<script>
|
||||
Blazor.start({
|
||||
circuit: {
|
||||
configureSignalR: builder => builder.withUrl('@($"{BaseHref}_blazor")')
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
|
||||
<script src="js/download.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
@using TrafagSalesExporter.Services
|
||||
@rendermode @(Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer)
|
||||
@inject IFinanceCockpitAccessService FinanceAccess
|
||||
@inject ISnackbar Snackbar
|
||||
@inject NavigationManager Navigation
|
||||
@inject IUiTextService UiText
|
||||
@inject ILogger<FinanceCockpitUnlockPanel> Logger
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-4">@T("Finance Cockpit", "Finance Cockpit")</MudText>
|
||||
|
||||
@@ -17,12 +19,20 @@
|
||||
@T("Finance-Cockpit-Zugang ist noch nicht konfiguriert. Bitte Username und PasswordHash in FinanceCockpitAccess konfigurieren.", "Finance Cockpit access is not configured yet. Please configure Username and PasswordHash in FinanceCockpitAccess.")
|
||||
</MudAlert>
|
||||
}
|
||||
<MudTextField @bind-Value="_username" Label="@T("Name", "Name")" Disabled="@(!FinanceAccess.IsConfigured)" />
|
||||
<MudTextField @bind-Value="_password" Label="@T("Passwort", "Password")" InputType="InputType.Password" Disabled="@(!FinanceAccess.IsConfigured)" />
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="UnlockAsync"
|
||||
StartIcon="@Icons.Material.Filled.LockOpen" Disabled="@(!FinanceAccess.IsConfigured)">
|
||||
@T("Finance Cockpit entsperren", "Unlock Finance Cockpit")
|
||||
</MudButton>
|
||||
<form method="post" action="@AccessUrl">
|
||||
<input type="hidden" name="returnUrl" value="@Navigation.Uri" />
|
||||
<MudStack Spacing="3">
|
||||
<MudTextField T="string" Name="username" Label="@T("Name", "Name")" Disabled="@(!FinanceAccess.IsConfigured)" />
|
||||
<MudTextField T="string" Name="password" Label="@T("Passwort", "Password")" InputType="InputType.Password" Disabled="@(!FinanceAccess.IsConfigured)" />
|
||||
<button type="submit" class="mud-button-root mud-button mud-button-filled mud-button-filled-primary mud-button-filled-size-medium mud-ripple">
|
||||
@T("Finance Cockpit entsperren", "Unlock Finance Cockpit")
|
||||
</button>
|
||||
</MudStack>
|
||||
</form>
|
||||
<MudText Typo="Typo.caption">
|
||||
@T("Server-Klicks", "Server clicks"): @_unlockClickCount |
|
||||
@T("Konfiguriert", "Configured"): @(FinanceAccess.IsConfigured ? "JA" : "NEIN")
|
||||
</MudText>
|
||||
<MudDivider />
|
||||
<MudExpansionPanels Elevation="0">
|
||||
<MudExpansionPanel Text="@T("Passwort ändern", "Change password")" Icon="@Icons.Material.Filled.Password">
|
||||
@@ -48,9 +58,19 @@
|
||||
private string? _currentPassword;
|
||||
private string? _newPassword;
|
||||
private string? _newPasswordRepeat;
|
||||
private int _unlockClickCount;
|
||||
private string AccessUrl => new Uri(new Uri(Navigation.BaseUri), "access/finance").ToString();
|
||||
|
||||
private Task UnlockAsync()
|
||||
{
|
||||
_unlockClickCount++;
|
||||
Logger.LogInformation(
|
||||
"Finance unlock button handler reached. ClickCount={ClickCount}, IsConfigured={IsConfigured}, UsernameLength={UsernameLength}, PasswordLength={PasswordLength}",
|
||||
_unlockClickCount,
|
||||
FinanceAccess.IsConfigured,
|
||||
_username?.Length ?? 0,
|
||||
_password?.Length ?? 0);
|
||||
|
||||
if (!FinanceAccess.TryUnlock(_username ?? string.Empty, _password ?? string.Empty))
|
||||
{
|
||||
Snackbar.Add(T("Finance-Cockpit-Anmeldung fehlgeschlagen.", "Finance Cockpit sign-in failed."), Severity.Error);
|
||||
|
||||
@@ -2,9 +2,13 @@
|
||||
@using TrafagSalesExporter.Models
|
||||
@using TrafagSalesExporter.Services
|
||||
@inject IUiTextService UiText
|
||||
@inject IJSRuntime JsRuntime
|
||||
|
||||
<MudTabs Elevation="1" Rounded="false" PanelClass="pt-4">
|
||||
<MudTabPanel Text="@T("Ueberblick", "Overview")" Icon="@Icons.Material.Filled.Dashboard">
|
||||
@PrintToolbar("hr-kpi-print-overview", T("Ueberblick als PDF", "Overview as PDF"))
|
||||
<div id="hr-kpi-print-overview" class="hr-print-section">
|
||||
@PrintHeader(T("Ueberblick", "Overview"))
|
||||
@MetricGrid(Result.Metrics)
|
||||
|
||||
<MudGrid Class="mt-4">
|
||||
@@ -24,9 +28,13 @@
|
||||
@CriticalBalancesTable(Result.CriticalTimeBalances)
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</div>
|
||||
</MudTabPanel>
|
||||
|
||||
<MudTabPanel Text="@T("Fluktuation", "Turnover")" Icon="@Icons.Material.Filled.TrendingDown">
|
||||
@PrintToolbar("hr-kpi-print-turnover", T("Fluktuation als PDF", "Turnover as PDF"))
|
||||
<div id="hr-kpi-print-turnover" class="hr-print-section">
|
||||
@PrintHeader(T("Fluktuation", "Turnover"))
|
||||
@MetricGrid(Result.TurnoverMetrics)
|
||||
|
||||
<MudGrid Class="mt-4">
|
||||
@@ -67,14 +75,22 @@
|
||||
@MonthlyBars(Result.TurnoverVisuals)
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</div>
|
||||
</MudTabPanel>
|
||||
|
||||
<MudTabPanel Text="@T("Ampel", "Status")" Icon="@Icons.Material.Filled.Traffic">
|
||||
@PrintToolbar("hr-kpi-print-status", T("Ampel als PDF", "Status as PDF"))
|
||||
<div id="hr-kpi-print-status" class="hr-print-section">
|
||||
@PrintHeader(T("Ampel", "Status"))
|
||||
@TrafficLightPanel(Result.TrafficLights)
|
||||
@MetricGrid(Result.PeriodComparisonMetrics)
|
||||
</div>
|
||||
</MudTabPanel>
|
||||
|
||||
<MudTabPanel Text="@T("Absenzen", "Absences")" Icon="@Icons.Material.Filled.Sick">
|
||||
@PrintToolbar("hr-kpi-print-absences", T("Absenzen als PDF", "Absences as PDF"))
|
||||
<div id="hr-kpi-print-absences" class="hr-print-section">
|
||||
@PrintHeader(T("Absenzen", "Absences"))
|
||||
@MetricGrid(Result.AbsenceMetrics)
|
||||
<MudGrid Class="mt-4">
|
||||
<MudItem xs="12" md="6">
|
||||
@@ -110,9 +126,13 @@
|
||||
</PagerContent>
|
||||
</MudTable>
|
||||
</MudPaper>
|
||||
</div>
|
||||
</MudTabPanel>
|
||||
|
||||
<MudTabPanel Text="@T("Zeit / Ferien", "Time / Vacation")" Icon="@Icons.Material.Filled.EventAvailable">
|
||||
@PrintToolbar("hr-kpi-print-time-vacation", T("Zeit/Ferien als PDF", "Time/vacation as PDF"))
|
||||
<div id="hr-kpi-print-time-vacation" class="hr-print-section">
|
||||
@PrintHeader(T("Zeit / Ferien", "Time / Vacation"))
|
||||
@MetricGrid(Result.TimeVacationMetrics)
|
||||
|
||||
<MudGrid Class="mt-4">
|
||||
@@ -145,19 +165,28 @@
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</div>
|
||||
</MudTabPanel>
|
||||
|
||||
<MudTabPanel Text="@T("Mitarbeitende", "Employees")" Icon="@Icons.Material.Filled.Groups">
|
||||
@PrintToolbar("hr-kpi-print-employees", T("Mitarbeitende als PDF", "Employees as PDF"))
|
||||
<div id="hr-kpi-print-employees" class="hr-print-section">
|
||||
@PrintHeader(T("Mitarbeitende", "Employees"))
|
||||
@EmployeesTable(Result.Employees)
|
||||
</div>
|
||||
</MudTabPanel>
|
||||
|
||||
<MudTabPanel Text="@T("Datenstatus", "Data status")" Icon="@Icons.Material.Filled.FactCheck">
|
||||
@PrintToolbar("hr-kpi-print-data-status", T("Datenstatus als PDF", "Data status as PDF"))
|
||||
<div id="hr-kpi-print-data-status" class="hr-print-section">
|
||||
@PrintHeader(T("Datenstatus", "Data status"))
|
||||
@FileStatusTable(Result.FileStatuses)
|
||||
<MudGrid Class="mt-4">
|
||||
<MudItem xs="12">
|
||||
@DataQualityTable(Result.DataQualityIssues)
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</div>
|
||||
</MudTabPanel>
|
||||
|
||||
<MudTabPanel Text="@T("Anleitung", "Guide")" Icon="@Icons.Material.Filled.HelpOutline">
|
||||
@@ -170,6 +199,42 @@
|
||||
|
||||
private string T(string german, string english) => UiText.Text(german, english);
|
||||
|
||||
private RenderFragment PrintToolbar(string targetId, string label) => @<MudStack Row Justify="Justify.FlexEnd" Class="mb-3 hr-print-toolbar">
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary" Size="Size.Small"
|
||||
StartIcon="@Icons.Material.Filled.PictureAsPdf"
|
||||
OnClick="@(() => PrintSectionAsync(targetId))">
|
||||
@label
|
||||
</MudButton>
|
||||
</MudStack>;
|
||||
|
||||
private RenderFragment PrintHeader(string title) => @<div class="hr-print-header">
|
||||
<h1>@title</h1>
|
||||
<p>@Result.Options.DataFolder</p>
|
||||
<p>@BuildFilterSummary()</p>
|
||||
</div>;
|
||||
|
||||
private async Task PrintSectionAsync(string targetId)
|
||||
{
|
||||
await JsRuntime.InvokeVoidAsync("trafagDownload.printElement", targetId);
|
||||
}
|
||||
|
||||
private string BuildFilterSummary()
|
||||
{
|
||||
var parts = new List<string>();
|
||||
if (Result.Options.FromDate.HasValue || Result.Options.ToDate.HasValue)
|
||||
parts.Add($"{T("Zeitraum", "Period")}: {FormatDate(Result.Options.FromDate)} - {FormatDate(Result.Options.ToDate)}");
|
||||
if (Result.Options.Year.HasValue)
|
||||
parts.Add($"{T("Austrittsjahr", "Leaver year")}: {Result.Options.Year.Value}");
|
||||
parts.Add($"{T("Organisation", "Organisation")}: {BlankAsAll(Result.Options.Organisationseinheit)}");
|
||||
parts.Add($"{T("Mitarbeitertyp", "Employee type")}: {BlankAsAll(Result.Options.Mitarbeitertyp)}");
|
||||
if (!string.IsNullOrWhiteSpace(Result.Options.KostenstelleText))
|
||||
parts.Add($"{T("Kostenstelle", "Cost center")}: {Result.Options.KostenstelleText}");
|
||||
return string.Join(" | ", parts);
|
||||
}
|
||||
|
||||
private string BlankAsAll(string? value)
|
||||
=> string.IsNullOrWhiteSpace(value) ? T("Alle", "All") : value;
|
||||
|
||||
private static Color MetricColor(string severity)
|
||||
=> severity == "Warning" ? Color.Warning : Color.Default;
|
||||
|
||||
@@ -629,6 +694,14 @@
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.hr-print-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hr-print-toolbar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hr-guide-steps {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(150px, 1fr));
|
||||
|
||||
@@ -7,42 +7,42 @@
|
||||
|
||||
<MudNavMenu>
|
||||
<MudNavGroup Title="@T("Finance Cockpit", "Finance Cockpit")" Icon="@Icons.Material.Filled.Analytics" Expanded="true">
|
||||
<MudNavLink Href="/export-dashboard" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Dashboard">
|
||||
<MudNavLink Href="export-dashboard" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Dashboard">
|
||||
@T("Export Dashboard", "Export dashboard")
|
||||
</MudNavLink>
|
||||
<MudNavLink Href="/management-cockpit" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.QueryStats">
|
||||
<MudNavLink Href="management-cockpit" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.QueryStats">
|
||||
@T("Management Analyse", "Management analysis")
|
||||
</MudNavLink>
|
||||
@if (ShowFinanceComparison)
|
||||
{
|
||||
<MudNavLink Href="/finance-cockpit/vergleich" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.CompareArrows">
|
||||
<MudNavLink Href="finance-cockpit/vergleich" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.CompareArrows">
|
||||
@T("Soll/Ist Vergleich", "Actual/reference comparison")
|
||||
</MudNavLink>
|
||||
}
|
||||
<MudNavLink Href="/finance-cockpit/schulung" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.School">
|
||||
<MudNavLink Href="finance-cockpit/schulung" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.School">
|
||||
@T("Finance Schulung", "Finance training")
|
||||
</MudNavLink>
|
||||
<MudNavLink Href="/manual-imports" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.UploadFile">
|
||||
<MudNavLink Href="manual-imports" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.UploadFile">
|
||||
@T("Manuelle Importe", "Manual imports")
|
||||
</MudNavLink>
|
||||
<MudNavGroup Title="@T("Admin", "Admin")" Icon="@Icons.Material.Filled.AdminPanelSettings">
|
||||
<AuthorizeView Policy="@SecurityPolicies.AdminOnly">
|
||||
<Authorized>
|
||||
<MudNavLink Href="/standorte" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.LocationOn">
|
||||
<MudNavLink Href="standorte" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.LocationOn">
|
||||
@T("Standorte", "Sites")
|
||||
</MudNavLink>
|
||||
<MudNavLink Href="/transformations" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Transform">
|
||||
<MudNavLink Href="transformations" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Transform">
|
||||
@T("Transformationen", "Transformations")
|
||||
</MudNavLink>
|
||||
<MudNavLink Href="/finance-rules" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Rule">
|
||||
<MudNavLink Href="finance-rules" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Rule">
|
||||
@T("Finance Regeln", "Finance rules")
|
||||
</MudNavLink>
|
||||
<MudNavLink Href="/settings" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Settings">
|
||||
<MudNavLink Href="settings" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Settings">
|
||||
@T("Settings", "Settings")
|
||||
</MudNavLink>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
<MudNavLink Href="/logs" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.List">
|
||||
<MudNavLink Href="logs" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.List">
|
||||
@T("Logs", "Logs")
|
||||
</MudNavLink>
|
||||
</MudNavGroup>
|
||||
@@ -55,14 +55,14 @@
|
||||
}
|
||||
</MudNavGroup>
|
||||
<MudNavGroup Title="@T("HR KPI (Login)", "HR KPI (login)")" Icon="@Icons.Material.Filled.Groups">
|
||||
<MudNavLink Href="/hr-kpi" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Dashboard">
|
||||
<MudNavLink Href="hr-kpi" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Dashboard">
|
||||
@T("HR Dashboard", "HR dashboard")
|
||||
</MudNavLink>
|
||||
<MudNavLink Href="/hr-kpi/schulung" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.School">
|
||||
<MudNavLink Href="hr-kpi/schulung" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.School">
|
||||
@T("HR KPI Schulung", "HR KPI training")
|
||||
</MudNavLink>
|
||||
</MudNavGroup>
|
||||
<MudNavLink Href="/admin/sessions" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.PeopleAlt">
|
||||
<MudNavLink Href="admin/sessions" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.PeopleAlt">
|
||||
@T("Admin Bereich", "Admin area")
|
||||
</MudNavLink>
|
||||
</MudNavMenu>
|
||||
@@ -78,7 +78,7 @@
|
||||
private void LockFinanceCockpit()
|
||||
{
|
||||
FinanceAccess.Lock();
|
||||
Navigation.NavigateTo("/");
|
||||
Navigation.NavigateTo(string.Empty);
|
||||
}
|
||||
|
||||
private void HandleLanguageChanged()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@page "/admin/sessions"
|
||||
@rendermode @(Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer)
|
||||
@attribute [Authorize(Policy = TrafagSalesExporter.Security.SecurityPolicies.AdminOnly)]
|
||||
@using TrafagSalesExporter.Services
|
||||
@inject IAccessSessionTracker SessionTracker
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@page "/"
|
||||
@rendermode @(Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer)
|
||||
@using TrafagSalesExporter.Services
|
||||
@inject IUiTextService UiText
|
||||
@inject ILandingPageSettingsService LandingSettings
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@page "/export-dashboard"
|
||||
@rendermode @(Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer)
|
||||
@using System.Diagnostics
|
||||
@using TrafagSalesExporter.Services
|
||||
@inject IDashboardPageService DashboardPageActions
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@page "/finance-cockpit/vergleich"
|
||||
@rendermode @(Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer)
|
||||
@using TrafagSalesExporter.Models
|
||||
@using TrafagSalesExporter.Services
|
||||
@inject IFinanceReconciliationService FinanceReconciliationService
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@page "/finance-rules"
|
||||
@rendermode @(Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer)
|
||||
@attribute [Authorize(Policy = TrafagSalesExporter.Security.SecurityPolicies.AdminOnly)]
|
||||
@using System.Reflection
|
||||
@using TrafagSalesExporter.Models
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@page "/finance-cockpit/schulung"
|
||||
@rendermode @(Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer)
|
||||
@inject TrafagSalesExporter.Services.IUiTextService UiText
|
||||
|
||||
<PageTitle>@T("Finance Schulung", "Finance training")</PageTitle>
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
@page "/hr-kpi"
|
||||
@using Microsoft.Extensions.Options
|
||||
@rendermode @(Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer)
|
||||
@using System.Globalization
|
||||
@using System.Text.Json
|
||||
@using TrafagSalesExporter.Components.HrKpi
|
||||
@using TrafagSalesExporter.Services
|
||||
@inject IHrKpiService HrKpiService
|
||||
@inject IOptions<HrKpiDataSourceOptions> DataSourceOptions
|
||||
@inject IHrKpiAccessService HrKpiAccess
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IUiTextService UiText
|
||||
@inject IJSRuntime JsRuntime
|
||||
@inject NavigationManager Navigation
|
||||
@inject IWebHostEnvironment Environment
|
||||
|
||||
<PageTitle>@T("HR KPI", "HR KPI")</PageTitle>
|
||||
|
||||
@@ -26,12 +29,20 @@
|
||||
@T("HR-KPI-Zugang ist noch nicht konfiguriert. Bitte Username und PasswordHash in HrKpiAccess konfigurieren.", "HR KPI access is not configured yet. Please configure Username and PasswordHash in HrKpiAccess.")
|
||||
</MudAlert>
|
||||
}
|
||||
<MudTextField @bind-Value="_hrUsername" Label="@T("Name", "Name")" Disabled="@(!HrKpiAccess.IsConfigured)" />
|
||||
<MudTextField @bind-Value="_hrPassword" Label="@T("Passwort", "Password")" InputType="InputType.Password" Disabled="@(!HrKpiAccess.IsConfigured)" />
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="UnlockHrKpiAsync"
|
||||
StartIcon="@Icons.Material.Filled.LockOpen" Disabled="@(!HrKpiAccess.IsConfigured)">
|
||||
@T("HR KPI entsperren", "Unlock HR KPI")
|
||||
</MudButton>
|
||||
<form method="post" action="@AccessUrl">
|
||||
<input type="hidden" name="returnUrl" value="@Navigation.Uri" />
|
||||
<MudStack Spacing="3">
|
||||
<MudTextField T="string" Name="username" Label="@T("Name", "Name")" Disabled="@(!HrKpiAccess.IsConfigured)" />
|
||||
<MudTextField T="string" Name="password" Label="@T("Passwort", "Password")" InputType="InputType.Password" Disabled="@(!HrKpiAccess.IsConfigured)" />
|
||||
<button type="submit" class="mud-button-root mud-button mud-button-filled mud-button-filled-primary mud-button-filled-size-medium mud-ripple">
|
||||
@T("HR KPI entsperren", "Unlock HR KPI")
|
||||
</button>
|
||||
</MudStack>
|
||||
</form>
|
||||
<MudText Typo="Typo.caption">
|
||||
@T("Server-Klicks", "Server clicks"): @_unlockClickCount |
|
||||
@T("Konfiguriert", "Configured"): @(HrKpiAccess.IsConfigured ? "JA" : "NEIN")
|
||||
</MudText>
|
||||
<MudDivider />
|
||||
<MudExpansionPanels Elevation="0">
|
||||
<MudExpansionPanel Text="@T("Passwort ändern", "Change password")" Icon="@Icons.Material.Filled.Password">
|
||||
@@ -57,7 +68,24 @@ else
|
||||
<MudItem xs="12" md="5">
|
||||
<MudTextField @bind-Value="_dataFolder"
|
||||
Label="@T("Datenordner fuer Rexx/SAP-Dateien", "Data folder for Rexx/SAP files")"
|
||||
HelperText="@T("Standard ist C:\\temp. Der Ordner kann hier fuer den aktuellen Lauf angepasst oder dauerhaft in appsettings.json unter HrKpi:DataFolder geaendert werden.", "Default is C:\\temp. The folder can be changed here for the current run or permanently in appsettings.json under HrKpi:DataFolder.")" />
|
||||
HelperText="@T("Serverordner fuer hochgeladene HR-KPI-Dateien. Auf der publizierten Webseite ist das ein Ordner auf dem Webserver, nicht C:\\temp auf dem lokalen PC.", "Server folder for uploaded HR KPI files. On the published site this is a folder on the web server, not C:\\temp on the local PC.")" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="5">
|
||||
<MudStack Spacing="1">
|
||||
<MudText Typo="Typo.caption">
|
||||
@T("Erwartete Dateien", "Expected files"): @string.Join(", ", ExpectedUploadFileNames)
|
||||
</MudText>
|
||||
<InputFile OnChange="UploadHrKpiFilesAsync" multiple accept=".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" disabled="@(_loading || _uploading)" />
|
||||
<MudText Typo="Typo.caption">
|
||||
@T("Uploadziel", "Upload target"): @_serverUploadFolder
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary" OnClick="UseServerUploadFolderAsync"
|
||||
StartIcon="@Icons.Material.Filled.Folder" Disabled="@(_loading || _uploading)" FullWidth>
|
||||
@T("Serverordner nutzen", "Use server folder")
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
<MudItem xs="6" md="2">
|
||||
<MudSelect T="int?" @bind-Value="_year" Label="@T("Austrittsjahr", "Leaver year")" Dense Clearable>
|
||||
@@ -86,10 +114,10 @@ else
|
||||
Label="@T("Managementsicht", "Management view")" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudDatePicker @bind-Date="_fromDate" Label="@T("Von Austritt", "Exit from")" Clearable DateFormat="dd.MM.yyyy" />
|
||||
<MudDatePicker @bind-Date="_fromDate" Label="@T("Von Datum", "From date")" Clearable DateFormat="dd.MM.yyyy" Culture="_dateCulture" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudDatePicker @bind-Date="_toDate" Label="@T("Bis Austritt", "Exit to")" Clearable DateFormat="dd.MM.yyyy" />
|
||||
<MudDatePicker @bind-Date="_toDate" Label="@T("Bis Datum", "To date")" Clearable DateFormat="dd.MM.yyyy" Culture="_dateCulture" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudSelect T="int?" @bind-Value="_entryYear" Label="@T("Eintrittsjahr", "Entry year")" Dense Clearable>
|
||||
@@ -154,6 +182,50 @@ else
|
||||
@T("Drucken/PDF", "Print/PDF")
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudDivider Class="my-2" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudSelect T="string" @bind-Value="_selectedVariantName" Label="@T("Variante", "Variant")" Dense Clearable>
|
||||
@foreach (var variant in _variantNames)
|
||||
{
|
||||
<MudSelectItem Value="@variant">@variant</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="3">
|
||||
<MudTextField @bind-Value="_variantName" Label="@T("Variantenname", "Variant name")" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary" OnClick="SaveVariantAsync"
|
||||
StartIcon="@Icons.Material.Filled.Save" Disabled="_loading" FullWidth>
|
||||
@T("Variante speichern", "Save variant")
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary" OnClick="RenameVariantAsync"
|
||||
StartIcon="@Icons.Material.Filled.Edit" Disabled="@(_loading || string.IsNullOrWhiteSpace(_selectedVariantName) || string.IsNullOrWhiteSpace(_variantName))" FullWidth>
|
||||
@T("Umbenennen", "Rename")
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary" OnClick="LoadVariantAsync"
|
||||
StartIcon="@Icons.Material.Filled.FileOpen" Disabled="@(_loading || string.IsNullOrWhiteSpace(_selectedVariantName))" FullWidth>
|
||||
@T("Variante laden", "Load variant")
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Error" OnClick="DeleteVariantAsync"
|
||||
StartIcon="@Icons.Material.Filled.Delete" Disabled="@(_loading || string.IsNullOrWhiteSpace(_selectedVariantName))" FullWidth>
|
||||
@T("Löschen", "Delete")
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Secondary" OnClick="UpdateSelectedVariantAsync"
|
||||
StartIcon="@Icons.Material.Filled.Update" Disabled="@(_loading || string.IsNullOrWhiteSpace(_selectedVariantName))" FullWidth>
|
||||
@T("Bestehende anpassen", "Update existing")
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
}
|
||||
@@ -194,8 +266,26 @@ else
|
||||
private string? _currentPassword;
|
||||
private string? _newPassword;
|
||||
private string? _newPasswordRepeat;
|
||||
private int _unlockClickCount;
|
||||
private string AccessUrl => new Uri(new Uri(Navigation.BaseUri), "access/hr").ToString();
|
||||
private bool _loading;
|
||||
private bool _uploading;
|
||||
private string _serverUploadFolder = string.Empty;
|
||||
private HrKpiResult? _result;
|
||||
private string _selectionStorePath = string.Empty;
|
||||
private HrKpiSelectionStore _selectionStore = new();
|
||||
private string? _variantName;
|
||||
private string? _selectedVariantName;
|
||||
private List<string> _variantNames = [];
|
||||
private static readonly SemaphoreSlim SelectionStoreLock = new(1, 1);
|
||||
private static readonly string[] ExpectedUploadFileNames =
|
||||
[
|
||||
"Saldiperstichdatum.xlsx",
|
||||
"Exportkommengehen.xlsx",
|
||||
"HR_KPI_Export.xlsx",
|
||||
"Abwesenheitinstunden.xlsx",
|
||||
"Personalausgeschieden.xlsx"
|
||||
];
|
||||
private readonly List<(string Key, string Label)> _fluktuationOptions =
|
||||
[
|
||||
("Alle", "Alle"),
|
||||
@@ -205,10 +295,20 @@ else
|
||||
];
|
||||
private readonly List<string> _ampelOptions = ["Gruen", "Gelb", "Rot"];
|
||||
private readonly List<string> _restferienOptions = ["Gruen", "Rot"];
|
||||
private readonly CultureInfo _dateCulture = CultureInfo.GetCultureInfo("de-CH");
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_dataFolder = DataSourceOptions.Value.Normalize().DataFolder;
|
||||
_serverUploadFolder = Path.Combine(Environment.ContentRootPath, "hrdata");
|
||||
Directory.CreateDirectory(_serverUploadFolder);
|
||||
_selectionStorePath = Path.Combine(_serverUploadFolder, "hr-kpi-variants.json");
|
||||
_selectionStore = await ReadSelectionStoreAsync();
|
||||
_dataFolder = _serverUploadFolder;
|
||||
if (_selectionStore.LastSelection is not null)
|
||||
ApplySelectionState(_selectionStore.LastSelection);
|
||||
else
|
||||
_mitarbeitertyp = "Festangestellt";
|
||||
RefreshVariantNames();
|
||||
if (CanShowHrKpi)
|
||||
{
|
||||
await LoadAsync();
|
||||
@@ -241,6 +341,8 @@ else
|
||||
SearchText = _searchText,
|
||||
ManagementView = _managementView
|
||||
});
|
||||
_selectionStore.LastSelection = CreateSelectionState();
|
||||
await WriteSelectionStoreAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -252,8 +354,243 @@ else
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UploadHrKpiFilesAsync(InputFileChangeEventArgs args)
|
||||
{
|
||||
if (!CanShowHrKpi)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_uploading = true;
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(_serverUploadFolder);
|
||||
var uploaded = 0;
|
||||
var skipped = new List<string>();
|
||||
var expected = ExpectedUploadFileNames.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var file in args.GetMultipleFiles(10))
|
||||
{
|
||||
var fileName = Path.GetFileName(file.Name);
|
||||
if (!expected.Contains(fileName))
|
||||
{
|
||||
skipped.Add(fileName);
|
||||
continue;
|
||||
}
|
||||
|
||||
var targetPath = Path.Combine(_serverUploadFolder, fileName);
|
||||
await using var source = file.OpenReadStream(50 * 1024 * 1024);
|
||||
await using var target = File.Create(targetPath);
|
||||
await source.CopyToAsync(target);
|
||||
uploaded++;
|
||||
}
|
||||
|
||||
_dataFolder = _serverUploadFolder;
|
||||
|
||||
if (uploaded > 0)
|
||||
{
|
||||
Snackbar.Add($"{uploaded} HR-KPI-Datei(en) auf den Server geladen.", Severity.Success);
|
||||
await LoadAsync();
|
||||
}
|
||||
|
||||
if (skipped.Count > 0)
|
||||
Snackbar.Add($"Nicht uebernommen, weil Dateiname nicht erwartet wird: {string.Join(", ", skipped)}", Severity.Warning);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Upload fehlgeschlagen: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_uploading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UseServerUploadFolderAsync()
|
||||
{
|
||||
_dataFolder = _serverUploadFolder;
|
||||
await LoadAsync();
|
||||
}
|
||||
|
||||
private async Task SaveVariantAsync()
|
||||
{
|
||||
var name = (_variantName ?? _selectedVariantName)?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
{
|
||||
Snackbar.Add(T("Bitte Variantenname eingeben.", "Please enter a variant name."), Severity.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
_selectionStore = await ReadSelectionStoreAsync();
|
||||
_selectionStore.Variants[name] = CreateSelectionState();
|
||||
await WriteSelectionStoreAsync();
|
||||
RefreshVariantNames();
|
||||
_selectedVariantName = name;
|
||||
_variantName = name;
|
||||
Snackbar.Add($"{T("Variante gespeichert", "Variant saved")}: {name}", Severity.Success);
|
||||
}
|
||||
|
||||
private async Task UpdateSelectedVariantAsync()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_selectedVariantName))
|
||||
return;
|
||||
|
||||
var name = _selectedVariantName.Trim();
|
||||
_selectionStore = await ReadSelectionStoreAsync();
|
||||
_selectionStore.Variants[name] = CreateSelectionState();
|
||||
await WriteSelectionStoreAsync();
|
||||
RefreshVariantNames();
|
||||
_selectedVariantName = name;
|
||||
_variantName = name;
|
||||
Snackbar.Add($"{T("Variante aktualisiert", "Variant updated")}: {name}", Severity.Success);
|
||||
}
|
||||
|
||||
private async Task RenameVariantAsync()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_selectedVariantName) || string.IsNullOrWhiteSpace(_variantName))
|
||||
return;
|
||||
|
||||
var oldName = _selectedVariantName.Trim();
|
||||
var newName = _variantName.Trim();
|
||||
if (string.Equals(oldName, newName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Snackbar.Add(T("Der Variantenname ist unverändert.", "The variant name is unchanged."), Severity.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
_selectionStore = await ReadSelectionStoreAsync();
|
||||
if (!_selectionStore.Variants.TryGetValue(oldName, out var selection))
|
||||
{
|
||||
Snackbar.Add(T("Variante nicht gefunden.", "Variant not found."), Severity.Warning);
|
||||
RefreshVariantNames();
|
||||
return;
|
||||
}
|
||||
|
||||
_selectionStore.Variants.Remove(oldName);
|
||||
_selectionStore.Variants[newName] = selection;
|
||||
await WriteSelectionStoreAsync();
|
||||
RefreshVariantNames();
|
||||
_selectedVariantName = newName;
|
||||
_variantName = newName;
|
||||
Snackbar.Add($"{T("Variante umbenannt", "Variant renamed")}: {oldName} -> {newName}", Severity.Success);
|
||||
}
|
||||
|
||||
private async Task LoadVariantAsync()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_selectedVariantName))
|
||||
return;
|
||||
|
||||
_selectionStore = await ReadSelectionStoreAsync();
|
||||
if (!_selectionStore.Variants.TryGetValue(_selectedVariantName.Trim(), out var selection))
|
||||
{
|
||||
Snackbar.Add(T("Variante nicht gefunden.", "Variant not found."), Severity.Warning);
|
||||
RefreshVariantNames();
|
||||
return;
|
||||
}
|
||||
|
||||
ApplySelectionState(selection);
|
||||
_variantName = _selectedVariantName;
|
||||
await LoadAsync();
|
||||
}
|
||||
|
||||
private async Task DeleteVariantAsync()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_selectedVariantName))
|
||||
return;
|
||||
|
||||
var name = _selectedVariantName.Trim();
|
||||
_selectionStore = await ReadSelectionStoreAsync();
|
||||
_selectionStore.Variants.Remove(name);
|
||||
await WriteSelectionStoreAsync();
|
||||
RefreshVariantNames();
|
||||
_selectedVariantName = null;
|
||||
_variantName = null;
|
||||
Snackbar.Add($"{T("Variante gelöscht", "Variant deleted")}: {name}", Severity.Success);
|
||||
}
|
||||
|
||||
private void RefreshVariantNames()
|
||||
{
|
||||
_variantNames = _selectionStore.Variants.Keys
|
||||
.OrderBy(x => x, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private HrKpiSelectionState CreateSelectionState()
|
||||
=> new()
|
||||
{
|
||||
DataFolder = _dataFolder,
|
||||
Year = _year,
|
||||
FromDate = _fromDate,
|
||||
ToDate = _toDate,
|
||||
EntryYear = _entryYear,
|
||||
Organisation = _organisation,
|
||||
Kostenstelle = _kostenstelle,
|
||||
Mitarbeitertyp = _mitarbeitertyp,
|
||||
FluktuationFilter = _fluktuationFilter,
|
||||
GlzAmpel = _glzAmpel,
|
||||
RestferienAmpel = _restferienAmpel,
|
||||
SearchText = _searchText,
|
||||
ManagementView = _managementView
|
||||
};
|
||||
|
||||
private void ApplySelectionState(HrKpiSelectionState state)
|
||||
{
|
||||
_dataFolder = string.IsNullOrWhiteSpace(state.DataFolder) ? _serverUploadFolder : state.DataFolder;
|
||||
_year = state.Year;
|
||||
_fromDate = state.FromDate;
|
||||
_toDate = state.ToDate;
|
||||
_entryYear = state.EntryYear;
|
||||
_organisation = state.Organisation;
|
||||
_kostenstelle = state.Kostenstelle;
|
||||
_mitarbeitertyp = string.IsNullOrWhiteSpace(state.Mitarbeitertyp) ? "Festangestellt" : state.Mitarbeitertyp;
|
||||
_fluktuationFilter = string.IsNullOrWhiteSpace(state.FluktuationFilter) ? "Alle" : state.FluktuationFilter;
|
||||
_glzAmpel = state.GlzAmpel;
|
||||
_restferienAmpel = state.RestferienAmpel;
|
||||
_searchText = state.SearchText;
|
||||
_managementView = state.ManagementView;
|
||||
}
|
||||
|
||||
private async Task<HrKpiSelectionStore> ReadSelectionStoreAsync()
|
||||
{
|
||||
await SelectionStoreLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
if (!File.Exists(_selectionStorePath))
|
||||
return new HrKpiSelectionStore();
|
||||
|
||||
await using var stream = File.OpenRead(_selectionStorePath);
|
||||
return await JsonSerializer.DeserializeAsync<HrKpiSelectionStore>(stream) ?? new HrKpiSelectionStore();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new HrKpiSelectionStore();
|
||||
}
|
||||
finally
|
||||
{
|
||||
SelectionStoreLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task WriteSelectionStoreAsync()
|
||||
{
|
||||
await SelectionStoreLock.WaitAsync();
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(_selectionStorePath) ?? _serverUploadFolder);
|
||||
var options = new JsonSerializerOptions { WriteIndented = true };
|
||||
await using var stream = File.Create(_selectionStorePath);
|
||||
await JsonSerializer.SerializeAsync(stream, _selectionStore, options);
|
||||
}
|
||||
finally
|
||||
{
|
||||
SelectionStoreLock.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UnlockHrKpiAsync()
|
||||
{
|
||||
_unlockClickCount++;
|
||||
|
||||
if (!HrKpiAccess.TryUnlock(_hrUsername ?? string.Empty, _hrPassword ?? string.Empty))
|
||||
{
|
||||
Snackbar.Add(T("HR-KPI-Anmeldung fehlgeschlagen.", "HR KPI sign-in failed."), Severity.Error);
|
||||
@@ -306,4 +643,27 @@ else
|
||||
private bool CanShowHrKpi => !HrKpiAccess.IsEnabled || HrKpiAccess.IsUnlocked;
|
||||
|
||||
private string T(string german, string english) => UiText.Text(german, english);
|
||||
|
||||
private sealed class HrKpiSelectionStore
|
||||
{
|
||||
public HrKpiSelectionState? LastSelection { get; set; }
|
||||
public Dictionary<string, HrKpiSelectionState> Variants { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private sealed class HrKpiSelectionState
|
||||
{
|
||||
public string? DataFolder { get; set; }
|
||||
public int? Year { get; set; }
|
||||
public DateTime? FromDate { get; set; }
|
||||
public DateTime? ToDate { get; set; }
|
||||
public int? EntryYear { get; set; }
|
||||
public string? Organisation { get; set; }
|
||||
public string? Kostenstelle { get; set; }
|
||||
public string? Mitarbeitertyp { get; set; }
|
||||
public string? FluktuationFilter { get; set; }
|
||||
public string? GlzAmpel { get; set; }
|
||||
public string? RestferienAmpel { get; set; }
|
||||
public string? SearchText { get; set; }
|
||||
public bool ManagementView { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@page "/hr-kpi/schulung"
|
||||
@rendermode @(Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer)
|
||||
@inject TrafagSalesExporter.Services.IUiTextService UiText
|
||||
|
||||
<PageTitle>@T("HR KPI Schulung", "HR KPI training")</PageTitle>
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
@page "/diagnostics/interactive"
|
||||
@rendermode @(Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer)
|
||||
@inject ILogger<InteractiveDiagnostics> Logger
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
<PageTitle>Interaktivitaet Diagnose</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-4">Interaktivitaet Diagnose</MudText>
|
||||
|
||||
<MudPaper Class="pa-4" Elevation="1" Style="max-width:760px;">
|
||||
<MudStack Spacing="3">
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined">
|
||||
HTML wurde vom Server gerendert.
|
||||
</MudAlert>
|
||||
|
||||
<MudText>Adresse: @Navigation.Uri</MudText>
|
||||
<MudText>Blazor interaktiv verbunden: @(_interactive ? "JA" : "NEIN")</MudText>
|
||||
<MudText>Server-Klicks angekommen: @_clickCount</MudText>
|
||||
<MudText>JavaScript Diagnose: <span id="js-diagnostic-status">nicht ausgefuehrt</span></MudText>
|
||||
<MudText>Blazor Objekt im Browser: <span id="blazor-diagnostic-status">unbekannt</span></MudText>
|
||||
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="RegisterClick">
|
||||
Server-Klick testen
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var jsStatus = document.getElementById('js-diagnostic-status');
|
||||
var blazorStatus = document.getElementById('blazor-diagnostic-status');
|
||||
if (jsStatus) {
|
||||
jsStatus.textContent = 'ausgefuehrt';
|
||||
}
|
||||
if (blazorStatus) {
|
||||
blazorStatus.textContent = window.Blazor ? 'vorhanden' : 'fehlt';
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
@code {
|
||||
private bool _interactive;
|
||||
private int _clickCount;
|
||||
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
if (!firstRender)
|
||||
return;
|
||||
|
||||
_interactive = true;
|
||||
Logger.LogInformation("Interactive diagnostics became interactive for {Uri}", Navigation.Uri);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void RegisterClick()
|
||||
{
|
||||
_clickCount++;
|
||||
Logger.LogInformation("Interactive diagnostics server click received. Count={ClickCount}", _clickCount);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
@page "/logs"
|
||||
@rendermode @(Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer)
|
||||
@using TrafagSalesExporter.Services
|
||||
@inject ILogsPageService LogsPageActions
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@page "/management-cockpit"
|
||||
@rendermode @(Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer)
|
||||
@using TrafagSalesExporter.Models
|
||||
@using TrafagSalesExporter.Services
|
||||
@inject IManagementCockpitPageService CockpitPageService
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@page "/manual-imports"
|
||||
@rendermode @(Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer)
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using TrafagSalesExporter.Data
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@page "/settings"
|
||||
@rendermode @(Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer)
|
||||
@attribute [Authorize(Policy = TrafagSalesExporter.Security.SecurityPolicies.AdminOnly)]
|
||||
@using TrafagSalesExporter.Models
|
||||
@using TrafagSalesExporter.Services
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@page "/source-viewer"
|
||||
@rendermode @(Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer)
|
||||
@using Microsoft.AspNetCore.Components
|
||||
@using Microsoft.AspNetCore.WebUtilities
|
||||
@inject IWebHostEnvironment Environment
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@page "/standorte"
|
||||
@rendermode @(Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer)
|
||||
@attribute [Authorize(Policy = TrafagSalesExporter.Security.SecurityPolicies.AdminOnly)]
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using System.Text.Json
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@page "/transformations"
|
||||
@rendermode @(Microsoft.AspNetCore.Components.Web.RenderMode.InteractiveServer)
|
||||
@attribute [Authorize(Policy = TrafagSalesExporter.Security.SecurityPolicies.AdminOnly)]
|
||||
@using System.Reflection
|
||||
@using TrafagSalesExporter.Models
|
||||
|
||||
Reference in New Issue
Block a user