diff --git a/src/App.jsx b/src/App.jsx index cc369ad..d9e4a91 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -10,6 +10,8 @@ import Settings from './pages/Settings'; import ForgotPassword from './pages/ForgotPassword'; import Roles from './pages/Roles'; import Users from './pages/Users'; +import Currency from './pages/Currency'; +import Location from './pages/Location'; // Protected Route Component const ProtectedRoute = ({ children }) => { @@ -113,6 +115,26 @@ const AppRoutes = () => { } + /> + + + + + + } + /> + + + + + + } /> diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx index 4673459..c74a75c 100644 --- a/src/components/Sidebar.jsx +++ b/src/components/Sidebar.jsx @@ -7,7 +7,9 @@ import { Menu, X, Users, - Shield + Shield, + DollarSign, + MapPin } from 'lucide-react'; const Sidebar = ({ isOpen, onToggle }) => { @@ -16,6 +18,8 @@ const Sidebar = ({ isOpen, onToggle }) => { { name: 'Transactions', href: '/transactions', icon: CreditCard }, { name: 'Roles', href: '/roles', icon: Shield }, { name: 'Users', href: '/users', icon: Users }, + { name: 'Currency', href: '/currency', icon: DollarSign }, + { name: 'Location', href: '/location', icon: MapPin }, { name: 'Settings', href: '/settings', icon: Settings }, ]; diff --git a/src/pages/Currency.jsx b/src/pages/Currency.jsx new file mode 100644 index 0000000..4becde9 --- /dev/null +++ b/src/pages/Currency.jsx @@ -0,0 +1,658 @@ +import React, { useEffect, useMemo, useState, useCallback } from 'react'; +import DataTable from '../components/DataTable'; +import { currencyAPI } from '../services/api'; +import { Plus, Search, Power, Wrench, Settings, DollarSign, Shield } from 'lucide-react'; +import { ToastContainer, toast } from 'react-toastify'; +import 'react-toastify/dist/ReactToastify.css'; +import { getErrorMessage, getSuccessMessage } from '../utils/errorHandler'; + +// debounce ساده +function debounce(func, delay) { + let timer; + return function (...args) { + clearTimeout(timer); + timer = setTimeout(() => func.apply(this, args), delay); + }; +} + +const Currency = () => { + const [currencies, setCurrencies] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const [filter, setFilter] = useState(''); + + const [addForm, setAddForm] = useState({ currencyCode: '' }); + const [isModalOpen, setIsModalOpen] = useState(false); + + // Modals for editing + const [permissionsModal, setPermissionsModal] = useState(null); + const [limitsModal, setLimitsModal] = useState(null); + const [feesModal, setFeesModal] = useState(null); + const [maintenanceModal, setMaintenanceModal] = useState(null); + const [maintenanceMessage, setMaintenanceMessage] = useState(''); + + // Form states for modals + const [permissionsForm, setPermissionsForm] = useState({ + depositEnabled: false, + cashOutEnabled: false, + exchangeEnabled: false, + purchaseEnabled: false, + transferFromEnabled: false, + transferToEnabled: false, + voucherCreationEnabled: false, + voucherRedeemEnabled: false, + }); + const [limitsForm, setLimitsForm] = useState({ + depositLimits: { min: null, max: null }, + cashOutLimits: { min: null, max: null }, + transferLimits: { min: null, max: null }, + voucherLimits: { min: null, max: null }, + }); + const [feesForm, setFeesForm] = useState({ + depositFee: { percent: 0, fixed: 0, minAmount: null, maxAmount: null }, + cashOutFee: { percent: 0, fixed: 0, minAmount: null, maxAmount: null }, + transferFee: { percent: 0, fixed: 0, minAmount: null, maxAmount: null }, + exchangeFee: { percent: 0, fixed: 0, minAmount: null, maxAmount: null }, + generateVoucherFee: { percent: 0, fixed: 0, minAmount: null, maxAmount: null }, + expireVoucherSystemFee: { percent: 0, fixed: 0, minAmount: null, maxAmount: null }, + }); + + // دریافت ارزها + const fetchCurrencies = useCallback(async () => { + try { + setLoading(true); + const list = await currencyAPI.list(); + setCurrencies(Array.isArray(list) ? list : []); + setError(''); + } catch (err) { + console.error(err); + const errorMsg = getErrorMessage(err); + setError(errorMsg); + toast.error(errorMsg); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + fetchCurrencies(); + }, [fetchCurrencies]); + + // --- اضافه کردن ارز --- + const onAddCurrency = async (e) => { + e.preventDefault(); + const { currencyCode } = addForm; + const code = currencyCode.trim().toUpperCase(); + + if (!code) { + toast.error('Please enter a currency code'); + return; + } + + // اعتبارسنجی: کد ارز باید 3 حرف باشد + if (code.length !== 3) { + toast.error('Currency code must be exactly 3 characters (e.g., USD, EUR)'); + return; + } + + // بررسی وجود ارز در لیست (برای جلوگیری از درخواست غیرضروری) + const exists = currencies.some(c => c.currencyCode?.code?.toUpperCase() === code); + if (exists) { + toast.error(`Currency ${code} already exists`); + return; + } + + try { + const result = await currencyAPI.create(code); + await fetchCurrencies(); + setIsModalOpen(false); + setAddForm({ currencyCode: '' }); + toast.success(getSuccessMessage(result) || 'Currency added successfully'); + } catch (err) { + console.error('Error creating currency:', err); + toast.error(getErrorMessage(err)); + } + }; + + // --- تغییر وضعیت فعال بودن --- + const onToggleActivation = async (currencyCode) => { + try { + const result = await currencyAPI.toggleActivation(currencyCode); + await fetchCurrencies(); + toast.success(getSuccessMessage(result) || 'Currency activation status updated'); + } catch (err) { + toast.error(getErrorMessage(err)); + } + }; + + // --- فعال/غیرفعال کردن Maintenance --- + const onEnableMaintenance = async (currencyCode, message = '') => { + try { + const result = await currencyAPI.enableMaintenance(currencyCode, message || null); + await fetchCurrencies(); + setMaintenanceModal(null); + setMaintenanceMessage(''); + toast.success(getSuccessMessage(result) || 'Maintenance enabled'); + } catch (err) { + toast.error(getErrorMessage(err)); + } + }; + + const onDisableMaintenance = async (currencyCode) => { + try { + const result = await currencyAPI.disableMaintenance(currencyCode); + await fetchCurrencies(); + setMaintenanceModal(null); + setMaintenanceMessage(''); + toast.success(getSuccessMessage(result) || 'Maintenance disabled'); + } catch (err) { + toast.error(getErrorMessage(err)); + } + }; + + // --- به‌روزرسانی Permissions --- + const onUpdatePermissions = async (currencyCode, permissions) => { + try { + const result = await currencyAPI.updatePermissions(currencyCode, permissions); + await fetchCurrencies(); + setPermissionsModal(null); + toast.success(getSuccessMessage(result) || 'Permissions updated successfully'); + } catch (err) { + toast.error(getErrorMessage(err)); + } + }; + + // --- به‌روزرسانی Limits --- + const onUpdateLimits = async (currencyCode, limits) => { + try { + const result = await currencyAPI.updateLimits(currencyCode, limits); + await fetchCurrencies(); + setLimitsModal(null); + toast.success(getSuccessMessage(result) || 'Limits updated successfully'); + } catch (err) { + toast.error(getErrorMessage(err)); + } + }; + + // --- به‌روزرسانی Fees --- + const onUpdateFees = async (currencyCode, fees) => { + try { + const result = await currencyAPI.updateFees(currencyCode, fees); + await fetchCurrencies(); + setFeesModal(null); + toast.success(getSuccessMessage(result) || 'Fees updated successfully'); + } catch (err) { + toast.error(getErrorMessage(err)); + } + }; + + // --- ستون‌های جدول --- + const columns = useMemo(() => [ + { + key: 'currencyCode', + header: 'Currency', + render: (val) => ( +
+
{val?.code || '—'}
+
{val?.name || ''}
+
+ ), + }, + { + key: 'currencyCode', + header: 'Symbol', + render: (val) => {val?.symbol || '—'}, + }, + { + key: 'isActive', + header: 'Active', + render: (val) => ( + + {val ? '✅ Yes' : '❌ No'} + + ), + }, + { + key: 'isUnderMaintenance', + header: 'Maintenance', + render: (val, row) => ( + + {val ? '⚠️ Yes' : '✓ No'} + + ), + }, + { + key: 'permissions', + header: 'Permissions', + render: (val) => { + if (!val) return '—'; + const enabled = Object.values(val).filter(Boolean).length; + return {enabled} enabled; + }, + }, + { + key: 'actions', + header: 'Actions', + render: (_val, row) => { + const code = row.currencyCode?.code; + if (!code) return '—'; + return ( +
+ + + + + +
+ ); + }, + }, + ], []); + + const handleFilterChange = useMemo(() => debounce(async (v) => { + // Filter is handled by DataTable component + }, 400), []); + + // --- مودال اضافه کردن ارز --- + const renderAddModal = () => { + if (!isModalOpen) return null; + return ( + <> +
setIsModalOpen(false)} /> +
+
+

Add Currency

+
+
+ setAddForm({ currencyCode: e.target.value.toUpperCase() })} + className="w-full p-2 border rounded-lg" + placeholder="Currency Code (e.g., USD, EUR)" + maxLength={3} + pattern="[A-Z]{3}" + title="Enter a 3-letter currency code" + /> +

Enter a 3-letter ISO currency code

+
+
+ + +
+
+
+
+ + ); + }; + + // --- مودال Maintenance --- + const renderMaintenanceModal = () => { + if (!maintenanceModal) return null; + const code = maintenanceModal.currencyCode?.code; + + return ( + <> +
{ + setMaintenanceModal(null); + setMaintenanceMessage(''); + }} /> +
+
+

+ Maintenance: {code} +

+
+
+ +