feat(topup-agent): add admin modal with create and search functionality
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React, { useEffect, useMemo, useState, useCallback } from 'react';
|
||||
import DataTable from '../components/DataTable';
|
||||
import { topUpAgentAPI, cityAPI, generalAPI } from '../services/api';
|
||||
import { Plus, Search, Pencil, Trash2, Power, DollarSign, Wallet, X } from 'lucide-react';
|
||||
import { topUpAgentAPI, cityAPI, generalAPI, currencyAPI } from '../services/api';
|
||||
import { Plus, Search, Pencil, Trash2, Power, DollarSign, Wallet, X, Users } from 'lucide-react';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import { getErrorMessage, getSuccessMessage } from '../utils/errorHandler';
|
||||
@@ -40,6 +40,16 @@ const TopUpAgent = () => {
|
||||
const [walletData, setWalletData] = useState(null);
|
||||
const [walletTransactions, setWalletTransactions] = useState([]);
|
||||
const [depositForm, setDepositForm] = useState({ amount: 0, currencyCode: '' });
|
||||
|
||||
// Admin management states
|
||||
const [selectedAgentForAdmin, setSelectedAgentForAdmin] = useState(null);
|
||||
const [isAdminModalOpen, setIsAdminModalOpen] = useState(false);
|
||||
const [agentAdmins, setAgentAdmins] = useState([]);
|
||||
const [adminForm, setAdminForm] = useState({
|
||||
email: '',
|
||||
});
|
||||
const [searchedUser, setSearchedUser] = useState(null);
|
||||
const [searchingUser, setSearchingUser] = useState(false);
|
||||
|
||||
// دریافت شهرها و ارزها
|
||||
useEffect(() => {
|
||||
@@ -54,7 +64,7 @@ const TopUpAgent = () => {
|
||||
|
||||
const fetchCurrencies = async () => {
|
||||
try {
|
||||
const list = await generalAPI.getCurrencies();
|
||||
const list = await currencyAPI.list({ pageSize: 1000 });
|
||||
setCurrencies(Array.isArray(list) ? list : []);
|
||||
} catch (err) {
|
||||
console.error('Failed to load currencies:', err);
|
||||
@@ -272,6 +282,77 @@ const TopUpAgent = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// باز کردن modal مدیریت Admin
|
||||
const openAdminModal = async (agent) => {
|
||||
setSelectedAgentForAdmin(agent);
|
||||
setSearchedUser(null);
|
||||
setAdminForm({ email: '' });
|
||||
try {
|
||||
const list = await topUpAgentAPI.getAdmins(agent.id);
|
||||
setAgentAdmins(Array.isArray(list) ? list : []);
|
||||
} catch (err) {
|
||||
toast.error(getErrorMessage(err));
|
||||
}
|
||||
setIsAdminModalOpen(true);
|
||||
};
|
||||
|
||||
// جستجوی کاربر با ایمیل
|
||||
const handleSearchUser = async () => {
|
||||
if (!adminForm.email?.trim()) {
|
||||
toast.error('Please enter an email address');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setSearchingUser(true);
|
||||
const users = await generalAPI.searchUsersByEmail(adminForm.email);
|
||||
if (users && users.length > 0) {
|
||||
setSearchedUser(users[0]); // استفاده از اولین نتیجه
|
||||
toast.success('User found');
|
||||
} else {
|
||||
setSearchedUser(null);
|
||||
toast.error('No user found with this email');
|
||||
}
|
||||
} catch (err) {
|
||||
setSearchedUser(null);
|
||||
toast.error(getErrorMessage(err) || 'Failed to search user');
|
||||
} finally {
|
||||
setSearchingUser(false);
|
||||
}
|
||||
};
|
||||
|
||||
// اضافه کردن Admin
|
||||
const handleAddAdmin = async () => {
|
||||
if (!searchedUser?.id) {
|
||||
toast.error('Please search and select a user first');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await topUpAgentAPI.addAdmin(selectedAgentForAdmin.id, searchedUser.id);
|
||||
toast.success('Admin added successfully');
|
||||
const list = await topUpAgentAPI.getAdmins(selectedAgentForAdmin.id);
|
||||
setAgentAdmins(Array.isArray(list) ? list : []);
|
||||
setAdminForm({ email: '' });
|
||||
setSearchedUser(null);
|
||||
} catch (err) {
|
||||
toast.error(getErrorMessage(err));
|
||||
}
|
||||
};
|
||||
|
||||
// حذف Admin
|
||||
const handleDeleteAdmin = async (admin) => {
|
||||
if (!window.confirm(`Are you sure you want to remove admin "${admin.email}"?`)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await topUpAgentAPI.removeAdmin(selectedAgentForAdmin.id, admin.id);
|
||||
toast.success('Admin removed successfully');
|
||||
const list = await topUpAgentAPI.getAdmins(selectedAgentForAdmin.id);
|
||||
setAgentAdmins(Array.isArray(list) ? list : []);
|
||||
} catch (err) {
|
||||
toast.error(getErrorMessage(err));
|
||||
}
|
||||
};
|
||||
|
||||
// Reset filters
|
||||
const handleResetFilters = () => {
|
||||
setFilters({
|
||||
@@ -334,6 +415,12 @@ const TopUpAgent = () => {
|
||||
>
|
||||
<Wallet className="h-3 w-3 mr-1" /> Wallet
|
||||
</button>
|
||||
<button
|
||||
onClick={() => openAdminModal(row)}
|
||||
className="inline-flex items-center px-2 py-1 text-xs rounded-md bg-teal-100 text-teal-700 hover:opacity-90"
|
||||
>
|
||||
<Users className="h-3 w-3 mr-1" /> Admins
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDelete(row)}
|
||||
className="inline-flex items-center px-2 py-1 text-xs rounded-md bg-red-100 text-red-700 hover:opacity-90"
|
||||
@@ -716,6 +803,112 @@ const TopUpAgent = () => {
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Admin Management Modal */}
|
||||
{isAdminModalOpen && selectedAgentForAdmin && (
|
||||
<>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 z-40" onClick={() => setIsAdminModalOpen(false)} />
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<div className="w-full max-w-2xl card p-6 relative bg-white dark:bg-gray-800 rounded-2xl shadow-lg max-h-[90vh] overflow-y-auto">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||
Manage Admins - {selectedAgentForAdmin.name}
|
||||
</h3>
|
||||
<button onClick={() => setIsAdminModalOpen(false)} className="text-gray-400 hover:text-gray-600">
|
||||
<X className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4 mb-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Search User by Email *</label>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
type="email"
|
||||
value={adminForm.email}
|
||||
onChange={(e) => {
|
||||
setAdminForm({ email: e.target.value });
|
||||
setSearchedUser(null);
|
||||
}}
|
||||
onKeyPress={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
handleSearchUser();
|
||||
}
|
||||
}}
|
||||
className="flex-1 p-2 border rounded-lg"
|
||||
placeholder="Enter user email..."
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleSearchUser}
|
||||
disabled={searchingUser || !adminForm.email?.trim()}
|
||||
className="btn-primary px-4 py-2 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{searchingUser ? 'Searching...' : 'Search'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{searchedUser && (
|
||||
<div className="p-4 bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||
<h4 className="font-medium mb-2">User Found:</h4>
|
||||
<div className="space-y-1 text-sm">
|
||||
<div><span className="font-medium">Email:</span> {searchedUser.email || '—'}</div>
|
||||
{searchedUser.firstName && (
|
||||
<div><span className="font-medium">Name:</span> {searchedUser.firstName} {searchedUser.lastName || ''}</div>
|
||||
)}
|
||||
{searchedUser.phoneNumber && (
|
||||
<div><span className="font-medium">Phone:</span> {searchedUser.phoneNumber}</div>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleAddAdmin}
|
||||
className="mt-3 btn-primary px-4 py-2 rounded-lg"
|
||||
>
|
||||
Add as Admin
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="border-t pt-4">
|
||||
<h4 className="font-medium mb-2">Current Admins</h4>
|
||||
<div className="space-y-2">
|
||||
{agentAdmins.length === 0 ? (
|
||||
<p className="text-sm text-gray-500">No admins added</p>
|
||||
) : (
|
||||
agentAdmins.map((admin) => (
|
||||
<div key={admin.id || admin.email} className="flex justify-between items-center p-2 bg-gray-50 dark:bg-gray-700 rounded">
|
||||
<div>
|
||||
<span className="font-medium">{admin.email}</span>
|
||||
{admin.firstName && admin.lastName && (
|
||||
<span className="text-sm text-gray-500 ml-2">
|
||||
({admin.firstName} {admin.lastName})
|
||||
</span>
|
||||
)}
|
||||
{admin.phoneNumber && (
|
||||
<span className="text-sm text-gray-500 ml-2">
|
||||
- {admin.phoneNumber}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleDeleteAdmin(admin)}
|
||||
className="text-red-600 hover:text-red-800"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -205,5 +205,62 @@ export const topUpAgentAPI = {
|
||||
});
|
||||
return res?.data?.data?.data || [];
|
||||
},
|
||||
|
||||
// POST /api/v1/TopUpAgent/{topUpAgentId}/Admin
|
||||
async addAdmin(topUpAgentId, userId) {
|
||||
try {
|
||||
const payload = {
|
||||
userId: String(userId || '').trim(),
|
||||
};
|
||||
const res = await api.post(`/api/v1/TopUpAgent/${encodeURIComponent(topUpAgentId)}/Admin`, payload, {
|
||||
skipAuthRedirect: true
|
||||
});
|
||||
return res?.data?.data || res?.data;
|
||||
} catch (error) {
|
||||
console.error('🔴 TopUpAgent API - addAdmin error:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// GET /api/v1/TopUpAgent/{topUpAgentId}/Admin
|
||||
async getAdmins(topUpAgentId, params = {}) {
|
||||
try {
|
||||
const { currentPage = 1, pageSize = 10, ...otherParams } = params;
|
||||
const res = await api.get(`/api/v1/TopUpAgent/${encodeURIComponent(topUpAgentId)}/Admin`, {
|
||||
params: { currentPage, pageSize, ...otherParams },
|
||||
skipAuthRedirect: true
|
||||
});
|
||||
// بررسی ساختار response
|
||||
if (res?.data?.data?.data) {
|
||||
return Array.isArray(res.data.data.data) ? res.data.data.data : [];
|
||||
}
|
||||
if (res?.data?.data) {
|
||||
return Array.isArray(res.data.data) ? res.data.data : [];
|
||||
}
|
||||
if (res?.data) {
|
||||
return Array.isArray(res.data) ? res.data : [];
|
||||
}
|
||||
return [];
|
||||
} catch (error) {
|
||||
console.error('🔴 TopUpAgent API - getAdmins error:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// DELETE /api/v1/TopUpAgent/{topUpAgentId}/Admin
|
||||
async removeAdmin(topUpAgentId, adminId) {
|
||||
try {
|
||||
const url = `/api/v1/TopUpAgent/${encodeURIComponent(topUpAgentId)}/Admin`;
|
||||
const config = {
|
||||
skipAuthRedirect: true,
|
||||
params: adminId ? { adminId } : {}
|
||||
};
|
||||
const res = await api.delete(url, config);
|
||||
return res?.data;
|
||||
} catch (error) {
|
||||
console.error('🔴 TopUpAgent API - removeAdmin error:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user