Cho phép chọn trực tiếp điểm trên bản đồ

This commit is contained in:
2026-06-13 21:34:07 +07:00
parent 25fcd5d926
commit 578b03d808
12 changed files with 489 additions and 248 deletions
+26 -22
View File
@@ -1,4 +1,4 @@
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { format, differenceInMinutes, parseISO } from 'date-fns';
import { CheckCircle2, Circle, Clock, MapPin, AlertCircle, Zap, Navigation } from 'lucide-react';
import { useTourStore } from './useTourStore.js';
@@ -25,29 +25,33 @@ const formatTravelTime = (minutes) => {
return mins > 0 ? `${hours} giờ ${mins} phút` : `${hours} giờ`;
};
export const ItineraryTimeline = () => {
const { legs, optimizeRouting, userRole } = useTourStore();
const { legs, optimizeRouting, userRole, activeLegId, setActiveLegId } = useTourStore();
const toggleComplete = async (locationId) => {
console.log("Toggle status for location:", locationId);
};
return (_jsxs("div", { className: "max-w-2xl mx-auto p-4 sm:p-6 bg-gray-50 min-h-screen", children: [_jsx("h2", { className: "text-2xl font-bold text-gray-800 mb-8 px-2", children: "L\u1ED9 tr\u00ECnh chuy\u1EBFn \u0111i" }), _jsx("div", { className: "space-y-8", children: legs.map((leg, legIdx) => {
const totalDwellMinutes = leg.locations.reduce((acc, loc) => {
if (loc.plannedStart && loc.plannedEnd) {
return acc + differenceInMinutes(parseISO(loc.plannedEnd), parseISO(loc.plannedStart));
}
return acc;
}, 0);
return (_jsxs("div", { className: "relative", children: [_jsxs("div", { className: "flex items-center mb-4 px-2", children: [_jsxs("div", { className: "bg-blue-600 text-white text-sm font-bold px-3 py-1 rounded-full shadow-sm", children: ["Ch\u1EB7ng ", leg.sequence] }), leg.totalDistance !== undefined && (_jsxs("div", { className: "flex flex-wrap items-center gap-2 ml-3", children: [_jsxs("div", { className: "text-xs font-bold text-blue-600 bg-blue-50 px-2 py-1 rounded-lg border border-blue-100", children: [leg.totalDistance, " km"] }), _jsxs("div", { className: "text-xs font-bold text-gray-500 bg-gray-50 px-2 py-1 rounded-lg border border-gray-100 flex items-center gap-1", children: [_jsx(Clock, { className: "w-3 h-3" }), "~ ", formatTravelTime(Math.round((leg.totalDistance / 35) * 60))] }), totalDwellMinutes > 0 && (_jsxs("div", { className: "text-xs font-bold text-amber-600 bg-amber-50 px-2 py-1 rounded-lg border border-amber-100 flex items-center gap-1", children: [_jsx(Clock, { className: "w-3 h-3" }), "D\u1EEBng: ", formatTravelTime(totalDwellMinutes)] }))] })), ['OWNER', 'MANAGER'].includes(userRole || '') && (_jsxs("button", { onClick: () => optimizeRouting(leg.id), className: "ml-auto flex items-center gap-1.5 text-xs font-bold text-indigo-600 hover:text-indigo-700 bg-indigo-50 hover:bg-indigo-100 px-3 py-1.5 rounded-xl transition-all", children: [_jsx(Zap, { className: "w-3 h-3" }), "T\u1ED1i \u01B0u \u0111\u01B0\u1EDDng \u0111i"] })), _jsx("div", { className: "ml-4 h-[1px] flex-1 bg-gray-200" })] }), _jsx("div", { className: "absolute left-6 top-10 bottom-0 w-0.5 bg-gray-200 -z-0" }), _jsx("div", { className: "ml-2", children: leg.locations.map((location, idx) => {
const nextLocation = leg.locations[idx + 1];
const distanceToNext = nextLocation
? calculateDistance(location.latitude, location.longitude, nextLocation.latitude, nextLocation.longitude)
: null;
const averageSpeed = 35;
const travelTimeMinutes = distanceToNext ? Math.round((distanceToNext / averageSpeed) * 60) : null;
const dwellMinutes = (location.plannedStart && location.plannedEnd)
? differenceInMinutes(parseISO(location.plannedEnd), parseISO(location.plannedStart))
: null;
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: [_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));
}) }), leg.notes && (_jsxs("p", { className: "ml-14 mt-4 text-sm text-gray-400 italic", children: ["* ", leg.notes] }))] }, leg.id));
}) })] }));
const activeLeg = legs.find(l => l.id === activeLegId) || legs[0];
return (_jsxs("div", { className: "max-w-2xl mx-auto p-0 bg-gray-50 min-h-screen", children: [_jsx("div", { className: "sticky top-[136px] z-20 bg-gray-50/80 backdrop-blur-sm pb-4 mb-4", children: _jsx("div", { className: "flex overflow-x-auto gap-2 px-2 no-scrollbar py-2", children: legs.map((leg) => (_jsxs("button", { onClick: () => setActiveLegId(leg.id), className: `flex-shrink-0 px-6 py-2.5 rounded-2xl text-xs font-black uppercase tracking-widest transition-all border ${activeLegId === leg.id
? 'bg-blue-600 text-white border-blue-600 shadow-lg shadow-blue-100 scale-105'
: 'bg-white text-gray-400 border-gray-100 hover:border-gray-200'}`, children: ["Ch\u1EB7ng ", leg.sequence] }, leg.id))) }) }), _jsx("div", { className: "px-2", children: activeLeg && (_jsx("div", { className: "relative animate-in fade-in slide-in-from-right-4 duration-300", children: (() => {
const leg = activeLeg;
const totalDwellMinutes = leg.locations.reduce((acc, loc) => {
if (loc.plannedStart && loc.plannedEnd) {
return acc + differenceInMinutes(parseISO(loc.plannedEnd), parseISO(loc.plannedStart));
}
return acc;
}, 0);
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center mb-4 px-2", children: [_jsx("div", { className: "font-black text-gray-900 text-lg", children: leg.note || `Chi tiết Chặng ${leg.sequence}` }), leg.totalDistance !== undefined && (_jsxs("div", { className: "flex flex-wrap items-center gap-2 ml-4", children: [_jsxs("div", { className: "text-xs font-bold text-blue-600 bg-blue-50 px-2 py-1 rounded-lg border border-blue-100", children: [leg.totalDistance, " km"] }), _jsxs("div", { className: "text-xs font-bold text-gray-500 bg-gray-50 px-2 py-1 rounded-lg border border-gray-100 flex items-center gap-1", children: [_jsx(Clock, { className: "w-3 h-3" }), "~ ", formatTravelTime(Math.round((leg.totalDistance / 35) * 60))] }), totalDwellMinutes > 0 && (_jsxs("div", { className: "text-xs font-bold text-amber-600 bg-amber-50 px-2 py-1 rounded-lg border border-amber-100 flex items-center gap-1", children: [_jsx(Clock, { className: "w-3 h-3" }), "D\u1EEBng: ", formatTravelTime(totalDwellMinutes)] }))] })), ['OWNER', 'MANAGER'].includes(userRole || '') && (_jsxs("button", { onClick: () => optimizeRouting(leg.id), className: "ml-auto flex items-center gap-1.5 text-xs font-bold text-indigo-600 hover:text-indigo-700 bg-indigo-50 hover:bg-indigo-100 px-3 py-1.5 rounded-xl transition-all", children: [_jsx(Zap, { className: "w-3 h-3" }), "T\u1ED1i \u01B0u"] }))] }), _jsx("div", { className: "absolute left-6 top-10 bottom-0 w-0.5 bg-gray-200 -z-0" }), _jsx("div", { className: "ml-2", children: leg.locations.map((location, idx) => {
const nextLocation = leg.locations[idx + 1];
const distanceToNext = nextLocation
? calculateDistance(location.latitude, location.longitude, nextLocation.latitude, nextLocation.longitude)
: null;
const averageSpeed = 35;
const travelTimeMinutes = distanceToNext ? Math.round((distanceToNext / averageSpeed) * 60) : null;
const dwellMinutes = (location.plannedStart && location.plannedEnd)
? differenceInMinutes(parseISO(location.plannedEnd), parseISO(location.plannedStart))
: null;
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: [_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));
}) }), leg.note && (_jsxs("p", { className: "ml-14 mt-4 text-sm text-gray-400 italic", children: ["* ", leg.note] }))] }));
})() })) })] }));
};
//# sourceMappingURL=ItineraryTimeline.js.map
+1 -1
View File
File diff suppressed because one or more lines are too long
+23 -4
View File
@@ -4,11 +4,27 @@ import { ItineraryTimeline } from './ItineraryTimeline.js';
import { ExpenseManager } from './ExpenseManager.js';
import { useTourStore } from './useTourStore.js';
import { AddLocationModal } from './AddLocationModal.js';
import { Map as MapIcon, Wallet, Image as ImageIcon, Calendar, Users, ChevronLeft, Settings, Quote, Plus } from 'lucide-react';
import { MapContainer, TileLayer, Marker, Popup, useMapEvents, Polyline } from 'react-leaflet';
import { Map as MapIcon, Wallet, Image as ImageIcon, Calendar, Users, ChevronLeft, Settings, Quote, Plus, List, Map as MapIconLucide } from 'lucide-react';
const MapContextMenu = ({ onAction }) => {
const [menuPos, setMenuPos] = useState(null);
const { legs } = useTourStore();
useMapEvents({
contextmenu: (e) => {
setMenuPos({ x: e.containerPoint.x, y: e.containerPoint.y, latlng: e.latlng });
},
click: () => setMenuPos(null),
dragstart: () => setMenuPos(null)
});
if (!menuPos)
return null;
return (_jsxs("div", { className: "absolute z-[2000] bg-white rounded-2xl shadow-2xl border border-gray-100 py-2 w-48 animate-in zoom-in-95 duration-200", style: { top: menuPos.y, left: menuPos.x }, children: [_jsxs("button", { onClick: () => { onAction('START', menuPos.latlng); setMenuPos(null); }, className: "w-full text-left px-4 py-2 hover:bg-blue-50 text-sm font-bold text-gray-700 flex items-center gap-2", children: [_jsx("div", { className: "w-2 h-2 rounded-full bg-green-500" }), " B\u1EAFt \u0111\u1EA7u t\u1EEB \u0111\u00E2y"] }), _jsxs("button", { onClick: () => { onAction('END', menuPos.latlng); setMenuPos(null); }, className: "w-full text-left px-4 py-2 hover:bg-blue-50 text-sm font-bold text-gray-700 flex items-center gap-2 border-b border-gray-50", children: [_jsx("div", { className: "w-2 h-2 rounded-full bg-red-500" }), " K\u1EBFt th\u00FAc \u1EDF \u0111\u00E2y"] }), _jsx("div", { className: "px-4 py-2 text-[10px] font-black text-gray-400 uppercase tracking-widest", children: "Th\u00EAm v\u00E0o ch\u1EB7ng" }), legs.map(leg => (_jsxs("button", { onClick: () => { onAction(`ADD_TO_LEG_${leg.id}`, menuPos.latlng); setMenuPos(null); }, className: "w-full text-left px-4 py-2 hover:bg-blue-50 text-sm font-medium text-gray-600 truncate", children: ["Ch\u1EB7ng ", leg.sequence, ": ", leg.note || 'Không có ghi chú'] }, leg.id)))] }));
};
export const TourDetailPage = ({ onBack }) => {
const [activeTab, setActiveTab] = useState('plan');
const [viewMode, setViewMode] = useState('timeline');
const [isAddLocationOpen, setIsAddLocationOpen] = useState(false);
const { currentTour, fetchTour, fetchPublicTours, publicTours, userRole } = useTourStore();
const { currentTour, fetchTour, fetchPublicTours, publicTours, userRole, legs } = useTourStore();
useEffect(() => {
const loadData = async () => {
if (publicTours.length === 0) {
@@ -43,8 +59,11 @@ export const TourDetailPage = ({ onBack }) => {
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-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
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: `${activeTab === 'plan' && viewMode === 'map' ? 'w-full' : '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-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 }))] }));
: '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, {})) : (_jsxs("div", { className: "h-[60vh] w-full rounded-3xl overflow-hidden shadow-xl border-4 border-white relative", children: [_jsxs(MapContainer, { center: [10.7769, 106.7009], zoom: 13, className: "h-full w-full", children: [_jsx(TileLayer, { url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" }), _jsx(MapContextMenu, { onAction: (action, latlng) => console.log(action, latlng) }), legs.map(leg => {
const positions = leg.locations.map((l) => [l.latitude, l.longitude]);
return _jsx(Polyline, { positions: positions, color: "#3b82f6", weight: 3, dashArray: "5, 10" }, leg.id);
}), legs.flatMap(l => l.locations).map((loc) => (_jsx(Marker, { position: [loc.latitude, loc.longitude], 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: () => 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
+1 -1
View File
File diff suppressed because one or more lines are too long
+2
View File
@@ -3,11 +3,13 @@ interface TourState {
legs: any[];
publicTours: any[];
userRole: string | null;
activeLegId: 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>;
setActiveLegId: (id: string | null) => void;
fetchTour: (id: string) => Promise<void>;
fetchPublicTours: () => Promise<void>;
}
+9 -1
View File
@@ -4,14 +4,22 @@ export const useTourStore = create((set, get) => ({
legs: [],
publicTours: [],
userRole: null,
activeLegId: null,
setTour: (tour) => set({ currentTour: tour }),
updateLegs: (legs) => set({ legs }),
setActiveLegId: (id) => set({ activeLegId: id }),
fetchTour: async (id) => {
const API_BASE = `http://${window.location.hostname}:3001`;
const response = await fetch(`${API_BASE}/api/v1/tours/${id}`);
const data = await response.json();
const role = data.participants?.[0]?.role || 'VIEWER_ONLY';
set({ currentTour: data, legs: data.legs || [], userRole: role });
const legs = data.legs || [];
set({
currentTour: data,
legs: legs,
userRole: role,
activeLegId: legs.length > 0 ? legs[0].id : null
});
},
fetchPublicTours: async () => {
const API_BASE = `http://${window.location.hostname}:3001`;
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"useTourStore.js","sourceRoot":"","sources":["../useTourStore.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"useTourStore.js","sourceRoot":"","sources":["../useTourStore.ts"],"names":[],"mappings":"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"}