mirror of
https://dev.azure.com/hugendubel/ISA/_git/ISA-Frontend
synced 2025-12-31 09:37:15 +01:00
Styling Produktinfo in Retoure Details angepasst
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
:host {
|
||||
@apply grid grid-cols-[1fr,auto] bg-white;
|
||||
@apply grid grid-cols-[1fr,auto] bg-white p-4;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,39 @@
|
||||
<shared-product-info
|
||||
[product]="product()"
|
||||
[fields]="[['ean'], ['format'], ['manufacturer'], ['serial'], ['edition'], ['publicationDate'], ['price'], ['quantity']]"
|
||||
>
|
||||
<div *productInfoField="'quantity'; label: 'Menge'; let field" class="font-bold">
|
||||
<shared-quantity-selector
|
||||
*ngIf="originalQuantity() > 1; else qtyTmpl"
|
||||
[value]="quantity || originalQuantity()"
|
||||
(valueChange)="quantityChange.emit($event)"
|
||||
[maxValue]="originalQuantity()"
|
||||
>
|
||||
</shared-quantity-selector>
|
||||
<ng-template #qtyTmpl>
|
||||
{{ originalQuantity() }}
|
||||
</ng-template>
|
||||
<div class="grid grid-cols-[5rem,1fr] gap-4" *ngIf="item(); let item">
|
||||
<div>
|
||||
<shared-product-thumbnail [product]="item.product"></shared-product-thumbnail>
|
||||
</div>
|
||||
</shared-product-info>
|
||||
<div class="grid grid-cols-[1fr,auto]">
|
||||
<div class="grid grid-flow-row gap-1">
|
||||
<div class="text-sm">{{ item.product?.contributors }}</div>
|
||||
<div class="text-lg font-semibold mb-2">{{ item.product?.name }}</div>
|
||||
<div class="grid grid-flow-col justify-start items-center gap-4">
|
||||
<img [src]="'/assets/images/Icon_' + item.product?.format + '.svg'" [alt]="item.product?.formatDetail" />
|
||||
<span>{{ item.product?.formatDetail }}</span>
|
||||
</div>
|
||||
<div *ngIf="item?.product?.serial">Band/Reihe {{ item.product.serial }}</div>
|
||||
<div *ngIf="item?.product?.edition">Edition {{ item.product.edition }}</div>
|
||||
<div *ngIf="item?.product?.publicationDate">
|
||||
{{ item?.product?.publicationDate | date: 'dd. MMMM yyyy' }}
|
||||
</div>
|
||||
<div class="h-4"></div>
|
||||
<div *ngIf="item?.product?.manufacturer">
|
||||
{{ item?.product?.manufacturer }}
|
||||
</div>
|
||||
<div *ngIf="item?.product?.locale">
|
||||
{{ item.product.locale }}
|
||||
</div>
|
||||
<div class="h-4"></div>
|
||||
<div *ngIf="item?.product?.ean">
|
||||
{{ item.product.ean }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-2xl font-bold">{{ item?.price?.value?.value | currency: item?.price?.value?.currency:'code' }}</div>
|
||||
<div *ngIf="item?.price?.vat?.inPercent">inkl. {{ item?.price?.vat?.inPercent }}% MwSt.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid items-center">
|
||||
<ui-select-bullet
|
||||
*ngIf="!hideSelectBullet"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BooleanInput, NumberInput, coerceBooleanProperty } from '@angular/cdk/coercion';
|
||||
import { CurrencyPipe, NgIf } from '@angular/common';
|
||||
import { CurrencyPipe, DatePipe, NgIf } from '@angular/common';
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
AfterViewInit,
|
||||
} from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Product, ProductInfoComponent, ProductInfoFieldDirective } from '@shared/components/product';
|
||||
import { ProductThumbnailComponent } from '@shared/components/product';
|
||||
import { QuantitySelectorComponent } from '@shared/components/quantity-selector';
|
||||
import { ReceiptItemDTO } from '@swagger/oms';
|
||||
import { UiSelectBulletModule } from '@ui/select-bullet';
|
||||
@@ -24,38 +24,20 @@ import { UiSelectBulletModule } from '@ui/select-bullet';
|
||||
styleUrls: ['receipt-item-details.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [
|
||||
ProductInfoComponent,
|
||||
ProductInfoFieldDirective,
|
||||
CurrencyPipe,
|
||||
UiSelectBulletModule,
|
||||
NgIf,
|
||||
QuantitySelectorComponent,
|
||||
FormsModule,
|
||||
],
|
||||
imports: [CurrencyPipe, DatePipe, UiSelectBulletModule, NgIf, QuantitySelectorComponent, FormsModule, ProductThumbnailComponent],
|
||||
})
|
||||
export class ReceiptDetailsItemComponent implements AfterViewInit {
|
||||
cdr = inject(ChangeDetectorRef);
|
||||
|
||||
itemSignal = signal<ReceiptItemDTO>(null);
|
||||
item = signal<ReceiptItemDTO>(null);
|
||||
|
||||
@Input({ required: true })
|
||||
set item(value: ReceiptItemDTO) {
|
||||
this.itemSignal.set(value);
|
||||
@Input({ alias: 'item', required: true })
|
||||
set itemInput(value: ReceiptItemDTO) {
|
||||
this.item.set(value);
|
||||
}
|
||||
|
||||
product = computed<Product>(() => {
|
||||
const item = this.itemSignal();
|
||||
return {
|
||||
...item?.product,
|
||||
price: item?.price?.value?.value,
|
||||
vat: item?.price?.vat?.inPercent,
|
||||
quantity: item?.quantity?.quantity,
|
||||
};
|
||||
});
|
||||
|
||||
originalQuantity = computed<number>(() => {
|
||||
const item = this.itemSignal();
|
||||
const item = this.item();
|
||||
return item?.quantity?.quantity;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { DatePipe, NgIf } from '@angular/common';
|
||||
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
|
||||
import { ProductInfoComponent } from '@shared/components/product';
|
||||
import { ReceiptTypePipe } from '@shared/pipes/enum';
|
||||
import { ReceiptListItemDTO } from '@swagger/oms';
|
||||
|
||||
@@ -10,7 +9,7 @@ import { ReceiptListItemDTO } from '@swagger/oms';
|
||||
styleUrls: ['retoure-list-item.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [NgIf, DatePipe, ProductInfoComponent, ReceiptTypePipe],
|
||||
imports: [NgIf, DatePipe, ReceiptTypePipe],
|
||||
})
|
||||
export class RetoureListItemComponent {
|
||||
@Input({ required: true })
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import { Product } from './product-info.component';
|
||||
|
||||
export const DEFAULT_PRODUCT_FIELD_NAME: Record<keyof Product, string> = {
|
||||
additionalName: 'Zusatznamen',
|
||||
catalogProductNumber: 'Katalogprodukt-Nummer',
|
||||
contributors: 'Mitwirkende',
|
||||
ean: 'ISBN/EAN',
|
||||
edition: 'Edition',
|
||||
format: 'Format',
|
||||
formatDetail: 'Format Info',
|
||||
locale: 'Sprache',
|
||||
manufacturer: 'Verlag',
|
||||
name: 'Name',
|
||||
price: 'Preis',
|
||||
quantity: 'Menge',
|
||||
productGroup: 'Produktgruppe',
|
||||
productGroupDetails: 'Produktgruppe Info',
|
||||
publicationDate: 'Veröffentlicht am',
|
||||
serial: 'Serie',
|
||||
size: 'Größe',
|
||||
supplierProductNumber: 'Lieferantenprodukt-Nummer',
|
||||
volume: 'Band',
|
||||
weight: 'Gewicht',
|
||||
vat: 'MwSt.',
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
import { Directive, Input, TemplateRef, ViewRef, inject } from '@angular/core';
|
||||
import { ProductDTO } from '@swagger/oms';
|
||||
import type { Product } from './product-info.component';
|
||||
|
||||
export type ProductInfoKeys = keyof Product;
|
||||
|
||||
export interface ProductInfoFieldContext<TKey extends ProductInfoKeys> {
|
||||
$implicit: {
|
||||
label: string;
|
||||
value: Product[TKey];
|
||||
};
|
||||
}
|
||||
|
||||
@Directive({ selector: '[productInfoField]', standalone: true })
|
||||
export class ProductInfoFieldDirective<TKey extends ProductInfoKeys> {
|
||||
@Input({ alias: 'productInfoField', required: true }) fieldName: TKey;
|
||||
|
||||
@Input({ alias: 'productInfoFieldLabel', required: true }) label: string;
|
||||
|
||||
templateRef = inject(TemplateRef);
|
||||
|
||||
static ngTemplateContextGuard<TKey extends ProductInfoKeys>(
|
||||
dir: ProductInfoFieldDirective<TKey>,
|
||||
ctx: ProductInfoFieldContext<TKey> | null
|
||||
): ctx is ProductInfoFieldContext<TKey> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
:host {
|
||||
@apply block gap-4 p-4 bg-white p-4;
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
<div class="grid grid-cols-[auto,1fr] gap-4">
|
||||
<div>
|
||||
<shared-product-thumbnail class="w-16" [product]="product"></shared-product-thumbnail>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="text-sm">{{ product.contributors }}</div>
|
||||
<h1 class="font-semibold line-clamp-1">{{ product.name }}</h1>
|
||||
<table class="w-full">
|
||||
<tbody>
|
||||
<ng-container *ngFor="let row of fields">
|
||||
<tr>
|
||||
<ng-container *ngFor="let col of row">
|
||||
<ng-container *ngIf="product[col]">
|
||||
<ng-container *ngIf="templates()[col]; let tmpl; else: defaultTmpl">
|
||||
<td [attr.data-name]="tmpl.fieldName" class="w-32 truncate">{{ tmpl.label }}</td>
|
||||
<td [attr.data-name-value]="col" class="w-auto truncate font-bold pl-4">
|
||||
<ng-container *ngTemplateOutlet="tmpl.templateRef; context: getFieldTemplateContext(tmpl.fieldName)"></ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-template #defaultTmpl>
|
||||
<td [attr.data-name]="col" class="w-32 truncate">{{ PRODUCT_FIELD_NAME[col] }}</td>
|
||||
<td [attr.data-name-value]="col" class="w-auto truncate font-bold pl-4">
|
||||
<ng-container [ngSwitch]="col">
|
||||
<span *ngSwitchCase="'publicationDate'">
|
||||
{{ product[col] | date: 'dd MMMM yyyy' }}
|
||||
</span>
|
||||
<span *ngSwitchCase="'price'">
|
||||
{{ product[col] | currency: 'EUR':'code' }}
|
||||
<span *ngIf="product['vat']" class="font-normal text-sm ml-2">inkl. {{ product['vat'] }}% MwSt. </span>
|
||||
</span>
|
||||
<div *ngSwitchCase="'format'" class="font-bold grid grid-flow-col items-center gap-3 justify-start">
|
||||
<img
|
||||
*ngIf="product[col] !== 'UNKNOWN'"
|
||||
class="format-icon"
|
||||
[src]="'/assets/images/Icon_' + product[col] + '.svg'"
|
||||
alt="format icon"
|
||||
/>
|
||||
<span>{{ product['formatDetail'] }}</span>
|
||||
</div>
|
||||
<span *ngSwitchDefault>
|
||||
{{ product[col] }}
|
||||
</span>
|
||||
</ng-container>
|
||||
</td>
|
||||
</ng-template>
|
||||
</ng-container></ng-container
|
||||
>
|
||||
</tr>
|
||||
</ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,78 +0,0 @@
|
||||
import {
|
||||
Component,
|
||||
ChangeDetectionStrategy,
|
||||
Input,
|
||||
QueryList,
|
||||
AfterViewInit,
|
||||
signal,
|
||||
inject,
|
||||
DestroyRef,
|
||||
ContentChildren,
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { ProductThumbnailComponent } from './product-thumbnail.component';
|
||||
import { ProductDTO } from '@swagger/oms';
|
||||
import { ProductInfoFieldContext, ProductInfoFieldDirective, ProductInfoKeys } from './product-info-field.directive';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { DEFAULT_PRODUCT_FIELD_NAME } from './constants';
|
||||
|
||||
export interface Product extends ProductDTO {
|
||||
price: number;
|
||||
quantity: number;
|
||||
vat: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'shared-product-info',
|
||||
templateUrl: 'product-info.component.html',
|
||||
styleUrls: ['product-info.component.css'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: true,
|
||||
imports: [CommonModule, ProductThumbnailComponent],
|
||||
})
|
||||
export class ProductInfoComponent implements AfterViewInit {
|
||||
PRODUCT_FIELD_NAME = DEFAULT_PRODUCT_FIELD_NAME;
|
||||
|
||||
destroyRef = inject(DestroyRef);
|
||||
|
||||
@ViewChild(ProductInfoFieldDirective, { static: true })
|
||||
defaultFieldTemplate: ProductInfoFieldDirective<ProductInfoKeys>;
|
||||
|
||||
@ContentChildren(ProductInfoFieldDirective)
|
||||
fieldTemplates: QueryList<ProductInfoFieldDirective<ProductInfoKeys>>;
|
||||
|
||||
templates = signal<Partial<Record<ProductInfoKeys, ProductInfoFieldDirective<ProductInfoKeys>>>>({});
|
||||
|
||||
@Input({ required: true })
|
||||
product: ProductDTO;
|
||||
|
||||
@Input()
|
||||
fields: ProductInfoKeys[][] = [];
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.fieldTemplates.changes.pipe(takeUntilDestroyed(this.destroyRef)).subscribe(() => {
|
||||
this.updateTemplates();
|
||||
});
|
||||
|
||||
this.updateTemplates();
|
||||
}
|
||||
|
||||
updateTemplates(): void {
|
||||
const templates = this.fieldTemplates.toArray().reduce((acc, t) => {
|
||||
acc[t.fieldName] = t;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
this.templates.set(templates);
|
||||
}
|
||||
|
||||
getFieldTemplateContext(field: ProductInfoKeys): ProductInfoFieldContext<typeof field> {
|
||||
return {
|
||||
$implicit: {
|
||||
label: this.templates[field]?.label,
|
||||
value: this.product[field],
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -33,10 +33,10 @@ export class ProductThumbnailComponent {
|
||||
thumbnailUrl = computed(() => this.thumbnailService.getThumnaulUrl({ ean: this.ean() }));
|
||||
|
||||
@Input({ required: true })
|
||||
get product() {
|
||||
get product(): { ean?: string; name?: string } | undefined {
|
||||
return this._product();
|
||||
}
|
||||
set product(value) {
|
||||
set product(value: { ean?: string; name?: string } | undefined) {
|
||||
this._product.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
export * from './lib/product-thumbnail.component';
|
||||
export * from './lib/product-info.component';
|
||||
export * from './lib/product-info-field.directive';
|
||||
|
||||
Reference in New Issue
Block a user