feat: tíng năng trò chuyện giữa các thành viên và tin nhắn trực tiếp cho bạn bè
This commit is contained in:
@@ -10,6 +10,7 @@ import { useConfirm } from '@/hooks/useConfirm';
|
||||
import { useNotification } from '@/hooks/useNotification';
|
||||
import { CommentModal } from '@/components/CommentModal';
|
||||
import { AddPhotoModal } from '@/components/AddPhotoModal';
|
||||
import { TourChat } from '../components/TourChat';
|
||||
import { MapContainer, TileLayer, Marker, Popup, useMapEvents, Polyline, useMap, Tooltip } from 'react-leaflet';
|
||||
import _MarkerClusterGroup from 'react-leaflet-cluster';
|
||||
const MarkerClusterGroup = (_MarkerClusterGroup as any).default || _MarkerClusterGroup;
|
||||
@@ -361,8 +362,14 @@ export const TourDetailPage = ({
|
||||
const [userSpeed, setUserSpeed] = useState<number | null>(null); // Tốc độ di chuyển từ GPS
|
||||
|
||||
// Di chuyển khai báo state lên trên useEffect để tránh lỗi "before initialization"
|
||||
const [activeTab, setActiveTab] = useState<'plan' | 'expense' | 'photo' | 'settings' | 'members'>('plan');
|
||||
const [activeTab, setActiveTab] = useState<'plan' | 'expense' | 'photo' | 'settings' | 'members' | 'chat'>(() => {
|
||||
const defaultTab = localStorage.getItem('tour_detail_default_tab');
|
||||
localStorage.removeItem('tour_detail_default_tab');
|
||||
if (defaultTab === 'chat') return 'chat';
|
||||
return 'plan';
|
||||
});
|
||||
const [mergingId, setMergingId] = useState<string | null>(null);
|
||||
const [unreadChatCount, setUnreadChatCount] = useState(0);
|
||||
const [viewMode, setViewMode] = useState<'map' | 'timeline'>('timeline');
|
||||
const [isHeadingMode, setIsHeadingMode] = useState(false);
|
||||
const [mapRotation, setMapRotation] = useState(0);
|
||||
@@ -1137,8 +1144,14 @@ export const TourDetailPage = ({
|
||||
handleCommentIncrement(data.locationId);
|
||||
});
|
||||
|
||||
socket.on('tourMessageReceived', (data: any) => {
|
||||
if (data.tourId === currentTour.id && activeTab !== 'chat') {
|
||||
setUnreadChatCount(prev => prev + 1);
|
||||
}
|
||||
});
|
||||
|
||||
return () => { socket.disconnect(); };
|
||||
}, [currentTour?.id]);
|
||||
}, [currentTour?.id, activeTab]);
|
||||
|
||||
// This useEffect is for initial demo loading, might not be needed if tourId is always passed
|
||||
// useEffect(() => {
|
||||
@@ -1400,6 +1413,7 @@ export const TourDetailPage = ({
|
||||
{ id: 'plan', label: 'Lộ trình', icon: MapIcon, visible: true },
|
||||
{ id: 'expense', label: 'Chi phí', icon: Wallet, visible: ['OWNER', 'MANAGER', 'MEMBER'].includes(userRole || '') },
|
||||
{ id: 'photo', label: 'Ảnh', icon: ImageIcon, visible: true },
|
||||
{ id: 'chat', label: 'Trò chuyện', icon: MessageSquare, visible: !!userRole, hasBadge: unreadChatCount > 0, badgeCount: unreadChatCount },
|
||||
{ id: 'members', label: 'Thành viên', icon: Users, visible: canManage },
|
||||
{ id: 'settings', label: 'Cài đặt', icon: Settings, visible: userRole === 'OWNER' },
|
||||
].filter(t => t.visible);
|
||||
@@ -1744,8 +1758,11 @@ export const TourDetailPage = ({
|
||||
{tabs.map((tab) => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveTab(tab.id as any)}
|
||||
className={`flex-1 flex items-center justify-center py-3 rounded-xl text-sm font-semibold transition-all ${tab.id === 'settings' && isPublicView ? 'hidden' : ''} ${ // Hide settings tab in public view
|
||||
onClick={() => {
|
||||
setActiveTab(tab.id as any);
|
||||
if (tab.id === 'chat') setUnreadChatCount(0);
|
||||
}}
|
||||
className={`flex-1 flex items-center justify-center py-3 rounded-xl text-sm font-semibold transition-all relative ${tab.id === 'settings' && isPublicView ? 'hidden' : ''} ${ // Hide settings tab in public view
|
||||
activeTab === tab.id
|
||||
? 'bg-blue-50 text-blue-600 shadow-sm'
|
||||
: 'text-gray-400 hover:text-gray-500 hover:bg-gray-50'
|
||||
@@ -1753,6 +1770,11 @@ export const TourDetailPage = ({
|
||||
>
|
||||
<tab.icon className={`w-4 h-4 mr-2 ${activeTab === tab.id ? 'scale-110' : ''}`} />
|
||||
{tab.label}
|
||||
{tab.hasBadge && tab.badgeCount !== undefined && tab.badgeCount > 0 && (
|
||||
<span className="absolute -top-1 -right-1.5 bg-red-500 text-white text-[9px] font-black rounded-full px-1.5 py-0.5 animate-bounce shadow-md">
|
||||
{tab.badgeCount}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -2667,6 +2689,11 @@ export const TourDetailPage = ({
|
||||
onOpenAddMember={() => setIsAddMemberOpen(true)}
|
||||
/>
|
||||
)}
|
||||
{activeTab === 'chat' && currentTour && (
|
||||
<div className="animate-in fade-in slide-in-from-bottom-2">
|
||||
<TourChat tourId={currentTour.id} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user