Files
MijnBesluit/src/MijnBesluit.AppUi/SchermBasis.cs
T
2026-09-27 14:21:31 +00:00

185 lines
7.0 KiB
C#

using MijnBesluit.Client;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
namespace MijnBesluit.AppUi;
/// <summary>
/// Gedeelde basis voor de schermen achter het inloggen: haalt de gegevens op, houdt één
/// foutmelding bij en stuurt wie niet is aangemeld naar het inlogscherm.
/// </summary>
public abstract class SchermBasis : ComponentBase, IDisposable
{
[Inject] protected AppToestand Toestand { get; set; } = default!;
[Inject] protected NavigationManager Navigatie { get; set; } = default!;
[Inject] private VernieuwSignaal Vernieuwen { get; set; } = default!;
/// <summary>Wordt getoond in een rode balk bovenaan het scherm.</summary>
protected string? Fout { get; set; }
/// <summary>Er loopt iets; knoppen zijn dan uit zodat er niet twee keer wordt gedrukt.</summary>
protected bool Bezig { get; set; }
protected override async Task OnInitializedAsync()
{
Toestand.Veranderd += OpVerandering;
// Op PersoonId en niet op IsAangemeld: een bewaard token zegt alleen dat we ooit zijn
// ingelogd, niet dat de app al weet wie hij is. Lukte het oppakken van de sessie de
// vorige keer niet omdat er even geen verbinding was, dan staat het token er nog en
// proberen we het hier gewoon opnieuw.
//
// En naar het inlogscherm alleen als er daarna echt geen token meer is. Dat betekent
// dat de server de sessie niet meer kent - de enige goede reden om iemand opnieuw te
// laten inloggen. Een haperende verbinding hoort dat niet te doen: dan komt er een rode
// balk "geen verbinding" en blijft de sessie staan.
if (Toestand.PersoonId == 0 && !await Toestand.HervatAsync() && !Toestand.IsAangemeld)
{
Navigatie.NavigateTo("/inloggen");
return;
}
await LaadAsync();
// Alleen het scherm dat nu open staat luistert naar het trek-naar-beneden-gebaar; zie
// VernieuwSignaal. Er wordt niet naar Bezig gekeken - dat gebruiken de schermen zelf al
// voor hun eigen knoppen en zou hier de verversing kunnen blokkeren zonder reden.
Vernieuwen.Gevraagd += OpVernieuwenGevraagdAsync;
}
/// <summary>Hier haalt het scherm zijn eigen gegevens op.</summary>
protected virtual Task LaadAsync() => Task.CompletedTask;
private async Task OpVernieuwenGevraagdAsync()
{
await LaadAsync();
await InvokeAsync(StateHasChanged);
}
/// <summary>
/// Voert iets uit dat kan mislukken en zet de melding klaar. Zo staat in elk scherm maar
/// één keer de afhandeling van fouten, en blijft de knop niet hangen als het misgaat.
/// </summary>
protected async Task<bool> ProbeerAsync(Func<Task<Uitkomst>> werk)
{
Bezig = true;
Fout = null;
StateHasChanged();
try
{
var uitkomst = await werk();
if (!uitkomst.Gelukt) Fout = uitkomst.Melding;
return uitkomst.Gelukt;
}
finally
{
Bezig = false;
StateHasChanged();
}
}
/// <summary>
/// Enter of spatie op een tegel die je normaal aantikt. Zo'n tegel is geen knop voor de
/// browser, dus zonder dit is hij alleen met een vinger of muis te bedienen - en niet met
/// een toetsenbord of een schermlezer.
/// </summary>
protected static Task ToetsAsync(KeyboardEventArgs toets, Func<Task> doen) =>
toets.Key is "Enter" or " " ? doen() : Task.CompletedTask;
private void OpVerandering() => InvokeAsync(StateHasChanged);
public virtual void Dispose()
{
Toestand.Veranderd -= OpVerandering;
Vernieuwen.Gevraagd -= OpVernieuwenGevraagdAsync;
}
}
/// <summary>Kleine hulpmiddelen om waarden leesbaar op het scherm te zetten.</summary>
public static class Tonen
{
// De maandnamen komen niet uit de taalinstelling van het toestel. Die bepaalde ze eerst, en
// op een telefoon die op Engels staat las de lijst "9 June 2031" terwijl de rest van het
// scherm Nederlands is. Een app die maar in één taal bestaat hoort ook maar in één taal te
// rekenen.
//
// De lijst zelf staat in MijnBesluit.Client, naast de website-versie in het domein; een
// zelfcontrole houdt die twee gelijk. De website liep tegen precies hetzelfde aan.
private static string[] Maanden => MijnBesluit.Client.Datums.Maanden;
private static string[] KorteMaanden => MijnBesluit.Client.Datums.KorteMaanden;
public static string Datum(DateTime? moment)
{
if (moment is null) return "";
var d = moment.Value.ToLocalTime();
return $"{d.Day} {Maanden[d.Month - 1]} {d.Year}";
}
public static string DatumTijd(DateTime? moment)
{
if (moment is null) return "";
var d = moment.Value.ToLocalTime();
return $"{Datum(d)} om {d:HH:mm}";
}
/// <summary>
/// De datum zoals hij op een tegel staat: "9 Jun. 2031". Daar telt elke pixel, en de maand
/// voluit duwde het jaartal van de regel af - "22 Septem...".
/// </summary>
public static string Kort(DateTime? moment)
{
if (moment is null) return "";
var d = moment.Value.ToLocalTime();
return $"{d.Day} {KorteMaanden[d.Month - 1]} {d.Year}";
}
/// <summary>
/// Hetzelfde voor een kale datum zonder tijd - een geboortedag, een sterfdag. Die hoort
/// niet omgerekend te worden naar een tijdzone: 9 juni is 9 juni, waar je ook bent.
/// </summary>
public static string Kort(DateOnly? dag) =>
dag is null ? "" : $"{dag.Value.Day} {KorteMaanden[dag.Value.Month - 1]} {dag.Value.Year}";
/// <summary>De nummers komen overeen met VaultCategory in het domein.</summary>
public static string Categorie(int categorie) => categorie switch
{
1 => "Uitvaartwens",
2 => "Polis",
3 => "Wachtwoord",
4 => "Apparaat",
5 => "Pincode",
6 => "Document",
7 => "Notitie",
8 => "Sociale media",
_ => "Overig"
};
/// <summary>
/// Hoe een relatie op het scherm heet. De lijst staat in MijnBesluit.Client, zodat de app en
/// de website er niet ieder een eigen versie van hebben.
/// </summary>
public static string Relatie(int relatie) => MijnBesluit.Client.Relaties.Naam(relatie);
public static string Vrijgave(int beleid) => beleid switch
{
0 => "Alleen voor mijzelf",
1 => "Na mijn overlijden",
2 => "Op een datum",
_ => ""
};
/// <summary>
/// Het tekentje bij de vrijgave. Het takje is hetzelfde als dat van Nalatenschap onderin,
/// en de kalender staat voor een datum - zo hoeft niemand een nieuwe taal te leren.
///
/// Voor "Alleen voor mijzelf" staat hier niets: dat ben jij, en daar hoort je eigen
/// profielfoto, geen plaatje. Het scherm zet daar een penning neer.
/// </summary>
public static string Vrijgaveteken(int beleid) => beleid switch
{
1 => "\U0001F33F", // takje, net als bij Nalatenschap
2 => "\U0001F4C5", // kalender
_ => ""
};
}