From 838aec562fc2ef9d12d068b33d15420c038be9d8 Mon Sep 17 00:00:00 2001 From: 3dtours Date: Sun, 14 Jun 2026 10:16:09 +0700 Subject: [PATCH] =?UTF-8?q?B=E1=BB=95=20sung=20t=C3=ADnh=20n=C4=83ng=20s?= =?UTF-8?q?=E1=BB=ADa=20ch=E1=BB=AFa,=20c=E1=BA=ADp=20nh=E1=BA=ADt=20?= =?UTF-8?q?=C4=91=E1=BB=8Ba=20=C4=91i=E1=BB=83m=20trong=20ch=E1=BA=B7ng?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AddLocationModal.tsx | 56 +++++++++++++++++++++++--------- ItineraryTimeline.tsx | 25 +++++++++++++-- TourDetailPage.tsx | 9 ++++++ dist/AddLocationModal.d.ts | 3 +- dist/AddLocationModal.js | 57 +++++++++++++++++++++++++-------- dist/AddLocationModal.js.map | 2 +- dist/ItineraryTimeline.d.ts | 3 +- dist/ItineraryTimeline.js | 16 +++++++-- dist/ItineraryTimeline.js.map | 2 +- dist/TourDetailPage.js | 10 +++++- dist/TourDetailPage.js.map | 2 +- dist/main.js | 46 +++++++++++++++++++++++++- dist/main.js.map | 2 +- dist/tsconfig.build.tsbuildinfo | 2 +- dist/useTourStore.d.ts | 2 ++ dist/useTourStore.js | 30 +++++++++++++++++ dist/useTourStore.js.map | 2 +- main.ts | 31 +++++++++++++++++- useTourStore.ts | 30 +++++++++++++++++ 19 files changed, 285 insertions(+), 45 deletions(-) diff --git a/AddLocationModal.tsx b/AddLocationModal.tsx index 06b199b..c3e16e1 100644 --- a/AddLocationModal.tsx +++ b/AddLocationModal.tsx @@ -59,7 +59,7 @@ const MapPicker = ({ onPick, center }: { onPick: (latlng: L.LatLng) => void, cen ); }; -export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId }: { isOpen: boolean, onClose: () => void, tourId: string, initialLegId?: string }) => { +export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editingLocation }: { isOpen: boolean, onClose: () => void, tourId: string, initialLegId?: string, editingLocation?: any }) => { const [formData, setFormData] = useState({ name: '', address: '', @@ -73,15 +73,32 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId }: { is const [isLoading, setIsLoading] = useState(false); // 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, mapCenter } = useTourStore(); + const { legs, addLocation, updateLocation, mapCenter } = useTourStore(); // 1. Luôn khai báo Hook ở cấp cao nhất, không đặt code logic/return giữa các Hook useEffect(() => { - // Chỉ cập nhật nếu giá trị thực sự thay đổi để tránh vòng lặp re-render - if (initialLegId && formData.legId !== initialLegId) { - setFormData(prev => ({ ...prev, legId: initialLegId })); + if (isOpen) { + if (editingLocation) { + setFormData({ + name: editingLocation.name || '', + address: editingLocation.address || '', + latitude: editingLocation.latitude, + longitude: editingLocation.longitude, + type: editingLocation.type || 'VISIT', + legId: editingLocation.legId || '', + plannedStart: editingLocation.plannedStart ? editingLocation.plannedStart.slice(0, 16) : '', + plannedEnd: editingLocation.plannedEnd ? editingLocation.plannedEnd.slice(0, 16) : '' + }); + } else { + setFormData(prev => ({ + ...prev, + name: '', address: '', + legId: initialLegId || (legs.length > 0 ? legs[0].id : ''), + plannedStart: '', plannedEnd: '' + })); + } } - }, [initialLegId, isOpen]); + }, [initialLegId, editingLocation, isOpen]); useEffect(() => { if (isOpen && !formData.name) { @@ -92,8 +109,8 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId }: { is // 2. Thực hiện các tính toán và hàm xử lý const currentLegId = formData.legId || (legs.length > 0 ? legs[0].id : ''); const targetLeg = legs.find(l => l.id === currentLegId); - const titleText = initialLegId ? `Thêm địa điểm cho ${targetLeg?.note || `Chặng ${targetLeg?.sequence}`}` : 'Thêm địa điểm mới'; - const buttonText = initialLegId ? 'Xác nhận thêm vào chặng' : 'Thêm địa điểm'; + const titleText = 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 = editingLocation ? 'Cập nhật thay đổi' : (initialLegId ? 'Xác nhận thêm vào chặng' : 'Thêm địa điểm'); const handlePickLocation = async (latlng: L.LatLng) => { setFormData(prev => ({ ...prev, latitude: latlng.lat, longitude: latlng.lng })); @@ -119,15 +136,24 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId }: { is e.preventDefault(); setIsLoading(true); try { - await addLocation(tourId, { - ...formData, - legId: currentLegId, - latitude: parseFloat(formData.latitude as any), - longitude: parseFloat(formData.longitude as any), - }); + if (editingLocation) { + await updateLocation(editingLocation.id, { + ...formData, + legId: currentLegId, + latitude: parseFloat(formData.latitude as any), + longitude: parseFloat(formData.longitude as any), + }); + } else { + await addLocation(tourId, { + ...formData, + legId: currentLegId, + latitude: parseFloat(formData.latitude as any), + longitude: parseFloat(formData.longitude as any), + }); + } onClose(); } catch (error) { - alert('Lỗi khi thêm địa điểm'); + alert('Lỗi khi lưu địa điểm'); } finally { setIsLoading(false); } diff --git a/ItineraryTimeline.tsx b/ItineraryTimeline.tsx index 547daa2..08f5f45 100644 --- a/ItineraryTimeline.tsx +++ b/ItineraryTimeline.tsx @@ -35,8 +35,11 @@ const formatTravelTime = (minutes: number) => { return mins > 0 ? `${hours} giờ ${mins} phút` : `${hours} giờ`; }; -export const ItineraryTimeline = ({ onAddLocation }: { onAddLocation?: (legId: string) => void }) => { - const { currentTour, legs, optimizeRouting, userRole, addLeg, updateLeg, deleteLeg, initializeLegs } = useTourStore(); +export const ItineraryTimeline = ({ + onAddLocation, + onEditLocation +}: { onAddLocation?: (legId: string) => void, onEditLocation?: (location: any) => void }) => { + const { currentTour, legs, optimizeRouting, userRole, addLeg, updateLeg, deleteLeg, initializeLegs, deleteLocation } = useTourStore(); const toggleComplete = async (locationId: string) => { // Gọi API PATCH /api/v1/locations/:id để cập nhật trạng thái @@ -75,6 +78,14 @@ export const ItineraryTimeline = ({ onAddLocation }: { onAddLocation?: (legId: s } }; + const handleDeleteLocation = async (id: string) => { + if (window.confirm("Bạn có chắc chắn muốn xóa địa điểm này?")) { + try { + await deleteLocation(id); + } catch (err: any) { alert(err.message); } + } + }; + return (
@@ -241,6 +252,16 @@ export const ItineraryTimeline = ({ onAddLocation }: { onAddLocation?: (legId: s Thực tế: {format(parseISO(location.actualStart), 'HH:mm')}
)} + {['OWNER', 'MANAGER'].includes(userRole || '') && !isStartPoint && !isEndPoint && ( +
+ + +
+ )}
diff --git a/TourDetailPage.tsx b/TourDetailPage.tsx index 26dc0aa..9937ed4 100644 --- a/TourDetailPage.tsx +++ b/TourDetailPage.tsx @@ -161,6 +161,7 @@ export const TourDetailPage = ({ onBack }: { onBack: () => void }) => { const [viewMode, setViewMode] = useState<'map' | 'timeline'>('timeline'); const [isAddLocationOpen, setIsAddLocationOpen] = useState(false); const [targetLegId, setTargetLegId] = useState(null); + const [editingLocation, setEditingLocation] = useState(null); // Khôi phục vị trí và mức zoom từ localStorage const [initialViewState] = useState(() => { @@ -499,6 +500,12 @@ export const TourDetailPage = ({ onBack }: { onBack: () => void }) => { {viewMode === 'timeline' ? ( { setTargetLegId(legId); + setEditingLocation(null); + setIsAddLocationOpen(true); + }} onEditLocation={(loc) => { + setEditingLocation(loc); + setTargetLegId(loc.legId); + setMapCenter([loc.latitude, loc.longitude]); setIsAddLocationOpen(true); }} /> ) : ( @@ -587,6 +594,7 @@ export const TourDetailPage = ({ onBack }: { onBack: () => void }) => {