680 lines
25 KiB
JavaScript
680 lines
25 KiB
JavaScript
import React, { useEffect, useMemo, useState, useCallback } from 'react';
|
||
import DataTable from '../components/DataTable';
|
||
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';
|
||
import { getErrorMessage, getSuccessMessage } from '../utils/errorHandler';
|
||
|
||
const Location = () => {
|
||
const [activeTab, setActiveTab] = useState('country'); // 'country', 'province', 'city'
|
||
|
||
// Country states
|
||
const [countries, setCountries] = useState([]);
|
||
const [countriesLoading, setCountriesLoading] = useState(true);
|
||
const [countryFilter, setCountryFilter] = useState('');
|
||
const [isCountryModalOpen, setIsCountryModalOpen] = useState(false);
|
||
const [editingCountry, setEditingCountry] = useState(null);
|
||
const [countryForm, setCountryForm] = useState({ name: '', phoneCode: '', currencyCode: '', timeZoneName: '' });
|
||
|
||
// Province states
|
||
const [provinces, setProvinces] = useState([]);
|
||
const [provincesLoading, setProvincesLoading] = useState(true);
|
||
const [provinceFilter, setProvinceFilter] = useState('');
|
||
const [selectedCountryFilter, setSelectedCountryFilter] = useState('');
|
||
const [isProvinceModalOpen, setIsProvinceModalOpen] = useState(false);
|
||
const [editingProvince, setEditingProvince] = useState(null);
|
||
const [provinceForm, setProvinceForm] = useState({ countryId: '', provinceName: '' });
|
||
const [selectedCountryForProvince, setSelectedCountryForProvince] = useState(null);
|
||
|
||
// City states
|
||
const [cities, setCities] = useState([]);
|
||
const [citiesLoading, setCitiesLoading] = useState(true);
|
||
const [cityFilter, setCityFilter] = useState('');
|
||
const [selectedCountryFilterForCity, setSelectedCountryFilterForCity] = useState('');
|
||
const [selectedProvinceFilterForCity, setSelectedProvinceFilterForCity] = useState('');
|
||
const [isCityModalOpen, setIsCityModalOpen] = useState(false);
|
||
const [editingCity, setEditingCity] = useState(null);
|
||
const [cityForm, setCityForm] = useState({ provinceId: '', cityName: '' });
|
||
const [selectedProvinceForCity, setSelectedProvinceForCity] = useState(null);
|
||
const [provincesForCity, setProvincesForCity] = useState([]);
|
||
|
||
// دریافت کشورها
|
||
const fetchCountries = useCallback(async () => {
|
||
try {
|
||
setCountriesLoading(true);
|
||
const list = await countryAPI.listAll();
|
||
setCountries(Array.isArray(list) ? list : []);
|
||
} catch (err) {
|
||
toast.error(getErrorMessage(err));
|
||
} finally {
|
||
setCountriesLoading(false);
|
||
}
|
||
}, []);
|
||
|
||
// دریافت استانها
|
||
const fetchProvinces = useCallback(async (countryId = null) => {
|
||
try {
|
||
setProvincesLoading(true);
|
||
const params = {};
|
||
if (countryId) {
|
||
params.countryId = countryId;
|
||
}
|
||
const list = await provinceAPI.list(params);
|
||
setProvinces(Array.isArray(list) ? list : []);
|
||
} catch (err) {
|
||
toast.error(getErrorMessage(err));
|
||
} finally {
|
||
setProvincesLoading(false);
|
||
}
|
||
}, []);
|
||
|
||
// دریافت شهرها
|
||
const fetchCities = useCallback(async (countryId = null, provinceId = null) => {
|
||
try {
|
||
setCitiesLoading(true);
|
||
const params = {};
|
||
if (countryId) {
|
||
params.countryId = countryId;
|
||
}
|
||
if (provinceId) {
|
||
params.provinceId = provinceId;
|
||
}
|
||
const list = await cityAPI.list(params);
|
||
setCities(Array.isArray(list) ? list : []);
|
||
} catch (err) {
|
||
toast.error(getErrorMessage(err));
|
||
} finally {
|
||
setCitiesLoading(false);
|
||
}
|
||
}, []);
|
||
|
||
// دریافت کشورها - لود اولیه (برای country tab و dropdown ها)
|
||
useEffect(() => {
|
||
fetchCountries();
|
||
}, [fetchCountries]);
|
||
|
||
// دریافت provinces برای province tab - وقتی tab تغییر میکند یا فیلتر country تغییر میکند
|
||
useEffect(() => {
|
||
if (activeTab === 'province') {
|
||
const countryId = selectedCountryFilter || null;
|
||
console.log('🔵 Fetching provinces for province tab with countryId:', countryId);
|
||
fetchProvinces(countryId);
|
||
}
|
||
}, [activeTab, selectedCountryFilter, fetchProvinces]);
|
||
|
||
// دریافت provinces برای city tab (برای dropdown)
|
||
useEffect(() => {
|
||
if (activeTab === 'city') {
|
||
if (selectedCountryFilterForCity) {
|
||
// دریافت provinces برای country انتخاب شده
|
||
fetchProvinces(selectedCountryFilterForCity);
|
||
} else {
|
||
// دریافت همه provinces
|
||
fetchProvinces();
|
||
}
|
||
}
|
||
}, [activeTab, selectedCountryFilterForCity, fetchProvinces]);
|
||
|
||
// دریافت cities - وقتی tab تغییر میکند یا فیلترها تغییر میکنند
|
||
useEffect(() => {
|
||
if (activeTab === 'city') {
|
||
const countryId = selectedCountryFilterForCity || null;
|
||
const provinceId = selectedProvinceFilterForCity || null;
|
||
console.log('🔵 Fetching cities with filters:', { countryId, provinceId });
|
||
fetchCities(countryId, provinceId);
|
||
}
|
||
}, [activeTab, selectedCountryFilterForCity, selectedProvinceFilterForCity, fetchCities]);
|
||
|
||
// Reset filters when switching tabs
|
||
useEffect(() => {
|
||
if (activeTab !== 'province') {
|
||
setSelectedCountryFilter('');
|
||
}
|
||
if (activeTab !== 'city') {
|
||
setSelectedCountryFilterForCity('');
|
||
setSelectedProvinceFilterForCity('');
|
||
}
|
||
}, [activeTab]);
|
||
|
||
// وقتی country برای city تغییر میکند، province filter را reset کن
|
||
useEffect(() => {
|
||
if (activeTab === 'city' && selectedCountryFilterForCity) {
|
||
setSelectedProvinceFilterForCity('');
|
||
}
|
||
}, [selectedCountryFilterForCity, activeTab]);
|
||
|
||
// ========== Country Functions ==========
|
||
const openCountryModal = (country = null) => {
|
||
if (country) {
|
||
setEditingCountry(country);
|
||
setCountryForm({
|
||
name: country.name || '',
|
||
phoneCode: country.phoneCode || '',
|
||
currencyCode: country.currencyCode || '',
|
||
timeZoneName: country.timeZoneName || country.timeZone || '',
|
||
});
|
||
} else {
|
||
setEditingCountry(null);
|
||
setCountryForm({ name: '', phoneCode: '', currencyCode: '', timeZoneName: '' });
|
||
}
|
||
setIsCountryModalOpen(true);
|
||
};
|
||
|
||
const onSaveCountry = async (e) => {
|
||
e.preventDefault();
|
||
|
||
// Validation
|
||
if (!countryForm.name?.trim()) {
|
||
toast.error('Name is required');
|
||
return;
|
||
}
|
||
if (!countryForm.phoneCode?.trim()) {
|
||
toast.error('Phone Code is required');
|
||
return;
|
||
}
|
||
if (!countryForm.currencyCode?.trim()) {
|
||
toast.error('Currency Code is required');
|
||
return;
|
||
}
|
||
if (!countryForm.timeZoneName?.trim()) {
|
||
toast.error('Time Zone is required');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
if (editingCountry) {
|
||
const countryId = editingCountry.id || editingCountry.countryId;
|
||
if (!countryId) {
|
||
toast.error('Country ID is missing');
|
||
console.error('Editing country:', editingCountry);
|
||
return;
|
||
}
|
||
console.log('Updating country with ID:', countryId, 'Payload:', countryForm);
|
||
await countryAPI.update(countryId, countryForm);
|
||
toast.success(getSuccessMessage({ data: { message: 'Country updated successfully' } }) || 'Country updated successfully');
|
||
} else {
|
||
console.log('Creating country with payload:', countryForm);
|
||
await countryAPI.create(countryForm);
|
||
toast.success(getSuccessMessage({ data: { message: 'Country added successfully' } }) || 'Country added successfully');
|
||
}
|
||
await fetchCountries();
|
||
setIsCountryModalOpen(false);
|
||
setEditingCountry(null);
|
||
setCountryForm({ name: '', phoneCode: '', currencyCode: '', timeZoneName: '' });
|
||
} catch (err) {
|
||
console.error('Error saving country:', err);
|
||
toast.error(getErrorMessage(err));
|
||
}
|
||
};
|
||
|
||
// ========== Province Functions ==========
|
||
const openProvinceModal = (province = null) => {
|
||
if (province) {
|
||
setEditingProvince(province);
|
||
setProvinceForm({
|
||
countryId: province.countryId || '',
|
||
provinceName: province.name || '',
|
||
});
|
||
setSelectedCountryForProvince(province.countryId);
|
||
} else {
|
||
setEditingProvince(null);
|
||
setProvinceForm({ countryId: '', provinceName: '' });
|
||
setSelectedCountryForProvince(null);
|
||
}
|
||
setIsProvinceModalOpen(true);
|
||
};
|
||
|
||
const onSaveProvince = async (e) => {
|
||
e.preventDefault();
|
||
if (!provinceForm.countryId) {
|
||
toast.error('Please select a country');
|
||
return;
|
||
}
|
||
try {
|
||
if (editingProvince) {
|
||
const provinceId = editingProvince.id || editingProvince.provinceId;
|
||
if (!provinceId) {
|
||
toast.error('Province ID is missing');
|
||
console.error('Editing province:', editingProvince);
|
||
return;
|
||
}
|
||
console.log('Updating province with ID:', provinceId, 'Payload:', provinceForm);
|
||
await provinceAPI.update(provinceId, provinceForm);
|
||
toast.success(getSuccessMessage({ data: { message: 'Province updated successfully' } }) || 'Province updated successfully');
|
||
} else {
|
||
await provinceAPI.create(provinceForm);
|
||
toast.success(getSuccessMessage({ data: { message: 'Province added successfully' } }) || 'Province added successfully');
|
||
}
|
||
await fetchProvinces();
|
||
setIsProvinceModalOpen(false);
|
||
setEditingProvince(null);
|
||
setProvinceForm({ countryId: '', provinceName: '' });
|
||
setSelectedCountryForProvince(null);
|
||
} catch (err) {
|
||
console.error('Error saving province:', err);
|
||
toast.error(getErrorMessage(err));
|
||
}
|
||
};
|
||
|
||
// ========== City Functions ==========
|
||
const openCityModal = (city = null) => {
|
||
if (city) {
|
||
setEditingCity(city);
|
||
setCityForm({
|
||
provinceId: city.provinceId || '',
|
||
cityName: city.name || '',
|
||
});
|
||
setSelectedProvinceForCity(city.provinceId);
|
||
} else {
|
||
setEditingCity(null);
|
||
setCityForm({ provinceId: '', cityName: '' });
|
||
setSelectedProvinceForCity(null);
|
||
}
|
||
setIsCityModalOpen(true);
|
||
};
|
||
|
||
const onSaveCity = async (e) => {
|
||
e.preventDefault();
|
||
if (!cityForm.provinceId) {
|
||
toast.error('Please select a province');
|
||
return;
|
||
}
|
||
try {
|
||
if (editingCity) {
|
||
const cityId = editingCity.id || editingCity.cityId;
|
||
if (!cityId) {
|
||
toast.error('City ID is missing');
|
||
console.error('Editing city:', editingCity);
|
||
return;
|
||
}
|
||
console.log('Updating city with ID:', cityId, 'Payload:', cityForm);
|
||
await cityAPI.update(cityId, cityForm);
|
||
toast.success(getSuccessMessage({ data: { message: 'City updated successfully' } }) || 'City updated successfully');
|
||
} else {
|
||
await cityAPI.create(cityForm);
|
||
toast.success(getSuccessMessage({ data: { message: 'City added successfully' } }) || 'City added successfully');
|
||
}
|
||
await fetchCities();
|
||
setIsCityModalOpen(false);
|
||
setEditingCity(null);
|
||
setCityForm({ provinceId: '', cityName: '' });
|
||
setSelectedProvinceForCity(null);
|
||
} catch (err) {
|
||
console.error('Error saving city:', err);
|
||
toast.error(getErrorMessage(err));
|
||
}
|
||
};
|
||
|
||
// ========== Table Columns ==========
|
||
const countryColumns = useMemo(() => [
|
||
{ key: 'name', header: 'Name' },
|
||
], []);
|
||
|
||
const provinceColumns = useMemo(() => [
|
||
{ key: 'name', header: 'Province Name' },
|
||
{ key: 'countryName', header: 'Country' },
|
||
{
|
||
key: 'actions',
|
||
header: 'Actions',
|
||
render: (_val, row) => (
|
||
<div className="flex gap-2">
|
||
<button
|
||
onClick={() => openProvinceModal(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 cityColumns = useMemo(() => [
|
||
{ key: 'name', header: 'City Name' },
|
||
{ key: 'provinceName', header: 'Province' },
|
||
{
|
||
key: 'actions',
|
||
header: 'Actions',
|
||
render: (_val, row) => (
|
||
<div className="flex gap-2">
|
||
<button
|
||
onClick={() => openCityModal(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>
|
||
),
|
||
},
|
||
], []);
|
||
|
||
// Filtered data
|
||
const filteredCountries = useMemo(() => {
|
||
if (!countryFilter) return countries;
|
||
const filter = countryFilter.toLowerCase();
|
||
return countries.filter(c =>
|
||
c.name?.toLowerCase().includes(filter)
|
||
);
|
||
}, [countries, countryFilter]);
|
||
|
||
const filteredProvinces = useMemo(() => {
|
||
if (!provinceFilter) return provinces;
|
||
const filter = provinceFilter.toLowerCase();
|
||
return provinces.filter(p =>
|
||
p.name?.toLowerCase().includes(filter) ||
|
||
p.countryName?.toLowerCase().includes(filter)
|
||
);
|
||
}, [provinces, provinceFilter]);
|
||
|
||
const filteredCities = useMemo(() => {
|
||
if (!cityFilter) return cities;
|
||
const filter = cityFilter.toLowerCase();
|
||
return cities.filter(c =>
|
||
c.name?.toLowerCase().includes(filter) ||
|
||
c.provinceName?.toLowerCase().includes(filter)
|
||
);
|
||
}, [cities, cityFilter]);
|
||
|
||
return (
|
||
<div className="p-6">
|
||
<ToastContainer position="top-right" autoClose={3000} />
|
||
|
||
<div className="mb-6">
|
||
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Location Management</h1>
|
||
<p className="text-gray-600 dark:text-gray-400">Manage countries, provinces, and cities</p>
|
||
</div>
|
||
|
||
{/* Tabs */}
|
||
<div className="mb-4 border-b border-gray-200 dark:border-gray-700">
|
||
<nav className="flex space-x-8">
|
||
<button
|
||
onClick={() => setActiveTab('country')}
|
||
className={`py-4 px-1 border-b-2 font-medium text-sm ${
|
||
activeTab === 'country'
|
||
? 'border-primary-500 text-primary-600'
|
||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||
}`}
|
||
>
|
||
<Globe className="inline h-4 w-4 mr-2" />
|
||
Countries
|
||
</button>
|
||
<button
|
||
onClick={() => setActiveTab('province')}
|
||
className={`py-4 px-1 border-b-2 font-medium text-sm ${
|
||
activeTab === 'province'
|
||
? 'border-primary-500 text-primary-600'
|
||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||
}`}
|
||
>
|
||
<MapPin className="inline h-4 w-4 mr-2" />
|
||
Provinces
|
||
</button>
|
||
<button
|
||
onClick={() => setActiveTab('city')}
|
||
className={`py-4 px-1 border-b-2 font-medium text-sm ${
|
||
activeTab === 'city'
|
||
? 'border-primary-500 text-primary-600'
|
||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
|
||
}`}
|
||
>
|
||
<Building2 className="inline h-4 w-4 mr-2" />
|
||
Cities
|
||
</button>
|
||
</nav>
|
||
</div>
|
||
|
||
{/* Country Tab */}
|
||
{activeTab === 'country' && (
|
||
<>
|
||
<div className="mb-4 flex justify-between items-center">
|
||
<div className="relative max-w-md flex-1">
|
||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
||
<input
|
||
value={countryFilter}
|
||
onChange={(e) => setCountryFilter(e.target.value)}
|
||
placeholder="Filter countries..."
|
||
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<DataTable
|
||
data={filteredCountries}
|
||
columns={countryColumns}
|
||
loading={countriesLoading}
|
||
searchable={false}
|
||
/>
|
||
</>
|
||
)}
|
||
|
||
{/* Province Tab */}
|
||
{activeTab === 'province' && (
|
||
<>
|
||
<div className="mb-4 flex justify-between items-center gap-4">
|
||
<div className="flex gap-4 flex-1">
|
||
<div className="relative max-w-md flex-1">
|
||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
||
<input
|
||
value={provinceFilter}
|
||
onChange={(e) => setProvinceFilter(e.target.value)}
|
||
placeholder="Filter provinces..."
|
||
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
||
/>
|
||
</div>
|
||
<div className="min-w-[200px]">
|
||
<select
|
||
value={selectedCountryFilter}
|
||
onChange={(e) => {
|
||
setSelectedCountryFilter(e.target.value);
|
||
setProvinceFilter(''); // Reset text filter when country changes
|
||
}}
|
||
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
||
>
|
||
<option value="">All Countries</option>
|
||
{countries.map((country) => (
|
||
<option key={country.id} value={country.id}>
|
||
{country.name}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<button
|
||
onClick={() => openProvinceModal()}
|
||
className="btn-primary inline-flex items-center"
|
||
>
|
||
<Plus className="h-4 w-4 mr-2" /> Add Province
|
||
</button>
|
||
</div>
|
||
|
||
<DataTable
|
||
data={filteredProvinces}
|
||
columns={provinceColumns}
|
||
loading={provincesLoading}
|
||
searchable={false}
|
||
/>
|
||
</>
|
||
)}
|
||
|
||
{/* City Tab */}
|
||
{activeTab === 'city' && (
|
||
<>
|
||
<div className="mb-4 flex justify-between items-center gap-4">
|
||
<div className="flex gap-4 flex-1">
|
||
<div className="relative max-w-md flex-1">
|
||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
|
||
<input
|
||
value={cityFilter}
|
||
onChange={(e) => setCityFilter(e.target.value)}
|
||
placeholder="Filter cities..."
|
||
className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
||
/>
|
||
</div>
|
||
<div className="min-w-[200px]">
|
||
<select
|
||
value={selectedCountryFilterForCity}
|
||
onChange={(e) => {
|
||
setSelectedCountryFilterForCity(e.target.value);
|
||
setSelectedProvinceFilterForCity(''); // Reset province filter when country changes
|
||
setCityFilter(''); // Reset text filter
|
||
}}
|
||
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
||
>
|
||
<option value="">All Countries</option>
|
||
{countries.map((country) => (
|
||
<option key={country.id} value={country.id}>
|
||
{country.name}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
<div className="min-w-[200px]">
|
||
<select
|
||
value={selectedProvinceFilterForCity}
|
||
onChange={(e) => {
|
||
setSelectedProvinceFilterForCity(e.target.value);
|
||
setCityFilter(''); // Reset text filter
|
||
}}
|
||
className="w-full p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary-500"
|
||
disabled={!selectedCountryFilterForCity}
|
||
>
|
||
<option value="">All Provinces</option>
|
||
{provinces
|
||
.filter(p => !selectedCountryFilterForCity || p.countryId === selectedCountryFilterForCity)
|
||
.map((province) => (
|
||
<option key={province.id} value={province.id}>
|
||
{province.name}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<button
|
||
onClick={() => openCityModal()}
|
||
className="btn-primary inline-flex items-center"
|
||
>
|
||
<Plus className="h-4 w-4 mr-2" /> Add City
|
||
</button>
|
||
</div>
|
||
|
||
<DataTable
|
||
data={filteredCities}
|
||
columns={cityColumns}
|
||
loading={citiesLoading}
|
||
searchable={false}
|
||
/>
|
||
</>
|
||
)}
|
||
|
||
|
||
{/* Province Modal */}
|
||
{isProvinceModalOpen && (
|
||
<>
|
||
<div className="fixed inset-0 bg-black bg-opacity-50 z-40" onClick={() => setIsProvinceModalOpen(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">
|
||
{editingProvince ? 'Edit Province' : 'Add Province'}
|
||
</h3>
|
||
<form onSubmit={onSaveProvince} className="space-y-4">
|
||
<div>
|
||
<label className="block text-sm font-medium mb-1">Country *</label>
|
||
<select
|
||
value={provinceForm.countryId}
|
||
onChange={(e) => {
|
||
setProvinceForm({ ...provinceForm, countryId: e.target.value });
|
||
setSelectedCountryForProvince(e.target.value);
|
||
}}
|
||
className="w-full p-2 border rounded-lg"
|
||
required
|
||
>
|
||
<option value="">Select Country</option>
|
||
{countries.map((country) => (
|
||
<option key={country.id} value={country.id}>
|
||
{country.name}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium mb-1">Province Name *</label>
|
||
<input
|
||
value={provinceForm.provinceName}
|
||
onChange={(e) => setProvinceForm({ ...provinceForm, provinceName: e.target.value })}
|
||
className="w-full p-2 border rounded-lg"
|
||
required
|
||
/>
|
||
</div>
|
||
<div className="flex justify-end gap-x-2">
|
||
<button type="button" onClick={() => setIsProvinceModalOpen(false)} className="px-4 py-2 border rounded-lg">
|
||
Cancel
|
||
</button>
|
||
<button type="submit" className="btn-primary px-4 py-2 rounded-lg">
|
||
{editingProvince ? 'Update' : 'Add'}
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)}
|
||
|
||
{/* City Modal */}
|
||
{isCityModalOpen && (
|
||
<>
|
||
<div className="fixed inset-0 bg-black bg-opacity-50 z-40" onClick={() => setIsCityModalOpen(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">
|
||
{editingCity ? 'Edit City' : 'Add City'}
|
||
</h3>
|
||
<form onSubmit={onSaveCity} className="space-y-4">
|
||
<div>
|
||
<label className="block text-sm font-medium mb-1">Province *</label>
|
||
<select
|
||
value={cityForm.provinceId}
|
||
onChange={(e) => {
|
||
setCityForm({ ...cityForm, provinceId: e.target.value });
|
||
setSelectedProvinceForCity(e.target.value);
|
||
}}
|
||
className="w-full p-2 border rounded-lg"
|
||
required
|
||
>
|
||
<option value="">Select Province</option>
|
||
{provinces.map((province) => (
|
||
<option key={province.id} value={province.id}>
|
||
{province.name} - {province.countryName}
|
||
</option>
|
||
))}
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm font-medium mb-1">City Name *</label>
|
||
<input
|
||
value={cityForm.cityName}
|
||
onChange={(e) => setCityForm({ ...cityForm, cityName: e.target.value })}
|
||
className="w-full p-2 border rounded-lg"
|
||
required
|
||
/>
|
||
</div>
|
||
<div className="flex justify-end gap-x-2">
|
||
<button type="button" onClick={() => setIsCityModalOpen(false)} className="px-4 py-2 border rounded-lg">
|
||
Cancel
|
||
</button>
|
||
<button type="submit" className="btn-primary px-4 py-2 rounded-lg">
|
||
{editingCity ? 'Update' : 'Add'}
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default Location;
|
||
|