feat(remission): enhance quantity input handling and error validation

Refactor quantity input to use a direct input field instead of a toggle button.
Add validation to ensure quantity does not exceed 999 and display relevant error messages.
Improve overall user experience in the remission process.

Refs: #5253
This commit is contained in:
Lorenz Hilpert
2025-07-28 19:11:08 +02:00
parent 32bd3e26d2
commit da5a42280a
5 changed files with 64 additions and 79 deletions

View File

@@ -1,38 +1,35 @@
<div class="grid grid-cols-[1fr,auto] items-center justify-between">
<div
class="grid grid-cols-[1fr,auto] items-center justify-between"
[attr.data-position]="position()"
>
<div class="isa-text-body-1-bold">Menge {{ position() }}</div>
<div class="-mr-4">
<ui-button
(click)="showNumberInput.set(!showNumberInput())"
role="switch"
color="secondary"
class="border-none justify-between w-auto min-w-0 gap-4"
data-what="button"
data-which="toggle-quantity"
[attr.data-position]="position()"
>
{{
showNumberInput() ? 'Menge' : quantityAndReason().quantity || 'Menge'
}}
<ng-icon
[name]="
showNumberInput() ? 'isaActionChevronUp' : 'isaActionChevronDown'
"
size="1.25rem"
></ng-icon>
</ui-button>
<input
name="quantity"
placeholder="Menge eingeben"
type="number"
[ngModel]="quantityAndReason().quantity"
(ngModelChange)="setQuantity($event)"
#model="ngModel"
[min]="1"
[max]="999"
required
data-what="input"
data-which="quantity"
class="isa-text-body-2-bold placeholder:isa-text-body-2-regular placeholder:text-isa-neutral-200 text-isa-neutral-900 focus:outline-none w-[9rem] px-4 text-right"
/>
<ui-dropdown
[ngModel]="quantityAndReason().reason"
(ngModelChange)="setReason($event)"
class="border-none max-w-56 truncate"
data-what="dropdown"
data-which="reason"
[attr.data-position]="position()"
label="Rückgabegrund"
>
@if (reasonResource.value(); as reasons) {
@for (reson of reasons; track reson.key) {
<ui-dropdown-option
[value]="reson.value"
<ui-dropdown-option
[value]="reson.value"
data-what="dropdown-option"
data-which="reason-option"
[attr.data-reason-key]="reson.key"
@@ -46,36 +43,3 @@
</ui-dropdown>
</div>
</div>
@if (showNumberInput(); as showInput) {
<form (ngSubmit)="setQuantity(model.value); showNumberInput.set(false)">
<ui-text-field class="w-full">
<input
name="quantity"
uiInputControl
placeholder="Menge"
type="number"
[ngModel]="quantityAndReason().quantity"
#model="ngModel"
[min]="1"
[max]="999"
required
data-what="input"
data-which="quantity"
[attr.data-position]="position()"
/>
<button
uiTextButton
type="submit"
class="-mr-4 bg-transparent"
color="strong"
[disabled]="!model.valid"
data-what="button"
data-which="save-quantity"
[attr.data-position]="position()"
>
Speichern
</button>
</ui-text-field>
</form>
}

View File

@@ -1,3 +1,11 @@
:host {
@apply grid grid-flow-row gap-[0.62rem] px-4 py-[.44rem] border border-isa-neutral-400 rounded-lg;
}
input[type="number"] {
&::-webkit-inner-spin-button,
&::-webkit-outer-spin-button {
-webkit-appearance: none;
margin: 0;
}
}

View File

@@ -6,8 +6,9 @@ import {
model,
resource,
signal,
viewChild,
} from '@angular/core';
import { FormsModule } from '@angular/forms';
import { FormsModule, NgModel } from '@angular/forms';
import { RemissionReasonService, ReturnItem } from '@isa/remission/data-access';
import {
DropdownButtonComponent,
@@ -15,12 +16,7 @@ import {
} from '@isa/ui/input-controls';
import { QuantityAndReason } from './select-remi-quantity-and-reason.component';
import { ReturnValue } from '@isa/common/data-access';
import {
TextFieldComponent,
InputControlDirective,
} from '@isa/ui/input-controls';
import { ButtonComponent, TextButtonComponent } from '@isa/ui/buttons';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { provideIcons } from '@ng-icons/core';
import { isaActionChevronDown, isaActionChevronUp } from '@isa/icons';
@Component({
@@ -29,21 +25,14 @@ import { isaActionChevronDown, isaActionChevronUp } from '@isa/icons';
styleUrls: ['./quantity-and-reason-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [
DropdownButtonComponent,
DropdownOptionComponent,
FormsModule,
TextFieldComponent,
InputControlDirective,
ButtonComponent,
TextButtonComponent,
NgIcon,
],
imports: [DropdownButtonComponent, DropdownOptionComponent, FormsModule],
providers: [provideIcons({ isaActionChevronDown, isaActionChevronUp })],
})
export class QuantityAndReasonItemComponent {
#reasonService = inject(RemissionReasonService);
readonly control = viewChild.required<NgModel>('#model');
showNumberInput = signal(false);
position = input.required<number>();

View File

@@ -47,12 +47,14 @@
</button>
</div>
<div class="text-isa-accent-red isa-text-body-1-regular text-left">
@if (canReturnErrors(); as errors) {
@for (error of errors; track $index) {
{{ error }}
<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

View File

@@ -21,6 +21,7 @@ import {
ReturnSuggestion,
} from '@isa/remission/data-access';
import { injectFeedbackDialog } from '@isa/ui/dialog';
import { BatchResponseArgs } from '@isa/common/data-access';
export interface QuantityAndReason {
quantity: number;
@@ -93,10 +94,27 @@ export class SelectRemiQuantityAndReasonComponent {
canAddToRemiListResource = resource({
params: this.params,
loader: async ({ params, abortSignal }) => {
if (!this.host.item() || params.some((p) => !p.reason)) {
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);
},
});
@@ -124,6 +142,10 @@ export class SelectRemiQuantityAndReasonComponent {
canReturnErrors = computed(() => {
const results = this.canAddToRemiListResource.value();
if (results?.invalidProperties) {
return Object.values(results.invalidProperties);
}
if (!results?.failed) {
return [];
}