mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
Merged PR 1884: #5213
- feat(dialog-feedback-dialog, remission-list-item): add feedback dialog and remission list item components - feat(remission-list-item): implement remission list item component Refs: #5213
This commit is contained in:
committed by
Lorenz Hilpert
parent
40c9d51dfc
commit
65ab3bfc0a
@@ -19,7 +19,7 @@
|
||||
<ui-item-row-data>
|
||||
<remi-product-stock-info
|
||||
[availableStock]="availableStock()"
|
||||
[stockToRemit]="stockToRemit()"
|
||||
[stockToRemit]="selectedStockToRemit() ?? stockToRemit()"
|
||||
[targetStock]="targetStock()"
|
||||
[zob]="stock()?.minStockCategoryManagement ?? 0"
|
||||
></remi-product-stock-info>
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
ProductStockInfoComponent,
|
||||
} from '@isa/remission/shared/product';
|
||||
import { TextButtonComponent } from '@isa/ui/buttons';
|
||||
import { injectTextInputDialog } from '@isa/ui/dialog';
|
||||
import { injectFeedbackDialog, injectTextInputDialog } from '@isa/ui/dialog';
|
||||
import { ClientRowImports, ItemRowDataImports } from '@isa/ui/item-rows';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { Breakpoint, breakpoint } from '@isa/ui/layout';
|
||||
@@ -63,6 +63,12 @@ export class RemissionListItemComponent {
|
||||
*/
|
||||
#dialog = injectTextInputDialog();
|
||||
|
||||
/**
|
||||
* Dialog service for providing feedback to the user.
|
||||
* @private
|
||||
*/
|
||||
#feedbackDialog = injectFeedbackDialog();
|
||||
|
||||
/**
|
||||
* Store for managing selected remission quantities.
|
||||
* @private
|
||||
@@ -139,48 +145,53 @@ export class RemissionListItemComponent {
|
||||
* Computes the available stock for the item using stock and removedFromStock.
|
||||
* @returns The calculated available stock.
|
||||
*/
|
||||
availableStock = computed(() => {
|
||||
return calculateAvailableStock({
|
||||
availableStock = computed(() =>
|
||||
calculateAvailableStock({
|
||||
stock: this.stock()?.inStock,
|
||||
removedFromStock: this.stock()?.removedFromStock,
|
||||
});
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* Computes the quantity to remit for the current item.
|
||||
* - Uses the selected quantity from the store if available.
|
||||
* - Otherwise, calculates based on available stock, predefined return quantity, and remaining quantity.
|
||||
* @returns The quantity to remit.
|
||||
* Computes the selected stock quantity to remit for the current item.
|
||||
* Uses the store's selected quantity for the item's ID.
|
||||
*/
|
||||
stockToRemit = computed(() => {
|
||||
const remissionItemId = this.item()?.id;
|
||||
return (
|
||||
this.#store.selectedQuantity()?.[remissionItemId!] ??
|
||||
calculateStockToRemit({
|
||||
availableStock: this.availableStock(),
|
||||
predefinedReturnQuantity: this.predefinedReturnQuantity(),
|
||||
remainingQuantityInStock: this.remainingQuantityInStock(),
|
||||
})
|
||||
);
|
||||
});
|
||||
selectedStockToRemit = computed(
|
||||
() => this.#store.selectedQuantity()?.[this.item().id!],
|
||||
);
|
||||
|
||||
/**
|
||||
* Computes the stock to remit based on available stock, predefined return quantity,
|
||||
* and remaining quantity in stock.
|
||||
*
|
||||
* @returns The calculated stock to remit.
|
||||
*/
|
||||
stockToRemit = computed(() =>
|
||||
calculateStockToRemit({
|
||||
availableStock: this.availableStock(),
|
||||
predefinedReturnQuantity: this.predefinedReturnQuantity(),
|
||||
remainingQuantityInStock: this.remainingQuantityInStock(),
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* Computes the target stock after remission.
|
||||
* @returns The calculated target stock.
|
||||
*/
|
||||
targetStock = computed(() => {
|
||||
return calculateTargetStock({
|
||||
targetStock = computed(() =>
|
||||
calculateTargetStock({
|
||||
availableStock: this.availableStock(),
|
||||
stockToRemit: this.stockToRemit(),
|
||||
remainingQuantityInStock: this.remainingQuantityInStock(),
|
||||
});
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* Opens a dialog to allow the user to change the remission quantity for the item.
|
||||
* Validates the input and updates the remission quantity in the store if valid.
|
||||
* Opens a dialog to change the remission quantity for the current item.
|
||||
* Prompts the user for a new quantity and updates the store if valid.
|
||||
* Displays feedback dialog upon successful update.
|
||||
*
|
||||
* @returns Promise<void>
|
||||
* @returns A promise that resolves when the dialog is closed.
|
||||
*/
|
||||
async openRemissionQuantityDialog(): Promise<void> {
|
||||
const dialogRef = this.#dialog({
|
||||
@@ -209,6 +220,9 @@ export class RemissionListItemComponent {
|
||||
|
||||
if (itemId && quantity > 0) {
|
||||
this.#store.updateRemissionQuantity(itemId, this.item(), quantity);
|
||||
this.#feedbackDialog({
|
||||
data: { message: 'Remi-Menge wurde geändert' },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.ui-dialog {
|
||||
@apply bg-isa-white p-8 grid gap-8 items-start rounded-[2rem] grid-flow-row text-isa-neutral-900 relative;
|
||||
@apply max-h-[90vh] max-w-[90vw] overflow-hidden;
|
||||
@apply max-h-[90vh] overflow-hidden;
|
||||
grid-template-rows: auto 1fr;
|
||||
|
||||
.ui-dialog-title {
|
||||
@@ -13,4 +13,8 @@
|
||||
@apply overflow-y-auto overflow-x-hidden;
|
||||
@apply min-h-0;
|
||||
}
|
||||
|
||||
&:has(ui-feedback-dialog) {
|
||||
@apply gap-0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,4 +4,5 @@ export * from './lib/injects';
|
||||
export * from './lib/message-dialog/message-dialog.component';
|
||||
export * from './lib/confirmation-dialog/confirmation-dialog.component';
|
||||
export * from './lib/text-input-dialog/text-input-dialog.component';
|
||||
export * from './lib/feedback-dialog/feedback-dialog.component';
|
||||
export * from './lib/tokens';
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<h2 class="ui-dialog-title" data-what="title">
|
||||
{{ title() }}
|
||||
</h2>
|
||||
@if (title()) {
|
||||
<h2 class="ui-dialog-title" data-what="title">
|
||||
{{ title() }}
|
||||
</h2>
|
||||
}
|
||||
|
||||
<ng-container *ngComponentOutlet="component"> </ng-container>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<div class="w-full flex flex-col gap-4 items-center justify-center">
|
||||
<span
|
||||
class="bg-isa-secondary-800 rounded-[6.25rem] flex flex-row items-center justify-center p-3"
|
||||
>
|
||||
<ng-icon
|
||||
class="text-isa-white"
|
||||
size="1.5rem"
|
||||
name="isaActionCheck"
|
||||
></ng-icon>
|
||||
</span>
|
||||
<p class="isa-text-body-1-bold text-isa-neutral-900" data-what="message">
|
||||
{{ data.message }}
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,58 @@
|
||||
import { createComponentFactory, Spectator } from '@ngneat/spectator/jest';
|
||||
import {
|
||||
FeedbackDialogComponent,
|
||||
FeedbackDialogData,
|
||||
} from './feedback-dialog.component';
|
||||
import { DialogRef, DIALOG_DATA } from '@angular/cdk/dialog';
|
||||
import { NgIcon } from '@ng-icons/core';
|
||||
import { DialogComponent } from '../dialog.component';
|
||||
|
||||
// Test suite for FeedbackDialogComponent
|
||||
describe('FeedbackDialogComponent', () => {
|
||||
let spectator: Spectator<FeedbackDialogComponent>;
|
||||
let mockDialogRef: DialogRef<void>;
|
||||
const mockData: FeedbackDialogData = {
|
||||
message: 'Feedback message',
|
||||
};
|
||||
|
||||
const createComponent = createComponentFactory({
|
||||
component: FeedbackDialogComponent,
|
||||
imports: [NgIcon],
|
||||
providers: [
|
||||
{
|
||||
provide: DialogRef,
|
||||
useValue: { close: jest.fn() },
|
||||
},
|
||||
{
|
||||
provide: DIALOG_DATA,
|
||||
useValue: mockData,
|
||||
},
|
||||
{
|
||||
provide: DialogComponent,
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
spectator = createComponent();
|
||||
mockDialogRef = spectator.inject(DialogRef);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(spectator.component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should display the feedback message passed in data', () => {
|
||||
// Adjust selector if template uses a different data-what value
|
||||
const messageElement = spectator.query('[data-what="message"]');
|
||||
expect(messageElement).toHaveText('Feedback message');
|
||||
});
|
||||
|
||||
it('should render the check icon', () => {
|
||||
// The icon should be present if the template uses NgIcon with isaActionCheck
|
||||
const iconElement = spectator.query('ng-icon');
|
||||
expect(iconElement).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
|
||||
import { DialogContentDirective } from '../dialog-content.directive';
|
||||
import { NgIcon, provideIcons } from '@ng-icons/core';
|
||||
import { isaActionCheck } from '@isa/icons';
|
||||
|
||||
/**
|
||||
* Input data for the message dialog
|
||||
*/
|
||||
export interface FeedbackDialogData {
|
||||
/** The message text to display in the dialog */
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple message dialog component
|
||||
* Used for displaying informational messages to the user
|
||||
* Returns void when closed (no result)
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ui-feedback-dialog',
|
||||
templateUrl: './feedback-dialog.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [NgIcon],
|
||||
providers: [provideIcons({ isaActionCheck })],
|
||||
host: {
|
||||
'[class]': '["ui-feedback-dialog"]',
|
||||
},
|
||||
})
|
||||
export class FeedbackDialogComponent extends DialogContentDirective<
|
||||
FeedbackDialogData,
|
||||
void
|
||||
> {}
|
||||
@@ -2,6 +2,7 @@ import { Dialog, DialogRef } from '@angular/cdk/dialog';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import {
|
||||
injectDialog,
|
||||
injectFeedbackDialog,
|
||||
injectMessageDialog,
|
||||
injectTextInputDialog,
|
||||
} from './injects';
|
||||
@@ -11,6 +12,7 @@ import { DIALOG_CONTENT, DIALOG_TITLE } from './tokens';
|
||||
import { Component } from '@angular/core';
|
||||
import { DialogContentDirective } from './dialog-content.directive';
|
||||
import { TextInputDialogComponent } from './text-input-dialog/text-input-dialog.component';
|
||||
import { FeedbackDialogComponent } from './feedback-dialog/feedback-dialog.component';
|
||||
|
||||
// Test component extending DialogContentDirective for testing
|
||||
@Component({ template: '' })
|
||||
@@ -174,4 +176,23 @@ describe('Dialog Injects', () => {
|
||||
expect(injector.get(DIALOG_CONTENT)).toBe(TextInputDialogComponent);
|
||||
});
|
||||
});
|
||||
|
||||
describe('injectFeedbackDialog', () => {
|
||||
it('should create a dialog injector for FeedbackDialogComponent', () => {
|
||||
// Act
|
||||
const openFeedbackDialog = TestBed.runInInjectionContext(() =>
|
||||
injectFeedbackDialog(),
|
||||
);
|
||||
openFeedbackDialog({
|
||||
data: {
|
||||
message: 'Test message',
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const callOptions = mockDialogOpen.mock.calls[0][1];
|
||||
const injector = callOptions.injector;
|
||||
expect(injector.get(DIALOG_CONTENT)).toBe(FeedbackDialogComponent);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import { DialogComponent } from './dialog.component';
|
||||
import { DIALOG_CONTENT, DIALOG_TITLE } from './tokens';
|
||||
import { MessageDialogComponent } from './message-dialog/message-dialog.component';
|
||||
import { TextInputDialogComponent } from './text-input-dialog/text-input-dialog.component';
|
||||
import { FeedbackDialogComponent } from './feedback-dialog/feedback-dialog.component';
|
||||
|
||||
export interface InjectDialogOptions {
|
||||
/** Optional title override for the dialog */
|
||||
@@ -114,3 +115,13 @@ export const injectMessageDialog = () => injectDialog(MessageDialogComponent);
|
||||
*/
|
||||
export const injectTextInputDialog = () =>
|
||||
injectDialog(TextInputDialogComponent);
|
||||
|
||||
/**
|
||||
* Convenience function that returns a pre-configured FeedbackDialog injector
|
||||
* @returns A function to open a feedback dialog
|
||||
*/
|
||||
export const injectFeedbackDialog = () =>
|
||||
injectDialog(FeedbackDialogComponent, {
|
||||
disableClose: false,
|
||||
minWidth: '20rem',
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user