mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Updated Catalog Store
This commit is contained in:
@@ -0,0 +1,300 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { StringDictionary } from '@cmf/core';
|
||||
import { ApplicationService } from '@core/application';
|
||||
import { BreadcrumbService } from '@core/breadcrumb';
|
||||
import { ComponentStore, tapResponse } from '@ngrx/component-store';
|
||||
import { isEqual } from 'lodash';
|
||||
import { combineLatest, Observable, Subscription } from 'rxjs';
|
||||
import { first, map, switchMap, withLatestFrom } from 'rxjs/operators';
|
||||
import { DomainCatalogService } from '@domain/catalog';
|
||||
import {
|
||||
mapFilterArrayToStringDictionary,
|
||||
mapInputArraysToFilterGroupArrayInsideInputGroup,
|
||||
mapSelectedFilterToParams,
|
||||
} from './article-search.mappings';
|
||||
import { Filter } from '@ui/filter';
|
||||
import { ItemDTO, QueryTokenDTO, OrderByDTO } from '@swagger/cat';
|
||||
|
||||
export interface ArticleSearchState {
|
||||
searchState: 'empty' | 'fetching' | '';
|
||||
params: {
|
||||
query?: string;
|
||||
filter?: string;
|
||||
primaryFilter?: string;
|
||||
orderBy?: string;
|
||||
desc?: string;
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
items?: ItemDTO[];
|
||||
hits?: number;
|
||||
history?: QueryTokenDTO[];
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ArticleSearchStore extends ComponentStore<ArticleSearchState> {
|
||||
private queryParamsQuerySelector = (s: ArticleSearchState) => s.params.query;
|
||||
readonly queryParamsQuery$ = this.select(this.queryParamsQuerySelector);
|
||||
|
||||
private itemsSelector = (s: ArticleSearchState) => s.items;
|
||||
readonly items$ = this.select(this.itemsSelector);
|
||||
|
||||
private orderBySelector = (s: ArticleSearchState) => s.params.orderBy;
|
||||
readonly queryParamsOrderBy$ = this.select(this.orderBySelector);
|
||||
|
||||
private descSelector = (s: ArticleSearchState) => s.params.desc;
|
||||
readonly queryParamsDesc$ = this.select(this.descSelector);
|
||||
|
||||
private hitsSelector = (s: ArticleSearchState) => s.hits;
|
||||
readonly hits$ = this.select(this.hitsSelector);
|
||||
|
||||
private queryParamsSelector = (s: ArticleSearchState) => s.params;
|
||||
readonly queryParams$ = this.select(this.queryParamsSelector);
|
||||
|
||||
private queryParamsFilterSelector = (s: ArticleSearchState) => s.params?.filter;
|
||||
readonly queryParamsFilter$ = this.select(this.queryParamsFilterSelector);
|
||||
|
||||
private queryParamsPrimaryFilterSelector = (s: ArticleSearchState) => s.params?.primaryFilter;
|
||||
readonly queryParamsPrimaryFilter$ = this.select(this.queryParamsPrimaryFilterSelector);
|
||||
|
||||
readonly defaultFilter$ = this.catalog.getFilters().pipe(
|
||||
map((ig) => {
|
||||
const groups = mapInputArraysToFilterGroupArrayInsideInputGroup(ig);
|
||||
return {
|
||||
filter: groups.filter((f) => f.group === 'filter'),
|
||||
main: groups.filter((f) => f.group === 'main'),
|
||||
inputSelector: groups.filter((f) => f.group === 'input_selector'),
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
readonly orderByOptions$ = this.catalog.getOrderBy();
|
||||
|
||||
readonly filter$ = combineLatest([this.queryParamsFilter$, this.defaultFilter$]).pipe(
|
||||
map(([selectedFilters, defaultFilters]) => {
|
||||
const filters = [...defaultFilters.filter, ...defaultFilters.inputSelector];
|
||||
const keys = selectedFilters.split(';');
|
||||
|
||||
filters?.forEach((filter) => {
|
||||
filter?.input?.forEach((input) => {
|
||||
input?.options?.forEach((option) => {
|
||||
option.selected = keys.includes(String(option.key));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return filters.reduce((agg, filter) => [...agg, ...filter.input], []) as Filter[];
|
||||
})
|
||||
);
|
||||
|
||||
readonly primaryFilter$ = combineLatest([this.queryParamsPrimaryFilter$, this.defaultFilter$]).pipe(
|
||||
map(([selectedFilters, defaultFilters]) => {
|
||||
const filters = [...defaultFilters.main];
|
||||
const keys = selectedFilters.split(';');
|
||||
|
||||
filters?.forEach((filter) => {
|
||||
filter?.input?.forEach((input) => {
|
||||
input?.options?.forEach((option) => {
|
||||
option.selected = keys.includes(String(option.key));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return filters.reduce((agg, filter) => [...agg, ...filter.input], []) as Filter[];
|
||||
})
|
||||
);
|
||||
|
||||
readonly queryTokenFilter$ = this.filter$.pipe(map((filter) => mapFilterArrayToStringDictionary(filter)));
|
||||
|
||||
readonly queryTokenInput$ = combineLatest([this.queryParamsQuery$, this.primaryFilter$]).pipe(
|
||||
map(([query, primaryFilter]) => {
|
||||
const dic: StringDictionary<string> = {};
|
||||
|
||||
primaryFilter.forEach((filter) =>
|
||||
filter.options.forEach((option) => {
|
||||
if (option.selected) {
|
||||
dic[option.key] = query;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
if (Object.keys(dic).length === 0) {
|
||||
dic['qs'] = query;
|
||||
}
|
||||
|
||||
return dic;
|
||||
})
|
||||
);
|
||||
|
||||
readonly queryTokenOrderBy$ = combineLatest([this.queryParamsOrderBy$, this.queryParamsDesc$, this.orderByOptions$]).pipe(
|
||||
map(([orderBy, desc, orderByOptions]) => orderByOptions.filter((opt) => opt.by === orderBy && !!opt.desc === Boolean(desc)))
|
||||
);
|
||||
|
||||
readonly queryToken$ = combineLatest([this.queryTokenInput$, this.queryTokenFilter$, this.queryTokenOrderBy$]).pipe(
|
||||
map(([input, filter, orderBy]) => {
|
||||
const query = {
|
||||
input,
|
||||
orderBy,
|
||||
filter,
|
||||
returnStockData: false,
|
||||
} as QueryTokenDTO;
|
||||
|
||||
const friendlyName = this.getFriendlyName(query);
|
||||
|
||||
if (!!friendlyName) {
|
||||
query.friendlyName = friendlyName;
|
||||
}
|
||||
|
||||
return query;
|
||||
})
|
||||
);
|
||||
|
||||
private connectedRouteSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private catalog: DomainCatalogService,
|
||||
private breadcrumb: BreadcrumbService,
|
||||
private application: ApplicationService
|
||||
) {
|
||||
super({
|
||||
searchState: '',
|
||||
params: {},
|
||||
});
|
||||
}
|
||||
|
||||
search = this.effect((options$: Observable<{ clear?: boolean; reload?: boolean }>) =>
|
||||
options$.pipe(
|
||||
withLatestFrom(this.queryToken$, this.items$),
|
||||
switchMap(([options, queryToken, items]) =>
|
||||
this.catalog
|
||||
.search({
|
||||
queryToken: {
|
||||
...queryToken,
|
||||
skip: options.clear || options.reload ? 0 : items.length,
|
||||
take: options.reload ? items.length : 25,
|
||||
},
|
||||
})
|
||||
.pipe(
|
||||
tapResponse(
|
||||
(res) => {
|
||||
if (options.clear || options.reload) {
|
||||
this.patchState({ items: res.result, hits: res.hits });
|
||||
} else {
|
||||
this.patchState({ items: [...items, ...res.result], hits: res.hits });
|
||||
}
|
||||
},
|
||||
(err) => {}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
connect(route: ActivatedRoute) {
|
||||
this.disconnect();
|
||||
|
||||
this.connectedRouteSubscription = route.queryParams.subscribe((params) => {
|
||||
const current = this.get(this.queryParamsSelector);
|
||||
|
||||
if (!isEqual(current, params)) {
|
||||
this.setQueryParams(params);
|
||||
}
|
||||
});
|
||||
|
||||
const queryParamsSub = this.queryParams$.subscribe((params) => {
|
||||
this.updateQueryParams(params);
|
||||
});
|
||||
|
||||
this.connectedRouteSubscription.add(queryParamsSub);
|
||||
|
||||
return { disconnect: () => this.disconnect() };
|
||||
}
|
||||
|
||||
readonly loadHistory = this.effect(($) =>
|
||||
$.pipe(
|
||||
switchMap((_) =>
|
||||
this.catalog.getSearchHistory({ take: 5 }).pipe(
|
||||
tapResponse(
|
||||
(history) => this.patchState({ history }),
|
||||
(err) => {}
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
async updateBreadcrumbs(params: StringDictionary<string>) {
|
||||
const crumbs = await this.breadcrumb
|
||||
.getBreadcrumbsByKeyAndTags$(this.application.activatedProcessId, ['catalog', 'filter'])
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
for (const crumb of crumbs) {
|
||||
this.breadcrumb.patchBreadcrumb(crumb.id, { params });
|
||||
}
|
||||
}
|
||||
|
||||
async updateQueryParams(params: StringDictionary<string>) {
|
||||
await this.updateBreadcrumbs(params);
|
||||
await this.router.navigate([], {
|
||||
queryParams: params,
|
||||
});
|
||||
}
|
||||
|
||||
getFriendlyName(query: QueryTokenDTO) {
|
||||
return this.get((s) => s.history).find((history) =>
|
||||
isEqual({ input: history.input, filter: history.filter }, { input: query.input, filter: query.filter })
|
||||
)?.friendlyName;
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.connectedRouteSubscription?.unsubscribe();
|
||||
}
|
||||
|
||||
setQueryParams(params: StringDictionary<string>) {
|
||||
this.patchState({ params });
|
||||
}
|
||||
|
||||
setQuery(query: string) {
|
||||
const queryParams = this.get(this.queryParamsSelector);
|
||||
this.patchState({
|
||||
params: {
|
||||
...queryParams,
|
||||
query,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setFilter(filter: Filter[]) {
|
||||
const queryParams = this.get(this.queryParamsSelector);
|
||||
this.patchState({
|
||||
params: {
|
||||
...queryParams,
|
||||
filter: mapSelectedFilterToParams(filter),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setPrimaryFilter(filter: Filter[]) {
|
||||
const queryParams = this.get(this.queryParamsSelector);
|
||||
this.patchState({
|
||||
params: {
|
||||
...queryParams,
|
||||
primaryFilter: mapSelectedFilterToParams(filter),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
setOrderBy(orderBy: OrderByDTO) {
|
||||
const queryParams = this.get(this.queryParamsSelector);
|
||||
this.patchState({
|
||||
params: {
|
||||
...queryParams,
|
||||
orderBy: orderBy?.by,
|
||||
desc: !!orderBy ? String(orderBy.desc) : undefined,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { StringDictionary } from '@cmf/core';
|
||||
import { InputGroupDTO, InputDTO, OptionDTO, InputType } from '@swagger/cat';
|
||||
import { SelectFilterGroup, SelectFilter, SelectFilterOption, FilterType, Filter } from '@ui/filter';
|
||||
|
||||
export function mapInputArraysToFilterGroupArrayInsideInputGroup(source: InputGroupDTO[]): SelectFilterGroup[] {
|
||||
const mappedArr = source.map((inputGroup) => {
|
||||
return {
|
||||
group: inputGroup.group || undefined,
|
||||
input: inputGroup.input.map((input) => this.fromInputDto(input)),
|
||||
label: inputGroup.label || undefined,
|
||||
} as SelectFilterGroup;
|
||||
});
|
||||
return mappedArr;
|
||||
}
|
||||
|
||||
export function concatNestedArrays(source: SelectFilterGroup[]): SelectFilter[] {
|
||||
const arr: SelectFilter[] = [];
|
||||
source.forEach((filter) => arr.push(...filter.input));
|
||||
return arr;
|
||||
}
|
||||
|
||||
export function fromInputDto(input: InputDTO): SelectFilter {
|
||||
return {
|
||||
key: input.key,
|
||||
name: input.label,
|
||||
max: input.options && input.options.max,
|
||||
type: this.inputTypeToType(input.type),
|
||||
target: input.target,
|
||||
options: input.options && input.options.values.map((v) => this.fromOptionDto(v)),
|
||||
} as SelectFilter;
|
||||
}
|
||||
|
||||
export function fromOptionDto(option: OptionDTO): SelectFilterOption {
|
||||
return {
|
||||
name: option.label,
|
||||
id: option.key || option.value,
|
||||
selected: option.selected || false,
|
||||
initial_selected_state: option.selected || false,
|
||||
expanded: false,
|
||||
options: option.values && option.values.map((v) => this.fromOptionDto(v)),
|
||||
} as SelectFilterOption;
|
||||
}
|
||||
|
||||
export function inputTypeToType(type: InputType): FilterType {
|
||||
switch (type) {
|
||||
case 1:
|
||||
return 'text';
|
||||
|
||||
case 2:
|
||||
return 'select';
|
||||
|
||||
case 4:
|
||||
return 'checkbox';
|
||||
|
||||
case 8 || 16:
|
||||
return 'date';
|
||||
|
||||
case 32 || 64:
|
||||
return 'number';
|
||||
|
||||
default:
|
||||
return 'select';
|
||||
}
|
||||
}
|
||||
|
||||
export function mapFilterArrayToStringDictionary(source: Filter[]): StringDictionary<string> {
|
||||
const dict: StringDictionary<string> = {};
|
||||
for (const filter of source) {
|
||||
const selected = filter.options.filter((o) => o.selected);
|
||||
if (selected.length > 0) {
|
||||
dict[filter.key] = selected.map((o) => o.id).join(';');
|
||||
}
|
||||
}
|
||||
return dict;
|
||||
}
|
||||
|
||||
export function mapSelectedFilterToParams(source: Filter[]): string {
|
||||
const dict: StringDictionary<string> = {};
|
||||
for (const filter of source) {
|
||||
const selected = filter.options.filter((o) => o.selected);
|
||||
if (selected.length > 0) {
|
||||
dict[filter.key] = selected.map((o) => o.id).join(';');
|
||||
}
|
||||
}
|
||||
return [...Object.values(dict)].join(';');
|
||||
}
|
||||
Reference in New Issue
Block a user