549 lines
29 KiB
TypeScript
549 lines
29 KiB
TypeScript
import { useState, useEffect } from 'react';
|
||
import { DollarSign, Save, Plus, X, Heart, Percent, Wallet, Calculator, ArrowUpRight, ArrowDownRight, Compass } from 'lucide-react';
|
||
import { toast } from 'react-hot-toast';
|
||
import api from '../services/api';
|
||
import Spinner from '../components/ui/Spinner';
|
||
import PriceInput from '../components/ui/PriceInput';
|
||
import Button from '../components/ui/Button';
|
||
import Input from '../components/ui/Input';
|
||
import type { FinancialSettings, PricingSettings } from '../types/admin';
|
||
import { calculateSellingPrice } from '../utils/pricing';
|
||
|
||
export default function FinancialSettingsPage() {
|
||
const [financial, setFinancial] = useState<FinancialSettings>({
|
||
taxPercentage: 10,
|
||
freeShippingThreshold: 2000000,
|
||
standardShippingFee: 85000,
|
||
charityDonationOptions: [10000, 20000, 50000],
|
||
walletWithdrawalEnabled: false,
|
||
});
|
||
const [charityStep, setCharityStep] = useState('10000');
|
||
const [charityEnabled, setCharityEnabled] = useState(true);
|
||
const [refillEnabled, setRefillEnabled] = useState(false);
|
||
const [refillPercent, setRefillPercent] = useState('5');
|
||
|
||
// Smart Pricing & Rounding Settings
|
||
const [pricing, setPricing] = useState<PricingSettings>({
|
||
roundingStep: 5000,
|
||
roundingMode: 'UP',
|
||
defaultRetailMarginPercent: 30,
|
||
defaultWholesaleMarginPercent: 15,
|
||
});
|
||
const [testBuyPrice, setTestBuyPrice] = useState('1000000');
|
||
const [applyToAllProductsNow, setApplyToAllProductsNow] = useState(true);
|
||
|
||
const [isLoading, setIsLoading] = useState(true);
|
||
const [isSaving, setIsSaving] = useState(false);
|
||
const [donationInput, setDonationInput] = useState('');
|
||
|
||
useEffect(() => {
|
||
let isSubscribed = true;
|
||
Promise.all([
|
||
api.get('/settings/financial'),
|
||
api.get('/admin/settings'),
|
||
api.get('/admin/pricing/settings'),
|
||
])
|
||
.then(([resFin, resAdmin, resPricing]) => {
|
||
if (!isSubscribed) return;
|
||
const data = resFin.data?.data || resFin.data;
|
||
if (data) {
|
||
setFinancial({
|
||
taxPercentage: Number(data.taxPercentage ?? 10),
|
||
freeShippingThreshold: Number(data.freeShippingThreshold ?? 2000000),
|
||
standardShippingFee: Number(data.standardShippingFee ?? 85000),
|
||
charityDonationOptions: Array.isArray(data.charityDonationOptions)
|
||
? data.charityDonationOptions.map(Number)
|
||
: [10000, 20000, 50000],
|
||
charityDonationEnabled: Boolean(data.charityDonationEnabled ?? true),
|
||
walletWithdrawalEnabled: Boolean(data.walletWithdrawalEnabled ?? false),
|
||
});
|
||
if (data.charityDonationEnabled !== undefined) {
|
||
setCharityEnabled(Boolean(data.charityDonationEnabled));
|
||
}
|
||
}
|
||
if (resAdmin.data?.success) {
|
||
setCharityStep(resAdmin.data.data.CHARITY_ROUND_STEP || '10000');
|
||
if (resAdmin.data.data.CHARITY_DONATION_ENABLED !== undefined) {
|
||
setCharityEnabled(resAdmin.data.data.CHARITY_DONATION_ENABLED !== 'false');
|
||
}
|
||
setRefillEnabled(resAdmin.data.data.REFILL_SUBSCRIPTION_ENABLED === 'true');
|
||
setRefillPercent(resAdmin.data.data.REFILL_REWARD_PERCENT || '5');
|
||
}
|
||
if (resPricing.data?.success && resPricing.data.data) {
|
||
const p = resPricing.data.data;
|
||
setPricing({
|
||
roundingStep: Number(p.roundingStep ?? 5000),
|
||
roundingMode: p.roundingMode || 'UP',
|
||
defaultRetailMarginPercent: Number(p.defaultRetailMarginPercent ?? 30),
|
||
defaultWholesaleMarginPercent: Number(p.defaultWholesaleMarginPercent ?? 15),
|
||
});
|
||
}
|
||
})
|
||
.catch((err) => {
|
||
console.warn('Failed to fetch Financial settings', err);
|
||
})
|
||
.finally(() => {
|
||
if (isSubscribed) setIsLoading(false);
|
||
});
|
||
return () => {
|
||
isSubscribed = false;
|
||
};
|
||
}, []);
|
||
|
||
const handleSave = async (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
try {
|
||
setIsSaving(true);
|
||
const [, , resPricing] = await Promise.all([
|
||
api.patch('/settings/financial', {
|
||
...financial,
|
||
charityDonationEnabled: charityEnabled,
|
||
}),
|
||
api.put('/admin/settings', {
|
||
TAX_PERCENTAGE: String(financial.taxPercentage),
|
||
CHARITY_ROUND_STEP: charityStep,
|
||
CHARITY_DONATION_ENABLED: charityEnabled ? 'true' : 'false',
|
||
REFILL_SUBSCRIPTION_ENABLED: refillEnabled ? 'true' : 'false',
|
||
REFILL_REWARD_PERCENT: refillPercent,
|
||
}),
|
||
api.put('/admin/pricing/settings', {
|
||
...pricing,
|
||
applyToAllProducts: applyToAllProductsNow,
|
||
}),
|
||
]);
|
||
const pricingMsg = resPricing.data?.message;
|
||
toast.success(pricingMsg || 'تنظیمات مالی و قیمتگذاری با موفقیت ذخیره شد');
|
||
} catch (err) {
|
||
console.error('Failed to update financial settings:', err);
|
||
toast.error('خطا در بروزرسانی تنظیمات مالی');
|
||
} finally {
|
||
setIsSaving(false);
|
||
}
|
||
};
|
||
|
||
const addDonationOption = () => {
|
||
const val = Number(donationInput);
|
||
if (val > 0 && !financial.charityDonationOptions.includes(val)) {
|
||
setFinancial({
|
||
...financial,
|
||
charityDonationOptions: [...financial.charityDonationOptions, val].sort((a, b) => a - b),
|
||
});
|
||
setDonationInput('');
|
||
}
|
||
};
|
||
|
||
const removeDonationOption = (amount: number) => {
|
||
setFinancial({
|
||
...financial,
|
||
charityDonationOptions: financial.charityDonationOptions.filter((a) => a !== amount),
|
||
});
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-6 font-vazir" dir="rtl">
|
||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||
<div>
|
||
<h2 className="text-2xl font-black text-gray-900 flex items-center gap-2">
|
||
<DollarSign className="w-6 h-6 text-emerald-600" />
|
||
تنظیمات مالی، مالیات و خیریه (Financial Settings)
|
||
</h2>
|
||
<p className="text-gray-500 font-medium mt-1">
|
||
تعیین درصد مالیات بر ارزش افزوده، کمکهای خیریه ردپای مهربانی و تسویهحساب کیف پول
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{isLoading ? (
|
||
<div className="flex justify-center p-12">
|
||
<Spinner size="lg" className="text-emerald-600" />
|
||
</div>
|
||
) : (
|
||
<form onSubmit={handleSave} className="bg-white p-6 rounded-2xl shadow-sm border border-gray-200 space-y-6">
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||
{/* Tax Percentage */}
|
||
<div>
|
||
<label className="block text-sm font-bold text-gray-700 mb-1.5 flex items-center gap-1.5">
|
||
<Percent className="w-4 h-4 text-emerald-600" />
|
||
<span>درصد مالیات بر ارزش افزوده (VAT) *</span>
|
||
</label>
|
||
<Input
|
||
required
|
||
type="number"
|
||
min="0"
|
||
max="100"
|
||
value={financial.taxPercentage}
|
||
onChange={(e) => setFinancial({ ...financial, taxPercentage: Number(e.target.value) })}
|
||
leftIcon={<span className="text-gray-400 font-bold text-xs">%</span>}
|
||
dir="ltr"
|
||
className="font-bold text-sm"
|
||
/>
|
||
<p className="text-xs text-gray-500 mt-1">درصد قانونی محاسبه مالیات بر ارزش افزوده در فاکتور نهایی سفارشات.</p>
|
||
</div>
|
||
|
||
{/* Charity Donation Toggle & Step */}
|
||
<div className="md:col-span-2 p-5 bg-rose-50/50 rounded-2xl border border-rose-100 space-y-4">
|
||
<label className="flex items-center justify-between cursor-pointer">
|
||
<div className="flex items-start gap-3">
|
||
<input
|
||
type="checkbox"
|
||
checked={charityEnabled}
|
||
onChange={(e) => setCharityEnabled(e.target.checked)}
|
||
className="w-5 h-5 mt-0.5 rounded-lg text-rose-600 focus:ring-rose-500 border-gray-300 cursor-pointer"
|
||
/>
|
||
<div>
|
||
<span className="text-sm font-black text-gray-900 block flex items-center gap-2">
|
||
<Heart className="w-4 h-4 text-rose-500 fill-rose-500" />
|
||
فعالسازی طرح ردپای مهربانی (کمک داوطلبانه به پناهگاه حیوانات)
|
||
</span>
|
||
<span className="text-xs text-gray-500 font-medium block mt-1">
|
||
در صورت غیرفعال بودن، بخش رند کردن فاکتور، مشارکت در خیریه و ردپای مهربانی به طور کامل از تسویهحساب و بخشهای مختلف سایت مخفی میشود.
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</label>
|
||
|
||
{charityEnabled && (
|
||
<div className="pt-4 border-t border-rose-100/80 grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<div>
|
||
<label className="block text-sm font-bold text-gray-700 mb-1.5 flex items-center gap-1.5">
|
||
<Heart className="w-4 h-4 text-rose-500" />
|
||
<span>پله رند کردن کمک به خیریه ردپای مهربانی (تومان)</span>
|
||
</label>
|
||
<PriceInput
|
||
value={charityStep}
|
||
onChange={(val) => setCharityStep(val)}
|
||
placeholder="10000"
|
||
className="px-4 py-3 rounded-xl border border-gray-200 focus:border-rose-500 text-sm bg-white"
|
||
/>
|
||
<p className="text-xs text-gray-500 mt-1">مبلغ فاکتور به نزدیکترین ضریب این عدد (مثلاً ۱۰,۰۰۰ یا ۵۰,۰۰۰ تومان) رند میگردد.</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Charity Donation Options */}
|
||
{charityEnabled && (
|
||
<div className="md:col-span-2 space-y-2 pt-3 border-t border-gray-100">
|
||
<label className="block text-sm font-bold text-gray-700 mb-1 flex items-center gap-1.5">
|
||
<Heart className="w-4 h-4 text-rose-500" />
|
||
<span>گزینههای دلخواه کمک مستقیم به خیریه (charityDonationOptions)</span>
|
||
</label>
|
||
<div className="flex gap-2 max-w-md">
|
||
<PriceInput
|
||
placeholder="مبلغ جدید (مثال: ۱۰۰,۰۰۰)"
|
||
value={donationInput}
|
||
onChange={(val) => setDonationInput(val)}
|
||
className="px-4 py-2 rounded-xl border border-gray-200 text-xs"
|
||
/>
|
||
<Button
|
||
type="button"
|
||
variant="primary"
|
||
size="xs"
|
||
startIcon={Plus}
|
||
onClick={addDonationOption}
|
||
>
|
||
افزودن
|
||
</Button>
|
||
</div>
|
||
|
||
<div className="flex flex-wrap gap-2 pt-2">
|
||
{financial.charityDonationOptions.map((amount) => (
|
||
<span
|
||
key={amount}
|
||
className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-rose-50 text-rose-700 text-xs font-bold rounded-xl border border-rose-200"
|
||
>
|
||
{amount.toLocaleString('fa-IR')} تومان
|
||
<button
|
||
type="button"
|
||
onClick={() => removeDonationOption(amount)}
|
||
className="hover:text-red-500 cursor-pointer"
|
||
>
|
||
<X className="w-3.5 h-3.5" />
|
||
</button>
|
||
</span>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Refill Subscription and Reward Cashback */}
|
||
<div className="md:col-span-2 pt-4 border-t border-gray-100 space-y-4">
|
||
<label className="flex items-center justify-between cursor-pointer p-4 bg-indigo-50/50 hover:bg-indigo-50 rounded-2xl border border-indigo-100 transition-colors">
|
||
<div className="flex items-start gap-3">
|
||
<input
|
||
type="checkbox"
|
||
checked={refillEnabled}
|
||
onChange={(e) => setRefillEnabled(e.target.checked)}
|
||
className="w-5 h-5 mt-0.5 rounded-lg text-indigo-600 focus:ring-indigo-500 border-gray-300 cursor-pointer"
|
||
/>
|
||
<div>
|
||
<span className="text-sm font-black text-gray-900 block flex items-center gap-2">
|
||
<Percent className="w-4 h-4 text-indigo-600" />
|
||
فعالسازی سرویس «رزرو هوشمند تمدید و پاداش خرید بعدی» (Refill Subscription)
|
||
</span>
|
||
<span className="text-xs text-gray-500 font-medium block mt-0.5">
|
||
در صورت فعال بودن، گزینهای در سبد خرید و تسویهحساب به مشتری پیشنهاد میشود تا با فعالسازی یادآور تمدید، درصد مشخصی پاداش (اعتبار خرید بعدی) رزرو کند.
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</label>
|
||
|
||
{refillEnabled && (
|
||
<div className="p-4 bg-white rounded-2xl border border-indigo-100 grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<div>
|
||
<label className="block text-xs font-bold text-gray-700 mb-1.5">
|
||
درصد پاداش رزرو تمدید (Cashback Reward Percent)
|
||
</label>
|
||
<Input
|
||
type="number"
|
||
min="1"
|
||
max="100"
|
||
value={refillPercent}
|
||
onChange={(e) => setRefillPercent(e.target.value)}
|
||
leftIcon={<span className="text-gray-400 font-bold text-xs">%</span>}
|
||
dir="ltr"
|
||
inputSize="sm"
|
||
className="font-bold"
|
||
/>
|
||
<p className="text-[11px] text-gray-400 mt-1">پیشفرض: ۵ درصد</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Smart Pricing & Rounding Rules Section */}
|
||
<div className="md:col-span-2 pt-6 border-t-2 border-dashed border-gray-200 space-y-5">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h3 className="text-base font-black text-gray-900 flex items-center gap-2">
|
||
<Calculator className="w-5 h-5 text-indigo-600" />
|
||
تنظیمات هوشمند فرمول سود و گرد کردن قیمتها (Pricing & Rounding Rules)
|
||
</h3>
|
||
<p className="text-xs text-gray-500 mt-1">
|
||
تعیین گام و جهت رند شدن قیمتها و درصد سود پیشفرض برای فروش خرد و عمده. این تنظیمات در فرم ویرایش محصول و تغییرات گروهی قیمت اعمال میگردد.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-gradient-to-br from-indigo-50/60 via-purple-50/30 to-blue-50/50 p-5 rounded-2xl border border-indigo-100/80 space-y-5">
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||
{/* Rounding Step */}
|
||
<div>
|
||
<label className="block text-xs font-bold text-gray-800 mb-1.5 flex items-center gap-1.5">
|
||
<Compass className="w-4 h-4 text-indigo-600" />
|
||
<span>گام گرد کردن قیمت (پله رند شدن به تومان)</span>
|
||
</label>
|
||
<PriceInput
|
||
value={String(pricing.roundingStep)}
|
||
onChange={(val) => setPricing({ ...pricing, roundingStep: Math.max(0, Number(val) || 0) })}
|
||
placeholder="5000"
|
||
className="px-4 py-2.5 bg-white rounded-xl border border-indigo-200 focus:border-indigo-600 text-sm font-bold"
|
||
/>
|
||
<div className="flex flex-wrap gap-1.5 mt-2">
|
||
{[1000, 5000, 10000, 50000, 100000].map((stepVal) => (
|
||
<button
|
||
key={stepVal}
|
||
type="button"
|
||
onClick={() => setPricing({ ...pricing, roundingStep: stepVal })}
|
||
className={`px-2.5 py-1 text-xs font-bold rounded-lg border transition-all cursor-pointer ${
|
||
pricing.roundingStep === stepVal
|
||
? 'bg-indigo-600 text-white border-indigo-600 shadow-sm'
|
||
: 'bg-white text-gray-600 border-gray-200 hover:border-indigo-300'
|
||
}`}
|
||
>
|
||
{stepVal.toLocaleString('fa-IR')} تومان
|
||
</button>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Rounding Mode / Direction */}
|
||
<div>
|
||
<label className="block text-xs font-bold text-gray-800 mb-1.5 flex items-center gap-1.5">
|
||
<span>جهت گرد کردن قیمت (Rounding Direction)</span>
|
||
</label>
|
||
<div className="grid grid-cols-3 gap-2">
|
||
<button
|
||
type="button"
|
||
onClick={() => setPricing({ ...pricing, roundingMode: 'UP' })}
|
||
className={`p-3 rounded-xl border text-center transition-all cursor-pointer ${
|
||
pricing.roundingMode === 'UP'
|
||
? 'bg-indigo-600 text-white border-indigo-600 shadow-md ring-2 ring-indigo-300'
|
||
: 'bg-white text-gray-700 border-gray-200 hover:border-indigo-300'
|
||
}`}
|
||
>
|
||
<ArrowUpRight className={`w-4 h-4 mx-auto mb-1 ${pricing.roundingMode === 'UP' ? 'text-white' : 'text-emerald-600'}`} />
|
||
<span className="block text-xs font-black">به سمت بالا</span>
|
||
<span className="block text-[10px] opacity-80 mt-0.5">سقف (Ceil)</span>
|
||
</button>
|
||
|
||
<button
|
||
type="button"
|
||
onClick={() => setPricing({ ...pricing, roundingMode: 'DOWN' })}
|
||
className={`p-3 rounded-xl border text-center transition-all cursor-pointer ${
|
||
pricing.roundingMode === 'DOWN'
|
||
? 'bg-indigo-600 text-white border-indigo-600 shadow-md ring-2 ring-indigo-300'
|
||
: 'bg-white text-gray-700 border-gray-200 hover:border-indigo-300'
|
||
}`}
|
||
>
|
||
<ArrowDownRight className={`w-4 h-4 mx-auto mb-1 ${pricing.roundingMode === 'DOWN' ? 'text-white' : 'text-rose-600'}`} />
|
||
<span className="block text-xs font-black">به سمت پایین</span>
|
||
<span className="block text-[10px] opacity-80 mt-0.5">کف (Floor)</span>
|
||
</button>
|
||
|
||
<button
|
||
type="button"
|
||
onClick={() => setPricing({ ...pricing, roundingMode: 'NEAREST' })}
|
||
className={`p-3 rounded-xl border text-center transition-all cursor-pointer ${
|
||
pricing.roundingMode === 'NEAREST'
|
||
? 'bg-indigo-600 text-white border-indigo-600 shadow-md ring-2 ring-indigo-300'
|
||
: 'bg-white text-gray-700 border-gray-200 hover:border-indigo-300'
|
||
}`}
|
||
>
|
||
<Compass className={`w-4 h-4 mx-auto mb-1 ${pricing.roundingMode === 'NEAREST' ? 'text-white' : 'text-indigo-600'}`} />
|
||
<span className="block text-xs font-black">نزدیکترین</span>
|
||
<span className="block text-[10px] opacity-80 mt-0.5">ریاضی (Round)</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Default Retail Margin */}
|
||
<div>
|
||
<label className="block text-xs font-bold text-gray-800 mb-1.5">
|
||
درصد سود پیشفرض تکفروشی (Retail Margin %)
|
||
</label>
|
||
<Input
|
||
type="number"
|
||
min="0"
|
||
max="500"
|
||
step="0.5"
|
||
value={pricing.defaultRetailMarginPercent}
|
||
onChange={(e) => setPricing({ ...pricing, defaultRetailMarginPercent: Number(e.target.value) || 0 })}
|
||
leftIcon={<span className="text-gray-400 font-bold text-xs">%</span>}
|
||
dir="ltr"
|
||
className="font-bold text-sm"
|
||
/>
|
||
<p className="text-[11px] text-gray-500 mt-1">فرمول: قیمت خرید × (۱ + سود٪)</p>
|
||
</div>
|
||
|
||
{/* Default Wholesale Margin */}
|
||
<div>
|
||
<label className="block text-xs font-bold text-gray-800 mb-1.5">
|
||
درصد سود پیشفرض عمدهفروشی (Wholesale Margin %)
|
||
</label>
|
||
<Input
|
||
type="number"
|
||
min="0"
|
||
max="500"
|
||
step="0.5"
|
||
value={pricing.defaultWholesaleMarginPercent}
|
||
onChange={(e) => setPricing({ ...pricing, defaultWholesaleMarginPercent: Number(e.target.value) || 0 })}
|
||
leftIcon={<span className="text-gray-400 font-bold text-xs">%</span>}
|
||
dir="ltr"
|
||
className="font-bold text-sm"
|
||
/>
|
||
<p className="text-[11px] text-gray-500 mt-1">برای مشتریان همکار و داروخانههای دامپزشکی B2B</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Live Simulation / Test Box */}
|
||
<div className="bg-white/90 p-4 rounded-xl border border-indigo-100 shadow-sm space-y-3">
|
||
<div className="flex items-center justify-between">
|
||
<span className="text-xs font-black text-indigo-900 flex items-center gap-1.5">
|
||
<Calculator className="w-4 h-4 text-indigo-600" />
|
||
پیشنمایش زنده خروجی با تنظیمات فعلی:
|
||
</span>
|
||
<span className="text-[11px] text-indigo-600 font-medium">
|
||
گام: {pricing.roundingStep.toLocaleString('fa-IR')} تومان ({pricing.roundingMode === 'UP' ? 'گرد به بالا' : pricing.roundingMode === 'DOWN' ? 'گرد به پایین' : 'گرد به نزدیکترین'})
|
||
</span>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3 items-center">
|
||
<div>
|
||
<span className="block text-[11px] text-gray-500 mb-1 font-bold">قیمت خرید فرضی (تست):</span>
|
||
<PriceInput
|
||
value={testBuyPrice}
|
||
onChange={(val) => setTestBuyPrice(val)}
|
||
placeholder="1,000,000"
|
||
className="px-3 py-2 bg-gray-50 rounded-lg border border-gray-200 text-xs font-bold"
|
||
/>
|
||
</div>
|
||
|
||
<div className="bg-emerald-50/70 p-3 rounded-xl border border-emerald-200">
|
||
<span className="block text-[10px] text-emerald-800 font-bold">قیمت تکفروشی محاسبهشده (+{pricing.defaultRetailMarginPercent}٪):</span>
|
||
<span className="text-sm font-black text-emerald-700 block mt-0.5">
|
||
{calculateSellingPrice(Number(testBuyPrice) || 0, pricing.defaultRetailMarginPercent, pricing.roundingStep, pricing.roundingMode).toLocaleString('fa-IR')} تومان
|
||
</span>
|
||
</div>
|
||
|
||
<div className="bg-blue-50/70 p-3 rounded-xl border border-blue-200">
|
||
<span className="block text-[10px] text-blue-800 font-bold">قیمت عمدهفروشی محاسبهشده (+{pricing.defaultWholesaleMarginPercent}٪):</span>
|
||
<span className="text-sm font-black text-blue-700 block mt-0.5">
|
||
{calculateSellingPrice(Number(testBuyPrice) || 0, pricing.defaultWholesaleMarginPercent, pricing.roundingStep, pricing.roundingMode).toLocaleString('fa-IR')} تومان
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Instant Recalculation Toggle */}
|
||
<div className="p-4 bg-gradient-to-r from-purple-100/90 to-indigo-100/80 border border-purple-200 rounded-2xl flex items-center justify-between">
|
||
<label className="flex items-start gap-3 cursor-pointer">
|
||
<input
|
||
type="checkbox"
|
||
checked={applyToAllProductsNow}
|
||
onChange={(e) => setApplyToAllProductsNow(e.target.checked)}
|
||
className="w-5 h-5 mt-0.5 rounded-lg text-purple-600 focus:ring-purple-500 border-purple-300 cursor-pointer shrink-0"
|
||
/>
|
||
<div>
|
||
<span className="text-xs font-black text-purple-950 block">
|
||
محاسبه و بروزرسانی فوری قیمت تمام کالاهای فروشگاه با این درصد سود و رند کردن جدید
|
||
</span>
|
||
<span className="text-[11px] text-purple-800 block mt-0.5 font-medium leading-relaxed">
|
||
با فعال بودن این گزینه هنگام زدن دکمه ذخیره، قیمت فروش تک و عمده تمامی محصولات بر اساس قیمت خرید، درصد سود جدید ({pricing.defaultRetailMarginPercent}٪ / {pricing.defaultWholesaleMarginPercent}٪) و پله رند کردن ({pricing.roundingStep.toLocaleString('fa-IR')} تومان {pricing.roundingMode === 'UP' ? 'به بالا' : pricing.roundingMode === 'DOWN' ? 'به پایین' : 'به نزدیکترین'}) فوراً در دیتابیس مجدداً محاسبه و بهروزرسانی میشوند.
|
||
</span>
|
||
</div>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Wallet Withdrawal Feature Toggle */}
|
||
<div className="md:col-span-2 pt-4 border-t border-gray-100">
|
||
<label className="flex items-center gap-3 cursor-pointer p-4 bg-emerald-50/50 hover:bg-emerald-50 rounded-2xl border border-emerald-100 transition-colors">
|
||
<input
|
||
type="checkbox"
|
||
checked={Boolean(financial.walletWithdrawalEnabled)}
|
||
onChange={(e) => setFinancial({ ...financial, walletWithdrawalEnabled: e.target.checked })}
|
||
className="w-5 h-5 rounded-lg text-emerald-600 focus:ring-emerald-500 border-gray-300 cursor-pointer"
|
||
/>
|
||
<div>
|
||
<span className="text-sm font-black text-gray-900 block flex items-center gap-2">
|
||
<Wallet className="w-4 h-4 text-emerald-600" />
|
||
فعالسازی امکان ثبت درخواست برداشت و تسویهحساب کیف پول
|
||
</span>
|
||
<span className="text-xs text-gray-500 font-medium block mt-0.5">
|
||
در صورت فعال بودن، کاربران میتوانند از طریق داشبورد خود درخواست تسویه و واریز وجه کیف پول به شماره شبا ثبت کنند.
|
||
</span>
|
||
</div>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex justify-end pt-4 border-t border-gray-100">
|
||
<Button
|
||
type="submit"
|
||
variant="primary"
|
||
size="md"
|
||
startIcon={Save}
|
||
isLoading={isSaving}
|
||
>
|
||
ذخیره تنظیمات مالی و قیمتگذاری
|
||
</Button>
|
||
</div>
|
||
</form>
|
||
)}
|
||
|
||
</div>
|
||
);
|
||
}
|