Mijn aanvragen (F1): ApplicationsStore + dashboard blocks
The payoff — A–E become visible. The dashboard now shows a "Mijn aanvragen"
section at the top with a block per application.
- ApplicationsStore (registratie/application, root): the list as a RemoteData
signal, parsed at the trust boundary; reload() (dashboard revisit reflects
server-computed auto-approval); optimistic cancel (hide → reload / un-hide on fail).
- aanvraag-block (organism): badge (tag → colour/label) + per-status body
("Stap X van Y" / referentie + ingediend-datum / manual note / reden) + actions
from the pure blockActions. Composes card + status-badge + button. Stories per status.
- dashboard: "Mijn aanvragen" section (hidden when empty), sorted Concept → In
behandeling → resolved; Verder gaan deep-links the wizard (?aanvraag=<id>),
Annuleren cancels via the store.
Deferred to F2: document-chip preview/download affordance.
Gates green: vitest 128, lint, build, check:tokens; backend dotnet 56.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
import { Injectable, computed, inject, signal } from '@angular/core';
|
||||
import { RemoteData, fromResource } from '@shared/application/remote-data';
|
||||
import { Aanvraag } from '@registratie/domain/aanvraag';
|
||||
import { ApplicationsAdapter, parseApplications } from '@registratie/infrastructure/applications.adapter';
|
||||
|
||||
type Err = Error | undefined;
|
||||
|
||||
/**
|
||||
* The dashboard's view of the user's applications (aanvragen) — the backend is the
|
||||
* system of record (PRD 0001). One root singleton owns the list resource and exposes
|
||||
* it as a RemoteData signal, validated at the trust boundary (DTO → domain). Cancel
|
||||
* is optimistic (hide immediately, reload to confirm, un-hide on failure); `reload`
|
||||
* re-fetches so a page revisit reflects auto-approval (Concept → In behandeling →
|
||||
* Goedgekeurd is computed server-side on read).
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ApplicationsStore {
|
||||
private adapter = inject(ApplicationsAdapter);
|
||||
private res = this.adapter.applicationsResource();
|
||||
/** Ids optimistically hidden while their cancel is in flight (or done). */
|
||||
private cancelling = signal<ReadonlySet<string>>(new Set());
|
||||
|
||||
readonly applications = computed<RemoteData<Err, Aanvraag[]>>(() => {
|
||||
const rd = fromResource(this.res);
|
||||
if (rd.tag !== 'Success') return rd;
|
||||
const parsed = parseApplications(rd.value ?? []);
|
||||
if (!parsed.ok) return { tag: 'Failure', error: new Error(parsed.error) };
|
||||
const hidden = this.cancelling();
|
||||
return { tag: 'Success', value: parsed.value.filter((a) => !hidden.has(a.id)) };
|
||||
});
|
||||
|
||||
/** Re-fetch (e.g. on dashboard revisit) so auto-approval transitions show up. */
|
||||
reload() {
|
||||
this.res.reload();
|
||||
}
|
||||
|
||||
/** Cancel a Concept: hide it optimistically, then confirm (reload) or roll back. */
|
||||
async cancel(id: string) {
|
||||
this.cancelling.update((s) => new Set(s).add(id));
|
||||
try {
|
||||
await this.adapter.cancel(id);
|
||||
this.res.reload(); // the reloaded list no longer contains it; the hide is now a no-op
|
||||
} catch {
|
||||
this.cancelling.update((s) => {
|
||||
const next = new Set(s);
|
||||
next.delete(id);
|
||||
return next; // roll back: the block reappears
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user