Bổ sung tính năng sửa chữa, cập nhật địa điểm trong chặng

This commit is contained in:
2026-06-14 10:16:09 +07:00
parent ec650fb45d
commit 838aec562f
19 changed files with 285 additions and 45 deletions
+2 -1
View File
@@ -1,7 +1,8 @@
import React from 'react';
export declare const AddLocationModal: ({ isOpen, onClose, tourId, initialLegId }: {
export declare const AddLocationModal: ({ isOpen, onClose, tourId, initialLegId, editingLocation }: {
isOpen: boolean;
onClose: () => void;
tourId: string;
initialLegId?: string;
editingLocation?: any;
}) => React.JSX.Element;
+43 -14
View File
@@ -33,7 +33,7 @@ const MapPicker = ({ onPick, center }) => {
}, [center]);
return (_jsx(_Fragment, { children: menuPos && (_jsx("div", { ref: menuRef, className: "absolute z-[3000] bg-white rounded-xl shadow-xl border border-gray-100 py-1 w-44 animate-in zoom-in-95 duration-200", style: { top: menuPos.y, left: menuPos.x }, children: _jsxs("button", { type: "button", onClick: () => { onPick(menuPos.latlng); setMenuPos(null); }, className: "w-full text-left px-4 py-2 hover:bg-blue-50 text-xs font-bold text-blue-600 flex items-center gap-2", children: [_jsx(MapPin, { className: "w-3 h-3" }), " Th\u00EAm v\u00E0o ch\u1EB7ng hi\u1EC7n t\u1EA1i"] }) })) }));
};
export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId }) => {
export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId, editingLocation }) => {
const [formData, setFormData] = useState({
name: '',
address: '',
@@ -45,12 +45,31 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId }) => {
plannedEnd: ''
});
const [isLoading, setIsLoading] = useState(false);
const { legs, addLocation, mapCenter } = useTourStore();
const { legs, addLocation, updateLocation, mapCenter } = useTourStore();
useEffect(() => {
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) {
setFormData(prev => ({ ...prev, latitude: mapCenter[0], longitude: mapCenter[1] }));
@@ -58,8 +77,8 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId }) => {
}, [isOpen, mapCenter]);
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) => {
setFormData(prev => ({ ...prev, latitude: latlng.lat, longitude: latlng.lng }));
try {
@@ -80,16 +99,26 @@ export const AddLocationModal = ({ isOpen, onClose, tourId, initialLegId }) => {
e.preventDefault();
setIsLoading(true);
try {
await addLocation(tourId, {
...formData,
legId: currentLegId,
latitude: parseFloat(formData.latitude),
longitude: parseFloat(formData.longitude),
});
if (editingLocation) {
await updateLocation(editingLocation.id, {
...formData,
legId: currentLegId,
latitude: parseFloat(formData.latitude),
longitude: parseFloat(formData.longitude),
});
}
else {
await addLocation(tourId, {
...formData,
legId: currentLegId,
latitude: parseFloat(formData.latitude),
longitude: parseFloat(formData.longitude),
});
}
onClose();
}
catch (error) {
alert('Lỗi khi thêm địa điểm');
alert('Lỗi khi lưu địa điểm');
}
finally {
setIsLoading(false);
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -1,4 +1,5 @@
import React from 'react';
export declare const ItineraryTimeline: ({ onAddLocation }: {
export declare const ItineraryTimeline: ({ onAddLocation, onEditLocation }: {
onAddLocation?: (legId: string) => void;
onEditLocation?: (location: any) => void;
}) => React.JSX.Element;
+13 -3
View File
@@ -24,8 +24,8 @@ const formatTravelTime = (minutes) => {
const mins = minutes % 60;
return mins > 0 ? `${hours} giờ ${mins} phút` : `${hours} giờ`;
};
export const ItineraryTimeline = ({ onAddLocation }) => {
const { currentTour, legs, optimizeRouting, userRole, addLeg, updateLeg, deleteLeg, initializeLegs } = useTourStore();
export const ItineraryTimeline = ({ onAddLocation, onEditLocation }) => {
const { currentTour, legs, optimizeRouting, userRole, addLeg, updateLeg, deleteLeg, initializeLegs, deleteLocation } = useTourStore();
const toggleComplete = async (locationId) => {
console.log("Toggle status for location:", locationId);
};
@@ -58,6 +58,16 @@ export const ItineraryTimeline = ({ onAddLocation }) => {
}
}
};
const handleDeleteLocation = async (id) => {
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) {
alert(err.message);
}
}
};
return (_jsx("div", { className: "max-w-2xl mx-auto p-0 bg-gray-50 min-h-screen", children: _jsxs("div", { className: "px-2 pt-4", children: [legs.length === 0 ? (_jsxs("div", { className: "text-center py-20 bg-white rounded-3xl border-2 border-dashed border-gray-200", children: [_jsx(List, { className: "w-12 h-12 text-gray-300 mx-auto mb-4" }), _jsx("p", { className: "text-gray-500 font-medium", children: "Ch\u01B0a c\u00F3 ch\u1EB7ng n\u00E0o trong l\u1ED9 tr\u00ECnh." })] })) : (legs.map((leg, legIdx) => {
const totalDwellMinutes = leg.locations.reduce((acc, loc) => {
if (loc.plannedStart && loc.plannedEnd) {
@@ -78,7 +88,7 @@ export const ItineraryTimeline = ({ onAddLocation }) => {
: null;
const isStartPoint = legs[0]?.id === leg.id && idx === 0;
const isEndPoint = legs[legs.length - 1]?.id === leg.id && idx === leg.locations.length - 1;
return (_jsxs("div", { children: [_jsxs("div", { className: "relative flex group mb-6", children: [_jsx("div", { className: "z-10 mt-1.5 mr-4", children: _jsx("button", { onClick: () => toggleComplete(location.id), className: `transition-colors duration-200 ${location.status === 'COMPLETED' ? 'text-green-500' : 'text-gray-300 hover:text-blue-500'}`, children: location.status === 'COMPLETED' ? (_jsx(CheckCircle2, { className: "w-8 h-8 bg-white rounded-full" })) : (_jsx(Circle, { className: "w-8 h-8 bg-white rounded-full fill-white" })) }) }), _jsxs("div", { className: `flex-1 bg-white p-4 rounded-xl border transition-all duration-200 ${location.status === 'COMPLETED' ? 'border-green-100 bg-green-50/30' : 'border-gray-100 shadow-sm hover:shadow-md'}`, children: [_jsxs("div", { className: "flex justify-between items-start", children: [_jsxs("div", { children: [isStartPoint && (_jsx("span", { className: "inline-block px-2 py-0.5 bg-green-100 text-green-700 text-[10px] font-bold rounded-md mb-1 uppercase tracking-wider", children: "\u0110i\u1EC3m b\u1EAFt \u0111\u1EA7u" })), isEndPoint && (_jsx("span", { className: "inline-block px-2 py-0.5 bg-red-100 text-red-700 text-[10px] font-bold rounded-md mb-1 uppercase tracking-wider", children: "\u0110i\u1EC3m k\u1EBFt th\u00FAc" })), _jsx("h3", { className: `font-semibold text-lg ${location.status === 'COMPLETED' ? 'text-gray-500 line-through' : 'text-gray-800'}`, children: location.name }), _jsxs("div", { className: "flex items-center text-sm text-gray-500 mt-1", children: [_jsx(MapPin, { className: "w-3 h-3 mr-1" }), _jsx("span", { className: "truncate max-w-[200px] sm:max-w-md", children: location.address })] }), dwellMinutes !== null && (_jsxs("div", { className: "flex items-center text-xs text-amber-600 font-medium mt-1", children: [_jsx(Clock, { className: "w-3 h-3 mr-1" }), _jsxs("span", { children: ["Th\u1EDDi gian d\u1EEBng: ", formatTravelTime(dwellMinutes)] })] }))] }), _jsxs("div", { className: "text-right flex flex-col items-end", children: [_jsxs("div", { className: "flex items-center text-sm font-medium text-blue-600", children: [_jsx(Clock, { className: "w-3 h-3 mr-1" }), location.plannedStart ? format(parseISO(location.plannedStart), 'HH:mm') : '--:--'] }), location.status === 'COMPLETED' && location.actualStart && (_jsxs("div", { className: "text-[10px] text-gray-400 mt-1 italic", children: ["Th\u1EF1c t\u1EBF: ", format(parseISO(location.actualStart), 'HH:mm')] }))] })] }), _jsx(TimeVariance, { planned: location.plannedStart || '', actual: location.actualStart || null })] })] }), distanceToNext !== null && travelTimeMinutes !== null && (_jsxs("div", { className: "ml-4 -mt-4 mb-2 flex items-center gap-3", children: [_jsx("div", { className: "w-8 flex justify-center", children: _jsx(Navigation, { className: "w-3 h-3 text-blue-400 rotate-180" }) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("span", { className: "text-[10px] font-bold text-blue-500 bg-blue-50 px-2 py-0.5 rounded-full border border-blue-100", children: [distanceToNext.toFixed(2), " km"] }), _jsxs("span", { className: "text-[10px] font-bold text-gray-500 bg-gray-50 px-2 py-0.5 rounded-full border border-gray-100 flex items-center gap-1", children: [_jsx(Clock, { className: "w-2.5 h-2.5" }), "~ ", formatTravelTime(travelTimeMinutes)] })] })] }))] }, location.id));
return (_jsxs("div", { children: [_jsxs("div", { className: "relative flex group mb-6", children: [_jsx("div", { className: "z-10 mt-1.5 mr-4", children: _jsx("button", { onClick: () => toggleComplete(location.id), className: `transition-colors duration-200 ${location.status === 'COMPLETED' ? 'text-green-500' : 'text-gray-300 hover:text-blue-500'}`, children: location.status === 'COMPLETED' ? (_jsx(CheckCircle2, { className: "w-8 h-8 bg-white rounded-full" })) : (_jsx(Circle, { className: "w-8 h-8 bg-white rounded-full fill-white" })) }) }), _jsxs("div", { className: `flex-1 bg-white p-4 rounded-xl border transition-all duration-200 ${location.status === 'COMPLETED' ? 'border-green-100 bg-green-50/30' : 'border-gray-100 shadow-sm hover:shadow-md'}`, children: [_jsxs("div", { className: "flex justify-between items-start", children: [_jsxs("div", { children: [isStartPoint && (_jsx("span", { className: "inline-block px-2 py-0.5 bg-green-100 text-green-700 text-[10px] font-bold rounded-md mb-1 uppercase tracking-wider", children: "\u0110i\u1EC3m b\u1EAFt \u0111\u1EA7u" })), isEndPoint && (_jsx("span", { className: "inline-block px-2 py-0.5 bg-red-100 text-red-700 text-[10px] font-bold rounded-md mb-1 uppercase tracking-wider", children: "\u0110i\u1EC3m k\u1EBFt th\u00FAc" })), _jsx("h3", { className: `font-semibold text-lg ${location.status === 'COMPLETED' ? 'text-gray-500 line-through' : 'text-gray-800'}`, children: location.name }), _jsxs("div", { className: "flex items-center text-sm text-gray-500 mt-1", children: [_jsx(MapPin, { className: "w-3 h-3 mr-1" }), _jsx("span", { className: "truncate max-w-[200px] sm:max-w-md", children: location.address })] }), dwellMinutes !== null && (_jsxs("div", { className: "flex items-center text-xs text-amber-600 font-medium mt-1", children: [_jsx(Clock, { className: "w-3 h-3 mr-1" }), _jsxs("span", { children: ["Th\u1EDDi gian d\u1EEBng: ", formatTravelTime(dwellMinutes)] })] }))] }), _jsxs("div", { className: "text-right flex flex-col items-end", children: [_jsxs("div", { className: "flex items-center text-sm font-medium text-blue-600", children: [_jsx(Clock, { className: "w-3 h-3 mr-1" }), location.plannedStart ? format(parseISO(location.plannedStart), 'HH:mm') : '--:--'] }), location.status === 'COMPLETED' && location.actualStart && (_jsxs("div", { className: "text-[10px] text-gray-400 mt-1 italic", children: ["Th\u1EF1c t\u1EBF: ", format(parseISO(location.actualStart), 'HH:mm')] })), ['OWNER', 'MANAGER'].includes(userRole || '') && !isStartPoint && !isEndPoint && (_jsxs("div", { className: "flex gap-1 mt-2", children: [_jsx("button", { onClick: () => onEditLocation?.(location), className: "p-1 text-gray-400 hover:text-blue-600 transition-colors", children: _jsx(Edit2, { className: "w-3.5 h-3.5" }) }), _jsx("button", { onClick: () => handleDeleteLocation(location.id), className: "p-1 text-gray-400 hover:text-red-600 transition-colors", children: _jsx(Trash2, { className: "w-3.5 h-3.5" }) })] }))] })] }), _jsx(TimeVariance, { planned: location.plannedStart || '', actual: location.actualStart || null })] })] }), distanceToNext !== null && travelTimeMinutes !== null && (_jsxs("div", { className: "ml-4 -mt-4 mb-2 flex items-center gap-3", children: [_jsx("div", { className: "w-8 flex justify-center", children: _jsx(Navigation, { className: "w-3 h-3 text-blue-400 rotate-180" }) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs("span", { className: "text-[10px] font-bold text-blue-500 bg-blue-50 px-2 py-0.5 rounded-full border border-blue-100", children: [distanceToNext.toFixed(2), " km"] }), _jsxs("span", { className: "text-[10px] font-bold text-gray-500 bg-gray-50 px-2 py-0.5 rounded-full border border-gray-100 flex items-center gap-1", children: [_jsx(Clock, { className: "w-2.5 h-2.5" }), "~ ", formatTravelTime(travelTimeMinutes)] })] })] }))] }, location.id));
}) })] }, leg.id));
})), ['OWNER', 'MANAGER'].includes(userRole || '') && (_jsxs("div", { className: "flex flex-col gap-3 pb-20 mt-8", children: [_jsxs("button", { onClick: handleDeclareLegs, className: "w-full py-4 rounded-2xl bg-white text-blue-600 border-2 border-dashed border-blue-200 hover:bg-blue-50 transition-all flex items-center justify-center gap-2 text-sm font-bold", children: [_jsx(List, { className: "w-5 h-5" }), legs.length > 0 ? "Khai báo lại số lượng chặng" : "Bắt đầu bằng việc khai báo số chặng"] }), _jsxs("button", { onClick: handleAddLeg, className: "w-full py-4 rounded-2xl bg-blue-600 text-white shadow-lg shadow-blue-100 hover:bg-blue-700 transition-all flex items-center justify-center gap-2 text-sm font-bold", children: [_jsx(Plus, { className: "w-5 h-5" }), " Th\u00EAm ch\u1EB7ng l\u1EBB v\u00E0o cu\u1ED1i"] })] }))] }) }));
};
+1 -1
View File
File diff suppressed because one or more lines are too long
+9 -1
View File
@@ -94,6 +94,7 @@ export const TourDetailPage = ({ onBack }) => {
const [viewMode, setViewMode] = useState('timeline');
const [isAddLocationOpen, setIsAddLocationOpen] = useState(false);
const [targetLegId, setTargetLegId] = useState(null);
const [editingLocation, setEditingLocation] = useState(null);
const [initialViewState] = useState(() => {
const saved = localStorage.getItem('map_view_state');
if (saved) {
@@ -238,6 +239,12 @@ export const TourDetailPage = ({ onBack }) => {
? 'bg-blue-50 text-blue-600 shadow-sm'
: 'text-gray-400 hover:text-gray-500 hover:bg-gray-50'}`, children: [_jsx(tab.icon, { className: `w-4 h-4 mr-2 ${activeTab === tab.id ? 'scale-110' : ''}` }), tab.label] }, tab.id))) }), _jsxs("div", { className: "transition-opacity duration-300", children: [activeTab === 'plan' && (_jsxs("div", { className: "animate-in fade-in slide-in-from-bottom-2", children: [_jsx("div", { className: "flex justify-center mb-6", children: _jsxs("div", { className: "bg-gray-100 p-1 rounded-2xl flex gap-1", children: [_jsxs("button", { onClick: () => setViewMode('timeline'), className: `flex items-center gap-2 px-4 py-2 rounded-xl text-xs font-bold transition-all ${viewMode === 'timeline' ? 'bg-white shadow-sm text-blue-600' : 'text-gray-500'}`, children: [_jsx(List, { className: "w-3.5 h-3.5" }), " Danh s\u00E1ch"] }), _jsxs("button", { onClick: () => setViewMode('map'), className: `flex items-center gap-2 px-4 py-2 rounded-xl text-xs font-bold transition-all ${viewMode === 'map' ? 'bg-white shadow-sm text-blue-600' : 'text-gray-500'}`, children: [_jsx(MapIconLucide, { className: "w-3.5 h-3.5" }), " B\u1EA3n \u0111\u1ED3"] })] }) }), viewMode === 'timeline' ? (_jsx(ItineraryTimeline, { onAddLocation: (legId) => {
setTargetLegId(legId);
setEditingLocation(null);
setIsAddLocationOpen(true);
}, onEditLocation: (loc) => {
setEditingLocation(loc);
setTargetLegId(loc.legId);
setMapCenter([loc.latitude, loc.longitude]);
setIsAddLocationOpen(true);
} })) : (_jsxs("div", { className: "h-[60vh] w-full rounded-3xl overflow-hidden shadow-xl border-4 border-white relative", children: [_jsxs(MapContainer, { center: initialViewState?.center || mapCenter, zoom: mapZoom, className: "h-full w-full", preferCanvas: true, children: [_jsx(TileLayer, { url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" }), _jsx(MapContextMenu, { onAction: handleMapAction }), _jsx(MapTourBounds, { locations: allLocations }), allLocations.length > 1 && (_jsx(Polyline, { positions: allLocations.map(l => [l.latitude, l.longitude]), color: "#3b82f6", weight: 3, dashArray: "5, 10", smoothFactor: 1.5 })), _jsx(MarkerClusterGroup, { chunkedLoading: true, children: legs.flatMap(l => l.locations).map((loc) => {
const isStart = startPoint?.id === loc.id;
@@ -246,8 +253,9 @@ export const TourDetailPage = ({ onBack }) => {
return (_jsx(Marker, { position: [loc.latitude, loc.longitude], icon: icon, children: _jsxs(Popup, { children: [_jsx("div", { className: "font-bold", children: loc.name }), _jsx("div", { className: "text-xs text-gray-500", children: loc.type })] }) }, loc.id));
}) })] }), _jsx("div", { className: "absolute top-4 left-4 z-[1000] bg-white/90 backdrop-blur-md p-3 rounded-2xl text-[10px] font-bold text-gray-500 shadow-lg border border-white", children: "M\u1EB9o: Nh\u1EA5n gi\u1EEF (Mobile) ho\u1EB7c Chu\u1ED9t ph\u1EA3i \u0111\u1EC3 ghim \u0111\u1ECBa \u0111i\u1EC3m" })] }))] })), activeTab === 'expense' && (_jsx("div", { className: "animate-in fade-in slide-in-from-bottom-4", children: _jsx(ExpenseManager, {}) })), activeTab === 'photo' && (_jsx("div", { className: "grid grid-cols-3 gap-1.5 animate-in fade-in", children: [1, 2, 3, 4, 5, 6].map((i) => (_jsxs("div", { className: "aspect-square bg-gray-200 rounded-xl overflow-hidden relative group border border-white", children: [_jsx("div", { className: "absolute inset-0 bg-black/0 group-hover:bg-black/20 transition-colors" }), _jsx("img", { src: `https://picsum.photos/seed/${i + 10}/400/400`, alt: "Tour photo", className: "w-full h-full object-cover group-hover:scale-105 transition-transform duration-500" })] }, i))) })), activeTab === 'settings' && (_jsxs("div", { className: "p-8 text-center bg-white rounded-3xl border border-dashed border-gray-200 animate-in zoom-in-95", children: [_jsx(Settings, { className: "w-12 h-12 text-gray-300 mx-auto mb-4" }), _jsx("p", { className: "text-gray-500 font-medium", children: "T\u00EDnh n\u0103ng qu\u1EA3n l\u00FD th\u00E0nh vi\u00EAn \u0111ang \u0111\u01B0\u1EE3c c\u1EADp nh\u1EADt..." })] }))] })] }), _jsx("div", { className: "fixed bottom-6 left-1/2 -translate-x-1/2 z-40", children: _jsx("button", { onClick: () => {
setTargetLegId(null);
setEditingLocation(null);
if (activeTab === 'plan')
setIsAddLocationOpen(true);
}, className: "bg-blue-600 text-white px-6 py-3 rounded-full shadow-lg hover:bg-blue-700 transition-all flex items-center font-bold", children: activeTab === 'plan' ? 'Thêm địa điểm' : activeTab === 'expense' ? 'Thêm chi phí' : 'Đăng ảnh' }) }), currentTour && (_jsx(AddLocationModal, { isOpen: isAddLocationOpen, onClose: () => setIsAddLocationOpen(false), initialLegId: targetLegId || undefined, tourId: currentTour.id }))] }));
}, className: "bg-blue-600 text-white px-6 py-3 rounded-full shadow-lg hover:bg-blue-700 transition-all flex items-center font-bold", children: activeTab === 'plan' ? 'Thêm địa điểm' : activeTab === 'expense' ? 'Thêm chi phí' : 'Đăng ảnh' }) }), currentTour && (_jsx(AddLocationModal, { isOpen: isAddLocationOpen, onClose: () => setIsAddLocationOpen(false), initialLegId: targetLegId || undefined, editingLocation: editingLocation, tourId: currentTour.id }))] }));
};
//# sourceMappingURL=TourDetailPage.js.map
+1 -1
View File
File diff suppressed because one or more lines are too long
+45 -1
View File
@@ -391,6 +391,50 @@ TourController = __decorate([
Controller('v1/tours'),
__metadata("design:paramtypes", [PrismaService])
], TourController);
let LocationController = class LocationController {
constructor(prisma) {
this.prisma = prisma;
}
async updateLocation(id, body) {
return this.prisma.location.update({
where: { id },
data: {
name: body.name,
address: body.address,
latitude: body.latitude,
longitude: body.longitude,
type: body.type,
plannedStart: body.plannedStart ? new Date(body.plannedStart) : undefined,
plannedEnd: body.plannedEnd ? new Date(body.plannedEnd) : undefined,
status: body.status,
}
});
}
async deleteLocation(id) {
await this.prisma.location.delete({ where: { id } });
return { success: true };
}
};
__decorate([
Patch(':id'),
__param(0, Param('id', ParseUUIDPipe)),
__param(1, Body()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, Object]),
__metadata("design:returntype", Promise)
], LocationController.prototype, "updateLocation", null);
__decorate([
Delete(':id'),
__param(0, Param('id', ParseUUIDPipe)),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String]),
__metadata("design:returntype", Promise)
], LocationController.prototype, "deleteLocation", null);
LocationController = __decorate([
Controller('v1/locations'),
UseGuards(JwtAuthGuard),
__metadata("design:paramtypes", [PrismaService])
], LocationController);
let LegController = class LegController {
constructor(prisma) {
this.prisma = prisma;
@@ -620,7 +664,7 @@ AppModule = __decorate([
signOptions: { expiresIn: '1d' },
}),
],
controllers: [AppController, AuthController, TourController, UserController, RoutingController, LegController],
controllers: [AppController, AuthController, TourController, UserController, RoutingController, LegController, LocationController],
providers: [PrismaService, JwtStrategy],
exports: [PrismaService]
})
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+2
View File
@@ -15,6 +15,8 @@ interface TourState {
updateLeg: (legId: string, data: any) => Promise<void>;
deleteLeg: (legId: string) => Promise<void>;
addLocation: (tourId: string, locationData: any) => Promise<void>;
updateLocation: (locationId: string, data: any) => Promise<void>;
deleteLocation: (locationId: string) => Promise<void>;
updateTourStartPoint: (tourId: string, data: any) => Promise<void>;
updateTourEndPoint: (tourId: string, data: any) => Promise<void>;
optimizeRouting: (legId: string) => Promise<void>;
+30
View File
@@ -145,6 +145,36 @@ export const useTourStore = create((set, get) => ({
throw new Error('Lỗi khi thêm địa điểm');
get().fetchTour(tourId);
},
updateLocation: async (locationId, data) => {
const API_BASE = `http://${window.location.hostname}:3001`;
const response = await fetch(`${API_BASE}/api/v1/locations/${locationId}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify(data),
});
if (!response.ok)
throw new Error('Lỗi khi cập nhật địa điểm');
const { currentTour } = get();
if (currentTour)
get().fetchTour(currentTour.id);
},
deleteLocation: async (locationId) => {
const API_BASE = `http://${window.location.hostname}:3001`;
const response = await fetch(`${API_BASE}/api/v1/locations/${locationId}`, {
method: 'DELETE',
headers: {
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
});
if (!response.ok)
throw new Error('Lỗi khi xóa địa điểm');
const { currentTour } = get();
if (currentTour)
get().fetchTour(currentTour.id);
},
updateTourStartPoint: async (tourId, data) => {
const API_BASE = `http://${window.location.hostname}:3001`;
const response = await fetch(`${API_BASE}/api/v1/tours/${tourId}/start-point`, {
+1 -1
View File
File diff suppressed because one or more lines are too long