fix: lỗi bong bóng Tour vẫn xuất hiện và điểm kết thúc
This commit is contained in:
Vendored
+1
@@ -607,6 +607,7 @@ let TourController = class TourController {
|
|||||||
await this.prisma.tour.delete({
|
await this.prisma.tour.delete({
|
||||||
where: { id },
|
where: { id },
|
||||||
});
|
});
|
||||||
|
await this.cacheManager.del(`/api/v1/tours/explore`);
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
async getPublicTours(req) {
|
async getPublicTours(req) {
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -643,6 +643,10 @@ class TourController {
|
|||||||
await this.prisma.tour.delete({
|
await this.prisma.tour.delete({
|
||||||
where: { id },
|
where: { id },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Xóa cache explore để Tour biến mất ngay lập tức trên bản đồ cộng đồng
|
||||||
|
await this.cacheManager.del(`/api/v1/tours/explore`);
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ const MapPicker = ({ onPick, center }: { onPick: (latlng: L.LatLng) => void, cen
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editingLocation, isStartPoint = false, isPublicView = false, onSuccess }: { isOpen: boolean, onClose: () => void, tourId: string, initialLegId?: string, editingLocation?: any, isStartPoint?: boolean, isPublicView?: boolean, onSuccess?: () => void }) => {
|
export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editingLocation, isStartPoint = false, isEndPoint = false, isPublicView = false, onSuccess }: { isOpen: boolean, onClose: () => void, tourId: string, initialLegId?: string, editingLocation?: any, isStartPoint?: boolean, isEndPoint?: boolean, isPublicView?: boolean, onSuccess?: () => void }) => {
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
name: '',
|
name: '',
|
||||||
address: '',
|
address: '',
|
||||||
@@ -85,7 +85,7 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin
|
|||||||
const searchTimeout = useRef<any>(null);
|
const searchTimeout = useRef<any>(null);
|
||||||
|
|
||||||
// Gom các selector lại để giảm số lượng Hook gọi nội bộ và tăng hiệu năng
|
// Gom các selector lại để giảm số lượng Hook gọi nội bộ và tăng hiệu năng
|
||||||
const { legs, addLocation, updateLocation, updateTourStartPoint, mapCenter, currentTour, userRole } = useTourStore();
|
const { legs, addLocation, updateLocation, updateTourStartPoint, updateTourEndPoint, mapCenter, currentTour, userRole } = useTourStore();
|
||||||
const notify = useNotification();
|
const notify = useNotification();
|
||||||
|
|
||||||
// 1. Luôn khai báo Hook ở cấp cao nhất, không đặt code logic/return giữa các Hook
|
// 1. Luôn khai báo Hook ở cấp cao nhất, không đặt code logic/return giữa các Hook
|
||||||
@@ -154,9 +154,12 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin
|
|||||||
const currentLegId = formData.legId || (legs.length > 0 ? legs[0].id : '');
|
const currentLegId = formData.legId || (legs.length > 0 ? legs[0].id : '');
|
||||||
const targetLeg = legs.find(l => l.id === currentLegId);
|
const targetLeg = legs.find(l => l.id === currentLegId);
|
||||||
const titleText = isStartPoint ? 'Thiết lập Điểm xuất phát' :
|
const titleText = isStartPoint ? 'Thiết lập Điểm xuất phát' :
|
||||||
|
isEndPoint ? 'Thiết lập Điểm kết thúc' :
|
||||||
editingLocation ? `Sửa địa điểm: ${editingLocation.name}` :
|
editingLocation ? `Sửa địa điểm: ${editingLocation.name}` :
|
||||||
(initialLegId ? `Thêm địa điểm cho ${targetLeg?.note || `Chặng ${targetLeg?.sequence}`}` : 'Thêm địa điểm mới');
|
(initialLegId ? `Thêm địa điểm cho ${targetLeg?.note || `Chặng ${targetLeg?.sequence}`}` : 'Thêm địa điểm mới');
|
||||||
const buttonText = isStartPoint ? 'Xác nhận Điểm xuất phát' : editingLocation ? 'Cập nhật thay đổi' : (initialLegId ? 'Xác nhận thêm vào chặng' : 'Thêm địa điểm');
|
const buttonText = isStartPoint ? 'Xác nhận Điểm xuất phát' :
|
||||||
|
isEndPoint ? 'Xác nhận Điểm kết thúc' :
|
||||||
|
editingLocation ? 'Cập nhật thay đổi' : (initialLegId ? 'Xác nhận thêm vào chặng' : 'Thêm địa điểm');
|
||||||
|
|
||||||
const handleSearchLocation = (query: string) => {
|
const handleSearchLocation = (query: string) => {
|
||||||
setFormData(prev => ({ ...prev, name: query }));
|
setFormData(prev => ({ ...prev, name: query }));
|
||||||
@@ -289,6 +292,12 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin
|
|||||||
latitude: parseFloat(formData.latitude as any),
|
latitude: parseFloat(formData.latitude as any),
|
||||||
longitude: parseFloat(formData.longitude as any),
|
longitude: parseFloat(formData.longitude as any),
|
||||||
});
|
});
|
||||||
|
} else if (isEndPoint) {
|
||||||
|
await updateTourEndPoint(tourId, {
|
||||||
|
name: formData.name || "Điểm kết thúc",
|
||||||
|
latitude: parseFloat(formData.latitude as any),
|
||||||
|
longitude: parseFloat(formData.longitude as any),
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const payload: any = {
|
const payload: any = {
|
||||||
...formData,
|
...formData,
|
||||||
@@ -304,7 +313,11 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin
|
|||||||
await addLocation(tourId, payload);
|
await addLocation(tourId, payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
notify({ title: 'Thành công', message: isStartPoint ? 'Đã thiết lập điểm xuất phát.' : editingLocation ? 'Đã cập nhật địa điểm.' : 'Đã thêm địa điểm mới.', type: 'success' });
|
notify({
|
||||||
|
title: 'Thành công',
|
||||||
|
message: isStartPoint ? 'Đã thiết lập điểm xuất phát.' : isEndPoint ? 'Đã thiết lập điểm kết thúc.' : editingLocation ? 'Đã cập nhật địa điểm.' : 'Đã thêm địa điểm mới.',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
onSuccess?.(); // Gọi callback onSuccess sau khi thành công
|
onSuccess?.(); // Gọi callback onSuccess sau khi thành công
|
||||||
onClose();
|
onClose();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect, useMemo } from 'react';
|
import React, { useState, useEffect, useMemo } from 'react';
|
||||||
import { format, differenceInMinutes, parseISO } from 'date-fns';
|
import { format, differenceInMinutes, parseISO } from 'date-fns';
|
||||||
import { CheckCircle2, Circle, Clock, MapPin, AlertCircle, Zap, Navigation, Edit2, Trash2, Plus, List, X, Calendar as CalendarIcon, AlignLeft, MessageSquare, FileText } from 'lucide-react';
|
import { CheckCircle2, Circle, Clock, MapPin, AlertCircle, Zap, Navigation, Edit2, Trash2, Plus, List, X, Calendar as CalendarIcon, AlignLeft, MessageSquare, FileText, Flag } from 'lucide-react';
|
||||||
import { useTourStore } from '@/store/useTourStore';
|
import { useTourStore } from '@/store/useTourStore';
|
||||||
import { useConfirm } from '@/hooks/useConfirm';
|
import { useConfirm } from '@/hooks/useConfirm';
|
||||||
import { useNotification } from '@/hooks/useNotification';
|
import { useNotification } from '@/hooks/useNotification';
|
||||||
@@ -45,7 +45,7 @@ export const ItineraryTimeline = ({
|
|||||||
onSuccess,
|
onSuccess,
|
||||||
isPublicView = false
|
isPublicView = false
|
||||||
}: {
|
}: {
|
||||||
onAddLocation?: (legId: string, isStart?: boolean) => void,
|
onAddLocation?: (legId: string, isStart?: boolean, isEnd?: boolean) => void,
|
||||||
onEditLocation?: (location: any) => void,
|
onEditLocation?: (location: any) => void,
|
||||||
onQuickNote?: (name: string) => void,
|
onQuickNote?: (name: string) => void,
|
||||||
onSuccess?: () => void,
|
onSuccess?: () => void,
|
||||||
@@ -322,6 +322,27 @@ export const ItineraryTimeline = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Nút thêm nhanh "Điểm kết thúc" cho Chặng cuối nếu chưa có */}
|
||||||
|
{legIdx === legs.length - 1 && !legs.some(l => l.locations.some(loc => loc.plannedEnd && new Date(loc.plannedEnd).getTime() === 0)) && (
|
||||||
|
<div className="relative flex group mb-6 opacity-80 hover:opacity-100 transition-opacity">
|
||||||
|
<div className="z-10 mt-1.5 mr-4">
|
||||||
|
<div className="w-8 h-8 bg-white rounded-full border-2 border-dashed border-red-200 flex items-center justify-center text-red-400">
|
||||||
|
<Flag className="w-4 h-4" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={() => onAddLocation?.(leg.id, false, true)}
|
||||||
|
className="flex-1 bg-red-50/20 p-4 rounded-xl border border-dashed border-red-100 hover:border-red-400 hover:bg-red-50 transition-all flex items-center justify-between group"
|
||||||
|
>
|
||||||
|
<div className="text-left">
|
||||||
|
<span className="inline-block px-2 py-0.5 bg-red-50 text-red-600 text-[10px] font-bold rounded-md mb-1 uppercase tracking-wider">Điểm kết thúc</span>
|
||||||
|
<h3 className="font-bold text-gray-400 text-sm italic">Nhấn để ghim điểm kết thúc cho Tour...</h3>
|
||||||
|
</div>
|
||||||
|
<Plus className="w-5 h-5 text-red-500 group-hover:scale-110 transition-transform" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{leg.locations.map((location, idx) => {
|
{leg.locations.map((location, idx) => {
|
||||||
// Tìm vị trí của điểm này trong toàn bộ hành trình
|
// Tìm vị trí của điểm này trong toàn bộ hành trình
|
||||||
const globalIdx = allLocations.findIndex(loc => loc.id === location.id);
|
const globalIdx = allLocations.findIndex(loc => loc.id === location.id);
|
||||||
|
|||||||
@@ -208,6 +208,7 @@ export const TourDetailPage = ({
|
|||||||
const [isAddMemberOpen, setIsAddMemberOpen] = useState(false);
|
const [isAddMemberOpen, setIsAddMemberOpen] = useState(false);
|
||||||
const [isAddPhotoOpen, setIsAddPhotoOpen] = useState(false);
|
const [isAddPhotoOpen, setIsAddPhotoOpen] = useState(false);
|
||||||
const [isStartPointAction, setIsStartPointAction] = useState(false);
|
const [isStartPointAction, setIsStartPointAction] = useState(false);
|
||||||
|
const [isEndPointAction, setIsEndPointAction] = useState(false);
|
||||||
const [targetLegId, setTargetLegId] = useState<string | null>(null);
|
const [targetLegId, setTargetLegId] = useState<string | null>(null);
|
||||||
const [editingLocation, setEditingLocation] = useState<any>(null);
|
const [editingLocation, setEditingLocation] = useState<any>(null);
|
||||||
const [selectedMember, setSelectedMember] = useState<any>(null);
|
const [selectedMember, setSelectedMember] = useState<any>(null);
|
||||||
@@ -1015,16 +1016,18 @@ export const TourDetailPage = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{viewMode === 'timeline' ? (
|
{viewMode === 'timeline' ? (
|
||||||
<ItineraryTimeline onAddLocation={(legId, isStart) => {
|
<ItineraryTimeline onAddLocation={(legId, isStart, isEnd) => {
|
||||||
setTargetLegId(legId);
|
setTargetLegId(legId);
|
||||||
setEditingLocation(null);
|
setEditingLocation(null);
|
||||||
setIsStartPointAction(!!isStart);
|
setIsStartPointAction(!!isStart);
|
||||||
|
setIsEndPointAction(!!isEnd);
|
||||||
setIsAddLocationOpen(true);
|
setIsAddLocationOpen(true);
|
||||||
}} onEditLocation={(loc) => {
|
}} onEditLocation={(loc) => {
|
||||||
setEditingLocation(loc);
|
setEditingLocation(loc);
|
||||||
setTargetLegId(loc.legId);
|
setTargetLegId(loc.legId);
|
||||||
setMapCenter([loc.latitude, loc.longitude]);
|
setMapCenter([loc.latitude, loc.longitude]);
|
||||||
setIsStartPointAction(false);
|
setIsStartPointAction(false);
|
||||||
|
setIsEndPointAction(false);
|
||||||
setIsAddLocationOpen(true);
|
setIsAddLocationOpen(true);
|
||||||
}}
|
}}
|
||||||
onQuickNote={(locName: string) => handleQuickNote(locName)}
|
onQuickNote={(locName: string) => handleQuickNote(locName)}
|
||||||
@@ -1509,6 +1512,7 @@ export const TourDetailPage = ({
|
|||||||
setTargetLegId(null); // Reset khi nhấn nút floating tổng quát
|
setTargetLegId(null); // Reset khi nhấn nút floating tổng quát
|
||||||
setEditingLocation(null);
|
setEditingLocation(null);
|
||||||
setIsStartPointAction(false);
|
setIsStartPointAction(false);
|
||||||
|
setIsEndPointAction(false);
|
||||||
if (activeTab === 'plan') setIsAddLocationOpen(true);
|
if (activeTab === 'plan') setIsAddLocationOpen(true);
|
||||||
if (activeTab === 'photo' && canUploadPhoto) setIsAddPhotoOpen(true);
|
if (activeTab === 'photo' && canUploadPhoto) setIsAddPhotoOpen(true);
|
||||||
}}
|
}}
|
||||||
@@ -1540,6 +1544,7 @@ export const TourDetailPage = ({
|
|||||||
onClose={() => setIsAddLocationOpen(false)}
|
onClose={() => setIsAddLocationOpen(false)}
|
||||||
initialLegId={targetLegId || undefined}
|
initialLegId={targetLegId || undefined}
|
||||||
isStartPoint={isStartPointAction}
|
isStartPoint={isStartPointAction}
|
||||||
|
isEndPoint={isEndPointAction}
|
||||||
editingLocation={editingLocation}
|
editingLocation={editingLocation}
|
||||||
tourId={currentTour.id}
|
tourId={currentTour.id}
|
||||||
isPublicView={isPublicView} // Pass isPublicView
|
isPublicView={isPublicView} // Pass isPublicView
|
||||||
|
|||||||
Reference in New Issue
Block a user