From db5b2bba668016bc36272160b5080672798c517b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Schultz=20Madsen?= Date: Thu, 3 Sep 2026 09:04:16 +0200 Subject: [PATCH] fix(eform-cases): resolve case id from an input, not the router URL ElementPictureComponent read its case id only from ActivatedRoute.params ('id', falling back to 'sdkCaseId'). Inside a MatDialog the injected route is the route the dialog was opened from, so on a route declaring neither param both lookups yield NaN. That NaN was posted as the literal string CaseId=NaN, bound to 0 server-side (EFormFilesController has no [ApiController] and there is no ModelState filter), and picture upload failed with CaseNotFound -- Danish "Sagen blev ikke fundet". Thread an optional caseId down the shared chain instead: case-edit-element -> case-edit-switch -> element-picture The bound input wins; the route lookup stays as a fallback, so the three routed consumers (cases/edit/:id, compliance/:sdkCaseId, and backend-configuration-case/:id) are behaviourally unchanged -- none binds the input, and a non-finite or <= 0 value falls through to the route. Also close the nesting gaps in the same chain, all of which dropped the value one level down: - element-container (the FieldContainer branch) never received or forwarded caseId, so a Picture inside a question group reproduced the same NaN bug one level deeper. - element-container and the recursive case-edit-element forwarded no (needUpdate), so a nested picture would upload but never refresh the host gallery. Only reachable once the id was fixed. Extra pictures deliberately get [caseId] but no [fieldId]: they are ExtraFieldValue rows with no FieldId column and serialize as fieldId 0, and element-picture gates its card on *ngIf="fieldId != 0", so binding that would hide the card and its existing thumbnails. Tracked in #8032. Refs #8031 Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015sXLtgzZU8QL9m84GqMkoJ --- .../case-edit-element.component.html | 4 ++ .../case-edit-element.component.ts | 7 ++++ .../case-edit-switch.component.html | 6 ++- .../case-edit-switch.component.spec.ts | 1 + .../case-edit-switch.component.ts | 6 +++ .../element-container.component.html | 2 +- .../element-container.component.ts | 13 +++++- .../element-picture.component.ts | 41 +++++++++++++++---- 8 files changed, 68 insertions(+), 12 deletions(-) diff --git a/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-edit-element/case-edit-element.component.html b/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-edit-element/case-edit-element.component.html index 530730cf6a..6f8c4382fe 100644 --- a/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-edit-element/case-edit-element.component.html +++ b/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-edit-element/case-edit-element.component.html @@ -19,6 +19,7 @@

{{ element.label }} @@ -34,6 +35,7 @@

{{ 'Extra picture' | translate }}

@@ -44,7 +46,9 @@

{{ 'Extra recording' | translate }}

