feat: complete all remaining backlog tasks 25.1 to 25.14
Some checks are pending
Deploy Canina / deploy (push) Waiting to run
Some checks are pending
Deploy Canina / deploy (push) Waiting to run
This commit is contained in:
parent
1542c85b29
commit
ce30ade808
@ -185,12 +185,12 @@
|
|||||||
- [x] **TASK-25.3 [Admin Panel Users Page]:** تکمیل مدیریت کامل کاربران (مشاهده تمامی فیلدها، ویرایش اطلاعات کاربر، تغییر نقش، مسدودی و...).
|
- [x] **TASK-25.3 [Admin Panel Users Page]:** تکمیل مدیریت کامل کاربران (مشاهده تمامی فیلدها، ویرایش اطلاعات کاربر، تغییر نقش، مسدودی و...).
|
||||||
- [x] **TASK-25.4 [Health Calculator UI]:** اصلاح فیلدهای گام دوم پایش فیزیکی در چکآپ سلامت جهت امکان تایپ مستقیم عددی علاوه بر دکمههای کم و زیاد.
|
- [x] **TASK-25.4 [Health Calculator UI]:** اصلاح فیلدهای گام دوم پایش فیزیکی در چکآپ سلامت جهت امکان تایپ مستقیم عددی علاوه بر دکمههای کم و زیاد.
|
||||||
- [x] **TASK-25.5 [Health Supplement Tracking Logic]:** پیادهسازی کامل منطق و الگوریتم پایش موجودی مکملها براساس تاریخ مصرف و سفارشات واقعی پت.
|
- [x] **TASK-25.5 [Health Supplement Tracking Logic]:** پیادهسازی کامل منطق و الگوریتم پایش موجودی مکملها براساس تاریخ مصرف و سفارشات واقعی پت.
|
||||||
- [ ] **TASK-25.6 [Backend Exception & Error Handling System]:** اصلاح منطق پاسخدهی خطاهای بهینهشده به تفکیک پارامترها و جلوگیری از نمایش پیامهای کلی غیرمنتظره و توستهای تکراری.
|
- [x] **TASK-25.6 [Backend Exception & Error Handling System]:** اصلاح منطق پاسخدهی خطاهای بهینهشده به تفکیک پارامترها و جلوگیری از نمایش پیامهای کلی غیرمنتظره و توستهای تکراری.
|
||||||
- [ ] **TASK-25.7 [Wallet Checkout Error Handling]:** اصلاح هندلینگ خطای ورودی هنگام پرداخت با کیف پول و نمایش دقیق فیلد یا دلیل خطای پرداخت.
|
- [x] **TASK-25.7 [Wallet Checkout Error Handling]:** اصلاح هندلینگ خطای ورودی هنگام پرداخت با کیف پول و نمایش دقیق فیلد یا دلیل خطای پرداخت.
|
||||||
- [ ] **TASK-25.8 [Address Add & Refresh Bug Fix]:** رفع مشکل خطای اضافه کردن آدرس، جلوگیری از نمایش توستهای تکراری و بروزرسانی خودکار لیست آدرسهای کاربر.
|
- [x] **TASK-25.8 [Address Add & Refresh Bug Fix]:** رفع مشکل خطای اضافه کردن آدرس، جلوگیری از نمایش توستهای تکراری و بروزرسانی خودکار لیست آدرسهای کاربر.
|
||||||
- [ ] **TASK-25.9 [Shipping Fee Calculation Fix]:** برطرف کردن خطای محاسبه هزینه ثابت ارسال در فاکتور و فاز تسویهحساب براساس تنظیمات پنل ادمین.
|
- [x] **TASK-25.9 [Shipping Fee Calculation Fix]:** برطرف کردن خطای محاسبه هزینه ثابت ارسال در فاکتور و فاز تسویهحساب براساس تنظیمات پنل ادمین.
|
||||||
- [ ] **TASK-25.10 [Admin Panel UI Cleanup]:** پاکسازی پسوند کد تپکها یا کدهای درون پرانتز (مانند TASK-x) از عناوین صفحات و منوهای پنل ادمین.
|
- [x] **TASK-25.10 [Admin Panel UI Cleanup]:** پاکسازی پسوند کد تپکها یا کدهای درون پرانتز (مانند TASK-x) از عناوین صفحات و منوهای پنل ادمین.
|
||||||
- [ ] **TASK-25.11 [Maintenance Mode Protection Guard]:** اصلاح و فعالسازی عملکرد گارد «حالت تعمیرات» (Maintenance Mode) در فرانتاند برای مسدودسازی دسترسی عمومی.
|
- [x] **TASK-25.11 [Maintenance Mode Protection Guard]:** اصلاح و فعالسازی عملکرد گارد «حالت تعمیرات» (Maintenance Mode) در فرانتاند برای مسدودسازی دسترسی عمومی.
|
||||||
- [ ] **TASK-25.12 [Flexible Catalog-Only Mode Settings]:** توسعه تنظیمات چندحالته «حالت کاتالوگ» (نمایش/عدم نمایش قیمت، فعال/غیرفعالسازی سبد خرید و فاکتور، تبدیل به پیشخرید).
|
- [x] **TASK-25.12 [Flexible Catalog-Only Mode Settings]:** توسعه تنظیمات چندحالته «حالت کاتالوگ» (نمایش/عدم نمایش قیمت، فعال/غیرفعالسازی سبد خرید و فاکتور، تبدیل به پیشخرید).
|
||||||
- [ ] **TASK-25.13 [Product Pre-order System]:** ایجاد امکان پیشخرید کالا در پنل ادمین (رایگان یا با بیعانه) و ثبت نهایی بعد از موجود شدن.
|
- [x] **TASK-25.13 [Product Pre-order System]:** ایجاد امکان پیشخرید کالا در پنل ادمین (رایگان یا با بیعانه) و ثبت نهایی بعد از موجود شدن.
|
||||||
- [ ] **TASK-25.14 [Stock Alert Queue & SMS Notification]:** ایجاد قابلیت «موجود شد اطلاع بده» برای کالاهای ناموجود، صف خودکار اطلاعرسانی و ارسال پیامک به محض شارژ موجودی.
|
- [x] **TASK-25.14 [Stock Alert Queue & SMS Notification]:** ایجاد قابلیت «موجود شد اطلاع بده» برای کالاهای ناموجود، صف خودکار اطلاعرسانی و ارسال پیامک به محض شارژ موجودی.
|
||||||
|
|||||||
@ -134,7 +134,9 @@ export default function Products() {
|
|||||||
podcastUrl: '',
|
podcastUrl: '',
|
||||||
videoUrl: '',
|
videoUrl: '',
|
||||||
pdfUrl: '',
|
pdfUrl: '',
|
||||||
symptoms: [] as string[]
|
symptoms: [] as string[],
|
||||||
|
isPreorder: false,
|
||||||
|
preorderDeposit: '' as number | string
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchData = useCallback(async () => {
|
const fetchData = useCallback(async () => {
|
||||||
@ -211,7 +213,9 @@ export default function Products() {
|
|||||||
podcastUrl: product.podcastUrl || '',
|
podcastUrl: product.podcastUrl || '',
|
||||||
videoUrl: product.videoUrl || '',
|
videoUrl: product.videoUrl || '',
|
||||||
pdfUrl: product.pdfUrl || '',
|
pdfUrl: product.pdfUrl || '',
|
||||||
symptoms: product.symptoms ? product.symptoms.map((s: { symptom: string } | string) => typeof s === 'string' ? s : s.symptom) : []
|
symptoms: product.symptoms ? product.symptoms.map((s: { symptom: string } | string) => typeof s === 'string' ? s : s.symptom) : [],
|
||||||
|
isPreorder: Boolean((product as any).isPreorder),
|
||||||
|
preorderDeposit: (product as any).preorderDeposit || ''
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setEditingProduct(null);
|
setEditingProduct(null);
|
||||||
@ -221,7 +225,7 @@ export default function Products() {
|
|||||||
priceDisplay: '', unit: '', packageSize: 0, dosageLogic: '', suitableFor: 'سگ و گربه',
|
priceDisplay: '', unit: '', packageSize: 0, dosageLogic: '', suitableFor: 'سگ و گربه',
|
||||||
imageUrl: '', metaTitle: '', metaDescription: '', keywords: '', canonicalUrl: '', slug: '',
|
imageUrl: '', metaTitle: '', metaDescription: '', keywords: '', canonicalUrl: '', slug: '',
|
||||||
images: [], podcastUrl: '', videoUrl: '', pdfUrl: '',
|
images: [], podcastUrl: '', videoUrl: '', pdfUrl: '',
|
||||||
symptoms: []
|
symptoms: [], isPreorder: false, preorderDeposit: ''
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setActiveTab('general');
|
setActiveTab('general');
|
||||||
@ -887,6 +891,40 @@ export default function Products() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Pre-order Configuration (TASK-25.13) */}
|
||||||
|
<div className="p-4 bg-purple-50 rounded-2xl border border-purple-100 space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h4 className="font-bold text-purple-900 text-sm">قابلیت پیشخرید کالا (Pre-Order Mode)</h4>
|
||||||
|
<p className="text-xs text-purple-700 mt-0.5">امکان پیشخرید کالا قبل از موجود شدن رسمی با بیعانه یا ثبت رایگان.</p>
|
||||||
|
</div>
|
||||||
|
<label className="relative inline-flex items-center cursor-pointer">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
className="sr-only peer"
|
||||||
|
checked={formData.isPreorder}
|
||||||
|
onChange={(e) => setFormData({ ...formData, isPreorder: e.target.checked })}
|
||||||
|
/>
|
||||||
|
<div className="w-12 h-6 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-purple-600"></div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{formData.isPreorder && (
|
||||||
|
<div className="pt-2 border-t border-purple-200/60">
|
||||||
|
<label className="block text-xs font-bold text-purple-900 mb-1">مبلغ بیعانه پیشخرید (تومان - ۰ برای ثبت رایگان)</label>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
placeholder="مثلاً: 200000 یا 0 برای پیشخرید بدون بیعانه"
|
||||||
|
value={formData.preorderDeposit}
|
||||||
|
onChange={(e) => setFormData({ ...formData, preorderDeposit: e.target.value ? Number(e.target.value) : '' })}
|
||||||
|
className="w-full px-4 py-2.5 rounded-xl border border-purple-200 bg-white font-mono text-xs outline-none focus:ring-2 focus:ring-purple-500"
|
||||||
|
dir="ltr"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2 col-span-1 md:col-span-2 pt-2">
|
<div className="space-y-2 col-span-1 md:col-span-2 pt-2">
|
||||||
<label className="text-sm font-bold text-gray-700 flex items-center justify-between">
|
<label className="text-sm font-bold text-gray-700 flex items-center justify-between">
|
||||||
<span>منطق و دوز دقیق مصرفی (Dosage Calculator Fields)</span>
|
<span>منطق و دوز دقیق مصرفی (Dosage Calculator Fields)</span>
|
||||||
|
|||||||
@ -130,6 +130,20 @@ export default function CheckoutPage() {
|
|||||||
shippingAddressStr = `${provCity ? provCity + '، ' : ''}${manualAddress}${manualZipCode ? ` (کد پستی: ${manualZipCode})` : ''}${manualName ? ` (گیرنده: ${manualName}${manualPhone ? ` - ${toPersian(manualPhone)}` : ''})` : ''}`;
|
shippingAddressStr = `${provCity ? provCity + '، ' : ''}${manualAddress}${manualZipCode ? ` (کد پستی: ${manualZipCode})` : ''}${manualName ? ` (گیرنده: ${manualName}${manualPhone ? ` - ${toPersian(manualPhone)}` : ''})` : ''}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check wallet balance if payment method is wallet
|
||||||
|
if (paymentMethod === 'wallet' && (profile.walletBalance || 0) < totalAmount) {
|
||||||
|
toast.error(`موجودی کیف پول شما (${(profile.walletBalance || 0).toLocaleString('fa-IR')} تومان) کمتر از مبلغ نهایی سفارش (${totalAmount.toLocaleString('fa-IR')} تومان) است. لطفاً ابتدا کیف پول خود را شارژ کنید.`);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check shipping address requirement
|
||||||
|
if (!selectedAddressId && !manualAddress) {
|
||||||
|
toast.error("لطفاً یک آدرس برای ارسال سفارش انتخاب کرده یا آدرس جدید وارد کنید.");
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const orderId = await addOrder({
|
const orderId = await addOrder({
|
||||||
items: [...items],
|
items: [...items],
|
||||||
total: totalAmount,
|
total: totalAmount,
|
||||||
@ -144,7 +158,7 @@ export default function CheckoutPage() {
|
|||||||
const { fetchProfile } = useUserStore.getState();
|
const { fetchProfile } = useUserStore.getState();
|
||||||
await fetchProfile();
|
await fetchProfile();
|
||||||
|
|
||||||
// 4. Update pet consumptions and charity contributions for active pet
|
// Update pet consumptions and charity contributions for active pet
|
||||||
const targetPet = activePet || usePetStore.getState().pets[0];
|
const targetPet = activePet || usePetStore.getState().pets[0];
|
||||||
if (targetPet) {
|
if (targetPet) {
|
||||||
const newConsumptions = [...(targetPet.consumptions || [])];
|
const newConsumptions = [...(targetPet.consumptions || [])];
|
||||||
@ -172,7 +186,10 @@ export default function CheckoutPage() {
|
|||||||
toast.success("سفارش شما با موفقیت ثبت شد!");
|
toast.success("سفارش شما با موفقیت ثبت شد!");
|
||||||
router.push(`/checkout/success/${orderId}`);
|
router.push(`/checkout/success/${orderId}`);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
toast.error(err.message || "خطا در ثبت سفارش. لطفاً مجدداً تلاش کنید.");
|
const backendMessage = err.response?.data?.message || err.response?.data?.details?.[0]?.message || err.message;
|
||||||
|
if (backendMessage && !backendMessage.includes("Request failed")) {
|
||||||
|
toast.error(`خطا در ثبت سفارش: ${backendMessage}`);
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -80,6 +80,8 @@ export default function ProductPage({ productSlug }: { productSlug: string }) {
|
|||||||
const [showRefillModal, setShowRefillModal] = useState(false);
|
const [showRefillModal, setShowRefillModal] = useState(false);
|
||||||
const [itemQuantity, setItemQuantity] = useState(1);
|
const [itemQuantity, setItemQuantity] = useState(1);
|
||||||
const [allProducts, setAllProducts] = useState<Product[]>([]);
|
const [allProducts, setAllProducts] = useState<Product[]>([]);
|
||||||
|
const [showNotifier, setShowNotifier] = useState(false);
|
||||||
|
const [notifierPhone, setNotifierPhone] = useState('');
|
||||||
const [isMounted, setIsMounted] = useState(false);
|
const [isMounted, setIsMounted] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -672,17 +674,38 @@ export default function ProductPage({ productSlug }: { productSlug: string }) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Add to Cart Button - 60% Width */}
|
{/* Add to Cart / Preorder / Stock Notify Button - 60% Width */}
|
||||||
<button
|
{(product as any).isPreorder ? (
|
||||||
onClick={() => {
|
<button
|
||||||
addItem(product, itemQuantity, { quantity: calculation.dailyDose, unit: calculation.unit });
|
onClick={() => {
|
||||||
toast.success(`${toPersian(itemQuantity)} عدد ${product.name} به سبد خرید اضافه شد`);
|
addItem(product, itemQuantity, { quantity: calculation.dailyDose, unit: calculation.unit });
|
||||||
}}
|
toast.success(`پیشخرید ${toPersian(itemQuantity)} عدد ${product.name} به سبد شما اضافه شد`);
|
||||||
className="w-[60%] flex items-center justify-center gap-1.5 px-4 bg-medical-gray-900 text-white rounded-2xl h-16 font-black text-sm hover:bg-canina-blue transition-all shadow-xl shadow-black/10 font-vazir whitespace-nowrap"
|
}}
|
||||||
>
|
className="w-[60%] flex items-center justify-center gap-1.5 px-4 bg-purple-700 text-white rounded-2xl h-16 font-black text-sm hover:bg-purple-800 transition-all shadow-xl shadow-purple-900/20 font-vazir whitespace-nowrap"
|
||||||
<ShoppingBag className="w-5 h-5 mb-1" />
|
>
|
||||||
افزودن به سبد خرید
|
<Sparkles className="w-5 h-5 mb-1 text-amber-300" />
|
||||||
</button>
|
{(product as any).preorderDeposit > 0 ? `پیشخرید (بیعانه: ${((product as any).preorderDeposit).toLocaleString('fa-IR')} تومان)` : 'ثبت پیشخرید (رایگان)'}
|
||||||
|
</button>
|
||||||
|
) : (product.packageSize || 0) <= 0 ? (
|
||||||
|
<button
|
||||||
|
onClick={() => setShowNotifier(true)}
|
||||||
|
className="w-[60%] flex items-center justify-center gap-1.5 px-4 bg-amber-500 text-white rounded-2xl h-16 font-black text-sm hover:bg-amber-600 transition-all shadow-xl shadow-amber-500/20 font-vazir whitespace-nowrap"
|
||||||
|
>
|
||||||
|
<Bell className="w-5 h-5 mb-1" />
|
||||||
|
موجود شد اطلاع بده
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
addItem(product, itemQuantity, { quantity: calculation.dailyDose, unit: calculation.unit });
|
||||||
|
toast.success(`${toPersian(itemQuantity)} عدد ${product.name} به سبد خرید اضافه شد`);
|
||||||
|
}}
|
||||||
|
className="w-[60%] flex items-center justify-center gap-1.5 px-4 bg-medical-gray-900 text-white rounded-2xl h-16 font-black text-sm hover:bg-canina-blue transition-all shadow-xl shadow-black/10 font-vazir whitespace-nowrap"
|
||||||
|
>
|
||||||
|
<ShoppingBag className="w-5 h-5 mb-1" />
|
||||||
|
افزودن به سبد خرید
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 flex flex-col gap-3 border-t border-medical-gray-50 pt-6">
|
<div className="mt-6 flex flex-col gap-3 border-t border-medical-gray-50 pt-6">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user