feat(WP-67): merge behandelportal into this repo as a monorepo
Restructures into apps/ssp + apps/behandelportal (two Angular projects) plus libs/shared + libs/beheer (cross-app libraries), replacing WP-61's separate sibling repo. That split had already produced real drift: a hand-vendored copy of the backend's OpenAPI doc, a shared/ui+layout tree forked and silently diverging (7 files), and beheer + the styles.scss token bridge duplicated byte-for-byte across both repos. - git mv the SSP's src/app/* into apps/ssp/; fold shared/, beheer/, environments/, the Storybook docs/*.mdx, and styles.scss into libs/shared + libs/beheer (all confirmed identical between the two repos before merging). auth stays deliberately duplicated per ADR-0002 (actor-specific, expected to diverge) - amended there. - One generated API client (libs/shared), no more vendored swagger.json. - .dependency-cruiser split into a base factory + one config per app, and Storybook into .storybook-ssp/.storybook-behandelportal - both forced by the @auth/* alias resolving to different directories per app. - SiteHeaderComponent/ShellComponent gained HEADER_NAV_ITEMS/ HEADER_ADMIN_LINKS/DEBUG_PANEL injection tokens so each app supplies its own nav/admin-links/dev-panel instead of one being hardcoded. - CLAUDE.md, ARCHITECTURE.md, dependencies.md, and ADR-0002 updated; WP-67 backlog entry documents the full decision trail. npm run ci green (lint, dep:check x2, 360 tests across ssp/ behandelportal/shared/beheer, both localized builds, backend tests, snippet + api-client drift); both dev servers, both Storybook instances, and docker compose verified working. The old sibling repo (/home/eho/repos/behandelportal) is left untouched, not deleted. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
import { Component, computed, input, output, signal } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { CheckboxComponent } from '@shared/ui/checkbox/checkbox.component';
|
||||
import { ButtonComponent } from '@shared/ui/button/button.component';
|
||||
import { TextInputComponent } from '@shared/ui/text-input/text-input.component';
|
||||
import { textOf } from '@shared/kernel/rich-text';
|
||||
import { LibraryPassage } from '@brief/domain/brief';
|
||||
|
||||
/** Molecule: multi-select list of the section's library passages. One "Voeg toe"
|
||||
inserts ALL checked passages at once (a single message upstream) — there is no
|
||||
single-insert path. Presentational: emits the chosen passages in list order.
|
||||
|
||||
Superseded by `besluit-panel` (WP-27's guided drafting): no consumer left in
|
||||
`src/app` outside its own story (WP-28 audit). Kept for now rather than deleted
|
||||
in-flight of an unrelated WP; a future cleanup can remove it. */
|
||||
@Component({
|
||||
selector: 'app-passage-picker',
|
||||
imports: [FormsModule, CheckboxComponent, ButtonComponent, TextInputComponent],
|
||||
styles: [
|
||||
`
|
||||
:host {
|
||||
display: block;
|
||||
background: var(--rhc-color-wit);
|
||||
border: var(--rhc-border-width-sm) solid var(--rhc-color-border-default);
|
||||
border-radius: var(--rhc-border-radius-md);
|
||||
padding: var(--rhc-space-max-md);
|
||||
}
|
||||
.search {
|
||||
margin-block-end: var(--rhc-space-max-md);
|
||||
}
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0 0 var(--rhc-space-max-md);
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: var(--rhc-space-max-sm);
|
||||
}
|
||||
.scope {
|
||||
color: var(--rhc-color-foreground-subtle);
|
||||
}
|
||||
.empty {
|
||||
color: var(--rhc-color-foreground-subtle);
|
||||
font-style: italic;
|
||||
margin: 0 0 var(--rhc-space-max-md);
|
||||
}
|
||||
`,
|
||||
],
|
||||
template: `
|
||||
<div class="search">
|
||||
<app-text-input
|
||||
[placeholder]="searchLabel()"
|
||||
[attr.aria-label]="searchLabel()"
|
||||
[ngModel]="query()"
|
||||
(ngModelChange)="query.set($event)"
|
||||
/>
|
||||
</div>
|
||||
<ul>
|
||||
@for (p of filtered(); track p.passageId) {
|
||||
<li>
|
||||
<app-checkbox
|
||||
[checkboxId]="'passage-' + p.passageId"
|
||||
[label]="p.label"
|
||||
[ngModel]="!!checked()[p.passageId]"
|
||||
(ngModelChange)="set(p.passageId, $event)"
|
||||
/>
|
||||
<span class="scope"> · {{ p.scope === 'beroep' ? beroepLabel() : globalLabel() }}</span>
|
||||
</li>
|
||||
} @empty {
|
||||
<li class="empty">{{ noMatchLabel() }}</li>
|
||||
}
|
||||
</ul>
|
||||
<app-button variant="secondary" [disabled]="count() === 0" (click)="add()"
|
||||
>{{ addLabel() }} ({{ count() }})</app-button
|
||||
>
|
||||
`,
|
||||
})
|
||||
export class PassagePickerComponent {
|
||||
passages = input.required<readonly LibraryPassage[]>();
|
||||
insert = output<LibraryPassage[]>();
|
||||
|
||||
addLabel = input($localize`:@@brief.picker.add:Voeg toe`);
|
||||
globalLabel = input($localize`:@@brief.picker.global:algemeen`);
|
||||
beroepLabel = input($localize`:@@brief.picker.beroep:beroepsspecifiek`);
|
||||
searchLabel = input($localize`:@@brief.picker.search:Zoek in standaardteksten…`);
|
||||
noMatchLabel = input($localize`:@@brief.picker.noMatch:Geen standaardteksten gevonden.`);
|
||||
|
||||
protected checked = signal<Record<string, boolean>>({});
|
||||
protected query = signal('');
|
||||
/** Client-side filter on label + rendered content text — the library is small, so no
|
||||
server search (WP-27). Placeholder keys are searchable too (see `textOf`). */
|
||||
protected filtered = computed(() => {
|
||||
const q = this.query().trim().toLowerCase();
|
||||
if (!q) return this.passages();
|
||||
return this.passages().filter(
|
||||
(p) => p.label.toLowerCase().includes(q) || textOf(p.content).includes(q),
|
||||
);
|
||||
});
|
||||
protected count = () => Object.values(this.checked()).filter(Boolean).length;
|
||||
|
||||
protected set(id: string, on: boolean) {
|
||||
this.checked.update((c) => ({ ...c, [id]: on }));
|
||||
}
|
||||
|
||||
protected add() {
|
||||
const chosen = this.passages().filter((p) => this.checked()[p.passageId]);
|
||||
if (chosen.length) {
|
||||
this.insert.emit(chosen);
|
||||
this.checked.set({});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user