mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Merged PR 1837: Fix - Filter Reset und Filter Sync - Removed unused code, logger performance
refactor: improve code formatting and readability in provide-filter.ts and filter-menu components fix: delay filter rollback on close in FilterMenuButtonComponent fix: update filter clear button text and method calls in filter-menu.component.html chore: update package-lock.json to remove unnecessary dev flags and add new dependencies Ref: #5125, #5076
This commit is contained in:
committed by
Nino Righi
parent
c322020c3f
commit
0d202ab97c
@@ -61,9 +61,8 @@ export function logger(ctxFn?: () => LoggerContext): LoggerApi {
|
||||
* @param message The log message.
|
||||
* @param additionalContext Optional context data specific to this log message.
|
||||
*/
|
||||
trace: (message: string, additionalContext?: LoggerContext): void => {
|
||||
loggingService.trace(
|
||||
message,
|
||||
trace: (message: string, additionalContext?: () => LoggerContext): void => {
|
||||
loggingService.trace(message, () =>
|
||||
mergeContexts(context, ctxFn, additionalContext),
|
||||
);
|
||||
},
|
||||
@@ -72,9 +71,8 @@ export function logger(ctxFn?: () => LoggerContext): LoggerApi {
|
||||
* @param message The log message.
|
||||
* @param additionalContext Optional context data specific to this log message.
|
||||
*/
|
||||
debug: (message: string, additionalContext?: LoggerContext): void => {
|
||||
loggingService.debug(
|
||||
message,
|
||||
debug: (message: string, additionalContext?: () => LoggerContext): void => {
|
||||
loggingService.debug(message, () =>
|
||||
mergeContexts(context, ctxFn, additionalContext),
|
||||
);
|
||||
},
|
||||
@@ -83,9 +81,8 @@ export function logger(ctxFn?: () => LoggerContext): LoggerApi {
|
||||
* @param message The log message.
|
||||
* @param additionalContext Optional context data specific to this log message.
|
||||
*/
|
||||
info: (message: string, additionalContext?: LoggerContext): void => {
|
||||
loggingService.info(
|
||||
message,
|
||||
info: (message: string, additionalContext?: () => LoggerContext): void => {
|
||||
loggingService.info(message, () =>
|
||||
mergeContexts(context, ctxFn, additionalContext),
|
||||
);
|
||||
},
|
||||
@@ -94,9 +91,8 @@ export function logger(ctxFn?: () => LoggerContext): LoggerApi {
|
||||
* @param message The log message.
|
||||
* @param additionalContext Optional context data specific to this log message.
|
||||
*/
|
||||
warn: (message: string, additionalContext?: LoggerContext): void => {
|
||||
loggingService.warn(
|
||||
message,
|
||||
warn: (message: string, additionalContext?: () => LoggerContext): void => {
|
||||
loggingService.warn(message, () =>
|
||||
mergeContexts(context, ctxFn, additionalContext),
|
||||
);
|
||||
},
|
||||
@@ -109,11 +105,9 @@ export function logger(ctxFn?: () => LoggerContext): LoggerApi {
|
||||
error: (
|
||||
message: string,
|
||||
error?: Error,
|
||||
additionalContext?: LoggerContext,
|
||||
additionalContext?: () => LoggerContext,
|
||||
): void => {
|
||||
loggingService.error(
|
||||
message,
|
||||
error,
|
||||
loggingService.error(message, error, () =>
|
||||
mergeContexts(context, ctxFn, additionalContext),
|
||||
);
|
||||
},
|
||||
@@ -135,7 +129,7 @@ export function logger(ctxFn?: () => LoggerContext): LoggerApi {
|
||||
function mergeContexts(
|
||||
baseContext: LoggerContext[] | null,
|
||||
injectorContext?: () => LoggerContext,
|
||||
additionalContext?: LoggerContext,
|
||||
additionalContext?: () => LoggerContext,
|
||||
): LoggerContext {
|
||||
const contextArray = Array.isArray(baseContext) ? baseContext : [];
|
||||
|
||||
@@ -145,9 +139,7 @@ function mergeContexts(
|
||||
contextArray.push(injectorCtx);
|
||||
}
|
||||
|
||||
if (typeof additionalContext === 'object') {
|
||||
contextArray.push(additionalContext);
|
||||
}
|
||||
contextArray.push(additionalContext ? additionalContext() : {});
|
||||
|
||||
if (!contextArray.length) {
|
||||
return {};
|
||||
|
||||
@@ -106,7 +106,7 @@ export class LoggingService implements LoggerApi {
|
||||
* @param message - The message to log
|
||||
* @param context - Optional metadata or structured data to include
|
||||
*/
|
||||
trace(message: string, context?: LoggerContext): void {
|
||||
trace(message: string, context?: () => LoggerContext): void {
|
||||
this.log(LogLevel.Trace, message, context);
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ export class LoggingService implements LoggerApi {
|
||||
* @param message - The message to log
|
||||
* @param context - Optional metadata or structured data to include
|
||||
*/
|
||||
debug(message: string, context?: LoggerContext): void {
|
||||
debug(message: string, context?: () => LoggerContext): void {
|
||||
this.log(LogLevel.Debug, message, context);
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ export class LoggingService implements LoggerApi {
|
||||
* @param message - The message to log
|
||||
* @param context - Optional metadata or structured data to include
|
||||
*/
|
||||
info(message: string, context?: LoggerContext): void {
|
||||
info(message: string, context?: () => LoggerContext): void {
|
||||
this.log(LogLevel.Info, message, context);
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ export class LoggingService implements LoggerApi {
|
||||
* @param message - The message to log
|
||||
* @param context - Optional metadata or structured data to include
|
||||
*/
|
||||
warn(message: string, context?: LoggerContext): void {
|
||||
warn(message: string, context?: () => LoggerContext): void {
|
||||
this.log(LogLevel.Warn, message, context);
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ export class LoggingService implements LoggerApi {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
error(message: string, error?: Error, context?: LoggerContext): void {
|
||||
error(message: string, error?: Error, context?: () => LoggerContext): void {
|
||||
this.log(LogLevel.Error, message, context, error);
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ export class LoggingService implements LoggerApi {
|
||||
private log(
|
||||
level: LogLevel,
|
||||
message: string,
|
||||
context?: LoggerContext,
|
||||
context?: () => LoggerContext,
|
||||
error?: Error,
|
||||
): void {
|
||||
// Short-circuit if logging is disabled or level is too low (performance optimization)
|
||||
@@ -192,7 +192,7 @@ export class LoggingService implements LoggerApi {
|
||||
}
|
||||
|
||||
// Merge global context with the provided context
|
||||
const mergedContext = this.mergeContext(context);
|
||||
const mergedContext = this.mergeContext(context?.());
|
||||
|
||||
// Send to all sinks
|
||||
for (const sink of this.sinks) {
|
||||
|
||||
@@ -108,25 +108,25 @@ export interface LoggerApi {
|
||||
* Logs a trace message with optional context.
|
||||
* Use for fine-grained debugging information.
|
||||
*/
|
||||
trace(message: string, context?: LoggerContext): void;
|
||||
trace(message: string, context?: () => LoggerContext): void;
|
||||
|
||||
/**
|
||||
* Logs a debug message with optional context.
|
||||
* Use for development-time debugging information.
|
||||
*/
|
||||
debug(message: string, context?: LoggerContext): void;
|
||||
debug(message: string, context?: () => LoggerContext): void;
|
||||
|
||||
/**
|
||||
* Logs an info message with optional context.
|
||||
* Use for general runtime information.
|
||||
*/
|
||||
info(message: string, context?: LoggerContext): void;
|
||||
info(message: string, context?: () => LoggerContext): void;
|
||||
|
||||
/**
|
||||
* Logs a warning message with optional context.
|
||||
* Use for potentially harmful situations.
|
||||
*/
|
||||
warn(message: string, context?: LoggerContext): void;
|
||||
warn(message: string, context?: () => LoggerContext): void;
|
||||
|
||||
/**
|
||||
* Logs an error message with an optional error object and context.
|
||||
@@ -136,7 +136,7 @@ export interface LoggerApi {
|
||||
* @param error - Any error object or value that caused this error condition
|
||||
* @param context - Optional context data associated with the error
|
||||
*/
|
||||
error(message: string, error: unknown, context?: LoggerContext): void;
|
||||
error(message: string, error: unknown, context?: () => LoggerContext): void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -126,10 +126,10 @@ export class ReturnDetailsOrderGroupItemControlsComponent {
|
||||
this.changeProductCategory(category || 'unknown');
|
||||
this.showProductCategoryDropdownLoading.set(false);
|
||||
} catch (error) {
|
||||
this.#logger.error('Failed to setProductCategory', error, {
|
||||
this.#logger.error('Failed to setProductCategory', error, () => ({
|
||||
itemId: this.item().id,
|
||||
category,
|
||||
});
|
||||
}));
|
||||
this.canReturn.emit(undefined);
|
||||
this.showProductCategoryDropdownLoading.set(false);
|
||||
}
|
||||
|
||||
@@ -157,9 +157,13 @@ export class ReturnProcessItemComponent {
|
||||
await this.#returnCanReturnService.canReturn(returnProcess);
|
||||
this.canReturn.set(canReturnResponse);
|
||||
} catch (error) {
|
||||
this.#logger.error('Failed to validate return process', error, {
|
||||
returnProcessId: returnProcess.id,
|
||||
});
|
||||
this.#logger.error(
|
||||
'Failed to validate return process',
|
||||
error,
|
||||
() => ({
|
||||
returnProcessId: returnProcess.id,
|
||||
}),
|
||||
);
|
||||
this.canReturn.set(undefined);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -160,9 +160,13 @@ export class ReturnProcessComponent {
|
||||
|
||||
canReturnResults.push(Boolean(canReturnResponse?.result));
|
||||
} catch (error) {
|
||||
this.#logger.error('Failed to check canReturn for process', error, {
|
||||
processId: returnProcess.processId,
|
||||
});
|
||||
this.#logger.error(
|
||||
'Failed to check canReturn for process',
|
||||
error,
|
||||
() => ({
|
||||
processId: returnProcess.processId,
|
||||
}),
|
||||
);
|
||||
canReturnResults.push(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,6 +183,7 @@ export class ReturnSearchResultComponent implements AfterViewInit {
|
||||
const processId = this.processId();
|
||||
if (processId) {
|
||||
this.#filterService.commit();
|
||||
|
||||
this.returnSearchStore.search({
|
||||
processId,
|
||||
query: this.#filterService.query(),
|
||||
@@ -200,7 +201,7 @@ export class ReturnSearchResultComponent implements AfterViewInit {
|
||||
searchCb = ({ data }: CallbackResult<ListResponseArgs<ReceiptListItem>>) => {
|
||||
if (data) {
|
||||
if (data.result.length === 1) {
|
||||
this.navigate(['receipt', data.result[0].id]);
|
||||
this.navigate(['../receipt', data.result[0].id]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
|
||||
import { ActivatedRoute, RouterOutlet } from '@angular/router';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
effect,
|
||||
inject,
|
||||
untracked,
|
||||
} from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { ActivatedRoute, Router, RouterOutlet } from '@angular/router';
|
||||
import { injectActivatedProcessId } from '@isa/core/process';
|
||||
import { ReturnSearchStore } from '@isa/oms/data-access';
|
||||
import {
|
||||
FilterService,
|
||||
provideFilter,
|
||||
withQueryParamsSync,
|
||||
withQuerySettingsFactory,
|
||||
withQueryParamsSync,
|
||||
} from '@isa/shared/filter';
|
||||
|
||||
function querySettingsFactory() {
|
||||
@@ -36,67 +26,4 @@ function querySettingsFactory() {
|
||||
'"flex flex-col gap-5 isa-desktop:gap-6 items-center overflow-x-hidden"',
|
||||
},
|
||||
})
|
||||
export class ReturnSearchComponent {
|
||||
#route = inject(ActivatedRoute);
|
||||
#router = inject(Router);
|
||||
#filterService = inject(FilterService);
|
||||
#returnSearchStore = inject(ReturnSearchStore);
|
||||
#processId = injectActivatedProcessId();
|
||||
|
||||
#queryParams = toSignal(inject(ActivatedRoute).queryParams);
|
||||
|
||||
parseFilterParamsEffectFn = () =>
|
||||
effect(() => {
|
||||
const processId = this.#processId();
|
||||
untracked(() => {
|
||||
if (processId) {
|
||||
const params = this.#queryParams();
|
||||
const entity = this.#returnSearchStore.getEntity(processId);
|
||||
|
||||
// TODO: Caching der Entities einbauen - Aktuell kommt er sonst in den Filter Reset bei F5/Refresh Page
|
||||
if (!entity || !params) {
|
||||
this.#filterService.reset({ commit: true });
|
||||
return;
|
||||
}
|
||||
|
||||
this.#filterService.parseQueryParams(params, { commit: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
searchResultsNavigationEffectFn = () =>
|
||||
effect(() => {
|
||||
// TODO: Wenn er hier beim Prozesswechsel nicht sofort navigieren soll, dann muss auf ReturnSearchStatus.Success geprüft werden und nach der Navigation auf ReturnSearchStatus.Idle gesetzt werden
|
||||
const processId = this.#processId();
|
||||
if (processId) {
|
||||
const entity = this.#returnSearchStore.getEntity(processId);
|
||||
if (entity && entity.status !== 'error') {
|
||||
untracked(async () => {
|
||||
const items = entity?.items;
|
||||
|
||||
if (items) {
|
||||
if (items?.length === 1) {
|
||||
return await this._navigateTo([
|
||||
'receipts',
|
||||
items[0].id.toString(),
|
||||
]);
|
||||
}
|
||||
|
||||
if (items?.length >= 0) {
|
||||
return await this._navigateTo(['receipts']);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
private async _navigateTo(url: string[]) {
|
||||
return await this.#router.navigate(url, {
|
||||
queryParams: this.#filterService.queryParams(),
|
||||
relativeTo: this.#route,
|
||||
});
|
||||
}
|
||||
}
|
||||
export class ReturnSearchComponent {}
|
||||
|
||||
@@ -94,9 +94,9 @@ export class ReturnSummaryComponent {
|
||||
*/
|
||||
async returnItemsAndPrintRecipt() {
|
||||
if (this.returnItemsAndPrintReciptStatus() === 'pending') {
|
||||
this.#logger.warn('Return process already in progress', {
|
||||
this.#logger.warn('Return process already in progress', () => ({
|
||||
function: 'returnItemsAndPrintRecipt',
|
||||
});
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -108,18 +108,18 @@ export class ReturnSummaryComponent {
|
||||
this.returnProcesses(),
|
||||
);
|
||||
|
||||
this.#logger.info('Return receipts created', {
|
||||
this.#logger.info('Return receipts created', () => ({
|
||||
count: returnReceipts.length,
|
||||
});
|
||||
}));
|
||||
this.returnItemsAndPrintReciptStatus.set('success');
|
||||
|
||||
await this.#router.navigate(['../', 'review'], {
|
||||
relativeTo: this.#activatedRoute,
|
||||
});
|
||||
} catch (error) {
|
||||
this.#logger.error('Error completing return process', error, {
|
||||
this.#logger.error('Error completing return process', error, () => ({
|
||||
function: 'returnItemsAndPrintRecipt',
|
||||
});
|
||||
}));
|
||||
this.returnItemsAndPrintReciptStatus.set('error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,9 +130,9 @@ export class ReturnTaskListComponent {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.#logger.error('Error completing task', error, {
|
||||
this.#logger.error('Error completing task', error, () => ({
|
||||
function: 'completeTask',
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,9 +160,10 @@ export class ReturnTaskListComponent {
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.#logger.error('Error updating task', error, {
|
||||
this.#logger.error('Error updating task', error, () => ({
|
||||
taskId,
|
||||
function: 'updateTask',
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,258 @@
|
||||
# shared-filter
|
||||
# ISA Filter Library
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
A powerful and flexible filtering library for Angular applications that provides a complete solution for implementing filters, search functionality, and sorting capabilities.
|
||||
|
||||
## Overview
|
||||
|
||||
The ISA Filter Library is designed to help developers quickly implement advanced filtering capabilities in their Angular applications. It provides a set of reusable components, schemas, and services to handle various types of filters, user inputs, and sorting options.
|
||||
|
||||
## Features
|
||||
|
||||
- **Multiple Filter Types**: Support for text search, checkboxes, date ranges, and more
|
||||
- **Flexible Architecture**: Easily extendable to support custom filter types
|
||||
- **Modern Angular Patterns**: Built with Angular signals for reactive state management
|
||||
- **Schema-based Validation**: Type-safe filter models with zod schema validation
|
||||
- **Responsive Design**: Mobile-friendly filter UI components
|
||||
- **Declarative API**: Simple and intuitive API for configuring filters
|
||||
- **Sorting Capabilities**: Built-in support for sorting data by various criteria
|
||||
- **TypeScript Support**: Fully typed for excellent developer experience
|
||||
|
||||
## Installation
|
||||
|
||||
The library is part of the ISA Frontend monorepo and can be imported using:
|
||||
|
||||
```typescript
|
||||
import { ... } from '@isa/shared/filter';
|
||||
```
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Filter Service
|
||||
|
||||
The `FilterService` is the central component of the library, responsible for managing filter state and providing methods to update and query filter values.
|
||||
|
||||
```typescript
|
||||
// Example of using FilterService
|
||||
export class MyComponent {
|
||||
constructor(private filterService: FilterService) {}
|
||||
|
||||
applyFilters() {
|
||||
// Apply filters and get the query
|
||||
const query = this.filterService.getQuery();
|
||||
// Use the query to fetch filtered data
|
||||
}
|
||||
|
||||
resetFilters() {
|
||||
this.filterService.reset();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Filter Types
|
||||
|
||||
The library supports several filter input types:
|
||||
|
||||
- **Text Filters** (`InputType.Text`): For search queries and text-based filtering
|
||||
- **Checkbox Filters** (`InputType.Checkbox`): For multi-select options
|
||||
- **Date Range Filters** (`InputType.DateRange`): For time-based filtering
|
||||
|
||||
### Query Settings
|
||||
|
||||
Filter configurations are defined using the `QuerySettings` interface, which includes filter groups, input fields, and sort options.
|
||||
|
||||
```typescript
|
||||
// Example QuerySettings configuration
|
||||
const settings: QuerySettings = {
|
||||
filter: [
|
||||
{
|
||||
group: 'products',
|
||||
label: 'Product Filters',
|
||||
input: [
|
||||
{
|
||||
key: 'category',
|
||||
label: 'Category',
|
||||
type: InputType.Checkbox,
|
||||
options: {
|
||||
values: [
|
||||
{ label: 'Electronics', value: 'electronics' },
|
||||
{ label: 'Clothing', value: 'clothing' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
input: [],
|
||||
orderBy: [
|
||||
{ by: 'price', label: 'Price (Low to High)', desc: false },
|
||||
{ by: 'price', label: 'Price (High to Low)', desc: true },
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
### Filter Menu
|
||||
|
||||
The `FilterMenuComponent` provides a UI for displaying and interacting with filters:
|
||||
|
||||
```html
|
||||
<filter-filter-menu (applied)="onApplyFilters()" (reseted)="onResetFilters()">
|
||||
</filter-filter-menu>
|
||||
```
|
||||
|
||||
### Order By Toolbar
|
||||
|
||||
The `OrderByToolbarComponent` allows users to sort data based on different criteria:
|
||||
|
||||
```html
|
||||
<filter-order-by-toolbar [commitOnToggle]="true" (toggled)="onSortChanged()">
|
||||
</filter-order-by-toolbar>
|
||||
```
|
||||
|
||||
### Input Components
|
||||
|
||||
The library includes specialized components for different input types:
|
||||
|
||||
- `CheckboxInputComponent`: For multi-select options
|
||||
- `DatepickerRangeInputComponent`: For date range selection
|
||||
- `SearchBarInputComponent`: For text-based search
|
||||
|
||||
### Input Renderer
|
||||
|
||||
The `InputRendererComponent` dynamically renders the appropriate input component based on the filter type:
|
||||
|
||||
```html
|
||||
<filter-input-renderer [filterInput]="myFilterInput"></filter-input-renderer>
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Filter Implementation
|
||||
|
||||
```typescript
|
||||
// Import filter library
|
||||
import { QuerySettings, InputType, provideFilter } from '@isa/shared/filter';
|
||||
|
||||
// Define filter settings
|
||||
const settings: QuerySettings = {
|
||||
filter: [
|
||||
{
|
||||
group: 'filter',
|
||||
label: 'Filters',
|
||||
input: [
|
||||
{
|
||||
key: 'status',
|
||||
label: 'Status',
|
||||
type: InputType.Checkbox,
|
||||
options: {
|
||||
values: [
|
||||
{ label: 'Active', value: 'active' },
|
||||
{ label: 'Inactive', value: 'inactive' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
input: [],
|
||||
orderBy: [
|
||||
{ by: 'name', label: 'Name (A-Z)', desc: false },
|
||||
{ by: 'name', label: 'Name (Z-A)', desc: true },
|
||||
],
|
||||
};
|
||||
|
||||
// Provide filter in component/module
|
||||
@Component({
|
||||
// ...
|
||||
providers: [provideFilter(settings)],
|
||||
})
|
||||
export class MyFilterComponent {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Implementing Filter Logic
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
// ...
|
||||
})
|
||||
export class ProductListComponent {
|
||||
constructor(private filterService: FilterService) {}
|
||||
|
||||
products = computed(() => {
|
||||
const query = this.filterService.getQuery();
|
||||
return this.applyFilters(this.allProducts, query);
|
||||
});
|
||||
|
||||
private applyFilters(products: Product[], query: Query): Product[] {
|
||||
// Implement filtering logic based on query
|
||||
return filteredProducts;
|
||||
}
|
||||
|
||||
onApplyFilters() {
|
||||
// Trigger data refresh or update
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Schema Validation
|
||||
|
||||
The library uses Zod schemas to validate filter configurations and user inputs:
|
||||
|
||||
```typescript
|
||||
// Example of a filter schema
|
||||
export const CheckboxFilterInputSchema = CheckboxFilterInputBaseSchema.extend({
|
||||
options: z
|
||||
.object({
|
||||
values: z.array(CheckboxFilterInputOptionSchema).optional(),
|
||||
max: z.number().optional(),
|
||||
})
|
||||
.optional(),
|
||||
selected: z.array(z.string()).default([]),
|
||||
type: z.literal(InputType.Checkbox),
|
||||
});
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
The Filter library is organized into several key modules:
|
||||
|
||||
### Core Module
|
||||
|
||||
- **Schemas**: Type definitions and validation using Zod
|
||||
- **Mappings**: Functions to transform between API and UI models
|
||||
- **Service**: The `FilterService` for state management
|
||||
- **Tokens**: Injection tokens for DI configuration
|
||||
|
||||
### Inputs Module
|
||||
|
||||
- **CheckboxInput**: Multi-select options component
|
||||
- **DatepickerRangeInput**: Date range selection component
|
||||
- **SearchBarInput**: Text-based search component
|
||||
- **InputRenderer**: Dynamic component renderer
|
||||
|
||||
### Menus Module
|
||||
|
||||
- **FilterMenu**: UI for displaying and interacting with filters
|
||||
- **InputMenu**: Specialized component for input configuration
|
||||
|
||||
### Actions Module
|
||||
|
||||
Components for filter operations (apply, reset, etc.)
|
||||
|
||||
### Order By Module
|
||||
|
||||
Components for sorting data based on different criteria
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Group Related Filters**: Use filter groups to organize related filters together
|
||||
2. **Provide Clear Labels**: Use descriptive labels for filters and options
|
||||
3. **Use Appropriate Filter Types**: Choose the right filter type for the data being filtered
|
||||
4. **Handle Filter State**: Properly manage filter state, especially for URL synchronization
|
||||
5. **Combine with NgRx**: For complex applications, consider using the library with NgRx for state management
|
||||
|
||||
## Running unit tests
|
||||
|
||||
|
||||
@@ -77,14 +77,7 @@ export class FilterActionsComponent {
|
||||
* Otherwise, all filter inputs are committed.
|
||||
*/
|
||||
onApply() {
|
||||
const inputKey = this.inputKey();
|
||||
|
||||
if (!inputKey) {
|
||||
this.filterService.commit();
|
||||
} else {
|
||||
this.filterService.commitInput(inputKey);
|
||||
}
|
||||
|
||||
this.filterService.commit();
|
||||
this.applied.emit();
|
||||
}
|
||||
|
||||
@@ -99,11 +92,11 @@ export class FilterActionsComponent {
|
||||
const inputKey = this.inputKey();
|
||||
|
||||
if (!inputKey) {
|
||||
this.filterInputs().forEach((input) => {
|
||||
this.filterService.resetInput(input.key);
|
||||
});
|
||||
this.filterService.resetInput(
|
||||
this.filterInputs().map((input) => input.key),
|
||||
);
|
||||
} else {
|
||||
this.filterService.resetInput(inputKey);
|
||||
this.filterService.resetInput([inputKey]);
|
||||
}
|
||||
|
||||
this.filterService.commit();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { computed, inject, Injectable, signal } from '@angular/core';
|
||||
import { computed, effect, inject, Injectable, signal } from '@angular/core';
|
||||
import { InputType } from '../types';
|
||||
import { getState, patchState, signalState } from '@ngrx/signals';
|
||||
import { filterMapping } from './mappings';
|
||||
@@ -11,9 +11,15 @@ import {
|
||||
QuerySchema,
|
||||
} from './schemas';
|
||||
import { FILTER_ON_COMMIT, FILTER_ON_INIT, QUERY_SETTINGS } from './tokens';
|
||||
import { logger } from '@isa/core/logging';
|
||||
|
||||
@Injectable()
|
||||
export class FilterService {
|
||||
#logger = logger(() => ({
|
||||
library: 'shared/filter',
|
||||
class: 'FilterService',
|
||||
}));
|
||||
|
||||
#onInit = inject(FILTER_ON_INIT, { optional: true })?.map((fn) => fn(this));
|
||||
#onCommit = inject(FILTER_ON_COMMIT, { optional: true })?.map((fn) =>
|
||||
fn(this),
|
||||
@@ -23,7 +29,8 @@ export class FilterService {
|
||||
|
||||
private readonly defaultState = filterMapping(this.settings);
|
||||
|
||||
#commitdState = signal(structuredClone(this.defaultState));
|
||||
// Use a more lightweight approach for creating the initial state
|
||||
#commitdState = signal({ ...this.defaultState });
|
||||
|
||||
#state = signalState(this.#commitdState());
|
||||
|
||||
@@ -39,11 +46,19 @@ export class FilterService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ordering field and direction for the filter.
|
||||
*
|
||||
* @param by - The field to order by
|
||||
* @param dir - The direction to order by (asc or desc)
|
||||
* @param options - Optional parameters
|
||||
* @param options.commit - If true, commits the changes immediately
|
||||
*/
|
||||
setOrderBy(
|
||||
by: string,
|
||||
dir: OrderByDirection | undefined,
|
||||
options?: { commit: boolean },
|
||||
) {
|
||||
): void {
|
||||
const orderByList = this.#state.orderBy().map((o) => {
|
||||
if (o.by === by && o.dir === dir) {
|
||||
return { ...o, selected: true };
|
||||
@@ -58,6 +73,14 @@ export class FilterService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text value for an input with the specified key.
|
||||
*
|
||||
* @param key - The key of the input to update
|
||||
* @param value - The new text value to set
|
||||
* @param options - Optional parameters
|
||||
* @param options.commit - If true, commits the changes immediately
|
||||
*/
|
||||
setInputTextValue(
|
||||
key: string,
|
||||
value: string | undefined,
|
||||
@@ -72,8 +95,7 @@ export class FilterService {
|
||||
return { ...input, value };
|
||||
}
|
||||
|
||||
console.warn(`Input type not supported: ${input.type}`);
|
||||
|
||||
this.logUnsupportedInputType(input, 'setInputTextValue');
|
||||
return input;
|
||||
});
|
||||
|
||||
@@ -84,6 +106,14 @@ export class FilterService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selected values for a checkbox input with the specified key.
|
||||
*
|
||||
* @param key - The key of the checkbox input to update
|
||||
* @param selected - Array of selected values
|
||||
* @param options - Optional parameters
|
||||
* @param options.commit - If true, commits the changes immediately
|
||||
*/
|
||||
setInputCheckboxValue(
|
||||
key: string,
|
||||
selected: string[],
|
||||
@@ -98,8 +128,7 @@ export class FilterService {
|
||||
return { ...input, selected };
|
||||
}
|
||||
|
||||
console.warn(`Input type not supported: ${input.type}`);
|
||||
|
||||
this.logUnsupportedInputType(input, 'setInputCheckboxValue');
|
||||
return input;
|
||||
});
|
||||
|
||||
@@ -110,6 +139,15 @@ export class FilterService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the date range values for an input with the specified key.
|
||||
*
|
||||
* @param key - The key of the date range input to update
|
||||
* @param start - The start date as a string
|
||||
* @param stop - The end date as a string
|
||||
* @param options - Optional parameters
|
||||
* @param options.commit - If true, commits the changes immediately
|
||||
*/
|
||||
setInputDateRangeValue(
|
||||
key: string,
|
||||
start?: string,
|
||||
@@ -125,8 +163,7 @@ export class FilterService {
|
||||
return { ...input, start, stop };
|
||||
}
|
||||
|
||||
console.warn(`Input type not supported: ${input.type}`);
|
||||
|
||||
this.logUnsupportedInputType(input, 'setInputDateRangeValue');
|
||||
return input;
|
||||
});
|
||||
|
||||
@@ -137,6 +174,20 @@ export class FilterService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to consistently log unsupported input type warnings
|
||||
* @private
|
||||
* @param input - The input that has an unsupported type
|
||||
* @param method - The method name where the warning occurred
|
||||
*/
|
||||
private logUnsupportedInputType(input: FilterInput, method: string): void {
|
||||
this.#logger.warn(`Input type not supported`, () => ({
|
||||
inputType: input.type,
|
||||
inputKey: input.key,
|
||||
method,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether the current state is the default state.
|
||||
* This computed property checks if the current state is equal to the default state.
|
||||
@@ -146,7 +197,13 @@ export class FilterService {
|
||||
return isEqual(currentState.inputs, this.defaultState.inputs);
|
||||
});
|
||||
|
||||
isDefaultFilterInput(filterInput: FilterInput) {
|
||||
/**
|
||||
* Checks if a specific filter input is in its default state.
|
||||
*
|
||||
* @param filterInput - The filter input to check
|
||||
* @returns True if the input is in its default state, false otherwise
|
||||
*/
|
||||
isDefaultFilterInput(filterInput: FilterInput): boolean {
|
||||
const currentInputState = this.#state
|
||||
.inputs()
|
||||
.find((i) => i.key === filterInput.key);
|
||||
@@ -160,7 +217,7 @@ export class FilterService {
|
||||
/**
|
||||
* Indicates whether the current state is empty.
|
||||
*/
|
||||
isEmptyFilter = computed(() => {
|
||||
isEmpty = computed(() => {
|
||||
const currentState = getState(this.#state);
|
||||
return currentState.inputs.every((input) => {
|
||||
if (input.type === InputType.Text) {
|
||||
@@ -175,31 +232,50 @@ export class FilterService {
|
||||
return !input.start && !input.stop;
|
||||
}
|
||||
|
||||
console.warn(`Input type not supported`);
|
||||
this.#logger.warn(`Input type not supported`, () => ({
|
||||
input,
|
||||
method: 'isEmptyFilter',
|
||||
}));
|
||||
|
||||
return true;
|
||||
});
|
||||
});
|
||||
|
||||
isEmptyFilterInput(filterInput: FilterInput) {
|
||||
/**
|
||||
* Checks if a specific filter input has an empty value.
|
||||
* For text inputs, checks if the value is falsy.
|
||||
* For checkbox inputs, checks if the selected array is empty.
|
||||
* For date range inputs, checks if both start and stop are falsy.
|
||||
*
|
||||
* @param filterInput - The filter input to check
|
||||
* @returns True if the filter input is empty, false otherwise
|
||||
*/
|
||||
isEmptyFilterInput(filterInput: FilterInput): boolean {
|
||||
const currentInputState = this.#state
|
||||
.inputs()
|
||||
.find((i) => i.key === filterInput.key);
|
||||
|
||||
if (currentInputState?.type === InputType.Text) {
|
||||
if (!currentInputState) {
|
||||
this.#logger.warn(`Input not found`, () => ({
|
||||
inputKey: filterInput.key,
|
||||
method: 'isEmptyFilterInput',
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (currentInputState.type === InputType.Text) {
|
||||
return !currentInputState.value;
|
||||
}
|
||||
|
||||
if (currentInputState?.type === InputType.Checkbox) {
|
||||
if (currentInputState.type === InputType.Checkbox) {
|
||||
return !currentInputState.selected?.length;
|
||||
}
|
||||
|
||||
if (currentInputState?.type === InputType.DateRange) {
|
||||
if (currentInputState.type === InputType.DateRange) {
|
||||
return !currentInputState.start && !currentInputState.stop;
|
||||
}
|
||||
|
||||
console.warn(`Input type not supported`);
|
||||
|
||||
this.logUnsupportedInputType(currentInputState, 'isEmptyFilterInput');
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -207,36 +283,86 @@ export class FilterService {
|
||||
* Reverts the current state to the last committed state.
|
||||
* This method restores the state by applying the previously saved committed state.
|
||||
*/
|
||||
rollback() {
|
||||
rollback(): void {
|
||||
const currentState = getState(this.#state);
|
||||
const committedState = this.#commitdState();
|
||||
if (isEqual(currentState, committedState)) {
|
||||
this.#logger.debug('No changes to rollback', () => ({
|
||||
changes: false,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
this.#logger.debug('Rolling back filter state', () => ({
|
||||
changes: true,
|
||||
currentState,
|
||||
committedState,
|
||||
}));
|
||||
patchState(this.#state, this.#commitdState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rolls back the input state for a specific key to its last committed state.
|
||||
* Rolls back the input state for specific keys to their last committed state.
|
||||
* If the input with the given key exists in the committed state, it replaces
|
||||
* the current input with the committed one. Otherwise, the input remains unchanged.
|
||||
*
|
||||
* @param key - The key of the input to roll back.
|
||||
* @param keys - The keys of the inputs to roll back
|
||||
*/
|
||||
rollbackInput(key: string) {
|
||||
rollbackInput(keys: string[]): void {
|
||||
// Find committed inputs for the specified keys
|
||||
const committedInputs = this.#commitdState().inputs;
|
||||
|
||||
// First check if there's anything to rollback
|
||||
const hasChangesToRollback = keys.some((key) => {
|
||||
const currentInput = this.#state.inputs().find((i) => i.key === key);
|
||||
const committedInput = committedInputs.find((i) => i.key === key);
|
||||
return committedInput && !isEqual(currentInput, committedInput);
|
||||
});
|
||||
|
||||
// Only proceed if there are changes to rollback
|
||||
if (!hasChangesToRollback) {
|
||||
this.#logger.debug('No changes to rollback for specified inputs', () => ({
|
||||
inputKeys: keys,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply rollback for changed inputs
|
||||
const inputs = this.#state.inputs().map((input) => {
|
||||
if (input.key !== key) {
|
||||
if (!keys.includes(input.key)) {
|
||||
return input;
|
||||
}
|
||||
|
||||
return this.#commitdState().inputs.find((i) => i.key === key) || input;
|
||||
// Get the committed version of this input
|
||||
const committedInput = committedInputs.find((i) => i.key === input.key);
|
||||
return committedInput || input;
|
||||
});
|
||||
|
||||
this.#logger.debug('Rolling back specified inputs', () => ({
|
||||
inputKeys: keys,
|
||||
}));
|
||||
|
||||
patchState(this.#state, { inputs });
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits the current state by capturing a snapshot of the internal state.
|
||||
* This method updates the private `#commitdState` property with the result
|
||||
* of the `getState` function applied to the current `#state`.
|
||||
* This method updates the private `#commitdState` property with the current state
|
||||
* and triggers any registered commit callbacks.
|
||||
*/
|
||||
commit() {
|
||||
this.#commitdState.set(getState(this.#state));
|
||||
commit(): void {
|
||||
const currentState = getState(this.#state);
|
||||
const committedState = this.#commitdState();
|
||||
|
||||
if (!isEqual(currentState, committedState)) {
|
||||
this.#commitdState.set(currentState);
|
||||
this.#logger.debug('Filter state committed', () => ({
|
||||
changes: true,
|
||||
}));
|
||||
} else {
|
||||
this.#logger.debug('No changes to commit', () => ({
|
||||
changes: false,
|
||||
}));
|
||||
}
|
||||
|
||||
this.#onCommit?.forEach((commitFn) => {
|
||||
commitFn();
|
||||
@@ -244,50 +370,39 @@ export class FilterService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits the input associated with the specified key to the committed state.
|
||||
* Clears all filter values without resetting to default values.
|
||||
* This sets text inputs to undefined, checkbox selections to empty arrays,
|
||||
* and date ranges to undefined for both start and stop.
|
||||
*
|
||||
* This method searches for an input in the current state that matches the given key.
|
||||
* If found, it clones the committed state, updates the corresponding input in the
|
||||
* cloned state with the found input, and then replaces the committed state with the
|
||||
* updated clone.
|
||||
*
|
||||
* @param key - The unique identifier of the input to commit.
|
||||
* @param options - Optional parameters
|
||||
* @param options.commit - If true, commits the changes immediately after clearing
|
||||
*/
|
||||
commitInput(key: string) {
|
||||
const inputToCommit = this.#state.inputs().find((i) => i.key === key);
|
||||
clear(options?: { commit: boolean }): void {
|
||||
// First check if there's anything to clear
|
||||
const hasDataToClear = this.#state.inputs().some((input) => {
|
||||
if (input.type === InputType.Text) {
|
||||
return !!input.value;
|
||||
}
|
||||
|
||||
if (!inputToCommit) {
|
||||
console.warn(`No input found with key: ${key}`);
|
||||
if (input.type === InputType.Checkbox) {
|
||||
return input.selected?.length > 0;
|
||||
}
|
||||
|
||||
if (input.type === InputType.DateRange) {
|
||||
return !!input.start || !!input.stop;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
// Only proceed if there's data to clear
|
||||
if (!hasDataToClear) {
|
||||
this.#logger.debug('No filter data to clear', () => ({
|
||||
changes: false,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
const inputIndex = this.#commitdState().inputs.findIndex(
|
||||
(i) => i.key === key,
|
||||
);
|
||||
|
||||
if (inputIndex === -1) {
|
||||
console.warn(`No committed input found with key: ${key}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.#commitdState.set({
|
||||
...this.#commitdState(),
|
||||
inputs: this.#commitdState().inputs.map((input, index) =>
|
||||
index === inputIndex ? inputToCommit : input,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
commitOrderBy() {
|
||||
const orderBy = this.#state.orderBy();
|
||||
|
||||
this.#commitdState.set({
|
||||
...this.#commitdState(),
|
||||
orderBy,
|
||||
});
|
||||
}
|
||||
|
||||
clear(options?: { commit: boolean }) {
|
||||
const inputs = this.#state.inputs().map((input) => {
|
||||
if (input.type === InputType.Text) {
|
||||
return { ...input, value: undefined };
|
||||
@@ -304,6 +419,10 @@ export class FilterService {
|
||||
return input;
|
||||
});
|
||||
|
||||
this.#logger.debug('Clearing filter state', () => ({
|
||||
changes: true,
|
||||
}));
|
||||
|
||||
patchState(this.#state, { inputs });
|
||||
if (options?.commit) {
|
||||
this.commit();
|
||||
@@ -316,49 +435,71 @@ export class FilterService {
|
||||
* @param options - Optional parameters for the reset operation.
|
||||
* @param options.commit - If `true`, the changes will be committed after resetting the state.
|
||||
*/
|
||||
reset(options?: { commit: boolean }) {
|
||||
patchState(this.#state, structuredClone(this.defaultState));
|
||||
reset(options?: { commit: boolean }): void {
|
||||
// Use a more lightweight approach than structuredClone
|
||||
patchState(this.#state, { ...this.defaultState });
|
||||
if (options?.commit) {
|
||||
this.commit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the input with the specified key to its default state.
|
||||
* Resets one or more inputs to their default state.
|
||||
*
|
||||
* @param key - The key of the input to reset.
|
||||
* @param keys - The key or array of keys of the inputs to reset.
|
||||
* @param options - Optional parameters for the reset operation.
|
||||
* @param options.commit - If `true`, commits the changes after resetting the input.
|
||||
* @param options.commit - If `true`, commits the changes after resetting the input(s).
|
||||
*
|
||||
* @remarks
|
||||
* - If no input is found with the specified key, a warning is logged, and the method exits.
|
||||
* - The method updates the state by replacing the input with its default configuration.
|
||||
* - If no input is found with the specified key, a warning is logged for that key.
|
||||
* - The method updates the state by replacing the input(s) with their default configuration.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Reset a single input
|
||||
* filterService.resetInput('exampleKey', { commit: true });
|
||||
*
|
||||
* // Reset multiple inputs
|
||||
* filterService.resetInput(['key1', 'key2'], { commit: true });
|
||||
* ```
|
||||
*/
|
||||
resetInput(key: string, options?: { commit: boolean }) {
|
||||
const defaultFilter = structuredClone(this.defaultState);
|
||||
const inputToReset = defaultFilter.inputs.find((i) => i.key === key);
|
||||
resetInput(keys: string[], options?: { commit: boolean }): void {
|
||||
// Use a more lightweight approach than structuredClone
|
||||
const defaultFilter = { ...this.defaultState };
|
||||
|
||||
if (!inputToReset) {
|
||||
console.warn(`No input found with key: ${key}`);
|
||||
// Find all default inputs that match the provided keys
|
||||
const inputsToReset = keys
|
||||
.map((key) => {
|
||||
const inputToReset = defaultFilter.inputs.find((i) => i.key === key);
|
||||
|
||||
if (!inputToReset) {
|
||||
this.#logger.warn(`No input found with key`, () => ({
|
||||
key,
|
||||
method: 'resetInput',
|
||||
}));
|
||||
}
|
||||
|
||||
return { key, defaultInput: inputToReset };
|
||||
})
|
||||
.filter((item) => item.defaultInput !== undefined);
|
||||
|
||||
if (inputsToReset.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inputIndex = this.#state.inputs().findIndex((i) => i.key === key);
|
||||
// Create a set of keys for faster lookups
|
||||
const keysToReset = new Set(inputsToReset.map((item) => item.key));
|
||||
|
||||
if (inputIndex === -1) {
|
||||
console.warn(`No input found with key: ${key}`);
|
||||
return;
|
||||
}
|
||||
const inputs = this.#state.inputs().map((input) => {
|
||||
if (!keysToReset.has(input.key)) {
|
||||
return input;
|
||||
}
|
||||
|
||||
const resetItem = inputsToReset.find((item) => item.key === input.key);
|
||||
return resetItem?.defaultInput ?? input;
|
||||
});
|
||||
|
||||
patchState(this.#state, {
|
||||
inputs: this.#state
|
||||
.inputs()
|
||||
.map((input, index) => (index === inputIndex ? inputToReset : input)),
|
||||
inputs,
|
||||
});
|
||||
|
||||
if (options?.commit) {
|
||||
@@ -366,9 +507,15 @@ export class FilterService {
|
||||
}
|
||||
}
|
||||
|
||||
resetOrderBy(options?: { commit: boolean }) {
|
||||
const defaultOrderBy = structuredClone(this.defaultState.orderBy);
|
||||
patchState(this.#state, { orderBy: defaultOrderBy });
|
||||
/**
|
||||
* Resets the orderBy state to its default values.
|
||||
*
|
||||
* @param options - Optional parameters for the reset operation.
|
||||
* @param options.commit - If `true`, the changes will be committed after resetting.
|
||||
*/
|
||||
resetOrderBy(options?: { commit: boolean }): void {
|
||||
// Use a more lightweight approach than structuredClone
|
||||
patchState(this.#state, { orderBy: [...this.defaultState.orderBy] });
|
||||
|
||||
if (options?.commit) {
|
||||
this.commit();
|
||||
@@ -384,11 +531,15 @@ export class FilterService {
|
||||
case InputType.Text:
|
||||
if (input.value) {
|
||||
result[input.key] = input.value;
|
||||
} else {
|
||||
result[input.key] = '';
|
||||
}
|
||||
break;
|
||||
case InputType.Checkbox:
|
||||
if (input.selected) {
|
||||
result[input.key] = input.selected.join(';');
|
||||
} else {
|
||||
result[input.key] = '';
|
||||
}
|
||||
break;
|
||||
case InputType.DateRange:
|
||||
@@ -398,6 +549,8 @@ export class FilterService {
|
||||
result[input.key] = `"${input.start}"-`;
|
||||
} else if (input.stop) {
|
||||
result[input.key] = `-"${input.stop}"`;
|
||||
} else {
|
||||
result[input.key] = '';
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -406,12 +559,21 @@ export class FilterService {
|
||||
const orderBy = commited.orderBy.find((o) => o.selected);
|
||||
|
||||
if (orderBy) {
|
||||
result['orderBy'] = `${orderBy.by}:${orderBy.dir}`;
|
||||
result['order_by'] = `${orderBy.by}:${orderBy.dir}`;
|
||||
} else {
|
||||
// Ensure 'orderBy' is not included if no order is selected
|
||||
result['order_by'] = '';
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
/**
|
||||
* Checks if the current query parameters match the provided parameters.
|
||||
*
|
||||
* @param params - The parameters to compare with the current query parameters
|
||||
* @returns True if the parameters match, false otherwise
|
||||
*/
|
||||
isQueryParamsEqual(params: Record<string, string>): boolean {
|
||||
const currentParams = this.queryParams();
|
||||
return this.queryParamKeys().every(
|
||||
@@ -424,7 +586,7 @@ export class FilterService {
|
||||
const orderBy = this.orderBy().find((o) => o.dir);
|
||||
|
||||
if (orderBy) {
|
||||
keys.push('orderBy');
|
||||
keys.push('order_by');
|
||||
}
|
||||
|
||||
return keys;
|
||||
@@ -483,29 +645,46 @@ export class FilterService {
|
||||
});
|
||||
});
|
||||
|
||||
parseQueryParams(
|
||||
/**
|
||||
* Parses query parameters into filter state.
|
||||
*
|
||||
* @param params - Record of query parameters to parse
|
||||
* @param options - Optional parameters
|
||||
* @param options.commit - If true, commits the changes immediately after parsing
|
||||
*/ parseQueryParams(
|
||||
params: Record<string, string>,
|
||||
options?: { commit: boolean },
|
||||
): void {
|
||||
this.reset();
|
||||
|
||||
for (const key in params) {
|
||||
if (key === 'orderBy') {
|
||||
if (key === 'order_by') {
|
||||
const [by, dir] = params[key].split(':');
|
||||
const orderBy = this.orderBy().some(
|
||||
(o) => o.by === by && o.dir === dir,
|
||||
);
|
||||
|
||||
if (orderBy) {
|
||||
console.warn(`OrderBy already exists: ${by}:${dir}`);
|
||||
this.#logger.warn(`OrderBy already exists`, () => ({
|
||||
by,
|
||||
dir,
|
||||
method: 'parseQueryParams',
|
||||
}));
|
||||
this.setOrderBy(by, OrderByDirectionSchema.parse(dir));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const inputType = this.inputs().find((i) => i.key === key)?.type;
|
||||
const input = this.inputs().find((i) => i.key === key);
|
||||
if (!input) {
|
||||
this.#logger.warn(`Input not found for key`, () => ({
|
||||
key,
|
||||
method: 'parseQueryParams',
|
||||
}));
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (inputType) {
|
||||
switch (input.type) {
|
||||
case InputType.Text:
|
||||
this.setInputTextValue(key, params[key]);
|
||||
break;
|
||||
@@ -521,7 +700,7 @@ export class FilterService {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.warn(`Input type not supported: ${inputType}`);
|
||||
this.logUnsupportedInputType(input, 'parseQueryParams');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { DestroyRef, EnvironmentProviders, inject, Provider } from '@angular/core';
|
||||
import {
|
||||
DestroyRef,
|
||||
EnvironmentProviders,
|
||||
inject,
|
||||
Provider,
|
||||
} from '@angular/core';
|
||||
import { QuerySettings } from '../types';
|
||||
import { FilterService, QUERY_SETTINGS } from './filter.service';
|
||||
import { NavigationEnd, NavigationExtras, Router } from '@angular/router';
|
||||
@@ -35,7 +40,9 @@ export function withQuerySettings(querySettings: QuerySettings): FilterFeature {
|
||||
]);
|
||||
}
|
||||
|
||||
export function withQuerySettingsFactory(querySettingsFactory: () => QuerySettings): FilterFeature {
|
||||
export function withQuerySettingsFactory(
|
||||
querySettingsFactory: () => QuerySettings,
|
||||
): FilterFeature {
|
||||
return filterFeature(ProvideFilterKind.QuerySettings, [
|
||||
{ provide: QUERY_SETTINGS, useFactory: querySettingsFactory },
|
||||
]);
|
||||
@@ -44,7 +51,10 @@ export function withQuerySettingsFactory(querySettingsFactory: () => QuerySettin
|
||||
export function withQueryParamsSync({
|
||||
replaceUrl = true,
|
||||
queryParamsHandling = 'merge',
|
||||
}: Pick<NavigationExtras, 'replaceUrl' | 'queryParamsHandling'> = {}): FilterFeature {
|
||||
}: Pick<
|
||||
NavigationExtras,
|
||||
'replaceUrl' | 'queryParamsHandling'
|
||||
> = {}): FilterFeature {
|
||||
function onCommitFactory() {
|
||||
const router = inject(Router);
|
||||
return (filterService: FilterService) => () => {
|
||||
|
||||
@@ -89,7 +89,9 @@ export class FilterMenuButtonComponent {
|
||||
*/
|
||||
this.closed.subscribe(() => {
|
||||
if (this.rollbackOnClose()) {
|
||||
this.#filter.rollback();
|
||||
setTimeout(() => {
|
||||
this.#filter.rollback();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
(click)="filter.clear()"
|
||||
type="button"
|
||||
>
|
||||
<span class="isa-text-body-2-bold" [class.active]="!filter.isEmptyFilter()">
|
||||
<span class="isa-text-body-2-bold" [class.active]="!filter.isEmpty()">
|
||||
Alle abwählen
|
||||
</span>
|
||||
</button>
|
||||
@@ -25,7 +25,11 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
<ng-icon class="text-isa-neutral-900" name="isaActionChevronRight" size="1.5rem"></ng-icon>
|
||||
<ng-icon
|
||||
class="text-isa-neutral-900"
|
||||
name="isaActionChevronRight"
|
||||
size="1.5rem"
|
||||
></ng-icon>
|
||||
</button>
|
||||
}
|
||||
} @else {
|
||||
@@ -39,7 +43,10 @@
|
||||
<span> {{ input!.label }} </span>
|
||||
</button>
|
||||
|
||||
<filter-input-renderer class="overflow-scroll" [filterInput]="input!"></filter-input-renderer>
|
||||
<filter-input-renderer
|
||||
class="overflow-scroll"
|
||||
[filterInput]="input!"
|
||||
></filter-input-renderer>
|
||||
}
|
||||
|
||||
<filter-actions
|
||||
|
||||
447
package-lock.json
generated
447
package-lock.json
generated
@@ -1331,7 +1331,6 @@
|
||||
"version": "19.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-19.1.7.tgz",
|
||||
"integrity": "sha512-Uu/TxfIcE1lStlCLmOPbghG1Y5o83odES89sr7bYNJz2mcG7TEonatf6GTOMzbJNil9FBJt6qnJkDkSjn4nUKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "7.26.0",
|
||||
@@ -1360,7 +1359,6 @@
|
||||
"version": "7.26.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz",
|
||||
"integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
@@ -1391,14 +1389,12 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@angular/compiler-cli/node_modules/@babel/core/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
@@ -1667,7 +1663,6 @@
|
||||
"version": "7.26.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz",
|
||||
"integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
@@ -1698,14 +1693,12 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
|
||||
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@babel/core/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
@@ -12492,6 +12485,306 @@
|
||||
"url": "https://opencollective.com/unts"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz",
|
||||
"integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz",
|
||||
"integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz",
|
||||
"integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz",
|
||||
"integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz",
|
||||
"integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz",
|
||||
"integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz",
|
||||
"integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz",
|
||||
"integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz",
|
||||
"integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz",
|
||||
"integrity": "sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz",
|
||||
"integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz",
|
||||
"integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz",
|
||||
"integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz",
|
||||
"integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz",
|
||||
"integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz",
|
||||
"integrity": "sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.0.tgz",
|
||||
"integrity": "sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz",
|
||||
"integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz",
|
||||
"integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz",
|
||||
"integrity": "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@rspack/binding": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.3.2.tgz",
|
||||
@@ -17658,7 +17951,6 @@
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
@@ -18143,7 +18435,6 @@
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
|
||||
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
@@ -19470,7 +19761,7 @@
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"iconv-lite": "^0.6.2"
|
||||
@@ -22025,7 +22316,7 @@
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
@@ -29494,6 +29785,17 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
||||
"integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
@@ -29533,7 +29835,6 @@
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
@@ -29588,7 +29889,6 @@
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
|
||||
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/regenerate": {
|
||||
@@ -30162,6 +30462,47 @@
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.41.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.0.tgz",
|
||||
"integrity": "sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.7"
|
||||
},
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.41.0",
|
||||
"@rollup/rollup-android-arm64": "4.41.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.41.0",
|
||||
"@rollup/rollup-darwin-x64": "4.41.0",
|
||||
"@rollup/rollup-freebsd-arm64": "4.41.0",
|
||||
"@rollup/rollup-freebsd-x64": "4.41.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.41.0",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.41.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.41.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.41.0",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.41.0",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.41.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.41.0",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.41.0",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.41.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.41.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.41.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.41.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.41.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.41.0",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/rrweb-cssom": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
|
||||
@@ -30266,7 +30607,7 @@
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sass": {
|
||||
@@ -30879,7 +31220,6 @@
|
||||
"version": "7.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz",
|
||||
"integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==",
|
||||
"devOptional": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
@@ -33496,7 +33836,6 @@
|
||||
"version": "5.6.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
|
||||
"integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@@ -33887,6 +34226,82 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.3.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
|
||||
"integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.4.4",
|
||||
"picomatch": "^4.0.2",
|
||||
"postcss": "^8.5.3",
|
||||
"rollup": "^4.34.9",
|
||||
"tinyglobby": "^0.2.13"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.0.0 || ^20.0.0 || >=22.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/vitejs/vite?sponsor=1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
|
||||
"jiti": ">=1.21.0",
|
||||
"less": "*",
|
||||
"lightningcss": "^1.21.0",
|
||||
"sass": "*",
|
||||
"sass-embedded": "*",
|
||||
"stylus": "*",
|
||||
"sugarss": "*",
|
||||
"terser": "^5.16.0",
|
||||
"tsx": "^4.8.1",
|
||||
"yaml": "^2.4.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
},
|
||||
"jiti": {
|
||||
"optional": true
|
||||
},
|
||||
"less": {
|
||||
"optional": true
|
||||
},
|
||||
"lightningcss": {
|
||||
"optional": true
|
||||
},
|
||||
"sass": {
|
||||
"optional": true
|
||||
},
|
||||
"sass-embedded": {
|
||||
"optional": true
|
||||
},
|
||||
"stylus": {
|
||||
"optional": true
|
||||
},
|
||||
"sugarss": {
|
||||
"optional": true
|
||||
},
|
||||
"terser": {
|
||||
"optional": true
|
||||
},
|
||||
"tsx": {
|
||||
"optional": true
|
||||
},
|
||||
"yaml": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/w3c-xmlserializer": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
|
||||
|
||||
Reference in New Issue
Block a user