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 React, { useEffect, useMemo, useState, useCallback } from 'react';
|
||||||
import DataTable from '../components/DataTable';
|
import DataTable from '../components/DataTable';
|
||||||
import { topUpAgentAPI, cityAPI, generalAPI } from '../services/api';
|
import { topUpAgentAPI, cityAPI, generalAPI, currencyAPI } from '../services/api';
|
||||||
import { Plus, Search, Pencil, Trash2, Power, DollarSign, Wallet, X } from 'lucide-react';
|
import { Plus, Search, Pencil, Trash2, Power, DollarSign, Wallet, X, Users } from 'lucide-react';
|
||||||
import { ToastContainer, toast } from 'react-toastify';
|
import { ToastContainer, toast } from 'react-toastify';
|
||||||
import 'react-toastify/dist/ReactToastify.css';
|
import 'react-toastify/dist/ReactToastify.css';
|
||||||
import { getErrorMessage, getSuccessMessage } from '../utils/errorHandler';
|
import { getErrorMessage, getSuccessMessage } from '../utils/errorHandler';
|
||||||
@@ -41,6 +41,16 @@ const TopUpAgent = () => {
|
|||||||
const [walletTransactions, setWalletTransactions] = useState([]);
|
const [walletTransactions, setWalletTransactions] = useState([]);
|
||||||
const [depositForm, setDepositForm] = useState({ amount: 0, currencyCode: '' });
|
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(() => {
|
useEffect(() => {
|
||||||
const fetchCities = async () => {
|
const fetchCities = async () => {
|
||||||
@@ -54,7 +64,7 @@ const TopUpAgent = () => {
|
|||||||
|
|
||||||
const fetchCurrencies = async () => {
|
const fetchCurrencies = async () => {
|
||||||
try {
|
try {
|
||||||
const list = await generalAPI.getCurrencies();
|
const list = await currencyAPI.list({ pageSize: 1000 });
|
||||||
setCurrencies(Array.isArray(list) ? list : []);
|
setCurrencies(Array.isArray(list) ? list : []);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load currencies:', 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
|
// Reset filters
|
||||||
const handleResetFilters = () => {
|
const handleResetFilters = () => {
|
||||||
setFilters({
|
setFilters({
|
||||||
@@ -334,6 +415,12 @@ const TopUpAgent = () => {
|
|||||||
>
|
>
|
||||||
<Wallet className="h-3 w-3 mr-1" /> Wallet
|
<Wallet className="h-3 w-3 mr-1" /> Wallet
|
||||||
</button>
|
</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
|
<button
|
||||||
onClick={() => handleDelete(row)}
|
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"
|
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>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -205,5 +205,62 @@ export const topUpAgentAPI = {
|
|||||||
});
|
});
|
||||||
return res?.data?.data?.data || [];
|
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