mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-28 22:42:11 +01:00
feat(oms-task-list): implement task action types and specialized UI handling
Enhance the ReturnTaskListComponent and ReturnTaskListItemComponent to: - Use properly typed TaskActionTypes enum (OK, NOK, PRINT, UNKNOWN) instead of string literals - Add specialized UI components for different action types - Implement conditional rendering for task actions based on type - Improve styling for different task types - Filter out completed tasks in main view feat(oms-data-access): add Zod schema validation for return receipts - Add ReturnReceiptValuesSchema for validation of API payloads - Implement proper type safety for task action types - Use schema validation in ReturnProcessService before API calls Ref: #4942
This commit is contained in:
@@ -1,4 +1,14 @@
|
||||
export const TaskActionTypes = {
|
||||
OK: 'OK',
|
||||
NOK: 'NOK',
|
||||
PRINT: 'PRINT',
|
||||
UNKNOWN: 'UNKNOWN',
|
||||
} as const;
|
||||
|
||||
export type TaskActionTypeType =
|
||||
(typeof TaskActionTypes)[keyof typeof TaskActionTypes];
|
||||
|
||||
export interface TaskActionType {
|
||||
type: 'complete' | 'damaged' | 'resell' | 'print';
|
||||
type: TaskActionTypeType;
|
||||
taskId: number;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@ import {
|
||||
} from './models';
|
||||
import { CategoryQuestions } from './questions';
|
||||
import { KeyValue } from '@angular/common';
|
||||
import { ReturnProcessChecklistAnswerSchema } from './schemas';
|
||||
import {
|
||||
ReturnProcessChecklistAnswerSchema,
|
||||
ReturnReceiptValuesSchema,
|
||||
} from './schemas';
|
||||
import { logger } from '@isa/core/logging';
|
||||
import {
|
||||
PropertyIsEmptyError,
|
||||
@@ -31,6 +34,7 @@ import {
|
||||
} from '@generated/swagger/oms-api';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { ReturnPrintReceiptsService } from './return-print-receipts.service';
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Service responsible for managing the return process workflow.
|
||||
@@ -286,19 +290,26 @@ export class ReturnProcessService {
|
||||
}
|
||||
|
||||
return {
|
||||
quantity: process.receiptItem.quantity.quantity,
|
||||
quantity: process.receiptItem.quantity.quantity, // TODO: Teilmenge handling implementieren - Aktuell wird die gesamte Quantity genommen
|
||||
comment: returnInfo.comment,
|
||||
itemCondition: returnInfo.itemCondition,
|
||||
otherProduct: returnInfo.otherProduct,
|
||||
returnDetails: returnInfo.returnDetails,
|
||||
returnReason: returnInfo.returnReason,
|
||||
receiptItem: process.receiptItem,
|
||||
receiptItem: { id: process.receiptItem.id },
|
||||
};
|
||||
});
|
||||
|
||||
const parsedPayload = z.array(ReturnReceiptValuesSchema).safeParse(payload);
|
||||
|
||||
if (!parsedPayload.success) {
|
||||
this.#logger.error('Payload validation failed', parsedPayload.error);
|
||||
return [];
|
||||
}
|
||||
|
||||
const response = await firstValueFrom(
|
||||
this.#receiptService.ReceiptCreateReturnReceipt({
|
||||
payload,
|
||||
payload: parsedPayload.data as ReturnReceiptValuesDTO[],
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from './fetch-return-details.schema';
|
||||
export * from './query-token.schema';
|
||||
export * from './return-process-question-answer.schema';
|
||||
export * from './return-receipt-values.schema';
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { z } from 'zod';
|
||||
import { Product } from '../models/product';
|
||||
|
||||
const ReceiptItemSchema = z.object({
|
||||
id: z.number(),
|
||||
});
|
||||
|
||||
export const ReturnReceiptValuesSchema = z.object({
|
||||
quantity: z.number(),
|
||||
comment: z.string().optional(),
|
||||
itemCondition: z.string().optional(),
|
||||
returnDetails: z.string().optional(),
|
||||
returnReason: z.string().optional(),
|
||||
receiptItem: ReceiptItemSchema,
|
||||
});
|
||||
|
||||
export type ReturnReceiptValues = z.infer<typeof ReturnReceiptValuesSchema> & {
|
||||
otherProduct: Product;
|
||||
};
|
||||
@@ -35,9 +35,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center gap-6 w-[24.25rem] justify-self-center">
|
||||
<span class="isa-text-subtitle-2-bold self-start">OFFENE AUFGABEN</span>
|
||||
<oms-shared-return-task-list
|
||||
[appearance]="'main'"
|
||||
></oms-shared-return-task-list>
|
||||
</div>
|
||||
<oms-shared-return-task-list
|
||||
[appearance]="'main'"
|
||||
></oms-shared-return-task-list>
|
||||
|
||||
@@ -4,27 +4,33 @@
|
||||
data-which="return-product-info"
|
||||
></oms-shared-return-product-info>
|
||||
|
||||
<div class="p-4 flex flex-col gap-4 rounded-lg bg-isa-secondary-100">
|
||||
@let taskActionType = type();
|
||||
<div
|
||||
class="h-full p-4 flex flex-col gap-4 rounded-lg bg-isa-secondary-100"
|
||||
[class.unkown-type]="taskActionType === 'UNKNOWN'"
|
||||
>
|
||||
<div
|
||||
data-what="review-list"
|
||||
data-which="processing-comment"
|
||||
class="isa-text-body-2-bold"
|
||||
class="processing-comment"
|
||||
>
|
||||
{{ processingComment() }}
|
||||
</div>
|
||||
@if (!item()?.completed) {
|
||||
<button
|
||||
class="flex items-center gap-2 self-end"
|
||||
type="button"
|
||||
uiButton
|
||||
color="primary"
|
||||
(click)="onActionClick({ type: 'complete' })"
|
||||
data-what="button"
|
||||
data-which="complete"
|
||||
>
|
||||
<ng-icon name="isaActionCheck" uiButtonIcon></ng-icon>
|
||||
Als erledigt markieren
|
||||
</button>
|
||||
@if (taskActionType !== 'UNKNOWN') {
|
||||
<button
|
||||
class="flex items-center gap-2 self-end"
|
||||
type="button"
|
||||
uiButton
|
||||
color="primary"
|
||||
(click)="onActionClick(taskActionType)"
|
||||
data-what="button"
|
||||
data-which="complete"
|
||||
>
|
||||
<ng-icon name="isaActionCheck" uiButtonIcon></ng-icon>
|
||||
Als erledigt markieren
|
||||
</button>
|
||||
}
|
||||
} @else {
|
||||
<span
|
||||
class="flex items-center gap-2 text-isa-accent-green isa-text-body-2-bold self-end"
|
||||
@@ -36,3 +42,43 @@
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (taskActionType === 'UNKNOWN' && !item()?.completed) {
|
||||
<div class="flex flex-row gap-3 h-full py-2">
|
||||
<button
|
||||
class="flex items-center"
|
||||
type="button"
|
||||
uiButton
|
||||
color="secondary"
|
||||
(click)="onActionClick(taskActionType)"
|
||||
data-what="button"
|
||||
data-which="resellable"
|
||||
>
|
||||
Ja verkaufsfähig
|
||||
</button>
|
||||
<button
|
||||
class="flex items-center"
|
||||
type="button"
|
||||
uiButton
|
||||
color="secondary"
|
||||
(click)="onActionClick(taskActionType)"
|
||||
data-what="button"
|
||||
data-which="damaged"
|
||||
>
|
||||
Nein, defekt
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (taskActionType === 'PRINT') {
|
||||
<button
|
||||
data-what="button"
|
||||
data-which="print-receipt"
|
||||
class="self-start"
|
||||
(click)="onActionClick(taskActionType)"
|
||||
uiInfoButton
|
||||
>
|
||||
<span uiInfoButtonLabel>Retourenschein drucken</span>
|
||||
<ng-icon name="isaActionPrinter" uiInfoButtonIcon></ng-icon>
|
||||
</button>
|
||||
}
|
||||
|
||||
@@ -7,5 +7,17 @@
|
||||
}
|
||||
|
||||
.oms-shared-return-task-list-item__main {
|
||||
@apply bg-white rounded-2xl p-6 flex flex-col gap-6;
|
||||
@apply bg-white rounded-2xl p-6 flex flex-col gap-6 w-[24.25rem];
|
||||
}
|
||||
|
||||
.processing-comment {
|
||||
@apply isa-text-body-2-bold;
|
||||
}
|
||||
|
||||
.unkown-type {
|
||||
@apply rounded-none bg-isa-white;
|
||||
|
||||
.processing-comment {
|
||||
@apply isa-text-body-1-regular;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,18 @@ import {
|
||||
computed,
|
||||
input,
|
||||
output,
|
||||
Signal,
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import { isaActionCheck } from '@isa/icons';
|
||||
import { isaActionCheck, isaActionPrinter } from '@isa/icons';
|
||||
import {
|
||||
Product,
|
||||
ReceiptItemTaskListItem,
|
||||
TaskActionType,
|
||||
TaskActionTypeType,
|
||||
} from '@isa/oms/data-access';
|
||||
import { ReturnProductInfoComponent } from '@isa/oms/shared/product-info';
|
||||
import { ButtonComponent } from '@isa/ui/buttons';
|
||||
import { ButtonComponent, InfoButtonComponent } from '@isa/ui/buttons';
|
||||
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
||||
|
||||
@Component({
|
||||
@@ -22,8 +24,13 @@ import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
||||
styleUrl: './return-task-list-item.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [ReturnProductInfoComponent, ButtonComponent, NgIconComponent],
|
||||
providers: [provideIcons({ isaActionCheck })],
|
||||
imports: [
|
||||
ReturnProductInfoComponent,
|
||||
ButtonComponent,
|
||||
NgIconComponent,
|
||||
InfoButtonComponent,
|
||||
],
|
||||
providers: [provideIcons({ isaActionCheck, isaActionPrinter })],
|
||||
host: {
|
||||
'[class]': "['oms-shared-return-task-list-item', appearanceClass()]",
|
||||
},
|
||||
@@ -60,9 +67,17 @@ export class ReturnTaskListItemComponent {
|
||||
return undefined;
|
||||
});
|
||||
|
||||
onActionClick(type: Omit<TaskActionType, 'taskId'>) {
|
||||
type: Signal<TaskActionTypeType> = computed(() => {
|
||||
const item = this.item();
|
||||
const mappedType: Omit<TaskActionType, 'taskId'> = {
|
||||
type: item.type as TaskActionTypeType,
|
||||
};
|
||||
return mappedType.type;
|
||||
});
|
||||
|
||||
onActionClick(type: TaskActionTypeType) {
|
||||
const taskId = this.item().id;
|
||||
const actionType = { ...type, taskId };
|
||||
const actionType = { type, taskId };
|
||||
this.action.emit(actionType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
@let taskList = taskListItems();
|
||||
@if (taskList?.length !== 0) {
|
||||
@if (appearance() === 'main') {
|
||||
<span class="isa-text-subtitle-2-bold self-start">OFFENE AUFGABEN</span>
|
||||
}
|
||||
|
||||
<div
|
||||
class="flex flex-col w-full items-center justify-center"
|
||||
[class.list-gap]="appearance() === 'main'"
|
||||
[class.return-search-main-task-list-styles]="appearance() === 'main'"
|
||||
>
|
||||
@for (item of taskList; track item.id) {
|
||||
@defer (on viewport) {
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
.list-gap {
|
||||
@apply gap-6;
|
||||
.oms-shared-return-task-list__main {
|
||||
@apply flex flex-col items-center gap-6 justify-self-center;
|
||||
}
|
||||
|
||||
.return-search-main-task-list-styles {
|
||||
@apply gap-6 desktop:overflow-y-scroll desktop:max-h-[calc(100vh-13rem)] justify-start;
|
||||
}
|
||||
|
||||
@@ -46,10 +46,24 @@ export class ReturnTaskListComponent {
|
||||
|
||||
taskListItems = computed(() => {
|
||||
const processId = this.processId();
|
||||
const appearance = this.appearance();
|
||||
|
||||
if (!processId) {
|
||||
return [];
|
||||
}
|
||||
return this.#returnReviewStore.entityMap()[processId].data ?? [];
|
||||
|
||||
const taskListItems = this.#returnReviewStore.entityMap()[processId].data;
|
||||
|
||||
if (!taskListItems || taskListItems?.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Filter out completed tasks if the appearance is 'main'
|
||||
if (appearance === 'main') {
|
||||
return taskListItems.filter((item) => !item.completed);
|
||||
}
|
||||
|
||||
return taskListItems;
|
||||
});
|
||||
|
||||
constructor() {
|
||||
@@ -69,11 +83,14 @@ export class ReturnTaskListComponent {
|
||||
}
|
||||
|
||||
async handleAction(action: TaskActionType) {
|
||||
switch (action.type) {
|
||||
case 'complete':
|
||||
await this.completeTask(action.taskId);
|
||||
break;
|
||||
// TODO: Implement other action types
|
||||
if (action.type === 'UNKNOWN') {
|
||||
// TODO: Neuer Endpoint - updateTask
|
||||
} else {
|
||||
if (action.type === 'PRINT') {
|
||||
// TODO: Spezieller Print request für Tolino - DIN-A4 Retourenschein Drucken
|
||||
}
|
||||
|
||||
await this.completeTask(action.taskId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user