diff --git a/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-edit-element/case-edit-element.component.ts b/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-edit-element/case-edit-element.component.ts index bc7795207d..4a29bf4839 100644 --- a/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-edit-element/case-edit-element.component.ts +++ b/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-edit-element/case-edit-element.component.ts @@ -22,6 +22,13 @@ export class CaseEditElementComponent implements OnInit { * keeps the heading it has today. */ @Input() showSectionTitle = true; + /** + * Id of the case being edited. Routed case pages may omit it — the picture + * element still falls back to the route params. Dialog hosts must supply it, + * because the ActivatedRoute they inject is the route the dialog was opened + * from and carries no case id at all. + */ + @Input() caseId?: number; @Output() needUpdate: EventEmitter = new EventEmitter(); requestModel: CaseEditRequest = new CaseEditRequest(); requestModels: Array = []; diff --git a/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-edit-switch/case-edit-switch.component.html b/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-edit-switch/case-edit-switch.component.html index 75df719de6..ac96de863b 100644 --- a/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-edit-switch/case-edit-switch.component.html +++ b/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-edit-switch/case-edit-switch.component.html @@ -18,7 +18,7 @@

{{dataItem.la - + @@ -59,7 +59,9 @@

{{dataItem.la + [dataItemLabel]="dataItem.label" + [caseId]="caseId" + (needUpdate)="emitNeedUpdate()"> diff --git a/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-edit-switch/case-edit-switch.component.spec.ts b/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-edit-switch/case-edit-switch.component.spec.ts index de455d4a81..e1d866d431 100644 --- a/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-edit-switch/case-edit-switch.component.spec.ts +++ b/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-edit-switch/case-edit-switch.component.spec.ts @@ -17,6 +17,7 @@ import {DataItemDto} from 'src/app/common/models'; class StubPictureComponent { @Input() fieldValues: any; @Input() fieldId: any; + @Input() caseId: any; } function makeDataItem(overrides: Partial = {}): DataItemDto { diff --git a/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-edit-switch/case-edit-switch.component.ts b/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-edit-switch/case-edit-switch.component.ts index 6e19d5a90a..c887cae300 100644 --- a/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-edit-switch/case-edit-switch.component.ts +++ b/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-edit-switch/case-edit-switch.component.ts @@ -8,6 +8,12 @@ import {DataItemDto} from 'src/app/common/models'; }) export class CaseEditSwitchComponent implements OnInit { @Input() dataItemList: Array = []; + /** + * Id of the case being edited, threaded down to the picture element. Optional + * — it is only needed where the ActivatedRoute cannot supply it, i.e. when + * the case editor is rendered inside a dialog rather than on a case route. + */ + @Input() caseId?: number; @Output() needUpdate: EventEmitter = new EventEmitter(); constructor() { } diff --git a/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-elements/element-container/element-container.component.html b/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-elements/element-container/element-container.component.html index 53465c468a..0defda93cf 100644 --- a/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-elements/element-container/element-container.component.html +++ b/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-elements/element-container/element-container.component.html @@ -5,6 +5,6 @@
{{dataItemLabel}}
{{isCollapsed ? 'chevron_right' : 'expand_more'}} - + diff --git a/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-elements/element-container/element-container.component.ts b/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-elements/element-container/element-container.component.ts index 38ff6b5c71..6f409a36ba 100644 --- a/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-elements/element-container/element-container.component.ts +++ b/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-elements/element-container/element-container.component.ts @@ -1,4 +1,4 @@ -import { Component, Input} from '@angular/core'; +import { Component, EventEmitter, Input, Output} from '@angular/core'; import { DataItemDto } from 'src/app/common/models'; @Component({ @@ -12,6 +12,12 @@ export class ElementContainerComponent { dataItemList: Array = []; isCollapsed = true; @Input() dataItemLabel: string; + /** + * Id of the case being edited, threaded on down to the nested switch so a + * Picture field grouped inside this container still resolves a case id when + * the editor is rendered inside a dialog rather than on a case route. + */ + @Input() caseId?: number; @Input() get fieldValue() { return this.dataItemList; @@ -19,6 +25,11 @@ export class ElementContainerComponent { set fieldValue(val) { this.dataItemList = val; } + @Output() needUpdate: EventEmitter = new EventEmitter(); constructor() {} + + emitNeedUpdate() { + this.needUpdate.emit(); + } } diff --git a/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-elements/element-picture/element-picture.component.ts b/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-elements/element-picture/element-picture.component.ts index a4e5176736..eac5818954 100644 --- a/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-elements/element-picture/element-picture.component.ts +++ b/eform-client/src/app/common/modules/eform-cases/components/case-edit/case-elements/element-picture/element-picture.component.ts @@ -29,12 +29,20 @@ export class ElementPictureComponent implements OnChanges, OnDestroy { @Input() fieldValues: Array = []; @Input() fieldId: number; + /** + * Case id supplied by the host component. It takes precedence over the route + * params below: inside a MatDialog the injected ActivatedRoute is the route + * the dialog was opened from, which carries neither ':id' nor ':sdkCaseId', + * so the fallback would resolve to NaN and the upload would fail with + * CaseNotFound. Routed case pages may keep omitting it. + */ + @Input() caseId?: number; @Output() pictureUpdated: EventEmitter = new EventEmitter(); buttonsLocked = false; geoObjects = []; images = []; galleryImages: GalleryItem[] = []; - caseId: number; + private routeCaseId: number; imageSub$: Subscription; rotateImageSub$: Subscription; @@ -48,12 +56,14 @@ export class ElementPictureComponent implements OnChanges, OnDestroy { ngOnChanges(changes: SimpleChanges): void { if (changes && changes.fieldValues) { this.images = []; - this.activatedRouteSub$ = this.activateRoute.params.subscribe((params) => { - this.caseId = +params['id']; - if (isNaN(this.caseId)) { - this.caseId = +params['sdkCaseId']; - } - }); + if (!this.hasCaseIdInput) { + this.activatedRouteSub$ = this.activateRoute.params.subscribe((params) => { + this.routeCaseId = +params['id']; + if (isNaN(this.routeCaseId)) { + this.routeCaseId = +params['sdkCaseId']; + } + }); + } this.fieldValues.forEach(value => { if (value.uploadedDataObj) { this.geoObjects.push({ @@ -83,6 +93,21 @@ export class ElementPictureComponent implements OnChanges, OnDestroy { } } + /** + * True when the host explicitly bound a usable case id. + */ + private get hasCaseIdInput(): boolean { + return Number.isFinite(this.caseId) && this.caseId > 0; + } + + /** + * The bound input always wins; the route params are only a fallback for the + * routed case pages that do not pass the id down. + */ + private get resolvedCaseId(): number { + return this.hasCaseIdInput ? this.caseId : this.routeCaseId; + } + updateGallery() { this.galleryImages = []; this.images = this.images.sort((a, b) => a.fileName.localeCompare(b.fileName)); @@ -146,7 +171,7 @@ export class ElementPictureComponent implements OnChanges, OnDestroy { addPicture(newImage: File, modalId: string) { const fieldId = this.fieldId; this.addImageSub$ = this.imageService - .addNewImage(fieldId, this.caseId, newImage) + .addNewImage(fieldId, this.resolvedCaseId, newImage) .subscribe(data => { if (data && data.success) { this.dialog.getDialogById(modalId).close();