From da27745ebe0153a0b352d6222518f546d9ba8091 Mon Sep 17 00:00:00 2001 From: Lorenz Hilpert Date: Thu, 3 Apr 2025 17:29:42 +0200 Subject: [PATCH] Implemented return search feature with main and result components. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ✨ **Feature**: Added return search main and result components - 🎨 **Style**: Updated styles for return search components - 🛠️ **Refactor**: Modified routing for return search functionality - 📚 **Docs**: Updated documentation references in settings --- .vscode/settings.json | 6 +- apps/isa-app/src/app/app-routing.module.ts | 2 +- .../src/lib/main/main-page.component.css | 4 - libs/oms/feature/return-search/src/index.ts | 2 +- .../return-search-main.component.html | 27 +++ .../return-search-main.component.scss | 3 + .../return-search-main.component.ts | 57 +++++- .../return-search-result-item.component.html | 37 ++++ .../return-search-result-item.component.scss | 0 .../return-search-result-item.component.ts | 45 +++++ .../return-search-result.component.html | 64 +++++++ .../return-search-result.component.scss | 3 + .../return-search-result.component.ts | 171 +++++++++++++++++- .../src/lib/return-search.component.ts | 7 +- .../feature/return-search/src/lib/routes.ts | 12 +- .../lib/actions/filter-actions.component.ts | 4 +- .../filter/src/lib/core/filter.service.ts | 60 +++++- .../checkbox-input.component.html | 4 +- .../filter-menu-button.component.html | 11 +- .../filter-menu-button.component.ts | 13 +- .../filter-menu/filter-menu.component.html | 27 ++- .../input-menu-button.component.html | 1 - .../input-menu/input-menu-button.component.ts | 13 +- package.json | 8 +- 24 files changed, 536 insertions(+), 45 deletions(-) create mode 100644 libs/oms/feature/return-search/src/lib/return-search-result/return-search-result-item/return-search-result-item.component.html create mode 100644 libs/oms/feature/return-search/src/lib/return-search-result/return-search-result-item/return-search-result-item.component.scss create mode 100644 libs/oms/feature/return-search/src/lib/return-search-result/return-search-result-item/return-search-result-item.component.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index a11e0188e..7c1a60c0f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -37,7 +37,7 @@ "file": ".github/testing-instructions.md" }, { - "file": "docs/text-stack.md" + "file": "docs/tech-stack.md" }, { "file": "docs/guidelines/code-style.md" @@ -60,7 +60,7 @@ "file": ".github/testing-instructions.md" }, { - "file": "docs/text-stack.md" + "file": "docs/tech-stack.md" }, { "file": "docs/guidelines/code-style.md" @@ -77,7 +77,7 @@ "file": ".github/review-instructions.md" }, { - "file": "docs/text-stack.md" + "file": "docs/tech-stack.md" }, { "file": "docs/guidelines/code-style.md" diff --git a/apps/isa-app/src/app/app-routing.module.ts b/apps/isa-app/src/app/app-routing.module.ts index be53146e9..e547f3877 100644 --- a/apps/isa-app/src/app/app-routing.module.ts +++ b/apps/isa-app/src/app/app-routing.module.ts @@ -163,7 +163,7 @@ const routes: Routes = [ children: [ { path: 'return', - loadChildren: () => import('@feature/return/pages').then((m) => m.routes), + loadChildren: () => import('@isa/oms/feature/return-search').then((m) => m.routes), }, ], }, diff --git a/libs/feature/return/pages/src/lib/main/main-page.component.css b/libs/feature/return/pages/src/lib/main/main-page.component.css index ea7e7eabb..63c1ceeb7 100644 --- a/libs/feature/return/pages/src/lib/main/main-page.component.css +++ b/libs/feature/return/pages/src/lib/main/main-page.component.css @@ -1,7 +1,3 @@ :host { @apply flex flex-col pt-12 items-center; } - -.lib-return-main-page__loading-spinner { - @apply h-12 w-full flex items-center justify-center mb-12; -} diff --git a/libs/oms/feature/return-search/src/index.ts b/libs/oms/feature/return-search/src/index.ts index b283e35c7..ece2c025d 100644 --- a/libs/oms/feature/return-search/src/index.ts +++ b/libs/oms/feature/return-search/src/index.ts @@ -1 +1 @@ -export * from './lib/oms-feature-return-search/oms-feature-return-search.component'; +export * from './lib/routes'; diff --git a/libs/oms/feature/return-search/src/lib/return-search-main/return-search-main.component.html b/libs/oms/feature/return-search/src/lib/return-search-main/return-search-main.component.html index e69de29bb..0e169f790 100644 --- a/libs/oms/feature/return-search/src/lib/return-search-main/return-search-main.component.html +++ b/libs/oms/feature/return-search/src/lib/return-search-main/return-search-main.component.html @@ -0,0 +1,27 @@ +
+

Rückgabe starten

+

+ Scannen Sie den QR-Code auf der Rechnung oder suchen Sie den Beleg +
+ via Rechnungsnummer, E-Mail-Adresse oder Kundennamen +

+
+ + + +@if (entityPending()) { +
+ +
+} + +
+ @for (filterInput of filterInputs(); track filterInput.key) { + + + } +
diff --git a/libs/oms/feature/return-search/src/lib/return-search-main/return-search-main.component.scss b/libs/oms/feature/return-search/src/lib/return-search-main/return-search-main.component.scss index e69de29bb..63c1ceeb7 100644 --- a/libs/oms/feature/return-search/src/lib/return-search-main/return-search-main.component.scss +++ b/libs/oms/feature/return-search/src/lib/return-search-main/return-search-main.component.scss @@ -0,0 +1,3 @@ +:host { + @apply flex flex-col pt-12 items-center; +} diff --git a/libs/oms/feature/return-search/src/lib/return-search-main/return-search-main.component.ts b/libs/oms/feature/return-search/src/lib/return-search-main/return-search-main.component.ts index e1bc27b44..ea6120d35 100644 --- a/libs/oms/feature/return-search/src/lib/return-search-main/return-search-main.component.ts +++ b/libs/oms/feature/return-search/src/lib/return-search-main/return-search-main.component.ts @@ -1,9 +1,62 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, computed, inject } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { injectActivatedProcessId } from '@isa/core/process'; +import { ReturnSearchStatus, ReturnSearchStore } from '@isa/oms/data-access'; +import { + FilterService, + SearchBarInputComponent, + FilterInputMenuButtonComponent, +} from '@isa/shared/filter'; +import { IconButtonComponent } from '@isa/ui/buttons'; @Component({ selector: 'oms-feature-return-search-main', templateUrl: './return-search-main.component.html', styleUrls: ['./return-search-main.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, + imports: [SearchBarInputComponent, IconButtonComponent, FilterInputMenuButtonComponent], }) -export class ReturnSearchMainComponent {} +export class ReturnSearchMainComponent { + #route = inject(ActivatedRoute); + #router = inject(Router); + + private _processId = injectActivatedProcessId(); + private _filterService = inject(FilterService); + private _returnSearchStore = inject(ReturnSearchStore); + + private _entity = computed(() => { + const processId = this._processId(); + if (processId) { + return this._returnSearchStore.getEntity(processId); + } + return undefined; + }); + + entityPending = computed(() => { + return this._entity()?.status === ReturnSearchStatus.Pending; + }); + + filterInputs = computed(() => + this._filterService.inputs().filter((input) => input.group === 'filter'), + ); + + // TODO: Suche als Provider in FilterService auslagern (+ Cancel Search, + Fetching Status) + async onSearch() { + const processId = this._processId(); + if (processId) { + await this._updateQueryParams(); + this._returnSearchStore.search({ + processId, + params: this._filterService.toParams(), + }); + } + } + + private async _updateQueryParams() { + await this.#router.navigate([], { + queryParams: this._filterService.toParams(), + relativeTo: this.#route, + replaceUrl: true, + }); + } +} diff --git a/libs/oms/feature/return-search/src/lib/return-search-result/return-search-result-item/return-search-result-item.component.html b/libs/oms/feature/return-search/src/lib/return-search-result/return-search-result-item/return-search-result-item.component.html new file mode 100644 index 000000000..7e1566789 --- /dev/null +++ b/libs/oms/feature/return-search/src/lib/return-search-result/return-search-result-item/return-search-result-item.component.html @@ -0,0 +1,37 @@ + + +

{{ name() }}

+
+ + + Belegdatum + + + {{ receiptDate() | date: 'dd.MM.yy' }} + + + + + Rechnugsnr. + + {{ receiptNumber() }} + + + + Vorgangs-ID + + {{ orderNumber() }} + + + + + + Email + {{ email() }} + + + Anschrift + {{ address() }} + + +
diff --git a/libs/oms/feature/return-search/src/lib/return-search-result/return-search-result-item/return-search-result-item.component.scss b/libs/oms/feature/return-search/src/lib/return-search-result/return-search-result-item/return-search-result-item.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/libs/oms/feature/return-search/src/lib/return-search-result/return-search-result-item/return-search-result-item.component.ts b/libs/oms/feature/return-search/src/lib/return-search-result/return-search-result-item/return-search-result-item.component.ts new file mode 100644 index 000000000..83eed0b5f --- /dev/null +++ b/libs/oms/feature/return-search/src/lib/return-search-result/return-search-result-item/return-search-result-item.component.ts @@ -0,0 +1,45 @@ +import { DatePipe } from '@angular/common'; +import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core'; +import { ReceiptListItem } from '@isa/oms/data-access'; +import { ClientRowImports, ItemRowDataImports } from '@isa/ui/item-rows'; + +@Component({ + selector: 'oms-feature-return-search-result-item', + templateUrl: './return-search-result-item.component.html', + styleUrls: ['./return-search-result-item.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ClientRowImports, ItemRowDataImports, DatePipe], +}) +export class ReturnSearchResultItemComponent { + item = input.required(); + + name = computed(() => { + const firstName = this.item()?.billing?.person?.firstName; + const lastName = this.item()?.billing?.person?.lastName; + const buyerName = [lastName, firstName].filter((f) => !!f); + const organisation = [this.item()?.billing?.organisation?.name].filter((f) => !!f); + + return [organisation.join(), buyerName.join(' ')].filter((f) => !!f).join(' - '); + }); + + receiptDate = computed(() => { + return this.item()?.printedDate; + }); + + receiptNumber = computed(() => { + return this.item()?.receiptNumber; + }); + + orderNumber = computed(() => { + return this.item()?.orderNumber; + }); + + email = computed(() => { + return this.item()?.billing?.communicationDetails?.email; + }); + + address = computed(() => { + const address = this.item()?.billing?.address; + return address ? [address.zipCode, address.city].join(' ') : ''; + }); +} diff --git a/libs/oms/feature/return-search/src/lib/return-search-result/return-search-result.component.html b/libs/oms/feature/return-search/src/lib/return-search-result/return-search-result.component.html index e69de29bb..65dbf86dd 100644 --- a/libs/oms/feature/return-search/src/lib/return-search-result/return-search-result.component.html +++ b/libs/oms/feature/return-search/src/lib/return-search-result/return-search-result.component.html @@ -0,0 +1,64 @@ +
+ + +
+ + + @if (isMobileDevice()) { + + + + } @else { + + } +
+
+ +@if (isMobileDevice() && toggleOrderByToolbar()) { + +} + + + {{ entityHits() }} Einträge + + +@let items = entityItems(); +@if (items.length > 0) { +
+ @for (item of items; track item.id) { + @defer (on viewport) { + + + + } @placeholder { + +
+ +
+ } + } + @if (entityStatus() === ReturnSearchStatus.Pending) { +
+ +
+ } +
+} @else if (items.length === 0 && entityStatus() === ReturnSearchStatus.Pending) { +
+ +
+} @else if (entityStatus() !== ReturnSearchStatus.Idle) { + + +} diff --git a/libs/oms/feature/return-search/src/lib/return-search-result/return-search-result.component.scss b/libs/oms/feature/return-search/src/lib/return-search-result/return-search-result.component.scss index e69de29bb..8950d55b8 100644 --- a/libs/oms/feature/return-search/src/lib/return-search-result/return-search-result.component.scss +++ b/libs/oms/feature/return-search/src/lib/return-search-result/return-search-result.component.scss @@ -0,0 +1,3 @@ +:host { + @apply flex flex-col gap-4 w-full justify-start items-center; +} diff --git a/libs/oms/feature/return-search/src/lib/return-search-result/return-search-result.component.ts b/libs/oms/feature/return-search/src/lib/return-search-result/return-search-result.component.ts index 0ca39eb5a..027a0214c 100644 --- a/libs/oms/feature/return-search/src/lib/return-search-result/return-search-result.component.ts +++ b/libs/oms/feature/return-search/src/lib/return-search-result/return-search-result.component.ts @@ -1,9 +1,176 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + computed, + effect, + inject, + QueryList, + signal, + untracked, + viewChildren, +} from '@angular/core'; +import { ReturnOrderByListComponent } from '@feature/return/containers'; +import { injectActivatedProcessId } from '@isa/core/process'; + +import { ActivatedRoute, Router, RouterLink } from '@angular/router'; +import { + FilterMenuButtonComponent, + FilterService, + SearchBarInputComponent, +} from '@isa/shared/filter'; +import { IconButtonComponent } from '@isa/ui/buttons'; +import { EmptyStateComponent } from '@isa/ui/empty-state'; +import { restoreScrollPosition } from '@isa/core/scroll-position'; +import { Platform } from '@angular/cdk/platform'; +import { NgIconComponent, provideIcons } from '@ng-icons/core'; +import { isaActionSort } from '@isa/icons'; +import { ReturnSearchEntity, ReturnSearchStatus, ReturnSearchStore } from '@isa/oms/data-access'; +import { ReturnSearchResultItemComponent } from './return-search-result-item/return-search-result-item.component'; + +type EmptyState = { + title: string; + description: string; +}; @Component({ selector: 'oms-feature-return-search-result', templateUrl: './return-search-result.component.html', styleUrls: ['./return-search-result.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + RouterLink, + ReturnSearchResultItemComponent, + ReturnOrderByListComponent, + IconButtonComponent, + SearchBarInputComponent, + EmptyStateComponent, + NgIconComponent, + FilterMenuButtonComponent, + ], + providers: [provideIcons({ isaActionSort })], }) -export class ReturnSearchResultComponent {} +export class ReturnSearchResultComponent { + #route = inject(ActivatedRoute); + #router = inject(Router); + #platform = inject(Platform); + + private _processId = injectActivatedProcessId(); + private _returnSearchStore = inject(ReturnSearchStore); + private _filterService = inject(FilterService); + + ReturnSearchStatus = ReturnSearchStatus; + + filterInputs = computed(() => + this._filterService.inputs().filter((input) => input.group === 'filter'), + ); + + private _entity = computed(() => { + const processId = this._processId(); + if (processId) { + return this._returnSearchStore.getEntity(processId); + } + return undefined; + }); + + entityItems = computed(() => { + return this._entity()?.items ?? []; + }); + + entityHits = computed(() => { + return this._entity()?.hits ?? 0; + }); + + entityStatus = computed(() => { + return this._entity()?.status ?? ReturnSearchStatus.Idle; + }); + + emptyState = computed(() => { + return { + title: 'Keine Suchergebnisse', + description: 'Suchen Sie nach einer Rechnungsnummer oder Kundennamen.', + }; + }); + + listElements = viewChildren>('listElement'); + + isMobileDevice = signal(this.#platform.ANDROID || this.#platform.IOS); + toggleOrderByToolbar = signal(false); + + searchEffectFn = () => + effect(() => { + const processId = this._processId(); + const listLength = this.listElements().length; + + untracked(async () => { + if (processId) { + const entity = this._entity(); + if (entity) { + 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 + if (!isPending) { + this._reload({ processId, entity, listLength }); + } + } else { + // Init Search after F5 / Refresh Page / No Entity Available + await this._initSearch(); + } + } + }); + }); + + constructor() { + this.searchEffectFn(); + restoreScrollPosition(); + } + + // TODO: Suche als Provider in FilterService auslagern (+ Cancel Search, + Fetching Status) + async onSearch() { + const processId = this._processId(); + if (processId) { + await this._updateQueryParams(); + this._returnSearchStore.search({ + processId, + params: this._filterService.toParams(), + }); + } + } + + private async _updateQueryParams() { + return await this.#router.navigate([], { + queryParams: this._filterService.toParams(), + relativeTo: this.#route, + replaceUrl: true, + }); + } + + private async _reload({ + processId, + entity, + listLength, + }: { + processId: number; + entity: ReturnSearchEntity; + listLength: number; + }) { + const entityItemsLength = entity?.items?.length ?? 0; + const hits = entity?.hits ?? 0; + // Soll reloaden wenn man am Ende der Liste in der View angekommen ist und noch nicht alle Items insgesamt geladen wurden + if (listLength === entityItemsLength && hits !== entityItemsLength) { + await this._updateQueryParams(); + this._returnSearchStore.reload({ + processId, + params: this._filterService.toParams(), + }); + } + } + + private async _initSearch() { + const entities = this._returnSearchStore.entities(); + // For routing away from the list this entities?.length === 0 check is necessary, otherwise the init search would trigger again + if (entities?.length === 0) { + await this.onSearch(); + } + } +} diff --git a/libs/oms/feature/return-search/src/lib/return-search.component.ts b/libs/oms/feature/return-search/src/lib/return-search.component.ts index c2b5cfb68..3f387554f 100644 --- a/libs/oms/feature/return-search/src/lib/return-search.component.ts +++ b/libs/oms/feature/return-search/src/lib/return-search.component.ts @@ -3,13 +3,14 @@ import { toSignal } from '@angular/core/rxjs-interop'; import { ActivatedRoute, Router, RouterOutlet } from '@angular/router'; import { injectActivatedProcessId } from '@isa/core/process'; import { ReturnSearchStore } from '@isa/oms/data-access'; -import { FilterService } from '@isa/shared/filter'; +import { FilterService, provideQuerySettings } from '@isa/shared/filter'; @Component({ selector: 'oms-feature-return-search', template: ``, changeDetection: ChangeDetectionStrategy.OnPush, imports: [RouterOutlet], + providers: [provideQuerySettings(() => inject(ActivatedRoute).snapshot.data['querySettings'])], host: { '[class]': '"flex flex-col gap-5 isa-desktop:gap-6 items-center overflow-x-hidden"', }, @@ -54,11 +55,11 @@ export class ReturnSearchComponent { if (items) { if (items?.length === 1) { - return await this._navigateTo(['receipt', items[0].id.toString()]); + return await this._navigateTo(['receipts', items[0].id.toString()]); } if (items?.length >= 0) { - return await this._navigateTo(['results']); + return await this._navigateTo(['receipts']); } } diff --git a/libs/oms/feature/return-search/src/lib/routes.ts b/libs/oms/feature/return-search/src/lib/routes.ts index fa626c736..9f7277a7a 100644 --- a/libs/oms/feature/return-search/src/lib/routes.ts +++ b/libs/oms/feature/return-search/src/lib/routes.ts @@ -1,19 +1,21 @@ -import { ActivatedRoute, Routes } from '@angular/router'; +import { Routes } from '@angular/router'; import { ReturnSearchMainComponent } from './return-search-main/return-search-main.component'; import { ReturnSearchComponent } from './return-search.component'; import { querySettingsResolverFn } from './resolvers/query-settings.resolver-fn'; -import { provideQuerySettings } from '@isa/shared/filter'; -import { inject } from '@angular/core'; +import { ReturnSearchResultComponent } from './return-search-result/return-search-result.component'; export const routes: Routes = [ { path: '', component: ReturnSearchComponent, resolve: { querySettings: querySettingsResolverFn }, - providers: [provideQuerySettings(() => inject(ActivatedRoute).snapshot.data['querySettings'])], children: [ { path: '', component: ReturnSearchMainComponent }, - { path: 'returns', component: ReturnSearchMainComponent }, + { path: 'receipts', component: ReturnSearchResultComponent }, ], }, + { + path: 'process', + loadChildren: () => import('@isa/oms/feature/return-process').then((feat) => feat.routes), + }, ]; diff --git a/libs/shared/filter/src/lib/actions/filter-actions.component.ts b/libs/shared/filter/src/lib/actions/filter-actions.component.ts index 01d873428..5d305b8c0 100644 --- a/libs/shared/filter/src/lib/actions/filter-actions.component.ts +++ b/libs/shared/filter/src/lib/actions/filter-actions.component.ts @@ -51,7 +51,9 @@ export class FilterActionsComponent { const inputKey = this.inputKey(); if (!inputKey) { - this.filterService.reset(); + this.filterInputs().forEach((input) => { + this.filterService.resetInput(input.key); + }); } else { this.filterService.resetInput(inputKey); } diff --git a/libs/shared/filter/src/lib/core/filter.service.ts b/libs/shared/filter/src/lib/core/filter.service.ts index 5bff99cf5..49ffac2d8 100644 --- a/libs/shared/filter/src/lib/core/filter.service.ts +++ b/libs/shared/filter/src/lib/core/filter.service.ts @@ -1,7 +1,9 @@ -import { inject, Injectable, InjectionToken, Provider } from '@angular/core'; +import { computed, inject, Injectable, InjectionToken, Provider } from '@angular/core'; import { InputType, QuerySettingsDTO } from '../types'; import { getState, patchState, signalState } from '@ngrx/signals'; import { mapToFilter } from './mappings'; +import { isEqual } from 'lodash-es'; +import { FilterInput } from './schemas'; export const QUERY_SETTINGS = new InjectionToken('QuerySettings'); @@ -13,7 +15,9 @@ export function provideQuerySettings(factory: () => QuerySettingsDTO): Provider[ export class FilterService { readonly settings = inject(QUERY_SETTINGS); - #commitdState = mapToFilter(this.settings); + private readonly defaultState = mapToFilter(this.settings); + + #commitdState = structuredClone(this.defaultState); #state = signalState(this.#commitdState); @@ -65,6 +69,58 @@ export class FilterService { } } + /** + * Indicates whether the current state is the default state. + * This computed property checks if the current state is equal to the default state. + */ + isDefaultFilter = computed(() => { + const currentState = getState(this.#state); + return isEqual(currentState, this.defaultState); + }); + + isDefaultFilterInput(filterInput: FilterInput) { + const currentInputState = this.#state.inputs().find((i) => i.key === filterInput.key); + const defaultInputState = this.defaultState.inputs.find((i) => i.key === filterInput.key); + + return isEqual(currentInputState, defaultInputState); + } + + /** + * Indicates whether the current state is empty. + */ + isEmptyFilter = computed(() => { + const currentState = getState(this.#state); + return currentState.inputs.every((input) => { + if (input.type === InputType.Text) { + return !input.value; + } + + if (input.type === InputType.Checkbox) { + return !input.selected?.length; + } + + console.warn(`Input type not supported: ${input.type}`); + + return true; + }); + }); + + isEmptyFilterInput(filterInput: FilterInput) { + const currentInputState = this.#state.inputs().find((i) => i.key === filterInput.key); + + if (currentInputState?.type === InputType.Text) { + return !currentInputState.value; + } + + if (currentInputState?.type === InputType.Checkbox) { + return !currentInputState.selected?.length; + } + + console.warn(`Input type not supported: ${currentInputState?.type}`); + + return true; + } + /** * Reverts the current state to the last committed state. * This method restores the state by applying the previously saved committed state. diff --git a/libs/shared/filter/src/lib/inputs/checkbox-input/checkbox-input.component.html b/libs/shared/filter/src/lib/inputs/checkbox-input/checkbox-input.component.html index 8a698413d..30a5e6753 100644 --- a/libs/shared/filter/src/lib/inputs/checkbox-input/checkbox-input.component.html +++ b/libs/shared/filter/src/lib/inputs/checkbox-input/checkbox-input.component.html @@ -2,9 +2,7 @@ @let options = input().options; @if (inp && options) {
-