mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Merge branch 'release/4.1' into develop
This commit is contained in:
@@ -44,5 +44,8 @@ export class DataAccessError<TCode extends string, TData = void> extends Error {
|
||||
public readonly data: TData,
|
||||
) {
|
||||
super(message);
|
||||
// Set the prototype explicitly to maintain the correct prototype chain
|
||||
Object.setPrototypeOf(this, new.target.prototype);
|
||||
this.name = this.constructor.name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,3 +18,4 @@ export * from './value-tuple-sting-and-integer';
|
||||
export * from './create-remission';
|
||||
export * from './remission-item-source';
|
||||
export * from './receipt-complete-status';
|
||||
export * from './remission-response-args-error-message';
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// #5331 - Messages kommen bis auf AlreadyRemoved aus dem Backend
|
||||
export const RemissionResponseArgsErrorMessage = {
|
||||
AlreadyCompleted: 'Remission wurde bereits abgeschlossen',
|
||||
AlreadyRemitted: 'Artikel wurde bereits remittiert',
|
||||
AlreadyRemoved: 'Artikel konnte nicht entfernt werden',
|
||||
} as const;
|
||||
|
||||
export type RemissionResponseArgsErrorMessageKey =
|
||||
keyof typeof RemissionResponseArgsErrorMessage;
|
||||
export type RemissionResponseArgsErrorMessageValue =
|
||||
(typeof RemissionResponseArgsErrorMessage)[RemissionResponseArgsErrorMessageKey];
|
||||
@@ -40,16 +40,18 @@ import {
|
||||
calculateAvailableStock,
|
||||
RemissionReturnReceiptService,
|
||||
getStockToRemit,
|
||||
RemissionListType,
|
||||
RemissionResponseArgsErrorMessage,
|
||||
} from '@isa/remission/data-access';
|
||||
import { injectDialog } from '@isa/ui/dialog';
|
||||
import { injectDialog, injectFeedbackErrorDialog } from '@isa/ui/dialog';
|
||||
import { SearchItemToRemitDialogComponent } from '@isa/remission/shared/search-item-to-remit-dialog';
|
||||
import { RemissionListType } from '@isa/remission/data-access';
|
||||
import { RemissionReturnCardComponent } from './remission-return-card/remission-return-card.component';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import { RemissionProcessedHintComponent } from './remission-processed-hint/remission-processed-hint.component';
|
||||
import { RemissionListDepartmentElementsComponent } from './remission-list-department-elements/remission-list-department-elements.component';
|
||||
import { injectTabId } from '@isa/core/tabs';
|
||||
import { RemissionListEmptyStateComponent } from './remission-list-empty-state/remission-list-empty-state.component';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
function querySettingsFactory() {
|
||||
return inject(ActivatedRoute).snapshot.data['querySettings'];
|
||||
@@ -118,6 +120,7 @@ export class RemissionListComponent {
|
||||
activatedTabId = injectTabId();
|
||||
|
||||
searchItemToRemitDialog = injectDialog(SearchItemToRemitDialogComponent);
|
||||
errorDialog = injectFeedbackErrorDialog();
|
||||
|
||||
/**
|
||||
* FilterService instance for managing filter state and queries.
|
||||
@@ -391,34 +394,50 @@ export class RemissionListComponent {
|
||||
});
|
||||
|
||||
/**
|
||||
* Effect that handles the case when there are no items in the remission list after a search.
|
||||
* If the search was triggered by the user, it opens a dialog to search for items to remit.
|
||||
* If remission has already started, it adds the found items to the remission store and remits them.
|
||||
* If not, it navigates to the default remission list.
|
||||
* Effect that handles scenarios where a search yields no results.
|
||||
* If the search was user-initiated and returned no hits, it opens a dialog
|
||||
* to allow the user to add a new item to remit.
|
||||
* If only one hit is found and a remission is started, it selects that item automatically.
|
||||
* This effect runs whenever the remission or stock resource status changes,
|
||||
* or when the search term changes.
|
||||
* It ensures that the user is prompted appropriately based on their actions and the current state of the remission process.
|
||||
* It also checks if the remission is started or if the list type is 'Abteilung' to determine navigation behavior.
|
||||
* @see {@link
|
||||
* https://angular.dev/guide/effects} for more information on Angular effects.
|
||||
* @remarks This effect uses `untracked` to avoid unnecessary re-evaluations
|
||||
* when accessing certain signals.
|
||||
*/
|
||||
emptySearchResultEffect = effect(() => {
|
||||
const status = this.remissionResource.status();
|
||||
const stockStatus = this.inStockResource.status();
|
||||
const searchTerm: string | undefined = this.searchTerm();
|
||||
|
||||
if (status !== 'resolved') {
|
||||
if (status !== 'resolved' || stockStatus !== 'resolved') {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasItems = !!this.remissionResource.value()?.result?.length;
|
||||
|
||||
if (hasItems || !searchTerm || !this.hasValidSearchTerm()) {
|
||||
return;
|
||||
}
|
||||
this.#store.clearSelectedItems();
|
||||
|
||||
untracked(() => {
|
||||
if (!this.searchTriggeredByUser()) {
|
||||
const hits = this.hits();
|
||||
|
||||
// #5338 - Select item automatically if only one hit after search
|
||||
if (
|
||||
!!hits ||
|
||||
!searchTerm ||
|
||||
!this.hasValidSearchTerm() ||
|
||||
!this.searchTriggeredByUser()
|
||||
) {
|
||||
if (hits === 1 && this.remissionStarted()) {
|
||||
this.preselectRemissionItem(this.items()[0]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.searchItemToRemitDialog({
|
||||
data: {
|
||||
searchTerm,
|
||||
isDepartment: this.isDepartment(),
|
||||
},
|
||||
}).closed.subscribe(async (result) => {
|
||||
if (result) {
|
||||
@@ -432,9 +451,8 @@ export class RemissionListComponent {
|
||||
} else if (this.isDepartment()) {
|
||||
return await this.navigateToDefaultRemissionList();
|
||||
}
|
||||
|
||||
this.reloadListAndReturnData();
|
||||
}
|
||||
this.reloadListAndReturnData();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -493,17 +511,10 @@ export class RemissionListComponent {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.remitItemsState.set('success');
|
||||
this.reloadListAndReturnData();
|
||||
} catch (error) {
|
||||
this.#logger.error('Failed to remit items', error);
|
||||
this.remitItemsError.set(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: 'Artikel konnten nicht remittiert werden',
|
||||
);
|
||||
this.remitItemsState.set('error');
|
||||
await this.handleRemitItemsError(error);
|
||||
}
|
||||
|
||||
this.#store.clearSelectedItems();
|
||||
@@ -520,6 +531,62 @@ export class RemissionListComponent {
|
||||
this.#store.reloadReturn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-Selects a remission item if it has available stock and can be remitted.
|
||||
* Updates the remission store with the selected item.
|
||||
* @param item - The ReturnItem or ReturnSuggestion to select.
|
||||
* @returns void
|
||||
*/
|
||||
preselectRemissionItem(item: RemissionItem) {
|
||||
if (!!item && item.id) {
|
||||
const inStock = this.getAvailableStockForItem(item);
|
||||
const stockToRemit = getStockToRemit({
|
||||
remissionItem: item,
|
||||
remissionListType: this.selectedRemissionListType(),
|
||||
availableStock: inStock,
|
||||
});
|
||||
|
||||
if (inStock > 0 && stockToRemit > 0) {
|
||||
this.#store.selectRemissionItem(item.id, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles errors that occur during the remission of items.
|
||||
* Logs the error, displays an error dialog, and reloads the list and return data.
|
||||
* If the error indicates that the remission is already completed, it clears the remission state.
|
||||
* Sets the stateful button to 'error' to indicate the failure.
|
||||
* @param error - The error object caught during the remission process.
|
||||
* @returns A promise that resolves when the error handling is complete.
|
||||
*/
|
||||
async handleRemitItemsError(error: any) {
|
||||
this.#logger.error('Failed to remit items', error);
|
||||
|
||||
const errorMessage =
|
||||
error?.error?.message ??
|
||||
error?.message ??
|
||||
'Artikel konnten nicht remittiert werden';
|
||||
|
||||
this.remitItemsError.set(errorMessage);
|
||||
|
||||
await firstValueFrom(
|
||||
this.errorDialog({
|
||||
data: {
|
||||
errorMessage,
|
||||
},
|
||||
}).closed,
|
||||
);
|
||||
|
||||
if (errorMessage === RemissionResponseArgsErrorMessage.AlreadyCompleted) {
|
||||
this.#store.clearState();
|
||||
}
|
||||
|
||||
this.reloadListAndReturnData();
|
||||
|
||||
this.remitItemsState.set('error'); // Stateful-Button auf Error setzen
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigates to the default remission list based on the current activated tab ID.
|
||||
* This method is used to redirect the user to the remission list after completing or starting a remission.
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from '@angular/core';
|
||||
import {
|
||||
ReceiptItem,
|
||||
RemissionResponseArgsErrorMessage,
|
||||
RemissionReturnReceiptService,
|
||||
} from '@isa/remission/data-access';
|
||||
import { ProductFormatComponent } from '@isa/shared/product-foramt';
|
||||
@@ -20,6 +21,8 @@ import { IconButtonComponent } from '@isa/ui/buttons';
|
||||
import { provideIcons } from '@ng-icons/core';
|
||||
import { isaActionClose } from '@isa/icons';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import { injectFeedbackErrorDialog } from '@isa/ui/dialog';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
/**
|
||||
* Component for displaying a single receipt item within the remission return receipt details.
|
||||
@@ -55,6 +58,8 @@ export class RemissionReturnReceiptDetailsItemComponent {
|
||||
}));
|
||||
#returnReceiptService = inject(RemissionReturnReceiptService);
|
||||
|
||||
errorDialog = injectFeedbackErrorDialog();
|
||||
|
||||
/**
|
||||
* Required input for the receipt item to display.
|
||||
* Contains product information and quantity details.
|
||||
@@ -85,7 +90,7 @@ export class RemissionReturnReceiptDetailsItemComponent {
|
||||
|
||||
removing = signal(false);
|
||||
|
||||
removed = output<ReceiptItem>();
|
||||
reloadReturn = output<void>();
|
||||
|
||||
async remove() {
|
||||
if (this.removing()) {
|
||||
@@ -98,10 +103,25 @@ export class RemissionReturnReceiptDetailsItemComponent {
|
||||
returnId: this.returnId(),
|
||||
receiptItemId: this.item().id,
|
||||
});
|
||||
this.removed.emit(this.item());
|
||||
} catch (error) {
|
||||
this.#logger.error('Failed to remove item', error);
|
||||
await this.handleRemoveItemError(error);
|
||||
}
|
||||
this.reloadReturn.emit();
|
||||
this.removing.set(false);
|
||||
}
|
||||
|
||||
async handleRemoveItemError(error: any) {
|
||||
this.#logger.error('Failed to remove item', error);
|
||||
|
||||
const errorMessage =
|
||||
error?.error?.message ?? RemissionResponseArgsErrorMessage.AlreadyRemoved;
|
||||
|
||||
await firstValueFrom(
|
||||
this.errorDialog({
|
||||
data: {
|
||||
errorMessage,
|
||||
},
|
||||
}).closed,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
[removeable]="canRemoveItems()"
|
||||
[receiptId]="receiptId()"
|
||||
[returnId]="returnId()"
|
||||
(removed)="returnResource.reload()"
|
||||
(reloadReturn)="returnResource.reload()"
|
||||
></remi-remission-return-receipt-details-item>
|
||||
@if (!last) {
|
||||
<hr class="border-isa-neutral-300" />
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
DropdownButtonComponent,
|
||||
DropdownOptionComponent,
|
||||
} from '@isa/ui/input-controls';
|
||||
import { QuantityAndReason } from './select-remi-quantity-and-reason.component';
|
||||
import { QuantityAndReason } from './select-remi-quantity-and-reason-dialog.component';
|
||||
import { ReturnValue } from '@isa/common/data-access';
|
||||
import { provideIcons } from '@ng-icons/core';
|
||||
import { isaActionChevronDown, isaActionChevronUp } from '@isa/icons';
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
@if (item()) {
|
||||
<remi-select-remi-quantity-and-reason></remi-select-remi-quantity-and-reason>
|
||||
} @else {
|
||||
<button
|
||||
class="absolute top-4 right-[1.33rem]"
|
||||
type="button"
|
||||
uiTextButton
|
||||
size="small"
|
||||
color="subtle"
|
||||
(click)="close(undefined)"
|
||||
tabindex="-1"
|
||||
data-what="button"
|
||||
data-which="close-dialog"
|
||||
>
|
||||
Schließen
|
||||
</button>
|
||||
<remi-search-item-to-remit-list></remi-search-item-to-remit-list>
|
||||
}
|
||||
<button
|
||||
class="absolute top-4 right-[1.33rem]"
|
||||
type="button"
|
||||
uiTextButton
|
||||
size="small"
|
||||
color="subtle"
|
||||
(click)="close(undefined)"
|
||||
tabindex="-1"
|
||||
data-what="button"
|
||||
data-which="close-dialog"
|
||||
>
|
||||
Schließen
|
||||
</button>
|
||||
<remi-search-item-to-remit-list></remi-search-item-to-remit-list>
|
||||
|
||||
@@ -1,33 +1,23 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
effect,
|
||||
isSignal,
|
||||
linkedSignal,
|
||||
signal,
|
||||
Signal,
|
||||
} from '@angular/core';
|
||||
import { DialogContentDirective, NumberInputValidation } from '@isa/ui/dialog';
|
||||
import { Item } from '@isa/catalogue/data-access';
|
||||
import { TextButtonComponent } from '@isa/ui/buttons';
|
||||
import { provideIcons } from '@ng-icons/core';
|
||||
import { isaActionSearch } from '@isa/icons';
|
||||
import { SearchItemToRemitListComponent } from './search-item-to-remit-list.component';
|
||||
import { SelectRemiQuantityAndReasonComponent } from './select-remi-quantity-and-reason.component';
|
||||
import { Validators } from '@angular/forms';
|
||||
import { ReturnSuggestion, ReturnItem } from '@isa/remission/data-access';
|
||||
import { ReturnItem } from '@isa/remission/data-access';
|
||||
|
||||
export type SearchItemToRemitDialogData = {
|
||||
searchTerm: string | Signal<string>;
|
||||
isDepartment: boolean;
|
||||
};
|
||||
|
||||
export type SearchItemToRemitDialogResult =
|
||||
SearchItemToRemitDialogData extends { isDepartment: infer D }
|
||||
? D extends true
|
||||
? ReturnSuggestion
|
||||
: ReturnItem
|
||||
: never;
|
||||
// #5273, #4768 Fix - Nur ReturnItems sind zugelassen und dürfen zur Pflichtremission hinzugefügt werden
|
||||
export type SearchItemToRemitDialogResult = ReturnItem;
|
||||
|
||||
@Component({
|
||||
selector: 'remi-search-item-to-remit-dialog',
|
||||
@@ -35,11 +25,7 @@ export type SearchItemToRemitDialogResult =
|
||||
styleUrls: ['./search-item-to-remit-dialog.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [
|
||||
TextButtonComponent,
|
||||
SearchItemToRemitListComponent,
|
||||
SelectRemiQuantityAndReasonComponent,
|
||||
],
|
||||
imports: [TextButtonComponent, SearchItemToRemitListComponent],
|
||||
providers: [provideIcons({ isaActionSearch })],
|
||||
})
|
||||
export class SearchItemToRemitDialogComponent extends DialogContentDirective<
|
||||
@@ -51,35 +37,4 @@ export class SearchItemToRemitDialogComponent extends DialogContentDirective<
|
||||
? this.data.searchTerm()
|
||||
: this.data.searchTerm,
|
||||
);
|
||||
|
||||
item = signal<Item | undefined>(undefined);
|
||||
|
||||
itemEffect = effect(() => {
|
||||
const item = this.item();
|
||||
this.dialogRef.updateSize(item ? '36rem' : 'auto');
|
||||
|
||||
if (item) {
|
||||
this.dialog.title.set(`Dieser Artikel steht nicht auf der Remi Liste`);
|
||||
} else {
|
||||
this.dialog.title.set(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
quantityValidators: NumberInputValidation[] = [
|
||||
{
|
||||
errorKey: 'required',
|
||||
inputValidator: Validators.required,
|
||||
errorText: 'Bitte geben Sie eine Menge an.',
|
||||
},
|
||||
{
|
||||
errorKey: 'min',
|
||||
inputValidator: Validators.min(1),
|
||||
errorText: 'Die Menge muss mindestens 1 sein.',
|
||||
},
|
||||
{
|
||||
errorKey: 'max',
|
||||
inputValidator: Validators.max(1000),
|
||||
errorText: 'Die Menge darf höchstens 1000 sein.',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
name="isaActionSearch"
|
||||
color="brand"
|
||||
(click)="triggerSearch()"
|
||||
[pending]="searchResource.isLoading()"
|
||||
[pending]="searchResource.isLoading() || inStockResource.isLoading()"
|
||||
data-what="button"
|
||||
data-which="search-submit"
|
||||
></ui-icon-button>
|
||||
@@ -34,24 +34,23 @@
|
||||
<ng-icon size="1.5rem" name="isaOtherInfo"></ng-icon>
|
||||
</button>
|
||||
</p>
|
||||
<div class="overflow-y-auto">
|
||||
<div class="overflow-y-auto overflow-x-hidden">
|
||||
@if (searchResource.value()?.result; as items) {
|
||||
@for (item of items; track item.id) {
|
||||
@for (item of availableSearchResults(); track item.id) {
|
||||
@defer {
|
||||
@let inStock = getAvailableStockForItem(item);
|
||||
@if (inStock > 0) {
|
||||
<remi-search-item-to-remit
|
||||
[item]="item"
|
||||
[inStock]="inStock"
|
||||
data-what="list-item"
|
||||
data-which="search-result"
|
||||
[attr.data-item-id]="item.id"
|
||||
></remi-search-item-to-remit>
|
||||
}
|
||||
<remi-search-item-to-remit
|
||||
[item]="item"
|
||||
[inStock]="getAvailableStockForItem(item)"
|
||||
data-what="list-item"
|
||||
data-which="search-result"
|
||||
[attr.data-item-id]="item.id"
|
||||
></remi-search-item-to-remit>
|
||||
}
|
||||
}
|
||||
}
|
||||
@if (!hasItems() && !searchResource.isLoading()) {
|
||||
@if (
|
||||
!hasItems() && !searchResource.isLoading() && !inStockResource.isLoading()
|
||||
) {
|
||||
<ui-empty-state
|
||||
class="w-full justify-self-center"
|
||||
title="Keine Suchergebnisse"
|
||||
|
||||
@@ -57,6 +57,14 @@ export class SearchItemToRemitListComponent implements OnInit {
|
||||
|
||||
searchParams = signal<SearchByTermInput | undefined>(undefined);
|
||||
|
||||
availableSearchResults = computed(() => {
|
||||
return (
|
||||
this.searchResource.value()?.result?.filter((item) => {
|
||||
return this.getAvailableStockForItem(item) > 0;
|
||||
}) ?? []
|
||||
);
|
||||
});
|
||||
|
||||
inStockResource = createInStockResource(() => {
|
||||
return {
|
||||
itemIds:
|
||||
@@ -69,7 +77,7 @@ export class SearchItemToRemitListComponent implements OnInit {
|
||||
inStockResponseValue = computed(() => this.inStockResource.value());
|
||||
|
||||
hasItems = computed(() => {
|
||||
return (this.searchResource.value()?.result?.length ?? 0) > 0;
|
||||
return (this.availableSearchResults()?.length ?? 0) > 0;
|
||||
});
|
||||
|
||||
stockInfoMap = computed(() => {
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
type="button"
|
||||
uiTextButton
|
||||
color="strong"
|
||||
(click)="host.item.set(item())"
|
||||
(click)="openQuantityAndReasonDialog()"
|
||||
>
|
||||
Remimenge auswählen
|
||||
</button>
|
||||
|
||||
@@ -10,6 +10,9 @@ import { ProductInfoComponent } from '@isa/remission/shared/product';
|
||||
import { TextButtonComponent } from '@isa/ui/buttons';
|
||||
import { Breakpoint, breakpoint } from '@isa/ui/layout';
|
||||
import { SearchItemToRemitDialogComponent } from './search-item-to-remit-dialog.component';
|
||||
import { injectDialog } from '@isa/ui/dialog';
|
||||
import { SelectRemiQuantityAndReasonDialogComponent } from './select-remi-quantity-and-reason-dialog.component';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'remi-search-item-to-remit',
|
||||
@@ -20,6 +23,9 @@ import { SearchItemToRemitDialogComponent } from './search-item-to-remit-dialog.
|
||||
})
|
||||
export class SearchItemToRemitComponent {
|
||||
host = inject(SearchItemToRemitDialogComponent);
|
||||
quantityAndReasonDialog = injectDialog(
|
||||
SelectRemiQuantityAndReasonDialogComponent,
|
||||
);
|
||||
|
||||
item = input.required<Item>();
|
||||
inStock = input.required<number>();
|
||||
@@ -29,4 +35,22 @@ export class SearchItemToRemitComponent {
|
||||
productInfoOrientation = computed(() => {
|
||||
return this.desktopBreakpoint() ? 'vertical' : 'horizontal';
|
||||
});
|
||||
|
||||
async openQuantityAndReasonDialog() {
|
||||
if (this.item()) {
|
||||
const dialogRef = this.quantityAndReasonDialog({
|
||||
title: 'Dieser Artikel steht nicht auf der Remi Liste',
|
||||
data: {
|
||||
item: this.item(),
|
||||
inStock: this.inStock(),
|
||||
},
|
||||
width: '36rem',
|
||||
});
|
||||
const dialogResult = await firstValueFrom(dialogRef.closed);
|
||||
|
||||
if (dialogResult) {
|
||||
this.host.close(dialogResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,84 +1,94 @@
|
||||
<p class="text-isa-neutral-600 isa-text-body-1-regular">
|
||||
Wie viele Exemplare können remittiert werden?
|
||||
</p>
|
||||
<div class="flex flex-col gap-4">
|
||||
@for (
|
||||
quantityAndReason of quantitiesAndResons();
|
||||
track $index;
|
||||
let i = $index
|
||||
) {
|
||||
<div class="flex items-center gap-1">
|
||||
<remi-quantity-and-reason-item
|
||||
[position]="$index + 1"
|
||||
[quantityAndReason]="quantityAndReason"
|
||||
(quantityAndReasonChange)="setQuantityAndReason($index, $event)"
|
||||
class="flex-1"
|
||||
data-what="component"
|
||||
data-which="quantity-reason-item"
|
||||
[attr.data-position]="$index + 1"
|
||||
></remi-quantity-and-reason-item>
|
||||
@if (i > 0) {
|
||||
<ui-icon-button
|
||||
type="button"
|
||||
(click)="removeQuantityReasonItem($index)"
|
||||
data-what="button"
|
||||
data-which="remove-quantity"
|
||||
[attr.data-position]="$index + 1"
|
||||
name="isaActionClose"
|
||||
color="neutral"
|
||||
></ui-icon-button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center gap-2 -ml-5"
|
||||
uiTextButton
|
||||
color="strong"
|
||||
(click)="addQuantityReasonItem()"
|
||||
data-what="button"
|
||||
data-which="add-quantity"
|
||||
>
|
||||
<ng-icon name="isaActionPlus" size="1.5rem"></ng-icon>
|
||||
<div>Menge hinzufügen</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="text-isa-accent-red isa-text-body-1-regular">
|
||||
<span>
|
||||
@if (canReturnErrors(); as errors) {
|
||||
@for (error of errors; track $index) {
|
||||
{{ error }}
|
||||
}
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
color="secondary"
|
||||
size="large"
|
||||
uiButton
|
||||
(click)="host.item.set(undefined)"
|
||||
data-what="button"
|
||||
data-which="back"
|
||||
>
|
||||
Zurück
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
color="primary"
|
||||
size="large"
|
||||
uiButton
|
||||
[pending]="canAddToRemiListResource.isLoading()"
|
||||
[disabled]="canAddToRemiListResource.isLoading() || canReturn() === false"
|
||||
(click)="addToRemiList()"
|
||||
data-what="button"
|
||||
data-which="save-remission"
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
<remi-product-info
|
||||
[item]="{
|
||||
product: data.item.product,
|
||||
retailPrice: data.item.catalogAvailability.price,
|
||||
}"
|
||||
></remi-product-info>
|
||||
<div class="text-isa-neutral-900 flex flex-row items-center justify-end gap-8">
|
||||
<span class="isa-text-body-2-regular">Aktueller Bestand</span>
|
||||
<span class="isa-text-body-2-bold">{{ data.inStock }}x</span>
|
||||
</div>
|
||||
<p class="text-isa-neutral-600 isa-text-body-1-regular">
|
||||
Wie viele Exemplare können remittiert werden?
|
||||
</p>
|
||||
<div class="flex flex-col gap-4">
|
||||
@for (
|
||||
quantityAndReason of quantitiesAndResons();
|
||||
track $index;
|
||||
let i = $index
|
||||
) {
|
||||
<div class="flex items-center gap-1">
|
||||
<remi-quantity-and-reason-item
|
||||
[position]="$index + 1"
|
||||
[quantityAndReason]="quantityAndReason"
|
||||
(quantityAndReasonChange)="setQuantityAndReason($index, $event)"
|
||||
class="flex-1"
|
||||
data-what="component"
|
||||
data-which="quantity-reason-item"
|
||||
[attr.data-position]="$index + 1"
|
||||
></remi-quantity-and-reason-item>
|
||||
@if (i > 0) {
|
||||
<ui-icon-button
|
||||
type="button"
|
||||
(click)="removeQuantityReasonItem($index)"
|
||||
data-what="button"
|
||||
data-which="remove-quantity"
|
||||
[attr.data-position]="$index + 1"
|
||||
name="isaActionClose"
|
||||
color="neutral"
|
||||
></ui-icon-button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
class="flex items-center gap-2 -ml-5"
|
||||
uiTextButton
|
||||
color="strong"
|
||||
(click)="addQuantityReasonItem()"
|
||||
data-what="button"
|
||||
data-which="add-quantity"
|
||||
>
|
||||
<ng-icon name="isaActionPlus" size="1.5rem"></ng-icon>
|
||||
<div>Menge hinzufügen</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="text-isa-accent-red isa-text-body-1-regular">
|
||||
<span>
|
||||
@if (canReturnErrors(); as errors) {
|
||||
@for (error of errors; track $index) {
|
||||
{{ error }}
|
||||
}
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
color="secondary"
|
||||
size="large"
|
||||
uiButton
|
||||
(click)="close(undefined)"
|
||||
data-what="button"
|
||||
data-which="back"
|
||||
>
|
||||
Zurück
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
color="primary"
|
||||
size="large"
|
||||
uiButton
|
||||
[pending]="canAddToRemiListResource.isLoading()"
|
||||
[disabled]="canAddToRemiListResource.isLoading() || canReturn() === false"
|
||||
(click)="addToRemiList()"
|
||||
data-what="button"
|
||||
data-which="save-remission"
|
||||
>
|
||||
Speichern
|
||||
</button>
|
||||
</div>
|
||||
@@ -0,0 +1,3 @@
|
||||
:host {
|
||||
@apply grid grid-flow-row gap-6 h-full;
|
||||
}
|
||||
@@ -1,184 +1,196 @@
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
inject,
|
||||
model,
|
||||
resource,
|
||||
} from '@angular/core';
|
||||
import { SearchItemToRemitDialogComponent } from './search-item-to-remit-dialog.component';
|
||||
import { QuantityAndReasonItemComponent } from './quantity-and-reason-item.component';
|
||||
import {
|
||||
ButtonComponent,
|
||||
TextButtonComponent,
|
||||
IconButtonComponent,
|
||||
} from '@isa/ui/buttons';
|
||||
import { NgIcon, provideIcons } from '@ng-icons/core';
|
||||
import { isaActionPlus, isaActionClose } from '@isa/icons';
|
||||
import {
|
||||
RemissionSearchService,
|
||||
RemissionStore,
|
||||
ReturnItem,
|
||||
ReturnSuggestion,
|
||||
} from '@isa/remission/data-access';
|
||||
import { injectFeedbackDialog } from '@isa/ui/dialog';
|
||||
import { BatchResponseArgs } from '@isa/common/data-access';
|
||||
|
||||
export interface QuantityAndReason {
|
||||
quantity: number;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'remi-select-remi-quantity-and-reason',
|
||||
templateUrl: './select-remi-quantity-and-reason.component.html',
|
||||
styleUrls: ['./select-remi-quantity-and-reason.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [
|
||||
QuantityAndReasonItemComponent,
|
||||
TextButtonComponent,
|
||||
NgIcon,
|
||||
ButtonComponent,
|
||||
IconButtonComponent,
|
||||
],
|
||||
providers: [provideIcons({ isaActionPlus, isaActionClose })],
|
||||
})
|
||||
export class SelectRemiQuantityAndReasonComponent {
|
||||
#remiService = inject(RemissionSearchService);
|
||||
#remiStore = inject(RemissionStore);
|
||||
#feedbackDialog = injectFeedbackDialog();
|
||||
host = inject(SearchItemToRemitDialogComponent);
|
||||
|
||||
initialItem: QuantityAndReason = { quantity: 0, reason: '' };
|
||||
|
||||
quantitiesAndResons = model<QuantityAndReason[]>([this.initialItem]);
|
||||
|
||||
addQuantityReasonItem(): void {
|
||||
this.quantitiesAndResons.update((items) => [...items, this.initialItem]);
|
||||
}
|
||||
|
||||
removeQuantityReasonItem(position: number): void {
|
||||
const currentItems = this.quantitiesAndResons();
|
||||
if (currentItems.length > 1) {
|
||||
this.quantitiesAndResons.update((items) =>
|
||||
items.filter((_, index) => index !== position),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
setQuantityAndReason(position: number, qar: QuantityAndReason): void {
|
||||
this.quantitiesAndResons.update((items) => {
|
||||
const newItems = [...items];
|
||||
newItems[position] = qar;
|
||||
return newItems;
|
||||
});
|
||||
}
|
||||
|
||||
params = computed(() => {
|
||||
const items = this.quantitiesAndResons();
|
||||
const item = this.host.item();
|
||||
|
||||
if (!item) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return items.map((qar) => ({
|
||||
item,
|
||||
quantity: qar.quantity,
|
||||
reason: qar.reason,
|
||||
}));
|
||||
});
|
||||
|
||||
canAddToRemiListResource = resource({
|
||||
params: this.params,
|
||||
loader: async ({ params, abortSignal }) => {
|
||||
if (
|
||||
!this.host.item() ||
|
||||
params.some((p) => !p.reason) ||
|
||||
params.some((p) => !p.quantity)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const maxQuantityErrors = params.filter((p) => !(p.quantity <= 999));
|
||||
if (maxQuantityErrors.length > 0) {
|
||||
const errRes: BatchResponseArgs<ReturnItem> = {
|
||||
completed: false,
|
||||
error: true,
|
||||
total: maxQuantityErrors.length,
|
||||
invalidProperties: {
|
||||
quantity: 'Die Menge darf maximal 999 sein.',
|
||||
},
|
||||
};
|
||||
return errRes;
|
||||
}
|
||||
|
||||
return this.#remiService.canAddItemToRemiList(params, abortSignal);
|
||||
},
|
||||
});
|
||||
|
||||
canReturn = computed(() => {
|
||||
const results = this.canAddToRemiListResource.value();
|
||||
|
||||
if (!results) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (results.failed && results.failed.length > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
results.successful &&
|
||||
results.successful.length === this.quantitiesAndResons().length
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
canReturnErrors = computed(() => {
|
||||
const results = this.canAddToRemiListResource.value();
|
||||
if (results?.invalidProperties) {
|
||||
return Object.values(results.invalidProperties);
|
||||
}
|
||||
|
||||
if (!results?.failed) {
|
||||
return [];
|
||||
}
|
||||
return results.failed.map((item) =>
|
||||
item.invalidProperties
|
||||
? Object.values(item.invalidProperties).join(', ')
|
||||
: [],
|
||||
) as string[];
|
||||
});
|
||||
|
||||
async addToRemiList() {
|
||||
const canAddValue = this.canAddToRemiListResource.value();
|
||||
|
||||
if (!canAddValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (canAddValue.failed?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// #5273, #4768 Fix - Items dürfen nur zur Pflichtremission hinzugefügt werden
|
||||
const result: Array<ReturnItem> = await this.#remiService.addToList(
|
||||
this.params(),
|
||||
);
|
||||
|
||||
this.#feedbackDialog({
|
||||
data: {
|
||||
message: this.#remiStore.remissionStarted()
|
||||
? 'Wurde zum Warenbegleitschein hinzugefügt'
|
||||
: 'Wurde zur Remi Liste hinzugefügt',
|
||||
},
|
||||
});
|
||||
|
||||
this.host.close(result);
|
||||
}
|
||||
}
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
computed,
|
||||
inject,
|
||||
model,
|
||||
resource,
|
||||
} from '@angular/core';
|
||||
import { QuantityAndReasonItemComponent } from './quantity-and-reason-item.component';
|
||||
import {
|
||||
ButtonComponent,
|
||||
TextButtonComponent,
|
||||
IconButtonComponent,
|
||||
} from '@isa/ui/buttons';
|
||||
import { NgIcon, provideIcons } from '@ng-icons/core';
|
||||
import { isaActionPlus, isaActionClose } from '@isa/icons';
|
||||
import {
|
||||
RemissionSearchService,
|
||||
RemissionStore,
|
||||
ReturnItem,
|
||||
} from '@isa/remission/data-access';
|
||||
import { DialogContentDirective, injectFeedbackDialog } from '@isa/ui/dialog';
|
||||
import { BatchResponseArgs } from '@isa/common/data-access';
|
||||
import { Item } from '@isa/catalogue/data-access';
|
||||
import { ProductInfoComponent } from '@isa/remission/shared/product';
|
||||
|
||||
export type SelectRemiQuantityAndReasonDialogData = {
|
||||
item: Item;
|
||||
inStock: number;
|
||||
};
|
||||
|
||||
export type SelectRemiQuantityAndReasonDialogResult =
|
||||
| undefined
|
||||
| Array<ReturnItem>;
|
||||
|
||||
export interface QuantityAndReason {
|
||||
quantity: number;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'remi-select-remi-quantity-and-reason-dialog',
|
||||
templateUrl: './select-remi-quantity-and-reason-dialog.component.html',
|
||||
styleUrls: ['./select-remi-quantity-and-reason-dialog.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [
|
||||
QuantityAndReasonItemComponent,
|
||||
TextButtonComponent,
|
||||
NgIcon,
|
||||
ButtonComponent,
|
||||
IconButtonComponent,
|
||||
ProductInfoComponent,
|
||||
],
|
||||
providers: [provideIcons({ isaActionPlus, isaActionClose })],
|
||||
})
|
||||
export class SelectRemiQuantityAndReasonDialogComponent extends DialogContentDirective<
|
||||
SelectRemiQuantityAndReasonDialogData,
|
||||
SelectRemiQuantityAndReasonDialogResult
|
||||
> {
|
||||
#remiService = inject(RemissionSearchService);
|
||||
#remiStore = inject(RemissionStore);
|
||||
#feedbackDialog = injectFeedbackDialog();
|
||||
|
||||
initialItem: QuantityAndReason = { quantity: 0, reason: '' };
|
||||
|
||||
quantitiesAndResons = model<QuantityAndReason[]>([this.initialItem]);
|
||||
|
||||
addQuantityReasonItem(): void {
|
||||
this.quantitiesAndResons.update((items) => [...items, this.initialItem]);
|
||||
}
|
||||
|
||||
removeQuantityReasonItem(position: number): void {
|
||||
const currentItems = this.quantitiesAndResons();
|
||||
if (currentItems.length > 1) {
|
||||
this.quantitiesAndResons.update((items) =>
|
||||
items.filter((_, index) => index !== position),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
setQuantityAndReason(position: number, qar: QuantityAndReason): void {
|
||||
this.quantitiesAndResons.update((items) => {
|
||||
const newItems = [...items];
|
||||
newItems[position] = qar;
|
||||
return newItems;
|
||||
});
|
||||
}
|
||||
|
||||
params = computed(() => {
|
||||
const items = this.quantitiesAndResons();
|
||||
const item = this.data.item;
|
||||
|
||||
if (!item) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return items.map((qar) => ({
|
||||
item,
|
||||
quantity: qar.quantity,
|
||||
reason: qar.reason,
|
||||
}));
|
||||
});
|
||||
|
||||
canAddToRemiListResource = resource({
|
||||
params: this.params,
|
||||
loader: async ({ params, abortSignal }) => {
|
||||
if (
|
||||
!this.data.item ||
|
||||
params.some((p) => !p.reason) ||
|
||||
params.some((p) => !p.quantity)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const maxQuantityErrors = params.filter((p) => !(p.quantity <= 999));
|
||||
if (maxQuantityErrors.length > 0) {
|
||||
const errRes: BatchResponseArgs<ReturnItem> = {
|
||||
completed: false,
|
||||
error: true,
|
||||
total: maxQuantityErrors.length,
|
||||
invalidProperties: {
|
||||
quantity: 'Die Menge darf maximal 999 sein.',
|
||||
},
|
||||
};
|
||||
return errRes;
|
||||
}
|
||||
|
||||
return this.#remiService.canAddItemToRemiList(params, abortSignal);
|
||||
},
|
||||
});
|
||||
|
||||
canReturn = computed(() => {
|
||||
const results = this.canAddToRemiListResource.value();
|
||||
|
||||
if (!results) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (results.failed && results.failed.length > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
results.successful &&
|
||||
results.successful.length === this.quantitiesAndResons().length
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
canReturnErrors = computed(() => {
|
||||
const results = this.canAddToRemiListResource.value();
|
||||
if (results?.invalidProperties) {
|
||||
return Object.values(results.invalidProperties);
|
||||
}
|
||||
|
||||
if (!results?.failed) {
|
||||
return [];
|
||||
}
|
||||
return results.failed.map((item) =>
|
||||
item.invalidProperties
|
||||
? Object.values(item.invalidProperties).join(', ')
|
||||
: [],
|
||||
) as string[];
|
||||
});
|
||||
|
||||
async addToRemiList() {
|
||||
const canAddValue = this.canAddToRemiListResource.value();
|
||||
|
||||
if (!canAddValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (canAddValue.failed?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// #5273, #4768 Fix - Items dürfen nur zur Pflichtremission hinzugefügt werden
|
||||
const result: Array<ReturnItem> = await this.#remiService.addToList(
|
||||
this.params(),
|
||||
);
|
||||
|
||||
this.#feedbackDialog({
|
||||
data: {
|
||||
message: this.#remiStore.remissionStarted()
|
||||
? 'Wurde zum Warenbegleitschein hinzugefügt'
|
||||
: 'Wurde zur Remi Liste hinzugefügt',
|
||||
},
|
||||
});
|
||||
|
||||
this.close(result);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
:host {
|
||||
@apply grid grid-flow-row gap-6;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<div class="w-full flex flex-col gap-4 items-center justify-center">
|
||||
<span
|
||||
class="bg-isa-accent-red rounded-[6.25rem] flex flex-row items-center justify-center p-3"
|
||||
>
|
||||
<ng-icon
|
||||
class="text-isa-white"
|
||||
size="1.5rem"
|
||||
name="isaActionClose"
|
||||
></ng-icon>
|
||||
</span>
|
||||
<p
|
||||
class="isa-text-body-1-bold text-isa-neutral-900"
|
||||
data-what="error-message"
|
||||
>
|
||||
{{ data.errorMessage }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,56 @@
|
||||
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
|
||||
import {
|
||||
FeedbackErrorDialogComponent,
|
||||
FeedbackErrorDialogData,
|
||||
} from './feedback-error-dialog.component';
|
||||
import { DialogRef, DIALOG_DATA } from '@angular/cdk/dialog';
|
||||
import { NgIcon } from '@ng-icons/core';
|
||||
import { DialogComponent } from '../dialog.component';
|
||||
|
||||
// Test suite for FeedbackErrorDialogComponent
|
||||
describe('FeedbackErrorDialogComponent', () => {
|
||||
let spectator: Spectator<FeedbackErrorDialogComponent>;
|
||||
const mockData: FeedbackErrorDialogData = {
|
||||
errorMessage: 'Something went wrong',
|
||||
};
|
||||
|
||||
const createComponent = createComponentFactory({
|
||||
component: FeedbackErrorDialogComponent,
|
||||
imports: [NgIcon],
|
||||
providers: [
|
||||
{
|
||||
provide: DialogRef,
|
||||
useValue: { close: jest.fn() },
|
||||
},
|
||||
{
|
||||
provide: DIALOG_DATA,
|
||||
useValue: mockData,
|
||||
},
|
||||
{
|
||||
provide: DialogComponent,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
spectator = createComponent();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(spectator.component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should display the error message passed in data', () => {
|
||||
const messageElement = spectator.query('[data-what="error-message"]');
|
||||
expect(messageElement).toHaveText('Something went wrong');
|
||||
});
|
||||
|
||||
it('should render the close icon', () => {
|
||||
// The icon should be present with isaActionClose
|
||||
const iconElement = spectator.query('ng-icon');
|
||||
expect(iconElement).toBeTruthy();
|
||||
expect(iconElement).toHaveAttribute('name', 'isaActionClose');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { DialogContentDirective } from '../dialog-content.directive';
|
||||
import { NgIcon, provideIcons } from '@ng-icons/core';
|
||||
import { isaActionClose } from '@isa/icons';
|
||||
|
||||
/**
|
||||
* Input data for the error message dialog
|
||||
*/
|
||||
export interface FeedbackErrorDialogData {
|
||||
/** The Error message text to display in the dialog */
|
||||
errorMessage: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple feedback dialog component that displays an error message and an error icon.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ui-feedback-error-dialog',
|
||||
templateUrl: './feedback-error-dialog.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [NgIcon],
|
||||
providers: [provideIcons({ isaActionClose })],
|
||||
host: {
|
||||
'[class]': '["ui-feedback-error-dialog"]',
|
||||
},
|
||||
})
|
||||
export class FeedbackErrorDialogComponent extends DialogContentDirective<
|
||||
FeedbackErrorDialogData,
|
||||
void
|
||||
> {}
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
injectTextInputDialog,
|
||||
injectNumberInputDialog,
|
||||
injectConfirmationDialog,
|
||||
injectFeedbackErrorDialog,
|
||||
} from './injects';
|
||||
import { MessageDialogComponent } from './message-dialog/message-dialog.component';
|
||||
import { DialogComponent } from './dialog.component';
|
||||
@@ -17,6 +18,7 @@ import { TextInputDialogComponent } from './text-input-dialog/text-input-dialog.
|
||||
import { FeedbackDialogComponent } from './feedback-dialog/feedback-dialog.component';
|
||||
import { NumberInputDialogComponent } from './number-input-dialog/number-input-dialog.component';
|
||||
import { ConfirmationDialogComponent } from './confirmation-dialog/confirmation-dialog.component';
|
||||
import { FeedbackErrorDialogComponent } from './feedback-error-dialog/feedback-error-dialog.component';
|
||||
|
||||
// Test component extending DialogContentDirective for testing
|
||||
@Component({ template: '' })
|
||||
@@ -290,4 +292,23 @@ describe('Dialog Injects', () => {
|
||||
expect(injector.get(DIALOG_CONTENT)).toBe(ConfirmationDialogComponent);
|
||||
});
|
||||
});
|
||||
|
||||
describe('injectFeedbackErrorDialog', () => {
|
||||
it('should create a dialog injector for FeedbackErrorDialogComponent', () => {
|
||||
// Act
|
||||
const openFeedbackErrorDialog = TestBed.runInInjectionContext(() =>
|
||||
injectFeedbackErrorDialog(),
|
||||
);
|
||||
openFeedbackErrorDialog({
|
||||
data: {
|
||||
errorMessage: 'Test error message',
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const callOptions = mockDialogOpen.mock.calls[0][1];
|
||||
const injector = callOptions.injector;
|
||||
expect(injector.get(DIALOG_CONTENT)).toBe(FeedbackErrorDialogComponent);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,6 +21,10 @@ import {
|
||||
ConfirmationDialogComponent,
|
||||
ConfirmationDialogData,
|
||||
} from './confirmation-dialog/confirmation-dialog.component';
|
||||
import {
|
||||
FeedbackErrorDialogComponent,
|
||||
FeedbackErrorDialogData,
|
||||
} from './feedback-error-dialog/feedback-error-dialog.component';
|
||||
|
||||
export interface InjectDialogOptions {
|
||||
/** Optional title override for the dialog */
|
||||
@@ -173,3 +177,17 @@ export const injectFeedbackDialog = (
|
||||
classList: ['gap-0'],
|
||||
...options,
|
||||
});
|
||||
|
||||
/**
|
||||
* Convenience function that returns a pre-configured FeedbackErrorDialog injector
|
||||
* @returns A function to open a feedback error dialog
|
||||
*/
|
||||
export const injectFeedbackErrorDialog = (
|
||||
options?: OpenDialogOptions<FeedbackErrorDialogData>,
|
||||
) =>
|
||||
injectDialog(FeedbackErrorDialogComponent, {
|
||||
disableClose: false,
|
||||
minWidth: '20rem',
|
||||
classList: ['gap-0'],
|
||||
...options,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user