feat(topup-agent): add admin modal with create and search functionality

This commit is contained in:
ghazall-ag
2026-01-01 20:59:56 +03:30
parent 144687c4e2
commit 50112bb40b
2 changed files with 253 additions and 3 deletions

View File

@@ -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>
);
};

View File

@@ -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;
}
},
};