517 lines
26 KiB
TypeScript
517 lines
26 KiB
TypeScript
import { useState, useEffect, useCallback } from 'react';
|
||
import { Tag, Plus, Edit2, Trash2, Search, XCircle, CheckCircle2, User, Heart, ShoppingBag, FolderTree, Shield, HelpCircle } from 'lucide-react';
|
||
import { toast } from 'react-hot-toast';
|
||
import api from '../services/api';
|
||
import Spinner from '../components/ui/Spinner';
|
||
import Pagination from '../components/ui/Pagination';
|
||
import ConfirmModal from '../components/ui/ConfirmModal';
|
||
import PriceInput from '../components/ui/PriceInput';
|
||
import Button from '../components/ui/Button';
|
||
import Modal from '../components/ui/Modal';
|
||
|
||
|
||
export interface CouponTarget {
|
||
targetType: string;
|
||
targetId: string;
|
||
modifierType: string;
|
||
modifierValue: number | string;
|
||
}
|
||
|
||
export interface Coupon {
|
||
id: string;
|
||
code: string;
|
||
type: string;
|
||
value: number;
|
||
minCartValue?: number;
|
||
maxCartValue?: number;
|
||
maxUses?: number;
|
||
usedCount?: number;
|
||
expiresAt?: string;
|
||
isActive: boolean;
|
||
targets?: CouponTarget[];
|
||
}
|
||
|
||
export interface CouponFormData {
|
||
code: string;
|
||
type: string;
|
||
value: number | string;
|
||
minCartValue: string;
|
||
maxCartValue: string;
|
||
maxUses: string;
|
||
expiresAt: string;
|
||
isActive: boolean;
|
||
targets: CouponTarget[];
|
||
}
|
||
|
||
export default function Coupons() {
|
||
const [coupons, setCoupons] = useState<Coupon[]>([]);
|
||
const [isLoading, setIsLoading] = useState(true);
|
||
const [search, setSearch] = useState('');
|
||
const [page, setPage] = useState(1);
|
||
const [totalPages, setTotalPages] = useState(1);
|
||
const limit = 10;
|
||
|
||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||
const [editingCoupon, setEditingCoupon] = useState<Coupon | null>(null);
|
||
const [deleteTargetId, setDeleteTargetId] = useState<string | null>(null);
|
||
const [formData, setFormData] = useState<CouponFormData>({
|
||
code: '', type: 'percent', value: 0, minCartValue: '', maxCartValue: '', maxUses: '', expiresAt: '', isActive: true,
|
||
targets: []
|
||
});
|
||
|
||
const fetchData = useCallback(async () => {
|
||
try {
|
||
setIsLoading(true);
|
||
const res = await api.get('/admin/coupons', { params: { page, limit, search } });
|
||
if (res.data?.data) {
|
||
setCoupons(res.data.data);
|
||
setTotalPages(res.data.meta?.lastPage || 1);
|
||
}
|
||
} catch (err) {
|
||
console.error(err);
|
||
} finally {
|
||
setIsLoading(false);
|
||
}
|
||
}, [page, search]);
|
||
|
||
useEffect(() => {
|
||
const timer = setTimeout(() => fetchData(), 500);
|
||
return () => clearTimeout(timer);
|
||
}, [fetchData]);
|
||
|
||
const openModal = (coupon: Coupon | null = null) => {
|
||
if (coupon) {
|
||
setEditingCoupon(coupon);
|
||
setFormData({
|
||
code: coupon.code,
|
||
type: coupon.type,
|
||
value: Number(coupon.value),
|
||
minCartValue: coupon.minCartValue ? String(coupon.minCartValue) : '',
|
||
maxCartValue: coupon.maxCartValue ? String(coupon.maxCartValue) : '',
|
||
maxUses: coupon.maxUses ? String(coupon.maxUses) : '',
|
||
expiresAt: coupon.expiresAt ? new Date(coupon.expiresAt).toISOString().split('T')[0] : '',
|
||
isActive: coupon.isActive,
|
||
targets: coupon.targets || []
|
||
});
|
||
} else {
|
||
setEditingCoupon(null);
|
||
setFormData({
|
||
code: '', type: 'percent', value: 0, minCartValue: '', maxCartValue: '', maxUses: '', expiresAt: '', isActive: true, targets: []
|
||
});
|
||
}
|
||
setIsModalOpen(true);
|
||
};
|
||
|
||
const handleSave = async (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
try {
|
||
const payload = {
|
||
...formData,
|
||
value: Number(formData.value),
|
||
minCartValue: formData.minCartValue ? Number(formData.minCartValue) : null,
|
||
maxCartValue: formData.maxCartValue ? Number(formData.maxCartValue) : null,
|
||
maxUses: formData.maxUses ? Number(formData.maxUses) : null,
|
||
expiresAt: formData.expiresAt || null,
|
||
targets: formData.targets.map(t => ({
|
||
...t, modifierValue: t.modifierValue ? Number(t.modifierValue) : null
|
||
}))
|
||
};
|
||
|
||
if (editingCoupon) {
|
||
await api.put(`/admin/coupons/${editingCoupon.id}`, payload);
|
||
toast.success('کد تخفیف با موفقیت ویرایش شد');
|
||
} else {
|
||
await api.post('/admin/coupons', payload);
|
||
toast.success('کد تخفیف با موفقیت ایجاد شد');
|
||
}
|
||
|
||
setIsModalOpen(false);
|
||
fetchData();
|
||
} catch (err) {
|
||
console.error('Save error', err);
|
||
toast.error('خطا در ذخیره کد تخفیف');
|
||
}
|
||
};
|
||
|
||
const confirmDelete = async () => {
|
||
if (!deleteTargetId) return;
|
||
try {
|
||
await api.delete(`/admin/coupons/${deleteTargetId}`);
|
||
toast.success('کد تخفیف با موفقیت حذف شد');
|
||
fetchData();
|
||
} catch {
|
||
toast.error('خطا در حذف کد تخفیف');
|
||
} finally {
|
||
setDeleteTargetId(null);
|
||
}
|
||
};
|
||
|
||
const handleToggle = async (id: string, currentStatus: boolean) => {
|
||
try {
|
||
await api.put(`/admin/coupons/${id}/toggle`, { isActive: !currentStatus });
|
||
fetchData();
|
||
} catch {
|
||
toast.error('خطا در تغییر وضعیت کد تخفیف');
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-6">
|
||
<div className="flex flex-col sm:flex-row justify-between gap-4">
|
||
<div>
|
||
<h2 className="text-2xl font-black text-gray-900 flex items-center gap-2">
|
||
<Tag className="w-6 h-6 text-purple-600" />
|
||
تخفیفهای پلیمورفیک (Polymorphic)
|
||
</h2>
|
||
<p className="text-gray-500 font-medium mt-1">مدیریت کدهای تخفیف با اهداف چندگانه (کاربران، حیوانات، محصولات و ...)</p>
|
||
</div>
|
||
<button onClick={() => openModal()} className="bg-purple-600 hover:bg-purple-700 text-white px-5 py-2.5 rounded-xl flex items-center gap-2 font-bold transition-all shadow-md shadow-purple-200">
|
||
<Plus className="w-5 h-5" />
|
||
کد تخفیف جدید
|
||
</button>
|
||
</div>
|
||
|
||
<div className="bg-white p-4 rounded-2xl shadow-sm border border-gray-200 flex flex-col sm:flex-row gap-4">
|
||
<div className="relative flex-1">
|
||
<Search className="w-5 h-5 absolute right-4 top-1/2 -translate-y-1/2 text-gray-400" />
|
||
<input type="text" placeholder="جستجوی کد..." value={search} onChange={(e) => { setSearch(e.target.value); setPage(1); }} className="w-full pl-4 pr-12 py-3 rounded-xl border border-gray-200 focus:border-purple-500 outline-none transition-all" />
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
||
<div className="overflow-x-auto">
|
||
<table className="w-full text-right">
|
||
<thead className="bg-gray-50 border-b border-gray-100">
|
||
<tr>
|
||
<th className="py-4 px-6 text-sm font-bold text-gray-500">کد</th>
|
||
<th className="py-4 px-6 text-sm font-bold text-gray-500">پایه تخفیف</th>
|
||
<th className="py-4 px-6 text-sm font-bold text-gray-500">تعداد اهداف اختصاصی</th>
|
||
<th className="py-4 px-6 text-sm font-bold text-gray-500">استفاده شده</th>
|
||
<th className="py-4 px-6 text-sm font-bold text-gray-500">وضعیت</th>
|
||
<th className="py-4 px-6 text-sm font-bold text-gray-500 w-24">عملیات</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody className="divide-y divide-gray-100">
|
||
{isLoading ? (
|
||
<tr><td colSpan={6} className="py-12 text-center"><Spinner size="lg" className="mx-auto text-purple-600" /></td></tr>
|
||
) : coupons.length === 0 ? (
|
||
<tr><td colSpan={6} className="py-12 text-center text-gray-500 font-medium">هیچ کدی یافت نشد</td></tr>
|
||
) : (
|
||
coupons.map((c) => (
|
||
<tr key={c.id} className="hover:bg-gray-50/50 transition-colors">
|
||
<td className="py-4 px-6 font-mono font-bold text-lg text-gray-900">{c.code}</td>
|
||
<td className="py-4 px-6 font-bold text-purple-700">
|
||
{c.type === 'percent' ? `${c.value}٪` : `${Number(c.value).toLocaleString()} تومان`}
|
||
</td>
|
||
<td className="py-4 px-6">
|
||
<span className="inline-flex items-center justify-center px-3 py-1 bg-purple-100 text-purple-700 rounded-full font-bold text-sm">
|
||
{c.targets?.length || 0} هدف
|
||
</span>
|
||
</td>
|
||
<td className="py-4 px-6">
|
||
<span className="font-bold text-gray-800">{c.usedCount}</span>
|
||
{c.maxUses && <span className="text-gray-400 text-xs mr-1">/ {c.maxUses}</span>}
|
||
</td>
|
||
<td className="py-4 px-6">
|
||
<button onClick={() => handleToggle(c.id, c.isActive)} className={`px-3 py-1 text-xs font-bold rounded-lg transition-colors flex items-center gap-1 ${c.isActive ? 'bg-green-100 text-green-700 hover:bg-green-200' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'}`}>
|
||
{c.isActive ? <CheckCircle2 className="w-3 h-3" /> : <XCircle className="w-3 h-3" />}
|
||
{c.isActive ? 'فعال' : 'غیرفعال'}
|
||
</button>
|
||
</td>
|
||
<td className="py-4 px-6">
|
||
<div className="flex items-center gap-2">
|
||
<button onClick={() => openModal(c)} className="p-2 text-blue-600 hover:bg-blue-50 rounded-lg transition-colors"><Edit2 className="w-4 h-4" /></button>
|
||
<button onClick={() => setDeleteTargetId(c.id)} className="p-2 text-red-600 hover:bg-red-50 rounded-lg transition-colors"><Trash2 className="w-4 h-4" /></button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
))
|
||
)}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{!isLoading && totalPages > 1 && (
|
||
<div className="p-4 border-t border-gray-100 flex justify-center bg-gray-50/50">
|
||
<Pagination currentPage={page} totalPages={totalPages} onPageChange={setPage} />
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<ConfirmModal
|
||
isOpen={!!deleteTargetId}
|
||
title="حذف کد تخفیف"
|
||
message="آیا از حذف این کد تخفیف مطمئن هستید؟ این عملیات قابل بازگشت نیست."
|
||
onConfirm={confirmDelete}
|
||
onCancel={() => setDeleteTargetId(null)}
|
||
/>
|
||
|
||
{isModalOpen && <CouponModal formData={formData} setFormData={setFormData} onSave={handleSave} onClose={() => setIsModalOpen(false)} isEditing={!!editingCoupon} />}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
interface CouponModalProps {
|
||
formData: CouponFormData;
|
||
setFormData: React.Dispatch<React.SetStateAction<CouponFormData>>;
|
||
onSave: (e: React.FormEvent) => Promise<void>;
|
||
onClose: () => void;
|
||
isEditing: boolean;
|
||
}
|
||
|
||
// Sub-component for Modal to keep code clean
|
||
function CouponModal({ formData, setFormData, onSave, onClose, isEditing }: CouponModalProps) {
|
||
const addTarget = () => {
|
||
setFormData({
|
||
...formData,
|
||
targets: [...formData.targets, { targetType: 'USER', targetId: '', modifierType: 'override', modifierValue: '' }]
|
||
});
|
||
};
|
||
|
||
const removeTarget = (index: number) => {
|
||
const nt = [...formData.targets];
|
||
nt.splice(index, 1);
|
||
setFormData({ ...formData, targets: nt });
|
||
};
|
||
|
||
const updateTarget = (index: number, key: keyof CouponTarget, val: string | number) => {
|
||
const nt = [...formData.targets];
|
||
nt[index] = { ...nt[index], [key]: val };
|
||
setFormData({ ...formData, targets: nt });
|
||
};
|
||
|
||
const getTypeIcon = (type: string) => {
|
||
switch (type) {
|
||
case 'USER': return <User className="w-4 h-4 text-blue-500" />;
|
||
case 'PET': return <Heart className="w-4 h-4 text-pink-500" />;
|
||
case 'PRODUCT': return <ShoppingBag className="w-4 h-4 text-orange-500" />;
|
||
case 'CATEGORY': return <FolderTree className="w-4 h-4 text-emerald-500" />;
|
||
case 'ROLE': return <Shield className="w-4 h-4 text-indigo-500" />;
|
||
default: return null;
|
||
}
|
||
};
|
||
|
||
return (
|
||
<Modal
|
||
isOpen={true}
|
||
onClose={onClose}
|
||
title={isEditing ? 'ویرایش تخفیف' : 'ساخت تخفیف جدید'}
|
||
icon={Tag}
|
||
maxWidth="4xl"
|
||
footer={
|
||
<div className="flex justify-end gap-2 w-full">
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
onClick={onClose}
|
||
>
|
||
انصراف
|
||
</Button>
|
||
<Button
|
||
variant="primary"
|
||
size="sm"
|
||
type="submit"
|
||
form="couponForm"
|
||
>
|
||
ثبت تخفیف
|
||
</Button>
|
||
</div>
|
||
}
|
||
>
|
||
<form id="couponForm" onSubmit={onSave} className="space-y-6 text-xs font-vazir">
|
||
{/* Base Settings */}
|
||
<div className="space-y-4">
|
||
<h4 className="font-bold text-gray-800 border-b border-gray-100 pb-2">تنظیمات پایه</h4>
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||
<div className="space-y-2">
|
||
<label className="text-xs font-bold text-gray-700 flex items-center gap-1.5">
|
||
<span>کد تخفیف *</span>
|
||
<div className="group relative inline-block">
|
||
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
|
||
<div className="absolute top-full right-1/2 translate-x-1/2 mt-2 hidden group-hover:block w-56 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-50 font-vazir leading-relaxed">
|
||
عبارت یکتا جهت وارد کردن توسط کاربر (مثال: CANINA20). حروف به طور خودکار بزرگ میشوند.
|
||
</div>
|
||
</div>
|
||
</label>
|
||
<input required type="text" value={formData.code} onChange={e => setFormData({...formData, code: e.target.value.toUpperCase()})} dir="ltr" className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 outline-none font-mono text-center uppercase font-bold" />
|
||
</div>
|
||
<div className="space-y-2">
|
||
<label className="text-xs font-bold text-gray-700 flex items-center gap-1.5">
|
||
<span>نوع محاسبه *</span>
|
||
<div className="group relative inline-block">
|
||
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
|
||
<div className="absolute top-full right-1/2 translate-x-1/2 mt-2 hidden group-hover:block w-56 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-50 font-vazir leading-relaxed">
|
||
درصدی (کسر درصد از کل مبلغ) یا مبلغ ثابت (کسر مقدار ریالی مشخص).
|
||
</div>
|
||
</div>
|
||
</label>
|
||
<select required value={formData.type} onChange={e => setFormData({...formData, type: e.target.value})} className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 bg-white">
|
||
<option value="percent">درصدی (٪)</option>
|
||
<option value="fixed">مبلغ ثابت (تومان)</option>
|
||
</select>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<label className="text-xs font-bold text-gray-700 flex items-center gap-1.5">
|
||
<span>مقدار پایه *</span>
|
||
<div className="group relative inline-block">
|
||
<HelpCircle className="w-3.5 h-3.5 text-gray-400 hover:text-purple-600 cursor-help" />
|
||
<div className="absolute top-full right-1/2 translate-x-1/2 mt-2 hidden group-hover:block w-56 p-2.5 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-50 font-vazir leading-relaxed">
|
||
عدد درصد یا مبلغ ثابت تخفیف به تومان.
|
||
</div>
|
||
</div>
|
||
</label>
|
||
{formData.type === 'fixed' ? (
|
||
<PriceInput
|
||
required
|
||
value={formData.value}
|
||
onChange={(val) => setFormData({ ...formData, value: val })}
|
||
placeholder="مثال: ۵۰,۰۰۰"
|
||
className="px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 text-xs"
|
||
/>
|
||
) : (
|
||
<div className="relative">
|
||
<input
|
||
required
|
||
type="number"
|
||
min="0"
|
||
max="100"
|
||
value={formData.value}
|
||
onChange={(e) => setFormData({ ...formData, value: e.target.value })}
|
||
dir="ltr"
|
||
placeholder="مثال: ۲۰"
|
||
className="w-full px-3 py-2.5 rounded-xl border border-gray-200 focus:border-purple-500 font-bold"
|
||
/>
|
||
<span className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 font-bold">%</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||
<div className="space-y-2">
|
||
<label className="text-xs font-bold text-gray-700 flex items-center gap-1">
|
||
<span>حداقل خرید (تومان)</span>
|
||
<div className="group relative inline-block">
|
||
<HelpCircle className="w-3 h-3 text-gray-400 hover:text-purple-600 cursor-help" />
|
||
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-48 p-2 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
|
||
حداقل مبلغ سبد خرید برای فعال شدن این کد.
|
||
</div>
|
||
</div>
|
||
</label>
|
||
<PriceInput
|
||
value={formData.minCartValue}
|
||
onChange={(val) => setFormData({ ...formData, minCartValue: val })}
|
||
placeholder="۰ = بدون محدودیت"
|
||
className="px-3 py-2 rounded-xl border border-gray-200 focus:border-purple-500 text-xs"
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<label className="text-xs font-bold text-gray-700 flex items-center gap-1">
|
||
<span>سقف تخفیف (تومان)</span>
|
||
<div className="group relative inline-block">
|
||
<HelpCircle className="w-3 h-3 text-gray-400 hover:text-purple-600 cursor-help" />
|
||
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-48 p-2 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
|
||
حداکثر سقف ریالی کسر شده در تخفیفهای درصدی.
|
||
</div>
|
||
</div>
|
||
</label>
|
||
<PriceInput
|
||
value={formData.maxCartValue}
|
||
onChange={(val) => setFormData({ ...formData, maxCartValue: val })}
|
||
placeholder="بدون سقف"
|
||
className="px-3 py-2 rounded-xl border border-gray-200 focus:border-purple-500 text-xs"
|
||
/>
|
||
</div>
|
||
<div className="space-y-2">
|
||
<label className="text-xs font-bold text-gray-700 flex items-center gap-1">
|
||
<span>دفعات مجاز استفاده</span>
|
||
<div className="group relative inline-block">
|
||
<HelpCircle className="w-3 h-3 text-gray-400 hover:text-purple-600 cursor-help" />
|
||
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-48 p-2 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
|
||
تعداد کل دفات قابل استفاده توسط کلیه کاربران.
|
||
</div>
|
||
</div>
|
||
</label>
|
||
<input type="number" min="1" value={formData.maxUses} onChange={e => setFormData({...formData, maxUses: e.target.value})} dir="ltr" className="w-full px-3 py-2 rounded-xl border border-gray-200 focus:border-purple-500 text-xs" />
|
||
</div>
|
||
<div className="space-y-2">
|
||
<label className="text-xs font-bold text-gray-700 flex items-center gap-1">
|
||
<span>تاریخ انقضا</span>
|
||
<div className="group relative inline-block">
|
||
<HelpCircle className="w-3 h-3 text-gray-400 hover:text-purple-600 cursor-help" />
|
||
<div className="absolute bottom-full right-1/2 translate-x-1/2 mb-2 hidden group-hover:block w-48 p-2 bg-gray-900 text-white text-[10px] rounded-lg shadow-xl text-center z-30 font-vazir leading-relaxed">
|
||
آخرین مهلت اعتبار کد تخفیف.
|
||
</div>
|
||
</div>
|
||
</label>
|
||
<input type="date" value={formData.expiresAt} onChange={e => setFormData({...formData, expiresAt: e.target.value})} className="w-full px-3 py-2 rounded-xl border border-gray-200 focus:border-purple-500 text-xs" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Target Builder */}
|
||
<div className="space-y-4">
|
||
<div className="flex items-center justify-between border-b border-gray-100 pb-2">
|
||
<h4 className="font-bold text-gray-800">اهداف اختصاصی (Targets & Modifiers)</h4>
|
||
<button type="button" onClick={addTarget} className="text-xs font-bold text-purple-600 bg-purple-50 px-3 py-1.5 rounded-xl hover:bg-purple-100 transition-colors flex items-center gap-1 cursor-pointer">
|
||
<Plus className="w-4 h-4" /> افزودن هدف
|
||
</button>
|
||
</div>
|
||
|
||
{formData.targets.length === 0 ? (
|
||
<div className="bg-gray-50 border border-dashed border-gray-300 rounded-2xl p-6 text-center text-gray-500 text-xs">
|
||
هیچ هدف اختصاصی تعریف نشده است. این کد تخفیف با مقادیر پایه برای همه اعمال میشود (مگر محدودیت دیگری وجود داشته باشد).
|
||
</div>
|
||
) : (
|
||
<div className="space-y-3">
|
||
{formData.targets.map((t: CouponTarget, i: number) => (
|
||
<div key={i} className="flex flex-col sm:flex-row gap-3 items-end bg-white border border-gray-200 p-3.5 rounded-2xl shadow-xs hover:border-purple-300 transition-colors relative">
|
||
<div className="space-y-1 w-full sm:w-1/5">
|
||
<label className="text-[11px] font-bold text-gray-500 flex items-center gap-1">{getTypeIcon(t.targetType)} نوع هدف</label>
|
||
<select value={t.targetType} onChange={(e) => updateTarget(i, 'targetType', e.target.value)} className="w-full px-2.5 py-2 rounded-xl border border-gray-200 text-xs bg-gray-50 focus:bg-white focus:border-purple-500 outline-none">
|
||
<option value="USER">کاربر خاص</option>
|
||
<option value="PET">پت (حیوان)</option>
|
||
<option value="PRODUCT">محصول خاص</option>
|
||
<option value="CATEGORY">دستهبندی خاص</option>
|
||
<option value="ROLE">نقش کاربری</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div className="space-y-1 w-full sm:w-2/5">
|
||
<label className="text-[11px] font-bold text-gray-500">شناسه (ID / Slug / موبایل)</label>
|
||
<input type="text" placeholder="مقدار شناسه هدف..." value={t.targetId} onChange={(e) => updateTarget(i, 'targetId', e.target.value)} className="w-full px-2.5 py-2 rounded-xl border border-gray-200 text-xs outline-none focus:border-purple-500" />
|
||
</div>
|
||
|
||
<div className="space-y-1 w-full sm:w-1/5">
|
||
<label className="text-[11px] font-bold text-gray-500">نوع تغییردهنده (Modifier)</label>
|
||
<select value={t.modifierType} onChange={(e) => updateTarget(i, 'modifierType', e.target.value)} className="w-full px-2.5 py-2 rounded-xl border border-gray-200 text-xs bg-gray-50 focus:bg-white focus:border-purple-500 outline-none">
|
||
<option value="override">جایگزین مقدار پایه (Override)</option>
|
||
<option value="add">افزایش به پایه (+)</option>
|
||
<option value="subtract">کاهش از پایه (-)</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div className="space-y-1 w-full sm:w-1/5">
|
||
<label className="text-[11px] font-bold text-gray-500">مقدار جدید (اختیاری)</label>
|
||
<input type="number" placeholder="مقدار پایه" value={t.modifierValue} onChange={(e) => updateTarget(i, 'modifierValue', e.target.value)} dir="ltr" className="w-full px-2.5 py-2 rounded-xl border border-gray-200 text-xs outline-none focus:border-purple-500" />
|
||
</div>
|
||
|
||
<button type="button" onClick={() => removeTarget(i)} className="p-2.5 text-red-500 bg-red-50 hover:bg-red-100 rounded-xl transition-colors shrink-0 cursor-pointer">
|
||
<Trash2 className="w-4 h-4" />
|
||
</button>
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="flex items-center">
|
||
<label className="flex items-center gap-3 cursor-pointer p-3 border border-green-200 bg-green-50/50 rounded-2xl w-full">
|
||
<input type="checkbox" checked={formData.isActive} onChange={(e) => setFormData({...formData, isActive: e.target.checked})} className="w-4 h-4 text-green-600 rounded" />
|
||
<span className="font-bold text-green-800 text-xs">کد تخفیف در سیستم فعال باشد</span>
|
||
</label>
|
||
</div>
|
||
</form>
|
||
</Modal>
|
||
);
|
||
}
|
||
|