Versiehistorie in een tabel, voor site en app
De versiehistorie stond als tekst in zowel de app als de website. Nu staat hij in tabel version_history, met een beheerpagina (Beheer > Versiehistorie) op de site. De site leest de tabel rechtstreeks, de app via de nieuwe actie app.versiehistorie. Startrijen: db/handmatig/22b_versiehistorie_startrijen.sql (als root). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014MGLqt1uKfNARQHut9ifcx
This commit is contained in:
@@ -1,61 +1,89 @@
|
||||
@* De versiehistorie: nieuwe of wezenlijk gewijzigde functies, nieuwste bovenaan.
|
||||
@* De versiehistorie: nieuwe of wezenlijk gewijzigde functies, nieuwste bovenaan.
|
||||
|
||||
Zelfde opzet als Productuitleg.razor en Privacytekst.razor: op de website een popup vanaf
|
||||
het overzicht, in de app hetzelfde. Dit bestand staat twee keer op schijf - de app en de site
|
||||
delen geen project - en een zelfcontrole vergelijkt ze letterlijk.
|
||||
De regels staan in een tabel op de server (version_history) en worden beheerd op de website
|
||||
onder Beheer > Versiehistorie. Site en app lezen dezelfde tabel; dit bestand bevat zelf geen
|
||||
tekst meer, dus een nieuwe regel vraagt geen nieuwe versie van de app. Zie docs/Versiehistorie.md.
|
||||
|
||||
Wat hier NIET in hoort: bugfixes, tekstuele correcties, opmaak die is rechtgezet. Dit is voor
|
||||
Wat daar NIET in hoort: bugfixes, tekstuele correcties, opmaak die is rechtgezet. Dit is voor
|
||||
"wat kan ik nu wat ik eerst niet kon", niet een logboek van elke wijziging in de code. *@
|
||||
@inject VersiehistorieService Historie
|
||||
|
||||
<p class="zacht" style="margin-top:0">
|
||||
Nieuwe of wezenlijk gewijzigde functies, nieuwste bovenaan.
|
||||
</p>
|
||||
|
||||
<h2 style="margin-top:0">20 oktober 2026 · versie 1.1</h2>
|
||||
<ul style="padding-left:1.2rem">
|
||||
<li>
|
||||
<strong>Meer soorten in de kluis.</strong> Naast wachtwoorden, polissen en uitvaartwensen
|
||||
kan er voortaan een nieuwe soort bijkomen, met eigen invulvelden, zonder dat je de app
|
||||
daarvoor opnieuw hoeft te updaten. Je bestaande kluisitems blijven zoals ze waren.
|
||||
</li>
|
||||
</ul>
|
||||
@if (Fout)
|
||||
{
|
||||
<p class="zacht">De versiehistorie kon niet worden opgehaald. Probeer het later nog eens.</p>
|
||||
}
|
||||
else if (Regels is null)
|
||||
{
|
||||
<p class="zacht">Bezig met laden…</p>
|
||||
}
|
||||
else if (Regels.Count == 0)
|
||||
{
|
||||
<p class="zacht">Er staat nog niets in de versiehistorie.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var groep in Groepen())
|
||||
{
|
||||
<h2 style="@(groep.Eerste ? "margin-top:0" : null)">@groep.Kop</h2>
|
||||
<ul style="padding-left:1.2rem">
|
||||
@foreach (var regel in groep.Regels)
|
||||
{
|
||||
<li style="margin-bottom:.5rem"><strong>@Punt(regel.Kop)</strong> @regel.Tekst</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
}
|
||||
|
||||
<h2>20 september 2026</h2>
|
||||
<ul style="padding-left:1.2rem">
|
||||
<li style="margin-bottom:.5rem">
|
||||
<strong>Rijbewijs en paspoort in de kluis.</strong> Twee nieuwe categorieën, met een
|
||||
knop om meteen de voor- en achterkant (rijbewijs) of de foto-pagina (paspoort) te maken
|
||||
of te kiezen.
|
||||
</li>
|
||||
<li style="margin-bottom:.5rem">
|
||||
<strong>Mijn profiel als pop-up.</strong> Je eigen profiel bewerk je nu in dezelfde
|
||||
pop-up met vaste "Opslaan"-knop onderin als bij de andere mensen in je familie, in de
|
||||
app en op de website.
|
||||
</li>
|
||||
<li style="margin-bottom:.5rem">
|
||||
<strong>Duidelijker voor de nalatenschapsbeheerder.</strong> Bij "Aan mij toevertrouwd"
|
||||
zie je nu meteen hoeveel meldingen er nodig zijn en hoe lang de bezwaartermijn is.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Muziek en verdere wensen groeien mee.</strong> Die velden bij een kluisitem
|
||||
passen zich nu vanzelf aan de lengte van de tekst aan, ook op de website.
|
||||
</li>
|
||||
</ul>
|
||||
@code {
|
||||
private IReadOnlyList<VersieRegelWeergave>? Regels;
|
||||
private bool Fout;
|
||||
|
||||
<h2>19 september 2026</h2>
|
||||
<ul style="padding-left:1.2rem">
|
||||
<li style="margin-bottom:.5rem">
|
||||
<strong>Met mij gedeeld, ook in de app.</strong> Heeft iemand een kluisitem rechtstreeks
|
||||
met jou gedeeld om het vast te bespreken - bijvoorbeeld een uitvaartwens - dan zie je
|
||||
dat voortaan ook op je telefoon, niet meer alleen op de website.
|
||||
</li>
|
||||
<li style="margin-bottom:.5rem">
|
||||
<strong>Een gedeeld of nagelaten kluisitem bekijken.</strong> Dat opent nu als pop-up in
|
||||
dezelfde opmaak als het invulformulier, inclusief de foto's en documenten die erbij
|
||||
horen.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Met de vinger naar beneden trekken ververst het scherm.</strong> Werkt op Kluis,
|
||||
Mijn mensen, Momenten en Nalatenschap.
|
||||
</li>
|
||||
</ul>
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try { Regels = await Historie.ZichtbaarAsync(); }
|
||||
catch { Fout = true; }
|
||||
}
|
||||
|
||||
private sealed record Groep(string Kop, bool Eerste, List<VersieRegelWeergave> Regels);
|
||||
|
||||
/// <summary>
|
||||
/// Opeenvolgende regels met dezelfde dag (en dezelfde versie) vormen samen één kop. De volgorde
|
||||
/// komt van de server: nieuwste dag bovenaan, binnen een dag in de volgorde van invoer.
|
||||
/// </summary>
|
||||
private IEnumerable<Groep> Groepen()
|
||||
{
|
||||
var uit = new List<Groep>();
|
||||
string? vorige = null;
|
||||
foreach (var r in Regels!)
|
||||
{
|
||||
var kop = Koptekst(r);
|
||||
if (kop != vorige)
|
||||
{
|
||||
uit.Add(new Groep(kop, uit.Count == 0, new List<VersieRegelWeergave>()));
|
||||
vorige = kop;
|
||||
}
|
||||
uit[^1].Regels.Add(r);
|
||||
}
|
||||
return uit;
|
||||
}
|
||||
|
||||
private static string Koptekst(VersieRegelWeergave r)
|
||||
{
|
||||
var dag = DateOnly.TryParseExact(r.Datum, "yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture,
|
||||
System.Globalization.DateTimeStyles.None, out var d)
|
||||
? $"{d.Day} {Datums.Maanden[d.Month - 1]} {d.Year}"
|
||||
: r.Datum;
|
||||
return string.IsNullOrWhiteSpace(r.Versie) ? dag : $"{dag} \u00b7 versie {r.Versie}";
|
||||
}
|
||||
|
||||
/// <summary>De kop eindigt op een punt, zodat de tekst erachter als gewone zin leest.</summary>
|
||||
private static string Punt(string kop)
|
||||
{
|
||||
var k = kop.Trim();
|
||||
return k.Length == 0 || ".!?".Contains(k[^1]) ? k : k + ".";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ else
|
||||
Ook een eigen scherm: dat is inrichten, geen terugdraaien. *@
|
||||
<p><a href="/beheer/soorten">Kluissoorten beheren ›</a></p>
|
||||
|
||||
@* Versiehistorie: de lijst "wat is er nieuw" op de website en in de app. Eén tabel voor beide. *@
|
||||
<p><a href="/beheer/versiehistorie">Versiehistorie beheren ›</a></p>
|
||||
|
||||
@if (Melding is not null) { <div class="melding @MeldingSoort">@Melding</div> }
|
||||
|
||||
@* ------------------------------------------------------------ statistieken *@
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
@page "/beheer/versiehistorie"
|
||||
@attribute [Authorize]
|
||||
@inject BeheerService Beheerkant
|
||||
@inject VersiehistorieService Historie
|
||||
@rendermode InteractiveServer
|
||||
|
||||
<PageTitle>Versiehistorie - Beheer</PageTitle>
|
||||
|
||||
@* Het beheerscherm voor de versiehistorie: de lijst "wat is er nieuw" die op de website en in de
|
||||
app onder "Versiehistorie" staat. Eén tabel voor beide, dus hier een regel toevoegen is genoeg.
|
||||
|
||||
Een regel is: een datum, eventueel een appversie, een vet kopje en een tekst. Gewone tekst,
|
||||
geen opmaak. Wat erin hoort: nieuwe of wezenlijk gewijzigde functies; geen bugfixes.
|
||||
|
||||
Een regel verbergen haalt hem weg bij de lezers zonder hem kwijt te raken. Wie de app al
|
||||
heeft van vóór deze tabel (versiecode 3 of ouder) ziet de tekst die in die app is ingebakken.
|
||||
|
||||
Zie docs/Versiehistorie.md. *@
|
||||
|
||||
@if (!Magdit)
|
||||
{
|
||||
<h1>Niet gevonden</h1>
|
||||
<p class="zacht">Deze pagina bestaat niet, of hij is niet voor jou.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h1>Versiehistorie</h1>
|
||||
<p class="zacht">
|
||||
Wat er nieuw is, nieuwste bovenaan. Een nieuwe regel staat meteen op de website en in de app,
|
||||
zonder dat er een nieuwe versie van de app nodig is.
|
||||
</p>
|
||||
<p><a href="/beheer">‹ Terug naar beheer</a></p>
|
||||
|
||||
@if (Melding is not null) { <div class="melding @MeldingSoort">@Melding</div> }
|
||||
|
||||
@* ------------------------------------------------------------ nieuwe of bestaande regel *@
|
||||
<div class="kaart">
|
||||
<h2 style="margin-top:0">@(BewerkId is null ? "Nieuwe regel" : "Regel wijzigen")</h2>
|
||||
<div class="raster twee">
|
||||
<div class="veld">
|
||||
<label for="vh-datum">Datum</label>
|
||||
<input id="vh-datum" type="date" @bind="Formulier.Datum" />
|
||||
</div>
|
||||
<div class="veld">
|
||||
<label for="vh-versie">Versie (mag leeg)</label>
|
||||
<input id="vh-versie" type="text" maxlength="20" @bind="Formulier.Versie" @bind:event="oninput"
|
||||
placeholder="Bijvoorbeeld: 1.2" />
|
||||
<div class="uitleg">Alleen invullen als dit bij een uitgebrachte versie van de app hoort.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="veld">
|
||||
<label for="vh-kop">Vet kopje</label>
|
||||
<input id="vh-kop" type="text" maxlength="@VersiehistorieService.MaxKop" @bind="Formulier.Kop" @bind:event="oninput"
|
||||
placeholder="Bijvoorbeeld: Meer soorten in de kluis." />
|
||||
</div>
|
||||
<div class="veld">
|
||||
<label for="vh-tekst">Tekst</label>
|
||||
<textarea id="vh-tekst" rows="4" maxlength="@VersiehistorieService.MaxTekst" @bind="Formulier.Tekst" @bind:event="oninput"></textarea>
|
||||
<div class="uitleg">@Formulier.Tekst.Length van @VersiehistorieService.MaxTekst tekens. Gewone tekst, geen opmaak.</div>
|
||||
</div>
|
||||
<div class="keuze">
|
||||
<input type="checkbox" id="vh-zichtbaar" @bind="Formulier.Zichtbaar" />
|
||||
<label for="vh-zichtbaar">Zichtbaar voor iedereen</label>
|
||||
</div>
|
||||
<div class="rij" style="margin-top:.8rem">
|
||||
<button class="knop" disabled="@Bezig" @onclick="Opslaan">@(BewerkId is null ? "Regel toevoegen" : "Wijzigingen opslaan")</button>
|
||||
@if (BewerkId is not null)
|
||||
{
|
||||
<button class="knop rustig" disabled="@Bezig" @onclick="Annuleren">Annuleren</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* ------------------------------------------------------------ de lijst *@
|
||||
@if (Lijst is null)
|
||||
{
|
||||
<p class="zacht">Bezig met laden…</p>
|
||||
}
|
||||
else if (Lijst.Count == 0)
|
||||
{
|
||||
<p class="zacht">Er staat nog niets in de versiehistorie.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="stapel">
|
||||
@foreach (var r in Lijst)
|
||||
{
|
||||
<div class="kaart">
|
||||
<div class="rij">
|
||||
<div class="groei">
|
||||
<div class="zacht">
|
||||
@Datums.Datum(r.Datum)
|
||||
@if (!string.IsNullOrWhiteSpace(r.Versie)) { <text> · versie @r.Versie</text> }
|
||||
</div>
|
||||
<div class="groot">@r.Kop</div>
|
||||
<div>@r.Tekst</div>
|
||||
@if (!r.Zichtbaar)
|
||||
{
|
||||
<div style="margin-top:.35rem"><span class="label uitgenodigd">Verborgen</span></div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="rij" style="margin-top:.6rem">
|
||||
<button class="knop rustig" disabled="@Bezig" @onclick="() => Wijzigen(r)">Wijzigen</button>
|
||||
<button class="knop rustig" disabled="@Bezig" @onclick="() => ZichtbaarOmzetten(r)">
|
||||
@(r.Zichtbaar ? "Verbergen" : "Tonen")
|
||||
</button>
|
||||
@if (Weghalen == r.Id)
|
||||
{
|
||||
<span class="zacht">Echt verwijderen?</span>
|
||||
<button class="knop gevaar" disabled="@Bezig" @onclick="() => Verwijderen(r)">Ja, verwijderen</button>
|
||||
<button class="knop rustig" disabled="@Bezig" @onclick="() => Weghalen = null">Nee</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button class="knop gevaar" disabled="@Bezig" @onclick="() => Weghalen = r.Id">Verwijderen</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
[CascadingParameter] private Task<AuthenticationState>? AuthState { get; set; }
|
||||
|
||||
private long _gebruikerId;
|
||||
private bool Magdit, Bezig;
|
||||
private string? Melding, MeldingSoort;
|
||||
|
||||
private IReadOnlyList<VersieRegelBeheerbeeld>? Lijst;
|
||||
private int? BewerkId;
|
||||
private int? Weghalen;
|
||||
private RegelFormulier Formulier = NieuwFormulier();
|
||||
|
||||
private sealed class RegelFormulier
|
||||
{
|
||||
public DateOnly Datum { get; set; }
|
||||
public string Versie { get; set; } = "";
|
||||
public string Kop { get; set; } = "";
|
||||
public string Tekst { get; set; } = "";
|
||||
public bool Zichtbaar { get; set; } = true;
|
||||
}
|
||||
|
||||
private static RegelFormulier NieuwFormulier() =>
|
||||
new() { Datum = DateOnly.FromDateTime(DateTime.Today) };
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (AuthState is null) return;
|
||||
_gebruikerId = (await AuthState).User.UserId() ?? 0;
|
||||
Magdit = await Beheerkant.IsBeheerderAsync(_gebruikerId);
|
||||
if (!Magdit) return;
|
||||
await Laden();
|
||||
}
|
||||
|
||||
private async Task Laden()
|
||||
{
|
||||
var resultaat = await Historie.OverzichtAsync(_gebruikerId);
|
||||
if (resultaat.Succeeded) Lijst = resultaat.Value;
|
||||
else Meld(resultaat.Error, false);
|
||||
}
|
||||
|
||||
private void Meld(string? tekst, bool goed)
|
||||
{
|
||||
Melding = tekst;
|
||||
MeldingSoort = goed ? "goed" : "fout";
|
||||
}
|
||||
|
||||
private void Wijzigen(VersieRegelBeheerbeeld r)
|
||||
{
|
||||
BewerkId = r.Id;
|
||||
Weghalen = null;
|
||||
Melding = null;
|
||||
Formulier = new RegelFormulier
|
||||
{
|
||||
Datum = r.Datum, Versie = r.Versie ?? "", Kop = r.Kop, Tekst = r.Tekst, Zichtbaar = r.Zichtbaar
|
||||
};
|
||||
}
|
||||
|
||||
private void Annuleren()
|
||||
{
|
||||
BewerkId = null;
|
||||
Formulier = NieuwFormulier();
|
||||
}
|
||||
|
||||
private async Task Opslaan()
|
||||
{
|
||||
Bezig = true;
|
||||
try
|
||||
{
|
||||
var versie = string.IsNullOrWhiteSpace(Formulier.Versie) ? null : Formulier.Versie.Trim();
|
||||
bool gelukt;
|
||||
string? fout;
|
||||
if (BewerkId is null)
|
||||
{
|
||||
var gemaakt = await Historie.MaakAsync(_gebruikerId, Formulier.Datum, versie, Formulier.Kop,
|
||||
Formulier.Tekst, Formulier.Zichtbaar);
|
||||
gelukt = gemaakt.Succeeded;
|
||||
fout = gemaakt.Error;
|
||||
}
|
||||
else
|
||||
{
|
||||
var gewijzigd = await Historie.WijzigAsync(_gebruikerId, BewerkId.Value, Formulier.Datum, versie,
|
||||
Formulier.Kop, Formulier.Tekst, Formulier.Zichtbaar);
|
||||
gelukt = gewijzigd.Succeeded;
|
||||
fout = gewijzigd.Error;
|
||||
}
|
||||
|
||||
if (gelukt)
|
||||
{
|
||||
Meld(BewerkId is null ? "De regel is toegevoegd." : "De wijziging is opgeslagen.", true);
|
||||
BewerkId = null;
|
||||
Formulier = NieuwFormulier();
|
||||
await Laden();
|
||||
}
|
||||
else Meld(fout, false);
|
||||
}
|
||||
finally { Bezig = false; }
|
||||
}
|
||||
|
||||
private async Task ZichtbaarOmzetten(VersieRegelBeheerbeeld r)
|
||||
{
|
||||
Bezig = true;
|
||||
try
|
||||
{
|
||||
var resultaat = await Historie.WijzigAsync(_gebruikerId, r.Id, r.Datum, r.Versie, r.Kop, r.Tekst, !r.Zichtbaar);
|
||||
if (resultaat.Succeeded)
|
||||
{
|
||||
Meld(r.Zichtbaar ? "De regel is verborgen." : "De regel is weer zichtbaar.", true);
|
||||
await Laden();
|
||||
}
|
||||
else Meld(resultaat.Error, false);
|
||||
}
|
||||
finally { Bezig = false; }
|
||||
}
|
||||
|
||||
private async Task Verwijderen(VersieRegelBeheerbeeld r)
|
||||
{
|
||||
Bezig = true;
|
||||
try
|
||||
{
|
||||
var resultaat = await Historie.VerwijderAsync(_gebruikerId, r.Id);
|
||||
if (resultaat.Succeeded)
|
||||
{
|
||||
Meld("De regel is verwijderd.", true);
|
||||
Weghalen = null;
|
||||
if (BewerkId == r.Id) Annuleren();
|
||||
await Laden();
|
||||
}
|
||||
else Meld(resultaat.Error, false);
|
||||
}
|
||||
finally { Bezig = false; }
|
||||
}
|
||||
}
|
||||
@@ -178,8 +178,7 @@
|
||||
</Popup>
|
||||
}
|
||||
|
||||
@* Zelfde tekst als in de app: Versiehistorie.razor staat twee keer op schijf en een
|
||||
zelfcontrole vergelijkt ze letterlijk. *@
|
||||
@* De tekst komt uit de tabel version_history, net als in de app. *@
|
||||
@if (VersiehistorieOpen)
|
||||
{
|
||||
<Popup Titel="Versiehistorie" Bevestigtekst="Sluiten" AlleenSluiten="true"
|
||||
|
||||
Reference in New Issue
Block a user