- Tabellen vault_categories en vault_category_fields (db/21), startrijen handmatig (db/handmatig/21b) - VaultCategoryService met regels voor zichtbaarheid, gebruik en eigen velden - Beheer > Kluissoorten (/beheer/soorten) met gebruikstellers; soort in gebruik niet te verwijderen - Kluis en Nalatenschap (web en app) tonen soorten en eigen velden uit de database - Socketprotocol: vault.categories; GRANTs voor mijnbesluit_app in db/02 - db/20 herhaalbaar gemaakt - App versie 1.1 (versiecode 3) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014MGLqt1uKfNARQHut9ifcx
343 lines
15 KiB
Plaintext
343 lines
15 KiB
Plaintext
@page "/beheer"
|
|
@attribute [Authorize]
|
|
@inject BeheerService Beheerkant
|
|
@rendermode InteractiveServer
|
|
|
|
<PageTitle>Beheer - MijnBesluit</PageTitle>
|
|
|
|
@* De beheerpagina. Twee handelingen: een overlijden terugdraaien dat ten onrechte is bevestigd,
|
|
en een samenvoeging terugdraaien die niet had gemoeten.
|
|
|
|
Zie BeheerService voor waarom er niet meer op staat. Kort: een beheerder kan geen kluis
|
|
openen en kan niet op naam zoeken. *@
|
|
|
|
@if (!Magdit)
|
|
{
|
|
<h1>Niet gevonden</h1>
|
|
<p class="zacht">Deze pagina bestaat niet, of hij is niet voor jou.</p>
|
|
}
|
|
else
|
|
{
|
|
<h1>Beheer</h1>
|
|
<p class="zacht">
|
|
Hier draai je twee dingen terug die niemand anders kan: een overlijden dat ten onrechte
|
|
is bevestigd, en een samenvoeging van twee profielen die niet had gemoeten. Voor het
|
|
eerste is dit nodig als de bezwaarlink uit de waarschuwingsmail verlopen is; de
|
|
betrokkene kan dan niet meer inloggen en heeft zelf geen weg terug.
|
|
</p>
|
|
|
|
@* Adverteren heeft een eigen scherm: dat gaat niet over terugdraaien maar over betalingen
|
|
afvinken en advertenties beoordelen, en daar hoor je dagelijks te komen in plaats van bij
|
|
hoge uitzondering. *@
|
|
<p><a href="/beheer/adverteren">Advertenties beheren ›</a></p>
|
|
|
|
@* Kluissoorten: wat mensen in hun kluis kunnen vastleggen, en wat je daar zelf bij zet.
|
|
Ook een eigen scherm: dat is inrichten, geen terugdraaien. *@
|
|
<p><a href="/beheer/soorten">Kluissoorten beheren ›</a></p>
|
|
|
|
@if (Melding is not null) { <div class="melding @MeldingSoort">@Melding</div> }
|
|
|
|
@* ------------------------------------------------------------ statistieken *@
|
|
<div class="kaart">
|
|
<h2 style="margin-top:0">Gebruik van MijnBesluit</h2>
|
|
<p class="zacht" style="margin-top:0">
|
|
Aantallen, niet inhoud: hier is niets van iemands kluis of berichten te zien. Bedoeld
|
|
om de belasting in te schatten, niet om iemand te volgen.
|
|
</p>
|
|
|
|
@if (Stats is null)
|
|
{
|
|
<p class="zacht" style="margin-bottom:0">Statistieken zijn hier niet beschikbaar.</p>
|
|
}
|
|
else
|
|
{
|
|
<div class="statgrid">
|
|
<div class="statcel">
|
|
<span class="statgetal">@Stats.Profielen</span>
|
|
<span class="statlabel">Profielen (@Stats.GeclaimdeProfielen geclaimd)</span>
|
|
</div>
|
|
<div class="statcel">
|
|
<span class="statgetal">@Stats.Families</span>
|
|
<span class="statlabel">Families</span>
|
|
</div>
|
|
<div class="statcel">
|
|
<span class="statgetal">@Stats.KluisItems</span>
|
|
<span class="statlabel">Kluisitems</span>
|
|
</div>
|
|
<div class="statcel">
|
|
<span class="statgetal">@Stats.KluisBestanden</span>
|
|
<span class="statlabel">Bijlagen</span>
|
|
</div>
|
|
<div class="statcel">
|
|
<span class="statgetal">@Stats.Momenten</span>
|
|
<span class="statlabel">Momenten (@Stats.BezorgdeMomenten bezorgd)</span>
|
|
</div>
|
|
<div class="statcel">
|
|
<span class="statgetal">@Stats.Nalatenschapbeheerders</span>
|
|
<span class="statlabel">Aangewezen beheerders</span>
|
|
</div>
|
|
</div>
|
|
|
|
<Groeigrafiek Punten="Stats.GroeiPerDag" />
|
|
|
|
@if (Stats.ItemsPerCategorie.Count > 0)
|
|
{
|
|
<h3 style="margin-bottom:.6rem">Kluisitems per categorie</h3>
|
|
<div class="stapel">
|
|
@foreach (var (categorie, aantal) in Stats.ItemsPerCategorie.OrderByDescending(kv => kv.Value))
|
|
{
|
|
<div class="rij">
|
|
<span class="zacht" style="min-width:140px">@Kluisweergave.Naam(categorie)</span>
|
|
<div class="groei">
|
|
<div class="voortgang">
|
|
<span style="width:@(CategoriePercentage(aantal))%"></span>
|
|
</div>
|
|
</div>
|
|
<span class="zacht" style="min-width:2.5rem;text-align:right">@aantal</span>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
|
|
<div class="kaart">
|
|
<h2 style="margin-top:0">Profiel opzoeken</h2>
|
|
<div class="veld">
|
|
<label for="adres">E-mailadres</label>
|
|
<input id="adres" type="email" @bind="Adres" placeholder="het volledige adres" />
|
|
<div class="uitleg">
|
|
Alleen het volledige adres, niet een deel ervan en niet op naam. Je hoort al te
|
|
weten wie je zoekt.
|
|
</div>
|
|
</div>
|
|
<button class="knop" disabled="@Bezig" @onclick="Zoeken">Zoeken</button>
|
|
</div>
|
|
|
|
@if (Gevonden is { } profiel)
|
|
{
|
|
<div class="kaart">
|
|
<h2 style="margin-top:0">@profiel.DisplayName</h2>
|
|
|
|
<dl style="margin:0">
|
|
<div class="rij" style="padding:.25rem 0">
|
|
<span class="zacht" style="min-width:190px">E-mailadres</span>
|
|
<span class="groei">@(profiel.Email ?? "onbekend")</span>
|
|
</div>
|
|
<div class="rij" style="padding:.25rem 0">
|
|
<span class="zacht" style="min-width:190px">Eigen account</span>
|
|
<span class="groei">@(profiel.IsGeclaimd ? "ja" : "nee, het profiel is nog niet geclaimd")</span>
|
|
</div>
|
|
<div class="rij" style="padding:.25rem 0">
|
|
<span class="zacht" style="min-width:190px">Meldingen</span>
|
|
<span class="groei">@profiel.Meldingen van @profiel.MeldingenNodig</span>
|
|
</div>
|
|
<div class="rij" style="padding:.25rem 0">
|
|
<span class="zacht" style="min-width:190px">Stand</span>
|
|
<span class="groei">@Stand(profiel.Stand)</span>
|
|
</div>
|
|
<div class="rij" style="padding:.25rem 0">
|
|
<span class="zacht" style="min-width:190px">Kan inloggen</span>
|
|
<span class="groei">@(profiel.AccountGeblokkeerd ? "nee, het account staat op overleden" : "ja")</span>
|
|
</div>
|
|
@if (profiel.OpgegaanIn is { } opgegaan)
|
|
{
|
|
<div class="rij" style="padding:.25rem 0">
|
|
<span class="zacht" style="min-width:190px">Opgegaan in</span>
|
|
<span class="groei">profiel @opgegaan</span>
|
|
</div>
|
|
}
|
|
</dl>
|
|
|
|
@if (profiel.OverlijdenBevestigdOp is { } bevestigd)
|
|
{
|
|
<div class="melding let-op" style="margin-top:1rem">
|
|
<p style="margin-top:0">
|
|
Het overlijden is bevestigd op @Datums.Datum(bevestigd)@(
|
|
profiel.Overlijdensdatum is { } datum ? $", met {Datums.Datum(datum)} als overlijdensdatum" : "").
|
|
</p>
|
|
<p>
|
|
Terugdraaien betekent: alle meldingen vervallen, het account wordt weer
|
|
actief, en de toegang die de nalatenschapsbeheerder had gekregen wordt
|
|
ingetrokken.
|
|
</p>
|
|
|
|
<div class="veld">
|
|
<label for="reden">Waarom draai je dit terug?</label>
|
|
<input id="reden" @bind="Reden" placeholder="bijvoorbeeld: telefonisch gesproken, leeft" />
|
|
<div class="uitleg">Dit komt in het logboek te staan, bij jouw naam.</div>
|
|
</div>
|
|
|
|
<button class="knop gevaar" disabled="@Bezig" @onclick="Herstellen">
|
|
Overlijden terugdraaien
|
|
</button>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<p class="zacht" style="margin-bottom:0">
|
|
Voor dit profiel is geen overlijden bevestigd. Er valt daar dus niets terug te draaien.
|
|
</p>
|
|
}
|
|
</div>
|
|
|
|
@* ------------------------------------------------------------ samenvoegingen *@
|
|
<div class="kaart">
|
|
<h2 style="margin-top:0">Samenvoegingen</h2>
|
|
|
|
@if (profiel.Samenvoegingen.Count == 0)
|
|
{
|
|
<p class="zacht" style="margin-bottom:0">
|
|
Dit profiel is nooit samengevoegd. Let op: samenvoegingen van vóór 15 september
|
|
2026 zijn niet vastgelegd en kunnen daarom ook niet worden teruggedraaid.
|
|
</p>
|
|
}
|
|
else
|
|
{
|
|
<p class="zacht" style="margin-top:0">
|
|
Terugdraaien zet alles wat toen is verhuisd terug bij het profiel dat is
|
|
opgegaan. Wat er ná die tijd bij het andere profiel is bijgekomen, blijft
|
|
daar staan — dat is daar aangemaakt en hoort daar.
|
|
</p>
|
|
|
|
<div class="stapel">
|
|
@foreach (var samenvoeging in profiel.Samenvoegingen)
|
|
{
|
|
<div class="kaart compact">
|
|
<div class="rij">
|
|
<div class="groei">
|
|
<span class="groot">
|
|
@samenvoeging.DuplicaatNaam (@samenvoeging.DuplicaatPersonId)
|
|
→ @samenvoeging.DoelNaam (@samenvoeging.DoelPersonId)
|
|
</span>
|
|
<div class="zacht">
|
|
@Datums.DatumTijd(samenvoeging.SamengevoegdOp)
|
|
· @samenvoeging.Stappen stap@(samenvoeging.Stappen == 1 ? "" : "pen")
|
|
</div>
|
|
</div>
|
|
|
|
@if (samenvoeging.IsTeruggedraaid)
|
|
{
|
|
<span class="label">Teruggedraaid</span>
|
|
}
|
|
</div>
|
|
|
|
@if (!samenvoeging.IsTeruggedraaid)
|
|
{
|
|
<div class="veld" style="margin-top:.8rem">
|
|
<label for="reden-@samenvoeging.Id">Waarom draai je dit terug?</label>
|
|
<input id="reden-@samenvoeging.Id" @bind="Terugreden"
|
|
placeholder="bijvoorbeeld: verkeerde uitnodiging aangenomen" />
|
|
<div class="uitleg">Dit komt in het logboek te staan, bij jouw naam.</div>
|
|
</div>
|
|
|
|
<button class="knop gevaar" disabled="@Bezig"
|
|
@onclick="() => TerugdraaienAsync(samenvoeging.Id)">
|
|
Samenvoeging terugdraaien
|
|
</button>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
}
|
|
|
|
@code {
|
|
[CascadingParameter] private Task<AuthenticationState>? AuthState { get; set; }
|
|
|
|
private long _gebruikerId;
|
|
private bool Magdit, Bezig;
|
|
|
|
private string Adres = "";
|
|
private string Reden = "";
|
|
private string Terugreden = "";
|
|
private BeheerService.Profielbeeld? Gevonden;
|
|
private string? Melding, MeldingSoort;
|
|
private Statistieken? Stats;
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
if (AuthState is null) return;
|
|
_gebruikerId = (await AuthState).User.UserId() ?? 0;
|
|
Magdit = await Beheerkant.IsBeheerderAsync(_gebruikerId);
|
|
if (!Magdit) return;
|
|
|
|
var statistieken = await Beheerkant.StatistiekenAsync(_gebruikerId);
|
|
if (statistieken.Succeeded) Stats = statistieken.Value;
|
|
}
|
|
|
|
/// <summary>Breedte van de staaf voor deze categorie, ten opzichte van de grootste categorie.</summary>
|
|
private int CategoriePercentage(int aantal)
|
|
{
|
|
var grootste = Stats!.ItemsPerCategorie.Values.Max();
|
|
return grootste == 0 ? 0 : Math.Max(2, aantal * 100 / grootste);
|
|
}
|
|
|
|
private async Task Zoeken()
|
|
{
|
|
Bezig = true;
|
|
Melding = null;
|
|
Gevonden = null;
|
|
try
|
|
{
|
|
var resultaat = await Beheerkant.ZoekOpAdresAsync(Adres, _gebruikerId);
|
|
if (resultaat.Succeeded) Gevonden = resultaat.Value;
|
|
else { Melding = resultaat.Error; MeldingSoort = "fout"; }
|
|
}
|
|
finally { Bezig = false; }
|
|
}
|
|
|
|
private async Task Herstellen()
|
|
{
|
|
if (Gevonden is not { } profiel) return;
|
|
|
|
Bezig = true;
|
|
Melding = null;
|
|
try
|
|
{
|
|
var resultaat = await Beheerkant.HerstelOverlijdenAsync(profiel.PersonId, _gebruikerId, Reden);
|
|
if (!resultaat.Succeeded) { Melding = resultaat.Error; MeldingSoort = "fout"; return; }
|
|
|
|
Melding = $"Het overlijden van {profiel.DisplayName} is teruggedraaid. " +
|
|
"Hij of zij kan weer inloggen, en de nalatenschapsbeheerder is zijn toegang kwijt.";
|
|
MeldingSoort = "goed";
|
|
Reden = "";
|
|
|
|
// Opnieuw ophalen, zodat het scherm laat zien wat er nu staat.
|
|
var opnieuw = await Beheerkant.ZoekOpAdresAsync(Adres, _gebruikerId);
|
|
Gevonden = opnieuw.Succeeded ? opnieuw.Value : null;
|
|
}
|
|
finally { Bezig = false; }
|
|
}
|
|
|
|
private async Task TerugdraaienAsync(long samenvoegingId)
|
|
{
|
|
Bezig = true;
|
|
Melding = null;
|
|
try
|
|
{
|
|
var resultaat = await Beheerkant.TerugdraaienAsync(samenvoegingId, _gebruikerId, Terugreden);
|
|
if (!resultaat.Succeeded) { Melding = resultaat.Error; MeldingSoort = "fout"; return; }
|
|
|
|
Melding = "De samenvoeging is teruggedraaid. De twee profielen staan weer los van elkaar.";
|
|
MeldingSoort = "goed";
|
|
Terugreden = "";
|
|
|
|
var opnieuw = await Beheerkant.ZoekOpAdresAsync(Adres, _gebruikerId);
|
|
Gevonden = opnieuw.Succeeded ? opnieuw.Value : null;
|
|
}
|
|
finally { Bezig = false; }
|
|
}
|
|
|
|
private static string Stand(DeathProcedureState stand) => stand switch
|
|
{
|
|
DeathProcedureState.Geen => "geen meldingen",
|
|
DeathProcedureState.Lopend => "procedure loopt",
|
|
DeathProcedureState.Betwist => "tegengesproken door de betrokkene",
|
|
DeathProcedureState.Bevestigd => "overlijden bevestigd",
|
|
_ => "onbekend"
|
|
};
|
|
}
|