chore(front-end):map color
Some checks failed
Build and Deploy / test-backend (push) Successful in 27s
Build and Deploy / test-frontend (push) Successful in 1m0s
Build and Deploy / build-and-push (push) Failing after 26s
Build and Deploy / deploy (push) Has been skipped

This commit is contained in:
2026-03-12 16:43:00 +01:00
parent 5d17b23c3a
commit fcdede2dd6
16 changed files with 262 additions and 51 deletions

View File

@@ -11,6 +11,8 @@ export interface ColorCategory {
colors: ColorOption[];
}
const DEFAULT_BRAND_COLOR = '#facf0a';
export const PRODUCT_COLORS: ColorCategory[] = [
{
name: 'COLOR.CATEGORY_GLOSSY',
@@ -38,10 +40,145 @@ export const PRODUCT_COLORS: ColorCategory[] = [
},
];
export function getColorHex(value: string): string {
for (const cat of PRODUCT_COLORS) {
const found = cat.colors.find((c) => c.value === value);
if (found) return found.hex;
}
return '#facf0a'; // Default Brand Color if not found
const COLOR_HEX_BY_TRANSLATION_KEY: Record<string, string> = {
...Object.fromEntries(
PRODUCT_COLORS.flatMap((category) =>
category.colors.map((color) => [color.label, color.hex] as const),
),
),
'COLOR.NAME.ORANGE': '#f5a623',
'COLOR.NAME.GRAY': '#b7b7b7',
'COLOR.NAME.LIGHT_GRAY': '#d8dadd',
'COLOR.NAME.DARK_GRAY': '#4f4f4f',
'COLOR.NAME.PURPLE': '#7b1fa2',
'COLOR.NAME.BEIGE': '#d4c09a',
'COLOR.NAME.SAND_BEIGE': '#d7c2a0',
};
const COLOR_TRANSLATION_KEY_BY_VALUE: Record<string, string> = {
black: 'COLOR.NAME.BLACK',
nero: 'COLOR.NAME.BLACK',
noir: 'COLOR.NAME.BLACK',
schwarz: 'COLOR.NAME.BLACK',
white: 'COLOR.NAME.WHITE',
bianco: 'COLOR.NAME.WHITE',
blanc: 'COLOR.NAME.WHITE',
weiss: 'COLOR.NAME.WHITE',
red: 'COLOR.NAME.RED',
rosso: 'COLOR.NAME.RED',
rouge: 'COLOR.NAME.RED',
rot: 'COLOR.NAME.RED',
blue: 'COLOR.NAME.BLUE',
blu: 'COLOR.NAME.BLUE',
bleu: 'COLOR.NAME.BLUE',
blau: 'COLOR.NAME.BLUE',
green: 'COLOR.NAME.GREEN',
verde: 'COLOR.NAME.GREEN',
vert: 'COLOR.NAME.GREEN',
grun: 'COLOR.NAME.GREEN',
yellow: 'COLOR.NAME.YELLOW',
giallo: 'COLOR.NAME.YELLOW',
jaune: 'COLOR.NAME.YELLOW',
gelb: 'COLOR.NAME.YELLOW',
orange: 'COLOR.NAME.ORANGE',
arancione: 'COLOR.NAME.ORANGE',
naranja: 'COLOR.NAME.ORANGE',
gris: 'COLOR.NAME.GRAY',
gray: 'COLOR.NAME.GRAY',
grey: 'COLOR.NAME.GRAY',
grigio: 'COLOR.NAME.GRAY',
grau: 'COLOR.NAME.GRAY',
'light gray': 'COLOR.NAME.LIGHT_GRAY',
'light grey': 'COLOR.NAME.LIGHT_GRAY',
'grigio chiaro': 'COLOR.NAME.LIGHT_GRAY',
'gris clair': 'COLOR.NAME.LIGHT_GRAY',
hellgrau: 'COLOR.NAME.LIGHT_GRAY',
'dark gray': 'COLOR.NAME.DARK_GRAY',
'dark grey': 'COLOR.NAME.DARK_GRAY',
'grigio scuro': 'COLOR.NAME.DARK_GRAY',
'gris fonce': 'COLOR.NAME.DARK_GRAY',
dunkelgrau: 'COLOR.NAME.DARK_GRAY',
purple: 'COLOR.NAME.PURPLE',
violet: 'COLOR.NAME.PURPLE',
viola: 'COLOR.NAME.PURPLE',
lila: 'COLOR.NAME.PURPLE',
beige: 'COLOR.NAME.BEIGE',
'sand beige': 'COLOR.NAME.SAND_BEIGE',
'beige sabbia': 'COLOR.NAME.SAND_BEIGE',
'beige sable': 'COLOR.NAME.SAND_BEIGE',
sandbeige: 'COLOR.NAME.SAND_BEIGE',
'matte black': 'COLOR.NAME.MATTE_BLACK',
'black matte': 'COLOR.NAME.MATTE_BLACK',
'nero opaco': 'COLOR.NAME.MATTE_BLACK',
'noir mat': 'COLOR.NAME.MATTE_BLACK',
'matt schwarz': 'COLOR.NAME.MATTE_BLACK',
'schwarz matt': 'COLOR.NAME.MATTE_BLACK',
'matte white': 'COLOR.NAME.MATTE_WHITE',
'white matte': 'COLOR.NAME.MATTE_WHITE',
'bianco opaco': 'COLOR.NAME.MATTE_WHITE',
'blanc mat': 'COLOR.NAME.MATTE_WHITE',
'matt weiss': 'COLOR.NAME.MATTE_WHITE',
'weiss matt': 'COLOR.NAME.MATTE_WHITE',
'matte gray': 'COLOR.NAME.MATTE_GRAY',
'matte grey': 'COLOR.NAME.MATTE_GRAY',
'grigio opaco': 'COLOR.NAME.MATTE_GRAY',
'gris mat': 'COLOR.NAME.MATTE_GRAY',
'matt grau': 'COLOR.NAME.MATTE_GRAY',
'grau matt': 'COLOR.NAME.MATTE_GRAY',
};
export function normalizeColorValue(value: string | null | undefined): string {
return String(value ?? '')
.trim()
.toLowerCase()
.replace(/ß/g, 'ss')
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/[_-]+/g, ' ')
.replace(/\s+/g, ' ');
}
export function getColorTranslationKey(
value: string | null | undefined,
): string | null {
const normalized = normalizeColorValue(value);
return normalized ? COLOR_TRANSLATION_KEY_BY_VALUE[normalized] ?? null : null;
}
export function getColorLabelToken(
value: string | null | undefined,
): string | null {
const raw = String(value ?? '').trim();
if (!raw) {
return null;
}
return getColorTranslationKey(raw) ?? raw;
}
export function findColorHex(value: string | null | undefined): string | null {
const translationKey = getColorTranslationKey(value);
if (translationKey) {
return COLOR_HEX_BY_TRANSLATION_KEY[translationKey] ?? null;
}
const normalized = normalizeColorValue(value);
if (!normalized) {
return null;
}
for (const category of PRODUCT_COLORS) {
const match = category.colors.find(
(color) => normalizeColorValue(color.value) === normalized,
);
if (match) {
return match.hex;
}
}
return null;
}
export function getColorHex(value: string): string {
return findColorHex(value) ?? DEFAULT_BRAND_COLOR;
}

View File

@@ -130,7 +130,7 @@
<div class="cart-line-copy">
<strong>{{ cartItemName(item) }}</strong>
@if (cartItemVariant(item); as variant) {
<span class="cart-line-meta">{{ variant }}</span>
<span class="cart-line-meta">{{ variant | translate }}</span>
}
@if (cartItemColor(item); as color) {
<span class="cart-line-color">
@@ -138,7 +138,7 @@
class="color-dot"
[style.background-color]="cartItemColorHex(item)"
></span>
<span>{{ color }}</span>
<span>{{ color | translate }}</span>
</span>
}
</div>

View File

@@ -15,6 +15,10 @@ import {
ShopService,
} from '../../features/shop/services/shop.service';
import { finalize } from 'rxjs';
import {
findColorHex,
getColorLabelToken,
} from '../constants/colors.const';
@Component({
selector: 'app-navbar',
@@ -143,15 +147,25 @@ export class NavbarComponent {
}
cartItemVariant(item: ShopCartItem): string | null {
return item.shopVariantLabel || item.shopVariantColorName || null;
return (
item.shopVariantLabel || getColorLabelToken(item.shopVariantColorName)
);
}
cartItemColor(item: ShopCartItem): string | null {
return item.shopVariantColorName || item.colorCode || null;
return (
getColorLabelToken(item.shopVariantColorName) ??
getColorLabelToken(item.colorCode)
);
}
cartItemColorHex(item: ShopCartItem): string {
return item.shopVariantColorHex || '#c9ced6';
return (
item.shopVariantColorHex ||
findColorHex(item.shopVariantColorName) ||
findColorHex(item.colorCode) ||
'#c9ced6'
);
}
trackByCartItem(_index: number, item: ShopCartItem): string {

View File

@@ -265,14 +265,16 @@
</span>
<span *ngIf="itemVariantLabel(item) as variantLabel">
{{ "SHOP.VARIANT" | translate }}:
{{ variantLabel }}
{{ variantLabel | translate }}
</span>
<span class="item-color" *ngIf="itemColorLabel(item) !== '-'">
<span
class="color-dot"
[style.background-color]="itemColorSwatch(item)"
></span>
<span class="color-name">{{ itemColorLabel(item) }}</span>
<span class="color-name">{{
itemColorLabel(item) | translate
}}</span>
</span>
</div>
<div class="item-specs-sub" *ngIf="showItemPrintMetrics(item)">

View File

@@ -22,7 +22,12 @@ import {
} from '../../shared/components/price-breakdown/price-breakdown.component';
import { LanguageService } from '../../core/services/language.service';
import { StlViewerComponent } from '../../shared/components/stl-viewer/stl-viewer.component';
import { getColorHex } from '../../core/constants/colors.const';
import {
findColorHex,
getColorHex,
getColorLabelToken,
normalizeColorValue,
} from '../../core/constants/colors.const';
@Component({
selector: 'app-checkout',
@@ -252,8 +257,7 @@ export class CheckoutComponent implements OnInit {
if (variantLabel) {
return variantLabel;
}
const colorName = String(item?.shopVariantColorName ?? '').trim();
return colorName || null;
return getColorLabelToken(item?.shopVariantColorName);
}
showItemMaterial(item: any): boolean {
@@ -284,10 +288,10 @@ export class CheckoutComponent implements OnInit {
itemColorLabel(item: any): string {
const shopColor = String(item?.shopVariantColorName ?? '').trim();
if (shopColor) {
return shopColor;
return getColorLabelToken(shopColor) ?? '-';
}
const raw = String(item?.colorCode ?? '').trim();
return raw || '-';
return getColorLabelToken(raw) ?? '-';
}
itemColorSwatch(item: any): string {
@@ -310,12 +314,12 @@ export class CheckoutComponent implements OnInit {
return raw;
}
const byName = this.variantHexByColorName.get(raw.toLowerCase());
const byName = this.variantHexByColorName.get(normalizeColorValue(raw));
if (byName) {
return byName;
}
const fallback = getColorHex(raw);
const fallback = findColorHex(raw) ?? getColorHex(raw);
if (fallback && fallback !== '#facf0a') {
return fallback;
}
@@ -373,7 +377,10 @@ export class CheckoutComponent implements OnInit {
this.variantHexById.set(variantId, colorHex);
}
if (colorName && colorHex) {
this.variantHexByColorName.set(colorName.toLowerCase(), colorHex);
this.variantHexByColorName.set(
normalizeColorValue(colorName),
colorHex,
);
}
}
}

View File

@@ -248,7 +248,8 @@
}}
</span>
<span *ngIf="itemVariantLabel(item) as variantLabel">
{{ "SHOP.VARIANT" | translate }}: {{ variantLabel }}
{{ "SHOP.VARIANT" | translate }}:
{{ variantLabel | translate }}
</span>
<span class="item-color-chip">
<span
@@ -256,7 +257,7 @@
*ngIf="itemColorHex(item) as colorHex"
[style.background-color]="colorHex"
></span>
<span>{{ itemColorLabel(item) }}</span>
<span>{{ itemColorLabel(item) | translate }}</span>
</span>
</div>

View File

@@ -6,6 +6,10 @@ import { AppCardComponent } from '../../shared/components/app-card/app-card.comp
import { QuoteEstimatorService } from '../calculator/services/quote-estimator.service';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { environment } from '../../../environments/environment';
import {
findColorHex,
getColorLabelToken,
} from '../../core/constants/colors.const';
import {
PriceBreakdownComponent,
PriceBreakdownRow,
@@ -278,23 +282,28 @@ export class OrderComponent implements OnInit {
return variantLabel;
}
const colorName = String(item?.shopVariantColorName ?? '').trim();
return colorName || null;
return getColorLabelToken(item?.shopVariantColorName);
}
itemColorLabel(item: PublicOrderItem): string {
const shopColor = String(item?.shopVariantColorName ?? '').trim();
if (shopColor) {
return shopColor;
return getColorLabelToken(shopColor) ?? this.translate.instant('ORDER.NOT_AVAILABLE');
}
const filamentColor = String(item?.filamentColorName ?? '').trim();
if (filamentColor) {
return filamentColor;
return (
getColorLabelToken(filamentColor) ??
this.translate.instant('ORDER.NOT_AVAILABLE')
);
}
const rawColor = String(item?.colorCode ?? '').trim();
return rawColor || this.translate.instant('ORDER.NOT_AVAILABLE');
return (
getColorLabelToken(rawColor) ??
this.translate.instant('ORDER.NOT_AVAILABLE')
);
}
itemColorHex(item: PublicOrderItem): string | null {
@@ -313,7 +322,7 @@ export class OrderComponent implements OnInit {
return rawColor;
}
return null;
return findColorHex(rawColor);
}
showItemMaterial(item: PublicOrderItem): boolean {

View File

@@ -152,7 +152,7 @@
@if (selectedMaterial()?.label) {
<span aria-hidden="true">·</span>
}
<span>{{ colorLabel(activeVariant) }}</span>
<span>{{ colorLabel(activeVariant) | translate }}</span>
}
</p>
}
@@ -267,7 +267,9 @@
</span>
<span class="color-trigger__copy">
<strong>{{ colorLabel(activeVariant) }}</strong>
<strong>{{
colorLabel(activeVariant) | translate
}}</strong>
<small>{{ selectedMaterial()?.label }}</small>
</span>
</button>
@@ -304,7 +306,7 @@
</span>
<span class="color-popup__name">{{
colorLabel(variant)
colorLabel(variant) | translate
}}</span>
</button>
}

View File

@@ -15,7 +15,11 @@ import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { catchError, combineLatest, finalize, of, switchMap, tap } from 'rxjs';
import { SeoService } from '../../core/services/seo.service';
import { LanguageService } from '../../core/services/language.service';
import { getColorHex } from '../../core/constants/colors.const';
import {
findColorHex,
getColorHex,
getColorLabelToken,
} from '../../core/constants/colors.const';
import { AppButtonComponent } from '../../shared/components/app-button/app-button.component';
import { AppCardComponent } from '../../shared/components/app-card/app-card.component';
import { StlViewerComponent } from '../../shared/components/stl-viewer/stl-viewer.component';
@@ -403,7 +407,9 @@ export class ProductDetailComponent {
}
colorLabel(variant: ShopProductVariantOption): string {
return variant.colorName || variant.variantLabel || '-';
return (
getColorLabelToken(variant.colorName || variant.variantLabel) ?? '-'
);
}
colorHex(variant: ShopProductVariantOption | null | undefined): string {
@@ -524,17 +530,7 @@ export class ProductDetailComponent {
}
private colorHexFromName(value: string | null | undefined): string | null {
const colorName = String(value ?? '').trim();
if (!colorName) {
return null;
}
const fallback = getColorHex(colorName);
if (!fallback || fallback === '#facf0a') {
return null;
}
return fallback;
return findColorHex(value);
}
private applySeo(product: ShopProductDetail): void {

View File

@@ -84,7 +84,7 @@
<div class="cart-line-copy">
<strong>{{ cartItemName(item) }}</strong>
@if (cartItemVariant(item); as variant) {
<span class="cart-line-meta">{{ variant }}</span>
<span class="cart-line-meta">{{ variant | translate }}</span>
}
@if (cartItemColor(item); as color) {
<span class="cart-line-color">
@@ -92,7 +92,7 @@
class="color-dot"
[style.background-color]="cartItemColorHex(item)"
></span>
<span>{{ color }}</span>
<span>{{ color | translate }}</span>
</span>
}
</div>

View File

@@ -22,6 +22,10 @@ import {
} from 'rxjs';
import { SeoService } from '../../core/services/seo.service';
import { LanguageService } from '../../core/services/language.service';
import {
findColorHex,
getColorLabelToken,
} from '../../core/constants/colors.const';
import { AppButtonComponent } from '../../shared/components/app-button/app-button.component';
import { AppCardComponent } from '../../shared/components/app-card/app-card.component';
import { ProductCardComponent } from './components/product-card/product-card.component';
@@ -157,15 +161,25 @@ export class ShopPageComponent {
}
cartItemVariant(item: ShopCartItem): string | null {
return item.shopVariantLabel || item.shopVariantColorName || null;
return (
item.shopVariantLabel || getColorLabelToken(item.shopVariantColorName)
);
}
cartItemColor(item: ShopCartItem): string | null {
return item.shopVariantColorName || item.colorCode || null;
return (
getColorLabelToken(item.shopVariantColorName) ??
getColorLabelToken(item.colorCode)
);
}
cartItemColorHex(item: ShopCartItem): string {
return item.shopVariantColorHex || '#c9ced6';
return (
item.shopVariantColorHex ||
findColorHex(item.shopVariantColorName) ||
findColorHex(item.colorCode) ||
'#c9ced6'
);
}
navigateToCategory(slug?: string | null): void {

View File

@@ -4,6 +4,7 @@ import { TranslateModule } from '@ngx-translate/core';
import {
PRODUCT_COLORS,
getColorHex,
getColorLabelToken,
ColorCategory,
ColorOption,
} from '../../../core/constants/colors.const';
@@ -32,7 +33,7 @@ export class ColorSelectorComponent {
const finish = v.finishType || 'AVAILABLE_COLORS';
const bucket = byFinish.get(finish) || [];
bucket.push({
label: v.colorName,
label: getColorLabelToken(v.colorName) ?? v.colorName,
value: v.colorName,
hex: v.hexColor,
variantId: v.id,

View File

@@ -569,6 +569,13 @@
"BLUE": "Blau",
"GREEN": "Grün",
"YELLOW": "Gelb",
"ORANGE": "Orange",
"GRAY": "Grau",
"LIGHT_GRAY": "Hellgrau",
"DARK_GRAY": "Dunkelgrau",
"PURPLE": "Lila",
"BEIGE": "Beige",
"SAND_BEIGE": "Sandbeige",
"MATTE_BLACK": "Matt Schwarz",
"MATTE_WHITE": "Matt Weiß",
"MATTE_GRAY": "Matt Grau"

View File

@@ -569,6 +569,13 @@
"BLUE": "Blue",
"GREEN": "Green",
"YELLOW": "Yellow",
"ORANGE": "Orange",
"GRAY": "Gray",
"LIGHT_GRAY": "Light Gray",
"DARK_GRAY": "Dark Gray",
"PURPLE": "Purple",
"BEIGE": "Beige",
"SAND_BEIGE": "Sand Beige",
"MATTE_BLACK": "Matte Black",
"MATTE_WHITE": "Matte White",
"MATTE_GRAY": "Matte Gray"

View File

@@ -575,6 +575,13 @@
"BLUE": "Bleu",
"GREEN": "Vert",
"YELLOW": "Jaune",
"ORANGE": "Orange",
"GRAY": "Gris",
"LIGHT_GRAY": "Gris clair",
"DARK_GRAY": "Gris foncé",
"PURPLE": "Violet",
"BEIGE": "Beige",
"SAND_BEIGE": "Beige sable",
"MATTE_BLACK": "Noir mat",
"MATTE_WHITE": "Blanc mat",
"MATTE_GRAY": "Gris mat"

View File

@@ -624,6 +624,13 @@
"BLUE": "Blu",
"GREEN": "Verde",
"YELLOW": "Giallo",
"ORANGE": "Arancione",
"GRAY": "Grigio",
"LIGHT_GRAY": "Grigio chiaro",
"DARK_GRAY": "Grigio scuro",
"PURPLE": "Viola",
"BEIGE": "Beige",
"SAND_BEIGE": "Beige sabbia",
"MATTE_BLACK": "Nero opaco",
"MATTE_WHITE": "Bianco opaco",
"MATTE_GRAY": "Grigio opaco"