195 lines
6.9 KiB
TypeScript
195 lines
6.9 KiB
TypeScript
import api from './api';
|
|
import { PRODUCTS, Product, PetType } from "../data/products";
|
|
|
|
const CATEGORY_SLUG_TO_NAME: Record<string, string> = {
|
|
'joints': 'مفاصل و استخوان',
|
|
'immune': 'تقویت سیستم ایمنی و گوارش',
|
|
'energy': 'ویتامینها و انرژیبخشها',
|
|
'special-care': 'مراقبتهای ویژه (پوست، دندان و چشم)',
|
|
'general': 'تقویت عمومی',
|
|
'nutrition': 'تغذیه تخصصی',
|
|
'supplements': 'مکملهای غذایی و درمانی',
|
|
};
|
|
|
|
export class ProductService {
|
|
private static instance: ProductService;
|
|
|
|
private constructor() {}
|
|
|
|
public static getInstance(): ProductService {
|
|
if (!ProductService.instance) {
|
|
ProductService.instance = new ProductService();
|
|
}
|
|
return ProductService.instance;
|
|
}
|
|
|
|
private mapBackendToFrontend(data: any): Product {
|
|
// Find the local static product to inherit functions like calculateDosage
|
|
const local = PRODUCTS.find(p =>
|
|
p.artNo === data.artNo ||
|
|
p.id === data.productGroup ||
|
|
(data.slug && data.slug.includes(p.id))
|
|
);
|
|
|
|
const safeParse = (val: any, fallback: any) => {
|
|
if (!val) return fallback;
|
|
if (typeof val === 'string') {
|
|
try { return JSON.parse(val); } catch (e) { return fallback; }
|
|
}
|
|
return val;
|
|
};
|
|
|
|
return {
|
|
id: data.id,
|
|
artNo: data.artNo,
|
|
name: data.nameFa || data.name || '',
|
|
nameFa: data.nameFa || data.name || '',
|
|
nameEn: data.nameEn || '',
|
|
scientificTagline: data.scientificTagline,
|
|
description: data.description,
|
|
shortDescription: data.shortDescription || '',
|
|
price: data.priceDisplay || `${data.priceValue.toLocaleString('fa-IR')} تومان`,
|
|
priceValue: Number(data.priceValue),
|
|
category: data.category?.name || data.category || CATEGORY_SLUG_TO_NAME[data.categorySlug] || data.categorySlug || '',
|
|
categorySlug: data.categorySlug,
|
|
unit: data.unit,
|
|
packageSize: data.packageSize,
|
|
dosage_logic: data.dosageLogic,
|
|
benefits: data.benefits,
|
|
suitableFor: data.suitableFor as PetType,
|
|
requiresRx: Boolean(data.requiresRx),
|
|
storage: data.storage,
|
|
specialBadge: data.specialBadge,
|
|
onSetOfAction: data.onSetOfAction,
|
|
optimisticTemplate: data.optimisticTemplate,
|
|
feedingAdvice: data.dosageLogic || data.feedingAdvice || '',
|
|
slug: data.slug,
|
|
image: (data.imageUrl && data.imageUrl.startsWith('http'))
|
|
? data.imageUrl
|
|
: (local?.image || (data.imageUrl ? `/api${data.imageUrl}` : data.image || '')),
|
|
|
|
main_ingredients: data.ingredientList?.map((i: any) => i.ingredient) || data.ingredients?.split(/[،,-]/).map((s: string) => s.trim()).filter(Boolean) || [],
|
|
symptoms: data.symptoms?.map((s: any) => s.symptom || s) || [],
|
|
keyBenefits: safeParse(data.keyBenefits, []),
|
|
expectedResults: safeParse(data.expectedResults, []),
|
|
benefitsList: safeParse(data.benefitsList, []),
|
|
faqs: safeParse(data.faqs, []),
|
|
analysis: safeParse(data.analysis, {}),
|
|
specialist: safeParse(data.specialist, null),
|
|
relatedProducts: safeParse(data.relatedProducts, []),
|
|
contraindications: safeParse(data.contraindications, []),
|
|
|
|
calculateDosage: local?.calculateDosage || (() => ({ quantity: 0, unit: '', description: '' }))
|
|
};
|
|
}
|
|
|
|
public async getProducts(filters?: {
|
|
category?: string;
|
|
petType?: PetType | "all";
|
|
query?: string;
|
|
symptom?: string;
|
|
page?: number;
|
|
limit?: number;
|
|
}): Promise<{ data: Product[]; meta: { total: number; page: number; lastPage: number; limit: number } }> {
|
|
const params = new URLSearchParams();
|
|
if (filters?.category && filters.category !== "all") params.append('category', filters.category);
|
|
if (filters?.petType && filters.petType !== "all") params.append('petType', filters.petType);
|
|
if (filters?.query) params.append('search', filters.query);
|
|
if (filters?.symptom) params.append('symptom', filters.symptom);
|
|
params.append('page', String(filters?.page || 1));
|
|
params.append('limit', String(filters?.limit || 12));
|
|
|
|
try {
|
|
const response = await api.get(`/products?${params.toString()}`);
|
|
return {
|
|
data: response.data.data.map((item: any) => this.mapBackendToFrontend(item)),
|
|
meta: response.data.meta,
|
|
};
|
|
} catch (error) {
|
|
console.error("[ProductService] Failed to fetch products:", error);
|
|
return { data: [], meta: { total: 0, page: 1, lastPage: 0, limit: 12 } };
|
|
}
|
|
}
|
|
|
|
public async getProductById(id: string): Promise<Product | null> {
|
|
try {
|
|
const response = await api.get(`/products/${id}`);
|
|
return this.mapBackendToFrontend(response.data);
|
|
} catch (error) {
|
|
console.error(`[ProductService] Failed to fetch product ${id}:`, error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async getProductBySlug(slug: string): Promise<Product | null> {
|
|
try {
|
|
const response = await api.get(`/products/${slug}`);
|
|
return this.mapBackendToFrontend(response.data);
|
|
} catch (error) {
|
|
console.error(`[ProductService] Failed to fetch product ${slug}:`, error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public async getFeaturedProducts(): Promise<Product[]> {
|
|
try {
|
|
const response = await api.get('/products?limit=30');
|
|
const all = response.data.data.map((item: any) => this.mapBackendToFrontend(item));
|
|
const featured: Product[] = [];
|
|
const categoriesSeen = new Set<string>();
|
|
|
|
for (const p of all) {
|
|
if (p.category && !categoriesSeen.has(p.category)) {
|
|
featured.push(p);
|
|
categoriesSeen.add(p.category);
|
|
}
|
|
if (featured.length === 4) break;
|
|
}
|
|
|
|
if (featured.length < 4) {
|
|
for (const p of all) {
|
|
if (!featured.some(f => f.id === p.id)) {
|
|
featured.push(p);
|
|
}
|
|
if (featured.length === 4) break;
|
|
}
|
|
}
|
|
return featured;
|
|
} catch (error) {
|
|
console.error("[ProductService] Failed to fetch featured products:", error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
public async getActiveFilters(): Promise<{
|
|
categories: { id: string; name: string; slug: string }[];
|
|
symptoms: string[];
|
|
petTypes: string[];
|
|
}> {
|
|
try {
|
|
const response = await api.get('/products/filters');
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error("[ProductService] Failed to fetch active filters:", error);
|
|
return { categories: [], symptoms: [], petTypes: [] };
|
|
}
|
|
}
|
|
|
|
public async getNavigationFilters(): Promise<{
|
|
id: string;
|
|
name: string;
|
|
slug: string;
|
|
symptoms: string[];
|
|
}[]> {
|
|
try {
|
|
const response = await api.get('/products/navigation-filters');
|
|
return response.data;
|
|
} catch (error) {
|
|
console.error("[ProductService] Failed to fetch navigation filters:", error);
|
|
return [];
|
|
}
|
|
}
|
|
}
|
|
|
|
export const productService = ProductService.getInstance();
|