feat(topup-agent): add agent page with create and filter functionality
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useMemo, useState, useCallback } from 'react';
|
||||
import DataTable from '../components/DataTable';
|
||||
import { countryAPI, provinceAPI, cityAPI, generalAPI } from '../services/api';
|
||||
import { countryAPI, provinceAPI, cityAPI } from '../services/api';
|
||||
import { Plus, Search, Pencil, Trash2, MapPin, Globe, Building2 } from 'lucide-react';
|
||||
import { ToastContainer, toast } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
@@ -16,8 +16,6 @@ const Location = () => {
|
||||
const [isCountryModalOpen, setIsCountryModalOpen] = useState(false);
|
||||
const [editingCountry, setEditingCountry] = useState(null);
|
||||
const [countryForm, setCountryForm] = useState({ name: '', phoneCode: '', currencyCode: '', timeZoneName: '' });
|
||||
const [currencies, setCurrencies] = useState([]);
|
||||
const [timeZones, setTimeZones] = useState([]);
|
||||
|
||||
// Province states
|
||||
const [provinces, setProvinces] = useState([]);
|
||||
@@ -41,32 +39,6 @@ const Location = () => {
|
||||
const [selectedProvinceForCity, setSelectedProvinceForCity] = useState(null);
|
||||
const [provincesForCity, setProvincesForCity] = useState([]);
|
||||
|
||||
// دریافت ارزها و timezone ها برای dropdown
|
||||
useEffect(() => {
|
||||
const fetchCurrencies = async () => {
|
||||
try {
|
||||
const list = await generalAPI.getCurrencies();
|
||||
setCurrencies(Array.isArray(list) ? list : []);
|
||||
} catch (err) {
|
||||
console.error('Failed to load currencies:', err);
|
||||
toast.error('Failed to load currencies');
|
||||
}
|
||||
};
|
||||
|
||||
const fetchTimeZones = async () => {
|
||||
try {
|
||||
const list = await generalAPI.getTimeZones();
|
||||
setTimeZones(Array.isArray(list) ? list : []);
|
||||
} catch (err) {
|
||||
console.error('Failed to load timezones:', err);
|
||||
toast.error('Failed to load timezones');
|
||||
}
|
||||
};
|
||||
|
||||
fetchCurrencies();
|
||||
fetchTimeZones();
|
||||
}, []);
|
||||
|
||||
// دریافت کشورها
|
||||
const fetchCountries = useCallback(async () => {
|
||||
try {
|
||||
@@ -337,23 +309,6 @@ const Location = () => {
|
||||
// ========== Table Columns ==========
|
||||
const countryColumns = useMemo(() => [
|
||||
{ key: 'name', header: 'Name' },
|
||||
{ key: 'phoneCode', header: 'Phone Code' },
|
||||
{ key: 'currencyCode', header: 'Currency' },
|
||||
{ key: 'timeZone', header: 'Time Zone', render: (val, row) => val || row.timeZoneName || '—' },
|
||||
{
|
||||
key: 'actions',
|
||||
header: 'Actions',
|
||||
render: (_val, row) => (
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => openCountryModal(row)}
|
||||
className="inline-flex items-center px-2 py-1 text-xs rounded-md bg-blue-100 text-blue-700 hover:opacity-90"
|
||||
>
|
||||
<Pencil className="h-3 w-3 mr-1" /> Edit
|
||||
</button>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
], []);
|
||||
|
||||
const provinceColumns = useMemo(() => [
|
||||
@@ -399,9 +354,7 @@ const Location = () => {
|
||||
if (!countryFilter) return countries;
|
||||
const filter = countryFilter.toLowerCase();
|
||||
return countries.filter(c =>
|
||||
c.name?.toLowerCase().includes(filter) ||
|
||||
c.phoneCode?.toLowerCase().includes(filter) ||
|
||||
c.currencyCode?.toLowerCase().includes(filter)
|
||||
c.name?.toLowerCase().includes(filter)
|
||||
);
|
||||
}, [countries, countryFilter]);
|
||||
|
||||
@@ -484,12 +437,6 @@ const Location = () => {
|
||||
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => openCountryModal()}
|
||||
className="btn-primary inline-flex items-center ml-4"
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-2" /> Add Country
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<DataTable
|
||||
@@ -620,79 +567,6 @@ const Location = () => {
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Country Modal */}
|
||||
{isCountryModalOpen && (
|
||||
<>
|
||||
<div className="fixed inset-0 bg-black bg-opacity-50 z-40" onClick={() => setIsCountryModalOpen(false)} />
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
<div className="w-full max-w-md card p-6 relative bg-white rounded-2xl shadow-lg">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">
|
||||
{editingCountry ? 'Edit Country' : 'Add Country'}
|
||||
</h3>
|
||||
<form onSubmit={onSaveCountry} className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Name *</label>
|
||||
<input
|
||||
value={countryForm.name}
|
||||
onChange={(e) => setCountryForm({ ...countryForm, name: e.target.value })}
|
||||
className="w-full p-2 border rounded-lg"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Phone Code *</label>
|
||||
<input
|
||||
value={countryForm.phoneCode}
|
||||
onChange={(e) => setCountryForm({ ...countryForm, phoneCode: e.target.value })}
|
||||
className="w-full p-2 border rounded-lg"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Currency Code *</label>
|
||||
<select
|
||||
value={countryForm.currencyCode}
|
||||
onChange={(e) => setCountryForm({ ...countryForm, currencyCode: e.target.value })}
|
||||
className="w-full p-2 border rounded-lg"
|
||||
required
|
||||
>
|
||||
<option value="">Select Currency</option>
|
||||
{currencies.map((curr) => (
|
||||
<option key={curr.code} value={curr.code}>
|
||||
{curr.code} - {curr.name || curr.nativeName || curr.symbol}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1">Time Zone *</label>
|
||||
<select
|
||||
value={countryForm.timeZoneName}
|
||||
onChange={(e) => setCountryForm({ ...countryForm, timeZoneName: e.target.value })}
|
||||
className="w-full p-2 border rounded-lg"
|
||||
required
|
||||
>
|
||||
<option value="">Select Time Zone</option>
|
||||
{timeZones.map((tz) => (
|
||||
<option key={tz.name} value={tz.name}>
|
||||
{tz.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex justify-end gap-x-2">
|
||||
<button type="button" onClick={() => setIsCountryModalOpen(false)} className="px-4 py-2 border rounded-lg">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" className="btn-primary px-4 py-2 rounded-lg">
|
||||
{editingCountry ? 'Update' : 'Add'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Province Modal */}
|
||||
{isProvinceModalOpen && (
|
||||
|
||||
Reference in New Issue
Block a user