"Annuleren" rendered as a separate line below the grey card — visually disconnected from the choice it belongs to. Fixing this properly means the card can't just be one big <a> anymore: a <button> can't nest inside an anchor (invalid HTML, broken a11y), and choice-link's [choiceActions] slot needs Annuleren to sit inside the same box. choice-link.component.ts now makes the card a <div> always, with the title wrapped in a vendored Bootstrap `.stretched-link` (its ::after overlay keeps the whole card clickable, same as before) instead of the whole box being the anchor. The projected action gets its own `position:relative;z-index:2` (in aanvraag-block.component.ts, which owns that markup) to stay clickable above the stretched-link overlay. Added `:focus-within` on the card to restore the focus-accent CIBG's `:focus` rule would have given the card itself, since focus now lands on the inner title link. Verified: lint/check:tokens/test/build green; drove it end-to-end — clicking anywhere on a card body still resumes the wizard, clicking Annuleren cancels without navigating. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
79 lines
3.7 KiB
TypeScript
79 lines
3.7 KiB
TypeScript
import { Component, computed, input, output } from '@angular/core';
|
|
import { ButtonComponent } from '@shared/ui/button/button.component';
|
|
import { ChoiceLinkComponent } from '@shared/ui/choice-link/choice-link.component';
|
|
import { Aanvraag, AanvraagType } from '@registratie/domain/aanvraag';
|
|
import { blockActions } from '@registratie/domain/block-actions';
|
|
|
|
const TYPE_LABELS: Record<AanvraagType, string> = {
|
|
registratie: $localize`:@@aanvraagBlock.type.registratie:Inschrijving`,
|
|
herregistratie: $localize`:@@aanvraagBlock.type.herregistratie:Herregistratie`,
|
|
intake: $localize`:@@aanvraagBlock.type.intake:Herregistratie-intake`,
|
|
};
|
|
|
|
/** Organism: one application on the dashboard's "Mijn aanvragen" list, rendered as
|
|
a CIBG Huisstijl "keuzelijst" choice (choice-link). Which text/actions it shows
|
|
is driven by the pure `blockActions(status)` and the status tag; the block only
|
|
renders. Only a resumable Concept is clickable (stretched-link over the whole
|
|
card) — a resolved status has nothing to navigate to, so it renders as a plain
|
|
(non-interactive) card. */
|
|
@Component({
|
|
selector: 'app-aanvraag-block',
|
|
imports: [ButtonComponent, ChoiceLinkComponent],
|
|
styles: [`
|
|
:host{display:contents} /* see choice-link.component.ts (keeps <li> a direct <ul> child) */
|
|
/* Sits inside the same card as the stretched-link title — needs its own
|
|
stacking context above the stretched-link overlay (z-index:1) to stay clickable. */
|
|
.actions{position:relative;z-index:2;margin-block-start:var(--rhc-space-max-sm)}
|
|
`],
|
|
template: `
|
|
<app-choice-link
|
|
[heading]="typeLabel()"
|
|
[instructions]="instructions()"
|
|
[clickable]="actions().includes('resume')"
|
|
(activate)="resume.emit()">
|
|
@if (actions().includes('cancel')) {
|
|
<div choiceActions class="actions">
|
|
<app-button variant="subtle" (click)="cancel.emit()" i18n="@@aanvraagBlock.annuleren">Annuleren</app-button>
|
|
</div>
|
|
}
|
|
</app-choice-link>
|
|
`,
|
|
})
|
|
export class AanvraagBlockComponent {
|
|
aanvraag = input.required<Aanvraag>();
|
|
|
|
resume = output<void>();
|
|
cancel = output<void>();
|
|
|
|
protected typeLabel = computed(() => TYPE_LABELS[this.aanvraag().type]);
|
|
protected actions = computed(() => blockActions(this.aanvraag().status));
|
|
|
|
// Status-tag → the choice's description line (the UI's mapping, not a domain rule).
|
|
private statusText = computed(() => {
|
|
const s = this.aanvraag().status;
|
|
switch (s.tag) {
|
|
case 'Concept': return $localize`:@@aanvraagBlock.stap:Stap ${s.stepIndex + 1}:index: van ${s.stepCount}:count:`;
|
|
case 'InBehandeling': return $localize`:@@aanvraagBlock.inBehandeling:Referentie ${s.referentie}:ref: · ingediend op ${formatNL(this.aanvraag().submittedAt)}:datum:`;
|
|
case 'Goedgekeurd': return $localize`:@@aanvraagBlock.goedgekeurd:Referentie ${s.referentie}:ref:`;
|
|
case 'Afgewezen': return $localize`:@@aanvraagBlock.afgewezen:Referentie ${s.referentie}:ref:`;
|
|
}
|
|
});
|
|
// Secondary note appended to the status line, when there's one to show.
|
|
private subtitle = computed(() => {
|
|
const s = this.aanvraag().status;
|
|
if (s.tag === 'InBehandeling' && s.manual) return $localize`:@@aanvraagBlock.manual:Uw aanvraag wordt handmatig beoordeeld in de backoffice.`;
|
|
if (s.tag === 'Afgewezen') return s.reden;
|
|
return '';
|
|
});
|
|
// The keuzelijst has one description paragraph — combine status + secondary note.
|
|
protected instructions = computed(() => {
|
|
const status = this.statusText();
|
|
const sub = this.subtitle();
|
|
return sub ? `${status} ${sub}` : status;
|
|
});
|
|
}
|
|
|
|
function formatNL(iso?: string): string {
|
|
return iso ? new Date(iso).toLocaleDateString('nl-NL', { day: 'numeric', month: 'long', year: 'numeric' }) : '';
|
|
}
|