feat(admin): implement symptoms management in admin panel product form
This commit is contained in:
parent
717a61a225
commit
8345410376
@ -101,7 +101,7 @@ export class AdminService {
|
||||
const [data, total] = await Promise.all([
|
||||
this.prisma.product.findMany({
|
||||
where, skip, take: limit, orderBy: { createdAt: 'desc' },
|
||||
include: { category: true }
|
||||
include: { category: true, symptoms: true }
|
||||
}),
|
||||
this.prisma.product.count({ where })
|
||||
]);
|
||||
@ -114,7 +114,7 @@ export class AdminService {
|
||||
}
|
||||
|
||||
async createProduct(data: any) {
|
||||
return this.prisma.product.create({
|
||||
const product = await this.prisma.product.create({
|
||||
data: {
|
||||
artNo: data.artNo,
|
||||
nameFa: data.nameFa,
|
||||
@ -138,10 +138,21 @@ export class AdminService {
|
||||
slug: data.slug || data.artNo,
|
||||
}
|
||||
});
|
||||
|
||||
if (data.symptoms && Array.isArray(data.symptoms)) {
|
||||
await this.prisma.productSymptom.createMany({
|
||||
data: data.symptoms.map((s: string) => ({
|
||||
productId: product.id,
|
||||
symptom: s.trim()
|
||||
})).filter((s: any) => s.symptom.length > 0)
|
||||
});
|
||||
}
|
||||
|
||||
return product;
|
||||
}
|
||||
|
||||
async updateProduct(id: string, data: any) {
|
||||
return this.prisma.product.update({
|
||||
const product = await this.prisma.product.update({
|
||||
where: { id },
|
||||
data: {
|
||||
artNo: data.artNo,
|
||||
@ -166,6 +177,20 @@ export class AdminService {
|
||||
slug: data.slug || data.artNo,
|
||||
}
|
||||
});
|
||||
|
||||
if (data.symptoms !== undefined && Array.isArray(data.symptoms)) {
|
||||
await this.prisma.productSymptom.deleteMany({ where: { productId: id } });
|
||||
if (data.symptoms.length > 0) {
|
||||
await this.prisma.productSymptom.createMany({
|
||||
data: data.symptoms.map((s: string) => ({
|
||||
productId: id,
|
||||
symptom: s.trim()
|
||||
})).filter((s: any) => s.symptom.length > 0)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return product;
|
||||
}
|
||||
|
||||
async deleteProduct(id: string) {
|
||||
|
||||
@ -43,7 +43,8 @@ export default function Products() {
|
||||
metaDescription: '',
|
||||
keywords: '',
|
||||
canonicalUrl: '',
|
||||
slug: ''
|
||||
slug: '',
|
||||
symptoms: [] as string[]
|
||||
});
|
||||
|
||||
const fetchData = async () => {
|
||||
@ -104,14 +105,16 @@ export default function Products() {
|
||||
metaDescription: product.metaDescription || '',
|
||||
keywords: product.keywords || '',
|
||||
canonicalUrl: product.canonicalUrl || '',
|
||||
slug: product.slug || ''
|
||||
slug: product.slug || '',
|
||||
symptoms: product.symptoms ? product.symptoms.map((s: any) => s.symptom) : []
|
||||
});
|
||||
} else {
|
||||
setEditingProduct(null);
|
||||
setFormData({
|
||||
artNo: '', nameFa: '', nameEn: '', scientificTagline: '', description: '', shortDescription: '', categoryId: '',
|
||||
priceValue: 0, priceDisplay: '', unit: '', packageSize: 0, dosageLogic: '', suitableFor: 'سگ و گربه',
|
||||
imageUrl: '', metaTitle: '', metaDescription: '', keywords: '', canonicalUrl: '', slug: ''
|
||||
imageUrl: '', metaTitle: '', metaDescription: '', keywords: '', canonicalUrl: '', slug: '',
|
||||
symptoms: []
|
||||
});
|
||||
}
|
||||
setActiveTab('general');
|
||||
@ -335,12 +338,52 @@ export default function Products() {
|
||||
<label className="text-sm font-bold text-gray-700">توضیحات کامل</label>
|
||||
<textarea rows={5} value={formData.description} onChange={(e) => setFormData({...formData, description: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"></textarea>
|
||||
</div>
|
||||
<div className="space-y-2 md:col-span-2">
|
||||
<label className="text-sm font-bold text-gray-700">منطق دوز مصرفی (Dosage Logic)</label>
|
||||
<textarea rows={2} value={formData.dosageLogic} onChange={(e) => setFormData({...formData, dosageLogic: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2 md:col-span-2">
|
||||
<label className="text-sm font-bold text-gray-700">منطق دوز مصرفی (Dosage Logic)</label>
|
||||
<textarea rows={2} value={formData.dosageLogic} onChange={(e) => setFormData({...formData, dosageLogic: e.target.value})} className="w-full px-4 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none"></textarea>
|
||||
</div>
|
||||
<div className="space-y-2 md:col-span-2">
|
||||
<label className="text-sm font-bold text-gray-700">علائم درمانی مرتبط (Symptom Tags)</label>
|
||||
<div className="flex flex-wrap gap-2 p-3 bg-white border border-gray-200 rounded-xl min-h-[50px] items-center focus-within:border-purple-500 focus-within:ring-2 focus-within:ring-purple-200 transition-all">
|
||||
{formData.symptoms && formData.symptoms.map((symptom, idx) => (
|
||||
<span key={idx} className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full text-xs font-bold bg-purple-50 border border-purple-200 text-purple-700">
|
||||
{symptom}
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
const newSymptoms = formData.symptoms.filter((_, i) => i !== idx);
|
||||
setFormData({ ...formData, symptoms: newSymptoms });
|
||||
}}
|
||||
className="text-purple-400 hover:text-purple-600 focus:outline-none transition-colors"
|
||||
>
|
||||
<X className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
<input
|
||||
type="text"
|
||||
placeholder="تایپ و اینتر کنید..."
|
||||
className="flex-1 bg-transparent border-none outline-none text-sm min-w-[150px] text-right font-vazir"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
const input = e.currentTarget;
|
||||
const value = input.value.trim();
|
||||
if (value && !formData.symptoms.includes(value)) {
|
||||
setFormData({
|
||||
...formData,
|
||||
symptoms: [...(formData.symptoms || []), value]
|
||||
});
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-[11px] text-gray-400 font-medium">پس از نوشتن هر علامت درمانی (مثلاً «درد مفاصل»)، کلید Enter را فشار دهید.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Pricing Tab */}
|
||||
<div className={activeTab === 'pricing' ? 'block space-y-4' : 'hidden'}>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user