Styling Produktinfo in Retoure Details angepasst

This commit is contained in:
Lorenz Hilpert
2024-05-17 20:34:50 +02:00
parent e59ddaf6a6
commit 22b0c57fb0
11 changed files with 47 additions and 237 deletions

View File

@@ -1,3 +1,3 @@
:host {
@apply grid grid-cols-[1fr,auto] bg-white;
@apply grid grid-cols-[1fr,auto] bg-white p-4;
}

View File

@@ -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"

View File

@@ -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;
});

View File

@@ -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 })

View File

@@ -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.',
};

View File

@@ -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;
}
}

View File

@@ -1,3 +0,0 @@
:host {
@apply block gap-4 p-4 bg-white p-4;
}

View File

@@ -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>

View File

@@ -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],
},
};
}
}

View File

@@ -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);
}
}

View File

@@ -1,3 +1 @@
export * from './lib/product-thumbnail.component';
export * from './lib/product-info.component';
export * from './lib/product-info-field.directive';