price
This commit is contained in:
@@ -68,6 +68,7 @@ export interface ItemDetail {
|
|||||||
colour?: string;
|
colour?: string;
|
||||||
size?: string;
|
size?: string;
|
||||||
price: number;
|
price: number;
|
||||||
|
deliveryPrice?: number;
|
||||||
currency: string;
|
currency: string;
|
||||||
remaining: number;
|
remaining: number;
|
||||||
}
|
}
|
||||||
@@ -80,6 +81,7 @@ export interface Item {
|
|||||||
description: string;
|
description: string;
|
||||||
currency: string;
|
currency: string;
|
||||||
price: number;
|
price: number;
|
||||||
|
deliveryPrice?: number;
|
||||||
discount: number;
|
discount: number;
|
||||||
remainings?: string;
|
remainings?: string;
|
||||||
rating: number;
|
rating: number;
|
||||||
|
|||||||
@@ -78,6 +78,12 @@
|
|||||||
} @else {
|
} @else {
|
||||||
<span class="current-price">{{ item.price }} {{ item.currency }}</span>
|
<span class="current-price">{{ item.price }} {{ item.currency }}</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (item.deliveryPrice != null) {
|
||||||
|
<span class="delivery-price">
|
||||||
|
{{ 'cart.deliveryLabel' | translate }}: {{ item.deliveryPrice | number:'1.2-2' }} {{ item.currency }}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="quantity-controls">
|
<div class="quantity-controls">
|
||||||
@@ -116,14 +122,16 @@
|
|||||||
<span class="value">{{ totalPrice() | number:'1.2-2' }} {{ currentCurrency }}</span>
|
<span class="value">{{ totalPrice() | number:'1.2-2' }} {{ currentCurrency }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="summary-row delivery">
|
@if (hasDeliveryPrice()) {
|
||||||
<span>{{ 'cart.deliveryLabel' | translate }}</span>
|
<div class="summary-row delivery">
|
||||||
<span>0 {{ currentCurrency }}</span>
|
<span>{{ 'cart.deliveryLabel' | translate }}</span>
|
||||||
</div>
|
<span class="value">{{ totalDeliveryPrice() | number:'1.2-2' }} {{ currentCurrency }}</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div class="summary-row total">
|
<div class="summary-row total">
|
||||||
<span>{{ 'cart.toPay' | translate }}</span>
|
<span>{{ 'cart.toPay' | translate }}</span>
|
||||||
<span class="total-price">{{ totalPrice() | number:'1.2-2' }} {{ currentCurrency }}</span>
|
<span class="total-price">{{ totalWithDelivery() | number:'1.2-2' }} {{ currentCurrency }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="terms-agreement">
|
<div class="terms-agreement">
|
||||||
|
|||||||
@@ -555,6 +555,12 @@
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #497671;
|
color: #497671;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.delivery-price {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #697777;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dexar quantity controls
|
// Dexar quantity controls
|
||||||
@@ -838,7 +844,7 @@
|
|||||||
color: #6b7280;
|
color: #6b7280;
|
||||||
|
|
||||||
&.delivery {
|
&.delivery {
|
||||||
display: none; // Hide delivery in Novo
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.total {
|
&.total {
|
||||||
@@ -1830,6 +1836,7 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
|
line-clamp: 2;
|
||||||
-webkit-line-clamp: 2;
|
-webkit-line-clamp: 2;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ export class CartComponent implements OnDestroy {
|
|||||||
items;
|
items;
|
||||||
itemCount;
|
itemCount;
|
||||||
totalPrice;
|
totalPrice;
|
||||||
|
totalDeliveryPrice;
|
||||||
|
totalWithDelivery;
|
||||||
|
hasDeliveryPrice;
|
||||||
termsAccepted = false;
|
termsAccepted = false;
|
||||||
isnovo = environment.theme === 'novo';
|
isnovo = environment.theme === 'novo';
|
||||||
|
|
||||||
@@ -68,6 +71,9 @@ export class CartComponent implements OnDestroy {
|
|||||||
this.items = this.cartService.items;
|
this.items = this.cartService.items;
|
||||||
this.itemCount = this.cartService.itemCount;
|
this.itemCount = this.cartService.itemCount;
|
||||||
this.totalPrice = this.cartService.totalPrice;
|
this.totalPrice = this.cartService.totalPrice;
|
||||||
|
this.totalDeliveryPrice = this.cartService.totalDeliveryPrice;
|
||||||
|
this.totalWithDelivery = this.cartService.totalWithDelivery;
|
||||||
|
this.hasDeliveryPrice = this.cartService.hasDeliveryPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
requestLogin(): void {
|
requestLogin(): void {
|
||||||
@@ -195,7 +201,7 @@ export class CartComponent implements OnDestroy {
|
|||||||
createPayment(): void {
|
createPayment(): void {
|
||||||
const orderId = this.generateOrderId();
|
const orderId = this.generateOrderId();
|
||||||
const paymentPayload = {
|
const paymentPayload = {
|
||||||
amount: Number(this.totalPrice()),
|
amount: Number(this.totalWithDelivery()),
|
||||||
currency: 'RUB' as const,
|
currency: 'RUB' as const,
|
||||||
siteuserID: this.getPaymentUserId(),
|
siteuserID: this.getPaymentUserId(),
|
||||||
siteorderID: orderId,
|
siteorderID: orderId,
|
||||||
|
|||||||
@@ -86,6 +86,18 @@ export class ApiService {
|
|||||||
return c.startsWith('0x') ? '#' + c.slice(2) : c;
|
return c.startsWith('0x') ? '#' + c.slice(2) : c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private normalizeOptionalNumber(value: unknown): number | undefined {
|
||||||
|
if (value === null || value === undefined || value === '') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized = typeof value === 'number'
|
||||||
|
? value
|
||||||
|
: Number(String(value).replace(',', '.'));
|
||||||
|
|
||||||
|
return Number.isFinite(normalized) ? normalized : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
/** Resolve relative image URLs (e.g. ./images/x.webp) against site origin */
|
/** Resolve relative image URLs (e.g. ./images/x.webp) against site origin */
|
||||||
private resolveImageUrl(url: string): string {
|
private resolveImageUrl(url: string): string {
|
||||||
if (!url) return '';
|
if (!url) return '';
|
||||||
@@ -102,6 +114,13 @@ export class ApiService {
|
|||||||
private normalizeItem(raw: any): Item {
|
private normalizeItem(raw: any): Item {
|
||||||
const { partnerID, ...rest } = raw;
|
const { partnerID, ...rest } = raw;
|
||||||
const item: Item = { ...rest };
|
const item: Item = { ...rest };
|
||||||
|
const topLevelDeliveryPrice = this.normalizeOptionalNumber(
|
||||||
|
raw.deliveryPrice ?? raw.delivery_price ?? raw.deliveryprice
|
||||||
|
);
|
||||||
|
|
||||||
|
if (topLevelDeliveryPrice !== undefined) {
|
||||||
|
item.deliveryPrice = topLevelDeliveryPrice;
|
||||||
|
}
|
||||||
|
|
||||||
// Extract price/currency/remaining/colour/size from itemDetails[]
|
// Extract price/currency/remaining/colour/size from itemDetails[]
|
||||||
// Note: Go struct tag is "itemdetails" but actual API may send "itemDetails"
|
// Note: Go struct tag is "itemdetails" but actual API may send "itemDetails"
|
||||||
@@ -112,11 +131,23 @@ export class ApiService {
|
|||||||
...d,
|
...d,
|
||||||
colour: this.normalizeColor(d.colour || d.color || ''),
|
colour: this.normalizeColor(d.colour || d.color || ''),
|
||||||
color: undefined,
|
color: undefined,
|
||||||
|
deliveryPrice: this.normalizeOptionalNumber(
|
||||||
|
d.deliveryPrice ?? d.delivery_price ?? d.deliveryprice
|
||||||
|
),
|
||||||
}));
|
}));
|
||||||
if (item.price == null || item.price === 0) item.price = detail.price;
|
if (item.price == null || item.price === 0) item.price = detail.price;
|
||||||
if (!item.currency) item.currency = detail.currency;
|
if (!item.currency) item.currency = detail.currency;
|
||||||
if (!item.colour) item.colour = this.normalizeColor(detail.colour || detail.color || '');
|
if (!item.colour) item.colour = this.normalizeColor(detail.colour || detail.color || '');
|
||||||
if (!item.size) item.size = detail.size || '';
|
if (!item.size) item.size = detail.size || '';
|
||||||
|
if (item.deliveryPrice == null) {
|
||||||
|
const detailDeliveryPrice = this.normalizeOptionalNumber(
|
||||||
|
detail.deliveryPrice ?? detail.delivery_price ?? detail.deliveryprice
|
||||||
|
);
|
||||||
|
|
||||||
|
if (detailDeliveryPrice !== undefined) {
|
||||||
|
item.deliveryPrice = detailDeliveryPrice;
|
||||||
|
}
|
||||||
|
}
|
||||||
// Use remaining from detail for stock level
|
// Use remaining from detail for stock level
|
||||||
if (raw.remaining == null && detail.remaining != null) {
|
if (raw.remaining == null && detail.remaining != null) {
|
||||||
(raw as any).remaining = detail.remaining;
|
(raw as any).remaining = detail.remaining;
|
||||||
|
|||||||
@@ -28,6 +28,17 @@ export class CartService {
|
|||||||
return total + (getDiscountedPrice(item) * item.quantity);
|
return total + (getDiscountedPrice(item) * item.quantity);
|
||||||
}, 0);
|
}, 0);
|
||||||
});
|
});
|
||||||
|
totalDeliveryPrice = computed(() => {
|
||||||
|
const items = this.cartItems();
|
||||||
|
if (!Array.isArray(items)) return 0;
|
||||||
|
return items.reduce((total, item) => total + ((item.deliveryPrice ?? 0) * item.quantity), 0);
|
||||||
|
});
|
||||||
|
totalWithDelivery = computed(() => this.totalPrice() + this.totalDeliveryPrice());
|
||||||
|
hasDeliveryPrice = computed(() => {
|
||||||
|
const items = this.cartItems();
|
||||||
|
if (!Array.isArray(items)) return false;
|
||||||
|
return items.some(item => item.deliveryPrice !== undefined && item.deliveryPrice !== null);
|
||||||
|
});
|
||||||
|
|
||||||
constructor(private apiService: ApiService) {
|
constructor(private apiService: ApiService) {
|
||||||
this.loadCart();
|
this.loadCart();
|
||||||
@@ -41,6 +52,29 @@ export class CartService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private normalizeOptionalNumber(value: unknown): number | undefined {
|
||||||
|
if (value === null || value === undefined || value === '') {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized = typeof value === 'number'
|
||||||
|
? value
|
||||||
|
: Number(String(value).replace(',', '.'));
|
||||||
|
|
||||||
|
return Number.isFinite(normalized) ? normalized : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeCartItem(item: CartItem): CartItem {
|
||||||
|
const { deliveryPrice, ...rest } = item;
|
||||||
|
const normalizedDeliveryPrice = this.normalizeOptionalNumber(deliveryPrice);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...rest,
|
||||||
|
quantity: item.quantity || 1,
|
||||||
|
...(normalizedDeliveryPrice !== undefined ? { deliveryPrice: normalizedDeliveryPrice } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private saveToStorage(items: CartItem[]): void {
|
private saveToStorage(items: CartItem[]): void {
|
||||||
const data = JSON.stringify(items);
|
const data = JSON.stringify(items);
|
||||||
|
|
||||||
@@ -90,10 +124,7 @@ export class CartService {
|
|||||||
try {
|
try {
|
||||||
const items = JSON.parse(json);
|
const items = JSON.parse(json);
|
||||||
if (Array.isArray(items)) {
|
if (Array.isArray(items)) {
|
||||||
this.cartItems.set(items.map(item => ({
|
this.cartItems.set(items.map(item => this.normalizeCartItem(item)));
|
||||||
...item,
|
|
||||||
quantity: item.quantity || 1
|
|
||||||
})));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -118,14 +149,14 @@ export class CartService {
|
|||||||
this.addingItems.add(itemID);
|
this.addingItems.add(itemID);
|
||||||
this.apiService.getItem(itemID).subscribe({
|
this.apiService.getItem(itemID).subscribe({
|
||||||
next: (item) => {
|
next: (item) => {
|
||||||
const cartItem: CartItem = {
|
const cartItem = this.normalizeCartItem({
|
||||||
...item,
|
...item,
|
||||||
quantity,
|
quantity,
|
||||||
...(variant?.colour != null && { colour: variant.colour }),
|
...(variant?.colour != null && { colour: variant.colour }),
|
||||||
...(variant?.size != null && { size: variant.size }),
|
...(variant?.size != null && { size: variant.size }),
|
||||||
...(variant?.price != null && { price: variant.price }),
|
...(variant?.price != null && { price: variant.price }),
|
||||||
...(variant?.currency != null && { currency: variant.currency }),
|
...(variant?.currency != null && { currency: variant.currency }),
|
||||||
};
|
});
|
||||||
this.cartItems.set([...this.cartItems(), cartItem]);
|
this.cartItems.set([...this.cartItems(), cartItem]);
|
||||||
this.addingItems.delete(itemID);
|
this.addingItems.delete(itemID);
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user