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 { 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';
|
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', () => {
|
describe('CaseDetailPage', () => {
|
||||||
let component: CaseDetailPage;
|
|
||||||
let fixture: ComponentFixture<CaseDetailPage>;
|
let fixture: ComponentFixture<CaseDetailPage>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
it('given a legacy/:id route, fetches via getLegacyDetail and renders the case', async () => {
|
||||||
await TestBed.configureTestingModule({
|
const getLegacyDetail = vi.fn().mockReturnValue(of(legacyDetail));
|
||||||
imports: [CaseDetailPage],
|
const getOwnedDetail = vi.fn();
|
||||||
}).compileComponents();
|
await configure('legacy/:id', { id: '1001' }, getLegacyDetail, getOwnedDetail);
|
||||||
|
|
||||||
fixture = TestBed.createComponent(CaseDetailPage);
|
fixture = TestBed.createComponent(CaseDetailPage);
|
||||||
component = fixture.componentInstance;
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
expect(getLegacyDetail).toHaveBeenCalledWith('1001');
|
||||||
|
expect(getOwnedDetail).not.toHaveBeenCalled();
|
||||||
|
expect(fixture.nativeElement.textContent).toContain('de Vries');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create', () => {
|
it('given an owned/:id route, fetches via getOwnedDetail', async () => {
|
||||||
expect(component).toBeTruthy();
|
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({
|
@Component({
|
||||||
selector: 'app-case-detail',
|
selector: 'app-case-detail',
|
||||||
imports: [],
|
imports: [CaseActions, KeyValuePipe],
|
||||||
templateUrl: './case-detail.html',
|
templateUrl: './case-detail.html',
|
||||||
styleUrl: './case-detail.css',
|
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