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:
2026-06-17 17:33:47 +07:00
parent 1933b58f84
commit bfbbb66747
6 changed files with 52 additions and 8 deletions
+1
View File
@@ -607,6 +607,7 @@ let TourController = class TourController {
await this.prisma.tour.delete({
where: { id },
});
await this.cacheManager.del(`/api/v1/tours/explore`);
return { success: true };
}
async getPublicTours(req) {
+1 -1
View File
File diff suppressed because one or more lines are too long
+4
View File
@@ -643,6 +643,10 @@ class TourController {
await this.prisma.tour.delete({
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 };
}
+17 -4
View File
@@ -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({
name: '',
address: '',
@@ -85,7 +85,7 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin
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
const { legs, addLocation, updateLocation, updateTourStartPoint, mapCenter, currentTour, userRole } = useTourStore();
const { legs, addLocation, updateLocation, updateTourStartPoint, updateTourEndPoint, mapCenter, currentTour, userRole } = useTourStore();
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
@@ -154,9 +154,12 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin
const currentLegId = formData.legId || (legs.length > 0 ? legs[0].id : '');
const targetLeg = legs.find(l => l.id === currentLegId);
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}` :
(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) => {
setFormData(prev => ({ ...prev, name: query }));
@@ -289,6 +292,12 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin
latitude: parseFloat(formData.latitude 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 {
const payload: any = {
...formData,
@@ -304,7 +313,11 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editin
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
onClose();
} catch (error) {
+23 -2
View File
@@ -1,6 +1,6 @@
import React, { useState, useEffect, useMemo } from 'react';
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 { useConfirm } from '@/hooks/useConfirm';
import { useNotification } from '@/hooks/useNotification';
@@ -45,7 +45,7 @@ export const ItineraryTimeline = ({
onSuccess,
isPublicView = false
}: {
onAddLocation?: (legId: string, isStart?: boolean) => void,
onAddLocation?: (legId: string, isStart?: boolean, isEnd?: boolean) => void,
onEditLocation?: (location: any) => void,
onQuickNote?: (name: string) => void,
onSuccess?: () => void,
@@ -322,6 +322,27 @@ export const ItineraryTimeline = ({
</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) => {
// 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);
+6 -1
View File
@@ -208,6 +208,7 @@ export const TourDetailPage = ({
const [isAddMemberOpen, setIsAddMemberOpen] = useState(false);
const [isAddPhotoOpen, setIsAddPhotoOpen] = useState(false);
const [isStartPointAction, setIsStartPointAction] = useState(false);
const [isEndPointAction, setIsEndPointAction] = useState(false);
const [targetLegId, setTargetLegId] = useState<string | null>(null);
const [editingLocation, setEditingLocation] = useState<any>(null);
const [selectedMember, setSelectedMember] = useState<any>(null);
@@ -1015,16 +1016,18 @@ export const TourDetailPage = ({
</div>
{viewMode === 'timeline' ? (
<ItineraryTimeline onAddLocation={(legId, isStart) => {
<ItineraryTimeline onAddLocation={(legId, isStart, isEnd) => {
setTargetLegId(legId);
setEditingLocation(null);
setIsStartPointAction(!!isStart);
setIsEndPointAction(!!isEnd);
setIsAddLocationOpen(true);
}} onEditLocation={(loc) => {
setEditingLocation(loc);
setTargetLegId(loc.legId);
setMapCenter([loc.latitude, loc.longitude]);
setIsStartPointAction(false);
setIsEndPointAction(false);
setIsAddLocationOpen(true);
}}
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
setEditingLocation(null);
setIsStartPointAction(false);
setIsEndPointAction(false);
if (activeTab === 'plan') setIsAddLocationOpen(true);
if (activeTab === 'photo' && canUploadPhoto) setIsAddPhotoOpen(true);
}}
@@ -1540,6 +1544,7 @@ export const TourDetailPage = ({
onClose={() => setIsAddLocationOpen(false)}
initialLegId={targetLegId || undefined}
isStartPoint={isStartPointAction}
isEndPoint={isEndPointAction}
editingLocation={editingLocation}
tourId={currentTour.id}
isPublicView={isPublicView} // Pass isPublicView