mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Merged PR 1867: #4769 Remi 3.0 - Remission – Scannen und Suchen
- feat(remission-list): Zwischencommit - feat(ui-input-controls): Adjusted Dropdown Styling and Added Droption Option Disable Class, Refs: #4769 - feat(remission): implement remission list feature shell and category select - Merge branch 'develop' into feature/4769-Remission-Liste
This commit is contained in:
committed by
Lorenz Hilpert
parent
4cf0ce820e
commit
d53540b8db
@@ -1 +1,3 @@
|
||||
export * from './lib/services';
|
||||
export * from './lib/models';
|
||||
export * from './lib/helpers';
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
RemissionListCategoryKey,
|
||||
RemissionListCategoryRoute,
|
||||
RemissionListCategoryRouteValue,
|
||||
} from '../models';
|
||||
import { getRemissionListCategoryKeyFromRoute } from './get-remission-list-category-key-from-route.helper';
|
||||
|
||||
describe('getRemissionListCategoryKeyFromRoute', () => {
|
||||
it('should return the correct key for each valid route value', () => {
|
||||
// Arrange & Act & Assert
|
||||
(
|
||||
Object.keys(RemissionListCategoryRoute) as RemissionListCategoryKey[]
|
||||
).forEach((key) => {
|
||||
const routeValue = RemissionListCategoryRoute[key];
|
||||
const result = getRemissionListCategoryKeyFromRoute(routeValue);
|
||||
expect(result).toBe(key);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return undefined for an invalid route value', () => {
|
||||
// Arrange
|
||||
const invalidRoute = 'not-a-real-route' as RemissionListCategoryRouteValue;
|
||||
|
||||
// Act
|
||||
const result = getRemissionListCategoryKeyFromRoute(invalidRoute);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for undefined input', () => {
|
||||
// Act
|
||||
const result = getRemissionListCategoryKeyFromRoute(
|
||||
undefined as unknown as RemissionListCategoryRouteValue,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should return undefined for null input', () => {
|
||||
// Act
|
||||
const result = getRemissionListCategoryKeyFromRoute(
|
||||
null as unknown as RemissionListCategoryRouteValue,
|
||||
);
|
||||
|
||||
// Assert
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not throw for any input type', () => {
|
||||
// Arrange
|
||||
const inputs = [
|
||||
undefined,
|
||||
null,
|
||||
123,
|
||||
{},
|
||||
[],
|
||||
'',
|
||||
Symbol('sym'),
|
||||
true,
|
||||
false,
|
||||
];
|
||||
|
||||
// Act & Assert
|
||||
inputs.forEach((input) => {
|
||||
expect(() =>
|
||||
getRemissionListCategoryKeyFromRoute(
|
||||
input as RemissionListCategoryRouteValue,
|
||||
),
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { RemissionListCategoryKey } from '../models';
|
||||
import {
|
||||
RemissionListCategoryRoute,
|
||||
RemissionListCategoryRouteValue,
|
||||
} from '../models/remission-list-category-routes';
|
||||
|
||||
export const getRemissionListCategoryKeyFromRoute = (
|
||||
route: RemissionListCategoryRouteValue,
|
||||
): RemissionListCategoryKey | undefined => {
|
||||
return (
|
||||
Object.keys(RemissionListCategoryRoute) as RemissionListCategoryKey[]
|
||||
).find((key) => RemissionListCategoryRoute[key] === route);
|
||||
};
|
||||
1
libs/remission/data-access/src/lib/helpers/index.ts
Normal file
1
libs/remission/data-access/src/lib/helpers/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './get-remission-list-category-key-from-route.helper';
|
||||
@@ -1,3 +1,5 @@
|
||||
export * from './remission-list-category';
|
||||
export * from './remission-list-category-routes';
|
||||
export * from './price-value';
|
||||
export * from './price';
|
||||
export * from './product';
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
export const RemissionListCategoryRoute = {
|
||||
Pflicht: '',
|
||||
Abteilung: 'department',
|
||||
Koerperlos: 'bodyless',
|
||||
} as const;
|
||||
|
||||
export type RemissionListCategoryRouteKey =
|
||||
keyof typeof RemissionListCategoryRoute;
|
||||
export type RemissionListCategoryRouteValue =
|
||||
(typeof RemissionListCategoryRoute)[RemissionListCategoryRouteKey];
|
||||
@@ -0,0 +1,13 @@
|
||||
export const RemissionListCategory = {
|
||||
Pflicht: 'Pflichtremission',
|
||||
Abteilung: 'Abteilungsremission',
|
||||
Koerperlos: 'Körperlose Remi',
|
||||
} as const;
|
||||
|
||||
export type RemissionListCategoryKey = keyof typeof RemissionListCategory;
|
||||
export const RemissionListCategoryKeys = Object.keys(
|
||||
RemissionListCategory,
|
||||
) as RemissionListCategoryKey[];
|
||||
|
||||
export type RemissionListCategory =
|
||||
(typeof RemissionListCategory)[keyof typeof RemissionListCategory];
|
||||
3
libs/remission/data-access/src/lib/services/index.ts
Normal file
3
libs/remission/data-access/src/lib/services/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './remission-instock.service';
|
||||
export * from './remission-product-group.service';
|
||||
export * from './remission-search.service';
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class RemissionInstockService {
|
||||
constructor() {}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class RemissionProductGroupService {
|
||||
constructor() {}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { RemissionListCategory, RemissionListCategoryKey } from '../models';
|
||||
import {
|
||||
RemissionListCategoryRoute,
|
||||
RemissionListCategoryRouteValue,
|
||||
} from '../models/remission-list-category-routes';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class RemissionSearchService {
|
||||
// 2xSettings + Pflicht + Abteilungsremi
|
||||
|
||||
remissionListCategories(): {
|
||||
key: RemissionListCategoryKey;
|
||||
value: RemissionListCategory;
|
||||
route: RemissionListCategoryRouteValue;
|
||||
}[] {
|
||||
return (
|
||||
Object.keys(RemissionListCategory) as RemissionListCategoryKey[]
|
||||
).map((key) => ({
|
||||
key,
|
||||
value: RemissionListCategory[key],
|
||||
route: RemissionListCategoryRoute[key],
|
||||
}));
|
||||
}
|
||||
|
||||
fetchList(): Promise<unknown> {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve({ data: 'Remission list data' });
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'remission-feature-remission-list-item',
|
||||
templateUrl: './remission-list-item.component.html',
|
||||
styleUrl: './remission-list-item.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class RemissionListItemComponent {}
|
||||
@@ -0,0 +1,17 @@
|
||||
<ui-dropdown
|
||||
class="remission-feature-remission-list-select__dropdown"
|
||||
[value]="selectedCategoryKey()"
|
||||
[appearance]="DropdownAppearance.Grey"
|
||||
(valueChange)="changeRemissionCategory($event)"
|
||||
data-which="remission-list-select-dropdown"
|
||||
[attr.data-what]="`remission-list-selected-value-${selectedCategoryKey()}`"
|
||||
>
|
||||
@for (kv of remissionListCategories; track kv.key) {
|
||||
<ui-dropdown-option
|
||||
[attr.data-what]="`remission-list-option-${kv.value}`"
|
||||
[disabled]="kv.value === RemissionListCategory.Koerperlos"
|
||||
[value]="kv.key"
|
||||
>{{ kv.value }}</ui-dropdown-option
|
||||
>
|
||||
}
|
||||
</ui-dropdown>
|
||||
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
inject,
|
||||
computed,
|
||||
} from '@angular/core';
|
||||
import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import {
|
||||
getRemissionListCategoryKeyFromRoute,
|
||||
RemissionListCategory,
|
||||
RemissionListCategoryKey,
|
||||
RemissionListCategoryKeys,
|
||||
RemissionListCategoryRoute,
|
||||
RemissionListCategoryRouteValue,
|
||||
RemissionSearchService,
|
||||
} from '@isa/remission/data-access';
|
||||
import {
|
||||
DropdownAppearance,
|
||||
DropdownButtonComponent,
|
||||
DropdownOptionComponent,
|
||||
} from '@isa/ui/input-controls';
|
||||
|
||||
@Component({
|
||||
selector: 'remission-feature-remission-list-select',
|
||||
templateUrl: './remission-list-select.component.html',
|
||||
styleUrl: './remission-list-select.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [DropdownButtonComponent, DropdownOptionComponent],
|
||||
})
|
||||
export class RemissionListSelectComponent {
|
||||
DropdownAppearance = DropdownAppearance;
|
||||
RemissionListCategory = RemissionListCategory;
|
||||
remissionSearchService = inject(RemissionSearchService);
|
||||
router = inject(Router);
|
||||
route = inject(ActivatedRoute);
|
||||
routeUrl = toSignal(this.route.url);
|
||||
|
||||
remissionListCategories =
|
||||
this.remissionSearchService.remissionListCategories();
|
||||
|
||||
selectedCategoryKey = computed(() => {
|
||||
const url = this.routeUrl();
|
||||
const path = url?.map((segment) => segment.path).join('/') ?? '';
|
||||
const defaultPath = RemissionListCategoryKeys[0]; // Default Path = 'Pflicht'
|
||||
return (
|
||||
getRemissionListCategoryKeyFromRoute(
|
||||
path as RemissionListCategoryRouteValue,
|
||||
) ?? defaultPath
|
||||
);
|
||||
});
|
||||
|
||||
async changeRemissionCategory(key: RemissionListCategoryKey | undefined) {
|
||||
if (!key) return;
|
||||
const route = RemissionListCategoryRoute[key];
|
||||
if (route) {
|
||||
await this.router.navigate([route], { relativeTo: this.route });
|
||||
} else {
|
||||
await this.router.navigate(['..'], { relativeTo: this.route });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<remission-feature-remission-start-card></remission-feature-remission-start-card>
|
||||
|
||||
<remission-feature-remission-list-select></remission-feature-remission-list-select>
|
||||
|
||||
<filter-controls-panel (triggerSearch)="search()"></filter-controls-panel>
|
||||
|
||||
<span
|
||||
class="text-isa-neutral-900 isa-text-body-2-regular self-start"
|
||||
data-what="result-count"
|
||||
>
|
||||
0 Einträge
|
||||
</span>
|
||||
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
inject,
|
||||
effect,
|
||||
} from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import {
|
||||
provideFilter,
|
||||
withQuerySettingsFactory,
|
||||
withQueryParamsSync,
|
||||
FilterControlsPanelComponent,
|
||||
FilterService,
|
||||
} from '@isa/shared/filter';
|
||||
import { injectRestoreScrollPosition } from '@isa/utils/scroll-position';
|
||||
import { createRemissionResource } from './resources/remission-list.resource';
|
||||
import { RemissionStartCardComponent } from './remission-start-card/remission-start-card.component';
|
||||
import { RemissionListSelectComponent } from './remission-list-select/remission-list-select.component';
|
||||
|
||||
function querySettingsFactory() {
|
||||
return inject(ActivatedRoute).snapshot.data['querySettings'];
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'remission-feature-remission-list',
|
||||
templateUrl: './remission-list.component.html',
|
||||
styleUrl: './remission-list.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
providers: [
|
||||
provideFilter(
|
||||
withQuerySettingsFactory(querySettingsFactory),
|
||||
withQueryParamsSync(),
|
||||
),
|
||||
],
|
||||
imports: [
|
||||
RemissionStartCardComponent,
|
||||
FilterControlsPanelComponent,
|
||||
RemissionListSelectComponent,
|
||||
],
|
||||
host: {
|
||||
'[class]':
|
||||
'"w-full flex flex-col gap-4 mt-5 isa-desktop:mt-6 overflow-x-hidden"',
|
||||
},
|
||||
})
|
||||
export class RemissionListComponent {
|
||||
/** Service for managing filters and search queries */
|
||||
#filterService = inject(FilterService);
|
||||
|
||||
/** Utility for restoring scroll position when returning to this view */
|
||||
restoreScrollPosition = injectRestoreScrollPosition();
|
||||
|
||||
remissionResource = createRemissionResource(() => 2);
|
||||
|
||||
constructor() {
|
||||
effect(() => {
|
||||
const res = this.remissionResource;
|
||||
const status = res.status();
|
||||
console.log('Remission Resource Status:', status, res.value());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates a search operation with the current filter settings
|
||||
* Navigates directly to the receipt if only one result is found
|
||||
*/
|
||||
search() {
|
||||
this.#filterService.commit();
|
||||
console.log(this.#filterService.query());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<div class="remission-feature-remission-start-card__title-container">
|
||||
<h2 class="isa-text-subtitle-1-regular">Remission</h2>
|
||||
|
||||
<p class="isa-text-body-1-regular">
|
||||
Starten Sie die Remission indem Sie den Warenbegleitschein erstellen.
|
||||
Anschließend können Sie Artikel hinzufügen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="remission-feature-remission-start-card__start-cta"
|
||||
data-which="start-remission"
|
||||
data-what="start-remission"
|
||||
uiButton
|
||||
color="brand"
|
||||
size="large"
|
||||
(click)="startRemission()"
|
||||
>
|
||||
Starten
|
||||
</button>
|
||||
@@ -0,0 +1,11 @@
|
||||
:host {
|
||||
@apply w-full flex flex-row gap-6 rounded-2xl bg-isa-neutral-400 p-6 justify-between;
|
||||
}
|
||||
|
||||
.remission-feature-remission-start-card__title-container {
|
||||
@apply flex flex-col gap-4 text-isa-neutral-900;
|
||||
}
|
||||
|
||||
.remission-feature-remission-start-card__start-cta {
|
||||
@apply h-12 w-40 justify-self-end;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { ButtonComponent } from '@isa/ui/buttons';
|
||||
|
||||
@Component({
|
||||
selector: 'remission-feature-remission-start-card',
|
||||
templateUrl: './remission-start-card.component.html',
|
||||
styleUrl: './remission-start-card.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [ButtonComponent],
|
||||
})
|
||||
export class RemissionStartCardComponent {
|
||||
startRemission() {
|
||||
console.log('Start');
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'remission-feature-remission-list',
|
||||
template: `<router-outlet></router-outlet>`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [RouterOutlet],
|
||||
host: {
|
||||
'[class]':
|
||||
'"flex flex-col gap-5 isa-desktop:gap-6 items-center overflow-x-hidden"',
|
||||
},
|
||||
})
|
||||
export class RemissionListComponent {}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { ResolveFn } from '@angular/router';
|
||||
import { QuerySettingsDTO } from '@generated/swagger/oms-api';
|
||||
import { ReturnSearchService } from '@isa/oms/data-access';
|
||||
|
||||
export const querySettingsDepartmentResolverFn: ResolveFn<
|
||||
QuerySettingsDTO
|
||||
> = () => {
|
||||
console.log('department');
|
||||
return inject(ReturnSearchService).querySettings();
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { ResolveFn } from '@angular/router';
|
||||
import { QuerySettingsDTO } from '@generated/swagger/oms-api';
|
||||
import { ReturnSearchService } from '@isa/oms/data-access';
|
||||
|
||||
export const querySettingsResolverFn: ResolveFn<QuerySettingsDTO> = () => {
|
||||
console.log('pflicht');
|
||||
return inject(ReturnSearchService).querySettings();
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import { inject, resource } from '@angular/core';
|
||||
import { RemissionSearchService } from '@isa/remission/data-access';
|
||||
|
||||
export const createRemissionResource = (params: () => number) => {
|
||||
const remissionSearchService = inject(RemissionSearchService);
|
||||
return resource({
|
||||
params,
|
||||
loader: async ({ abortSignal, params }) => {
|
||||
if (!params) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return await remissionSearchService.fetchList();
|
||||
//
|
||||
},
|
||||
});
|
||||
};
|
||||
@@ -1,9 +1,19 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { RemissionListComponent } from './resmission-list.component';
|
||||
import { RemissionListComponent } from './remission-list.component';
|
||||
import { querySettingsResolverFn } from './resolvers/query-settings.resolver-fn';
|
||||
import { querySettingsDepartmentResolverFn } from './resolvers/query-settings-department.resolver-fn';
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: RemissionListComponent,
|
||||
resolve: { querySettings: querySettingsResolverFn },
|
||||
data: { scrollPositionRestoration: true },
|
||||
},
|
||||
{
|
||||
path: 'department',
|
||||
component: RemissionListComponent,
|
||||
resolve: { querySettings: querySettingsDepartmentResolverFn },
|
||||
data: { scrollPositionRestoration: true },
|
||||
},
|
||||
];
|
||||
|
||||
@@ -5,3 +5,4 @@ export * from './lib/actions';
|
||||
export * from './lib/menus/filter-menu';
|
||||
export * from './lib/menus/input-menu';
|
||||
export * from './lib/order-by';
|
||||
export * from './lib/controls-panel';
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<div class="w-full flex flex-row justify-between items-start">
|
||||
<filter-search-bar-input
|
||||
class="flex flex-row gap-4 h-12"
|
||||
[appearance]="'results'"
|
||||
inputKey="qs"
|
||||
(triggerSearch)="triggerSearch.emit()"
|
||||
data-what="search-input"
|
||||
></filter-search-bar-input>
|
||||
|
||||
<div class="flex flex-row gap-4 items-center">
|
||||
<filter-filter-menu-button
|
||||
(applied)="triggerSearch.emit()"
|
||||
[rollbackOnClose]="true"
|
||||
></filter-filter-menu-button>
|
||||
|
||||
@if (mobileBreakpoint()) {
|
||||
<ui-icon-button
|
||||
type="button"
|
||||
(click)="showOrderByToolbarMobile.set(!showOrderByToolbarMobile())"
|
||||
[class.active]="showOrderByToolbarMobile()"
|
||||
data-what="sort-button-mobile"
|
||||
name="isaActionSort"
|
||||
></ui-icon-button>
|
||||
} @else {
|
||||
<filter-order-by-toolbar
|
||||
(toggled)="triggerSearch.emit()"
|
||||
></filter-order-by-toolbar>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (mobileBreakpoint() && showOrderByToolbarMobile()) {
|
||||
<filter-order-by-toolbar
|
||||
class="w-full"
|
||||
(toggled)="triggerSearch.emit()"
|
||||
data-what="sort-toolbar-mobile"
|
||||
></filter-order-by-toolbar>
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.filter-controls-panel {
|
||||
@apply flex flex-col gap-4;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
linkedSignal,
|
||||
output,
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import { SearchBarInputComponent } from '../inputs';
|
||||
import { FilterMenuButtonComponent } from '../menus/filter-menu';
|
||||
import { provideIcons } from '@ng-icons/core';
|
||||
import { isaActionFilter, isaActionSort } from '@isa/icons';
|
||||
import { IconButtonComponent } from '@isa/ui/buttons';
|
||||
import { OrderByToolbarComponent } from '../order-by';
|
||||
import { Breakpoint, breakpoint } from '@isa/ui/layout';
|
||||
|
||||
@Component({
|
||||
selector: 'filter-controls-panel',
|
||||
templateUrl: './controls-panel.component.html',
|
||||
styleUrls: ['./controls-panel.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
standalone: true,
|
||||
imports: [
|
||||
SearchBarInputComponent,
|
||||
FilterMenuButtonComponent,
|
||||
IconButtonComponent,
|
||||
OrderByToolbarComponent,
|
||||
],
|
||||
host: {
|
||||
'[class]': "['filter-controls-panel']",
|
||||
},
|
||||
providers: [provideIcons({ isaActionSort, isaActionFilter })],
|
||||
})
|
||||
export class FilterControlsPanelComponent {
|
||||
triggerSearch = output<void>();
|
||||
|
||||
/** Signal tracking whether the viewport is at tablet size or above */
|
||||
mobileBreakpoint = breakpoint([Breakpoint.Tablet]);
|
||||
|
||||
/**
|
||||
* Signal controlling the visibility of the order-by toolbar on mobile
|
||||
* Initially shows toolbar when NOT on mobile
|
||||
*/
|
||||
showOrderByToolbarMobile = linkedSignal(() => !this.mobileBreakpoint());
|
||||
}
|
||||
1
libs/shared/filter/src/lib/controls-panel/index.ts
Normal file
1
libs/shared/filter/src/lib/controls-panel/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './controls-panel.component';
|
||||
@@ -8,7 +8,11 @@ import {
|
||||
output,
|
||||
} from '@angular/core';
|
||||
import { FilterInput, FilterService } from '../../core';
|
||||
import { Overlay, CdkOverlayOrigin, CdkConnectedOverlay } from '@angular/cdk/overlay';
|
||||
import {
|
||||
Overlay,
|
||||
CdkOverlayOrigin,
|
||||
CdkConnectedOverlay,
|
||||
} from '@angular/cdk/overlay';
|
||||
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
||||
import { isaActionChevronDown, isaActionChevronUp } from '@isa/icons';
|
||||
import { FilterInputMenuComponent } from './input-menu.component';
|
||||
@@ -23,7 +27,12 @@ import { FilterInputMenuComponent } from './input-menu.component';
|
||||
templateUrl: './input-menu-button.component.html',
|
||||
styleUrls: ['./input-menu-button.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [NgIconComponent, FilterInputMenuComponent, CdkOverlayOrigin, CdkConnectedOverlay],
|
||||
imports: [
|
||||
NgIconComponent,
|
||||
FilterInputMenuComponent,
|
||||
CdkOverlayOrigin,
|
||||
CdkConnectedOverlay,
|
||||
],
|
||||
providers: [provideIcons({ isaActionChevronDown, isaActionChevronUp })],
|
||||
})
|
||||
export class FilterInputMenuButtonComponent {
|
||||
|
||||
@@ -49,12 +49,13 @@
|
||||
@apply bg-isa-neutral-500;
|
||||
}
|
||||
|
||||
&:active {
|
||||
@apply border-isa-accent-blue text-isa-accent-blue bg-isa-white;
|
||||
&:active,
|
||||
&.has-value {
|
||||
@apply border-isa-accent-blue text-isa-accent-blue;
|
||||
}
|
||||
|
||||
&.open {
|
||||
@apply border-isa-neutral-900 text-isa-neutral-900;
|
||||
@apply border-isa-neutral-900 text-isa-neutral-900 bg-isa-white;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,5 +96,15 @@
|
||||
&.selected {
|
||||
@apply text-isa-accent-blue;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
@apply text-isa-neutral-400;
|
||||
|
||||
&.active,
|
||||
&:focus,
|
||||
&:hover {
|
||||
@apply bg-isa-white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
effect,
|
||||
ElementRef,
|
||||
inject,
|
||||
Input,
|
||||
input,
|
||||
model,
|
||||
signal,
|
||||
@@ -24,8 +25,9 @@ import { DropdownAppearance } from './dropdown.types';
|
||||
selector: 'ui-dropdown-option',
|
||||
template: '<ng-content></ng-content>',
|
||||
host: {
|
||||
'[class]': '["ui-dropdown-option", activeClass(), selectedClass()]',
|
||||
role: 'option',
|
||||
'[class]':
|
||||
'["ui-dropdown-option", activeClass(), selectedClass(), disabledClass()]',
|
||||
'role': 'option',
|
||||
'[attr.aria-selected]': 'selected()',
|
||||
'[attr.tabindex]': '-1',
|
||||
'(click)': 'select()',
|
||||
@@ -37,6 +39,19 @@ export class DropdownOptionComponent<T> implements Highlightable {
|
||||
|
||||
active = signal(false);
|
||||
|
||||
readonly _disabled = signal<boolean>(false);
|
||||
|
||||
@Input()
|
||||
get disabled(): boolean {
|
||||
return this._disabled();
|
||||
}
|
||||
|
||||
set disabled(value: boolean) {
|
||||
this._disabled.set(value);
|
||||
}
|
||||
|
||||
disabledClass = computed(() => (this.disabled ? 'disabled' : ''));
|
||||
|
||||
activeClass = computed(() => (this.active() ? 'active' : ''));
|
||||
|
||||
setActiveStyles(): void {
|
||||
@@ -62,6 +77,9 @@ export class DropdownOptionComponent<T> implements Highlightable {
|
||||
value = input.required<T>();
|
||||
|
||||
select() {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
this.host.select(this);
|
||||
this.host.close();
|
||||
}
|
||||
@@ -83,8 +101,8 @@ export class DropdownOptionComponent<T> implements Highlightable {
|
||||
],
|
||||
host: {
|
||||
'[class]':
|
||||
'["ui-dropdown", appearanceClass(), isOpenClass(), disabledClass()]',
|
||||
role: 'combobox',
|
||||
'["ui-dropdown", appearanceClass(), isOpenClass(), disabledClass(), valueClass()]',
|
||||
'role': 'combobox',
|
||||
'aria-haspopup': 'listbox',
|
||||
'[attr.id]': 'id()',
|
||||
'[attr.tabindex]': 'disabled() ? -1 : tabIndex()',
|
||||
@@ -112,6 +130,8 @@ export class DropdownButtonComponent<T>
|
||||
|
||||
disabledClass = computed(() => (this.disabled() ? 'disabled' : ''));
|
||||
|
||||
valueClass = computed(() => (this.value() ? 'has-value' : ''));
|
||||
|
||||
id = input<string>();
|
||||
|
||||
value = model<T>();
|
||||
@@ -173,7 +193,9 @@ export class DropdownButtonComponent<T>
|
||||
this.keyManger?.destroy();
|
||||
this.keyManger = new ActiveDescendantKeyManager<
|
||||
DropdownOptionComponent<T>
|
||||
>(this.options()).withWrap();
|
||||
>(this.options())
|
||||
.withWrap()
|
||||
.skipPredicate((option) => option.disabled);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user