The folder now equals the layer, as CLAUDE.md decision 2 requires. 33 directories move by git mv (25 flat, plus upload/'s 8 subfolders split across all three layers). 28 distinct @shared/ui/* specifiers rewrite across 73 files, longest-first. Five relative imports inside upload/ become @shared/ui aliases because their sibling now lives in a different layer; two stay relative because both ends stay in the same layer. Four .mdx docs get their seven broken story imports fixed; atomic-design.mdx's page-shell import is untouched, because layout/ does not move. No component, template, story title, or layer-tag comment changes. That is RD-28's job. Verified against the ticket's acceptance commands: the 26 flat directories become exactly 3 layer folders with the counts the ticket names, only three @shared/ui/* prefixes remain (atoms, molecules, organisms), the .mdx import count holds at 7, and the relative-import count inside ui/ drops from 7 to 2 as decision 4 requires. The @shared/ui/ occurrence count moves from 200 to 205: decision 4 mandates turning 5 of those 7 relative imports into @shared/ui/* aliases, which decision 3's "200 before, 200 after" check does not account for. The 5-occurrence gap is exactly the 5 conversions decision 4 names, not a lost or duplicated specifier. npm run ci --full passes: lint, typecheck, dep:check, format, tokens, seam, both apps' + both libraries' tests, both apps' localized build, audit, backend tests, all three generated-artifact drift checks, and both Storybook instances' build + axe-core a11y suite (67+45 suites, 198+112 tests, all green). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
65 lines
2.7 KiB
TypeScript
65 lines
2.7 KiB
TypeScript
import { Component, input, output } from '@angular/core';
|
|
import type { DeliveryChannel, UploadState } from '@shared/domain/upload.machine';
|
|
import { AlertComponent } from '@shared/ui/atoms/alert/alert.component';
|
|
import { DocumentCategoryComponent } from '../document-category/document-category.component';
|
|
|
|
/** Organism: the full document-upload step — the category list, or a load-error
|
|
banner. Pure UI: re-exposes the category events, tagging each with its category
|
|
where the parent needs it. The container wires these to the upload reducer. */
|
|
@Component({
|
|
selector: 'app-document-upload',
|
|
imports: [DocumentCategoryComponent, AlertComponent],
|
|
styles: [
|
|
`
|
|
:host {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--rhc-space-max-xl);
|
|
}
|
|
`,
|
|
],
|
|
template: `
|
|
@if (state().categoriesError) {
|
|
<app-alert type="error">{{ state().categoriesError }}</app-alert>
|
|
} @else {
|
|
@if (state().backgroundSyncAvailable === false && state().categories.length > 0) {
|
|
<app-alert type="info">{{ foregroundOnlyMessage }}</app-alert>
|
|
}
|
|
@for (c of state().categories; track c.categoryId) {
|
|
<app-document-category
|
|
[category]="c"
|
|
[uploads]="uploadsFor(c.categoryId)"
|
|
[channel]="channelFor(c.categoryId)"
|
|
[rejection]="state().rejections[c.categoryId]"
|
|
[previewUrlFor]="previewUrlFor()"
|
|
(fileSelected)="fileSelected.emit({ categoryId: c.categoryId, files: $event })"
|
|
(removeUpload)="removeUpload.emit($event)"
|
|
(retryUpload)="retryUpload.emit($event)"
|
|
(deleteUpload)="deleteUpload.emit($event)"
|
|
(channelChange)="channelChange.emit({ categoryId: c.categoryId, channel: $event })"
|
|
/>
|
|
}
|
|
}
|
|
`,
|
|
})
|
|
export class DocumentUploadComponent {
|
|
state = input.required<UploadState>();
|
|
/** Optional: builds a preview/download URL for a completed upload's documentId. */
|
|
previewUrlFor = input<(documentId: string) => string | undefined>();
|
|
|
|
fileSelected = output<{ categoryId: string; files: File[] }>();
|
|
removeUpload = output<string>();
|
|
retryUpload = output<string>();
|
|
deleteUpload = output<{ localId: string; documentId: string }>();
|
|
channelChange = output<{ categoryId: string; channel: DeliveryChannel }>();
|
|
|
|
protected readonly foregroundOnlyMessage = $localize`:@@upload.foregroundOnly:Uploads gaan alleen door zolang deze pagina open blijft.`;
|
|
|
|
protected uploadsFor(categoryId: string) {
|
|
return this.state().uploads.filter((u) => u.categoryId === categoryId);
|
|
}
|
|
protected channelFor(categoryId: string): DeliveryChannel {
|
|
return this.state().deliveryChannel[categoryId] ?? 'digital';
|
|
}
|
|
}
|