Upload feature (c): atomic UI components (atoms/molecules/organisms) + stories

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 06:25:30 +02:00
parent c4bfe9d39b
commit 9521739ac1
18 changed files with 677 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
import { Component, input, output } from '@angular/core';
import type { DeliveryChannel, UploadState } from '@shared/upload/upload.machine';
import { DocumentCategoryComponent } from '../document-category/document-category.component';
import { UploadStatusBannerComponent } from '../upload-status-banner/upload-status-banner.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, UploadStatusBannerComponent],
styles: [`
:host { display: flex; flex-direction: column; gap: var(--rhc-space-max-xl); }
`],
template: `
@if (state().categoriesError) {
<app-upload-status-banner type="error" [message]="state().categoriesError!" />
} @else {
@if (state().backgroundSyncAvailable === false) {
<app-upload-status-banner type="info" [message]="foregroundOnlyMessage" />
}
@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]"
(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>();
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';
}
}