diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx
index 8821d57..d3ebac9 100644
--- a/src/components/Sidebar.jsx
+++ b/src/components/Sidebar.jsx
@@ -16,13 +16,13 @@ import {
const Sidebar = ({ isOpen, onToggle }) => {
const navigation = [
{ name: 'Dashboard', href: '/', icon: LayoutDashboard },
- { name: 'Transactions', href: '/transactions', icon: CreditCard },
+ // { 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: 'Issuer', href: '/issuer', icon: Building2 },
- { name: 'Settings', href: '/settings', icon: Settings },
+ // { name: 'Settings', href: '/settings', icon: Settings },
];
return (
diff --git a/src/pages/Issuer.jsx b/src/pages/Issuer.jsx
index 663d4b1..a203be8 100644
--- a/src/pages/Issuer.jsx
+++ b/src/pages/Issuer.jsx
@@ -1,7 +1,7 @@
import React, { useEffect, useMemo, useState, useCallback } from 'react';
import DataTable from '../components/DataTable';
-import { issuerAPI, cityAPI, currencyAPI, countryAPI, provinceAPI, generalAPI } from '../services/api';
-import { Plus, Trash2, Search, Pencil, Power, Settings, DollarSign, Filter } from 'lucide-react';
+import { issuerAPI, cityAPI, currencyAPI, generalAPI } from '../services/api';
+import { Plus, Trash2, Search, Pencil, Power, Settings, DollarSign, Filter, X, Wallet, RefreshCw, Users } from 'lucide-react';
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { getErrorMessage, getSuccessMessage } from '../utils/errorHandler';
@@ -15,15 +15,10 @@ const Issuer = () => {
// Filter states
const [isFilterModalOpen, setIsFilterModalOpen] = useState(false);
const [filterState, setFilterState] = useState({
- countryId: '',
- provinceId: '',
- cityId: '',
isActive: '',
supportCurrencyCode: '',
supportCapabilities: ''
});
- const [countries, setCountries] = useState([]);
- const [provinces, setProvinces] = useState([]);
const [issuerCapabilities, setIssuerCapabilities] = useState([]);
const [addForm, setAddForm] = useState({
@@ -47,12 +42,41 @@ const Issuer = () => {
const [editingIssuer, setEditingIssuer] = useState(null);
const [capabilitiesModal, setCapabilitiesModal] = useState(null);
const [currenciesModal, setCurrenciesModal] = useState(null);
+ const [topUpAgentModal, setTopUpAgentModal] = useState(null);
+ const [adminModal, setAdminModal] = useState(null);
const [cities, setCities] = useState([]);
const [currencies, setCurrencies] = useState([]);
const [selectedCapabilities, setSelectedCapabilities] = useState([]);
const [selectedCurrencies, setSelectedCurrencies] = useState([]);
const [capabilitiesData, setCapabilitiesData] = useState([]); // Store full capabilities data from API
const [currenciesData, setCurrenciesData] = useState([]); // Store full currencies data from API
+
+ // TopUpAgentManagement states
+ const [topUpAgentData, setTopUpAgentData] = useState({ maxAgents: 0, isActive: false });
+ const [topUpAgentCurrencies, setTopUpAgentCurrencies] = useState([]);
+ const [topUpAgentCurrencyModal, setTopUpAgentCurrencyModal] = useState(null);
+ const [topUpAgentCurrencyForm, setTopUpAgentCurrencyForm] = useState({
+ currencyCode: '',
+ voucherGeneratingFee: { percent: 0, fixed: 0, minAmount: 0, maxAmount: 0 },
+ agentMarketingFee: { percent: 0, fixed: 0, minAmount: 0, maxAmount: 0 },
+ agentWalletMaxBalanceLimit: { min: 0, max: 0 },
+ agentWalletDepositMonthlyLimit: { amount: 0 }
+ });
+
+ // Wallet states
+ const [walletBalance, setWalletBalance] = useState(null);
+ const [walletTransactions, setWalletTransactions] = useState([]);
+ const [walletLoading, setWalletLoading] = useState(false);
+ const [selectedCurrencyForWallet, setSelectedCurrencyForWallet] = useState(null);
+ const [depositAmount, setDepositAmount] = useState(0);
+ const [isDepositModalOpen, setIsDepositModalOpen] = useState(false);
+
+ // Admin states
+ const [admins, setAdmins] = useState([]);
+ const [adminLoading, setAdminLoading] = useState(false);
+ const [adminSearchEmail, setAdminSearchEmail] = useState('');
+ const [searchResults, setSearchResults] = useState([]);
+ const [searchLoading, setSearchLoading] = useState(false);
// Capabilities options - will be loaded from API
const capabilityOptions = issuerCapabilities.map(cap => cap.name);
@@ -62,9 +86,6 @@ const Issuer = () => {
try {
setLoading(true);
const params = {};
- if (filters.countryId) params.countryId = filters.countryId;
- if (filters.provinceId) params.provinceId = filters.provinceId;
- if (filters.cityId) params.cityId = filters.cityId;
if (filters.isActive !== undefined && filters.isActive !== null && filters.isActive !== '') {
params.isActive = filters.isActive === 'true' || filters.isActive === true;
}
@@ -91,26 +112,8 @@ const Issuer = () => {
}
}, []);
- // Load countries, provinces, and capabilities for filter modal
+ // Load capabilities for filter modal
useEffect(() => {
- const loadCountries = async () => {
- try {
- const countriesList = await countryAPI.listAll();
- setCountries(Array.isArray(countriesList) ? countriesList : []);
- } catch (err) {
- console.error('Failed to load countries:', err);
- }
- };
-
- const loadProvinces = async () => {
- try {
- const provincesList = await provinceAPI.list({ currentPage: 1, pageSize: 1000 });
- setProvinces(Array.isArray(provincesList) ? provincesList : []);
- } catch (err) {
- console.error('Failed to load provinces:', err);
- }
- };
-
const loadCapabilities = async () => {
try {
const capabilitiesList = await generalAPI.getIssuerCapabilities();
@@ -120,8 +123,6 @@ const Issuer = () => {
}
};
- loadCountries();
- loadProvinces();
loadCapabilities();
}, []);
@@ -340,111 +341,77 @@ const Issuer = () => {
setCurrenciesModal(issuer);
try {
// Get allowed currencies from /api/v1/Issuer/{issuerId}/AllowedCurrencies
+ // API returns: { data: [{ currencyCode, currencyEnglishName, allowed }, ...], ... }
const allowed = await issuerAPI.getAllowedCurrencies(issuer.id);
console.log('🔵 Allowed currencies received from API:', allowed);
if (Array.isArray(allowed) && allowed.length > 0) {
- // Use currencies from API
- setCurrenciesData(allowed);
+ // Use currencies directly from API response
+ const allCurrenciesList = allowed.map(curr => {
+ const code = String(curr.currencyCode || curr.code || '').trim();
+ if (!code) return null;
+
+ return {
+ code: code,
+ currencyCode: code,
+ name: curr.currencyEnglishName || curr.name || '',
+ currencyEnglishName: curr.currencyEnglishName || curr.name || '',
+ allowed: curr.allowed === true || curr.allowed === 'true'
+ };
+ }).filter(c => c !== null);
+
+ setCurrenciesData(allCurrenciesList);
+
// Extract selected currencies (those with allowed = true)
- const selected = allowed
+ const selected = allCurrenciesList
.filter(c => c.allowed === true || c.allowed === 'true')
.map(c => c.code || c.currencyCode);
setSelectedCurrencies(selected);
console.log('🔵 Selected currencies:', selected);
+ console.log('🔵 All currencies with allowed status:', allCurrenciesList);
} else {
- // If no currencies from API, create default structure from available currencies
- const defaultCurrencies = currencies.map(curr => {
- const code = curr.currencyCode?.code || curr.code || '';
- const name = curr.currencyCode?.name || curr.name || '';
- return {
- code: code,
- currencyCode: code,
- name: name,
- currencyEnglishName: name,
- allowed: false
- };
- }).filter(c => c.code);
- setCurrenciesData(defaultCurrencies);
+ // If API returns empty array, show empty list
+ setCurrenciesData([]);
setSelectedCurrencies([]);
- console.log('🔵 Using default currencies structure');
+ console.log('🔵 No currencies returned from API');
}
} catch (err) {
console.error('🔴 Error loading allowed currencies:', err);
- // Fallback: create default structure
- const defaultCurrencies = currencies.map(curr => {
- const code = curr.currencyCode?.code || curr.code || '';
- const name = curr.currencyCode?.name || curr.name || '';
- return {
- code: code,
- currencyCode: code,
- name: name,
- currencyEnglishName: name,
- allowed: false
- };
- }).filter(c => c.code);
- setCurrenciesData(defaultCurrencies);
+ toast.error('Failed to load allowed currencies');
+ setCurrenciesData([]);
setSelectedCurrencies([]);
- toast.error('Failed to load allowed currencies. Using default structure.');
}
};
const onUpdateCurrencies = async () => {
if (!currenciesModal) return;
try {
- // Get current allowed currencies from API
- let currentAllowedCurrencies = [];
- try {
- currentAllowedCurrencies = await issuerAPI.getAllowedCurrencies(currenciesModal.id);
- } catch (err) {
- console.warn('⚠️ Could not fetch current allowed currencies:', err);
- }
-
- // Build map of current currencies (those already configured)
- const currentCurrenciesMap = new Map();
- if (Array.isArray(currentAllowedCurrencies)) {
- currentAllowedCurrencies.forEach(curr => {
- const code = curr.code || curr.currencyCode || '';
- if (code) {
- currentCurrenciesMap.set(String(code).trim(), curr);
- }
- });
- }
-
- // Build IssuerAllowedCurrencyDto array from SELECTED currencies only
- // Only send currencies that are selected (to avoid conflict with already configured ones)
+ // Get all currencies that should be sent (from currenciesData or currencies)
const allCurrencies = currenciesData.length > 0 ? currenciesData : currencies;
- const allowedCurrencies = selectedCurrencies
- .filter(selectedCode => {
- // Filter out invalid currency codes
- return selectedCode && String(selectedCode).trim() !== '';
- })
- .map(selectedCode => {
- const code = String(selectedCode).trim();
+ // Build IssuerAllowedCurrencyDto array for ALL currencies
+ // Set allowed=true for selected currencies, allowed=false for unselected ones
+ const allowedCurrencies = allCurrencies
+ .map(curr => {
+ const code = String(curr.code || curr.currencyCode || curr.currencyCode?.code || '').trim();
+ if (!code) return null;
- // Find the currency data
- const currencyData = allCurrencies.find(curr => {
- const currCode = String(curr.code || curr.currencyCode || curr.currencyCode?.code || '').trim();
- return currCode === code ||
- currCode === String(curr.currencyCode || '').trim() ||
- currCode === String(curr.currencyCode?.code || '').trim();
+ const name = String(curr.currencyEnglishName || curr.name || curr.currencyCode?.name || '').trim();
+
+ // Check if this currency is in selectedCurrencies
+ const isSelected = selectedCurrencies.some(selected => {
+ const selectedStr = String(selected).trim();
+ return selectedStr === code ||
+ selectedStr === String(curr.currencyCode || '').trim();
});
- const name = currencyData
- ? String(currencyData.currencyEnglishName || currencyData.name || currencyData.currencyCode?.name || '').trim()
- : '';
-
- // Use existing currency data if available
- const existingCurrency = currentCurrenciesMap.get(code);
-
return {
currencyCode: code,
- currencyEnglishName: name || existingCurrency?.currencyEnglishName || existingCurrency?.name || code,
- allowed: true // All selected currencies have allowed=true
+ currencyEnglishName: name || code,
+ allowed: isSelected // true if selected, false if not
};
})
- .filter(curr => curr.currencyCode && curr.currencyCode.trim() !== '');
+ .filter(curr => curr !== null && curr.currencyCode && curr.currencyCode.trim() !== '');
console.log('🔵 Sending ALL currencies to update:', JSON.stringify(allowedCurrencies, null, 2));
console.log('🔵 Issuer ID:', currenciesModal.id);
@@ -452,7 +419,6 @@ const Issuer = () => {
console.log('🔵 Selected currencies:', selectedCurrencies);
console.log('🔵 Currencies with allowed=true:', allowedCurrencies.filter(c => c.allowed).length);
console.log('🔵 Currencies with allowed=false:', allowedCurrencies.filter(c => !c.allowed).length);
- console.log('🔵 Current currencies from API:', currentAllowedCurrencies.length);
// Call PUT /api/v1/Issuer/{issuerId}/AllowedCurrencies
await issuerAPI.updateAllowedCurrencies(currenciesModal.id, allowedCurrencies);
@@ -474,6 +440,229 @@ const Issuer = () => {
}
};
+ // --- مدیریت TopUpAgentManagement ---
+ const openTopUpAgentModal = async (issuer) => {
+ setTopUpAgentModal(issuer);
+ try {
+ // دریافت تنظیمات TopUpAgentManagement
+ const data = await issuerAPI.getTopUpAgentManagement(issuer.id);
+ setTopUpAgentData(data || { maxAgents: 0, isActive: false });
+
+ // دریافت لیست currencies
+ const currenciesList = await issuerAPI.getTopUpAgentManagementCurrencies(issuer.id);
+ setTopUpAgentCurrencies(Array.isArray(currenciesList) ? currenciesList : []);
+ } catch (err) {
+ console.error('Error loading TopUpAgentManagement:', err);
+ toast.error('Failed to load TopUpAgentManagement data');
+ setTopUpAgentData({ maxAgents: 0, isActive: false });
+ setTopUpAgentCurrencies([]);
+ }
+ };
+
+ const onUpdateTopUpAgentManagement = async (e) => {
+ e.preventDefault();
+ if (!topUpAgentModal) return;
+ try {
+ await issuerAPI.updateTopUpAgentManagement(topUpAgentModal.id, topUpAgentData.maxAgents);
+ toast.success('TopUpAgentManagement updated successfully');
+ // Refresh data
+ const data = await issuerAPI.getTopUpAgentManagement(topUpAgentModal.id);
+ setTopUpAgentData(data || { maxAgents: 0, isActive: false });
+ await fetchIssuers(filterState);
+ } catch (err) {
+ toast.error(getErrorMessage(err));
+ }
+ };
+
+ const openTopUpAgentCurrencyModal = (currency = null) => {
+ if (currency) {
+ // Edit mode
+ setTopUpAgentCurrencyForm({
+ currencyCode: currency.currencyCode || currency.code || '',
+ voucherGeneratingFee: currency.voucherGeneratingFee || { percent: 0, fixed: 0, minAmount: 0, maxAmount: 0 },
+ agentMarketingFee: currency.agentMarketingFee || { percent: 0, fixed: 0, minAmount: 0, maxAmount: 0 },
+ agentWalletMaxBalanceLimit: currency.agentWalletMaxBalanceLimit || { min: 0, max: 0 },
+ agentWalletDepositMonthlyLimit: currency.agentWalletDepositMonthlyLimit || { amount: 0 }
+ });
+ } else {
+ // Add mode
+ setTopUpAgentCurrencyForm({
+ currencyCode: '',
+ voucherGeneratingFee: { percent: 0, fixed: 0, minAmount: 0, maxAmount: 0 },
+ agentMarketingFee: { percent: 0, fixed: 0, minAmount: 0, maxAmount: 0 },
+ agentWalletMaxBalanceLimit: { min: 0, max: 0 },
+ agentWalletDepositMonthlyLimit: { amount: 0 }
+ });
+ }
+ setTopUpAgentCurrencyModal(currency);
+ };
+
+ const onSaveTopUpAgentCurrency = async (e) => {
+ e.preventDefault();
+ if (!topUpAgentModal || !topUpAgentCurrencyForm.currencyCode) {
+ toast.error('Currency code is required');
+ return;
+ }
+ try {
+ if (topUpAgentCurrencyModal) {
+ // Update existing currency
+ await issuerAPI.updateTopUpAgentManagementCurrency(
+ topUpAgentModal.id,
+ topUpAgentCurrencyForm.currencyCode,
+ topUpAgentCurrencyForm
+ );
+ toast.success('Currency updated successfully');
+ } else {
+ // Create new currency
+ await issuerAPI.createTopUpAgentManagementCurrency(
+ topUpAgentModal.id,
+ topUpAgentCurrencyForm.currencyCode,
+ topUpAgentCurrencyForm
+ );
+ toast.success('Currency added successfully');
+ }
+
+ // Refresh currencies list
+ const currenciesList = await issuerAPI.getTopUpAgentManagementCurrencies(topUpAgentModal.id);
+ setTopUpAgentCurrencies(Array.isArray(currenciesList) ? currenciesList : []);
+ setTopUpAgentCurrencyModal(null);
+ setTopUpAgentCurrencyForm({
+ currencyCode: '',
+ voucherGeneratingFee: { percent: 0, fixed: 0, minAmount: 0, maxAmount: 0 },
+ agentMarketingFee: { percent: 0, fixed: 0, minAmount: 0, maxAmount: 0 },
+ agentWalletMaxBalanceLimit: { min: 0, max: 0 },
+ agentWalletDepositMonthlyLimit: { amount: 0 }
+ });
+ } catch (err) {
+ toast.error(getErrorMessage(err));
+ }
+ };
+
+ const onDeleteTopUpAgentCurrency = async (currencyCode) => {
+ if (!topUpAgentModal || !currencyCode) return;
+ if (!window.confirm(`Are you sure you want to delete currency ${currencyCode}?`)) return;
+ try {
+ await issuerAPI.deleteTopUpAgentManagementCurrency(topUpAgentModal.id, currencyCode);
+ toast.success('Currency deleted successfully');
+
+ // Refresh currencies list
+ const currenciesList = await issuerAPI.getTopUpAgentManagementCurrencies(topUpAgentModal.id);
+ setTopUpAgentCurrencies(Array.isArray(currenciesList) ? currenciesList : []);
+ } catch (err) {
+ toast.error(getErrorMessage(err));
+ }
+ };
+
+ // --- مدیریت Wallet ---
+ const openWalletView = async (currencyCode) => {
+ if (!topUpAgentModal || !currencyCode) return;
+ setSelectedCurrencyForWallet(currencyCode);
+ setWalletLoading(true);
+ try {
+ // دریافت balance و transactions به صورت همزمان
+ const [balance, transactions] = await Promise.all([
+ issuerAPI.getTopUpAgentManagementWalletBalance(topUpAgentModal.id, currencyCode),
+ issuerAPI.getTopUpAgentManagementWalletTransactions(topUpAgentModal.id, currencyCode)
+ ]);
+ setWalletBalance(balance);
+ setWalletTransactions(Array.isArray(transactions) ? transactions : []);
+ } catch (err) {
+ console.error('Error loading wallet data:', err);
+ toast.error('Failed to load wallet data');
+ setWalletBalance(null);
+ setWalletTransactions([]);
+ } finally {
+ setWalletLoading(false);
+ }
+ };
+
+ const onDepositWallet = async (e) => {
+ e.preventDefault();
+ if (!topUpAgentModal || !selectedCurrencyForWallet || !depositAmount || depositAmount <= 0) {
+ toast.error('Please enter a valid deposit amount');
+ return;
+ }
+ try {
+ await issuerAPI.depositTopUpAgentManagementWallet(
+ topUpAgentModal.id,
+ selectedCurrencyForWallet,
+ depositAmount
+ );
+ toast.success('Deposit successful');
+ setIsDepositModalOpen(false);
+ setDepositAmount(0);
+
+ // Refresh wallet data
+ await openWalletView(selectedCurrencyForWallet);
+ } catch (err) {
+ toast.error(getErrorMessage(err));
+ }
+ };
+
+ const refreshWalletData = async () => {
+ if (selectedCurrencyForWallet) {
+ await openWalletView(selectedCurrencyForWallet);
+ }
+ };
+
+ // --- مدیریت Admin ---
+ const openAdminModal = async (issuer) => {
+ setAdminModal(issuer);
+ setAdminLoading(true);
+ setAdmins([]);
+ setSearchResults([]);
+ setAdminSearchEmail('');
+ try {
+ const adminsList = await issuerAPI.getAdmins(issuer.id);
+ setAdmins(Array.isArray(adminsList) ? adminsList : []);
+ } catch (err) {
+ console.error('Error loading admins:', err);
+ toast.error('Failed to load admins');
+ setAdmins([]);
+ } finally {
+ setAdminLoading(false);
+ }
+ };
+
+ const searchUsers = async () => {
+ if (!adminSearchEmail || !adminSearchEmail.trim()) {
+ toast.error('Please enter an email to search');
+ return;
+ }
+ setSearchLoading(true);
+ setSearchResults([]);
+ try {
+ const results = await generalAPI.searchUsersByEmail(adminSearchEmail.trim());
+ setSearchResults(Array.isArray(results) ? results : []);
+ if (results.length === 0) {
+ toast.info('No users found with this email');
+ }
+ } catch (err) {
+ console.error('Error searching users:', err);
+ toast.error(getErrorMessage(err));
+ setSearchResults([]);
+ } finally {
+ setSearchLoading(false);
+ }
+ };
+
+ const onAddAdmin = async (user) => {
+ if (!adminModal || !user) return;
+ try {
+ await issuerAPI.addAdmin(adminModal.id, { userId: user.id || user.userId });
+ toast.success('Admin added successfully');
+ // Refresh admins list
+ const adminsList = await issuerAPI.getAdmins(adminModal.id);
+ setAdmins(Array.isArray(adminsList) ? adminsList : []);
+ // Clear search
+ setSearchResults([]);
+ setAdminSearchEmail('');
+ } catch (err) {
+ console.error('Error adding admin:', err);
+ toast.error(getErrorMessage(err));
+ }
+ };
+
// --- مدیریت فیلترها ---
const handleApplyFilter = () => {
fetchIssuers(filterState);
@@ -482,9 +671,6 @@ const Issuer = () => {
const handleResetFilter = () => {
setFilterState({
- countryId: '',
- provinceId: '',
- cityId: '',
isActive: '',
supportCurrencyCode: '',
supportCapabilities: ''
@@ -493,74 +679,6 @@ const Issuer = () => {
setIsFilterModalOpen(false);
};
- // وقتی country در فیلتر تغییر میکند، provinces را بهروزرسانی کن
- useEffect(() => {
- if (filterState.countryId) {
- const loadProvinces = async () => {
- try {
- const provincesList = await provinceAPI.list({
- currentPage: 1,
- pageSize: 1000,
- countryId: filterState.countryId
- });
- setProvinces(Array.isArray(provincesList) ? provincesList : []);
- } catch (err) {
- console.error('Failed to load provinces:', err);
- }
- };
- loadProvinces();
- // Reset province and city when country changes
- setFilterState(prev => ({ ...prev, provinceId: '', cityId: '' }));
- } else {
- // Load all provinces if no country selected
- const loadProvinces = async () => {
- try {
- const provincesList = await provinceAPI.list({ currentPage: 1, pageSize: 1000 });
- setProvinces(Array.isArray(provincesList) ? provincesList : []);
- } catch (err) {
- console.error('Failed to load provinces:', err);
- }
- };
- loadProvinces();
- }
- }, [filterState.countryId]);
-
- // وقتی province در فیلتر تغییر میکند، cities را بهروزرسانی کن
- useEffect(() => {
- if (filterState.provinceId) {
- const loadCities = async () => {
- try {
- const citiesList = await cityAPI.list({
- currentPage: 1,
- pageSize: 1000,
- provinceId: filterState.provinceId
- });
- setCities(Array.isArray(citiesList) ? citiesList : []);
- } catch (err) {
- console.error('Failed to load cities:', err);
- }
- };
- loadCities();
- // Reset city when province changes
- setFilterState(prev => ({ ...prev, cityId: '' }));
- } else if (filterState.countryId) {
- // Load cities for selected country if no province selected
- const loadCities = async () => {
- try {
- const citiesList = await cityAPI.list({
- currentPage: 1,
- pageSize: 1000,
- countryId: filterState.countryId
- });
- setCities(Array.isArray(citiesList) ? citiesList : []);
- } catch (err) {
- console.error('Failed to load cities:', err);
- }
- };
- loadCities();
- }
- }, [filterState.provinceId, filterState.countryId]);
-
// --- ستونهای جدول ---
const columns = useMemo(() => [
{ key: 'name', header: 'Name' },
@@ -617,6 +735,18 @@ const Issuer = () => {
>