mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Refactor menu components and styles for improved organization.
- 🛠️ **Refactor**: Removed old menu-button component and styles - ✨ **Feature**: Added new input-menu and filter-menu components - 🎨 **Style**: Updated styles for input-menu and filter-menu components - 🗑️ **Chore**: Cleaned up unused input-button component files
This commit is contained in:
@@ -21,10 +21,7 @@
|
||||
|
||||
<div class="flex flex-row gap-4">
|
||||
@for (filterInput of filterInputs(); track filterInput.key) {
|
||||
<filter-input-button
|
||||
[appearance]="'main'"
|
||||
[filterInput]="filterInput"
|
||||
(applyFilter)="onSearch()"
|
||||
></filter-input-button>
|
||||
<filter-input-menu-button [filterInput]="filterInput" (applied)="onSearch()">
|
||||
</filter-input-menu-button>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
inject,
|
||||
} from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import {
|
||||
ReturnSearchStatus,
|
||||
ReturnSearchStore,
|
||||
} from '@feature/return/services';
|
||||
import { ReturnSearchStatus, ReturnSearchStore } from '@feature/return/services';
|
||||
import { injectActivatedProcessId } from '@isa/core/process';
|
||||
import {
|
||||
FilterService,
|
||||
SearchBarInputComponent,
|
||||
InputButtonComponent,
|
||||
FilterInputMenuButtonComponent,
|
||||
} from '@isa/shared/filter';
|
||||
import { IconButtonComponent } from '@isa/ui/buttons';
|
||||
|
||||
@@ -23,7 +15,7 @@ import { IconButtonComponent } from '@isa/ui/buttons';
|
||||
styleUrls: ['./main-page.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [SearchBarInputComponent, IconButtonComponent, InputButtonComponent],
|
||||
imports: [SearchBarInputComponent, IconButtonComponent, FilterInputMenuButtonComponent],
|
||||
})
|
||||
export class MainPageComponent {
|
||||
#route = inject(ActivatedRoute);
|
||||
|
||||
@@ -7,15 +7,13 @@
|
||||
></filter-search-bar-input>
|
||||
|
||||
<div class="flex flex-row gap-4 items-center">
|
||||
<filter-menu-button
|
||||
[appearance]="'results'"
|
||||
(applyFilter)="onSearch()"
|
||||
></filter-menu-button>
|
||||
<filter-filter-menu-button
|
||||
(applied)="onSearch()"
|
||||
[rollbackOnClose]="true"
|
||||
></filter-filter-menu-button>
|
||||
|
||||
@if (isMobileDevice()) {
|
||||
<ui-icon-button
|
||||
(click)="toggleOrderByToolbar.set(!toggleOrderByToolbar())"
|
||||
>
|
||||
<ui-icon-button (click)="toggleOrderByToolbar.set(!toggleOrderByToolbar())">
|
||||
<ng-icon name="isaActionSort"></ng-icon>
|
||||
</ui-icon-button>
|
||||
} @else {
|
||||
@@ -38,18 +36,12 @@
|
||||
@for (item of items; track item.id) {
|
||||
@defer (on viewport) {
|
||||
<a [routerLink]="['../', 'receipt', item.id]" class="w-full">
|
||||
<lib-return-results-item-list
|
||||
#listElement
|
||||
[item]="item"
|
||||
></lib-return-results-item-list>
|
||||
<lib-return-results-item-list #listElement [item]="item"></lib-return-results-item-list>
|
||||
</a>
|
||||
} @placeholder {
|
||||
<!-- TODO: Den Spinner durch Skeleton Loader Kacheln ersetzen -->
|
||||
<div class="h-[7.75rem] w-full flex items-center justify-center">
|
||||
<ui-icon-button
|
||||
[pending]="true"
|
||||
[color]="'tertiary'"
|
||||
></ui-icon-button>
|
||||
<ui-icon-button [pending]="true" [color]="'tertiary'"></ui-icon-button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -59,16 +51,11 @@
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
} @else if (
|
||||
items.length === 0 && entityStatus() === ReturnSearchStatus.Pending
|
||||
) {
|
||||
} @else if (items.length === 0 && entityStatus() === ReturnSearchStatus.Pending) {
|
||||
<div class="h-[7.75rem] w-full flex items-center justify-center">
|
||||
<ui-icon-button [pending]="true" [color]="'tertiary'"></ui-icon-button>
|
||||
</div>
|
||||
} @else if (entityStatus() !== ReturnSearchStatus.Idle) {
|
||||
<ui-empty-state
|
||||
[title]="emptyState().title"
|
||||
[description]="emptyState().description"
|
||||
>
|
||||
<ui-empty-state [title]="emptyState().title" [description]="emptyState().description">
|
||||
</ui-empty-state>
|
||||
}
|
||||
|
||||
@@ -97,8 +97,7 @@ export class ResultsPageComponent {
|
||||
};
|
||||
});
|
||||
|
||||
listElements =
|
||||
viewChildren<QueryList<ReturnResultsItemListComponent>>('listElement');
|
||||
listElements = viewChildren<QueryList<ReturnResultsItemListComponent>>('listElement');
|
||||
|
||||
isMobileDevice = signal(this.#platform.ANDROID || this.#platform.IOS);
|
||||
toggleOrderByToolbar = signal(false);
|
||||
@@ -112,8 +111,7 @@ export class ResultsPageComponent {
|
||||
if (processId) {
|
||||
const entity = this._entity();
|
||||
if (entity) {
|
||||
const isPending =
|
||||
this.entityStatus() === ReturnSearchStatus.Pending;
|
||||
const isPending = this.entityStatus() === ReturnSearchStatus.Pending;
|
||||
// Trigger reload search if no search request is already pending and
|
||||
// 1. List scrolled to bottom
|
||||
// 2. After Process change AND no items in entity
|
||||
|
||||
@@ -2,5 +2,5 @@ export * from './lib/core';
|
||||
export * from './lib/inputs';
|
||||
export * from './lib/types';
|
||||
export * from './lib/actions';
|
||||
export * from './lib/input-button';
|
||||
export * from './lib/menu-button';
|
||||
export * from './lib/menus/filter-menu';
|
||||
export * from './lib/menus/input-menu';
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { createComponentFactory, Spectator, mockProvider } from '@ngneat/spectator/jest';
|
||||
import { FilterActionsComponent } from './filter-actions.component';
|
||||
import { FilterService } from '../core';
|
||||
|
||||
describe('FilterActionsComponent', () => {
|
||||
let spectator: Spectator<FilterActionsComponent>;
|
||||
const createComponent = createComponentFactory({
|
||||
component: FilterActionsComponent,
|
||||
providers: [
|
||||
mockProvider(FilterService, {
|
||||
inputs: jest.fn().mockReturnValue([
|
||||
{ group: 'filter', key: 'key1' },
|
||||
{ group: 'other', key: 'key2' },
|
||||
]),
|
||||
commit: jest.fn(),
|
||||
commitInput: jest.fn(),
|
||||
reset: jest.fn(),
|
||||
resetInput: jest.fn(),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
spectator = createComponent();
|
||||
});
|
||||
|
||||
it('should create the component', () => {
|
||||
expect(spectator.component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should filter inputs by group "filter"', () => {
|
||||
const filteredInputs = spectator.component.filterInputs();
|
||||
expect(filteredInputs).toEqual([{ group: 'filter', key: 'key1' }]);
|
||||
});
|
||||
|
||||
it('should call commit and emit applied when onApply is called without inputKey', () => {
|
||||
const filterService = spectator.inject(FilterService);
|
||||
const appliedSpy = jest.spyOn(spectator.component.applied, 'emit');
|
||||
|
||||
spectator.setInput('inputKey', null);
|
||||
spectator.component.onApply();
|
||||
|
||||
expect(filterService.commit).toHaveBeenCalled();
|
||||
expect(appliedSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call commitInput and emit applied when onApply is called with inputKey', () => {
|
||||
const filterService = spectator.inject(FilterService);
|
||||
const appliedSpy = jest.spyOn(spectator.component.applied, 'emit');
|
||||
|
||||
spectator.setInput('inputKey', 'key1');
|
||||
spectator.component.onApply();
|
||||
|
||||
expect(filterService.commitInput).toHaveBeenCalledWith('key1');
|
||||
expect(appliedSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call reset, commit, and emit reseted when onReset is called without inputKey', () => {
|
||||
const filterService = spectator.inject(FilterService);
|
||||
const resetedSpy = jest.spyOn(spectator.component.reseted, 'emit');
|
||||
|
||||
spectator.setInput('inputKey', null);
|
||||
spectator.component.onReset();
|
||||
|
||||
expect(filterService.reset).toHaveBeenCalled();
|
||||
expect(filterService.commit).toHaveBeenCalled();
|
||||
expect(resetedSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call resetInput, commit, and emit reseted when onReset is called with inputKey', () => {
|
||||
const filterService = spectator.inject(FilterService);
|
||||
const resetedSpy = jest.spyOn(spectator.component.reseted, 'emit');
|
||||
|
||||
spectator.setInput('inputKey', 'key1');
|
||||
spectator.component.onReset();
|
||||
|
||||
expect(filterService.resetInput).toHaveBeenCalledWith('key1');
|
||||
expect(filterService.commit).toHaveBeenCalled();
|
||||
expect(resetedSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
output,
|
||||
computed,
|
||||
ViewEncapsulation,
|
||||
input,
|
||||
} from '@angular/core';
|
||||
import { ButtonComponent } from '@isa/ui/buttons';
|
||||
import { FilterService } from '../core';
|
||||
@@ -24,22 +25,38 @@ import { FilterService } from '../core';
|
||||
export class FilterActionsComponent {
|
||||
readonly filterService = inject(FilterService);
|
||||
|
||||
inputKey = input<string>();
|
||||
|
||||
filterInputs = computed(() =>
|
||||
this.filterService.inputs().filter((input) => input.group === 'filter'),
|
||||
);
|
||||
applyFilter = output<void>();
|
||||
|
||||
applied = output<void>();
|
||||
|
||||
reseted = output<void>();
|
||||
|
||||
onApply() {
|
||||
this.filterService.commit();
|
||||
this.applyFilter.emit();
|
||||
const inputKey = this.inputKey();
|
||||
|
||||
if (!inputKey) {
|
||||
this.filterService.commit();
|
||||
} else {
|
||||
this.filterService.commitInput(inputKey);
|
||||
}
|
||||
|
||||
this.applied.emit();
|
||||
}
|
||||
|
||||
onReset() {
|
||||
for (const input of this.filterInputs()) {
|
||||
this.filterService.resetInput(input.key, { commit: true });
|
||||
const inputKey = this.inputKey();
|
||||
|
||||
if (!inputKey) {
|
||||
this.filterService.reset();
|
||||
} else {
|
||||
this.filterService.resetInput(inputKey);
|
||||
}
|
||||
|
||||
// TODO: Falls man auch die Suche zurücksetzen möchte
|
||||
// this.filterService.reset({ commit: true });
|
||||
this.filterService.commit();
|
||||
this.reseted.emit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,9 @@ import { InputType, QuerySettingsDTO } from '../types';
|
||||
import { getState, patchState, signalState } from '@ngrx/signals';
|
||||
import { mapToFilter } from './mappings';
|
||||
|
||||
export const QUERY_SETTINGS = new InjectionToken<QuerySettingsDTO>(
|
||||
'QuerySettings',
|
||||
);
|
||||
export const QUERY_SETTINGS = new InjectionToken<QuerySettingsDTO>('QuerySettings');
|
||||
|
||||
export function provideQuerySettings(
|
||||
factory: () => QuerySettingsDTO,
|
||||
): Provider[] {
|
||||
export function provideQuerySettings(factory: () => QuerySettingsDTO): Provider[] {
|
||||
return [{ provide: QUERY_SETTINGS, useFactory: factory }, FilterService];
|
||||
}
|
||||
|
||||
@@ -25,11 +21,7 @@ export class FilterService {
|
||||
|
||||
inputs = this.#state.inputs;
|
||||
|
||||
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;
|
||||
@@ -51,11 +43,7 @@ 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;
|
||||
@@ -131,9 +119,7 @@ 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}`);
|
||||
@@ -148,6 +134,26 @@ export class FilterService {
|
||||
};
|
||||
}
|
||||
|
||||
clear(options?: { commit: boolean }) {
|
||||
const inputs = this.#state.inputs().map((input) => {
|
||||
if (input.type === InputType.Text) {
|
||||
return { ...input, value: undefined };
|
||||
}
|
||||
|
||||
if (input.type === InputType.Checkbox) {
|
||||
return { ...input, selected: [] };
|
||||
}
|
||||
|
||||
return input;
|
||||
});
|
||||
|
||||
patchState(this.#state, { inputs });
|
||||
|
||||
if (options?.commit) {
|
||||
this.commit();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the filter state to its default values based on the current settings.
|
||||
*
|
||||
@@ -226,10 +232,7 @@ export class FilterService {
|
||||
return params;
|
||||
}
|
||||
|
||||
parseParams(
|
||||
params: Record<string, string>,
|
||||
options?: { commit: boolean },
|
||||
): void {
|
||||
parseParams(params: Record<string, string>, options?: { commit: boolean }): void {
|
||||
// Mögliche Alternative zum reset
|
||||
// const inputKeys = this.inputs().map((i) => i.key);
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './input-button.component';
|
||||
@@ -1,42 +0,0 @@
|
||||
<button
|
||||
class="filter-input-button__filter-button"
|
||||
[class.open]="!!activeInput()"
|
||||
(click)="toggleActiveInput()"
|
||||
type="button"
|
||||
cdkOverlayOrigin
|
||||
#trigger="cdkOverlayOrigin"
|
||||
>
|
||||
<span class="filter-input-button__filter-button-label">{{
|
||||
filterInput().label
|
||||
}}</span>
|
||||
<ng-icon
|
||||
class="filter-input-button__filter-button-icon"
|
||||
[name]="!!activeInput() ? 'isaActionChevronUp' : 'isaActionChevronDown'"
|
||||
size="1.5rem"
|
||||
>
|
||||
</ng-icon>
|
||||
</button>
|
||||
|
||||
@if (appearance() === 'main') {
|
||||
<ng-template
|
||||
cdkConnectedOverlay
|
||||
[cdkConnectedOverlayOrigin]="trigger"
|
||||
[cdkConnectedOverlayOpen]="!!activeInput()"
|
||||
[cdkConnectedOverlayHasBackdrop]="true"
|
||||
[cdkConnectedOverlayBackdropClass]="'cdk-overlay-transparent-backdrop'"
|
||||
[cdkConnectedOverlayOffsetX]="-10"
|
||||
[cdkConnectedOverlayOffsetY]="18"
|
||||
(backdropClick)="toggleActiveInput()"
|
||||
>
|
||||
<div class="relative flex flex-col items-center">
|
||||
<filter-input-renderer
|
||||
[filterInput]="activeInput()"
|
||||
></filter-input-renderer>
|
||||
<filter-actions
|
||||
class="absolute bottom-0 w-full"
|
||||
(applyFilter)="applyFilter.emit(); toggleActiveInput()"
|
||||
>
|
||||
</filter-actions>
|
||||
</div>
|
||||
</ng-template>
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
.filter-input-button__main {
|
||||
.filter-input-button__filter-button {
|
||||
@apply flex flex-row gap-2 items-center justify-center px-6 h-12 bg-isa-neutral-400 rounded-[3.125rem] border border-solid border-transparent;
|
||||
|
||||
.filter-input-button__filter-button-label {
|
||||
@apply text-isa-neutral-600 isa-text-body-2-bold;
|
||||
}
|
||||
|
||||
.filter-input-button__filter-button-icon {
|
||||
@apply text-isa-accent-blue;
|
||||
}
|
||||
|
||||
&.open {
|
||||
@apply bg-transparent border border-solid border-isa-neutral-900;
|
||||
|
||||
.filter-input-button__filter-button-label {
|
||||
@apply text-isa-neutral-900;
|
||||
}
|
||||
|
||||
.filter-input-button__filter-button-icon {
|
||||
@apply text-isa-neutral-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-input-button__results {
|
||||
.filter-input-button__filter-button {
|
||||
@apply flex flex-row;
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
import { OverlayModule } from '@angular/cdk/overlay';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
input,
|
||||
model,
|
||||
inject,
|
||||
ViewEncapsulation,
|
||||
output,
|
||||
} from '@angular/core';
|
||||
import { isaActionChevronDown, isaActionChevronUp } from '@isa/icons';
|
||||
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
||||
import { FilterInput, FilterService } from '../core';
|
||||
import { FilterActionsComponent } from '../actions';
|
||||
import { InputRendererComponent } from '../inputs';
|
||||
|
||||
@Component({
|
||||
selector: 'filter-input-button',
|
||||
templateUrl: 'input-button.component.html',
|
||||
styleUrls: ['./input-button.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
standalone: true,
|
||||
imports: [
|
||||
OverlayModule,
|
||||
NgIconComponent,
|
||||
InputRendererComponent,
|
||||
FilterActionsComponent,
|
||||
],
|
||||
host: {
|
||||
'[class]': "['filter-input-button', appearanceClass()]",
|
||||
},
|
||||
providers: [provideIcons({ isaActionChevronDown, isaActionChevronUp })],
|
||||
})
|
||||
export class InputButtonComponent {
|
||||
appearance = input<'main' | 'results'>('main');
|
||||
appearanceClass = computed(() => `filter-input-button__${this.appearance()}`);
|
||||
|
||||
filterInput = input.required<FilterInput>();
|
||||
activeInput = model<FilterInput | undefined>(undefined);
|
||||
applyFilter = output<void>();
|
||||
|
||||
#filterService = inject(FilterService);
|
||||
|
||||
toggleActiveInput() {
|
||||
const activeInput = this.activeInput();
|
||||
|
||||
if (activeInput) {
|
||||
this.rollbackInputAfterClose(activeInput);
|
||||
this.activeInput.set(undefined);
|
||||
} else {
|
||||
this.activeInput.set(this.filterInput());
|
||||
}
|
||||
}
|
||||
|
||||
rollbackInputAfterClose(activeInput: FilterInput) {
|
||||
const key = activeInput.key;
|
||||
if (key) {
|
||||
this.#filterService.rollbackInput(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,29 @@
|
||||
@let inp = input();
|
||||
@let options = input().options;
|
||||
@if (inp && options) {
|
||||
<div [formGroup]="checkboxes" class="filter-checkbox-input__container">
|
||||
<div class="filter-checkbox-input__checkbox-container">
|
||||
<ui-checkbox [appearance]="appearance()">
|
||||
<input
|
||||
(click)="toggleSelection()"
|
||||
id="selection"
|
||||
[checked]="allChecked"
|
||||
type="checkbox"
|
||||
/>
|
||||
<div [formGroup]="checkboxes" class="flex flex-col items-center justify-start gap-5">
|
||||
<label
|
||||
class="w-full flex items-center gap-4 cursor-pointer isa-text-body-2-regular has-[:checked]:isa-text-body-2-bold"
|
||||
>
|
||||
<ui-checkbox>
|
||||
<input (click)="toggleSelection()" [checked]="allChecked" type="checkbox" />
|
||||
</ui-checkbox>
|
||||
<label class="filter-checkbox-input__label checked" for="selection">
|
||||
Alle aus/abwählen</label
|
||||
>
|
||||
</div>
|
||||
<span> Alle aus/abwählen</span>
|
||||
</label>
|
||||
|
||||
@for (checkbox of options; track checkbox.label; let i = $index) {
|
||||
<div class="filter-checkbox-input__checkbox-container">
|
||||
<ui-checkbox [appearance]="appearance()">
|
||||
@for (option of options; track option.label; let i = $index) {
|
||||
<label
|
||||
class="w-full flex items-center gap-4 cursor-pointer isa-text-body-2-regular has-[:checked]:isa-text-body-2-bold"
|
||||
>
|
||||
<ui-checkbox>
|
||||
<input
|
||||
[attr.aria-label]="checkbox.label"
|
||||
[id]="checkbox.value"
|
||||
[formControlName]="checkbox.value"
|
||||
[attr.aria-label]="option.label"
|
||||
[formControlName]="option.value"
|
||||
type="checkbox"
|
||||
/>
|
||||
</ui-checkbox>
|
||||
<label
|
||||
class="filter-checkbox-input__label"
|
||||
[class.checked]="checkboxes.get(checkbox.value).value"
|
||||
[for]="checkbox.value"
|
||||
>
|
||||
{{ checkbox.label }}</label
|
||||
>
|
||||
</div>
|
||||
<span>{{ option.label }}</span>
|
||||
</label>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
.filter-checkbox-input {
|
||||
@apply h-[28.75rem] w-[15rem] flex flex-col items-center justify-start gap-4 px-6 pt-6 bg-isa-white rounded-[1.25rem] shadow-[0px_0px_16px_0px_rgba(0,0,0,0.15)] overflow-y-scroll;
|
||||
}
|
||||
|
||||
.filter-checkbox-input__container {
|
||||
@apply flex flex-col gap-5 w-full overflow-hidden overflow-y-scroll max-h-[21rem] pb-4;
|
||||
}
|
||||
|
||||
.filter-checkbox-input__checkbox-container {
|
||||
@apply flex flex-row items-center w-full;
|
||||
}
|
||||
|
||||
.filter-checkbox-input__label {
|
||||
@apply text-isa-neutral-900 isa-text-body-2-regular px-4 cursor-pointer;
|
||||
|
||||
&.checked {
|
||||
@apply isa-text-body-2-bold;
|
||||
}
|
||||
@apply inline-block p-6 text-isa-neutral-900;
|
||||
}
|
||||
|
||||
@@ -11,13 +11,9 @@ import {
|
||||
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { provideIcons } from '@ng-icons/core';
|
||||
import { isaActionCheck } from '@isa/icons';
|
||||
import {
|
||||
FilterService,
|
||||
CheckboxFilterInput,
|
||||
CheckboxFilterInputOption,
|
||||
} from '../../core';
|
||||
import { FilterService, CheckboxFilterInput, CheckboxFilterInputOption } from '../../core';
|
||||
import { InputType } from '../../types';
|
||||
import { CheckboxAppearance, CheckboxComponent } from '@isa/ui/input-controls';
|
||||
import { CheckboxComponent } from '@isa/ui/input-controls';
|
||||
import { OverlayModule } from '@angular/cdk/overlay';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { sortBy, isEqual } from 'lodash';
|
||||
@@ -31,7 +27,8 @@ import { sortBy, isEqual } from 'lodash';
|
||||
standalone: true,
|
||||
imports: [ReactiveFormsModule, CheckboxComponent, OverlayModule],
|
||||
host: {
|
||||
'[class]': "['filter-checkbox-input', appearanceClass()]",
|
||||
'[class]': "['filter-checkbox-input']",
|
||||
'[formGroup]': 'checkboxes',
|
||||
},
|
||||
providers: [provideIcons({ isaActionCheck })],
|
||||
})
|
||||
@@ -46,17 +43,11 @@ export class CheckboxInputComponent {
|
||||
}
|
||||
|
||||
inputKey = input.required<string>();
|
||||
appearance = input<CheckboxAppearance>(CheckboxAppearance.Checkbox);
|
||||
|
||||
appearanceClass = computed(
|
||||
() => `filter-checkbox-input__${this.appearance()}`,
|
||||
);
|
||||
|
||||
input = computed<CheckboxFilterInput>(() => {
|
||||
const inputs = this.filterService.inputs();
|
||||
const input = inputs.find(
|
||||
(input) =>
|
||||
input.key === this.inputKey() && input.type === InputType.Checkbox,
|
||||
(input) => input.key === this.inputKey() && input.type === InputType.Checkbox,
|
||||
) as CheckboxFilterInput;
|
||||
|
||||
if (!input) {
|
||||
@@ -94,16 +85,10 @@ export class CheckboxInputComponent {
|
||||
.filter(([, value]) => value === true)
|
||||
.map(([key]) => key);
|
||||
|
||||
const controlEqualsInput = isEqual(
|
||||
sortBy(this.input().selected),
|
||||
sortBy(selectedKeys),
|
||||
);
|
||||
const controlEqualsInput = isEqual(sortBy(this.input().selected), sortBy(selectedKeys));
|
||||
|
||||
if (!controlEqualsInput) {
|
||||
this.filterService.setInputCheckboxValue(
|
||||
this.inputKey(),
|
||||
selectedKeys,
|
||||
);
|
||||
this.filterService.setInputCheckboxValue(this.inputKey(), selectedKeys);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
@switch (filterInput().type) {
|
||||
@case (InputType.Checkbox) {
|
||||
<filter-checkbox-input [inputKey]="filterInput().key">
|
||||
</filter-checkbox-input>
|
||||
<filter-checkbox-input [inputKey]="filterInput().key"> </filter-checkbox-input>
|
||||
}
|
||||
@case (InputType.DateRange) {
|
||||
<filter-datepicker-input [inputKey]="filterInput().key">
|
||||
</filter-datepicker-input>
|
||||
<filter-datepicker-input [inputKey]="filterInput().key"> </filter-datepicker-input>
|
||||
}
|
||||
@default {
|
||||
<div class="text-isa-accent-red isa-text-body-1-bold">
|
||||
Fehler: Kein Template für diesen Typ gefunden! {{ filterInput().type }}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './menu-button.component';
|
||||
@@ -1,25 +0,0 @@
|
||||
<ui-icon-button
|
||||
cdkOverlayOrigin
|
||||
#trigger="cdkOverlayOrigin"
|
||||
(click)="isOpen.set(!isOpen())"
|
||||
>
|
||||
<ng-icon name="isaActionFilter"></ng-icon>
|
||||
</ui-icon-button>
|
||||
|
||||
<ng-template
|
||||
cdkConnectedOverlay
|
||||
[cdkConnectedOverlayOrigin]="trigger"
|
||||
[cdkConnectedOverlayOpen]="isOpen()"
|
||||
[cdkConnectedOverlayHasBackdrop]="true"
|
||||
[cdkConnectedOverlayBackdropClass]="'cdk-overlay-transparent-backdrop'"
|
||||
[cdkConnectedOverlayOffsetX]="-10"
|
||||
[cdkConnectedOverlayOffsetY]="18"
|
||||
(backdropClick)="closeOverlay()"
|
||||
>
|
||||
<filter-menu
|
||||
[appearance]="appearance()"
|
||||
(activeInputChange)="activeFilterInput.set($event)"
|
||||
></filter-menu>
|
||||
<filter-actions (applyFilter)="applyFilter.emit(); closeOverlay()">
|
||||
</filter-actions>
|
||||
</ng-template>
|
||||
@@ -1,67 +0,0 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
input,
|
||||
signal,
|
||||
computed,
|
||||
Component,
|
||||
ViewEncapsulation,
|
||||
inject,
|
||||
output,
|
||||
} from '@angular/core';
|
||||
import { FilterInput, FilterService } from '../core';
|
||||
import { FilterActionsComponent } from '../actions';
|
||||
import { IconButtonComponent } from '@isa/ui/buttons';
|
||||
import { OverlayModule } from '@angular/cdk/overlay';
|
||||
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
||||
import { isaActionFilter } from '@isa/icons';
|
||||
import { FilterMenuComponent } from './menu/menu.component';
|
||||
|
||||
@Component({
|
||||
selector: 'filter-menu-button',
|
||||
templateUrl: 'menu-button.component.html',
|
||||
styleUrls: ['./menu-button.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
standalone: true,
|
||||
imports: [
|
||||
FilterActionsComponent,
|
||||
IconButtonComponent,
|
||||
OverlayModule,
|
||||
NgIconComponent,
|
||||
FilterMenuComponent,
|
||||
],
|
||||
host: {
|
||||
'[class]': "['filter-menu-button', appearanceClass()]",
|
||||
},
|
||||
providers: [provideIcons({ isaActionFilter })],
|
||||
})
|
||||
export class FilterMenuButtonComponent {
|
||||
appearance = input<'main' | 'results'>('main');
|
||||
appearanceClass = computed(() => `filter-menu-button__${this.appearance()}`);
|
||||
|
||||
#filterService = inject(FilterService);
|
||||
|
||||
filterInputs = computed(() =>
|
||||
this.#filterService.inputs().filter((input) => input.group === 'filter'),
|
||||
);
|
||||
activeFilterInput = signal<FilterInput | undefined>(undefined);
|
||||
applyFilter = output<void>();
|
||||
|
||||
isOpen = signal<boolean>(false);
|
||||
|
||||
rollbackInputAfterClose() {
|
||||
const activeInput = this.activeFilterInput();
|
||||
if (activeInput) {
|
||||
const key = activeInput.key;
|
||||
if (key) {
|
||||
this.#filterService.rollbackInput(key);
|
||||
this.activeFilterInput.set(undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closeOverlay() {
|
||||
this.rollbackInputAfterClose();
|
||||
this.isOpen.set(false);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
@if (!activeInput()) {
|
||||
<button (click)="rollbackAllFilterInputs()" type="button">
|
||||
<span class="lib-return-filter__filter-button-label"> Alle abwählen </span>
|
||||
</button>
|
||||
|
||||
<div class="flex flex-col">
|
||||
@for (filterInput of filterInputs(); track filterInput.key) {
|
||||
<filter-input-button
|
||||
[appearance]="appearance()"
|
||||
[filterInput]="filterInput"
|
||||
(activeInputChange)="toggleActiveInput($event)"
|
||||
></filter-input-button>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<div class="flex flex-col">
|
||||
<button
|
||||
class="lib-return-filter__filter-button"
|
||||
(click)="toggleActiveInput(undefined)"
|
||||
type="button"
|
||||
>
|
||||
<ng-icon
|
||||
class="lib-return-filter__filter-button-icon"
|
||||
name="isaActionChevronUp"
|
||||
size="1.5rem"
|
||||
>
|
||||
</ng-icon>
|
||||
<span class="lib-return-filter__filter-button-label">{{
|
||||
activeInput().label
|
||||
}}</span>
|
||||
</button>
|
||||
|
||||
<filter-input-renderer
|
||||
[filterInput]="activeInput()"
|
||||
></filter-input-renderer>
|
||||
</div>
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
input,
|
||||
computed,
|
||||
Component,
|
||||
ViewEncapsulation,
|
||||
inject,
|
||||
model,
|
||||
} from '@angular/core';
|
||||
import { FilterInput, FilterService } from '../../core';
|
||||
import { OverlayModule } from '@angular/cdk/overlay';
|
||||
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
||||
import { isaActionChevronUp } from '@isa/icons';
|
||||
import { InputButtonComponent } from '../../input-button';
|
||||
import { InputRendererComponent } from '../../inputs';
|
||||
|
||||
@Component({
|
||||
selector: 'filter-menu',
|
||||
templateUrl: 'menu.component.html',
|
||||
styleUrls: ['./menu.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
standalone: true,
|
||||
imports: [
|
||||
InputButtonComponent,
|
||||
InputRendererComponent,
|
||||
OverlayModule,
|
||||
NgIconComponent,
|
||||
],
|
||||
host: {
|
||||
'[class]': "['filter-menu', appearanceClass()]",
|
||||
},
|
||||
providers: [provideIcons({ isaActionChevronUp })],
|
||||
})
|
||||
export class FilterMenuComponent {
|
||||
appearance = input<'main' | 'results'>('main');
|
||||
appearanceClass = computed(() => `filter-menu__${this.appearance()}`);
|
||||
|
||||
#filterService = inject(FilterService);
|
||||
|
||||
activeInput = model<FilterInput | undefined>(undefined);
|
||||
filterInputs = computed(() =>
|
||||
this.#filterService.inputs().filter((input) => input.group === 'filter'),
|
||||
);
|
||||
|
||||
toggleActiveInput(newActiveInput: FilterInput | undefined) {
|
||||
if (newActiveInput) {
|
||||
this.activeInput.set(newActiveInput);
|
||||
} else {
|
||||
this.rollbackInputAfterClose();
|
||||
this.activeInput.set(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
rollbackInputAfterClose() {
|
||||
const activeInput = this.activeInput();
|
||||
if (activeInput) {
|
||||
const key = activeInput.key;
|
||||
if (key) {
|
||||
this.#filterService.rollbackInput(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rollbackAllFilterInputs() {
|
||||
for (const input of this.filterInputs()) {
|
||||
this.#filterService.rollbackInput(input.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<ui-icon-button cdkOverlayOrigin #trigger="cdkOverlayOrigin" (click)="toggle()">
|
||||
<ng-icon name="isaActionFilter"></ng-icon>
|
||||
</ui-icon-button>
|
||||
|
||||
<ng-template
|
||||
cdkConnectedOverlay
|
||||
[cdkConnectedOverlayOrigin]="trigger"
|
||||
[cdkConnectedOverlayOpen]="open()"
|
||||
[cdkConnectedOverlayHasBackdrop]="true"
|
||||
[cdkConnectedOverlayBackdropClass]="'cdk-overlay-transparent-backdrop'"
|
||||
[cdkConnectedOverlayOffsetX]="-10"
|
||||
[cdkConnectedOverlayOffsetY]="18"
|
||||
(backdropClick)="toggle()"
|
||||
>
|
||||
<filter-filter-menu (applied)="applied.emit()" (reseted)="reseted.emit()"></filter-filter-menu>
|
||||
</ng-template>
|
||||
@@ -0,0 +1,87 @@
|
||||
import { OverlayModule } from '@angular/cdk/overlay';
|
||||
import { ChangeDetectionStrategy, Component, inject, input, model, output } from '@angular/core';
|
||||
import { IconButtonComponent } from '@isa/ui/buttons';
|
||||
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
||||
import { FilterMenuComponent } from './filter-menu.component';
|
||||
import { isaActionFilter } from '@isa/icons';
|
||||
import { FilterService } from '../../core';
|
||||
|
||||
/**
|
||||
* A button component that toggles the visibility of a filter menu.
|
||||
* It emits events when the menu is opened, closed, reset, or applied.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'filter-filter-menu-button',
|
||||
templateUrl: './filter-menu-button.component.html',
|
||||
styleUrls: ['./filter-menu-button.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [IconButtonComponent, OverlayModule, NgIconComponent, FilterMenuComponent],
|
||||
providers: [provideIcons({ isaActionFilter })],
|
||||
})
|
||||
export class FilterMenuButtonComponent {
|
||||
#filter = inject(FilterService);
|
||||
|
||||
/**
|
||||
* Tracks the open state of the filter menu.
|
||||
*/
|
||||
open = model<boolean>(false);
|
||||
|
||||
/**
|
||||
* Emits an event when the filter menu is closed.
|
||||
*/
|
||||
closed = output<void>();
|
||||
|
||||
/**
|
||||
* Emits an event when the filter menu is opened.
|
||||
*/
|
||||
opened = output<void>();
|
||||
|
||||
/**
|
||||
* Emits an event when the filter menu is reset.
|
||||
*/
|
||||
reseted = output<void>();
|
||||
|
||||
/**
|
||||
* Emits an event when the filter menu is applied.
|
||||
*/
|
||||
applied = output<void>();
|
||||
|
||||
/**
|
||||
* Determines whether to roll back changes when the menu is closed.
|
||||
*/
|
||||
rollbackOnClose = input<boolean>(false);
|
||||
|
||||
/**
|
||||
* Toggles the open state of the filter menu.
|
||||
* Emits `opened` or `closed` events based on the new state.
|
||||
*/
|
||||
toggle() {
|
||||
const open = this.open();
|
||||
this.open.set(!open);
|
||||
|
||||
if (open) {
|
||||
this.closed.emit();
|
||||
} else {
|
||||
this.opened.emit();
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
/**
|
||||
* Subscribes to the `closed` event to roll back changes if `rollbackOnClose` is true.
|
||||
*/
|
||||
this.closed.subscribe(() => {
|
||||
if (this.rollbackOnClose()) {
|
||||
this.#filter.rollback();
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Subscribes to the `applied` event to automatically close the menu.
|
||||
*/
|
||||
this.applied.subscribe(() => {
|
||||
this.toggle();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
@if (!activeInput()) {
|
||||
<button
|
||||
class="rounded-t-[1.25rem] px-6 py-5 border-b border-isa-neutral-300"
|
||||
(click)="filter.clear()"
|
||||
type="button"
|
||||
>
|
||||
<span class="isa-text-body-2-bold text-isa-neutral-500"> Alle abwählen </span>
|
||||
</button>
|
||||
@for (filterInput of filterInputs(); track filterInput.key) {
|
||||
<button
|
||||
type="button"
|
||||
class="px-6 py-5 border-b border-isa-neutral-300 inline-flex items-center gap-2 justify-between"
|
||||
(click)="activeInput.set(filterInput)"
|
||||
>
|
||||
<span class="text-isa-neutral-900 isa-text-body-2-regular">
|
||||
{{ filterInput.label }}
|
||||
</span>
|
||||
<ng-icon class="text-isa-neutral-900" name="isaActionChevronRight" size="1.5rem"></ng-icon>
|
||||
</button>
|
||||
}
|
||||
} @else {
|
||||
@let input = activeInput();
|
||||
<button
|
||||
class="rounded-t-[1.25rem] px-6 py-5 border-b border-isa-neutral-300"
|
||||
(click)="filter.rollbackInput(input!.key); activeInput.set(undefined)"
|
||||
type="button"
|
||||
>
|
||||
<span class="isa-text-body-2-bold text-isa-neutral-500"> {{ input!.label }} </span>
|
||||
</button>
|
||||
|
||||
<filter-input-renderer class="overflow-scroll" [filterInput]="input!"></filter-input-renderer>
|
||||
}
|
||||
|
||||
<filter-actions
|
||||
[inputKey]="activeInput()?.key"
|
||||
(applied)="applied.emit()"
|
||||
(reseted)="reseted.emit()"
|
||||
></filter-actions>
|
||||
@@ -0,0 +1,7 @@
|
||||
:host {
|
||||
@apply grid grid-flow-row;
|
||||
@apply bg-isa-white;
|
||||
@apply rounded-[1.25rem];
|
||||
@apply w-[14.3125rem] max-h-[33.5rem];
|
||||
box-shadow: 0px 0px 16px 0px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { ChangeDetectionStrategy, computed, Component, inject, model, output } from '@angular/core';
|
||||
import { FilterInput, FilterService } from '../../core';
|
||||
import { OverlayModule } from '@angular/cdk/overlay';
|
||||
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
||||
import { isaActionChevronLeft, isaActionChevronRight } from '@isa/icons';
|
||||
import { InputRendererComponent } from '../../inputs';
|
||||
import { FilterActionsComponent } from '../../actions';
|
||||
|
||||
/**
|
||||
* A component that renders a filter menu with input fields and actions.
|
||||
* It allows users to reset or apply filters and manages the active filter input.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'filter-filter-menu',
|
||||
templateUrl: 'filter-menu.component.html',
|
||||
styleUrls: ['./filter-menu.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [InputRendererComponent, OverlayModule, NgIconComponent, FilterActionsComponent],
|
||||
providers: [provideIcons({ isaActionChevronLeft, isaActionChevronRight })],
|
||||
})
|
||||
export class FilterMenuComponent {
|
||||
/**
|
||||
* The filter service used to manage filter inputs and their states.
|
||||
*/
|
||||
filter = inject(FilterService);
|
||||
|
||||
/**
|
||||
* Tracks the currently active filter input in the menu.
|
||||
*/
|
||||
activeInput = model<FilterInput | undefined>(undefined);
|
||||
|
||||
/**
|
||||
* A computed list of filter inputs belonging to the 'filter' group.
|
||||
*/
|
||||
filterInputs = computed(() => this.filter.inputs().filter((input) => input.group === 'filter'));
|
||||
|
||||
/**
|
||||
* Emits an event when the filter inputs are reset.
|
||||
*/
|
||||
reseted = output<void>();
|
||||
|
||||
/**
|
||||
* Emits an event when the filter inputs are applied.
|
||||
*/
|
||||
applied = output<void>();
|
||||
}
|
||||
2
libs/shared/filter/src/lib/menus/filter-menu/index.ts
Normal file
2
libs/shared/filter/src/lib/menus/filter-menu/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './filter-menu-button.component';
|
||||
export * from './filter-menu.component';
|
||||
2
libs/shared/filter/src/lib/menus/input-menu/index.ts
Normal file
2
libs/shared/filter/src/lib/menus/input-menu/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './input-menu-button.component';
|
||||
export * from './input-menu.component';
|
||||
@@ -0,0 +1,34 @@
|
||||
@let input = filterInput();
|
||||
<button
|
||||
class="filter-input-button__filter-button"
|
||||
[class.open]="open()"
|
||||
(click)="toggle()"
|
||||
type="button"
|
||||
cdkOverlayOrigin
|
||||
#trigger="cdkOverlayOrigin"
|
||||
>
|
||||
<span class="filter-input-button__filter-button-label">{{ input.label }}</span>
|
||||
<ng-icon
|
||||
class="filter-input-button__filter-button-icon"
|
||||
[name]="open() ? 'isaActionChevronUp' : 'isaActionChevronDown'"
|
||||
size="1.5rem"
|
||||
>
|
||||
</ng-icon>
|
||||
</button>
|
||||
|
||||
<ng-template
|
||||
cdkConnectedOverlay
|
||||
[cdkConnectedOverlayOrigin]="trigger"
|
||||
[cdkConnectedOverlayOpen]="open()"
|
||||
[cdkConnectedOverlayHasBackdrop]="true"
|
||||
[cdkConnectedOverlayBackdropClass]="'cdk-overlay-transparent-backdrop'"
|
||||
[cdkConnectedOverlayOffsetX]="-10"
|
||||
[cdkConnectedOverlayOffsetY]="18"
|
||||
(backdropClick)="toggle()"
|
||||
>
|
||||
<filter-input-menu
|
||||
[filterInput]="input"
|
||||
(applied)="applied.emit()"
|
||||
(reseted)="reseted.emit()"
|
||||
></filter-input-menu>
|
||||
</ng-template>
|
||||
@@ -0,0 +1,23 @@
|
||||
.filter-input-button__filter-button {
|
||||
@apply flex flex-row gap-2 items-center justify-center px-6 h-12 bg-isa-neutral-400 rounded-[3.125rem] border border-solid border-transparent;
|
||||
|
||||
.filter-input-button__filter-button-label {
|
||||
@apply text-isa-neutral-600 isa-text-body-2-bold;
|
||||
}
|
||||
|
||||
.filter-input-button__filter-button-icon {
|
||||
@apply text-isa-accent-blue;
|
||||
}
|
||||
|
||||
&.open {
|
||||
@apply bg-transparent border border-solid border-isa-neutral-900;
|
||||
|
||||
.filter-input-button__filter-button-label {
|
||||
@apply text-isa-neutral-900;
|
||||
}
|
||||
|
||||
.filter-input-button__filter-button-icon {
|
||||
@apply text-isa-neutral-900;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { ChangeDetectionStrategy, Component, input, model, output } from '@angular/core';
|
||||
import { FilterInput } from '../../core';
|
||||
import { OverlayModule } from '@angular/cdk/overlay';
|
||||
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
||||
import { isaActionChevronDown, isaActionChevronUp } from '@isa/icons';
|
||||
import { FilterInputMenuComponent } from './input-menu.component';
|
||||
|
||||
/**
|
||||
* A button component that toggles the visibility of an input menu for filtering.
|
||||
* It emits events when the menu is opened, closed, reset, or applied.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'filter-input-menu-button',
|
||||
templateUrl: './input-menu-button.component.html',
|
||||
styleUrls: ['./input-menu-button.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [OverlayModule, NgIconComponent, FilterInputMenuComponent],
|
||||
providers: [provideIcons({ isaActionChevronDown, isaActionChevronUp })],
|
||||
})
|
||||
export class FilterInputMenuButtonComponent {
|
||||
/**
|
||||
* Tracks the open state of the input menu.
|
||||
*/
|
||||
open = model<boolean>(false);
|
||||
|
||||
/**
|
||||
* The filter input configuration used to render the input menu.
|
||||
*/
|
||||
filterInput = input.required<FilterInput>();
|
||||
|
||||
/**
|
||||
* Emits an event when the input menu is closed.
|
||||
*/
|
||||
closed = output<void>();
|
||||
|
||||
/**
|
||||
* Emits an event when the input menu is opened.
|
||||
*/
|
||||
opened = output<void>();
|
||||
|
||||
/**
|
||||
* Emits an event when the input menu is reset.
|
||||
*/
|
||||
reseted = output<void>();
|
||||
|
||||
/**
|
||||
* Emits an event when the input menu is applied.
|
||||
*/
|
||||
applied = output<void>();
|
||||
|
||||
/**
|
||||
* Subscribes to the `applied` event to automatically close the menu.
|
||||
*/
|
||||
constructor() {
|
||||
this.applied.subscribe(() => {
|
||||
this.toggle();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the open state of the input menu.
|
||||
* Emits `opened` or `closed` events based on the new state.
|
||||
*/
|
||||
toggle() {
|
||||
const open = this.open();
|
||||
this.open.set(!open);
|
||||
|
||||
if (open) {
|
||||
this.closed.emit();
|
||||
} else {
|
||||
this.opened.emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<filter-input-renderer
|
||||
class="overflow-scroll"
|
||||
[filterInput]="filterInput()"
|
||||
></filter-input-renderer>
|
||||
<filter-actions
|
||||
[inputKey]="filterInput().key"
|
||||
(applied)="applied.emit()"
|
||||
(reseted)="reseted.emit()"
|
||||
></filter-actions>
|
||||
@@ -0,0 +1,3 @@
|
||||
:host {
|
||||
@apply grid grid-flow-row bg-isa-white rounded-[1.25rem] shadow-[0px,0px,16px,0px,rgba(0,0,0,0.15)] max-h-[32.3rem];
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { ChangeDetectionStrategy, Component, input, output } from '@angular/core';
|
||||
import { FilterInput } from '../../core';
|
||||
import { FilterActionsComponent } from '../../actions';
|
||||
import { InputRendererComponent } from '../../inputs/input-renderer';
|
||||
|
||||
/**
|
||||
* A component that renders a menu for filter input.
|
||||
* It provides actions to reset or apply the filter.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'filter-input-menu',
|
||||
templateUrl: './input-menu.component.html',
|
||||
styleUrls: ['./input-menu.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [FilterActionsComponent, InputRendererComponent],
|
||||
})
|
||||
export class FilterInputMenuComponent {
|
||||
/**
|
||||
* The filter input configuration used to render the input menu.
|
||||
*/
|
||||
filterInput = input.required<FilterInput>();
|
||||
|
||||
/**
|
||||
* Emits an event when the filter input is reset.
|
||||
*/
|
||||
reseted = output<void>();
|
||||
|
||||
/**
|
||||
* Emits an event when the filter input is applied.
|
||||
*/
|
||||
applied = output<void>();
|
||||
}
|
||||
11
package.json
11
package.json
@@ -86,7 +86,6 @@
|
||||
"@swc-node/register": "~1.9.1",
|
||||
"@swc/core": "~1.5.7",
|
||||
"@swc/helpers": "~0.5.11",
|
||||
"@types/jasmine": "~5.1.4",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/lodash": "^4.17.16",
|
||||
"@types/node": "18.16.9",
|
||||
@@ -98,20 +97,10 @@
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.2.3",
|
||||
"husky": "^9.1.7",
|
||||
"jasmine-core": "~5.4.0",
|
||||
"jasmine-marbles": "^0.9.2",
|
||||
"jasmine-spec-reporter": "~7.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"jest-environment-node": "^29.7.0",
|
||||
"jest-preset-angular": "~14.4.0",
|
||||
"karma": "~6.4.4",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
"karma-coverage": "^2.2.1",
|
||||
"karma-coverage-istanbul-reporter": "~3.0.3",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"karma-junit-reporter": "~2.0.1",
|
||||
"ng-mocks": "^14.13.4",
|
||||
"ng-swagger-gen": "^2.3.1",
|
||||
"nx": "20.4.6",
|
||||
|
||||
Reference in New Issue
Block a user