fix: lỗi hiển thị ở frontend

This commit is contained in:
2026-06-21 21:53:14 +07:00
parent 403c169ddd
commit b1a539235b
40 changed files with 5094 additions and 668 deletions
+602 -1
View File
@@ -5,8 +5,9 @@ const MarkerClusterGroup = (_MarkerClusterGroup as any).default || _MarkerCluste
import L from 'leaflet';
import 'leaflet/dist/leaflet.css';
import { useTourStore } from '@/store/useTourStore';
import { X, Navigation, Image as ImageIcon, LogOut, Settings, Share2, Filter, MapPin, Loader2, UserPlus, Clock, ChevronLeft, Lock, Globe, Sun, Moon, Laptop, Users } from 'lucide-react';
import { X, Navigation, Image as ImageIcon, LogOut, Settings, Share2, Filter, MapPin, Loader2, UserPlus, Clock, ChevronLeft, Lock, Globe, Sun, Moon, Laptop, Users, ShieldAlert, Star } from 'lucide-react';
import { UserManagementModal } from '@/components/UserManagementModal';
import { ReportBusinessModal } from '../components/ReportBusinessModal';
import { useNotification } from '@/hooks/useNotification';
import { useConfirm } from '@/hooks/useConfirm';
import { CreateTourModal } from '../components/CreateTourModal';
@@ -227,6 +228,54 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
const [trustedUsers, setTrustedUsers] = useState<any[]>([]);
const [isLeaderboardOpen, setIsLeaderboardOpen] = useState(false);
const [blacklist, setBlacklist] = useState<any[]>([]);
const [isReportModalOpen, setIsReportModalOpen] = useState(false);
const [isBlacklistOpen, setIsBlacklistOpen] = useState(false);
const mapCenter = useTourStore(state => state.mapCenter);
// Recommendations and GPS States
const [recommendedLocations, setRecommendedLocations] = useState<any[]>([]);
const [isRecommendedOpen, setIsRecommendedOpen] = useState(false);
const [isProposeModalOpen, setIsProposeModalOpen] = useState(false);
const [sortBlacklistByDistance, setSortBlacklistByDistance] = useState(false);
const [sortRecsByDistance, setSortRecsByDistance] = useState(false);
const [userGpsPos, setUserGpsPos] = useState<[number, number] | null>(null);
const [proposeForm, setProposeForm] = useState({
name: '',
type: 'RESTAURANT',
phone: '',
email: '',
address: '',
latitude: '',
longitude: '',
description: '',
stars: 5
});
const fetchBlacklist = async () => {
try {
const res = await fetch('/api/v1/reports/blacklist');
if (res.ok) {
const data = await res.json();
setBlacklist(data);
}
} catch (e) {
console.error('Lỗi khi tải blacklist:', e);
}
};
const fetchRecommendations = async () => {
try {
const res = await fetch('/api/v1/recommendations');
if (res.ok) {
const data = await res.json();
setRecommendedLocations(data);
}
} catch (e) {
console.error('Lỗi khi tải danh sách đề xuất:', e);
}
};
const fetchTrustedUsers = async () => {
try {
@@ -240,8 +289,121 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
}
};
const calculateDistance = (lat1: number, lon1: number, lat2: number, lon2: number) => {
const R = 6371; // Earth radius in km
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLon = (lon2 - lon1) * Math.PI / 180;
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c;
};
const requestGpsPosition = () => {
if (!navigator.geolocation) {
alert("Trình duyệt không hỗ trợ định vị GPS.");
return;
}
navigator.geolocation.getCurrentPosition(
(pos) => {
const posArray: [number, number] = [pos.coords.latitude, pos.coords.longitude];
setUserGpsPos(posArray);
setUserPos(posArray);
setMapCenter(posArray);
},
() => {
alert("Không thể truy cập vị trí của bạn. Vui lòng cho phép quyền truy cập vị trí.");
}
);
};
const handleProposeSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!proposeForm.name || !proposeForm.description) {
alert("Vui lòng điền tên địa điểm và mô tả.");
return;
}
const token = localStorage.getItem('token');
if (!token) {
alert("Bạn cần đăng nhập để gửi đề xuất địa điểm.");
return;
}
try {
const res = await fetch('/api/v1/recommendations', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify({
...proposeForm,
latitude: parseFloat(proposeForm.latitude),
longitude: parseFloat(proposeForm.longitude)
})
});
if (res.ok) {
alert("Đề xuất của bạn đã được gửi thành công và đang chờ Admin phê duyệt!");
setIsProposeModalOpen(false);
setProposeForm({
name: '',
type: 'RESTAURANT',
phone: '',
email: '',
address: '',
latitude: userPos[0].toString(),
longitude: userPos[1].toString(),
description: '',
stars: 5
});
fetchRecommendations();
} else {
const errData = await res.json();
alert(errData.message || "Gửi đề xuất thất bại.");
}
} catch (err) {
alert("Đã xảy ra lỗi kết nối.");
}
};
const processedBlacklist = React.useMemo(() => {
if (!sortBlacklistByDistance || !userGpsPos) return blacklist;
return [...blacklist].sort((a, b) => {
if (typeof a.latitude !== 'number' || typeof a.longitude !== 'number') return 1;
if (typeof b.latitude !== 'number' || typeof b.longitude !== 'number') return -1;
const distA = calculateDistance(userGpsPos[0], userGpsPos[1], a.latitude, a.longitude);
const distB = calculateDistance(userGpsPos[0], userGpsPos[1], b.latitude, b.longitude);
return distA - distB;
});
}, [blacklist, sortBlacklistByDistance, userGpsPos]);
const processedRecommendations = React.useMemo(() => {
if (!sortRecsByDistance || !userGpsPos) return recommendedLocations;
return [...recommendedLocations].sort((a, b) => {
if (typeof a.latitude !== 'number' || typeof a.longitude !== 'number') return 1;
if (typeof b.latitude !== 'number' || typeof b.longitude !== 'number') return -1;
const distA = calculateDistance(userGpsPos[0], userGpsPos[1], a.latitude, a.longitude);
const distB = calculateDistance(userGpsPos[0], userGpsPos[1], b.latitude, b.longitude);
return distA - distB;
});
}, [recommendedLocations, sortRecsByDistance, userGpsPos]);
useEffect(() => {
if (isProposeModalOpen) {
setProposeForm(prev => ({
...prev,
latitude: userPos[0].toString(),
longitude: userPos[1].toString()
}));
}
}, [isProposeModalOpen, userPos]);
useEffect(() => {
fetchTrustedUsers();
fetchBlacklist();
fetchRecommendations();
}, []);
const [isFilterDropdownOpen, setIsFilterDropdownOpen] = useState(false); // State để điều khiển hiển thị dropdown lọc
const [searchQuery, setSearchQuery] = useState(''); // State cho giá trị tìm kiếm
@@ -565,6 +727,16 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
</button>
)}
{/* Nút Báo cáo sai phạm */}
<button
onClick={() => setIsReportModalOpen(true)}
className="w-11 h-11 md:w-auto bg-red-600 p-0 md:px-4 md:py-3 rounded-2xl shadow-xl hover:bg-red-700 text-white transition-all flex items-center justify-center md:justify-start gap-2 font-bold shrink-0 cursor-pointer"
title={t('reportBusinessBtn') || 'Báo cáo sai phạm'}
>
<ShieldAlert className="w-5 h-5" />
<span className="hidden md:inline text-sm">{t('reportBusinessBtn') || 'Báo cáo'}</span>
</button>
{/* Lựa chọn Ngôn ngữ */}
<div className="relative group shrink-0">
<button className="w-11 h-11 bg-white dark:bg-slate-800 p-0 rounded-2xl shadow-xl hover:bg-slate-50 dark:hover:bg-slate-700 text-slate-700 dark:text-slate-200 transition-all flex items-center justify-center border border-gray-100 dark:border-slate-700">
@@ -824,6 +996,88 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
/>
);
})}
{/* Render blacklist markers */}
{blacklist.map((item) => {
if (typeof item.latitude !== 'number' || typeof item.longitude !== 'number') return null;
return (
<Marker
key={`blacklist-${item.id}`}
position={[item.latitude, item.longitude]}
icon={L.divIcon({
html: `<div class="bg-red-600 text-white p-2 rounded-full shadow-lg border-2 border-white flex items-center justify-center animate-pulse"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-shield-alert"><path d="M20 13c0 5-3.5 7.5-7.66 9.7a1 1 0 0 1-.68 0C7.5 20.5 4 18 4 13V6a1 1 0 0 1 .76-.97l8-2a1 1 0 0 1 .48 0l8 2A1 1 0 0 1 20 6z"/><path d="M12 8v4"/><path d="M12 16h.01"/></svg></div>`,
className: 'custom-blacklist-marker',
iconSize: [32, 32],
iconAnchor: [16, 16],
})}
>
<Tooltip direction="top" offset={[0, -16]} opacity={1}>
<div className="p-2 max-w-[200px] text-left">
<div className="font-black text-red-600 text-xs mb-1 uppercase tracking-tight flex items-center gap-1">
Blacklist: {item.name}
</div>
<div className="text-[10px] bg-red-50 text-red-700 px-1.5 py-0.5 rounded font-black uppercase tracking-wider mb-1 w-max">
{item.type}
</div>
{item.address && (
<div className="text-[10px] text-gray-500 mb-1 font-semibold">{item.address}</div>
)}
{item.phone && (
<div className="text-[9px] text-gray-400">SĐT: {item.phone}</div>
)}
<div className="text-[10px] text-red-600 font-medium italic mt-1 pt-1 border-t border-red-100">
do: {item.reason}
</div>
</div>
</Tooltip>
</Marker>
);
})}
{/* Render recommended markers */}
{recommendedLocations.map((item) => {
if (typeof item.latitude !== 'number' || typeof item.longitude !== 'number') return null;
return (
<Marker
key={`recommendation-${item.id}`}
position={[item.latitude, item.longitude]}
icon={L.divIcon({
html: `<div class="bg-emerald-600 text-white p-2 rounded-full shadow-lg border-2 border-white flex items-center justify-center animate-pulse"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg></div>`,
className: 'custom-recommended-marker',
iconSize: [32, 32],
iconAnchor: [16, 16],
})}
>
<Tooltip direction="top" offset={[0, -16]} opacity={1}>
<div className="p-2.5 max-w-[220px] text-left">
<div className="font-black text-emerald-600 text-xs mb-1 uppercase tracking-tight flex items-center gap-1">
🌟 {item.name}
</div>
<div className="flex items-center gap-0.5 text-amber-500 mb-1">
{Array.from({ length: item.stars }).map((_, i) => (
<span key={i}></span>
))}
</div>
<div className="text-[10px] bg-emerald-50 text-emerald-700 px-1.5 py-0.5 rounded font-black uppercase tracking-wider mb-1 w-max">
{item.type === 'RESTAURANT' ? 'Nhà hàng' : item.type === 'HOTEL' ? 'Khách sạn' : 'Homestay'}
</div>
{item.address && (
<div className="text-[10px] text-gray-500 mb-1 font-semibold">{item.address}</div>
)}
{item.phone && (
<div className="text-[9px] text-gray-400">SĐT: {item.phone}</div>
)}
{item.email && (
<div className="text-[9px] text-gray-400">Email: {item.email}</div>
)}
<div className="text-[10px] text-slate-600 font-medium italic mt-1 pt-1 border-t border-emerald-100 whitespace-pre-line">
{item.description}
</div>
</div>
</Tooltip>
</Marker>
);
})}
</MapContainer>
{/* Context Menu Chia sẻ */}
@@ -965,6 +1219,353 @@ export const ExploreMap = ({ onBack, onLogout, user, onViewTour, onOpenMyPhotos,
</div>
)}
</div>
{/* Floating Widgets Container (Bottom Right) */}
<div className="absolute bottom-6 right-6 z-[1002] pointer-events-auto flex flex-col items-end gap-2">
{/* Recommended Locations Toggle Button */}
<button
onClick={() => {
setIsRecommendedOpen(prev => !prev);
setIsBlacklistOpen(false);
}}
className="bg-white dark:bg-slate-900 border border-gray-150 dark:border-slate-800 text-slate-800 dark:text-slate-100 shadow-2xl rounded-2xl py-2.5 px-4 text-xs font-bold flex items-center gap-2 hover:bg-gray-50 dark:hover:bg-slate-850 transition-all cursor-pointer active:scale-95 border-emerald-500/20"
>
<Star className="w-4 h-4 text-emerald-500 fill-emerald-500 animate-pulse" />
<span>{t('recommendedTitle') || 'Đề xuất dịch vụ'} ({recommendedLocations.length})</span>
</button>
{/* Blacklist Toggle Button */}
<button
onClick={() => {
setIsBlacklistOpen(prev => !prev);
setIsRecommendedOpen(false);
}}
className="bg-white dark:bg-slate-900 border border-gray-150 dark:border-slate-800 text-slate-800 dark:text-slate-100 shadow-2xl rounded-2xl py-2.5 px-4 text-xs font-bold flex items-center gap-2 hover:bg-gray-50 dark:hover:bg-slate-850 transition-all cursor-pointer active:scale-95 border-red-500/20"
>
<ShieldAlert className="w-4 h-4 text-red-500 animate-pulse" />
<span>{t('blacklistTitle') || 'Danh sách đen'} ({blacklist.length})</span>
</button>
{/* Recommendations Panel */}
{isRecommendedOpen && (
<div className="bg-white/95 dark:bg-slate-900/95 backdrop-blur-md border border-gray-100 dark:border-slate-800 rounded-3xl shadow-2xl p-4 w-72 max-h-[350px] overflow-y-auto no-scrollbar animate-in slide-in-from-bottom-2 duration-300">
<h4 className="text-xs font-black uppercase text-emerald-600 dark:text-emerald-500 tracking-widest mb-3 flex items-center gap-2 border-b border-gray-100 dark:border-slate-800 pb-2">
🌟 {t('recommendedTitle') || 'Đề xuất chất lượng'}
</h4>
<div className="flex items-center justify-between gap-2 mb-2">
<button
onClick={() => {
if (!sortRecsByDistance && !userGpsPos) {
requestGpsPosition();
}
setSortRecsByDistance(prev => !prev);
}}
className={`text-[9px] px-2.5 py-1 rounded-xl font-bold transition-all cursor-pointer ${
sortRecsByDistance
? 'bg-emerald-600 text-white shadow-sm'
: 'bg-gray-100 hover:bg-gray-200 text-gray-600 dark:bg-slate-850 dark:text-slate-300 dark:hover:bg-slate-800'
}`}
>
{sortRecsByDistance ? '✓ Đang lọc gần đây' : '🔍 Xem gần tôi'}
</button>
<button
onClick={() => setIsProposeModalOpen(true)}
className="text-[9px] px-2.5 py-1 bg-blue-600 hover:bg-blue-700 text-white rounded-xl font-bold transition-all shadow-sm cursor-pointer"
>
+ Đ xuất đa điểm
</button>
</div>
{processedRecommendations.length === 0 ? (
<p className="text-xs text-gray-400 italic text-center py-4">Chưa đa điểm đ xuất nào.</p>
) : (
<div className="space-y-2.5">
{processedRecommendations.map((item) => {
const dist = userGpsPos && typeof item.latitude === 'number' && typeof item.longitude === 'number'
? calculateDistance(userGpsPos[0], userGpsPos[1], item.latitude, item.longitude)
: null;
return (
<div key={item.id} className="flex flex-col gap-1 p-2.5 bg-gray-50/50 dark:bg-slate-850/50 rounded-2xl border border-gray-100/50 dark:border-slate-800/50 text-left">
<div className="flex items-start justify-between gap-1.5">
<span className="text-xs font-bold text-gray-800 dark:text-slate-200 truncate">{item.name}</span>
<span className="text-[8px] bg-emerald-50 dark:bg-emerald-950/40 text-emerald-600 dark:text-emerald-400 px-1.5 py-0.5 rounded font-black shrink-0">
{item.type === 'RESTAURANT' ? 'Nhà hàng' : item.type === 'HOTEL' ? 'Khách sạn' : 'Homestay'}
</span>
</div>
<div className="flex items-center gap-0.5 text-amber-500 text-[10px]">
{Array.from({ length: item.stars || 5 }).map((_, i) => (
<span key={i}></span>
))}
</div>
{item.address && (
<div className="text-[10px] text-gray-500 dark:text-gray-400 truncate">{item.address}</div>
)}
{item.phone && (
<div className="text-[9px] text-gray-400">SĐT: {item.phone}</div>
)}
{dist !== null && (
<div className="text-[10px] text-emerald-600 dark:text-emerald-400 font-bold">
Cách đây: {dist < 1 ? `${Math.round(dist * 1000)} m` : `${dist.toFixed(1)} km`}
</div>
)}
{item.latitude && item.longitude && (
<button
onClick={() => {
setUserPos([item.latitude, item.longitude]);
setMapCenter([item.latitude, item.longitude]);
}}
className="text-[9px] font-bold text-blue-600 dark:text-blue-400 text-left hover:underline flex items-center gap-1 mt-0.5 cursor-pointer bg-transparent border-none p-0"
>
📍 Đnh vị trên bản đ
</button>
)}
<div className="text-[10px] text-slate-600 dark:text-slate-400 font-medium italic mt-1.5 pt-1.5 border-t border-gray-100 dark:border-slate-800 whitespace-pre-line">
{item.description}
</div>
</div>
);
})}
</div>
)}
</div>
)}
{/* Blacklist Panel */}
{isBlacklistOpen && (
<div className="bg-white/95 dark:bg-slate-900/95 backdrop-blur-md border border-gray-100 dark:border-slate-800 rounded-3xl shadow-2xl p-4 w-72 max-h-[350px] overflow-y-auto no-scrollbar animate-in slide-in-from-bottom-2 duration-300">
<h4 className="text-xs font-black uppercase text-red-600 dark:text-red-500 tracking-widest mb-3 flex items-center gap-2 border-b border-gray-100 dark:border-slate-800 pb-2">
{t('blacklistTitle')}
</h4>
<div className="flex items-center justify-between gap-2 mb-2">
<button
onClick={() => {
if (!sortBlacklistByDistance && !userGpsPos) {
requestGpsPosition();
}
setSortBlacklistByDistance(prev => !prev);
}}
className={`text-[9px] px-2.5 py-1 rounded-xl font-bold transition-all cursor-pointer ${
sortBlacklistByDistance
? 'bg-red-600 text-white shadow-sm'
: 'bg-gray-100 hover:bg-gray-200 text-gray-600 dark:bg-slate-850 dark:text-slate-300 dark:hover:bg-slate-800'
}`}
>
{sortBlacklistByDistance ? '✓ Đang lọc gần đây' : '🔍 Xem gần tôi'}
</button>
</div>
{processedBlacklist.length === 0 ? (
<p className="text-xs text-gray-400 italic text-center py-4">{t('emptyBlacklist')}</p>
) : (
<div className="space-y-2.5">
{processedBlacklist.map((item) => {
const dist = userGpsPos && typeof item.latitude === 'number' && typeof item.longitude === 'number'
? calculateDistance(userGpsPos[0], userGpsPos[1], item.latitude, item.longitude)
: null;
return (
<div key={item.id} className="flex flex-col gap-1 p-2.5 bg-gray-50/50 dark:bg-slate-850/50 rounded-2xl border border-gray-100/50 dark:border-slate-800/50 text-left">
<div className="flex items-start justify-between gap-1.5">
<span className="text-xs font-bold text-gray-800 dark:text-slate-200 truncate">{item.name}</span>
<span className="text-[8px] bg-red-50 dark:bg-red-950 text-red-600 dark:text-red-400 px-1 py-0.5 rounded font-black shrink-0">
{item.type}
</span>
</div>
{item.address && (
<div className="text-[10px] text-gray-500 dark:text-gray-400 truncate">{item.address}</div>
)}
{item.phone && (
<div className="text-[9px] text-gray-400">SĐT: {item.phone}</div>
)}
{dist !== null && (
<div className="text-[10px] text-red-600 dark:text-red-400 font-bold">
Cách đây: {dist < 1 ? `${Math.round(dist * 1000)} m` : `${dist.toFixed(1)} km`}
</div>
)}
{item.latitude && item.longitude && (
<button
onClick={() => {
setUserPos([item.latitude, item.longitude]);
setMapCenter([item.latitude, item.longitude]);
}}
className="text-[9px] font-bold text-blue-600 dark:text-blue-400 text-left hover:underline flex items-center gap-1 mt-0.5 cursor-pointer bg-transparent border-none p-0"
>
📍 Đnh vị trên bản đ
</button>
)}
<div className="text-[10px] text-red-600 dark:text-red-400 font-medium italic mt-1.5 pt-1.5 border-t border-gray-100 dark:border-slate-850">
do: {item.reason}
</div>
</div>
);
})}
</div>
)}
</div>
)}
</div>
{/* Propose Location Modal */}
{isProposeModalOpen && (
<div className="fixed inset-0 z-[3000] flex items-center justify-center p-4">
<div className="absolute inset-0 bg-slate-950/60 backdrop-blur-sm" onClick={() => setIsProposeModalOpen(false)} />
<div className="relative w-full max-w-md bg-white dark:bg-slate-900 border border-gray-150 dark:border-slate-800 rounded-3xl shadow-2xl overflow-hidden flex flex-col p-6 animate-in zoom-in-95 duration-200">
<h3 className="text-base font-black uppercase text-emerald-600 dark:text-emerald-500 tracking-wider mb-4 flex items-center gap-1.5">
🌟 Đ xuất đa điểm chất lượng
</h3>
<form onSubmit={handleProposeSubmit} className="space-y-3.5 text-left text-gray-850 dark:text-slate-100 text-xs">
<div>
<label className="block text-[10px] uppercase font-black text-gray-450 mb-1">Tên đa điểm *</label>
<input
type="text"
required
placeholder="Ví dụ: Khách sạn Mường Thanh"
value={proposeForm.name}
onChange={(e) => setProposeForm(prev => ({ ...prev, name: e.target.value }))}
className="w-full px-3 py-2 border border-gray-200 dark:border-slate-800 rounded-xl focus:outline-none focus:ring-1 focus:ring-emerald-500 bg-gray-50/20 dark:bg-slate-950/40 text-gray-900 dark:text-slate-100"
/>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-[10px] uppercase font-black text-gray-455 mb-1">Loại hình *</label>
<select
value={proposeForm.type}
onChange={(e) => setProposeForm(prev => ({ ...prev, type: e.target.value }))}
className="w-full px-3 py-2 border border-gray-200 dark:border-slate-800 rounded-xl focus:outline-none focus:ring-1 focus:ring-emerald-500 bg-gray-50/20 dark:bg-slate-950/40 text-gray-900 dark:text-slate-100"
>
<option value="RESTAURANT">Nhà hàng</option>
<option value="HOTEL">Khách sạn</option>
<option value="HOMESTAY">Homestay</option>
</select>
</div>
<div>
<label className="block text-[10px] uppercase font-black text-gray-455 mb-1">Đánh giá sao *</label>
<select
value={proposeForm.stars}
onChange={(e) => setProposeForm(prev => ({ ...prev, stars: parseInt(e.target.value) || 5 }))}
className="w-full px-3 py-2 border border-gray-200 dark:border-slate-800 rounded-xl focus:outline-none focus:ring-1 focus:ring-emerald-500 bg-gray-50/20 dark:bg-slate-950/40 text-gray-900 dark:text-slate-100"
>
<option value="5"> (5 sao)</option>
<option value="4"> (4 sao)</option>
<option value="3"> (3 sao)</option>
<option value="2"> (2 sao)</option>
<option value="1"> (1 sao)</option>
</select>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-[10px] uppercase font-black text-gray-455 mb-1">Số điện thoại</label>
<input
type="tel"
placeholder="SĐT liên hệ"
value={proposeForm.phone}
onChange={(e) => setProposeForm(prev => ({ ...prev, phone: e.target.value }))}
className="w-full px-3 py-2 border border-gray-200 dark:border-slate-800 rounded-xl focus:outline-none focus:ring-1 focus:ring-emerald-500 bg-gray-50/20 dark:bg-slate-950/40 text-gray-900 dark:text-slate-100"
/>
</div>
<div>
<label className="block text-[10px] uppercase font-black text-gray-455 mb-1">Email</label>
<input
type="email"
placeholder="Email liên hệ"
value={proposeForm.email}
onChange={(e) => setProposeForm(prev => ({ ...prev, email: e.target.value }))}
className="w-full px-3 py-2 border border-gray-200 dark:border-slate-800 rounded-xl focus:outline-none focus:ring-1 focus:ring-emerald-500 bg-gray-50/20 dark:bg-slate-950/40 text-gray-900 dark:text-slate-100"
/>
</div>
</div>
<div>
<label className="block text-[10px] uppercase font-black text-gray-455 mb-1">Đa chỉ</label>
<input
type="text"
placeholder="Địa chỉ cụ thể"
value={proposeForm.address}
onChange={(e) => setProposeForm(prev => ({ ...prev, address: e.target.value }))}
className="w-full px-3 py-2 border border-gray-200 dark:border-slate-800 rounded-xl focus:outline-none focus:ring-1 focus:ring-emerald-500 bg-gray-50/20 dark:bg-slate-950/40 text-gray-900 dark:text-slate-100"
/>
</div>
<div className="grid grid-cols-2 gap-3 bg-gray-50 dark:bg-slate-950/40 p-2.5 rounded-xl border border-gray-150 dark:border-slate-850">
<div>
<label className="block text-[9px] uppercase font-black text-gray-450 mb-0.5"> đ (Lat)</label>
<input
type="number"
step="any"
value={proposeForm.latitude}
onChange={(e) => setProposeForm(prev => ({ ...prev, latitude: e.target.value }))}
className="w-full px-2 py-1 border border-gray-200 dark:border-slate-800 rounded-lg focus:outline-none text-[11px] bg-white dark:bg-slate-900 text-gray-900 dark:text-slate-100"
/>
</div>
<div>
<label className="block text-[9px] uppercase font-black text-gray-450 mb-0.5">Kinh đ (Lng)</label>
<input
type="number"
step="any"
value={proposeForm.longitude}
onChange={(e) => setProposeForm(prev => ({ ...prev, longitude: e.target.value }))}
className="w-full px-2 py-1 border border-gray-200 dark:border-slate-800 rounded-lg focus:outline-none text-[11px] bg-white dark:bg-slate-900 text-gray-900 dark:text-slate-100"
/>
</div>
</div>
<div>
<label className="block text-[10px] uppercase font-black text-gray-455 mb-1"> tả / Review do đ xuất *</label>
<textarea
required
rows={3}
placeholder="Ví dụ: Thức ăn tươi ngon, không gian ấm cúng, phục vụ chu đáo..."
value={proposeForm.description}
onChange={(e) => setProposeForm(prev => ({ ...prev, description: e.target.value }))}
className="w-full px-3 py-2 border border-gray-200 dark:border-slate-800 rounded-xl focus:outline-none focus:ring-1 focus:ring-emerald-500 bg-gray-50/20 dark:bg-slate-950/40 text-gray-900 dark:text-slate-100"
/>
</div>
<div className="flex gap-3 pt-2">
<button
type="button"
onClick={() => setIsProposeModalOpen(false)}
className="flex-1 py-2.5 bg-gray-100 dark:bg-slate-800 hover:bg-gray-200 dark:hover:bg-slate-750 text-gray-600 dark:text-slate-200 rounded-xl font-bold transition-all text-xs cursor-pointer"
>
Hủy bỏ
</button>
<button
type="submit"
className="flex-1 py-2.5 bg-emerald-600 hover:bg-emerald-700 text-white rounded-xl font-bold transition-all text-xs shadow-md active:scale-95 cursor-pointer"
>
Gửi đ xuất
</button>
</div>
</form>
</div>
</div>
)}
{/* Report Business Modal */}
<ReportBusinessModal
isOpen={isReportModalOpen}
onClose={() => {
setIsReportModalOpen(false);
fetchBlacklist();
}}
initialLatitude={mapCenter ? mapCenter[0] : undefined}
initialLongitude={mapCenter ? mapCenter[1] : undefined}
/>
</div>
);
};