Files
2026-07-10 16:01:27 +03:30

174 lines
10 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState, useEffect } from 'react';
import { Product, CreateProductData, UpdateProductData, PRODUCT_TYPES } from '@/types/product';
import { PlusIcon, XMarkIcon } from '@heroicons/react/24/outline';
interface ProductFormProps {
product?: Product | null;
packageName: string;
onSubmit: (data: CreateProductData | UpdateProductData) => Promise<void>;
onCancel: () => void;
isLoading: boolean;
}
export default function ProductForm({ product, packageName, onSubmit, onCancel, isLoading }: ProductFormProps) {
const [formData, setFormData] = useState<CreateProductData | UpdateProductData>({
title: '',
price: 0,
type: 4,
discounted_price: '',
daily_price: '',
discount: '',
is_best_seller: false,
sort_order: 0,
});
const [descriptions, setDescriptions] = useState<string[]>([]);
const [newDescription, setNewDescription] = useState('');
useEffect(() => {
if (product) {
setFormData({
title: product.title || '',
price: product.price || 0,
type: product.type || 4,
discounted_price: product.discounted_price || '',
daily_price: product.daily_price || '',
discount: product.discount || '',
is_best_seller: product.is_best_seller || false,
sort_order: product.sort_order || 0,
});
setDescriptions(product.descriptions || []);
}
}, [product]);
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
const { name, value, type } = e.target;
if (type === 'checkbox') {
const { checked } = e.target as HTMLInputElement;
setFormData(prev => ({ ...prev, [name]: checked }));
return;
}
setFormData(prev => ({
...prev,
[name]: name === 'price' || name === 'sort_order' ? parseInt(value) || 0 : value,
}));
};
const handleAddDescription = () => {
if (newDescription.trim()) {
setDescriptions([...descriptions, newDescription.trim()]);
setNewDescription('');
}
};
const handleRemoveDescription = (index: number) => {
setDescriptions(descriptions.filter((_, i) => i !== index));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const submitData = {
...formData,
...(descriptions.length > 0 && { descriptions }),
};
await onSubmit(submitData);
};
const inputClass = 'block w-full px-4 py-2.5 text-sm text-gray-900 dark:text-gray-100 border border-gray-200 dark:border-gray-700 rounded-xl bg-gray-50 dark:bg-gray-800/60 placeholder-gray-400 dark:placeholder-gray-500 focus:ring-2 focus:ring-indigo-500/20 focus:border-indigo-500 dark:focus:border-indigo-400 transition-colors';
const labelClass = 'block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1.5';
return (
<form onSubmit={handleSubmit} className="space-y-6 bg-white dark:bg-gray-900 p-6 rounded-2xl ring-1 ring-gray-200 dark:ring-gray-800 shadow-sm">
<div className="pb-4 border-b border-gray-100 dark:border-gray-800">
<h3 className="text-lg font-semibold text-gray-900 dark:text-gray-100">
{product ? 'ویرایش محصول' : 'ایجاد محصول جدید'} برای پکیج {packageName}
</h3>
</div>
<div className="grid grid-cols-1 gap-5 md:grid-cols-2">
<div>
<label htmlFor="title" className={labelClass}>عنوان <span className="text-red-500">*</span></label>
<input type="text" id="title" name="title" required value={formData.title || ''} onChange={handleInputChange} className={inputClass} placeholder="مثال: اشتراک ماهانه" />
</div>
<div>
<label htmlFor="price" className={labelClass}>قیمت (تومان) <span className="text-red-500">*</span></label>
<input type="number" id="price" name="price" required min="0" value={formData.price || ''} onChange={handleInputChange} className={inputClass} placeholder="مثال: 60000" />
</div>
<div>
<label htmlFor="type" className={labelClass}>نوع اشتراک <span className="text-red-500">*</span></label>
<select id="type" name="type" required value={formData.type || 4} onChange={handleInputChange} className={inputClass}>
{Object.entries(PRODUCT_TYPES).map(([value, label]) => (
<option key={value} value={value}>{label}</option>
))}
</select>
</div>
<div>
<label htmlFor="discounted_price" className={labelClass}>قیمت کلی تخفیف خورده <span className="text-xs text-gray-400">(فقط نمایشی)</span></label>
<input type="text" id="discounted_price" name="discounted_price" value={formData.discounted_price || ''} onChange={handleInputChange} dir="rtl" className={inputClass} placeholder="مثال: ۱٬۴۶۰٬۰۰۰" />
</div>
<div>
<label htmlFor="daily_price" className={labelClass}>قیمت روزانه <span className="text-xs text-gray-400">(فقط نمایشی)</span></label>
<input type="text" id="daily_price" name="daily_price" value={formData.daily_price || ''} onChange={handleInputChange} dir="rtl" className={inputClass} placeholder="مثال: معادل روزانه ۴٬۰۰۰ تومان" />
</div>
<div>
<label htmlFor="discount" className={labelClass}>تخفیف <span className="text-xs text-gray-400">(فقط نمایشی)</span></label>
<input type="text" id="discount" name="discount" value={formData.discount || ''} onChange={handleInputChange} dir="rtl" className={inputClass} placeholder="مثال: ۵۵٪ تخفیف" />
</div>
<div>
<label htmlFor="sort_order" className={labelClass}>ترتیب نمایش <span className="text-xs text-gray-400">(۰ بالاترین)</span></label>
<input type="number" id="sort_order" name="sort_order" min="0" value={formData.sort_order ?? 0} onChange={handleInputChange} className={inputClass} placeholder="0" />
</div>
<div className="md:col-span-2">
<label className="flex items-center gap-3 cursor-pointer p-3 rounded-xl bg-gray-50 dark:bg-gray-800/60 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors">
<input type="checkbox" name="is_best_seller" checked={!!formData.is_best_seller} onChange={handleInputChange} className="h-4 w-4 text-indigo-600 border-gray-300 dark:border-gray-600 rounded-lg focus:ring-indigo-500 dark:bg-gray-800" />
<div>
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">پرفروش‌ترین اشتراک</span>
<span className="text-xs text-gray-400 mr-2">(فقط برای یک محصول فعال می‌شود)</span>
</div>
</label>
</div>
</div>
<div className="pt-4 border-t border-gray-100 dark:border-gray-800">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">ویژگی‌ها</label>
<div className="flex gap-2">
<input
type="text"
value={newDescription}
onChange={(e) => setNewDescription(e.target.value)}
className={`flex-1 ${inputClass}`}
placeholder="ویژگی جدید را وارد کنید"
onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); handleAddDescription(); } }}
/>
<button type="button" onClick={handleAddDescription} className="inline-flex items-center px-4 py-2.5 rounded-xl bg-indigo-600 text-white text-sm font-medium hover:bg-indigo-700 focus:ring-2 focus:ring-indigo-500/30 transition-colors shadow-sm shadow-indigo-500/20">
<PlusIcon className="h-5 w-5" />
</button>
</div>
{descriptions.length > 0 && (
<ul className="mt-3 space-y-2">
{descriptions.map((desc, index) => (
<li key={index} className="flex items-center justify-between px-4 py-2.5 rounded-xl bg-gray-50 dark:bg-gray-800/60 ring-1 ring-gray-100 dark:ring-gray-800">
<span className="text-sm text-gray-700 dark:text-gray-300">{desc}</span>
<button type="button" onClick={() => handleRemoveDescription(index)} className="text-red-500 hover:text-red-700 dark:text-red-400 transition-colors">
<XMarkIcon className="h-4 w-4" />
</button>
</li>
))}
</ul>
)}
</div>
<div className="flex justify-end gap-3 pt-4 border-t border-gray-100 dark:border-gray-800">
<button type="button" onClick={onCancel} className="px-5 py-2.5 rounded-xl border border-gray-200 dark:border-gray-700 text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
انصراف
</button>
<button type="submit" disabled={isLoading} className="px-5 py-2.5 rounded-xl bg-indigo-600 text-white text-sm font-medium hover:bg-indigo-700 focus:ring-2 focus:ring-indigo-500/30 disabled:opacity-50 transition-colors shadow-sm shadow-indigo-500/20">
{isLoading ? 'در حال ذخیره...' : product ? 'به‌روزرسانی' : 'ایجاد'}
</button>
</div>
</form>
);
}