198 lines
8.4 KiB
TypeScript
198 lines
8.4 KiB
TypeScript
'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, // Default to monthly
|
|
});
|
|
|
|
const [descriptions, setDescriptions] = useState<string[]>([]);
|
|
const [newDescription, setNewDescription] = useState('');
|
|
|
|
useEffect(() => {
|
|
if (product) {
|
|
setFormData({
|
|
title: product.title || '',
|
|
price: product.price || 0,
|
|
type: product.type || 4,
|
|
});
|
|
setDescriptions(product.descriptions || []);
|
|
}
|
|
}, [product]);
|
|
|
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
|
|
const { name, value } = e.target;
|
|
setFormData(prev => ({
|
|
...prev,
|
|
[name]: name === 'price' ? 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);
|
|
};
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit} className="space-y-6 bg-white p-6 rounded-lg shadow">
|
|
<div>
|
|
<h3 className="text-lg font-medium text-gray-900 mb-4">
|
|
{product ? 'ویرایش محصول' : 'ایجاد محصول جدید'} برای پکیج {packageName}
|
|
</h3>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
|
{/* Title */}
|
|
<div>
|
|
<label htmlFor="title" className="block text-sm font-medium text-gray-700 mb-1">
|
|
عنوان <span className="text-red-500">*</span>
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="title"
|
|
name="title"
|
|
required
|
|
value={formData.title || ''}
|
|
onChange={handleInputChange}
|
|
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
|
placeholder="مثال: اشتراک ماهانه"
|
|
/>
|
|
</div>
|
|
|
|
{/* Price */}
|
|
<div>
|
|
<label htmlFor="price" className="block text-sm font-medium text-gray-700 mb-1">
|
|
قیمت (تومان) <span className="text-red-500">*</span>
|
|
</label>
|
|
<input
|
|
type="number"
|
|
id="price"
|
|
name="price"
|
|
required
|
|
min="0"
|
|
value={formData.price || ''}
|
|
onChange={handleInputChange}
|
|
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
|
placeholder="مثال: 60000"
|
|
/>
|
|
</div>
|
|
|
|
{/* Type */}
|
|
<div>
|
|
<label htmlFor="type" className="block text-sm font-medium text-gray-700 mb-1">
|
|
نوع اشتراک <span className="text-red-500">*</span>
|
|
</label>
|
|
<select
|
|
id="type"
|
|
name="type"
|
|
required
|
|
value={formData.type || 4}
|
|
onChange={handleInputChange}
|
|
className="block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
|
>
|
|
{Object.entries(PRODUCT_TYPES).map(([value, label]) => (
|
|
<option key={value} value={value}>
|
|
{label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Descriptions */}
|
|
<div>
|
|
<label className="block text-sm font-medium text-gray-700 mb-1">
|
|
ویژگیها
|
|
</label>
|
|
<div className="flex space-x-2 space-x-reverse">
|
|
<input
|
|
type="text"
|
|
value={newDescription}
|
|
onChange={(e) => setNewDescription(e.target.value)}
|
|
className="flex-1 px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
|
|
placeholder="ویژگی جدید را وارد کنید"
|
|
onKeyPress={(e) => {
|
|
if (e.key === 'Enter') {
|
|
e.preventDefault();
|
|
handleAddDescription();
|
|
}
|
|
}}
|
|
/>
|
|
<button
|
|
type="button"
|
|
onClick={handleAddDescription}
|
|
className="inline-flex items-center px-3 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
>
|
|
<PlusIcon className="h-5 w-5" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Descriptions List */}
|
|
{descriptions.length > 0 && (
|
|
<ul className="mt-3 space-y-2">
|
|
{descriptions.map((desc, index) => (
|
|
<li key={index} className="flex items-center justify-between bg-gray-50 px-3 py-2 rounded-md">
|
|
<span className="text-sm text-gray-700">{desc}</span>
|
|
<button
|
|
type="button"
|
|
onClick={() => handleRemoveDescription(index)}
|
|
className="text-red-600 hover:text-red-900"
|
|
>
|
|
<XMarkIcon className="h-5 w-5" />
|
|
</button>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</div>
|
|
|
|
{/* Form Actions */}
|
|
<div className="flex justify-end space-x-3 space-x-reverse pt-4 border-t">
|
|
<button
|
|
type="button"
|
|
onClick={onCancel}
|
|
className="px-4 py-2 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
>
|
|
انصراف
|
|
</button>
|
|
<button
|
|
type="submit"
|
|
disabled={isLoading}
|
|
className="px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50"
|
|
>
|
|
{isLoading ? 'در حال ذخیره...' : product ? 'بهروزرسانی' : 'ایجاد'}
|
|
</button>
|
|
</div>
|
|
</form>
|
|
);
|
|
} |