mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Merged PR 2049: feat(oms): add auto-refresh for open reward tasks
feat(oms): add auto-refresh for open reward tasks Implements 5-minute polling to automatically update open reward tasks without requiring manual page refresh. Uses reference counting to safely handle multiple consumers starting/stopping the refresh. Changes: - Add startAutoRefresh/stopAutoRefresh methods with ref counting - Add lifecycle hooks to carousel component for proper cleanup - Add logging for debugging auto-refresh behavior - Add refresh interval constant (5 minutes) Closes #5463 Related work items: #5463
This commit is contained in:
committed by
Nino Righi
parent
8b852cbd7a
commit
dc26c4de04
@@ -1,4 +1,11 @@
|
||||
import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
inject,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
} from '@angular/core';
|
||||
import { OpenRewardTasksResource } from '@isa/oms/data-access';
|
||||
import { CarouselComponent } from '@isa/ui/carousel';
|
||||
import { OpenTaskCardComponent } from './open-task-card.component';
|
||||
@@ -32,7 +39,7 @@ import { OpenTaskCardComponent } from './open-task-card.component';
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class OpenTasksCarouselComponent {
|
||||
export class OpenTasksCarouselComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* Global resource managing open reward tasks data
|
||||
*/
|
||||
@@ -48,7 +55,7 @@ export class OpenTasksCarouselComponent {
|
||||
const tasks = this.openTasksResource.tasks();
|
||||
const seenOrderIds = new Set<number>();
|
||||
|
||||
return tasks.filter(task => {
|
||||
return tasks.filter((task) => {
|
||||
if (!task.orderId || seenOrderIds.has(task.orderId)) {
|
||||
return false;
|
||||
}
|
||||
@@ -56,4 +63,21 @@ export class OpenTasksCarouselComponent {
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Initializes the component by loading open tasks and starting auto-refresh.
|
||||
* Ensures the carousel displays current data when mounted.
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.openTasksResource.refresh();
|
||||
this.openTasksResource.startAutoRefresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup lifecycle hook that stops auto-refresh polling.
|
||||
* Prevents memory leaks by clearing the refresh interval.
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.openTasksResource.stopAutoRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
export const OMS_DISPLAY_ORDERS_KEY = 'OMS-DATA-ACCESS.DISPLAY_ORDERS';
|
||||
|
||||
/**
|
||||
* Auto-refresh interval for open reward tasks polling.
|
||||
* Set to 5 minutes (300,000 ms) to periodically check for new or completed tasks.
|
||||
*/
|
||||
export const OMS_OPEN_REWARD_TASK_REFRESH_INTERVAL_MS = 60 * 1000 * 5; // 5 minutes
|
||||
|
||||
@@ -1,73 +1,137 @@
|
||||
import { computed, inject, Injectable, resource, Signal } from '@angular/core';
|
||||
import { DBHOrderItemListItemDTO } from '@generated/swagger/oms-api';
|
||||
import { OpenRewardTasksService } from '../services/open-reward-tasks.service';
|
||||
|
||||
/**
|
||||
* Global resource for managing open reward distribution tasks (Prämienausgabe).
|
||||
*
|
||||
* Provides reactive access to unfinished reward orders across the application.
|
||||
* This resource is provided at root level to ensure a single shared instance
|
||||
* for both the side menu indicator and the reward catalog carousel.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // In component
|
||||
* readonly openTasksResource = inject(OpenRewardTasksResource);
|
||||
* readonly hasOpenTasks = computed(() =>
|
||||
* (this.openTasksResource.tasks()?.length ?? 0) > 0
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class OpenRewardTasksResource {
|
||||
#openRewardTasksService = inject(OpenRewardTasksService);
|
||||
|
||||
/**
|
||||
* Internal resource that manages data fetching and caching
|
||||
*/
|
||||
#resource = resource({
|
||||
loader: async ({ abortSignal }): Promise<DBHOrderItemListItemDTO[]> => {
|
||||
return await this.#openRewardTasksService.getOpenRewardTasks(
|
||||
abortSignal,
|
||||
);
|
||||
},
|
||||
defaultValue: [],
|
||||
});
|
||||
|
||||
/**
|
||||
* Signal containing the array of open reward tasks.
|
||||
* Returns empty array when loading or on error.
|
||||
*/
|
||||
readonly tasks: Signal<readonly DBHOrderItemListItemDTO[]> =
|
||||
this.#resource.value.asReadonly();
|
||||
|
||||
/**
|
||||
* Signal indicating whether data is currently being fetched
|
||||
*/
|
||||
readonly loading: Signal<boolean> = this.#resource.isLoading;
|
||||
|
||||
/**
|
||||
* Signal containing error message if fetch failed, otherwise null
|
||||
*/
|
||||
readonly error = computed(
|
||||
() => this.#resource.error()?.message ?? null,
|
||||
);
|
||||
|
||||
/**
|
||||
* Signal indicating whether there are any open tasks
|
||||
*/
|
||||
readonly hasOpenTasks = computed(() => (this.tasks()?.length ?? 0) > 0);
|
||||
|
||||
/**
|
||||
* Signal containing the count of open tasks
|
||||
*/
|
||||
readonly taskCount = computed(() => this.tasks()?.length ?? 0);
|
||||
|
||||
/**
|
||||
* Manually refresh the open tasks data.
|
||||
* Useful for updating after a task is completed.
|
||||
*/
|
||||
refresh(): void {
|
||||
this.#resource.reload();
|
||||
}
|
||||
}
|
||||
import { computed, inject, Injectable, resource, Signal } from '@angular/core';
|
||||
import { DBHOrderItemListItemDTO } from '@generated/swagger/oms-api';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import { OpenRewardTasksService } from '../services/open-reward-tasks.service';
|
||||
import { OMS_OPEN_REWARD_TASK_REFRESH_INTERVAL_MS } from '../constants';
|
||||
|
||||
/**
|
||||
* Global resource for managing open reward distribution tasks (Prämienausgabe).
|
||||
*
|
||||
* Provides reactive access to unfinished reward orders across the application.
|
||||
* This resource is provided at root level to ensure a single shared instance
|
||||
* for both the side menu indicator and the reward catalog carousel.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // In component
|
||||
* readonly openTasksResource = inject(OpenRewardTasksResource);
|
||||
* readonly hasOpenTasks = computed(() =>
|
||||
* (this.openTasksResource.tasks()?.length ?? 0) > 0
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class OpenRewardTasksResource {
|
||||
#logger = logger({ resource: 'OpenRewardTasksResource' });
|
||||
|
||||
/**
|
||||
* Interval ID for auto-refresh timer. Null when not actively polling.
|
||||
*/
|
||||
#interval: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
/**
|
||||
* Reference counter tracking how many consumers have started auto-refresh.
|
||||
* Used to prevent stopping the interval while other consumers still need it.
|
||||
*/
|
||||
#refCount = 0;
|
||||
|
||||
#openRewardTasksService = inject(OpenRewardTasksService);
|
||||
|
||||
/**
|
||||
* Internal resource that manages data fetching and caching
|
||||
*/
|
||||
#resource = resource({
|
||||
loader: async ({ abortSignal }): Promise<DBHOrderItemListItemDTO[]> => {
|
||||
return await this.#openRewardTasksService.getOpenRewardTasks(abortSignal);
|
||||
},
|
||||
defaultValue: [],
|
||||
});
|
||||
|
||||
/**
|
||||
* Signal containing the array of open reward tasks.
|
||||
* Returns empty array when loading or on error.
|
||||
*/
|
||||
readonly tasks: Signal<readonly DBHOrderItemListItemDTO[]> =
|
||||
this.#resource.value.asReadonly();
|
||||
|
||||
/**
|
||||
* Signal indicating whether data is currently being fetched
|
||||
*/
|
||||
readonly loading: Signal<boolean> = this.#resource.isLoading;
|
||||
|
||||
/**
|
||||
* Signal containing error message if fetch failed, otherwise null
|
||||
*/
|
||||
readonly error = computed(() => this.#resource.error()?.message ?? null);
|
||||
|
||||
/**
|
||||
* Signal indicating whether there are any open tasks
|
||||
*/
|
||||
readonly hasOpenTasks = computed(() => (this.tasks()?.length ?? 0) > 0);
|
||||
|
||||
/**
|
||||
* Signal containing the count of open tasks
|
||||
*/
|
||||
readonly taskCount = computed(() => this.tasks()?.length ?? 0);
|
||||
|
||||
/**
|
||||
* Manually refresh the open tasks data.
|
||||
* Useful for updating after a task is completed.
|
||||
*/
|
||||
refresh(): void {
|
||||
this.#resource.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts automatic polling of open reward tasks.
|
||||
* Refreshes data every 5 minutes to keep the task list up-to-date.
|
||||
* Uses reference counting - multiple consumers can safely call this method.
|
||||
* The interval only starts when the first consumer calls it.
|
||||
*/
|
||||
startAutoRefresh(): void {
|
||||
this.#refCount++;
|
||||
|
||||
this.#logger.debug('Auto-refresh start requested', () => ({
|
||||
refCount: this.#refCount,
|
||||
intervalActive: this.#interval !== null,
|
||||
}));
|
||||
|
||||
if (this.#interval) {
|
||||
return; // Already running
|
||||
}
|
||||
|
||||
this.#interval = setInterval(() => {
|
||||
this.refresh();
|
||||
}, OMS_OPEN_REWARD_TASK_REFRESH_INTERVAL_MS);
|
||||
|
||||
this.#logger.info('Auto-refresh started', () => ({
|
||||
intervalMs: OMS_OPEN_REWARD_TASK_REFRESH_INTERVAL_MS,
|
||||
refCount: this.#refCount,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops automatic polling of open reward tasks.
|
||||
* Uses reference counting - only actually stops when all consumers have called this.
|
||||
* Should be called during component cleanup to prevent memory leaks.
|
||||
*/
|
||||
stopAutoRefresh(): void {
|
||||
if (this.#refCount > 0) {
|
||||
this.#refCount--;
|
||||
}
|
||||
|
||||
this.#logger.debug('Auto-refresh stop requested', () => ({
|
||||
refCount: this.#refCount,
|
||||
intervalActive: this.#interval !== null,
|
||||
}));
|
||||
|
||||
if (this.#refCount === 0 && this.#interval) {
|
||||
clearInterval(this.#interval);
|
||||
this.#interval = null;
|
||||
|
||||
this.#logger.info('Auto-refresh stopped', () => ({
|
||||
reason: 'All consumers released',
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user