From ab45745eacde86700051893249e0169d44b148e9 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Tue, 19 May 2026 00:08:23 +0200 Subject: [PATCH 1/4] Fix frontend ESLint autocorrections Apply safe TypeScript ESLint cleanups and trailing whitespace fixes across frontend files. Keep DOM lookups typed and nullable when generic autocorrection would otherwise assert through missing elements or erase intended guards. --- .../editable-query-props.component.ts | 2 +- .../bim-manage-ifc-models-button.component.ts | 2 +- .../board/board-list/board-list.component.ts | 2 +- .../op-work-packages-calendar.service.ts | 8 +++--- .../te-calendar/te-calendar.component.ts | 1 + .../work-package-filter-values.spec.ts | 8 +++--- .../handlers/row/wp-state-links-handler.ts | 8 ++++-- .../wp-inline-create.component.ts | 3 ++- .../wp-list/wp-list-invalid-query.service.ts | 2 +- .../components/wp-list/wp-list.service.ts | 2 +- .../sort-header/sort-header.directive.ts | 2 +- .../timeline/cells/timeline-cell-renderer.ts | 4 ++- .../view-services/wp-view-group-by.service.ts | 2 +- .../basic-range-date-picker.component.ts | 2 +- .../basic-single-date-picker.component.ts | 2 +- .../ckeditor/ckeditor-setup.service.ts | 7 ++++-- .../wp-spent-time-display-field.module.ts | 2 +- .../hal-resource-edit-field-handler.ts | 2 +- .../project-edit-field.component.ts | 5 ++-- .../components/grids/grid/area.service.ts | 2 +- .../components/grids/grid/resize.service.ts | 2 +- .../wp-create-settings-menu.directive.ts | 2 +- .../persistent-toggle.component.ts | 2 +- .../searchable-project-list.service.ts | 2 +- .../directives/search-highlight.directive.ts | 2 +- .../shared/helpers/chronic_duration.spec.ts | 2 +- .../dynamic/admin/custom-fields.controller.ts | 10 +++++--- .../admin/hierarchy-item.controller.ts | 6 +++-- .../admin/progress-tracking.controller.ts | 4 +-- .../dynamic/admin/registration.controller.ts | 4 +-- .../costs/budget-subform.controller.ts | 3 ++- .../dynamic/hide-sections.controller.ts | 9 +++---- .../dynamic/menus/main.controller.ts | 3 +-- .../dynamic/my/time-tracking.controller.ts | 6 +++-- .../shares/bulk-selection.controller.ts | 6 ++--- .../dynamic/sort-by-config.controller.ts | 25 +++++++++++-------- .../date-picker/preview.controller.ts | 10 +++++--- .../export/generate-pdf-form.controller.ts | 6 ++--- frontend/src/turbo/turbo-global-listeners.ts | 2 +- 39 files changed, 99 insertions(+), 75 deletions(-) diff --git a/frontend/src/app/features/admin/editable-query-props/editable-query-props.component.ts b/frontend/src/app/features/admin/editable-query-props/editable-query-props.component.ts index 18bc34b5649..7cfe946d14c 100644 --- a/frontend/src/app/features/admin/editable-query-props/editable-query-props.component.ts +++ b/frontend/src/app/features/admin/editable-query-props/editable-query-props.component.ts @@ -54,7 +54,7 @@ export class EditableQueryPropsComponent implements OnInit { })(); this.externalQuery.show({ - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + currentQuery: queryProperties, urlParams: this.urlParams, callback: (queryProps:string) => { diff --git a/frontend/src/app/features/bim/ifc_models/toolbar/manage-ifc-models-button/bim-manage-ifc-models-button.component.ts b/frontend/src/app/features/bim/ifc_models/toolbar/manage-ifc-models-button/bim-manage-ifc-models-button.component.ts index dbd75aed06c..89a3e2af962 100644 --- a/frontend/src/app/features/bim/ifc_models/toolbar/manage-ifc-models-button/bim-manage-ifc-models-button.component.ts +++ b/frontend/src/app/features/bim/ifc_models/toolbar/manage-ifc-models-button/bim-manage-ifc-models-button.component.ts @@ -42,7 +42,7 @@ import { IfcModelsDataService } from 'core-app/features/bim/ifc_models/pages/vie aria-hidden="true"> } - + `, changeDetection: ChangeDetectionStrategy.OnPush, selector: 'op-bcf-manage-ifc-button', diff --git a/frontend/src/app/features/boards/board/board-list/board-list.component.ts b/frontend/src/app/features/boards/board/board-list/board-list.component.ts index 6b3df5dc4f6..4498aaae582 100644 --- a/frontend/src/app/features/boards/board/board-list/board-list.component.ts +++ b/frontend/src/app/features/boards/board/board-list/board-list.component.ts @@ -184,7 +184,7 @@ export class BoardListComponent extends AbstractWidgetComponent implements OnIni this.resource.isNewWidget = false; // Set initial selection if split view open - const detailsMatch = window.location.pathname.match(/\/details\/(\d+)/); + const detailsMatch = /\/details\/(\d+)/.exec(window.location.pathname); if (detailsMatch) { this.wpViewSelectionService.initializeSelection([detailsMatch[1]]); } diff --git a/frontend/src/app/features/calendar/op-work-packages-calendar.service.ts b/frontend/src/app/features/calendar/op-work-packages-calendar.service.ts index 641751a4e31..6e1c1d13a05 100644 --- a/frontend/src/app/features/calendar/op-work-packages-calendar.service.ts +++ b/frontend/src/app/features/calendar/op-work-packages-calendar.service.ts @@ -171,7 +171,7 @@ export class OpWorkPackagesCalendarService extends UntilDestroyedMixin { let queryId:string|null = null; if (this.urlParams.query_id) { - queryId = this.urlParams.query_id as string; + queryId = this.urlParams.query_id; } // We derive the necessary props in the following cases // 1. We load a queryId with no props @@ -199,7 +199,7 @@ export class OpWorkPackagesCalendarService extends UntilDestroyedMixin { // There might also be a query_id but the settings persisted in it are overwritten by the props. if (this.urlParams.query_props) { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const oldQueryProps:Record = JSON.parse(this.urlParams.query_props as string); + const oldQueryProps:Record = JSON.parse(this.urlParams.query_props); // Update the date period of the calendar in the filter const newQueryProps = { @@ -254,7 +254,7 @@ export class OpWorkPackagesCalendarService extends UntilDestroyedMixin { } public get initialView():string|undefined { - return this.urlParams.cview as string|undefined; + return this.urlParams.cview; } dateEditable(wp:WorkPackageResource):boolean { @@ -425,7 +425,7 @@ export class OpWorkPackagesCalendarService extends UntilDestroyedMixin { } private get initialDate():string|undefined { - const date = this.urlParams.cdate as string|undefined; + const date = this.urlParams.cdate; if (date) { return this.timezoneService.formattedISODate(date); } diff --git a/frontend/src/app/features/calendar/te-calendar/te-calendar.component.ts b/frontend/src/app/features/calendar/te-calendar/te-calendar.component.ts index 3d2f844f2b0..14c303257d9 100644 --- a/frontend/src/app/features/calendar/te-calendar/te-calendar.component.ts +++ b/frontend/src/app/features/calendar/te-calendar/te-calendar.component.ts @@ -239,6 +239,7 @@ export class TimeEntryCalendarComponent implements AfterViewInit, OnDestroy { } protected fetchTimeEntries(start:Moment, end:Moment):Promise> { + // eslint-disable-next-line @typescript-eslint/prefer-optional-chain if (!this.memoizedTimeEntries || this.memoizedTimeEntries.start.valueOf() !== start.valueOf() || this.memoizedTimeEntries.end.valueOf() !== end.valueOf()) { diff --git a/frontend/src/app/features/work-packages/components/wp-edit-form/work-package-filter-values.spec.ts b/frontend/src/app/features/work-packages/components/wp-edit-form/work-package-filter-values.spec.ts index db6dc6b67e3..227fd306d0d 100644 --- a/frontend/src/app/features/work-packages/components/wp-edit-form/work-package-filter-values.spec.ts +++ b/frontend/src/app/features/work-packages/components/wp-edit-form/work-package-filter-values.spec.ts @@ -135,12 +135,12 @@ describe('WorkPackageFilterValues', () => { setupTestBed(); }); - it('it should not apply the first value (Regression #30817)', (() => { + it('should not apply the first value (Regression #30817)', () => { subject.applyDefaultsFromFilters(changeset); expect(changeset.changedAttributes.length).toEqual(0); expect(changeset.value('type').href).toEqual('/api/v3/types/1'); - })); + }); }); describe('with the second type applied', () => { @@ -158,12 +158,12 @@ describe('WorkPackageFilterValues', () => { setupTestBed(); }); - it('it should not keep the second value (Regression #30817)', (() => { + it('should not keep the second value (Regression #30817)', () => { subject.applyDefaultsFromFilters(changeset); expect(changeset.changedAttributes.length).toEqual(0); expect(changeset.value('type').href).toEqual('/api/v3/types/2'); - })); + }); }); }); }); diff --git a/frontend/src/app/features/work-packages/components/wp-fast-table/handlers/row/wp-state-links-handler.ts b/frontend/src/app/features/work-packages/components/wp-fast-table/handlers/row/wp-state-links-handler.ts index 0dfd3be4e70..4c31aa91257 100644 --- a/frontend/src/app/features/work-packages/components/wp-fast-table/handlers/row/wp-state-links-handler.ts +++ b/frontend/src/app/features/work-packages/components/wp-fast-table/handlers/row/wp-state-links-handler.ts @@ -50,7 +50,9 @@ export class WorkPackageStateLinksHandler implements TableEventHandler { // Locate the details link from event const target = evt.target as HTMLElement; - const element = target.closest(this.SELECTOR) as HTMLElement; + const element = target.closest(this.SELECTOR); + if (!element) { return true; } + const state = element.dataset.wpState; const workPackageId = element.dataset.workPackageId; @@ -66,7 +68,9 @@ export class WorkPackageStateLinksHandler implements TableEventHandler { // not matter what other rows are (de-)selected below. // Thus save that row for the details view button. // Locate the row from event - const row = target.closest(`.${tableRowClassName}`) as HTMLElement; + const row = target.closest(`.${tableRowClassName}`); + if (!row) { return true; } + const classIdentifier = row.dataset.classIdentifier!; const [index] = view.workPackageTable.findRenderedRow(classIdentifier); diff --git a/frontend/src/app/features/work-packages/components/wp-inline-create/wp-inline-create.component.ts b/frontend/src/app/features/work-packages/components/wp-inline-create/wp-inline-create.component.ts index daecf685f38..6dd2af1b3c9 100644 --- a/frontend/src/app/features/work-packages/components/wp-inline-create/wp-inline-create.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-inline-create/wp-inline-create.component.ts @@ -187,7 +187,8 @@ export class WorkPackageInlineCreateComponent extends UntilDestroyedMixin implem this.untilDestroyed(), ) .subscribe((wp:WorkPackageResource) => { - if (this.currentWorkPackage && this.currentWorkPackage.__initialized_at === wp.__initialized_at) { + // eslint-disable-next-line no-underscore-dangle + if (this.currentWorkPackage?.__initialized_at === wp.__initialized_at) { // Remove row and focus this.resetRow(); diff --git a/frontend/src/app/features/work-packages/components/wp-list/wp-list-invalid-query.service.ts b/frontend/src/app/features/work-packages/components/wp-list/wp-list-invalid-query.service.ts index e576e7ca231..98fd9893d5e 100644 --- a/frontend/src/app/features/work-packages/components/wp-list/wp-list-invalid-query.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-list/wp-list-invalid-query.service.ts @@ -98,7 +98,7 @@ export class WorkPackagesListInvalidQueryService { } private restoreGroupBy(query:QueryResource, stubQuery:QueryResource, schema:SchemaResource) { - const groupBy = _.find((schema.groupBy.allowedValues as QueryGroupByResource[]), (candidate) => stubQuery.groupBy && stubQuery.groupBy.href === candidate.href) as any; + const groupBy = _.find((schema.groupBy.allowedValues as QueryGroupByResource[]), (candidate) => stubQuery.groupBy?.href === candidate.href) as any; query.groupBy = groupBy; } diff --git a/frontend/src/app/features/work-packages/components/wp-list/wp-list.service.ts b/frontend/src/app/features/work-packages/components/wp-list/wp-list.service.ts index ffa019a4d83..69dcfa5a859 100644 --- a/frontend/src/app/features/work-packages/components/wp-list/wp-list.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-list/wp-list.service.ts @@ -366,7 +366,7 @@ export class WorkPackagesListService { } // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (!currentForm || !query.$links.update || query.$links.update.href !== currentForm.href) { + if (!currentForm || query.$links.update?.href !== currentForm.href) { return this.loadForm(query); } diff --git a/frontend/src/app/features/work-packages/components/wp-table/sort-header/sort-header.directive.ts b/frontend/src/app/features/work-packages/components/wp-table/sort-header/sort-header.directive.ts index 4dd50182efe..8462334473b 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/sort-header/sort-header.directive.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/sort-header/sort-header.directive.ts @@ -109,7 +109,7 @@ export class SortHeaderDirective extends UntilDestroyedMixin implements AfterVie .subscribe(() => { const latestSortElement = this.wpTableSortBy.current[0]; - if (!latestSortElement || this.headerColumn.href !== latestSortElement.column.href) { + if (this.headerColumn.href !== latestSortElement?.column.href) { this.currentSortDirection = null; } else { this.currentSortDirection = latestSortElement.direction; diff --git a/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-cell-renderer.ts b/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-cell-renderer.ts index 5ba1091f696..efc280ee2e3 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-cell-renderer.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-cell-renderer.ts @@ -240,7 +240,9 @@ export class TimelineCellRenderer { */ public update(element:HTMLDivElement, labels:WorkPackageCellLabels|null, renderInfo:RenderInfo):boolean { const { change } = renderInfo; - const bar = element.querySelector(`.${timelineBackgroundElementClass}`) as HTMLElement; + const bar = element.querySelector(`.${timelineBackgroundElementClass}`); + if (!bar) { return false; } + let start = moment(change.projectedResource.startDate); let due = moment(change.projectedResource.dueDate); diff --git a/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-group-by.service.ts b/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-group-by.service.ts index f49b9e037dc..45da0e34db4 100644 --- a/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-group-by.service.ts +++ b/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-group-by.service.ts @@ -91,6 +91,6 @@ export class WorkPackageViewGroupByService extends WorkPackageQueryStateService< public isCurrentlyGroupedBy(column:QueryColumn):boolean { const cur = this.current; - return !!(cur && cur.id === column.id); + return cur?.id === column.id; } } diff --git a/frontend/src/app/shared/components/datepicker/basic-range-date-picker/basic-range-date-picker.component.ts b/frontend/src/app/shared/components/datepicker/basic-range-date-picker/basic-range-date-picker.component.ts index 2652ca4eb0e..c405e4753c0 100644 --- a/frontend/src/app/shared/components/datepicker/basic-range-date-picker/basic-range-date-picker.component.ts +++ b/frontend/src/app/shared/components/datepicker/basic-range-date-picker/basic-range-date-picker.component.ts @@ -264,7 +264,7 @@ export class OpBasicRangeDatePickerComponent implements OnInit, ControlValueAcce private appendToBodyOrDialog():HTMLElement|undefined { if (this.inDialog) { - return document.querySelector(`#${this.inDialog}`) as HTMLElement; + return document.querySelector(`#${this.inDialog}`)!; } return undefined; diff --git a/frontend/src/app/shared/components/datepicker/basic-single-date-picker/basic-single-date-picker.component.ts b/frontend/src/app/shared/components/datepicker/basic-single-date-picker/basic-single-date-picker.component.ts index ec008a482a8..6adbaa8f662 100644 --- a/frontend/src/app/shared/components/datepicker/basic-single-date-picker/basic-single-date-picker.component.ts +++ b/frontend/src/app/shared/components/datepicker/basic-single-date-picker/basic-single-date-picker.component.ts @@ -236,7 +236,7 @@ export class OpBasicSingleDatePickerComponent implements ControlValueAccessor, O private appendToBodyOrDialog():HTMLElement|undefined { if (this.inDialog) { - return document.querySelector(`#${this.inDialog}`) as HTMLElement; + return document.querySelector(`#${this.inDialog}`)!; } return undefined; diff --git a/frontend/src/app/shared/components/editor/components/ckeditor/ckeditor-setup.service.ts b/frontend/src/app/shared/components/editor/components/ckeditor/ckeditor-setup.service.ts index 1dc058140f8..57137adf518 100644 --- a/frontend/src/app/shared/components/editor/components/ckeditor/ckeditor-setup.service.ts +++ b/frontend/src/app/shared/components/editor/components/ckeditor/ckeditor-setup.service.ts @@ -58,8 +58,11 @@ export class CKEditorSetupService { const editorClass = type === 'constrained' ? window.OPConstrainedEditor : window.OPClassicEditor; wrapper.classList.add(`ckeditor-type-${type}`); - const toolbarWrapper = wrapper.querySelector('.document-editor__toolbar')!; - const contentWrapper = wrapper.querySelector('.document-editor__editable') as HTMLElement; + const toolbarWrapper = wrapper.querySelector('.document-editor__toolbar'); + const contentWrapper = wrapper.querySelector('.document-editor__editable'); + if (!toolbarWrapper || !contentWrapper) { + throw new Error('Missing CKEditor wrapper elements.'); + } const config = this.createConfig(context, initialData); return this diff --git a/frontend/src/app/shared/components/fields/display/field-types/wp-spent-time-display-field.module.ts b/frontend/src/app/shared/components/fields/display/field-types/wp-spent-time-display-field.module.ts index 1b3bb8798db..3aadc541460 100644 --- a/frontend/src/app/shared/components/fields/display/field-types/wp-spent-time-display-field.module.ts +++ b/frontend/src/app/shared/components/fields/display/field-types/wp-spent-time-display-field.module.ts @@ -78,7 +78,7 @@ export class WorkPackageSpentTimeDisplayField extends WorkDisplayField { // Link to the cost report having the work package filter preselected. No grouping. const href = URI( this.PathHelper.projectTimeEntriesPath( - project.identifier as string, + project.identifier, ), ) .search( diff --git a/frontend/src/app/shared/components/fields/edit/field-handler/hal-resource-edit-field-handler.ts b/frontend/src/app/shared/components/fields/edit/field-handler/hal-resource-edit-field-handler.ts index 1983e828048..fcd2c48b0ab 100644 --- a/frontend/src/app/shared/components/fields/edit/field-handler/hal-resource-edit-field-handler.ts +++ b/frontend/src/app/shared/components/fields/edit/field-handler/hal-resource-edit-field-handler.ts @@ -98,7 +98,7 @@ export class HalResourceEditFieldHandler extends EditFieldHandler { } public focus(setClickOffset?:number) { - const target = this.element.querySelector('.inline-edit--field') as HTMLElement; + const target = this.element.querySelector('.inline-edit--field'); if (!target) { debugLog(`Tried to focus on ${this.fieldName}, but element does not (yet) exist.`); diff --git a/frontend/src/app/shared/components/fields/edit/field-types/project-edit-field.component.ts b/frontend/src/app/shared/components/fields/edit/field-types/project-edit-field.component.ts index f33d2c95cb7..8c832bc57d4 100644 --- a/frontend/src/app/shared/components/fields/edit/field-types/project-edit-field.component.ts +++ b/frontend/src/app/shared/components/fields/edit/field-types/project-edit-field.component.ts @@ -84,8 +84,9 @@ export class ProjectEditFieldComponent extends EditFieldComponent implements OnI { name: 'active', operator: '=' as FilterOperator, values: ['t'] }, ]; - if (isNewResource(this.resource) && this.change.value('type')) { - const typeId = idFromLink((this.change.value('type') as { href:string }).href); + const type = this.change.value<{ href:string }|null>('type'); + if (isNewResource(this.resource) && type) { + const typeId = idFromLink(type.href); filters.push({ name: 'type_id', operator: '=' as FilterOperator, values: [typeId] }); } diff --git a/frontend/src/app/shared/components/grids/grid/area.service.ts b/frontend/src/app/shared/components/grids/grid/area.service.ts index 6ed8e5a87d0..c1f733af355 100644 --- a/frontend/src/app/shared/components/grids/grid/area.service.ts +++ b/frontend/src/app/shared/components/grids/grid/area.service.ts @@ -399,7 +399,7 @@ export class GridAreaService { } public resetAreas(ignoredArea:GridWidgetArea|null = null) { - this.widgetAreas.filter((area) => !ignoredArea || area.guid !== ignoredArea.guid).forEach((area) => area.reset()); + this.widgetAreas.filter((area) => area.guid !== ignoredArea?.guid).forEach((area) => area.reset()); this.numRows = this.resource.rowCount; this.numColumns = this.resource.columnCount; diff --git a/frontend/src/app/shared/components/grids/grid/resize.service.ts b/frontend/src/app/shared/components/grids/grid/resize.service.ts index 38a6f7552ee..5677976d6f7 100644 --- a/frontend/src/app/shared/components/grids/grid/resize.service.ts +++ b/frontend/src/app/shared/components/grids/grid/resize.service.ts @@ -88,7 +88,7 @@ export class GridResizeService { } public isResized(area:GridWidgetArea) { - return this.resizedArea && this.resizedArea.guid === area.guid; + return this.resizedArea?.guid === area.guid; } public isPassive(area:GridWidgetArea) { diff --git a/frontend/src/app/shared/components/op-context-menu/handlers/wp-create-settings-menu.directive.ts b/frontend/src/app/shared/components/op-context-menu/handlers/wp-create-settings-menu.directive.ts index 407e97aa6af..5dd4f3dff8f 100644 --- a/frontend/src/app/shared/components/op-context-menu/handlers/wp-create-settings-menu.directive.ts +++ b/frontend/src/app/shared/components/op-context-menu/handlers/wp-create-settings-menu.directive.ts @@ -41,7 +41,7 @@ export class WorkPackageCreateSettingsMenuDirective extends OpContextMenuTrigger readonly halEditing = inject(HalResourceEditingService); override readonly placement = 'bottom-end'; - + protected open(evt:Event) { const wp = this.states.workPackages.get('new').value; diff --git a/frontend/src/app/shared/components/persistent-toggle/persistent-toggle.component.ts b/frontend/src/app/shared/components/persistent-toggle/persistent-toggle.component.ts index 4f55e5920b6..ece5ebe4c3f 100644 --- a/frontend/src/app/shared/components/persistent-toggle/persistent-toggle.component.ts +++ b/frontend/src/app/shared/components/persistent-toggle/persistent-toggle.component.ts @@ -88,7 +88,7 @@ export class PersistentToggleComponent implements OnInit { window.OpenProject.guardedLocalStorage(this.identifier, (!!isNowHidden).toString()); const targetNotification = this.targetNotification; - if (!targetNotification) return; + if (!targetNotification) return; if (isNowHidden) { slideUp(targetNotification, 400); diff --git a/frontend/src/app/shared/components/searchable-project-list/searchable-project-list.service.ts b/frontend/src/app/shared/components/searchable-project-list/searchable-project-list.service.ts index 04055898ec0..de550244fae 100644 --- a/frontend/src/app/shared/components/searchable-project-list/searchable-project-list.service.ts +++ b/frontend/src/app/shared/components/searchable-project-list/searchable-project-list.service.ts @@ -299,7 +299,7 @@ export class SearchableProjectListService { const listParent = findSearchableListParent(event.currentTarget as HTMLElement); const focused = document.activeElement; - (listParent?.querySelector('.spot-list--item-action_active') as HTMLElement)?.click(); + listParent?.querySelector('.spot-list--item-action_active')?.click(); (focused as HTMLElement)?.focus(); } diff --git a/frontend/src/app/shared/directives/search-highlight.directive.ts b/frontend/src/app/shared/directives/search-highlight.directive.ts index 68280c727a4..4f637d6b3f6 100644 --- a/frontend/src/app/shared/directives/search-highlight.directive.ts +++ b/frontend/src/app/shared/directives/search-highlight.directive.ts @@ -13,7 +13,7 @@ export class OpSearchHighlightDirective implements AfterViewChecked { let el = this.elementRef.nativeElement as HTMLElement; const highlightedElement = el.querySelector('.op-search-highlight'); - if (!!highlightedElement && this.query && highlightedElement.innerHTML.toLocaleLowerCase() === this.query.toLocaleLowerCase()) { + if (!!highlightedElement && highlightedElement.innerHTML.toLocaleLowerCase() === this.query?.toLocaleLowerCase()) { return; } diff --git a/frontend/src/app/shared/helpers/chronic_duration.spec.ts b/frontend/src/app/shared/helpers/chronic_duration.spec.ts index f0418c58c3e..4b598c1aee9 100644 --- a/frontend/src/app/shared/helpers/chronic_duration.spec.ts +++ b/frontend/src/app/shared/helpers/chronic_duration.spec.ts @@ -118,7 +118,7 @@ describe('parseChronicDuration', () => { }); /* The cecile case */ - it('it parses 2h15 correctly to 2h 15 minutes even when the default unit is hours', () => { + it('parses 2h15 correctly to 2h 15 minutes even when the default unit is hours', () => { expect(parseChronicDuration('2h15', { defaultUnit: 'hours' })).toBe(2 * 3600 + 15 * 60); }); diff --git a/frontend/src/stimulus/controllers/dynamic/admin/custom-fields.controller.ts b/frontend/src/stimulus/controllers/dynamic/admin/custom-fields.controller.ts index e9039f7336f..c3935cb76bf 100644 --- a/frontend/src/stimulus/controllers/dynamic/admin/custom-fields.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/admin/custom-fields.controller.ts @@ -117,9 +117,12 @@ export default class CustomFieldsController extends Controller { addOption() { const count = this.customOptionRowTargets.length; const last = this.customOptionRowTargets[count - 1]; + if (!last) { return false; } + const dup = last.cloneNode(true) as HTMLElement; - const input = dup.querySelector('.custom-option-value input') as HTMLInputElement; + const input = dup.querySelector('.custom-option-value input'); + if (!input) { return false; } input.setAttribute('name', `custom_field[custom_options_attributes][${count}][value]`); input.setAttribute('id', `custom_field_custom_options_attributes_${count}_value`); @@ -129,8 +132,9 @@ export default class CustomFieldsController extends Controller { .querySelector('.custom-option-id') ?.remove(); - const defaultValueCheckbox = dup.querySelector('input[type="checkbox"]') as HTMLInputElement; - const defaultValueHidden = dup.querySelector('input[type="hidden"]') as HTMLInputElement; + const defaultValueCheckbox = dup.querySelector('input[type="checkbox"]'); + const defaultValueHidden = dup.querySelector('input[type="hidden"]'); + if (!defaultValueCheckbox || !defaultValueHidden) { return false; } defaultValueHidden.setAttribute('name', `custom_field[custom_options_attributes][${count}][default_value]`); defaultValueHidden.removeAttribute('id'); diff --git a/frontend/src/stimulus/controllers/dynamic/admin/hierarchy-item.controller.ts b/frontend/src/stimulus/controllers/dynamic/admin/hierarchy-item.controller.ts index bfee93d82c5..028713cbd75 100644 --- a/frontend/src/stimulus/controllers/dynamic/admin/hierarchy-item.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/admin/hierarchy-item.controller.ts @@ -85,10 +85,12 @@ export default class HierarchyItemController extends Controller { if (event.dataTransfer) { const origin = event.dataTransfer.getData('application/dragkey'); - const originElement = document.querySelector(`[data-hierarchy-item-id='${origin}']`) as HTMLElement; + const originElement = document.querySelector(`[data-hierarchy-item-id='${origin}']`); + if (!originElement) { return; } + originElement.style.opacity = '1'; - if (targetElement.dataset.hierarchyItemId === (originElement as HTMLElement).dataset.hierarchyItemId) { + if (targetElement.dataset.hierarchyItemId === originElement.dataset.hierarchyItemId) { return; } diff --git a/frontend/src/stimulus/controllers/dynamic/admin/progress-tracking.controller.ts b/frontend/src/stimulus/controllers/dynamic/admin/progress-tracking.controller.ts index 0778fb4ca23..c514ca3a0b8 100644 --- a/frontend/src/stimulus/controllers/dynamic/admin/progress-tracking.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/admin/progress-tracking.controller.ts @@ -83,8 +83,8 @@ export default class ProgressTrackingController extends Controller { } getSelectedMode() { - const checkedRadio = this.progressCalculationModeRadioGroupTarget.querySelector('input:checked') as HTMLInputElement; - return checkedRadio?.value || ''; + const checkedRadio = this.progressCalculationModeRadioGroupTarget.querySelector('input:checked'); + return checkedRadio?.value ?? ''; } getWarningMessageHtml():string { diff --git a/frontend/src/stimulus/controllers/dynamic/admin/registration.controller.ts b/frontend/src/stimulus/controllers/dynamic/admin/registration.controller.ts index fdd323321d7..9f2f2b63d1a 100644 --- a/frontend/src/stimulus/controllers/dynamic/admin/registration.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/admin/registration.controller.ts @@ -65,7 +65,7 @@ export default class RegistrationController extends Controller { } getSelectedOption() { - const checkedRadio = this.selfRegistrationRadioGroupTarget.querySelector('input[type="radio"]:checked') as HTMLInputElement; - return checkedRadio?.value || ''; + const checkedRadio = this.selfRegistrationRadioGroupTarget.querySelector('input[type="radio"]:checked'); + return checkedRadio?.value ?? ''; } } diff --git a/frontend/src/stimulus/controllers/dynamic/costs/budget-subform.controller.ts b/frontend/src/stimulus/controllers/dynamic/costs/budget-subform.controller.ts index fd1128458f5..2ff5b9d8623 100644 --- a/frontend/src/stimulus/controllers/dynamic/costs/budget-subform.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/costs/budget-subform.controller.ts @@ -129,7 +129,8 @@ export default class BudgetSubformController extends Controller { const row = this.element.querySelector(`#${row_identifier}`)!; const body = new FormData(); body.append('element_id', row_identifier); - body.append('fixed_date', (document.querySelector('#budget_fixed_date') as HTMLInputElement).value); + const fixedDateInput = document.querySelector('#budget_fixed_date'); + body.append('fixed_date', fixedDateInput?.value ?? ''); row.querySelectorAll('.budget-item-value').forEach((itemValue:HTMLInputElement|HTMLSelectElement) => { body.append(itemValue.dataset.requestKey!, (itemValue.value || '0')); diff --git a/frontend/src/stimulus/controllers/dynamic/hide-sections.controller.ts b/frontend/src/stimulus/controllers/dynamic/hide-sections.controller.ts index e506d44dcba..497ce4eae50 100644 --- a/frontend/src/stimulus/controllers/dynamic/hide-sections.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/hide-sections.controller.ts @@ -36,12 +36,11 @@ export default class extends Controller { } hide(event:MouseEvent) { - const section = (event.target as HTMLElement).closest('.hide-section') as HTMLElement; - if (section) { - section.hidden = true; - } + const section = (event.target as HTMLElement).closest('.hide-section'); + if (!section) { return; } - const name = (section as HTMLElement).dataset.name!; + section.hidden = true; + const name = section.dataset.name!; this.toggleOption(name); } diff --git a/frontend/src/stimulus/controllers/dynamic/menus/main.controller.ts b/frontend/src/stimulus/controllers/dynamic/menus/main.controller.ts index 15ccd953b51..4fbd0fd0ac5 100644 --- a/frontend/src/stimulus/controllers/dynamic/menus/main.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/menus/main.controller.ts @@ -33,8 +33,7 @@ export default class MainMenuController extends Controller { targetLi.querySelector('li > a, .tree-menu--title')?.focus(); - const backArrow = targetLi.querySelector('.main-menu--arrow-left-to-project') as HTMLElement; - backArrow.focus(); + targetLi.querySelector('.main-menu--arrow-left-to-project')?.focus(); this.markActive(targetLi.dataset.name!); } diff --git a/frontend/src/stimulus/controllers/dynamic/my/time-tracking.controller.ts b/frontend/src/stimulus/controllers/dynamic/my/time-tracking.controller.ts index f2ad9d638fe..c18eeb6b3e8 100644 --- a/frontend/src/stimulus/controllers/dynamic/my/time-tracking.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/my/time-tracking.controller.ts @@ -337,8 +337,10 @@ export default class MyTimeTrackingController extends Controller { const colgroup = document.createElement('colgroup'); const col = document.createElement('col'); - const otherCol = document.querySelector('.fc-scrollgrid-section-header .fc-col-header col') as HTMLElement; - col.style.width = otherCol?.style?.width; + const otherCol = document.querySelector('.fc-scrollgrid-section-header .fc-col-header col'); + if (otherCol) { + col.style.width = otherCol.style.width; + } const tbody = document.createElement('tbody'); tbody.setAttribute('role', 'presentation'); diff --git a/frontend/src/stimulus/controllers/dynamic/shares/bulk-selection.controller.ts b/frontend/src/stimulus/controllers/dynamic/shares/bulk-selection.controller.ts index 69f03483303..b13993b71da 100644 --- a/frontend/src/stimulus/controllers/dynamic/shares/bulk-selection.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/shares/bulk-selection.controller.ts @@ -213,12 +213,12 @@ export default class BulkSelectionController extends Controller { }); } - private get selectedPermissions() { + private get selectedPermissions():string[] { return this.selectedRoleButtons.map((button) => { const label = button.querySelector('.Button-label')!; - return label.textContent; - }) as string[]; + return label.textContent ?? ''; + }); } private get selectedRoleButtons() { diff --git a/frontend/src/stimulus/controllers/dynamic/sort-by-config.controller.ts b/frontend/src/stimulus/controllers/dynamic/sort-by-config.controller.ts index 0dadb0bb572..97d0a9967fe 100644 --- a/frontend/src/stimulus/controllers/dynamic/sort-by-config.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/sort-by-config.controller.ts @@ -84,9 +84,9 @@ export default class SortByConfigController extends Controller { this.parentForm = this.sortByFieldTarget.closest('form'); if (this.parentForm) { - this.pageTarget = this.parentForm.querySelector('input[data-sort-by-config-target="page"]')!; - - if (this.pageTarget) { + const pageTarget = this.parentForm.querySelector('input[data-sort-by-config-target="page"]'); + if (pageTarget) { + this.pageTarget = pageTarget; this.parentForm.addEventListener('submit', this.onFormSubmit.bind(this)); } } @@ -116,7 +116,8 @@ export default class SortByConfigController extends Controller { fieldChanged(event:Event):void { const target = event.target as HTMLElement; - const row = target.closest('div[data-sort-by-config-target="inputRow"]') as HTMLElement; + const row = target.closest('div[data-sort-by-config-target="inputRow"]'); + if (!row) { return; } this.manageRow(row); @@ -208,18 +209,20 @@ export default class SortByConfigController extends Controller { } getSelectedField(row:HTMLElement):string|null { - const selectedField = row.querySelector('select[name="sort_field"]') as HTMLSelectElement; - return selectedField?.value || null; + const selectedField = row.querySelector('select[name="sort_field"]'); + return selectedField?.value ?? null; } getSelectedDirection(row:HTMLElement):string|null { const selectedSegment = row.querySelector('li.SegmentedControl-item--selected > button'); - return selectedSegment?.getAttribute('data-direction') || null; + return selectedSegment?.getAttribute('data-direction') ?? null; } unsetField(row:HTMLElement):void { - const select = row.querySelector('select[name="sort_field"]') as HTMLSelectElement; - select.value = ''; + const select = row.querySelector('select[name="sort_field"]'); + if (select) { + select.value = ''; + } } unsetDirection(row:HTMLElement):void { @@ -283,8 +286,8 @@ export default class SortByConfigController extends Controller { disableSelectedFieldsForOtherSelects():void { this.inputRowTargets.forEach((row) => { const selectedFieldsInOtherRows = this.getAllSelectedFields(row); - const otherSelect = row.querySelector('select[name="sort_field"]')!; - otherSelect.querySelectorAll('option').forEach((option) => { + const otherSelect = row.querySelector('select[name="sort_field"]'); + otherSelect?.querySelectorAll('option').forEach((option) => { option.disabled = selectedFieldsInOtherRows.includes(option.value); }); }); diff --git a/frontend/src/stimulus/controllers/dynamic/work-packages/date-picker/preview.controller.ts b/frontend/src/stimulus/controllers/dynamic/work-packages/date-picker/preview.controller.ts index 9aafd516db2..176de654298 100644 --- a/frontend/src/stimulus/controllers/dynamic/work-packages/date-picker/preview.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/work-packages/date-picker/preview.controller.ts @@ -534,14 +534,16 @@ export default class PreviewController extends DialogPreviewController { } private focusOnOpen() { - const banner = document.querySelector('.wp-datepicker--banner') as HTMLElement; + const banner = document.querySelector('.wp-datepicker--banner'); if (banner) { banner.setAttribute('tabindex', '-1'); banner.focus(); } else { - const tabs = document.querySelector('.wp-datepicker-dialog--UnderlineNav') as HTMLElement; - tabs.setAttribute('tabindex', '-1'); - tabs.focus(); + const tabs = document.querySelector('.wp-datepicker-dialog--UnderlineNav'); + if (tabs) { + tabs.setAttribute('tabindex', '-1'); + tabs.focus(); + } } } } diff --git a/frontend/src/stimulus/controllers/dynamic/work-packages/export/generate-pdf-form.controller.ts b/frontend/src/stimulus/controllers/dynamic/work-packages/export/generate-pdf-form.controller.ts index 61305d443c4..25327b7ac28 100644 --- a/frontend/src/stimulus/controllers/dynamic/work-packages/export/generate-pdf-form.controller.ts +++ b/frontend/src/stimulus/controllers/dynamic/work-packages/export/generate-pdf-form.controller.ts @@ -10,10 +10,10 @@ export default class GeneratePdfController extends Controller { const data = target.options[target.selectedIndex].dataset; const template = target.options[target.selectedIndex].value; - const formControl = target.closest('.FormControl')!; - const captionElement = formControl.querySelector('.FormControl-caption') as HTMLElement; + const formControl = target.closest('.FormControl'); + const captionElement = formControl?.querySelector('.FormControl-caption'); if (captionElement) { - captionElement.innerText = (data.caption || ''); + captionElement.innerText = (data.caption ?? ''); } this.inputGroupsTargets.forEach((inputGroup:HTMLElement) => { if (inputGroup.dataset.template === template) { diff --git a/frontend/src/turbo/turbo-global-listeners.ts b/frontend/src/turbo/turbo-global-listeners.ts index 4cb36c0e00b..37aa21aafff 100644 --- a/frontend/src/turbo/turbo-global-listeners.ts +++ b/frontend/src/turbo/turbo-global-listeners.ts @@ -16,7 +16,7 @@ export function addTurboGlobalListeners() { const runOnRenderAndLoad = () => { // Add to content if warnings displayed if (document.querySelector('.warning-bar--item')) { - const content = document.querySelector('#content') as HTMLElement; + const content = document.querySelector('#content'); if (content) { content.style.marginBottom = '100px'; } From 2f5106881fed2d8632df17fc97724311dcded870 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Sat, 23 May 2026 02:28:43 +0200 Subject: [PATCH 2/4] Fix ESLint errors in wp-list-invalid-query Replaces global lodash (`_`) calls with native Array methods (`map`, `find`, `filter`, `forEach`, `slice`) and adds proper type annotations to eliminate all 38 `@typescript-eslint/no-unsafe-*` and `no-explicit-any` errors. Introduces a local `QueryFormSchema` intersection type so schema attribute access is statically typed. --- .../wp-list/wp-list-invalid-query.service.ts | 92 ++++++++++++------- 1 file changed, 57 insertions(+), 35 deletions(-) diff --git a/frontend/src/app/features/work-packages/components/wp-list/wp-list-invalid-query.service.ts b/frontend/src/app/features/work-packages/components/wp-list/wp-list-invalid-query.service.ts index 98fd9893d5e..74b1d459383 100644 --- a/frontend/src/app/features/work-packages/components/wp-list/wp-list-invalid-query.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-list/wp-list-invalid-query.service.ts @@ -36,23 +36,37 @@ import { SchemaResource } from 'core-app/features/hal/resources/schema-resource' import { QuerySortByResource } from 'core-app/features/hal/resources/query-sort-by-resource'; import { QueryGroupByResource } from 'core-app/features/hal/resources/query-group-by-resource'; import { QueryColumn } from '../wp-query/query-column'; +import { HalResource } from 'core-app/features/hal/resources/hal-resource'; +import { SchemaAttributeObject } from 'core-app/features/hal/resources/schema-attribute-object'; +import { QueryFilterInstanceResource } from 'core-app/features/hal/resources/query-filter-instance-resource'; + +interface QueryFormSchemaProperties { + columns:SchemaAttributeObject; + sortBy:SchemaAttributeObject; + groupBy:SchemaAttributeObject; +} + +type QueryFormSchema = SchemaResource & QueryFormSchemaProperties; @Injectable() export class WorkPackagesListInvalidQueryService { protected halResourceService = inject(HalResourceService); - public restoreQuery(query:QueryResource, form:QueryFormResource) { - this.restoreFilters(query, form.payload, form.schema); - this.restoreColumns(query, form.payload, form.schema); - this.restoreSortBy(query, form.payload, form.schema); - this.restoreGroupBy(query, form.payload, form.schema); - this.restoreOtherProperties(query, form.payload); + const payload = form.payload as QueryResource; + const schema = form.schema as QueryFormSchema; + this.restoreFilters(query, payload, form.filtersSchemas); + this.restoreColumns(query, payload, schema); + this.restoreSortBy(query, payload, schema); + this.restoreGroupBy(query, payload, schema); + this.restoreOtherProperties(query, payload); } - private restoreFilters(query:QueryResource, payload:QueryResource, querySchema:SchemaResource) { - let filters = _.map((payload.filters), (filter) => { - const filterInstanceSchema = _.find(querySchema.filtersSchemas.elements, (schema:QueryFilterInstanceSchemaResource) => (schema.filter.allowedValues as QueryFilterResource[])[0].href === filter.filter.href); + private restoreFilters(query:QueryResource, payload:QueryResource, filtersSchemas:QueryFilterInstanceSchemaResource[]) { + const filters = payload.filters.map((filter) => { + const filterInstanceSchema = filtersSchemas.find( + (schema) => (schema.filter.allowedValues as QueryFilterResource[])[0].href === filter.filter.href, + ); if (!filterInstanceSchema) { return null; @@ -60,56 +74,64 @@ export class WorkPackagesListInvalidQueryService { const recreatedFilter = filterInstanceSchema.getFilter(); - const operator = _.find(filterInstanceSchema.operator.allowedValues, (operator) => operator.href === filter.operator.href); + const operator = (filterInstanceSchema.operator.allowedValues as HalResource[]).find( + (op) => op.href === filter.operator.href, + ); if (operator) { - recreatedFilter.operator = operator; + recreatedFilter.operator = operator as typeof recreatedFilter.operator; } - recreatedFilter.values.length = 0; - _.each(filter.values, (value) => recreatedFilter.values.push(value)); + recreatedFilter.values = filter.values.slice(); return recreatedFilter; - }); - - filters = _.compact(filters); + }).filter((f):f is QueryFilterInstanceResource => f != null); // clear filters while keeping reference query.filters.length = 0; - _.each(filters, (filter) => query.filters.push(filter)); + filters.forEach((filter) => query.filters.push(filter)); } - private restoreColumns(query:QueryResource, stubQuery:QueryResource, schema:SchemaResource) { - let columns = _.map(stubQuery.columns, (column) => _.find((schema.columns.allowedValues as QueryColumn[]), (candidate) => candidate.href === column.href)); - - columns = _.compact(columns); + private restoreColumns(query:QueryResource, stubQuery:QueryResource, schema:QueryFormSchema) { + const columns = stubQuery.columns + .map((column) => (schema.columns.allowedValues as QueryColumn[]).find((candidate) => candidate.href === column.href)) + .filter((column):column is QueryColumn => column != null); query.columns.length = 0; - _.each(columns, (column) => query.columns.push(column!)); + columns.forEach((column) => query.columns.push(column)); } - private restoreSortBy(query:QueryResource, stubQuery:QueryResource, schema:SchemaResource) { - let sortBys = _.map((stubQuery.sortBy), (sortBy) => _.find((schema.sortBy.allowedValues as QuerySortByResource[]), (candidate) => candidate.href === sortBy.href)!); - - sortBys = _.compact(sortBys); + private restoreSortBy(query:QueryResource, stubQuery:QueryResource, schema:QueryFormSchema) { + const sortBys = stubQuery.sortBy + .map((sortBy) => (schema.sortBy.allowedValues as QuerySortByResource[]).find((candidate) => candidate.href === sortBy.href)) + .filter((sortBy):sortBy is QuerySortByResource => sortBy != null); query.sortBy.length = 0; - _.each(sortBys, (sortBy) => query.sortBy.push(sortBy)); + sortBys.forEach((sortBy) => query.sortBy.push(sortBy)); } - private restoreGroupBy(query:QueryResource, stubQuery:QueryResource, schema:SchemaResource) { - const groupBy = _.find((schema.groupBy.allowedValues as QueryGroupByResource[]), (candidate) => stubQuery.groupBy?.href === candidate.href) as any; + private restoreGroupBy(query:QueryResource, stubQuery:QueryResource, schema:QueryFormSchema) { + const groupBy = (schema.groupBy.allowedValues as QueryGroupByResource[]).find( + (candidate) => stubQuery.groupBy?.href === candidate.href, + ); query.groupBy = groupBy; } private restoreOtherProperties(query:QueryResource, stubQuery:QueryResource) { - _.without(Object.keys(stubQuery.$source), '_links', 'filters').forEach((property:any) => { - query[property] = stubQuery[property]; - }); + const source = stubQuery.$source as Record; + const links = (source._links ?? {}) as Record; - _.without(Object.keys(stubQuery.$source._links), 'columns', 'groupBy', 'sortBy').forEach((property:any) => { - query[property] = stubQuery[property]; - }); + Object.keys(source) + .filter((key) => key !== '_links' && key !== 'filters') + .forEach((property) => { + (query as Record)[property] = (stubQuery as Record)[property]; + }); + + Object.keys(links) + .filter((key) => key !== 'columns' && key !== 'groupBy' && key !== 'sortBy') + .forEach((property) => { + (query as Record)[property] = (stubQuery as Record)[property]; + }); } } From e3184d47c1a3856b3a0cf5460311648c23600e30 Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Sat, 23 May 2026 02:28:50 +0200 Subject: [PATCH 3/4] Fix ESLint errors in project-edit-field Adds explicit type assertions for `this.resource` when passed to `isNewResource()`, which expects `{ id: string | null }`. The base `Field.resource` is typed as `any`, causing two `no-unsafe-argument` errors. --- .../fields/edit/field-types/project-edit-field.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/shared/components/fields/edit/field-types/project-edit-field.component.ts b/frontend/src/app/shared/components/fields/edit/field-types/project-edit-field.component.ts index 8c832bc57d4..a9dbd635acb 100644 --- a/frontend/src/app/shared/components/fields/edit/field-types/project-edit-field.component.ts +++ b/frontend/src/app/shared/components/fields/edit/field-types/project-edit-field.component.ts @@ -53,7 +53,7 @@ export class ProjectEditFieldComponent extends EditFieldComponent implements OnI readonly http = inject(HttpClient); readonly halResourceService = inject(HalResourceService); - isNew = isNewResource(this.resource); + isNew = isNewResource(this.resource as { id:string | null }); url:string; @@ -85,7 +85,7 @@ export class ProjectEditFieldComponent extends EditFieldComponent implements OnI ]; const type = this.change.value<{ href:string }|null>('type'); - if (isNewResource(this.resource) && type) { + if (isNewResource(this.resource as { id:string | null }) && type) { const typeId = idFromLink(type.href); filters.push({ name: 'type_id', operator: '=' as FilterOperator, values: [typeId] }); } From ee8e95403019042f55c518dc556fdef5abf9d0db Mon Sep 17 00:00:00 2001 From: Alexander Brandon Coles Date: Thu, 28 May 2026 21:01:09 +0200 Subject: [PATCH 4/4] Fix frontend ESLint autocorrections (second pass) Removes unnecessary type assertions that ESLint now detects after library version drift. Adds eslint-disable for three casts that are still required for type safety. --- .../src/app/core/routing/openproject-router.module.ts | 2 +- frontend/src/app/core/setup/globals/global-helpers.ts | 4 ++-- .../app/core/state/attachments/attachments.service.ts | 2 +- .../calendar/wp-calendar/wp-calendar.component.ts | 4 ++-- .../team-planner/planner/team-planner.component.ts | 2 +- .../filter-date-time-value.component.ts | 10 +++++----- .../wp-list/wp-states-initialization.service.ts | 2 +- .../components/wp-new/wp-create.service.ts | 4 ++-- .../components/wp-query/url-params-helper.ts | 2 +- .../components/wp-relations/wp-relations.component.ts | 1 - .../wp-table-configuration-relation-selector.ts | 2 +- .../wp-table/embedded/wp-embedded-base.component.ts | 2 +- .../table-actions/actions/unlink-table-action.ts | 2 +- .../wp-table/timeline/cells/timeline-cell-renderer.ts | 4 ++-- .../timeline/cells/timeline-milestone-cell-renderer.ts | 2 +- .../global-elements/wp-timeline-relations.directive.ts | 2 +- .../view-services/wp-view-highlighting.service.ts | 2 +- .../view-services/wp-view-timeline.service.ts | 2 +- .../work-packages/services/work-package.service.ts | 4 ++-- .../attribute-help-text-modal.service.spec.ts | 2 +- .../op-autocompleter/op-autocompleter.spec.ts | 7 +++---- ...ime-entries-work-package-autocompleter.component.ts | 2 +- .../components/fields/display/display-field.service.ts | 10 +++++----- .../field-types/wp-id-display-field.module.spec.ts | 3 +-- .../components/fields/edit/edit-form/edit-form.spec.ts | 8 ++++---- .../edit/field-types/project-edit-field.component.ts | 2 +- .../modal-with-turbo-content.directive.ts | 5 ++--- .../fields/macros/attribute-model-loader.service.ts | 2 +- .../searchable-project-list.service.ts | 4 ++-- .../helpers/drag-and-drop/dom-autoscroll.service.ts | 6 +++--- .../spot/components/drop-modal/drop-modal.component.ts | 2 +- 31 files changed, 52 insertions(+), 56 deletions(-) diff --git a/frontend/src/app/core/routing/openproject-router.module.ts b/frontend/src/app/core/routing/openproject-router.module.ts index 33c6024ce70..238f9629895 100644 --- a/frontend/src/app/core/routing/openproject-router.module.ts +++ b/frontend/src/app/core/routing/openproject-router.module.ts @@ -42,7 +42,7 @@ import { states: OPENPROJECT_ROUTES, useHash: false, config: uiRouterConfiguration, - } as any), + }), ], providers: [ FirstRouteService, diff --git a/frontend/src/app/core/setup/globals/global-helpers.ts b/frontend/src/app/core/setup/globals/global-helpers.ts index c1f5ca2fdc4..5b280c0630b 100644 --- a/frontend/src/app/core/setup/globals/global-helpers.ts +++ b/frontend/src/app/core/setup/globals/global-helpers.ts @@ -37,7 +37,7 @@ export function getMetaContent( defaultValue?:T ):string|T { const content = getMetaElement(name)?.content ?? defaultValue ?? ''; - return content as string|T; + return content; } export function getMetaValue(name:string, key:string):string; @@ -48,5 +48,5 @@ export function getMetaValue( defaultValue?:T ):string|T { const value = getMetaElement(name)?.dataset[key] ?? defaultValue ?? ''; - return value as string|T; + return value; } diff --git a/frontend/src/app/core/state/attachments/attachments.service.ts b/frontend/src/app/core/state/attachments/attachments.service.ts index 59bf11725ea..781cd02c366 100644 --- a/frontend/src/app/core/state/attachments/attachments.service.ts +++ b/frontend/src/app/core/state/attachments/attachments.service.ts @@ -195,7 +195,7 @@ export class AttachmentsResourceService extends ResourceStoreService { - this.states.schemas.get(schema.href!).putValue(schema as any); + this.states.schemas.get(schema.href!).putValue(schema); }); this.wpTableFilters.initializeFilters(query, schema); diff --git a/frontend/src/app/features/work-packages/components/wp-new/wp-create.service.ts b/frontend/src/app/features/work-packages/components/wp-new/wp-create.service.ts index ed928939038..6fc88b98d36 100644 --- a/frontend/src/app/features/work-packages/components/wp-new/wp-create.service.ts +++ b/frontend/src/app/features/work-packages/components/wp-new/wp-create.service.ts @@ -59,7 +59,7 @@ import { HalResourceService } from 'core-app/features/hal/services/hal-resource. import { ResourceChangeset } from 'core-app/shared/components/fields/changeset/resource-changeset'; import { AttachmentsResourceService } from 'core-app/core/state/attachments/attachments.service'; import { AttachmentCollectionResource } from 'core-app/features/hal/resources/attachment-collection-resource'; -import { HalSource, HalSourceLink } from 'core-app/features/hal/interfaces'; +import { HalSource } from 'core-app/features/hal/interfaces'; export const newWorkPackageHref = '/api/v3/work_packages/new'; @@ -371,7 +371,7 @@ export class WorkPackageCreateService extends UntilDestroyedMixin { } else if (!value) { payload._links[attribute] = { href: null }; } else { - payload._links[attribute] = value as unknown as HalSourceLink; + payload._links[attribute] = value; } delete payload[attribute]; }); diff --git a/frontend/src/app/features/work-packages/components/wp-query/url-params-helper.ts b/frontend/src/app/features/work-packages/components/wp-query/url-params-helper.ts index 1ad8c474663..82818ab51a9 100644 --- a/frontend/src/app/features/work-packages/components/wp-query/url-params-helper.ts +++ b/frontend/src/app/features/work-packages/components/wp-query/url-params-helper.ts @@ -373,7 +373,7 @@ export class UrlParamsHelperService { queryData.sortBy = this.buildV3GetSortByFromQuery(query); queryData.timestamps = query.timestamps.join(','); - return _.extend(additionalParams, queryData) as Partial; + return _.extend(additionalParams, queryData); } public queryFilterValueToParam(value:HalResource|string|boolean):string { diff --git a/frontend/src/app/features/work-packages/components/wp-relations/wp-relations.component.ts b/frontend/src/app/features/work-packages/components/wp-relations/wp-relations.component.ts index 50f2c6c3d5a..345bc6f8865 100644 --- a/frontend/src/app/features/work-packages/components/wp-relations/wp-relations.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-relations/wp-relations.component.ts @@ -102,7 +102,6 @@ export class WorkPackageRelationsComponent extends UntilDestroyedMixin implement const updateWorkPackage = !!form.dataset?.updateWorkPackage; if (updateWorkPackage) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (event.detail?.success) { // Update the work package void this.apiV3Service diff --git a/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/wp-table-configuration-relation-selector.ts b/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/wp-table-configuration-relation-selector.ts index 9fd53229297..fcd9e21082a 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/wp-table-configuration-relation-selector.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/configuration-modal/wp-table-configuration-relation-selector.ts @@ -69,7 +69,7 @@ export class WpTableConfigurationRelationSelectorComponent implements OnInit { private async initializeRelationFilters():Promise { await this.wpTableFilters.onReady(); - this.availableRelationFilters = this.relationFiltersOf(this.wpTableFilters.availableFilters) as QueryFilterResource[]; + this.availableRelationFilters = this.relationFiltersOf(this.wpTableFilters.availableFilters); this.setSelectedRelationFilter(); this.cdRef.markForCheck(); } diff --git a/frontend/src/app/features/work-packages/components/wp-table/embedded/wp-embedded-base.component.ts b/frontend/src/app/features/work-packages/components/wp-table/embedded/wp-embedded-base.component.ts index 2da789ff39f..4964b84abaf 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/embedded/wp-embedded-base.component.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/embedded/wp-embedded-base.component.ts @@ -89,7 +89,7 @@ export abstract class WorkPackageEmbeddedBaseComponent extends WorkPackagesViewB const query = this.querySpace.query.value!; this.wpStatesInitialization.applyToQuery(query); - return this.urlParamsHelper.buildV3GetQueryFromQueryResource(query) as object; + return this.urlParamsHelper.buildV3GetQueryFromQueryResource(query); } public buildUrlParams() { diff --git a/frontend/src/app/features/work-packages/components/wp-table/table-actions/actions/unlink-table-action.ts b/frontend/src/app/features/work-packages/components/wp-table/table-actions/actions/unlink-table-action.ts index 8b2a4003f09..de20ef74ba9 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/table-actions/actions/unlink-table-action.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/table-actions/actions/unlink-table-action.ts @@ -33,7 +33,7 @@ export class OpUnlinkTableAction extends OpTableAction { identifier, title, applicable, - onClick) as OpTableAction; + onClick); } public buildElement() { diff --git a/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-cell-renderer.ts b/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-cell-renderer.ts index efc280ee2e3..d89bc756414 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-cell-renderer.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-cell-renderer.ts @@ -83,8 +83,8 @@ export class TimelineCellRenderer { } public isEmpty(wp:WorkPackageResource) { - const start = moment(wp.startDate as any); - const due = moment(wp.dueDate as any); + const start = moment(wp.startDate); + const due = moment(wp.dueDate); const noStartAndDueValues = _.isNaN(start.valueOf()) && _.isNaN(due.valueOf()); return noStartAndDueValues; } diff --git a/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-milestone-cell-renderer.ts b/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-milestone-cell-renderer.ts index 224ecb58743..c7a087bef4b 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-milestone-cell-renderer.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/timeline/cells/timeline-milestone-cell-renderer.ts @@ -30,7 +30,7 @@ export class TimelineMilestoneCellRenderer extends TimelineCellRenderer { } public isEmpty(wp:WorkPackageResource) { - const date = moment(wp.date as any); + const date = moment(wp.date); return _.isNaN(date.valueOf()); } diff --git a/frontend/src/app/features/work-packages/components/wp-table/timeline/global-elements/wp-timeline-relations.directive.ts b/frontend/src/app/features/work-packages/components/wp-table/timeline/global-elements/wp-timeline-relations.directive.ts index ea8b916d918..dd9ce04581a 100644 --- a/frontend/src/app/features/work-packages/components/wp-table/timeline/global-elements/wp-timeline-relations.directive.ts +++ b/frontend/src/app/features/work-packages/components/wp-table/timeline/global-elements/wp-timeline-relations.directive.ts @@ -131,7 +131,7 @@ export class WorkPackageTableTimelineRelations extends UntilDestroyedMixin imple ) .subscribe((list) => { // ... make sure that the corresponding relations are loaded ... - const wps = _.compact(list.map((row) => row.workPackageId) as string[]); + const wps = _.compact(list.map((row) => row.workPackageId)); void this.wpRelations.requireAll(wps); }); diff --git a/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-highlighting.service.ts b/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-highlighting.service.ts index d47077dc9b8..beca9656068 100644 --- a/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-highlighting.service.ts +++ b/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-highlighting.service.ts @@ -40,7 +40,7 @@ export class WorkPackageViewHighlightingService extends WorkPackageQueryStateSer } public get current():WorkPackageViewHighlight { - const value = this.lastUpdatedState.getValueOr({ mode: 'inline' } as WorkPackageViewHighlight); + const value = this.lastUpdatedState.getValueOr({ mode: 'inline' } as WorkPackageViewHighlight); // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion return this.filteredValue(value); } diff --git a/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-timeline.service.ts b/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-timeline.service.ts index a9ee99762a5..41a0d192b7f 100644 --- a/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-timeline.service.ts +++ b/frontend/src/app/features/work-packages/routing/wp-view-base/view-services/wp-view-timeline.service.ts @@ -158,7 +158,7 @@ export class WorkPackageViewTimelineService extends WorkPackageQueryStateService * @param update */ private modify(update:Partial) { - this.update({ ...this.current, ...update } as WorkPackageTimelineState); + this.update({ ...this.current, ...update }); } /** diff --git a/frontend/src/app/features/work-packages/services/work-package.service.ts b/frontend/src/app/features/work-packages/services/work-package.service.ts index 3d9d3f5741f..34248d0e0ce 100644 --- a/frontend/src/app/features/work-packages/services/work-package.service.ts +++ b/frontend/src/app/features/work-packages/services/work-package.service.ts @@ -33,7 +33,7 @@ import { PathHelperService } from 'core-app/core/path-helper/path-helper.service import { UrlParamsHelperService } from 'core-app/features/work-packages/components/wp-query/url-params-helper'; import { ToastService } from 'core-app/shared/components/toaster/toast.service'; import { I18nService } from 'core-app/core/i18n/i18n.service'; -import { HalDeletedEvent, HalEventsService } from 'core-app/features/hal/services/hal-events.service'; +import { HalEventsService } from 'core-app/features/hal/services/hal-events.service'; import { States } from 'core-app/core/states/states.service'; import { resolveNumericId } from 'core-app/features/work-packages/helpers/work-package-id-resolvers'; @@ -69,7 +69,7 @@ export class WorkPackageService { .then(() => { this.toastService.addSuccess(this.text.successful_delete); - ids.forEach((id) => this.halEvents.push({ _type: 'WorkPackage', id }, { eventType: 'deleted' } as HalDeletedEvent)); + ids.forEach((id) => this.halEvents.push({ _type: 'WorkPackage', id }, { eventType: 'deleted' })); const routeWpId = this.$state.params.workPackageId as string; const numericId = resolveNumericId(this.states, routeWpId); diff --git a/frontend/src/app/shared/components/attribute-help-texts/attribute-help-text-modal.service.spec.ts b/frontend/src/app/shared/components/attribute-help-texts/attribute-help-text-modal.service.spec.ts index 8d7c28da137..daac3adf686 100644 --- a/frontend/src/app/shared/components/attribute-help-texts/attribute-help-text-modal.service.spec.ts +++ b/frontend/src/app/shared/components/attribute-help-texts/attribute-help-text-modal.service.spec.ts @@ -12,7 +12,7 @@ describe('AttributeHelpTextModalService', () => { let dialog:HTMLDialogElement|null; beforeEach(() => { - fetchSpy = vi.spyOn(window, 'fetch') as unknown as Mock; + fetchSpy = vi.spyOn(window, 'fetch'); // eslint-disable-line @typescript-eslint/no-unsafe-assignment }); beforeEach(async () => { diff --git a/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.spec.ts b/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.spec.ts index 58ad73324e2..79c6aedd6e3 100644 --- a/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.spec.ts +++ b/frontend/src/app/shared/components/autocompleter/op-autocompleter/op-autocompleter.spec.ts @@ -7,7 +7,6 @@ import { of, map } from 'rxjs'; import { NgSelectModule } from '@ng-select/ng-select'; import { OpAutocompleterComponent } from './op-autocompleter.component'; -import { TOpAutocompleterResource } from './typings'; import { By } from '@angular/platform-browser'; import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http'; @@ -93,11 +92,11 @@ describe('autocompleter', () => { }).compileComponents(); fixture = TestBed.createComponent(OpAutocompleterComponent); - getOptionsFnSpy = vi.fn().mockImplementation((searchTerm:string) => { + getOptionsFnSpy = vi.fn().mockImplementation((searchTerm:string) => { // eslint-disable-line @typescript-eslint/no-unsafe-assignment return of(workPackagesStub).pipe(map((wps) => wps.filter((wp) => searchTerm !== '' && wp.subject.includes(searchTerm)))); - }) as unknown as Mock; + }); - fixture.componentInstance.resource = 'work_packages' as TOpAutocompleterResource; + fixture.componentInstance.resource = 'work_packages'; fixture.componentInstance.filters = []; fixture.componentInstance.searchKey = 'typeahead'; fixture.componentInstance.appendTo = 'body'; diff --git a/frontend/src/app/shared/components/autocompleter/time-entries-work-package-autocompleter/time-entries-work-package-autocompleter.component.ts b/frontend/src/app/shared/components/autocompleter/time-entries-work-package-autocompleter/time-entries-work-package-autocompleter.component.ts index 1d9cd0403ee..932c96c7bc4 100644 --- a/frontend/src/app/shared/components/autocompleter/time-entries-work-package-autocompleter/time-entries-work-package-autocompleter.component.ts +++ b/frontend/src/app/shared/components/autocompleter/time-entries-work-package-autocompleter/time-entries-work-package-autocompleter.component.ts @@ -124,7 +124,7 @@ export class TimeEntriesWorkPackageAutocompleterComponent extends OpAutocomplete const base = this.filters ?? []; const isRecent = this.mode === 'recent'; if (isRecent && this.recentWorkPackageIds.length > 0) { - return [...base, { name: 'id', operator: '=', values: this.recentWorkPackageIds } as IAPIFilter]; + return [...base, { name: 'id', operator: '=', values: this.recentWorkPackageIds }]; } return base; diff --git a/frontend/src/app/shared/components/fields/display/display-field.service.ts b/frontend/src/app/shared/components/fields/display/display-field.service.ts index 48bd727ab4b..bfeb26b3e91 100644 --- a/frontend/src/app/shared/components/fields/display/display-field.service.ts +++ b/frontend/src/app/shared/components/fields/display/display-field.service.ts @@ -91,27 +91,27 @@ export class DisplayFieldService extends AbstractFieldService { let field:WorkPackageIdDisplayField; @@ -37,7 +36,7 @@ describe('WorkPackageIdDisplayField', () => { const mockInjector = { get: (token:unknown, notFoundValue?:unknown) => serviceMap.get(token) ?? notFoundValue ?? {}, - } as unknown as Injector; + }; field = new WorkPackageIdDisplayField('id', { injector: mockInjector, diff --git a/frontend/src/app/shared/components/fields/edit/edit-form/edit-form.spec.ts b/frontend/src/app/shared/components/fields/edit/edit-form/edit-form.spec.ts index f9993c7a396..e7cf25d086b 100644 --- a/frontend/src/app/shared/components/fields/edit/edit-form/edit-form.spec.ts +++ b/frontend/src/app/shared/components/fields/edit/edit-form/edit-form.spec.ts @@ -30,7 +30,7 @@ import { ApplicationRef, Injector } from '@angular/core'; import { EditForm } from 'core-app/shared/components/fields/edit/edit-form/edit-form'; import { HalResource } from 'core-app/features/hal/resources/hal-resource'; import { EditFieldHandler } from 'core-app/shared/components/fields/edit/editing-portal/edit-field-handler'; -import { IFieldSchema } from 'core-app/shared/components/fields/field.base'; +import { vi } from 'vitest'; class TestEditForm extends EditForm { constructor(injector:Injector, private readonly requireVisibleSpy:(fieldName:string) => Promise, private readonly activateFieldSpy:() => Promise, private readonly resetSpy:(fieldName:string, focus?:boolean) => void) { @@ -58,7 +58,7 @@ describe('EditForm', () => { it('does not require visibility twice for newly erroneous inactive fields', async () => { const tick = vi.fn(); const requireVisible = vi.fn().mockResolvedValue(undefined); - const activateField = vi.fn().mockResolvedValue({} as EditFieldHandler); + const activateField = vi.fn().mockResolvedValue({}); const reset = vi.fn(); const injector = { get: vi.fn().mockImplementation((token:unknown) => { @@ -68,7 +68,7 @@ describe('EditForm', () => { throw new Error(`Unexpected token: ${String(token)}`); }), - } as unknown as Injector; + }; const form = new TestEditForm(injector, requireVisible, activateField, reset); const change = { @@ -77,7 +77,7 @@ describe('EditForm', () => { ofProperty: vi.fn().mockReturnValue({ writable: true, name: 'Foo', - } as IFieldSchema), + }), }, getForm: vi.fn().mockResolvedValue(undefined), }; diff --git a/frontend/src/app/shared/components/fields/edit/field-types/project-edit-field.component.ts b/frontend/src/app/shared/components/fields/edit/field-types/project-edit-field.component.ts index a9dbd635acb..a1bc16dd719 100644 --- a/frontend/src/app/shared/components/fields/edit/field-types/project-edit-field.component.ts +++ b/frontend/src/app/shared/components/fields/edit/field-types/project-edit-field.component.ts @@ -87,7 +87,7 @@ export class ProjectEditFieldComponent extends EditFieldComponent implements OnI const type = this.change.value<{ href:string }|null>('type'); if (isNewResource(this.resource as { id:string | null }) && type) { const typeId = idFromLink(type.href); - filters.push({ name: 'type_id', operator: '=' as FilterOperator, values: [typeId] }); + filters.push({ name: 'type_id', operator: '=', values: [typeId] }); } return filters; diff --git a/frontend/src/app/shared/components/fields/edit/modal-with-turbo-content/modal-with-turbo-content.directive.ts b/frontend/src/app/shared/components/fields/edit/modal-with-turbo-content/modal-with-turbo-content.directive.ts index b28678ef00d..3789bd5c98f 100644 --- a/frontend/src/app/shared/components/fields/edit/modal-with-turbo-content/modal-with-turbo-content.directive.ts +++ b/frontend/src/app/shared/components/fields/edit/modal-with-turbo-content/modal-with-turbo-content.directive.ts @@ -27,7 +27,6 @@ //++ import { AfterViewInit, ChangeDetectorRef, Directive, ElementRef, EventEmitter, Input, OnDestroy, Output, inject } from '@angular/core'; -import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { HalResource } from 'core-app/features/hal/resources/hal-resource'; import { HalEventsService } from 'core-app/features/hal/services/hal-events.service'; import { ToastService } from 'core-app/shared/components/toaster/toast.service'; @@ -132,11 +131,11 @@ export class ModalWithTurboContentDirective implements AfterViewInit, OnDestroy if (fetchResponse?.succeeded) { this.halEvents.push( - this.resource as WorkPackageResource, + this.resource, { eventType: 'updated' }, ); - void this.apiV3Service.work_packages.id(this.resource as WorkPackageResource).refresh(); + void this.apiV3Service.work_packages.id(this.resource).refresh(); this.successfulUpdate.emit(); diff --git a/frontend/src/app/shared/components/fields/macros/attribute-model-loader.service.ts b/frontend/src/app/shared/components/fields/macros/attribute-model-loader.service.ts index 6defe967826..1d8b4202dc0 100644 --- a/frontend/src/app/shared/components/fields/macros/attribute-model-loader.service.ts +++ b/frontend/src/app/shared/components/fields/macros/attribute-model-loader.service.ts @@ -94,7 +94,7 @@ export class AttributeModelLoaderService { shareReplay(1), ); - state.clearAndPutFromPromise(firstValueFrom(observable) as PromiseLike); + state.clearAndPutFromPromise(firstValueFrom(observable)); return observable; } diff --git a/frontend/src/app/shared/components/searchable-project-list/searchable-project-list.service.ts b/frontend/src/app/shared/components/searchable-project-list/searchable-project-list.service.ts index de550244fae..3a8a8528d34 100644 --- a/frontend/src/app/shared/components/searchable-project-list/searchable-project-list.service.ts +++ b/frontend/src/app/shared/components/searchable-project-list/searchable-project-list.service.ts @@ -102,11 +102,11 @@ export class SearchableProjectListService { // such as favorites or the preloaded projects (current project, selected projects) // in a filtered view, it's legitimate for them to be missing, thus we skip extra fetching if a search text is present if(!loadingEnabled || searchText.length > 0) { - return of([projects, false as boolean]); + return of([projects, false]); } return this.pipeConcatProjects(projects, this.preloadProjectIds.concat(favoriteIds)) - .pipe(map((p) => [p, true as boolean])); + .pipe(map((p) => [p, true])); }), switchMap(([projects, enhancePreloadedProjects]:[IProject[],boolean]) => { // These can be fetched in parallel to ancestors, since they share ancestors with preloadProjectIds entries and thus diff --git a/frontend/src/app/shared/helpers/drag-and-drop/dom-autoscroll.service.ts b/frontend/src/app/shared/helpers/drag-and-drop/dom-autoscroll.service.ts index 35b3099177a..60d352eea2b 100644 --- a/frontend/src/app/shared/helpers/drag-and-drop/dom-autoscroll.service.ts +++ b/frontend/src/app/shared/helpers/drag-and-drop/dom-autoscroll.service.ts @@ -120,9 +120,9 @@ export class DomAutoscrollService { public getElementsUnderPoint():HTMLElement[] { const underPoint = []; - for (let i = 0; i < this.elements.length; i++) { - if (this.inside(this.point, this.elements[i])) { - underPoint.push(this.elements[i] as HTMLElement); + for (const element of this.elements) { + if (this.inside(this.point, element)) { + underPoint.push(element as HTMLElement); // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion } } diff --git a/frontend/src/app/spot/components/drop-modal/drop-modal.component.ts b/frontend/src/app/spot/components/drop-modal/drop-modal.component.ts index 111d7155064..4b07dd2e49b 100644 --- a/frontend/src/app/spot/components/drop-modal/drop-modal.component.ts +++ b/frontend/src/app/spot/components/drop-modal/drop-modal.component.ts @@ -183,7 +183,7 @@ export class SpotDropModalComponent implements OnDestroy { (this.focusGrabber.nativeElement as HTMLElement).focus(); } - private onGlobalClick = this.close.bind(this) as () => void; + private onGlobalClick = this.close.bind(this); ngOnDestroy():void { if (this.opened) {