feat(products): remove mock static fallbacks and add dynamic specialist recommendation management in database and admin panel
This commit is contained in:
parent
1ca06d5740
commit
00839017ff
@ -476,6 +476,7 @@ export class AdminService {
|
||||
...(data.expectedResults !== undefined
|
||||
? { expectedResults: data.expectedResults }
|
||||
: {}),
|
||||
...(data.specialist !== undefined ? { specialist: data.specialist } : {}),
|
||||
...(data.isPreorder !== undefined ? { isPreorder: data.isPreorder } : {}),
|
||||
...(data.preorderDeposit !== undefined
|
||||
? { preorderDeposit: data.preorderDeposit }
|
||||
@ -616,6 +617,7 @@ export class AdminService {
|
||||
...(data.expectedResults !== undefined
|
||||
? { expectedResults: data.expectedResults }
|
||||
: {}),
|
||||
...(data.specialist !== undefined ? { specialist: data.specialist } : {}),
|
||||
...(data.isPreorder !== undefined ? { isPreorder: data.isPreorder } : {}),
|
||||
...(data.preorderDeposit !== undefined
|
||||
? { preorderDeposit: data.preorderDeposit }
|
||||
|
||||
@ -332,6 +332,15 @@ export class ProductDto {
|
||||
@IsArray()
|
||||
expectedResults?: Array<{ icon: string; text: string }>;
|
||||
|
||||
@ApiPropertyOptional({ description: 'توصیه و نظر پزشک متخصص (Specialist Endorsement)' })
|
||||
@IsOptional()
|
||||
specialist?: {
|
||||
name?: string;
|
||||
title?: string;
|
||||
image?: string;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
@ApiPropertyOptional({ description: 'پیکربندی جامع دوز و دادههای تکمیلی' })
|
||||
@IsOptional()
|
||||
dosageConfig?: Record<string, any>;
|
||||
|
||||
@ -232,7 +232,7 @@ export default function Products() {
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [previewImage, setPreviewImage] = useState<{ url: string; title: string; subtitle?: string; artNo?: string } | null>(null);
|
||||
const [isMediaSelectorOpen, setIsMediaSelectorOpen] = useState(false);
|
||||
const [mediaTargetField, setMediaTargetField] = useState<'imageUrl' | 'ogImage' | 'podcastUrl' | 'podcastCover' | 'videoUrl' | 'videoCover' | 'pdfUrl' | 'pdfCover' | 'gallery'>('imageUrl');
|
||||
const [mediaTargetField, setMediaTargetField] = useState<'imageUrl' | 'ogImage' | 'podcastUrl' | 'podcastCover' | 'videoUrl' | 'videoCover' | 'pdfUrl' | 'pdfCover' | 'specialistImage' | 'gallery'>('imageUrl');
|
||||
const [editingProduct, setEditingProduct] = useState<Product | null>(null);
|
||||
const [deleteTargetId, setDeleteTargetId] = useState<string | null>(null);
|
||||
const [activeTab, setActiveTab] = useState('general');
|
||||
@ -297,6 +297,12 @@ export default function Products() {
|
||||
preorderDeposit: '' as number | string,
|
||||
roundingStep: '' as number | string,
|
||||
roundingMode: '' as string,
|
||||
specialist: {
|
||||
name: '',
|
||||
title: '',
|
||||
image: '',
|
||||
message: '',
|
||||
},
|
||||
});
|
||||
|
||||
// Smart Pricing & Rounding state
|
||||
@ -482,6 +488,14 @@ export default function Products() {
|
||||
? (Array.isArray((product as unknown as { contraindications?: any }).contraindications) ? (product as unknown as { contraindications?: any }).contraindications.join('\n') : String((product as unknown as { contraindications?: any }).contraindications))
|
||||
: '');
|
||||
|
||||
const rawSpecialist = dosageConfig.specialist || (product as unknown as { specialist?: any }).specialist;
|
||||
const initialSpecialist = rawSpecialist && typeof rawSpecialist === 'object' ? {
|
||||
name: rawSpecialist.name || '',
|
||||
title: rawSpecialist.title || '',
|
||||
image: rawSpecialist.image || '',
|
||||
message: rawSpecialist.message || '',
|
||||
} : { name: '', title: '', image: '', message: '' };
|
||||
|
||||
setFormData({
|
||||
artNo: product.artNo || '',
|
||||
nameFa: product.nameFa || '',
|
||||
@ -542,6 +556,7 @@ export default function Products() {
|
||||
preorderDeposit: product.preorderDeposit !== undefined && product.preorderDeposit !== null ? product.preorderDeposit : '',
|
||||
roundingStep: product.roundingStep !== undefined && product.roundingStep !== null ? product.roundingStep : '',
|
||||
roundingMode: product.roundingMode || '',
|
||||
specialist: initialSpecialist,
|
||||
});
|
||||
} else {
|
||||
setEditingProduct(null);
|
||||
@ -574,7 +589,8 @@ export default function Products() {
|
||||
podcastUrl: '', podcastTitle: '', podcastDescription: '', podcastCover: '',
|
||||
videoUrl: '', videoTitle: '', videoDescription: '', videoCover: '',
|
||||
pdfUrl: '', pdfTitle: '', pdfDescription: '', pdfCover: '',
|
||||
symptoms: [], isPreorder: false, preorderDeposit: ''
|
||||
symptoms: [], isPreorder: false, preorderDeposit: '',
|
||||
specialist: { name: '', title: '', image: '', message: '' }
|
||||
});
|
||||
}
|
||||
setIsModalOpen(true);
|
||||
@ -916,7 +932,15 @@ export default function Products() {
|
||||
slug: derivedSlug,
|
||||
symptoms: formData.symptoms || [],
|
||||
isPreorder: Boolean(formData.isPreorder),
|
||||
preorderDeposit: formData.preorderDeposit ? String(formData.preorderDeposit) : undefined
|
||||
preorderDeposit: formData.preorderDeposit ? String(formData.preorderDeposit) : undefined,
|
||||
specialist: formData.specialist && (formData.specialist.name?.trim() || formData.specialist.message?.trim())
|
||||
? {
|
||||
name: (formData.specialist.name || '').trim(),
|
||||
title: (formData.specialist.title || '').trim(),
|
||||
image: (formData.specialist.image || '').trim(),
|
||||
message: (formData.specialist.message || '').trim(),
|
||||
}
|
||||
: null,
|
||||
};
|
||||
|
||||
if (editingProduct) {
|
||||
@ -1822,6 +1846,103 @@ export default function Products() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Specialist Endorsement Card */}
|
||||
<div className="bg-white p-5 rounded-2xl border border-gray-200 shadow-sm space-y-4">
|
||||
<div className="flex items-center justify-between border-b pb-2">
|
||||
<h4 className="text-sm font-black text-gray-800 flex items-center gap-2">
|
||||
<Activity className="w-4 h-4 text-canina-blue" />
|
||||
<span>توصیه و نظر پزشک متخصص (Specialist Endorsement)</span>
|
||||
</h4>
|
||||
{formData.specialist?.name && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setFormData({
|
||||
...formData,
|
||||
specialist: { name: '', title: '', image: '', message: '' }
|
||||
})}
|
||||
className="text-xs text-red-600 hover:underline font-bold"
|
||||
>
|
||||
پاک کردن نظر پزشک ✕
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-[11px] text-gray-500 font-medium">
|
||||
در صورت تکمیل این بخش، کادر نظر و توصیه بالینی پزشک در کاتالوگ و صفحه محصول نمایش داده میشود. در صورت خالی ماندن، هیچ کادری نمایش داده نخواهد شد.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-bold text-gray-700 block">نام پزشک / متخصص</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.specialist?.name || ''}
|
||||
onChange={(e) => setFormData({
|
||||
...formData,
|
||||
specialist: { ...(formData.specialist || { name: '', title: '', image: '', message: '' }), name: e.target.value }
|
||||
})}
|
||||
placeholder="مثال: دکتر علیرضا رضایی یا Dr. Klaus Henning"
|
||||
className="w-full px-3 py-2 rounded-xl border border-gray-200 focus:border-purple-500 outline-none text-xs font-bold font-vazir"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-bold text-gray-700 block">عنوان / تخصص پزشک</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData.specialist?.title || ''}
|
||||
onChange={(e) => setFormData({
|
||||
...formData,
|
||||
specialist: { ...(formData.specialist || { name: '', title: '', image: '', message: '' }), title: e.target.value }
|
||||
})}
|
||||
placeholder="مثال: متخصص جراحی استخوان و مفاصل دامپزشکی"
|
||||
className="w-full px-3 py-2 rounded-xl border border-gray-200 focus:border-purple-500 outline-none text-xs font-bold font-vazir"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-bold text-gray-700 block">تصویر پزشک (اختیاری)</label>
|
||||
<div className="flex gap-2 items-center">
|
||||
<input
|
||||
type="text"
|
||||
value={formData.specialist?.image || ''}
|
||||
onChange={(e) => setFormData({
|
||||
...formData,
|
||||
specialist: { ...(formData.specialist || { name: '', title: '', image: '', message: '' }), image: e.target.value }
|
||||
})}
|
||||
placeholder="آدرس تصویر یا انتخاب از رسانهها (/uploads/doctor.jpg)"
|
||||
className="flex-1 px-3 py-2 rounded-xl border border-gray-200 focus:border-purple-500 outline-none text-xs font-mono"
|
||||
dir="ltr"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setMediaTargetField('specialistImage');
|
||||
setIsMediaSelectorOpen(true);
|
||||
}}
|
||||
className="px-3 py-2 bg-purple-600 hover:bg-purple-700 text-white rounded-xl text-xs font-bold whitespace-nowrap cursor-pointer flex items-center gap-1"
|
||||
>
|
||||
<ImageIcon className="w-3.5 h-3.5" />
|
||||
انتخاب تصویر
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label className="text-xs font-bold text-gray-700 block">متن توصیه / نظر بالینی پزشک</label>
|
||||
<textarea
|
||||
rows={3}
|
||||
value={formData.specialist?.message || ''}
|
||||
onChange={(e) => setFormData({
|
||||
...formData,
|
||||
specialist: { ...(formData.specialist || { name: '', title: '', image: '', message: '' }), message: e.target.value }
|
||||
})}
|
||||
placeholder="متن نظر پزشک درباره اثربخشی، کیفیت مواد اولیه یا پروتکل درمانی مکمل..."
|
||||
className="w-full px-3 py-2 rounded-xl border border-gray-200 focus:border-purple-500 outline-none text-xs leading-relaxed font-vazir resize-none"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Benefits & Analysis Tab */}
|
||||
@ -4030,6 +4151,14 @@ export default function Products() {
|
||||
if (!formData.images.includes(url)) {
|
||||
setFormData({ ...formData, images: [...formData.images, url] });
|
||||
}
|
||||
} else if (mediaTargetField === 'specialistImage') {
|
||||
setFormData({
|
||||
...formData,
|
||||
specialist: {
|
||||
...(formData.specialist || { name: '', title: '', image: '', message: '' }),
|
||||
image: url,
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setFormData({ ...formData, [mediaTargetField]: url });
|
||||
if (mediaTargetField === 'imageUrl') setMediaImageError(false);
|
||||
@ -4038,9 +4167,11 @@ export default function Products() {
|
||||
selectedUrl={
|
||||
(mediaTargetField as string) === 'gallery'
|
||||
? undefined
|
||||
: formData[mediaTargetField]?.startsWith('http')
|
||||
? formData[mediaTargetField]
|
||||
: formData[mediaTargetField]
|
||||
: mediaTargetField === 'specialistImage'
|
||||
? (formData.specialist?.image?.startsWith('http') ? formData.specialist?.image : formData.specialist?.image ? `${BASE_DOMAIN}${formData.specialist?.image}` : undefined)
|
||||
: (formData[mediaTargetField] as string)?.startsWith('http')
|
||||
? (formData[mediaTargetField] as string)
|
||||
: (formData[mediaTargetField] as string)
|
||||
? `${BASE_DOMAIN}${formData[mediaTargetField]}`
|
||||
: undefined
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import FlipbookCatalog from "../../components/catalog/FlipbookCatalog";
|
||||
import { productService } from "../../lib/services/productService";
|
||||
import { Product, PRODUCTS } from "../../lib/data/products";
|
||||
import { Product } from "../../lib/data/products";
|
||||
|
||||
export default function CatalogClient() {
|
||||
const [products, setProducts] = useState<Product[]>([]);
|
||||
@ -15,12 +15,12 @@ export default function CatalogClient() {
|
||||
if (res.data && res.data.length > 0) {
|
||||
setProducts(res.data);
|
||||
} else {
|
||||
setProducts(PRODUCTS);
|
||||
setProducts([]);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error("Catalog fetch error, fallback to static products:", err);
|
||||
setProducts(PRODUCTS);
|
||||
console.error("Catalog fetch error:", err);
|
||||
setProducts([]);
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
|
||||
@ -495,19 +495,34 @@ export default function CatalogPageSpread({
|
||||
)}
|
||||
|
||||
{/* Specialist Endorsement */}
|
||||
{product.specialist && (
|
||||
{product.specialist && product.specialist.name && (
|
||||
<div className="bg-white border border-slate-200/80 rounded-xl p-2 flex items-center gap-2 shrink-0 shadow-3xs">
|
||||
<div className="w-7 h-7 rounded-full bg-canina-blue/10 border border-canina-blue/20 text-canina-blue flex items-center justify-center font-black text-xs font-mono shadow-3xs shrink-0">
|
||||
<Stethoscope className="w-3.5 h-3.5" />
|
||||
<div className="w-7 h-7 rounded-full bg-canina-blue/10 border border-canina-blue/20 text-canina-blue flex items-center justify-center font-black text-xs font-mono shadow-3xs shrink-0 overflow-hidden">
|
||||
{product.specialist.image ? (
|
||||
<img
|
||||
src={product.specialist.image}
|
||||
alt={product.specialist.name}
|
||||
className="w-full h-full object-cover"
|
||||
onError={(e) => {
|
||||
(e.target as HTMLElement).style.display = 'none';
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Stethoscope className="w-3.5 h-3.5" />
|
||||
)}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-1 truncate">
|
||||
<span className="text-[8.5px] font-black text-slate-900 truncate">{product.specialist.name}</span>
|
||||
<span className="text-[7.5px] text-slate-500 truncate font-medium">({product.specialist.title})</span>
|
||||
{product.specialist.title && (
|
||||
<span className="text-[7.5px] text-slate-500 truncate font-medium">({product.specialist.title})</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-[8px] text-slate-600 italic truncate mt-0.2">
|
||||
«{product.specialist.message}»
|
||||
</p>
|
||||
{product.specialist.message && (
|
||||
<p className="text-[8px] text-slate-600 italic truncate mt-0.2">
|
||||
«{product.specialist.message}»
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -58,7 +58,7 @@ export interface Product {
|
||||
calculateDosage?: (weight: number, isYoung: boolean) => DosageResult;
|
||||
analysis: Record<string, string>;
|
||||
feedingAdvice: string;
|
||||
specialist: Specialist;
|
||||
specialist?: Specialist;
|
||||
image: string;
|
||||
images?: string[];
|
||||
podcastUrl?: string;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import api from './api';
|
||||
import { PRODUCTS, Product, PetType } from "../data/products";
|
||||
import { Product, PetType } from "../data/products";
|
||||
import { Banner } from "../types";
|
||||
import { getMediaUrl } from "../media";
|
||||
|
||||
@ -85,13 +85,8 @@ export class ProductService {
|
||||
|
||||
private mapBackendToFrontend(inputData: Record<string, unknown>): Product {
|
||||
const data = inputData as unknown as BackendProduct;
|
||||
// Find the local static product to inherit functions like calculateDosage
|
||||
const local = PRODUCTS.find(p =>
|
||||
p.artNo === data.artNo ||
|
||||
p.id === data.productGroup ||
|
||||
(typeof data.slug === 'string' && data.slug.includes(p.id))
|
||||
);
|
||||
|
||||
const dosageConfig = (data.dosageConfig as Record<string, any>) || {};
|
||||
|
||||
const safeParse = <T>(val: unknown, fallback: T): T => {
|
||||
if (!val) return fallback;
|
||||
if (typeof val === 'string') {
|
||||
@ -106,12 +101,15 @@ export class ProductService {
|
||||
|
||||
const priceVal = Number(data.priceValue || 0);
|
||||
|
||||
const defaultSpecialist = local?.specialist || {
|
||||
name: 'تیم تخصصی کنینا',
|
||||
title: 'مشاور علمی و دارویی',
|
||||
image: '/images/vets/vet1.webp',
|
||||
message: 'تمامی محصولات مکمل کنینا دارای استانداردهای دارویی آلمان و تاییدیه کلینیکی هستند.'
|
||||
};
|
||||
const specialistData = dosageConfig.specialist || safeParse<Product['specialist'] | undefined>(data.specialist, undefined);
|
||||
const validSpecialist = specialistData && typeof specialistData === 'object' && (specialistData.name || specialistData.message)
|
||||
? {
|
||||
name: specialistData.name || '',
|
||||
title: specialistData.title || '',
|
||||
image: specialistData.image ? getMediaUrl(specialistData.image) : '',
|
||||
message: specialistData.message || ''
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
id: data.id || '',
|
||||
@ -132,48 +130,43 @@ export class ProductService {
|
||||
benefits: data.benefits || '',
|
||||
suitableFor: (data.suitableFor as PetType) || 'سگ',
|
||||
requiresRx: Boolean(data.requiresRx),
|
||||
storage: (data.dosageConfig as Record<string, any>)?.storageInfo || data.storage,
|
||||
storage: dosageConfig.storageInfo || data.storage,
|
||||
optimisticTemplate: data.optimisticTemplate,
|
||||
feedingAdvice: (data.dosageConfig as Record<string, any>)?.feedingAdvice || data.dosageLogic || data.feedingAdvice || '',
|
||||
showFaqs: (data.dosageConfig as Record<string, any>)?.showFaqs !== undefined ? Boolean((data.dosageConfig as Record<string, any>).showFaqs) : true,
|
||||
faqs: (data.dosageConfig as Record<string, any>)?.faqs && Array.isArray((data.dosageConfig as Record<string, any>).faqs) && (data.dosageConfig as Record<string, any>).faqs.length > 0
|
||||
? (data.dosageConfig as Record<string, any>).faqs
|
||||
: (local?.faqs || []),
|
||||
paoMonths: (data.dosageConfig as Record<string, any>)?.paoMonths || undefined,
|
||||
storageInfo: (data.dosageConfig as Record<string, any>)?.storageInfo || data.storage || undefined,
|
||||
precautions: (data.dosageConfig as Record<string, any>)?.precautions || undefined,
|
||||
lifeStage: (data.dosageConfig as Record<string, any>)?.lifeStage || undefined,
|
||||
keyHighlights: (data.dosageConfig as Record<string, any>)?.keyHighlights || undefined,
|
||||
dosageConfig: (data.dosageConfig as Record<string, any>) || undefined,
|
||||
feedingAdvice: dosageConfig.feedingAdvice || data.dosageLogic || data.feedingAdvice || '',
|
||||
showFaqs: dosageConfig.showFaqs !== undefined ? Boolean(dosageConfig.showFaqs) : true,
|
||||
faqs: dosageConfig.faqs && Array.isArray(dosageConfig.faqs) && dosageConfig.faqs.length > 0
|
||||
? dosageConfig.faqs
|
||||
: (data.faqs || []),
|
||||
paoMonths: dosageConfig.paoMonths || undefined,
|
||||
storageInfo: dosageConfig.storageInfo || data.storage || undefined,
|
||||
precautions: dosageConfig.precautions || undefined,
|
||||
lifeStage: dosageConfig.lifeStage || undefined,
|
||||
keyHighlights: dosageConfig.keyHighlights || undefined,
|
||||
dosageConfig: dosageConfig,
|
||||
slug: data.slug,
|
||||
image: (() => {
|
||||
if (!data.imageUrl) return local?.image || '/assets/images/hero-section-image.png';
|
||||
if (data.imageUrl.startsWith('/products/')) return local?.image || '/assets/images/hero-section-image.png';
|
||||
return getMediaUrl(data.imageUrl) || local?.image || '/assets/images/hero-section-image.png';
|
||||
})(),
|
||||
image: data.imageUrl ? getMediaUrl(data.imageUrl) : '/assets/images/hero-section-image.png',
|
||||
images: Array.isArray(data.images) && data.images.length > 0
|
||||
? data.images.map((img: string) => getMediaUrl(img)).filter(Boolean)
|
||||
: (local?.images || []),
|
||||
: [],
|
||||
|
||||
main_ingredients: data.ingredientList?.map(i => i.ingredient) || data.ingredients?.split(/[،,-]/).map(s => s.trim()).filter(Boolean) || [],
|
||||
symptoms: data.symptoms?.map(s => typeof s === 'string' ? s : s.symptom || '').filter(Boolean) || [],
|
||||
keyBenefits: (data.dosageConfig as Record<string, any>)?.keyBenefits && Array.isArray((data.dosageConfig as Record<string, any>).keyBenefits) && (data.dosageConfig as Record<string, any>).keyBenefits.length > 0
|
||||
? (data.dosageConfig as Record<string, any>).keyBenefits
|
||||
: safeParse<Product['keyBenefits']>(data.keyBenefits, local?.keyBenefits || []),
|
||||
expectedResults: (data.dosageConfig as Record<string, any>)?.expectedResults && Array.isArray((data.dosageConfig as Record<string, any>).expectedResults) && (data.dosageConfig as Record<string, any>).expectedResults.length > 0
|
||||
? (data.dosageConfig as Record<string, any>).expectedResults
|
||||
: safeParse<Product['expectedResults']>(data.expectedResults, local?.expectedResults || []),
|
||||
analysis: (data.dosageConfig as Record<string, any>)?.analysis && typeof (data.dosageConfig as Record<string, any>).analysis === 'object' && Object.keys((data.dosageConfig as Record<string, any>).analysis).length > 0
|
||||
? (data.dosageConfig as Record<string, any>).analysis
|
||||
: safeParse<Record<string, string>>(data.compositionTable, local?.analysis || {}),
|
||||
onSetOfAction: (data.dosageConfig as Record<string, any>)?.onSetOfAction || data.onSetOfAction || local?.onSetOfAction || undefined,
|
||||
specialBadge: (data.dosageConfig as Record<string, any>)?.specialBadge || data.specialBadge || local?.specialBadge || undefined,
|
||||
contraindications: (data.dosageConfig as Record<string, any>)?.contraindications
|
||||
? (Array.isArray((data.dosageConfig as Record<string, any>).contraindications) ? (data.dosageConfig as Record<string, any>).contraindications : [(data.dosageConfig as Record<string, any>).contraindications])
|
||||
: (local?.contraindications || undefined),
|
||||
specialist: safeParse<Product['specialist']>(data.specialist, defaultSpecialist),
|
||||
keyBenefits: dosageConfig.keyBenefits && Array.isArray(dosageConfig.keyBenefits) && dosageConfig.keyBenefits.length > 0
|
||||
? dosageConfig.keyBenefits
|
||||
: safeParse<Product['keyBenefits']>(data.keyBenefits, []),
|
||||
expectedResults: dosageConfig.expectedResults && Array.isArray(dosageConfig.expectedResults) && dosageConfig.expectedResults.length > 0
|
||||
? dosageConfig.expectedResults
|
||||
: safeParse<Product['expectedResults']>(data.expectedResults, []),
|
||||
analysis: dosageConfig.analysis && typeof dosageConfig.analysis === 'object' && Object.keys(dosageConfig.analysis).length > 0
|
||||
? dosageConfig.analysis
|
||||
: safeParse<Record<string, string>>(data.compositionTable, {}),
|
||||
onSetOfAction: dosageConfig.onSetOfAction || data.onSetOfAction || undefined,
|
||||
specialBadge: dosageConfig.specialBadge || data.specialBadge || undefined,
|
||||
contraindications: dosageConfig.contraindications
|
||||
? (Array.isArray(dosageConfig.contraindications) ? dosageConfig.contraindications : [dosageConfig.contraindications])
|
||||
: undefined,
|
||||
specialist: validSpecialist,
|
||||
|
||||
calculateDosage: local?.calculateDosage,
|
||||
podcastUrl: data.podcastUrl ? getMediaUrl(data.podcastUrl) : undefined,
|
||||
podcastTitle: data.podcastTitle || undefined,
|
||||
podcastDescription: data.podcastDescription || undefined,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user