Model Registration status as a discriminated union
Each status variant now owns its own data: only Geregistreerd carries a herregistratieDatum; Geschorst/Doorgehaald carry their own dates + reason. A struck-off registration can no longer hold a future herregistratie date — that impossible combination is gone from the type. - status-badge keys color off the tag via a switch + assertNever - registration-summary renders only the rows a variant's data supports - registration.json nests the status; stories cover all three variants Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -2,8 +2,7 @@
|
|||||||
"bigNummer": "19012345601",
|
"bigNummer": "19012345601",
|
||||||
"naam": "Dr. A. (Anna) de Vries",
|
"naam": "Dr. A. (Anna) de Vries",
|
||||||
"beroep": "Arts",
|
"beroep": "Arts",
|
||||||
"status": "Geregistreerd",
|
|
||||||
"registratiedatum": "2012-09-01",
|
"registratiedatum": "2012-09-01",
|
||||||
"herregistratieDatum": "2027-09-01",
|
"geboortedatum": "1985-03-14",
|
||||||
"geboortedatum": "1985-03-14"
|
"status": { "tag": "Geregistreerd", "herregistratieDatum": "2027-09-01" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { Component, computed, input } from '@angular/core';
|
import { Component, computed, input } from '@angular/core';
|
||||||
import { RegistrationStatus } from '../../core/models';
|
import { StatusTag } from '../../core/models';
|
||||||
|
import { assertNever } from '../../core/fp';
|
||||||
|
|
||||||
/** Atom: status badge = colored RHC dot-badge + label. Hand-built to show how
|
/** Atom: status badge = colored RHC dot-badge + label. The color is a total
|
||||||
you compose a new atom from design tokens (dot color driven per status). */
|
function of the status tag — assertNever makes a new status fail to compile
|
||||||
|
until a color is chosen for it. */
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-status-badge',
|
selector: 'app-status-badge',
|
||||||
template: `
|
template: `
|
||||||
@@ -13,10 +15,18 @@ import { RegistrationStatus } from '../../core/models';
|
|||||||
`,
|
`,
|
||||||
})
|
})
|
||||||
export class StatusBadgeComponent {
|
export class StatusBadgeComponent {
|
||||||
status = input.required<RegistrationStatus>();
|
status = input.required<StatusTag>();
|
||||||
color = computed(() => ({
|
color = computed(() => {
|
||||||
Geregistreerd: 'var(--rhc-color-groen-500)',
|
const tag = this.status();
|
||||||
Doorgehaald: 'var(--rhc-color-rood-500)',
|
switch (tag) {
|
||||||
Geschorst: 'var(--rhc-color-oranje-500)',
|
case 'Geregistreerd':
|
||||||
}[this.status()]));
|
return 'var(--rhc-color-groen-500)';
|
||||||
|
case 'Doorgehaald':
|
||||||
|
return 'var(--rhc-color-rood-500)';
|
||||||
|
case 'Geschorst':
|
||||||
|
return 'var(--rhc-color-oranje-500)';
|
||||||
|
default:
|
||||||
|
return assertNever(tag);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,25 @@
|
|||||||
export type RegistrationStatus = 'Geregistreerd' | 'Doorgehaald' | 'Geschorst';
|
/**
|
||||||
|
* Registration status as a discriminated union: each variant owns exactly the
|
||||||
|
* data that makes sense for it. Only an active (Geregistreerd) registration has
|
||||||
|
* a herregistratie date; a struck-off (Doorgehaald) one cannot carry one. The
|
||||||
|
* old flat interface allowed that impossible combination — this makes it
|
||||||
|
* unrepresentable.
|
||||||
|
*/
|
||||||
|
export type RegistrationStatus =
|
||||||
|
| { tag: 'Geregistreerd'; herregistratieDatum: string } // ISO date
|
||||||
|
| { tag: 'Geschorst'; geschorstTot: string; reden: string }
|
||||||
|
| { tag: 'Doorgehaald'; doorgehaaldOp: string; reden: string };
|
||||||
|
|
||||||
|
/** Just the discriminant — for atoms that only need the label/color. */
|
||||||
|
export type StatusTag = RegistrationStatus['tag'];
|
||||||
|
|
||||||
export interface Registration {
|
export interface Registration {
|
||||||
bigNummer: string;
|
bigNummer: string;
|
||||||
naam: string;
|
naam: string;
|
||||||
beroep: string; // arts, verpleegkundige, apotheker, ...
|
beroep: string; // arts, verpleegkundige, apotheker, ...
|
||||||
status: RegistrationStatus;
|
|
||||||
registratiedatum: string; // ISO date
|
registratiedatum: string; // ISO date
|
||||||
herregistratieDatum: string;
|
|
||||||
geboortedatum: string;
|
geboortedatum: string;
|
||||||
|
status: RegistrationStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Aantekening {
|
export interface Aantekening {
|
||||||
|
|||||||
@@ -15,10 +15,23 @@ import { StatusBadgeComponent } from '../../atoms/status-badge/status-badge.comp
|
|||||||
<app-data-row key="Naam" [value]="reg().naam" />
|
<app-data-row key="Naam" [value]="reg().naam" />
|
||||||
<app-data-row key="Beroep" [value]="reg().beroep" />
|
<app-data-row key="Beroep" [value]="reg().beroep" />
|
||||||
<app-data-row key="Status">
|
<app-data-row key="Status">
|
||||||
<app-status-badge [status]="reg().status" />
|
<app-status-badge [status]="reg().status.tag" />
|
||||||
</app-data-row>
|
</app-data-row>
|
||||||
<app-data-row key="Registratiedatum" [value]="reg().registratiedatum | date:'longDate'" />
|
<app-data-row key="Registratiedatum" [value]="reg().registratiedatum | date:'longDate'" />
|
||||||
<app-data-row key="Uiterste herregistratie" [value]="reg().herregistratieDatum | date:'longDate'" />
|
<!-- Each status variant renders only the row its own data supports. -->
|
||||||
|
@switch (reg().status.tag) {
|
||||||
|
@case ('Geregistreerd') {
|
||||||
|
<app-data-row key="Uiterste herregistratie" [value]="$any(reg().status).herregistratieDatum | date:'longDate'" />
|
||||||
|
}
|
||||||
|
@case ('Geschorst') {
|
||||||
|
<app-data-row key="Geschorst tot" [value]="$any(reg().status).geschorstTot | date:'longDate'" />
|
||||||
|
<app-data-row key="Reden" [value]="$any(reg().status).reden" />
|
||||||
|
}
|
||||||
|
@case ('Doorgehaald') {
|
||||||
|
<app-data-row key="Doorgehaald op" [value]="$any(reg().status).doorgehaaldOp | date:'longDate'" />
|
||||||
|
<app-data-row key="Reden" [value]="$any(reg().status).reden" />
|
||||||
|
}
|
||||||
|
}
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
|
|||||||
@@ -2,15 +2,13 @@ import type { Meta, StoryObj } from '@storybook/angular';
|
|||||||
import { RegistrationSummaryComponent } from './registration-summary.component';
|
import { RegistrationSummaryComponent } from './registration-summary.component';
|
||||||
import { Registration } from '../../core/models';
|
import { Registration } from '../../core/models';
|
||||||
|
|
||||||
const sample: Registration = {
|
const base = {
|
||||||
bigNummer: '19012345601',
|
bigNummer: '19012345601',
|
||||||
naam: 'Dr. A. (Anna) de Vries',
|
naam: 'Dr. A. (Anna) de Vries',
|
||||||
beroep: 'Arts',
|
beroep: 'Arts',
|
||||||
status: 'Geregistreerd',
|
|
||||||
registratiedatum: '2012-09-01',
|
registratiedatum: '2012-09-01',
|
||||||
herregistratieDatum: '2027-09-01',
|
|
||||||
geboortedatum: '1985-03-14',
|
geboortedatum: '1985-03-14',
|
||||||
};
|
} satisfies Omit<Registration, 'status'>;
|
||||||
|
|
||||||
const meta: Meta<RegistrationSummaryComponent> = {
|
const meta: Meta<RegistrationSummaryComponent> = {
|
||||||
title: 'Organisms/Registration Summary',
|
title: 'Organisms/Registration Summary',
|
||||||
@@ -19,5 +17,14 @@ const meta: Meta<RegistrationSummaryComponent> = {
|
|||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<RegistrationSummaryComponent>;
|
type Story = StoryObj<RegistrationSummaryComponent>;
|
||||||
|
|
||||||
export const Default: Story = { args: { reg: sample } };
|
// Each story feeds a different union variant; the card renders only the rows
|
||||||
export const Geschorst: Story = { args: { reg: { ...sample, status: 'Geschorst' } } };
|
// that variant's data supports (note Doorgehaald has no herregistratie date).
|
||||||
|
export const Geregistreerd: Story = {
|
||||||
|
args: { reg: { ...base, status: { tag: 'Geregistreerd', herregistratieDatum: '2027-09-01' } } },
|
||||||
|
};
|
||||||
|
export const Geschorst: Story = {
|
||||||
|
args: { reg: { ...base, status: { tag: 'Geschorst', geschorstTot: '2026-12-31', reden: 'Lopend tuchtonderzoek' } } },
|
||||||
|
};
|
||||||
|
export const Doorgehaald: Story = {
|
||||||
|
args: { reg: { ...base, status: { tag: 'Doorgehaald', doorgehaaldOp: '2024-05-01', reden: 'Op eigen verzoek' } } },
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user