canina/frontend/application/lib/services/productService.ts
parsa aghaei 7615aa0e51 fix: resolve seed encoding, search param, and wiki data source issues
- Fix seed-products.ts TS error (implicit any) and BOM handling
- Re-run full seed to restore correct Persian encoding in DB
- Fix productService query→search param mismatch
- Fix IngredientWiki hardcoded port 4000→use settingsStore
- Restore seed-products-data.json from git after accidental corruption
2026-07-11 15:40:49 +03:30

127 lines
4.8 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);
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.name,
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,
storage: data.storage,
specialBadge: data.specialBadge,
onSetOfAction: data.onSetOfAction,
optimisticTemplate: data.optimisticTemplate,
feedingAdvice: data.feedingAdvice,
image: data.imageUrl?.startsWith('http') ? data.imageUrl : data.imageUrl ? `http://localhost:4001${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;
}): Promise<Product[]> {
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);
try {
const response = await api.get(`/products?${params.toString()}`);
return response.data.data.map((item: any) => this.mapBackendToFrontend(item));
} catch (error) {
console.error("[ProductService] Failed to fetch products:", error);
return [];
}
}
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');
return response.data.data.slice(0, 4).map((item: any) => this.mapBackendToFrontend(item));
} catch (error) {
console.error("[ProductService] Failed to fetch featured products:", error);
return [];
}
}
}
export const productService = ProductService.getInstance();