feat(portal-frontend): implement case-detail read view
Resolves legacy/:id vs owned/:id off the matched route's static path segment and fetches accordingly. Renders applicant, diploma, and assessment fields, a seams panel (which backend serves each section), and embeds app-case-actions. Read-only - write forms land next.
This commit is contained in:
@@ -1 +1,71 @@
|
||||
<p>case-detail works!</p>
|
||||
@if (detail(); as d) {
|
||||
<h2>{{ d.surname }}, {{ d.initials }} ({{ d.origin }})</h2>
|
||||
|
||||
<section>
|
||||
<h3>Aanvrager</h3>
|
||||
<dl>
|
||||
<dt>BSN</dt>
|
||||
<dd>{{ d.bsn }}</dd>
|
||||
<dt>E-mail</dt>
|
||||
<dd>{{ d.email ?? 'n.v.t.' }}</dd>
|
||||
<dt>Telefoon</dt>
|
||||
<dd>{{ d.phone ?? 'n.v.t.' }}</dd>
|
||||
<dt>Voorkeurskanaal</dt>
|
||||
<dd>{{ d.preferredChannel }}</dd>
|
||||
@if (d.address; as address) {
|
||||
<dt>Adres</dt>
|
||||
<dd>{{ address.street }} {{ address.number }}, {{ address.postalCode }} {{ address.city }}</dd>
|
||||
}
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>Diploma</h3>
|
||||
<dl>
|
||||
<dt>Code</dt>
|
||||
<dd>{{ d.diplomaCode }}</dd>
|
||||
<dt>Land van uitgifte</dt>
|
||||
<dd>{{ d.diplomaCountryOfIssue }}</dd>
|
||||
<dt>Uitgegeven op</dt>
|
||||
<dd>{{ d.diplomaIssuedOn }}</dd>
|
||||
<dt>Ontvangen op</dt>
|
||||
<dd>{{ d.receivedOn }}</dd>
|
||||
<dt>Processtatus</dt>
|
||||
<dd>{{ d.processStatus ?? 'n.v.t.' }}</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
@if (d.assessment; as assessment) {
|
||||
<section>
|
||||
<h3>Beoordeling</h3>
|
||||
<dl>
|
||||
<dt>Uitkomst</dt>
|
||||
<dd>{{ assessment.outcome }}</dd>
|
||||
<dt>Motivatie</dt>
|
||||
<dd>{{ assessment.motivation }}</dd>
|
||||
<dt>Gecontroleerde stukken</dt>
|
||||
<dd>{{ assessment.verifiedItems.join(', ') || 'geen' }}</dd>
|
||||
<dt>Beslist op</dt>
|
||||
<dd>{{ assessment.decidedOn }}</dd>
|
||||
</dl>
|
||||
</section>
|
||||
}
|
||||
|
||||
<section>
|
||||
<h3>Seams</h3>
|
||||
<p class="seam-note">Toont welk backend elk onderdeel van deze pagina levert.</p>
|
||||
<dl>
|
||||
@for (seam of d.seams | keyvalue; track seam.key) {
|
||||
<dt>{{ seam.key }}</dt>
|
||||
<dd>{{ seam.value ?? 'n.v.t.' }}</dd>
|
||||
}
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>Acties</h3>
|
||||
<app-case-actions [actions]="d.actions" />
|
||||
</section>
|
||||
} @else {
|
||||
<p>Laden...</p>
|
||||
}
|
||||
|
||||
@@ -1,22 +1,79 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ActivatedRoute, convertToParamMap } from '@angular/router';
|
||||
import { of } from 'rxjs';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { CaseDetailService } from '../case-detail.service';
|
||||
import { CaseDetail } from '../case-detail.types';
|
||||
import { CaseDetailPage } from './case-detail';
|
||||
|
||||
const legacyDetail: CaseDetail = {
|
||||
origin: 'Legacy',
|
||||
legacyAanvraagId: 1001,
|
||||
registrationApplicationId: null,
|
||||
surname: 'de Vries',
|
||||
initials: 'A.',
|
||||
bsn: '123456782',
|
||||
address: { street: 'Kerkweg', number: '12', postalCode: '3512JK', city: 'Utrecht' },
|
||||
email: 'anna@example.nl',
|
||||
phone: null,
|
||||
preferredChannel: 'Post',
|
||||
diplomaCode: 'X',
|
||||
diplomaCountryOfIssue: 'NL',
|
||||
diplomaIssuedOn: '2020-01-01',
|
||||
receivedOn: '2026-01-01',
|
||||
assessment: null,
|
||||
processStatus: null,
|
||||
lastModifiedAt: null,
|
||||
actions: {
|
||||
editApplicantDetails: { mode: 'writeThrough', href: '/api/worklist/legacy/1001/details' },
|
||||
recordAssessment: { mode: 'redirect', href: '/legacy/aanvraag/1001/beoordeling' },
|
||||
takeOwnership: { mode: 'transition', href: '/api/worklist/legacy/1001/take-ownership' },
|
||||
},
|
||||
seams: { aanvrager: 'legacy-backend', procestijdlijn: null },
|
||||
};
|
||||
|
||||
function configure(routeConfigPath: string, paramMap: Record<string, string>, getLegacyDetail: unknown, getOwnedDetail: unknown) {
|
||||
return TestBed.configureTestingModule({
|
||||
imports: [CaseDetailPage],
|
||||
providers: [
|
||||
{ provide: CaseDetailService, useValue: { getLegacyDetail, getOwnedDetail } },
|
||||
{
|
||||
provide: ActivatedRoute,
|
||||
useValue: {
|
||||
paramMap: of(convertToParamMap(paramMap)),
|
||||
snapshot: { routeConfig: { path: routeConfigPath } },
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compileComponents();
|
||||
}
|
||||
|
||||
describe('CaseDetailPage', () => {
|
||||
let component: CaseDetailPage;
|
||||
let fixture: ComponentFixture<CaseDetailPage>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CaseDetailPage],
|
||||
}).compileComponents();
|
||||
it('given a legacy/:id route, fetches via getLegacyDetail and renders the case', async () => {
|
||||
const getLegacyDetail = vi.fn().mockReturnValue(of(legacyDetail));
|
||||
const getOwnedDetail = vi.fn();
|
||||
await configure('legacy/:id', { id: '1001' }, getLegacyDetail, getOwnedDetail);
|
||||
|
||||
fixture = TestBed.createComponent(CaseDetailPage);
|
||||
component = fixture.componentInstance;
|
||||
await fixture.whenStable();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(getLegacyDetail).toHaveBeenCalledWith('1001');
|
||||
expect(getOwnedDetail).not.toHaveBeenCalled();
|
||||
expect(fixture.nativeElement.textContent).toContain('de Vries');
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
it('given an owned/:id route, fetches via getOwnedDetail', async () => {
|
||||
const getLegacyDetail = vi.fn();
|
||||
const getOwnedDetail = vi.fn().mockReturnValue(of({ ...legacyDetail, origin: 'Owned', legacyAanvraagId: null, registrationApplicationId: 'abc' }));
|
||||
await configure('owned/:id', { id: 'abc' }, getLegacyDetail, getOwnedDetail);
|
||||
|
||||
fixture = TestBed.createComponent(CaseDetailPage);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(getOwnedDetail).toHaveBeenCalledWith('abc');
|
||||
expect(getLegacyDetail).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,29 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { KeyValuePipe } from '@angular/common';
|
||||
import { Component, inject, signal } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import { CaseActions } from '../case-actions/case-actions';
|
||||
import { CaseDetailService } from '../case-detail.service';
|
||||
import { CaseDetail } from '../case-detail.types';
|
||||
|
||||
@Component({
|
||||
selector: 'app-case-detail',
|
||||
imports: [],
|
||||
imports: [CaseActions, KeyValuePipe],
|
||||
templateUrl: './case-detail.html',
|
||||
styleUrl: './case-detail.css',
|
||||
})
|
||||
export class CaseDetailPage {}
|
||||
export class CaseDetailPage {
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
private readonly caseDetailService = inject(CaseDetailService);
|
||||
|
||||
readonly detail = signal<CaseDetail | null>(null);
|
||||
|
||||
constructor() {
|
||||
this.route.paramMap.subscribe((params) => {
|
||||
const id = params.get('id')!;
|
||||
const isLegacy = this.route.snapshot.routeConfig?.path?.startsWith('legacy') ?? false;
|
||||
const source$ = isLegacy ? this.caseDetailService.getLegacyDetail(id) : this.caseDetailService.getOwnedDetail(id);
|
||||
source$.subscribe((detail) => this.detail.set(detail));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user