canina/frontend/admin-panel/src/pages/Settings.tsx

184 lines
8.5 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

import { useState, useEffect } from 'react';
import { Settings as SettingsIcon, Save, Truck, ShieldAlert, Percent, AlertCircle } from 'lucide-react';
import api from '../services/api';
import Spinner from '../components/ui/Spinner';
export default function Settings() {
const [settings, setSettings] = useState({
SHIPPING_FEE: '',
MIN_ORDER_AMOUNT: '',
B2B_DISCOUNT_PERCENT: '',
MAINTENANCE_MODE: 'false',
CHARITY_ROUND_STEP: '10000',
});
const [isLoading, setIsLoading] = useState(true);
const [isSaving, setIsSaving] = useState(false);
const fetchSettings = async () => {
try {
setIsLoading(true);
const response = await api.get('/admin/settings');
if (response.data?.success) {
setSettings({
SHIPPING_FEE: response.data.data.SHIPPING_FEE || '0',
MIN_ORDER_AMOUNT: response.data.data.MIN_ORDER_AMOUNT || '0',
B2B_DISCOUNT_PERCENT: response.data.data.B2B_DISCOUNT_PERCENT || '0',
MAINTENANCE_MODE: response.data.data.MAINTENANCE_MODE || 'false',
CHARITY_ROUND_STEP: response.data.data.CHARITY_ROUND_STEP || '10000',
});
}
} catch (err) {
console.error('Failed to fetch settings', err);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
fetchSettings();
}, []);
const handleSave = async (e: React.FormEvent) => {
e.preventDefault();
try {
setIsSaving(true);
await api.put('/admin/settings', settings);
alert('تنظیمات با موفقیت بروزرسانی شد.');
} catch (err) {
console.error('Failed to update settings', err);
} finally {
setIsSaving(false);
}
};
return (
<div className="space-y-6">
<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">
<SettingsIcon className="w-6 h-6 text-purple-600" />
تنظیمات فروشگاه
</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-purple-600" /></div>
) : (
<form onSubmit={handleSave} className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Financial Settings */}
<div className="bg-white p-6 rounded-2xl shadow-sm border border-gray-200 space-y-6">
<h3 className="text-lg font-bold text-gray-900 border-b border-gray-100 pb-4 flex items-center gap-2">
<Truck className="w-5 h-5 text-blue-500" />
تنظیمات مالی و ارسال
</h3>
<div className="space-y-5">
<div>
<label className="block text-sm font-bold text-gray-700 mb-2">هزینه ثابت ارسال (تومان)</label>
<input
type="number"
value={settings.SHIPPING_FEE}
onChange={(e) => setSettings({ ...settings, SHIPPING_FEE: e.target.value })}
className="w-full border border-gray-200 rounded-xl p-3 outline-none focus:ring-2 focus:ring-purple-500 font-bold"
dir="ltr"
/>
</div>
<div>
<label className="block text-sm font-bold text-gray-700 mb-2">حداقل مبلغ سفارش (تومان)</label>
<input
type="number"
value={settings.MIN_ORDER_AMOUNT}
onChange={(e) => setSettings({ ...settings, MIN_ORDER_AMOUNT: e.target.value })}
className="w-full border border-gray-200 rounded-xl p-3 outline-none focus:ring-2 focus:ring-purple-500 font-bold"
dir="ltr"
/>
<p className="text-xs text-gray-500 mt-2 font-medium">سفارشات زیر این مبلغ امکان ثبت نخواهند داشت (۰ = بدون محدودیت).</p>
</div>
<div>
<label className="block text-sm font-bold text-gray-700 mb-2">پله رند کردن ردپای مهربانی (تومان)</label>
<input
type="number"
step="1000"
value={settings.CHARITY_ROUND_STEP}
onChange={(e) => setSettings({ ...settings, CHARITY_ROUND_STEP: e.target.value })}
className="w-full border border-gray-200 rounded-xl p-3 outline-none focus:ring-2 focus:ring-purple-500 font-bold"
dir="ltr"
placeholder="10000"
/>
<p className="text-xs text-gray-500 mt-2 font-medium">مبلغ فاکتور به اولین ضریب این عدد (مثلاً ۱۰,۰۰۰ یا ۵۰,۰۰۰ تومان) رند می‌شود.</p>
</div>
</div>
</div>
{/* B2B Settings */}
<div className="bg-white p-6 rounded-2xl shadow-sm border border-gray-200 space-y-6">
<h3 className="text-lg font-bold text-gray-900 border-b border-gray-100 pb-4 flex items-center gap-2">
<Percent className="w-5 h-5 text-green-500" />
همکاران B2B
</h3>
<div className="space-y-5">
<div>
<label className="block text-sm font-bold text-gray-700 mb-2">تخفیف پیش‌فرض همکار (درصد)</label>
<input
type="number"
value={settings.B2B_DISCOUNT_PERCENT}
onChange={(e) => setSettings({ ...settings, B2B_DISCOUNT_PERCENT: e.target.value })}
className="w-full border border-gray-200 rounded-xl p-3 outline-none focus:ring-2 focus:ring-purple-500 font-bold"
dir="ltr"
max="100"
min="0"
/>
<p className="text-xs text-gray-500 mt-2 font-medium">این درصد به صورت خودکار روی قیمت تمام محصولات برای کاربران تایید شده B2B اعمال می‌شود.</p>
</div>
</div>
</div>
{/* System Settings */}
<div className="bg-white p-6 rounded-2xl shadow-sm border border-gray-200 space-y-6 lg:col-span-2">
<h3 className="text-lg font-bold text-gray-900 border-b border-gray-100 pb-4 flex items-center gap-2">
<ShieldAlert className="w-5 h-5 text-orange-500" />
وضعیت سیستم
</h3>
<div className="flex items-center justify-between p-4 bg-orange-50 rounded-xl border border-orange-100">
<div className="flex items-center gap-3">
<AlertCircle className="w-6 h-6 text-orange-500" />
<div>
<h4 className="font-bold text-gray-900">حالت تعمیرات (Maintenance Mode)</h4>
<p className="text-sm text-gray-600 mt-1">با فعال کردن این گزینه، سایت برای کاربران از دسترس خارج شده و پیام «در حال بروزرسانی» نمایش داده می‌شود.</p>
</div>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
className="sr-only peer"
checked={settings.MAINTENANCE_MODE === 'true'}
onChange={(e) => setSettings({ ...settings, MAINTENANCE_MODE: e.target.checked ? 'true' : 'false' })}
/>
<div className="w-14 h-7 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-6 after:w-6 after:transition-all peer-checked:bg-orange-500"></div>
</label>
</div>
</div>
<div className="lg:col-span-2 flex justify-end">
<button
type="submit"
disabled={isSaving}
className="bg-purple-600 hover:bg-purple-700 text-white font-bold py-3 px-8 rounded-xl transition-colors flex items-center gap-2 shadow-md shadow-purple-200"
>
{isSaving ? <Spinner size="sm" /> : <Save className="w-5 h-5" />}
ذخیره تغییرات
</button>
</div>
</form>
)}
</div>
);
}