#4982 #5004 Filter Datepicker QueryParams + QueryToken

This commit is contained in:
Nino
2025-04-10 17:31:29 +02:00
parent de47c493bf
commit a93251f082
3 changed files with 149 additions and 27 deletions

View File

@@ -1,15 +1,22 @@
import { computed, inject, Injectable, signal } from '@angular/core';
import { computed, inject, Injectable, Input, signal } from '@angular/core';
import { InputType } from '../types';
import { getState, patchState, signalState } from '@ngrx/signals';
import { mapToFilter } from './mappings';
import { isEqual } from 'lodash';
import { FilterInput, OrderByDirectionSchema, Query, QuerySchema } from './schemas';
import {
FilterInput,
OrderByDirectionSchema,
Query,
QuerySchema,
} from './schemas';
import { FILTER_ON_COMMIT, FILTER_ON_INIT, QUERY_SETTINGS } from './tokens';
@Injectable()
export class FilterService {
#onInit = inject(FILTER_ON_INIT, { optional: true })?.map((fn) => fn(this));
#onCommit = inject(FILTER_ON_COMMIT, { optional: true })?.map((fn) => fn(this));
#onCommit = inject(FILTER_ON_COMMIT, { optional: true })?.map((fn) =>
fn(this),
);
readonly settings = inject(QUERY_SETTINGS);
@@ -72,7 +79,11 @@ export class FilterService {
this.setOrderBy({ by, dir: orderByDir }, options);
}
setInputTextValue(key: string, value: string | undefined, options?: { commit: boolean }): void {
setInputTextValue(
key: string,
value: string | undefined,
options?: { commit: boolean },
): void {
const inputs = this.#state.inputs().map((input) => {
if (input.key !== key) {
return input;
@@ -94,7 +105,11 @@ export class FilterService {
}
}
setInputCheckboxValue(key: string, selected: string[], options?: { commit: boolean }): void {
setInputCheckboxValue(
key: string,
selected: string[],
options?: { commit: boolean },
): void {
const inputs = this.#state.inputs().map((input) => {
if (input.key !== key) {
return input;
@@ -116,6 +131,33 @@ export class FilterService {
}
}
setInputDateRangeValue(
key: string,
start?: string,
stop?: string,
options?: { commit: boolean },
): void {
const inputs = this.#state.inputs().map((input) => {
if (input.key !== key) {
return input;
}
if (input.type === InputType.DateRange) {
return { ...input, start, stop };
}
console.warn(`Input type not supported: ${input.type}`);
return input;
});
patchState(this.#state, { inputs });
if (options?.commit) {
this.commit();
}
}
/**
* Indicates whether the current state is the default state.
* This computed property checks if the current state is equal to the default state.
@@ -126,8 +168,12 @@ export class FilterService {
});
isDefaultFilterInput(filterInput: FilterInput) {
const currentInputState = this.#state.inputs().find((i) => i.key === filterInput.key);
const defaultInputState = this.defaultState.inputs.find((i) => i.key === filterInput.key);
const currentInputState = this.#state
.inputs()
.find((i) => i.key === filterInput.key);
const defaultInputState = this.defaultState.inputs.find(
(i) => i.key === filterInput.key,
);
return isEqual(currentInputState, defaultInputState);
}
@@ -146,14 +192,20 @@ export class FilterService {
return !input.selected?.length;
}
console.warn(`Input type not supported: ${input.type}`);
if (input.type === InputType.DateRange) {
return !input.start && !input.stop;
}
console.warn(`Input type not supported`);
return true;
});
});
isEmptyFilterInput(filterInput: FilterInput) {
const currentInputState = this.#state.inputs().find((i) => i.key === filterInput.key);
const currentInputState = this.#state
.inputs()
.find((i) => i.key === filterInput.key);
if (currentInputState?.type === InputType.Text) {
return !currentInputState.value;
@@ -163,7 +215,11 @@ export class FilterService {
return !currentInputState.selected?.length;
}
console.warn(`Input type not supported: ${currentInputState?.type}`);
if (currentInputState?.type === InputType.DateRange) {
return !currentInputState.start && !currentInputState.stop;
}
console.warn(`Input type not supported`);
return true;
}
@@ -226,7 +282,9 @@ export class FilterService {
return;
}
const inputIndex = this.#commitdState().inputs.findIndex((i) => i.key === key);
const inputIndex = this.#commitdState().inputs.findIndex(
(i) => i.key === key,
);
if (inputIndex === -1) {
console.warn(`No committed input found with key: ${key}`);
@@ -243,7 +301,9 @@ export class FilterService {
commitOrderBy() {
const orderBy = this.#state.orderBy().map((o) => {
const committedOrderBy = this.#commitdState().orderBy.find((co) => co.by === o.by);
const committedOrderBy = this.#commitdState().orderBy.find(
(co) => co.by === o.by,
);
return { ...o, dir: committedOrderBy?.dir };
});
@@ -260,6 +320,10 @@ export class FilterService {
return { ...input, selected: [] };
}
if (input.type === InputType.DateRange) {
return { ...input, start: undefined, stop: undefined };
}
return input;
});
@@ -355,6 +419,15 @@ export class FilterService {
result[input.key] = input.selected.join(';');
}
break;
case InputType.DateRange:
if (input.start && input.stop) {
result[input.key] = `"${input.start}"-"${input.stop}"`;
} else if (input.start) {
result[input.key] = `"${input.start}"-`;
} else if (input.stop) {
result[input.key] = `-"${input.stop}"`;
}
break;
}
}
@@ -369,7 +442,9 @@ export class FilterService {
isQueryParamsEqual(params: Record<string, string>): boolean {
const currentParams = this.queryParams();
return this.queryParamKeys().every((key) => params[key] === currentParams[key]);
return this.queryParamKeys().every(
(key) => params[key] === currentParams[key],
);
}
queryParamKeys = computed(() => {
@@ -396,6 +471,14 @@ export class FilterService {
acc[input.key] = input.value || '';
} else if (input.type === InputType.Checkbox) {
acc[input.key] = input.selected?.join(';') || '';
} else if (input.type === InputType.DateRange) {
if (input.start && input.stop) {
acc[input.key] = `"${input.start}"-"${input.stop}"`;
} else if (input.start) {
acc[input.key] = `"${input.start}"-`;
} else if (input.stop) {
acc[input.key] = `-"${input.stop}"`;
}
}
return acc;
}, {}),
@@ -404,6 +487,14 @@ export class FilterService {
acc[input.key] = input.value || '';
} else if (input.type === InputType.Checkbox) {
acc[input.key] = input.selected?.join(';') || '';
} else if (input.type === InputType.DateRange) {
if (input.start && input.stop) {
acc[input.key] = `"${input.start}"-"${input.stop}"`;
} else if (input.start) {
acc[input.key] = `"${input.start}"-`;
} else if (input.stop) {
acc[input.key] = `-"${input.stop}"`;
}
}
return acc;
}, {}),
@@ -418,7 +509,10 @@ export class FilterService {
});
});
parseQueryParams(params: Record<string, string>, options?: { commit: boolean }): void {
parseQueryParams(
params: Record<string, string>,
options?: { commit: boolean },
): void {
this.reset();
for (const key in params) {
if (key === 'orderBy') {
@@ -440,6 +534,14 @@ export class FilterService {
case InputType.Checkbox:
this.setInputCheckboxValue(key, params[key].split(';'));
break;
case InputType.DateRange: {
const decoded = decodeURIComponent(params[key]);
const [startRaw, stopRaw] = decoded.split('-"');
const start = startRaw?.replace(/"/g, '') || undefined;
const stop = stopRaw?.replace(/"/g, '') || undefined;
this.setInputDateRangeValue(key, start, stop);
break;
}
default:
console.warn(`Input type not supported: ${inputType}`);
break;

View File

@@ -22,9 +22,7 @@ import { InputType } from '../../types';
encapsulation: ViewEncapsulation.None,
standalone: true,
imports: [RangeDatepickerComponent, ReactiveFormsModule],
host: {
'[class]': "['filter-datepicker-range-input']",
},
host: { '[class]': "['filter-datepicker-range-input']" },
})
export class DatepickerRangeInputComponent {
readonly filterService = inject(FilterService);
@@ -36,19 +34,14 @@ export class DatepickerRangeInputComponent {
input = computed<DateRangeFilterInput>(() => {
const inputs = this.filterService.inputs();
const input = inputs.find(
(input) => input.key === this.inputKey() && input.type === InputType.DateRange,
(input) =>
input.key === this.inputKey() && input.type === InputType.DateRange,
) as DateRangeFilterInput;
if (!input) {
throw new Error(`Input not found for key: ${this.inputKey()}`);
}
const startDate = input.start ? new Date(input.start) : undefined;
const stopDate = input.stop ? new Date(input.stop) : undefined;
this.datepicker.setValue([startDate, stopDate]);
this.datepicker.updateValueAndValidity();
return input;
});
@@ -63,12 +56,38 @@ export class DatepickerRangeInputComponent {
});
constructor() {
effect(() => {
const input = this.input();
const startDate = input.start ? new Date(input.start) : undefined;
const stopDate = input.stop ? new Date(input.stop) : undefined;
this.datepicker.patchValue([startDate, stopDate]);
this.datepicker.updateValueAndValidity();
});
effect(() => {
this.valueChanges();
untracked(() => {
console.log('Datepicker Value Changes', {
values: this.datepicker?.value,
});
const startDate = this.datepicker?.value?.[0];
const stopDate = this.datepicker?.value?.[1];
if (!startDate && !stopDate) {
return;
}
const start = startDate?.toISOString();
const stop = stopDate?.toISOString();
const controlEqualsInput =
this.input().start === start && this.input().stop === stop;
if (!controlEqualsInput) {
this.filterService.setInputDateRangeValue(
this.inputKey(),
start,
stop,
);
}
});
});
}

View File

@@ -19,6 +19,7 @@ import { format, isValid, parse, isEqual } from 'date-fns';
multi: true,
},
],
standalone: true,
})
// TODO: Date Input Directive soll ausgelagert werden in eine eigene Lib
export class DateInputDirective implements ControlValueAccessor {