+
+
+
+
+ @if (filterContainerDefinition) {
+
+ }
+
+
+
+
diff --git a/frontend/src/app/features/calendar/wp-calendar-page/wp-calendar-page.component.ts b/frontend/src/app/features/calendar/wp-calendar-page/wp-calendar-page.component.ts
index 06bc7e78440..1c0a3052e78 100644
--- a/frontend/src/app/features/calendar/wp-calendar-page/wp-calendar-page.component.ts
+++ b/frontend/src/app/features/calendar/wp-calendar-page/wp-calendar-page.component.ts
@@ -29,6 +29,8 @@
import {
ChangeDetectionStrategy,
Component,
+ Input,
+ OnInit,
ViewChild,
} from '@angular/core';
import { WorkPackagesCalendarComponent } from 'core-app/features/calendar/wp-calendar/wp-calendar.component';
@@ -50,7 +52,8 @@ import { ActionsService } from 'core-app/core/state/actions/actions.service';
import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decorator';
@Component({
- templateUrl: '../../work-packages/routing/partitioned-query-space-page/partitioned-query-space-page.component.html',
+ selector: 'op-wp-calendar-page',
+ templateUrl: './wp-calendar-page.component.html',
styleUrls: [
'../../work-packages/routing/partitioned-query-space-page/partitioned-query-space-page.component.sass',
],
@@ -60,7 +63,9 @@ import { InjectField } from 'core-app/shared/helpers/angular/inject-field.decora
],
standalone: false,
})
-export class WorkPackagesCalendarPageComponent extends PartitionedQuerySpacePageComponent {
+export class WorkPackagesCalendarPageComponent extends PartitionedQuerySpacePageComponent implements OnInit {
+ @Input() queryId:string;
+
@InjectField(ActionsService) actions$:ActionsService;
@ViewChild(WorkPackagesCalendarComponent, { static: true }) calendarElement:WorkPackagesCalendarComponent;
@@ -121,6 +126,20 @@ export class WorkPackagesCalendarPageComponent extends PartitionedQuerySpacePage
},
];
+ override ngOnInit():void {
+ super.ngOnInit();
+ // Fix showToolbarSaveButton from actual URL params (not uiRouter state)
+ this.showToolbarSaveButton = !!new URLSearchParams(window.location.search).get('query_props');
+
+ // Update save button reactively when query_props changes via pushState (non-uiRouter pages)
+ this.wpListChecksumService.visibleChecksum$
+ .pipe(this.untilDestroyed())
+ .subscribe((checksum) => {
+ this.showToolbarSaveButton = !!checksum;
+ this.cdRef.detectChanges();
+ });
+ }
+
/**
* We need to set the current partition to the grid to ensure
* either side gets expanded to full width if we're not in '-split' mode.
diff --git a/frontend/src/app/features/calendar/wp-calendar/wp-calendar.component.ts b/frontend/src/app/features/calendar/wp-calendar/wp-calendar.component.ts
index e0f6f2f5f2c..63e93f1ad67 100644
--- a/frontend/src/app/features/calendar/wp-calendar/wp-calendar.component.ts
+++ b/frontend/src/app/features/calendar/wp-calendar/wp-calendar.component.ts
@@ -57,7 +57,6 @@ import {
WorkPackageViewFiltersService,
} from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-view-filters.service';
import { WorkPackagesListService } from 'core-app/features/work-packages/components/wp-list/wp-list.service';
-import { StateService } from '@uirouter/core';
import { I18nService } from 'core-app/core/i18n/i18n.service';
import { ToastService } from 'core-app/shared/components/toaster/toast.service';
import { DomSanitizer } from '@angular/platform-browser';
@@ -70,7 +69,7 @@ import {
HalResourceEditingService,
} from 'core-app/shared/components/fields/edit/services/hal-resource-editing.service';
import { HalResourceNotificationService } from 'core-app/features/hal/services/hal-resource-notification.service';
-import { splitViewRoute } from 'core-app/features/work-packages/routing/split-view-routes.helper';
+import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import {
CalendarViewEvent,
OpWorkPackagesCalendarService,
@@ -88,7 +87,6 @@ import {
import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service';
import { ApiV3FilterBuilder } from 'core-app/shared/helpers/api-v3/api-v3-filter-builder';
import allLocales from '@fullcalendar/core/locales-all';
-import { PathHelperService } from 'core-app/core/path-helper/path-helper.service';
import { MeetingResource } from 'core-app/features/hal/resources/meeting-resource';
import { TimezoneService } from 'core-app/core/datetime/timezone.service';
@@ -128,7 +126,6 @@ export class WorkPackagesCalendarComponent extends UntilDestroyedMixin implement
constructor(
readonly actions$:ActionsService,
readonly states:States,
- readonly $state:StateService,
readonly wpTableFilters:WorkPackageViewFiltersService,
readonly wpListService:WorkPackagesListService,
readonly querySpace:IsolatedQuerySpace,
@@ -345,13 +342,10 @@ export class WorkPackagesCalendarComponent extends UntilDestroyedMixin implement
const workPackageId = (evt.event.extendedProps.workPackage as WorkPackageResource).id!;
// Currently the calendar widget is shown on multiple pages,
// but only the calendar module itself is a partitioned query space which can deal with a split screen request
- if (this.$state.includes('calendar')) {
+ if (window.location.pathname.includes('/calendars/')) {
this.workPackagesCalendar.openSplitView(workPackageId);
} else {
- void this.$state.go(
- 'work-packages.show',
- { workPackageId },
- );
+ window.location.href = this.pathHelper.workPackagePath(workPackageId);
}
}
},
@@ -414,7 +408,7 @@ export class WorkPackagesCalendarComponent extends UntilDestroyedMixin implement
durationEditable: this.workPackagesCalendar.eventDurationEditable(workPackage),
end: exclusiveEnd,
allDay: true,
- className: `fc-event-clickable __hl_background_type_${workPackage.type.id || ''}`,
+ className: `fc-event-clickable __hl_background_type_${workPackage.type.id ?? ''}`,
workPackage,
};
});
@@ -444,13 +438,17 @@ export class WorkPackagesCalendarComponent extends UntilDestroyedMixin implement
ignoreNonWorkingDays: nonWorkingDays,
};
- void this.$state.go(
- splitViewRoute(this.$state, 'new'),
- {
- defaults,
- tabIdentifier: 'overview',
- },
- );
+ if (window.location.pathname.includes('/calendars/')) {
+ const basePath = window.location.pathname.replace(/\/details\/.*$/, '');
+ const params = new URLSearchParams(window.location.search);
+ params.set('startDate', defaults.startDate);
+ params.set('dueDate', defaults.dueDate);
+ if (defaults.ignoreNonWorkingDays) {
+ params.set('ignoreNonWorkingDays', 'true');
+ }
+ const link = `${basePath}/details/new?${params.toString()}`;
+ Turbo.visit(link, { frame: 'content-bodyRight', action: 'advance' });
+ }
}
@EffectCallback(calendarRefreshRequest)
diff --git a/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.html b/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.html
index 3ebc827c247..e43d51c6725 100644
--- a/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.html
+++ b/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.html
@@ -98,7 +98,7 @@
[showStartDate]="!isWpStartDateInCurrentView(wp)"
[showEndDate]="!isWpEndDateInCurrentView(wp)"
(stateLinkClicked)="openStateLink($event)"
- (cardClicked)="workPackagesCalendar.onCardClicked($event)"
+ (cardClicked)="onCardClicked($event)"
(cardDblClicked)="workPackagesCalendar.onCardDblClicked($event)"
(cardContextMenu)="workPackagesCalendar.showEventContextMenu($event)"
/>
diff --git a/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.ts b/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.ts
index 14a92373753..87846cdfd3e 100644
--- a/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.ts
+++ b/frontend/src/app/features/team-planner/team-planner/planner/team-planner.component.ts
@@ -40,6 +40,7 @@ import {
import {
CalendarOptions,
DateSelectArg,
+ DatesSetArg,
EventApi,
EventDropArg,
EventInput,
@@ -78,6 +79,7 @@ import { WorkPackageViewFiltersService } from 'core-app/features/work-packages/r
import { IsolatedQuerySpace } from 'core-app/features/work-packages/directives/query-space/isolated-query-space';
import { CurrentProjectService } from 'core-app/core/current-project/current-project.service';
import { splitViewRoute } from 'core-app/features/work-packages/routing/split-view-routes.helper';
+import { isClickedWithModifier } from 'core-app/shared/helpers/link-handling/link-handling';
import { QueryFilterInstanceResource } from 'core-app/features/hal/resources/query-filter-instance-resource';
import { PrincipalsResourceService } from 'core-app/core/state/principals/principals.service';
import {
@@ -460,6 +462,7 @@ export class TeamPlannerComponent extends UntilDestroyedMixin implements OnInit,
.pipe(
this.untilDestroyed(),
debounceTime(0),
+ filter(() => !!this.ucCalendar),
)
.subscribe(([principals, showAddAssignee]) => {
const api = this.ucCalendar.getApi();
@@ -511,6 +514,19 @@ export class TeamPlannerComponent extends UntilDestroyedMixin implements OnInit,
.then(() => {
this.calendarOptions$.next(
this.workPackagesCalendar.calendarOptions({
+ // Override datesSet to persist cdate/cview via uiRouter instead of pushState,
+ // because uiRouter manages the TeamPlanner URL and would otherwise strip these params.
+ // Remove once uiRouter is removed.
+ datesSet: (dates:DatesSetArg) => {
+ void this.$state.go(
+ '.',
+ {
+ cdate: this.workPackagesCalendar.timezoneService.formattedISODate(dates.view.calendar.getDate()),
+ cview: (dates.view as unknown as { type:string }).type,
+ },
+ { custom: { notify: false } },
+ );
+ },
locales: allLocales,
locale: this.I18n.locale,
schedulerLicenseKey: 'GPL-My-Project-Is-Open-Source',
@@ -857,6 +873,20 @@ export class TeamPlannerComponent extends UntilDestroyedMixin implements OnInit,
}
}
+ onCardClicked({ workPackageId, event }:{ workPackageId:string, event:MouseEvent }):void {
+ if (isClickedWithModifier(event)) {
+ return;
+ }
+
+ // Only switch the split view if it is already open
+ if (!window.location.pathname.includes('/details/')) {
+ return;
+ }
+
+ this.workPackagesCalendar.wpTableSelection.setSelection(workPackageId, -1);
+ this.keepTab.goCurrentDetailsState({ workPackageId });
+ }
+
shouldShowAsGhost(id:string, globalDraggingId:string|undefined):boolean {
if (globalDraggingId === undefined) {
return false;
diff --git a/frontend/src/app/features/work-packages/components/wp-list/wp-list-checksum.service.ts b/frontend/src/app/features/work-packages/components/wp-list/wp-list-checksum.service.ts
index 45bcd15c26a..8b214522f5f 100644
--- a/frontend/src/app/features/work-packages/components/wp-list/wp-list-checksum.service.ts
+++ b/frontend/src/app/features/work-packages/components/wp-list/wp-list-checksum.service.ts
@@ -31,6 +31,7 @@ import { UrlParamsHelperService } from 'core-app/features/work-packages/componen
import { Injectable } from '@angular/core';
import { WorkPackageViewPagination } from 'core-app/features/work-packages/routing/wp-view-base/view-services/wp-table-pagination';
import { QueryResource } from 'core-app/features/hal/resources/query-resource';
+import { Subject } from 'rxjs';
@Injectable()
export class WorkPackagesListChecksumService {
@@ -44,6 +45,9 @@ export class WorkPackagesListChecksumService {
public visibleChecksum:string|null;
+ /** Emits whenever visibleChecksum changes (useful for non-uiRouter pages to react to URL param changes) */
+ public readonly visibleChecksum$ = new Subject