mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
#683 Add Chips to Filter View
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// start:ng42.barrel
|
||||
export * from './shell-search-device.model';
|
||||
export * from './shelf-primary-filter-options';
|
||||
// end:ng42.barrel
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface ShelfPrimaryFilterOptions {
|
||||
allBranches: boolean;
|
||||
customerName: boolean;
|
||||
author: boolean;
|
||||
title: boolean;
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// start:ng42.barrel
|
||||
export * from './shelf-primary-filters.component';
|
||||
// end:ng42.barrel
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<div class="container" *ngIf="primaryFilters$ | async as primaryFilters">
|
||||
<button
|
||||
class="isa-chip"
|
||||
id="allBranches"
|
||||
(click)="handleClick($event.target)"
|
||||
[class.selected]="primaryFilters['allBranches']"
|
||||
>
|
||||
Alle Filialen
|
||||
</button>
|
||||
<button
|
||||
class="isa-chip"
|
||||
id="customerName"
|
||||
(click)="handleClick($event.target)"
|
||||
[class.selected]="primaryFilters['customerName']"
|
||||
>
|
||||
Kundenname
|
||||
</button>
|
||||
<button
|
||||
class="isa-chip"
|
||||
id="author"
|
||||
(click)="handleClick($event.target)"
|
||||
[class.selected]="primaryFilters['author']"
|
||||
>
|
||||
Autor
|
||||
</button>
|
||||
<button
|
||||
class="isa-chip"
|
||||
id="title"
|
||||
(click)="handleClick($event.target)"
|
||||
[class.selected]="primaryFilters['title']"
|
||||
>
|
||||
Titel
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,7 @@
|
||||
.container {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
grid-gap: 30px;
|
||||
grid-template-columns: repeat(4, auto);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
ChangeDetectionStrategy,
|
||||
Renderer2,
|
||||
} from '@angular/core';
|
||||
import { ShelfPrimaryFilterOptions } from '../../../defs';
|
||||
import { Observable } from 'rxjs';
|
||||
import { SearchStateFacade } from 'apps/sales/src/app/store/customer';
|
||||
import { map, take } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'app-shelf-primary-filters',
|
||||
templateUrl: 'shelf-primary-filters.component.html',
|
||||
styleUrls: ['./shelf-primary-filters.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ShelfPrimaryFiltersComponent implements OnInit {
|
||||
primaryFilters$: Observable<ShelfPrimaryFilterOptions>;
|
||||
|
||||
constructor(private searchStateFacade: SearchStateFacade) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.initFilters();
|
||||
}
|
||||
|
||||
handleClick(target: HTMLButtonElement) {
|
||||
const identifier = target.id;
|
||||
if (!identifier) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.handleUpdate({ identifier });
|
||||
}
|
||||
|
||||
private handleUpdate(params: { identifier: string }) {
|
||||
this.primaryFilters$.pipe(take(1)).subscribe((currentFilters) => {
|
||||
const updatedValue = !currentFilters[params.identifier];
|
||||
|
||||
this.updateSelectedFilters({ [params.identifier]: updatedValue });
|
||||
});
|
||||
}
|
||||
|
||||
private updateSelectedFilters(changes: Partial<ShelfPrimaryFilterOptions>) {
|
||||
this.searchStateFacade.setPrimaryFilters(changes);
|
||||
}
|
||||
|
||||
private initFilters() {
|
||||
this.primaryFilters$ = this.searchStateFacade.getPrimaryFilters();
|
||||
}
|
||||
}
|
||||
@@ -14,5 +14,9 @@
|
||||
></lib-icon>
|
||||
</button>
|
||||
<h2 class="isa-filter-title">Filter</h2>
|
||||
|
||||
<div>
|
||||
<app-shelf-primary-filters></app-shelf-primary-filters>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,11 +3,12 @@ import { CommonModule } from '@angular/common';
|
||||
import { ShelfFilterComponent } from './shelf-filter.component';
|
||||
import { SharedModule } from 'apps/sales/src/app/shared/shared.module';
|
||||
import { IconModule } from '@libs/ui';
|
||||
import { ShelfPrimaryFiltersComponent } from './primary-filters';
|
||||
|
||||
@NgModule({
|
||||
imports: [CommonModule, SharedModule, IconModule],
|
||||
exports: [ShelfFilterComponent],
|
||||
declarations: [ShelfFilterComponent],
|
||||
exports: [ShelfFilterComponent, ShelfPrimaryFiltersComponent],
|
||||
declarations: [ShelfFilterComponent, ShelfPrimaryFiltersComponent],
|
||||
entryComponents: [ShelfFilterComponent],
|
||||
})
|
||||
export class ShelfFilterModule {}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { ShelfPrimaryFilterOptions } from 'apps/sales/src/app/modules/shelf/defs';
|
||||
|
||||
export interface SearchProcess {
|
||||
id: number; // Prozess ID;
|
||||
input?: string;
|
||||
filters?: { [key: string]: string[] };
|
||||
filters?: {
|
||||
selectedFilters?: { [key: string]: string[] };
|
||||
primaryFilters?: ShelfPrimaryFilterOptions;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createAction, props } from '@ngrx/store';
|
||||
import { ShelfPrimaryFilterOptions } from 'apps/sales/src/app/modules/shelf/defs';
|
||||
|
||||
const prefix = '[CUSTOMER] [SHELF] [SEARCH]';
|
||||
|
||||
@@ -17,12 +18,17 @@ export const setInput = createAction(
|
||||
props<{ id: number; input: string }>()
|
||||
);
|
||||
|
||||
export const setFilters = createAction(
|
||||
`${prefix} Set Filters`,
|
||||
export const setSelectedFilters = createAction(
|
||||
`${prefix} Set Selected Filters`,
|
||||
props<{ id: number; filters: { [key: string]: string[] } }>()
|
||||
);
|
||||
|
||||
export const clearFilters = createAction(
|
||||
`${prefix} Clear Filters`,
|
||||
export const clearSelectedFilters = createAction(
|
||||
`${prefix} Clear Selected Filters`,
|
||||
props<{ id: number }>()
|
||||
);
|
||||
|
||||
export const setPrimaryFilters = createAction(
|
||||
`${prefix} Set Primary Filters`,
|
||||
props<{ id: number; filters: ShelfPrimaryFilterOptions }>()
|
||||
);
|
||||
|
||||
@@ -5,9 +5,13 @@ import * as actions from './search.actions';
|
||||
import { SharedSelectors } from 'apps/sales/src/app/core/store/selectors/shared.selectors';
|
||||
import { map, first, take, switchMap, filter } from 'rxjs/operators';
|
||||
import { from, Observable } from 'rxjs';
|
||||
import { selectSearchProcessById } from './search.selectors';
|
||||
import {
|
||||
selectSearchProcessById,
|
||||
selectProcessPrimaryFiltersById,
|
||||
} from './search.selectors';
|
||||
import { SearchProcess } from './defs';
|
||||
import { isNullOrUndefined } from 'util';
|
||||
import { ShelfPrimaryFilterOptions } from 'apps/sales/src/app/modules/shelf/defs';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class SearchStateFacade {
|
||||
@@ -35,8 +39,11 @@ export class SearchStateFacade {
|
||||
|
||||
get currentSearchProcessFilters$(): Observable<{ [key: string]: string[] }> {
|
||||
return this.currentSearchProcess$.pipe(
|
||||
filter((process) => !isNullOrUndefined(process)),
|
||||
map((process) => process.filters)
|
||||
filter(
|
||||
(process) =>
|
||||
!isNullOrUndefined(process) && !isNullOrUndefined(process.filters)
|
||||
),
|
||||
map((process) => process.filters.selectedFilters)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,13 +57,54 @@ export class SearchStateFacade {
|
||||
this.store.dispatch(actions.setInput({ input, id: processId }));
|
||||
}
|
||||
|
||||
async setFilters(filters: { [key: string]: string[] }, id?: number) {
|
||||
async setSelectedFilters(filters: { [key: string]: string[] }, id?: number) {
|
||||
if (id) {
|
||||
return this.store.dispatch(actions.setFilters({ filters, id }));
|
||||
return this.store.dispatch(actions.setSelectedFilters({ filters, id }));
|
||||
}
|
||||
|
||||
const processId = await this.getProcessId();
|
||||
|
||||
this.store.dispatch(actions.setFilters({ filters, id: processId }));
|
||||
this.store.dispatch(actions.setSelectedFilters({ filters, id: processId }));
|
||||
}
|
||||
|
||||
async setPrimaryFilters(
|
||||
filters: Partial<ShelfPrimaryFilterOptions>,
|
||||
id?: number
|
||||
) {
|
||||
let updatedFilters: ShelfPrimaryFilterOptions;
|
||||
let processId = id;
|
||||
|
||||
if (!id) {
|
||||
processId = await this.getProcessId();
|
||||
}
|
||||
|
||||
const currentPrimaryFilters = await this.getCurrentPrimaryFilters(processId)
|
||||
.pipe(first())
|
||||
.toPromise();
|
||||
|
||||
updatedFilters = {
|
||||
...currentPrimaryFilters,
|
||||
...filters,
|
||||
} as ShelfPrimaryFilterOptions;
|
||||
|
||||
return this.store.dispatch(
|
||||
actions.setPrimaryFilters({ filters: updatedFilters, id: processId })
|
||||
);
|
||||
}
|
||||
|
||||
getPrimaryFilters(id?: number): Observable<ShelfPrimaryFilterOptions> {
|
||||
if (id) {
|
||||
return this.getCurrentPrimaryFilters(id);
|
||||
}
|
||||
|
||||
return from(this.getProcessId()).pipe(
|
||||
switchMap((processId) => this.getCurrentPrimaryFilters(processId))
|
||||
);
|
||||
}
|
||||
|
||||
private getCurrentPrimaryFilters(
|
||||
processId: number
|
||||
): Observable<ShelfPrimaryFilterOptions> {
|
||||
return this.store.select(selectProcessPrimaryFiltersById, processId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,14 @@ import {
|
||||
INITIAL_SEARCH_STATE,
|
||||
SearchState,
|
||||
searchStateAdapter,
|
||||
INITIAL_FILTERS,
|
||||
} from './search.state';
|
||||
import * as actions from './search.actions';
|
||||
|
||||
const _searchReducer = createReducer(
|
||||
INITIAL_SEARCH_STATE,
|
||||
on(actions.addSearchProcess, (s, a) =>
|
||||
searchStateAdapter.addOne({ id: a.id }, s)
|
||||
searchStateAdapter.addOne({ id: a.id, filters: INITIAL_FILTERS }, s)
|
||||
),
|
||||
on(actions.removeSearchProcess, (s, a) =>
|
||||
searchStateAdapter.removeOne(a.id, s)
|
||||
@@ -17,22 +18,30 @@ const _searchReducer = createReducer(
|
||||
on(actions.setInput, (s, a) =>
|
||||
searchStateAdapter.updateOne({ id: a.id, changes: { input: a.input } }, s)
|
||||
),
|
||||
on(actions.setFilters, (s, a) =>
|
||||
on(actions.setSelectedFilters, (s, a) =>
|
||||
searchStateAdapter.updateOne(
|
||||
{ id: a.id, changes: { filters: a.filters } },
|
||||
{ id: a.id, changes: { filters: { selectedFilters: a.filters } } },
|
||||
s
|
||||
)
|
||||
),
|
||||
on(actions.clearFilters, (s, a) =>
|
||||
on(actions.clearSelectedFilters, (s, a) =>
|
||||
searchStateAdapter.updateOne(
|
||||
{
|
||||
id: a.id,
|
||||
changes: {
|
||||
filters: {},
|
||||
filters: {
|
||||
selectedFilters: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
s
|
||||
)
|
||||
),
|
||||
on(actions.setPrimaryFilters, (s, a) =>
|
||||
searchStateAdapter.updateOne(
|
||||
{ id: a.id, changes: { filters: { primaryFilters: a.filters } } },
|
||||
s
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -31,3 +31,25 @@ export const selectSearchProcessFiltersById = createSelector(
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const selectProcessSelectedFiltersById = createSelector(
|
||||
selectAllSearchProcesses,
|
||||
(s: SearchProcess[], processId: number) => {
|
||||
const searchProcess = s.find((p) => p.id === processId);
|
||||
|
||||
if (searchProcess && searchProcess.filters) {
|
||||
return searchProcess.filters.selectedFilters;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const selectProcessPrimaryFiltersById = createSelector(
|
||||
selectAllSearchProcesses,
|
||||
(s: SearchProcess[], processId: number) => {
|
||||
const searchProcess = s.find((p) => p.id === processId);
|
||||
|
||||
if (searchProcess && searchProcess.filters) {
|
||||
return searchProcess.filters.primaryFilters;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { EntityState, createEntityAdapter } from '@ngrx/entity';
|
||||
import { SearchProcess } from './defs';
|
||||
import { ShelfPrimaryFilterOptions } from 'apps/sales/src/app/modules/shelf/defs';
|
||||
|
||||
export interface SearchState extends EntityState<SearchProcess> {}
|
||||
|
||||
@@ -8,3 +9,18 @@ export const searchStateAdapter = createEntityAdapter<SearchProcess>();
|
||||
export const INITIAL_SEARCH_STATE: SearchState = {
|
||||
...searchStateAdapter.getInitialState(),
|
||||
};
|
||||
|
||||
export const INITIAL_PRIMARY_FILTERS: ShelfPrimaryFilterOptions = {
|
||||
allBranches: false,
|
||||
customerName: false,
|
||||
author: false,
|
||||
title: false,
|
||||
};
|
||||
|
||||
export const INITIAL_FILTERS: {
|
||||
selectedFilters?: { [key: string]: string[] };
|
||||
primaryFilters?: ShelfPrimaryFilterOptions;
|
||||
} = {
|
||||
selectedFilters: {},
|
||||
primaryFilters: INITIAL_PRIMARY_FILTERS,
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
/* MODULES */
|
||||
@import 'modules/button';
|
||||
@import 'modules/card';
|
||||
@import 'modules/chip';
|
||||
@import 'modules/content';
|
||||
@import 'modules/forms';
|
||||
@import 'modules/modal';
|
||||
|
||||
@@ -14,6 +14,7 @@ $text-black: #000000;
|
||||
$isa-red: #f70400;
|
||||
$isa-white: #ffffff;
|
||||
$isa-lightgray: #e2e2e2;
|
||||
$isa-customer: #557596;
|
||||
$isa-customer-active: #59647a;
|
||||
$isa-customer-active: #1f466c;
|
||||
|
||||
@@ -41,6 +42,11 @@ $big-desktop: 1800px;
|
||||
$content-background-color: #fff;
|
||||
$content-border-radius: 5px;
|
||||
|
||||
/** CHIP */
|
||||
$chip-border-radius: 27px;
|
||||
$chip-padding: 0 20px 0 21px;
|
||||
$chip-height: 53px;
|
||||
|
||||
/* HEADLINE */
|
||||
$headline-font-size-l: 26px;
|
||||
$headline-font-size-m: 22px;
|
||||
|
||||
45
apps/sales/src/scss/modules/_chip.scss
Normal file
45
apps/sales/src/scss/modules/_chip.scss
Normal file
@@ -0,0 +1,45 @@
|
||||
.isa-chip {
|
||||
background: $isa-white;
|
||||
color: $isa-customer;
|
||||
border: 2px solid transparent;
|
||||
border-radius: $chip-border-radius;
|
||||
font-family: $font-family;
|
||||
font-weight: $font-weight-emphasis;
|
||||
font-size: $font-size;
|
||||
line-height: $button-line-height-l;
|
||||
|
||||
padding: $chip-padding;
|
||||
width: fit-content;
|
||||
height: $chip-height;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&.selected {
|
||||
position: relative;
|
||||
font-weight: $font-weight-bold;
|
||||
color: $isa-customer;
|
||||
border: 2px solid $isa-customer;
|
||||
&::before {
|
||||
content: '';
|
||||
background-image: url(/assets/images/Check.svg);
|
||||
position: relative;
|
||||
left: 0px;
|
||||
top: 4px;
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
background-size: 16px;
|
||||
background-repeat: no-repeat;
|
||||
transition: all 2s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset Default Button Styles
|
||||
button.isa-chip {
|
||||
border: none;
|
||||
outline: none;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
.isa-filter-title {
|
||||
text-align: center;
|
||||
margin-top: 25px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.isa-btn-close {
|
||||
|
||||
Reference in New Issue
Block a user