mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Merged PR 1910: feat(remission-list, ui-tooltip): add info tooltip with performance optimization
feat(remission-list, ui-tooltip): add info tooltip with performance optimization Add tooltip to department capacity info button with enhanced trigger management. Optimize department list fetching to only load when search input or department filter is active, improving initial load performance. - Add tooltip directive to info button showing capacity details - Implement conditional department list fetching based on input/filter presence - Enhance tooltip directive with improved trigger management and positioning - Update tooltip component to use modern Angular control flow syntax - Add proper show/hide logic with trigger-specific behavior Refs: #5255
This commit is contained in:
committed by
Andreas Schickinger
parent
a0f24aac17
commit
d22e320294
@@ -5,16 +5,16 @@
|
||||
>
|
||||
</filter-input-menu-button>
|
||||
|
||||
@if (displayCapacityValues()) {
|
||||
@if (selectedDepartments()) {
|
||||
<ui-toolbar class="ui-toolbar-rounded">
|
||||
<span class="isa-text-body-2-regular"
|
||||
><span class="isa-text-body-2-bold"
|
||||
<span class="flex gap-1 isa-text-body-2-regular"
|
||||
><span *uiSkeletonLoader="capacityFetching()" class="isa-text-body-2-bold"
|
||||
>{{ leistung() }}/{{ maxLeistung() }}</span
|
||||
>
|
||||
Leistung</span
|
||||
>
|
||||
<span class="isa-text-body-2-regular"
|
||||
><span class="isa-text-body-2-bold"
|
||||
<span class="flex gap-1 isa-text-body-2-regular"
|
||||
><span *uiSkeletonLoader="capacityFetching()" class="isa-text-body-2-bold"
|
||||
>{{ stapel() }}/{{ maxStapel() }}</span
|
||||
>
|
||||
Stapel</span
|
||||
@@ -23,7 +23,6 @@
|
||||
class="w-6 h-6 flex items-center justify-center text-isa-accent-blue"
|
||||
uiTooltip
|
||||
[title]="'Stapel/Leistungsplätze'"
|
||||
[content]="''"
|
||||
[triggerOn]="['click', 'hover']"
|
||||
>
|
||||
<ng-icon size="1.5rem" name="isaOtherInfo"></ng-icon>
|
||||
|
||||
@@ -18,6 +18,7 @@ import { ToolbarComponent } from '@isa/ui/toolbar';
|
||||
import { TooltipDirective } from '@isa/ui/tooltip';
|
||||
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
||||
import { createRemissionCapacityResource } from '../resources';
|
||||
import { SkeletonLoaderDirective } from '@isa/ui/skeleton-loader';
|
||||
|
||||
@Component({
|
||||
selector: 'remi-feature-remission-list-department-elements',
|
||||
@@ -30,6 +31,7 @@ import { createRemissionCapacityResource } from '../resources';
|
||||
ToolbarComponent,
|
||||
TooltipDirective,
|
||||
NgIconComponent,
|
||||
SkeletonLoaderDirective,
|
||||
],
|
||||
})
|
||||
export class RemissionListDepartmentElementsComponent {
|
||||
@@ -75,12 +77,19 @@ export class RemissionListDepartmentElementsComponent {
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Computed signal to get the current value of the capacity resource.
|
||||
* @returns {Array} The current capacity values or an empty array if not available.
|
||||
*/
|
||||
capacityResourceValue = computed(() => this.capacityResource.value());
|
||||
|
||||
displayCapacityValues = computed(() => {
|
||||
const value = this.capacityResourceValue();
|
||||
return !!value && value?.length > 0;
|
||||
});
|
||||
/**
|
||||
* Computed signal to check if the capacity resource is currently fetching data.
|
||||
* @returns {boolean} True if the resource is loading, false otherwise.
|
||||
*/
|
||||
capacityFetching = computed(
|
||||
() => this.capacityResource.status() === 'loading',
|
||||
);
|
||||
|
||||
leistungValues = computed(() => {
|
||||
const value = this.capacityResourceValue();
|
||||
|
||||
@@ -73,18 +73,15 @@ export const createRemissionListResource = (
|
||||
let res: ListResponseArgs<RemissionItem> | undefined = undefined;
|
||||
|
||||
const queryToken = { ...params.queryToken };
|
||||
const exactSearch = isExactSearch(queryToken, params.searchTrigger);
|
||||
|
||||
// #5128 #5234 Bei Exact Search soll er über Alle Listen nur mit dem Input ohne aktive Filter / orderBy suchen
|
||||
const isExactSearch =
|
||||
params.searchTrigger === 'scan' || isEan(queryToken?.input?.['qs']);
|
||||
|
||||
if (isExactSearch) {
|
||||
if (exactSearch) {
|
||||
queryToken.filter = {};
|
||||
queryToken.orderBy = [];
|
||||
}
|
||||
|
||||
if (
|
||||
isExactSearch ||
|
||||
exactSearch ||
|
||||
params.remissionListType === RemissionListType.Pflicht
|
||||
) {
|
||||
const fetchListResponse = await remissionSearchService.fetchList(
|
||||
@@ -99,8 +96,8 @@ export const createRemissionListResource = (
|
||||
}
|
||||
|
||||
if (
|
||||
isExactSearch ||
|
||||
params.remissionListType === RemissionListType.Abteilung
|
||||
exactSearch ||
|
||||
canFetchDepartmentList(queryToken, params.remissionListType)
|
||||
) {
|
||||
const fetchDepartmentListResponse =
|
||||
await remissionSearchService.fetchDepartmentList(
|
||||
@@ -129,36 +126,88 @@ export const createRemissionListResource = (
|
||||
throw new ResponseArgsError(res);
|
||||
}
|
||||
|
||||
// Sort items: manually-added items first, then by created date (latest first)
|
||||
if (res && res.result && Array.isArray(res.result)) {
|
||||
res.result.sort((a, b) => {
|
||||
const aIsManuallyAdded = a.source === 'manually-added';
|
||||
const bIsManuallyAdded = b.source === 'manually-added';
|
||||
|
||||
// First priority: manually-added items come first
|
||||
if (aIsManuallyAdded && !bIsManuallyAdded) {
|
||||
return -1;
|
||||
}
|
||||
if (!aIsManuallyAdded && bIsManuallyAdded) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Second priority: sort by created date (latest first)
|
||||
if (a.created && b.created) {
|
||||
const dateA = parseISO(a.created);
|
||||
const dateB = parseISO(b.created);
|
||||
return compareDesc(dateA, dateB); // Descending order (latest first)
|
||||
}
|
||||
|
||||
// Handle cases where created date might be missing
|
||||
if (a.created && !b.created) return -1;
|
||||
if (!a.created && b.created) return 1;
|
||||
|
||||
return 0;
|
||||
});
|
||||
sortResponseResult(res);
|
||||
}
|
||||
|
||||
return res;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Sorts the response result of remission items.
|
||||
* - Manually added items come first.
|
||||
* - Then sorted by created date in descending order (latest first).
|
||||
*
|
||||
* @param {ListResponseArgs<RemissionItem>} resopnse - The response containing remission items to sort
|
||||
*/
|
||||
const sortResponseResult = (
|
||||
resopnse: ListResponseArgs<RemissionItem>,
|
||||
): void => {
|
||||
resopnse.result.sort((a, b) => {
|
||||
const aIsManuallyAdded = a.source === 'manually-added';
|
||||
const bIsManuallyAdded = b.source === 'manually-added';
|
||||
|
||||
// First priority: manually-added items come first
|
||||
if (aIsManuallyAdded && !bIsManuallyAdded) {
|
||||
return -1;
|
||||
}
|
||||
if (!aIsManuallyAdded && bIsManuallyAdded) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Second priority: sort by created date (latest first)
|
||||
if (a.created && b.created) {
|
||||
const dateA = parseISO(a.created);
|
||||
const dateB = parseISO(b.created);
|
||||
return compareDesc(dateA, dateB); // Descending order (latest first)
|
||||
}
|
||||
|
||||
// Handle cases where created date might be missing
|
||||
if (a.created && !b.created) return -1;
|
||||
if (!a.created && b.created) return 1;
|
||||
|
||||
return 0;
|
||||
});
|
||||
};
|
||||
|
||||
// #5128 #5234 Bei Exact Search soll er über Alle Listen nur mit dem Input ohne aktive Filter / orderBy suchen
|
||||
/**
|
||||
* Checks if the query token is an exact search based on the search trigger.
|
||||
* An exact search is defined as:
|
||||
* - Triggered by 'scan'
|
||||
* - Or if the query token input contains a valid EAN (barcode) in 'qs'
|
||||
* @param {QueryTokenInput} queryToken - The query token containing input parameters
|
||||
* @param {SearchTrigger} searchTrigger - The trigger that initiated the search
|
||||
* @returns {boolean} True if the search is exact, false otherwise
|
||||
*/
|
||||
const isExactSearch = (
|
||||
queryToken: QueryTokenInput,
|
||||
searchTrigger: SearchTrigger | 'reload' | 'initial',
|
||||
): boolean => {
|
||||
return searchTrigger === 'scan' || isEan(queryToken?.input?.['qs']);
|
||||
};
|
||||
|
||||
// #5255 Performance optimization for initial department list fetch
|
||||
/**
|
||||
* Checks if the query token allows fetching the department list.
|
||||
* This is true if the remission list type is 'Abteilung' and either:
|
||||
* - There is a search input (queryToken.input['qs'])
|
||||
* - There is an active filter for 'abteilungen'
|
||||
*
|
||||
* @param {QueryTokenInput} queryToken - The query token containing input and filter
|
||||
* @param {RemissionListType} remissionListType - The type of remission list being queried
|
||||
* @returns {boolean} True if the department list can be fetched, false otherwise
|
||||
*/
|
||||
const canFetchDepartmentList = (
|
||||
queryToken: QueryTokenInput,
|
||||
remissionListType: RemissionListType,
|
||||
): boolean => {
|
||||
const hasInput = queryToken?.input?.['qs'];
|
||||
const hasAbteilungFilter = queryToken?.filter?.['abteilungen'];
|
||||
return (
|
||||
remissionListType === RemissionListType.Abteilung &&
|
||||
(hasInput || hasAbteilungFilter)
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,11 +3,14 @@
|
||||
{{ title() }}
|
||||
</div>
|
||||
}
|
||||
<div class="ui-tooltip-content">
|
||||
@let t = template();
|
||||
@if (t) {
|
||||
<ng-container *ngTemplateOutlet="t"></ng-container>
|
||||
} @else {
|
||||
{{ text() }}
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (text() || template()) {
|
||||
<div class="ui-tooltip-content">
|
||||
@let t = template();
|
||||
@if (t) {
|
||||
<ng-container *ngTemplateOutlet="t"></ng-container>
|
||||
} @else {
|
||||
{{ text() }}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -82,8 +82,8 @@ export class TooltipDirective implements OnDestroy {
|
||||
|
||||
/** Optional title for the tooltip. */
|
||||
title = input<string>();
|
||||
/** Content for the tooltip. Can be a string or a TemplateRef. This input is required. */
|
||||
content = input.required<string | TemplateRef<unknown>>();
|
||||
/** Content for the tooltip. Can be a string or a TemplateRef. */
|
||||
content = input<string | TemplateRef<unknown>>();
|
||||
|
||||
/**
|
||||
* Array of triggers that will cause the tooltip to show.
|
||||
|
||||
Reference in New Issue
Block a user