Tạo UI cho người dùng tạo Tour

This commit is contained in:
2026-06-13 20:46:31 +07:00
parent c51ddc34c7
commit 25fcd5d926
28 changed files with 767 additions and 76 deletions
+6
View File
@@ -0,0 +1,6 @@
import React from 'react';
export declare const AddLocationModal: ({ isOpen, onClose, tourId }: {
isOpen: boolean;
onClose: () => void;
tourId: string;
}) => React.JSX.Element;
+39
View File
@@ -0,0 +1,39 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState } from 'react';
import { X, MapPin, Loader2 } from 'lucide-react';
import { useTourStore } from './useTourStore.js';
export const AddLocationModal = ({ isOpen, onClose, tourId }) => {
const [formData, setFormData] = useState({
name: '',
address: '',
latitude: 10.7769,
longitude: 106.7009,
type: 'VISIT',
plannedStart: '',
plannedEnd: ''
});
const [isLoading, setIsLoading] = useState(false);
const addLocation = useTourStore(state => state.addLocation);
if (!isOpen)
return null;
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoading(true);
try {
await addLocation(tourId, {
...formData,
latitude: parseFloat(formData.latitude),
longitude: parseFloat(formData.longitude),
});
onClose();
}
catch (error) {
alert('Lỗi khi thêm địa điểm');
}
finally {
setIsLoading(false);
}
};
return (_jsxs("div", { className: "fixed inset-0 z-[2000] flex items-center justify-center p-4", children: [_jsx("div", { className: "absolute inset-0 bg-gray-900/60 backdrop-blur-sm", onClick: onClose }), _jsxs("div", { className: "relative w-full max-w-lg bg-white rounded-3xl shadow-2xl overflow-hidden p-8 max-h-[90vh] overflow-y-auto", children: [_jsxs("div", { className: "flex justify-between items-center mb-6", children: [_jsxs("h2", { className: "text-2xl font-bold text-gray-900 flex items-center gap-2", children: [_jsx(MapPin, { className: "w-6 h-6 text-blue-600" }), " Th\u00EAm \u0111\u1ECBa \u0111i\u1EC3m m\u1EDBi"] }), _jsx("button", { onClick: onClose, className: "p-2 hover:bg-gray-100 rounded-full transition-colors", children: _jsx(X, { className: "w-6 h-6 text-gray-400" }) })] }), _jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-sm font-bold text-gray-700 mb-1", children: "T\u00EAn \u0111\u1ECBa \u0111i\u1EC3m" }), _jsx("input", { required: true, className: "w-full px-4 py-3 bg-gray-50 border border-gray-100 rounded-xl outline-none", value: formData.name, onChange: e => setFormData({ ...formData, name: e.target.value }) })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-sm font-bold text-gray-700 mb-1", children: "\u0110\u1ECBa ch\u1EC9" }), _jsx("input", { className: "w-full px-4 py-3 bg-gray-50 border border-gray-100 rounded-xl outline-none", value: formData.address, onChange: e => setFormData({ ...formData, address: e.target.value }) })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-sm font-bold text-gray-700 mb-1", children: "V\u0129 \u0111\u1ED9" }), _jsx("input", { type: "number", step: "any", required: true, className: "w-full px-4 py-3 bg-gray-50 border border-gray-100 rounded-xl outline-none", value: formData.latitude, onChange: e => setFormData({ ...formData, latitude: e.target.value }) })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-sm font-bold text-gray-700 mb-1", children: "Kinh \u0111\u1ED9" }), _jsx("input", { type: "number", step: "any", required: true, className: "w-full px-4 py-3 bg-gray-50 border border-gray-100 rounded-xl outline-none", value: formData.longitude, onChange: e => setFormData({ ...formData, longitude: e.target.value }) })] })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-sm font-bold text-gray-700 mb-1", children: "Lo\u1EA1i" }), _jsxs("select", { className: "w-full px-4 py-3 bg-gray-50 border border-gray-100 rounded-xl outline-none", value: formData.type, onChange: e => setFormData({ ...formData, type: e.target.value }), children: [_jsx("option", { value: "VISIT", children: "Tham quan" }), _jsx("option", { value: "EAT", children: "\u0102n u\u1ED1ng" }), _jsx("option", { value: "REST", children: "Ngh\u1EC9 ng\u01A1i" }), _jsx("option", { value: "MOVE", children: "Di chuy\u1EC3n" })] })] }), _jsx("div", { className: "grid grid-cols-2 gap-4", children: _jsxs("div", { children: [_jsx("label", { className: "block text-sm font-bold text-gray-700 mb-1", children: "B\u1EAFt \u0111\u1EA7u" }), _jsx("input", { type: "datetime-local", className: "w-full px-4 py-3 bg-gray-50 border border-gray-100 rounded-xl outline-none", value: formData.plannedStart, onChange: e => setFormData({ ...formData, plannedStart: e.target.value }) })] }) }), _jsx("button", { disabled: isLoading, className: "w-full py-4 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-2xl flex items-center justify-center gap-2 mt-4", children: isLoading ? _jsx(Loader2, { className: "w-5 h-5 animate-spin" }) : 'Thêm địa điểm' })] })] })] }));
};
//# sourceMappingURL=AddLocationModal.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"AddLocationModal.js","sourceRoot":"","sources":["../AddLocationModal.tsx"],"names":[],"mappings":"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"}
Vendored
+6 -1
View File
@@ -4,11 +4,13 @@ import { LandingPage } from './LandingPage.js';
import { TourDetailPage } from './TourDetailPage.js';
import { ExploreMap } from './ExploreMap.js';
import { SignupPage } from './SignupPage.js';
import { useTourStore } from './useTourStore.js';
const App = () => {
const [view, setView] = useState('landing');
const [isInitialSetup, setIsInitialSetup] = useState(false);
const [user, setUser] = useState(null);
const [isUserLoaded, setIsUserLoaded] = useState(false);
const fetchTour = useTourStore(state => state.fetchTour);
useEffect(() => {
const API_BASE = `http://${window.location.hostname}:3001`;
const savedUser = localStorage.getItem('user');
@@ -36,7 +38,10 @@ const App = () => {
setUser(null);
setView('landing');
};
return (_jsxs("div", { className: "app-container", children: [view === 'landing' && (_jsx(LandingPage, { isInitialSetup: isInitialSetup, onContinue: () => setView('explore'), onGoToSignup: () => setView('signup'), onGoToMap: () => setView('explore'), onLoginSuccess: handleLoginSuccess })), view === 'signup' && (_jsx(SignupPage, { onBack: () => setView('landing'), onSuccess: () => setView('landing') })), view === 'explore' && (_jsx(ExploreMap, { onBack: () => setView('landing'), onLogout: user ? handleLogout : undefined, user: user })), view === 'detail' && (_jsx(TourDetailPage, {}))] }));
return (_jsxs("div", { className: "app-container", children: [view === 'landing' && (_jsx(LandingPage, { isInitialSetup: isInitialSetup, onContinue: () => setView('explore'), onGoToSignup: () => setView('signup'), onGoToMap: () => setView('explore'), onLoginSuccess: handleLoginSuccess })), view === 'signup' && (_jsx(SignupPage, { onBack: () => setView('landing'), onSuccess: () => setView('landing') })), view === 'explore' && (_jsx(ExploreMap, { onBack: () => setView('landing'), onLogout: user ? handleLogout : undefined, user: user, onViewTour: (id) => {
fetchTour(id);
setView('detail');
} })), view === 'detail' && (_jsx(TourDetailPage, { onBack: () => setView('explore') }))] }));
};
export default App;
//# sourceMappingURL=App.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"App.js","sourceRoot":"","sources":["../App.tsx"],"names":[],"mappings":"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"}
{"version":3,"file":"App.js","sourceRoot":"","sources":["../App.tsx"],"names":[],"mappings":"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"}
+6
View File
@@ -0,0 +1,6 @@
import React from 'react';
export declare const CreateTourModal: ({ isOpen, onClose, onSuccess }: {
isOpen: boolean;
onClose: () => void;
onSuccess: (tour: any) => void;
}) => React.JSX.Element;
+30
View File
@@ -0,0 +1,30 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState } from 'react';
import { X, Map as MapIcon, Loader2 } from 'lucide-react';
import { useTourStore } from './useTourStore.js';
export const CreateTourModal = ({ isOpen, onClose, onSuccess }) => {
const [title, setTitle] = useState('');
const [startDate, setStartDate] = useState('');
const [endDate, setEndDate] = useState('');
const [isLoading, setIsLoading] = useState(false);
const createTour = useTourStore(state => state.createTour);
if (!isOpen)
return null;
const handleSubmit = async (e) => {
e.preventDefault();
setIsLoading(true);
try {
const tour = await createTour({ title, startDate, endDate });
onSuccess(tour);
onClose();
}
catch (error) {
alert('Lỗi khi tạo tour');
}
finally {
setIsLoading(false);
}
};
return (_jsxs("div", { className: "fixed inset-0 z-[2000] flex items-center justify-center p-4", children: [_jsx("div", { className: "absolute inset-0 bg-gray-900/60 backdrop-blur-sm", onClick: onClose }), _jsxs("div", { className: "relative w-full max-w-md bg-white rounded-3xl shadow-2xl overflow-hidden p-8", children: [_jsxs("div", { className: "flex justify-between items-center mb-6", children: [_jsxs("h2", { className: "text-2xl font-bold text-gray-900 flex items-center gap-2", children: [_jsx(MapIcon, { className: "w-6 h-6 text-blue-600" }), " T\u1EA1o Tour m\u1EDBi"] }), _jsx("button", { onClick: onClose, className: "p-2 hover:bg-gray-100 rounded-full transition-colors", children: _jsx(X, { className: "w-6 h-6 text-gray-400" }) })] }), _jsxs("form", { onSubmit: handleSubmit, className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-sm font-bold text-gray-700 mb-1", children: "T\u00EAn Tour" }), _jsx("input", { required: true, className: "w-full px-4 py-3 bg-gray-50 border border-gray-100 rounded-xl outline-none focus:ring-2 focus:ring-blue-500", value: title, onChange: e => setTitle(e.target.value), placeholder: "VD: Kh\u00E1m ph\u00E1 \u0110\u00E0 L\u1EA1t" })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-sm font-bold text-gray-700 mb-1", children: "B\u1EAFt \u0111\u1EA7u" }), _jsx("input", { type: "date", className: "w-full px-4 py-3 bg-gray-50 border border-gray-100 rounded-xl outline-none focus:ring-2 focus:ring-blue-500", value: startDate, onChange: e => setStartDate(e.target.value) })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-sm font-bold text-gray-700 mb-1", children: "K\u1EBFt th\u00FAc" }), _jsx("input", { type: "date", className: "w-full px-4 py-3 bg-gray-50 border border-gray-100 rounded-xl outline-none focus:ring-2 focus:ring-blue-500", value: endDate, onChange: e => setEndDate(e.target.value) })] })] }), _jsx("button", { disabled: isLoading, className: "w-full py-4 bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-2xl shadow-lg transition-all flex items-center justify-center gap-2", children: isLoading ? _jsx(Loader2, { className: "w-5 h-5 animate-spin" }) : 'Xác nhận tạo Tour' })] })] })] }));
};
//# sourceMappingURL=CreateTourModal.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"CreateTourModal.js","sourceRoot":"","sources":["../CreateTourModal.tsx"],"names":[],"mappings":"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"}
+2 -1
View File
@@ -1,7 +1,8 @@
import React from 'react';
import 'leaflet/dist/leaflet.css';
export declare const ExploreMap: ({ onBack, onLogout, user }: {
export declare const ExploreMap: ({ onBack, onLogout, user, onViewTour }: {
onBack: () => void;
onLogout?: () => void;
user?: any;
onViewTour: (id: string) => void;
}) => React.JSX.Element;
+10 -5
View File
@@ -6,6 +6,7 @@ import 'leaflet/dist/leaflet.css';
import { useTourStore } from './useTourStore.js';
import { X, Navigation, Image as ImageIcon, LogOut, Settings } from 'lucide-react';
import { UserManagementModal } from './UserManagementModal.js';
import { CreateTourModal } from './CreateTourModal.js';
const DefaultIcon = L.icon({
iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png',
shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png',
@@ -20,15 +21,16 @@ function RecenterMap({ position }) {
}, [position, map]);
return null;
}
export const ExploreMap = ({ onBack, onLogout, user }) => {
const { publicTours, fetchPublicTours } = useTourStore();
export const ExploreMap = ({ onBack, onLogout, user, onViewTour }) => {
const { publicTours, fetchPublicTours, fetchTour } = useTourStore();
const [userPos, setUserPos] = useState([10.7769, 106.7009]);
const [isAdminModalOpen, setIsAdminModalOpen] = useState(false);
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
useEffect(() => {
fetchPublicTours();
navigator.geolocation.getCurrentPosition((pos) => setUserPos([pos.coords.latitude, pos.coords.longitude]), () => console.log("Không thể lấy vị trí người dùng"));
}, []);
return (_jsxs("div", { className: "h-screen w-full relative", children: [_jsx("button", { onClick: onBack, className: "absolute top-6 left-6 z-[1000] bg-white p-3 rounded-full shadow-xl hover:bg-gray-50 transition-all", children: _jsx(X, { className: "w-6 h-6 text-gray-800" }) }), onLogout && (_jsxs("button", { onClick: onLogout, className: "absolute top-6 right-6 z-[1000] bg-white px-4 py-3 rounded-2xl shadow-xl hover:bg-red-50 hover:text-red-600 transition-all flex items-center gap-2 font-bold text-gray-700", children: [_jsx(LogOut, { className: "w-5 h-5" }), _jsx("span", { className: "hidden sm:inline", children: "\u0110\u0103ng xu\u1EA5t" })] })), user?.isAdmin && (_jsxs("button", { onClick: () => setIsAdminModalOpen(true), className: "absolute top-6 right-40 z-[1000] bg-blue-600 px-4 py-3 rounded-2xl shadow-xl hover:bg-blue-700 text-white transition-all flex items-center gap-2 font-bold", children: [_jsx(Settings, { className: "w-5 h-5" }), _jsx("span", { className: "hidden sm:inline", children: "Qu\u1EA3n l\u00FD h\u1EC7 th\u1ED1ng" })] })), _jsx("div", { className: "absolute top-6 left-20 z-[1000] bg-white/90 backdrop-blur-md px-6 py-3 rounded-2xl shadow-xl border border-white/20 hidden sm:block", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Navigation, { className: "w-4 h-4 text-blue-600" }), _jsx("span", { className: "font-bold text-gray-800", children: "\u0110ang kh\u00E1m ph\u00E1 khu v\u1EF1c c\u1EE7a b\u1EA1n" })] }) }), _jsxs(MapContainer, { center: userPos, zoom: 13, className: "h-full w-full", children: [_jsx(TileLayer, { url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", attribution: '\u00A9 OpenStreetMap contributors' }), _jsx(RecenterMap, { position: userPos }), publicTours.map((tour) => {
return (_jsxs("div", { className: "h-screen w-full relative", children: [_jsx("button", { onClick: onBack, className: "absolute top-6 left-6 z-[1000] bg-white p-3 rounded-full shadow-xl hover:bg-gray-50 transition-all", children: _jsx(X, { className: "w-6 h-6 text-gray-800" }) }), onLogout && (_jsxs("button", { onClick: onLogout, className: "absolute top-6 right-6 z-[1000] bg-white px-4 py-3 rounded-2xl shadow-xl hover:bg-red-50 hover:text-red-600 transition-all flex items-center gap-2 font-bold text-gray-700", children: [_jsx(LogOut, { className: "w-5 h-5" }), _jsx("span", { className: "hidden sm:inline", children: "\u0110\u0103ng xu\u1EA5t" })] })), user?.isAdmin && (_jsxs("button", { onClick: () => setIsAdminModalOpen(true), className: "absolute top-6 right-40 z-[1000] bg-blue-600 px-4 py-3 rounded-2xl shadow-xl hover:bg-blue-700 text-white transition-all flex items-center gap-2 font-bold", children: [_jsx(Settings, { className: "w-5 h-5" }), _jsx("span", { className: "hidden sm:inline", children: "Qu\u1EA3n l\u00FD h\u1EC7 th\u1ED1ng" })] })), user && (_jsxs("button", { onClick: () => setIsCreateModalOpen(true), className: "absolute top-6 right-80 z-[1000] bg-green-600 px-4 py-3 rounded-2xl shadow-xl hover:bg-green-700 text-white transition-all flex items-center gap-2 font-bold", children: [_jsx(Navigation, { className: "w-5 h-5" }), _jsx("span", { className: "hidden sm:inline", children: "T\u1EA1o Tour m\u1EDBi" })] })), _jsx("div", { className: "absolute top-6 left-20 z-[1000] bg-white/90 backdrop-blur-md px-6 py-3 rounded-2xl shadow-xl border border-white/20 hidden sm:block", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Navigation, { className: "w-4 h-4 text-blue-600" }), _jsx("span", { className: "font-bold text-gray-800", children: "\u0110ang kh\u00E1m ph\u00E1 khu v\u1EF1c c\u1EE7a b\u1EA1n" })] }) }), _jsxs(MapContainer, { center: userPos, zoom: 13, className: "h-full w-full", children: [_jsx(TileLayer, { url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", attribution: '\u00A9 OpenStreetMap contributors' }), _jsx(RecenterMap, { position: userPos }), publicTours.map((tour) => {
const location = tour.legs?.[0]?.locations?.[0];
if (!location)
return null;
@@ -46,7 +48,10 @@ export const ExploreMap = ({ onBack, onLogout, user }) => {
</div>
`,
iconSize: [48, 48],
}), children: _jsx(Popup, { className: "custom-popup", children: _jsxs("div", { className: "p-1 max-w-[200px]", children: [_jsx("img", { src: tourImage, className: "w-full h-32 object-cover rounded-lg mb-2" }), _jsx("h4", { className: "font-bold text-gray-900 leading-tight mb-1", children: tour.title }), _jsxs("button", { className: "text-xs font-bold text-blue-600 flex items-center gap-1", children: ["Xem chi ti\u1EBFt h\u00ECnh \u1EA3nh ", _jsx(ImageIcon, { className: "w-3 h-3" })] })] }) }) }, tour.id));
})] }), _jsx(UserManagementModal, { isOpen: isAdminModalOpen, onClose: () => setIsAdminModalOpen(false) })] }));
}), children: _jsx(Popup, { className: "custom-popup", children: _jsxs("div", { className: "p-1 max-w-[200px]", children: [_jsx("img", { src: tourImage, className: "w-full h-32 object-cover rounded-lg mb-2" }), _jsx("h4", { className: "font-bold text-gray-900 leading-tight mb-1", children: tour.title }), _jsxs("button", { onClick: () => onViewTour(tour.id), className: "text-xs font-bold text-blue-600 flex items-center gap-1", children: ["Xem chi ti\u1EBFt h\u00ECnh \u1EA3nh ", _jsx(ImageIcon, { className: "w-3 h-3" })] })] }) }) }, tour.id));
})] }), _jsx(UserManagementModal, { isOpen: isAdminModalOpen, onClose: () => setIsAdminModalOpen(false) }), _jsx(CreateTourModal, { isOpen: isCreateModalOpen, onClose: () => setIsCreateModalOpen(false), onSuccess: (tour) => {
fetchTour(tour.id);
onViewTour(tour.id);
} })] }));
};
//# sourceMappingURL=ExploreMap.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"ExploreMap.js","sourceRoot":"","sources":["../ExploreMap.tsx"],"names":[],"mappings":"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"}
{"version":3,"file":"ExploreMap.js","sourceRoot":"","sources":["../ExploreMap.tsx"],"names":[],"mappings":"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"}
+3 -1
View File
@@ -1,2 +1,4 @@
import React from 'react';
export declare const TourDetailPage: () => React.JSX.Element;
export declare const TourDetailPage: ({ onBack }: {
onBack: () => void;
}) => React.JSX.Element;
+23 -16
View File
@@ -1,11 +1,13 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect } from 'react';
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useState, useEffect, useMemo } from 'react';
import { ItineraryTimeline } from './ItineraryTimeline.js';
import { ExpenseManager } from './ExpenseManager.js';
import { useTourStore } from './useTourStore.js';
import { Map as MapIcon, Wallet, Image as ImageIcon, Calendar, Users, ChevronLeft } from 'lucide-react';
export const TourDetailPage = () => {
import { AddLocationModal } from './AddLocationModal.js';
import { Map as MapIcon, Wallet, Image as ImageIcon, Calendar, Users, ChevronLeft, Settings, Quote, Plus } from 'lucide-react';
export const TourDetailPage = ({ onBack }) => {
const [activeTab, setActiveTab] = useState('plan');
const [isAddLocationOpen, setIsAddLocationOpen] = useState(false);
const { currentTour, fetchTour, fetchPublicTours, publicTours, userRole } = useTourStore();
useEffect(() => {
const loadData = async () => {
@@ -21,23 +23,28 @@ export const TourDetailPage = () => {
}
}, [publicTours, currentTour, fetchTour]);
const tabs = [
{ id: 'plan', label: 'Lộ trình', icon: MapIcon, visible: ['OWNER', 'EDITOR', 'MEMBER_PLAN_ONLY'].includes(userRole || '') },
{ id: 'expense', label: 'Chi phí', icon: Wallet, visible: ['OWNER', 'EDITOR'].includes(userRole || '') },
{ 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: 'settings', label: 'Cài đặt', icon: Settings, visible: ['OWNER', 'MANAGER'].includes(userRole || '') },
].filter(t => t.visible);
useEffect(() => {
if (userRole === 'MEMBER_PHOTO_ONLY') {
setActiveTab('photo');
}
}, [userRole]);
const hasFinanceAccess = ['OWNER', 'MANAGER', 'MEMBER'].includes(userRole || '');
const travelQuotes = [
"Đừng nghe họ nói, hãy tự mình đi xem.",
"Thế giới là một cuốn sách, và ai không đi du lịch thì chỉ mới đọc được một trang.",
"Hành trình ngàn dặm bắt đầu từ một bước chân.",
"Đi là để trở về, nhưng với một tâm hồn mới."
];
const randomQuote = useMemo(() => travelQuotes[Math.floor(Math.random() * travelQuotes.length)], []);
const tourInfo = {
title: currentTour?.title || "Hành trình khám phá TP.HCM",
date: "20 - 21 Tháng 11, 2023",
members: 5,
budget: "2.500.000 VND"
date: currentTour?.startDate ? `${new Date(currentTour.startDate).toLocaleDateString('vi-VN')} - ${new Date(currentTour.endDate).toLocaleDateString('vi-VN')}` : "Chưa xác định ngày",
membersCount: currentTour?.participants?.length || 0,
budget: currentTour?.totalCost ? `${Number(currentTour.totalCost).toLocaleString()} VND` : "0 VND",
coverImage: currentTour?.photos?.[0]?.imageUrl || "https://images.unsplash.com/photo-1476514525535-07fb3b4ae5f1?q=80&w=2070&auto=format&fit=crop"
};
return (_jsxs("div", { className: "min-h-screen bg-white", children: [_jsxs("div", { className: "sticky top-0 z-30 bg-white border-b border-gray-100 px-4 py-3 flex items-center justify-between", children: [_jsx("button", { className: "p-2 hover:bg-gray-100 rounded-full transition-colors", children: _jsx(ChevronLeft, { className: "w-6 h-6 text-gray-600" }) }), _jsx("h1", { className: "text-lg font-bold text-gray-800 truncate px-4", children: tourInfo.title }), _jsx("div", { className: "w-10" }), " "] }), _jsx("div", { className: "bg-blue-600 text-white p-6 pb-20", children: _jsxs("div", { className: "max-w-2xl mx-auto space-y-4", children: [_jsxs("div", { className: "flex flex-wrap gap-4 text-sm opacity-90", children: [_jsxs("div", { className: "flex items-center", children: [_jsx(Calendar, { className: "w-4 h-4 mr-1.5" }), tourInfo.date] }), _jsxs("div", { className: "flex items-center", children: [_jsx(Users, { className: "w-4 h-4 mr-1.5" }), tourInfo.members, " th\u00E0nh vi\u00EAn"] })] }), _jsxs("div", { className: "flex items-baseline gap-2", children: [_jsx("span", { className: "text-3xl font-bold", children: tourInfo.budget }), _jsx("span", { className: "text-blue-100 text-sm", children: "d\u1EF1 ki\u1EBFn" })] })] }) }), _jsxs("div", { className: "max-w-2xl mx-auto -mt-12 px-4 pb-24", children: [_jsx("div", { className: "bg-white rounded-2xl shadow-xl border border-gray-100 p-1 flex mb-6", children: tabs.map((tab) => (_jsxs("button", { onClick: () => setActiveTab(tab.id), className: `flex-1 flex items-center justify-center py-3 rounded-xl text-sm font-semibold transition-all ${activeTab === tab.id
return (_jsxs("div", { className: "min-h-screen bg-gray-50 pb-20", children: [_jsxs("div", { className: "sticky top-0 z-30 bg-white/80 backdrop-blur-md border-b border-gray-100 px-4 py-3 flex items-center justify-between", children: [_jsx("button", { onClick: onBack, className: "p-2 hover:bg-gray-100 rounded-full transition-colors", children: _jsx(ChevronLeft, { className: "w-6 h-6 text-gray-600" }) }), _jsx("h1", { className: "text-lg font-bold text-gray-800 truncate px-4", children: tourInfo.title }), _jsx("div", { className: "w-10" }), " "] }), _jsxs("div", { className: "relative h-64 w-full bg-blue-900", children: [_jsx("img", { src: tourInfo.coverImage, className: "w-full h-full object-cover opacity-60", alt: "Tour Cover" }), _jsx("div", { className: "absolute inset-0 bg-gradient-to-t from-black/80 via-black/20 to-transparent" }), _jsx("div", { className: "absolute bottom-16 left-0 right-0 p-6 text-white", children: _jsxs("div", { className: "max-w-2xl mx-auto space-y-2", children: [_jsx("h2", { className: "text-3xl font-black tracking-tight drop-shadow-md", children: tourInfo.title }), _jsxs("div", { className: "flex flex-wrap gap-4 text-sm font-medium opacity-90", children: [_jsxs("div", { className: "flex items-center bg-black/20 backdrop-blur-sm px-3 py-1 rounded-full border border-white/10", children: [_jsx(Calendar, { className: "w-4 h-4 mr-1.5" }), tourInfo.date] }), _jsxs("div", { className: "flex items-center bg-black/20 backdrop-blur-sm px-3 py-1 rounded-full border border-white/10", children: [_jsx(Users, { className: "w-4 h-4 mr-1.5" }), tourInfo.membersCount, " th\u00E0nh vi\u00EAn"] })] }), _jsxs("div", { className: "flex items-center gap-2 mt-4", children: [_jsxs("div", { className: "flex -space-x-3", children: [currentTour?.participants?.slice(0, 5).map((p, i) => (_jsx("div", { className: "w-10 h-10 rounded-full border-2 border-white bg-blue-100 flex items-center justify-center text-blue-600 font-bold overflow-hidden shadow-lg", children: _jsx("img", { src: `https://i.pravatar.cc/100?u=${p.userId}`, alt: "Avatar" }) }, i))), tourInfo.membersCount > 5 && (_jsxs("div", { className: "w-10 h-10 rounded-full border-2 border-white bg-gray-800 flex items-center justify-center text-[10px] font-bold text-white shadow-lg", children: ["+", tourInfo.membersCount - 5] }))] }), _jsx("button", { className: "p-2 bg-white/10 hover:bg-white/20 rounded-full border border-white/20 backdrop-blur-sm transition-all ml-2", children: _jsx(Plus, { className: "w-4 h-4" }) })] })] }) })] }), _jsx("div", { className: "max-w-2xl mx-auto -mt-10 px-4 relative z-10", children: _jsx("div", { className: `rounded-3xl p-6 shadow-2xl transition-all duration-500 ${hasFinanceAccess ? 'bg-indigo-600 text-white shadow-indigo-200' : 'bg-white text-gray-600 border border-gray-100'}`, children: _jsx("div", { className: "flex justify-between items-center", children: hasFinanceAccess ? (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("p", { className: "text-indigo-100 text-xs font-black uppercase tracking-widest mb-1", children: "T\u1ED5ng chi ti\u00EAu hi\u1EC7n t\u1EA1i" }), _jsx("h3", { className: "text-3xl font-black", children: tourInfo.budget })] }), _jsx("div", { className: "p-4 bg-white/10 rounded-2xl backdrop-blur-md", children: _jsx(Wallet, { className: "w-8 h-8" }) })] })) : (_jsxs("div", { className: "flex items-start gap-4 py-2", children: [_jsx(Quote, { className: "w-8 h-8 text-blue-500 opacity-30 flex-shrink-0" }), _jsxs("p", { className: "italic text-lg font-medium leading-relaxed", children: ["\"", randomQuote, "\""] })] })) }) }) }), _jsxs("div", { className: "max-w-2xl mx-auto mt-6 px-4 pb-24", children: [_jsx("div", { className: "bg-white rounded-2xl shadow-lg border border-gray-100 p-1 flex mb-6 sticky top-20 z-20", children: tabs.map((tab) => (_jsxs("button", { onClick: () => setActiveTab(tab.id), className: `flex-1 flex items-center justify-center py-3 rounded-xl text-sm font-semibold transition-all ${activeTab === tab.id
? 'bg-blue-50 text-blue-600 shadow-sm'
: 'text-gray-400 hover:text-gray-600'}`, children: [_jsx(tab.icon, { className: `w-4 h-4 mr-2 ${activeTab === tab.id ? 'animate-pulse' : ''}` }), tab.label] }, tab.id))) }), _jsxs("div", { className: "transition-opacity duration-300", children: [activeTab === 'plan' && (_jsx("div", { className: "animate-in fade-in slide-in-from-bottom-4", children: _jsx(ItineraryTimeline, {}) })), 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-2 animate-in fade-in", children: [1, 2, 3, 4, 5, 6].map((i) => (_jsxs("div", { className: "aspect-square bg-gray-100 rounded-lg overflow-hidden relative group", 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" })] }, i))) }))] })] }), _jsx("div", { className: "fixed bottom-6 left-1/2 -translate-x-1/2 z-40", children: _jsx("button", { 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' }) })] }));
: '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' && (_jsx("div", { className: "animate-in fade-in slide-in-from-bottom-4", children: _jsx(ItineraryTimeline, {}) })), 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: () => activeTab === 'plan' ? setIsAddLocationOpen(true) : null, 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), tourId: currentTour.id }))] }));
};
//# sourceMappingURL=TourDetailPage.js.map
+1 -1
View File
File diff suppressed because one or more lines are too long
+59 -1
View File
@@ -12,7 +12,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
};
import 'reflect-metadata';
import { NestFactory } from '@nestjs/core';
import { Module, Controller, Get, Post, Body, Param, Patch, Delete, ParseUUIDPipe, NotFoundException, BadRequestException, UnauthorizedException, UseGuards } from '@nestjs/common';
import { Module, Controller, Get, Post, Body, Param, Patch, Delete, ParseUUIDPipe, NotFoundException, BadRequestException, UnauthorizedException, UseGuards, Req } from '@nestjs/common';
import { PrismaService } from './prisma.service.js';
import 'dotenv/config';
import * as bcrypt from 'bcrypt';
@@ -106,6 +106,46 @@ let TourController = class TourController {
constructor(prisma) {
this.prisma = prisma;
}
async createTour(body, req) {
const { title, startDate, endDate } = body;
return this.prisma.tour.create({
data: {
title,
startDate: startDate ? new Date(startDate) : null,
endDate: endDate ? new Date(endDate) : null,
createdById: req.user.id,
participants: {
create: {
userId: req.user.id,
role: 'OWNER'
}
},
legs: {
create: {
sequence: 1,
note: 'Chặng khởi đầu'
}
}
}
});
}
async addLocation(tourId, body) {
const leg = await this.prisma.leg.findFirst({ where: { tourId } });
if (!leg)
throw new NotFoundException('Không tìm thấy chặng nào trong tour');
return this.prisma.location.create({
data: {
name: body.name,
address: body.address,
latitude: body.latitude,
longitude: body.longitude,
type: body.type,
legId: leg.id,
plannedStart: body.plannedStart ? new Date(body.plannedStart) : null,
plannedEnd: body.plannedEnd ? new Date(body.plannedEnd) : null,
}
});
}
async getPublicTours() {
return this.prisma.tour.findMany({
take: 20,
@@ -140,6 +180,24 @@ let TourController = class TourController {
return tour;
}
};
__decorate([
UseGuards(JwtAuthGuard),
Post(),
__param(0, Body()),
__param(1, Req()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], TourController.prototype, "createTour", null);
__decorate([
UseGuards(JwtAuthGuard),
Post(':tourId/locations'),
__param(0, Param('tourId', ParseUUIDPipe)),
__param(1, Body()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [String, Object]),
__metadata("design:returntype", Promise)
], TourController.prototype, "addLocation", null);
__decorate([
Get('explore'),
__metadata("design:type", Function),
+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
@@ -5,6 +5,8 @@ interface TourState {
userRole: string | null;
setTour: (tour: any) => void;
updateLegs: (legs: any[]) => void;
createTour: (tourData: any) => Promise<any>;
addLocation: (tourId: string, locationData: any) => Promise<void>;
optimizeRouting: (legId: string) => Promise<void>;
fetchTour: (id: string) => Promise<void>;
fetchPublicTours: () => Promise<void>;
+26
View File
@@ -19,6 +19,32 @@ export const useTourStore = create((set, get) => ({
const data = await response.json();
set({ publicTours: data });
},
createTour: async (tourData) => {
const API_BASE = `http://${window.location.hostname}:3001`;
const response = await fetch(`${API_BASE}/api/v1/tours`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify(tourData),
});
return await response.json();
},
addLocation: async (tourId, locationData) => {
const API_BASE = `http://${window.location.hostname}:3001`;
const response = await fetch(`${API_BASE}/api/v1/tours/${tourId}/locations`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify(locationData),
});
if (!response.ok)
throw new Error('Lỗi khi thêm địa điểm');
get().fetchTour(tourId);
},
optimizeRouting: async (legId) => {
const API_BASE = `http://${window.location.hostname}:3001`;
const response = await fetch(`${API_BASE}/api/v1/routing/optimize/${legId}`, {
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"useTourStore.js","sourceRoot":"","sources":["../useTourStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAcjC,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAY,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC;IAC3D,WAAW,EAAE,IAAI;IACjB,IAAI,EAAE,EAAE;IACR,WAAW,EAAE,EAAE;IACf,QAAQ,EAAE,IAAI;IACd,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;IAC7C,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC;IACnC,SAAS,EAAE,KAAK,EAAE,EAAU,EAAE,EAAE;QAC9B,MAAM,QAAQ,GAAG,UAAU,MAAM,CAAC,QAAQ,CAAC,QAAQ,OAAO,CAAC;QAC3D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,iBAAiB,EAAE,EAAE,CAAC,CAAC;QAC/D,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAGnC,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,IAAI,aAAa,CAAC;QAC3D,GAAG,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACpE,CAAC;IACD,gBAAgB,EAAE,KAAK,IAAI,EAAE;QAC3B,MAAM,QAAQ,GAAG,UAAU,MAAM,CAAC,QAAQ,CAAC,QAAQ,OAAO,CAAC;QAC3D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,uBAAuB,CAAC,CAAC;QACjE,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,GAAG,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7B,CAAC;IACD,eAAe,EAAE,KAAK,EAAE,KAAa,EAAE,EAAE;QACvC,MAAM,QAAQ,GAAG,UAAU,MAAM,CAAC,QAAQ,CAAC,QAAQ,OAAO,CAAC;QAC3D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,4BAA4B,KAAK,EAAE,EAAE;YAC3E,MAAM,EAAE,MAAM;SACf,CAAC,CAAC;QACH,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAE3D,MAAM,EAAE,WAAW,EAAE,GAAG,GAAG,EAAE,CAAC;QAC9B,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,WAAW,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAClD,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,CAAC,CAClF,CAAC;YACF,GAAG,CAAC,EAAE,WAAW,EAAE,EAAE,GAAG,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;QACjF,CAAC;IACH,CAAC;CACF,CAAC,CAAC,CAAC"}
{"version":3,"file":"useTourStore.js","sourceRoot":"","sources":["../useTourStore.ts"],"names":[],"mappings":"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"}