182 lines
6.3 KiB
TypeScript
182 lines
6.3 KiB
TypeScript
import { ProductResponse, ProductCardResponse } from '../services/api/catalogService';
|
|
import { Product } from '../types';
|
|
import { getCatalogPriceInfo, parseMoney } from '../lib/utils';
|
|
|
|
/**
|
|
* Normalize a list/card API product into a full ProductResponse with numeric variant prices.
|
|
*/
|
|
export function mapProductCardToProductResponse(
|
|
p: ProductCardResponse | Record<string, any>
|
|
): ProductResponse {
|
|
const price = parseMoney(p.price);
|
|
const comparePrice = p.compare_price != null ? parseMoney(p.compare_price) : null;
|
|
|
|
const colorsFromCard = Array.isArray((p as any).colors)
|
|
? (p as any).colors.filter(Boolean)
|
|
: (p as any).color
|
|
? [(p as any).color]
|
|
: [];
|
|
|
|
const variants =
|
|
Array.isArray((p as any).variants) && (p as any).variants.length > 0
|
|
? (p as any).variants.map((v: any) => ({
|
|
...v,
|
|
price: parseMoney(v.price),
|
|
compare_price:
|
|
v.compare_price != null && v.compare_price !== ''
|
|
? parseMoney(v.compare_price)
|
|
: null,
|
|
cost_price: parseMoney(v.cost_price),
|
|
attributes: Array.isArray(v.attributes) ? v.attributes : [],
|
|
}))
|
|
: [
|
|
{
|
|
variant_id: p.first_variant_id || p.product_id,
|
|
product_id: p.product_id,
|
|
sku: (p as any).sku || '',
|
|
barcode: null,
|
|
price,
|
|
compare_price: comparePrice && comparePrice > 0 ? comparePrice : null,
|
|
cost_price: 0,
|
|
low_stock_threshold: 5,
|
|
status: 'active',
|
|
available_stock: p.stock_count,
|
|
attributes: colorsFromCard.map((color: string, index: number) => ({
|
|
id: `color-${index}`,
|
|
variant_id: p.first_variant_id || p.product_id,
|
|
attribute_id: `color-${index}`,
|
|
attribute_value: color,
|
|
attribute_name: 'Color',
|
|
attribute_code: 'color',
|
|
})),
|
|
images: [],
|
|
},
|
|
];
|
|
|
|
return {
|
|
product_id: p.product_id,
|
|
category_id: p.category_id || '',
|
|
brand_id: (p as any).brand_id || null,
|
|
device_series_id: (p as any).device_series_id || null,
|
|
device_model_id: (p as any).device_model_id || null,
|
|
name: p.name,
|
|
slug: p.slug,
|
|
full_path: (p as any).full_path || `/products/${p.slug}`,
|
|
description: (p as any).description ?? null,
|
|
status: (p as any).status || 'active',
|
|
created_at: (p as any).created_at || '',
|
|
rating: (() => {
|
|
const n = Number((p as any).rating ?? (p as any).average_rating ?? (p as any).avg_rating);
|
|
return Number.isFinite(n) && n > 0 ? n : undefined;
|
|
})(),
|
|
badge: (p.badge as ProductResponse['badge']) || null,
|
|
review_count: (p as any).review_count || 12,
|
|
discount_percent: parseMoney((p as any).discount_percent) || undefined,
|
|
color: (p as any).color || colorsFromCard[0] || null,
|
|
colors: colorsFromCard,
|
|
brand_name: (p as any).brand_name || null,
|
|
images:
|
|
(p as any).images?.length > 0
|
|
? (p as any).images
|
|
: p.thumbnail_url
|
|
? [
|
|
{
|
|
image_id: '1',
|
|
product_id: p.product_id,
|
|
image_url: p.thumbnail_url,
|
|
alt_text: p.name,
|
|
sort_order: 0,
|
|
is_banner: true,
|
|
},
|
|
]
|
|
: [],
|
|
variants,
|
|
} as ProductResponse;
|
|
}
|
|
|
|
/**
|
|
* Standardizes the mapping from database ProductResponse / ProductCardResponse (from API)
|
|
* to the storefront's local Product interface.
|
|
*/
|
|
export function mapCatalogProductToProduct(
|
|
p: ProductResponse | ProductCardResponse | Record<string, any>
|
|
): Product {
|
|
const normalized: ProductResponse =
|
|
Array.isArray((p as any).variants) && (p as any).variants.length > 0
|
|
? ({
|
|
...mapProductCardToProductResponse(p),
|
|
...p,
|
|
variants: (p as any).variants.map((v: any) => ({
|
|
...v,
|
|
price: parseMoney(v.price),
|
|
compare_price:
|
|
v.compare_price != null && v.compare_price !== ''
|
|
? parseMoney(v.compare_price)
|
|
: null,
|
|
})),
|
|
} as ProductResponse)
|
|
: mapProductCardToProductResponse(p);
|
|
|
|
const { price, priceMax, oldPrice, discount } = getCatalogPriceInfo(normalized);
|
|
|
|
const hasTimer = false;
|
|
|
|
return {
|
|
id: normalized.slug,
|
|
product_id: normalized.product_id,
|
|
first_variant_id:
|
|
(p as any).first_variant_id ||
|
|
normalized.variants?.[0]?.variant_id ||
|
|
undefined,
|
|
slug: normalized.slug,
|
|
name: normalized.name,
|
|
price,
|
|
priceMax: priceMax && priceMax > price ? priceMax : undefined,
|
|
oldPrice: oldPrice && oldPrice > price ? oldPrice : undefined,
|
|
rating: normalized.rating || 5.0,
|
|
reviewsCount: normalized.review_count || 12,
|
|
image:
|
|
(p as any).thumbnail_url ||
|
|
(normalized.images && normalized.images[0] ? normalized.images[0].image_url : ''),
|
|
imageHover:
|
|
normalized.images && normalized.images[1]
|
|
? normalized.images[1].image_url
|
|
: (p as any).thumbnail_url || undefined,
|
|
discount,
|
|
category: normalized.category_id,
|
|
brand: normalized.brand_id || (p as any).brand_name || 'iFixKart',
|
|
// Deal countdown belongs only on the Deals Of The Day card — not catalog tiles
|
|
hasTimer,
|
|
timeRemaining: undefined,
|
|
isHot: normalized.badge === 'HOT',
|
|
isFeatured: normalized.badge === 'HOT' || (normalized.rating !== undefined && normalized.rating >= 4.8),
|
|
isNewArrival: normalized.badge === 'NEW' || !normalized.badge,
|
|
isRecentlyLaunched: normalized.badge === 'NEW',
|
|
isTechGadget: true,
|
|
totalStock: typeof (p as any).stock_count === 'number' ? (p as any).stock_count : 50,
|
|
soldCount: 15,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Deterministically selects the Hot Deal product from a list.
|
|
* Priority:
|
|
* 1. Product tagged "Hot Deal" (isHot === true)
|
|
* 2. Product with highest discount percentage
|
|
* 3. Fallback to first available product
|
|
*/
|
|
export function getHotDealProduct(products: Product[]): Product | null {
|
|
if (!products || products.length === 0) return null;
|
|
|
|
const hotTagged = products.find((p) => p.isHot);
|
|
if (hotTagged) return hotTagged;
|
|
|
|
const sortedByDiscount = [...products].sort(
|
|
(a, b) => (b.discount || 0) - (a.discount || 0)
|
|
);
|
|
if (sortedByDiscount[0] && (sortedByDiscount[0].discount || 0) > 0) {
|
|
return sortedByDiscount[0];
|
|
}
|
|
|
|
return products[0];
|
|
}
|