import React from 'react'; import { format, differenceInMinutes, parseISO } from 'date-fns'; import { CheckCircle2, Circle, Clock, MapPin, AlertCircle, Zap, Navigation, Edit2, Trash2, Plus } from 'lucide-react'; import { useTourStore } from './useTourStore.js'; const TimeVariance = ({ planned, actual }: { planned: string, actual: string | null }) => { if (!actual) return null; const diff = differenceInMinutes(parseISO(actual), parseISO(planned)); const isLate = diff > 0; return (
{isLate ? : } {isLate ? `Trễ ${diff} phút` : diff === 0 ? 'Đúng giờ' : `Sớm ${Math.abs(diff)} phút`}
); }; const calculateDistance = (lat1: number, lon1: number, lat2: number, lon2: number) => { const p = 0.017453292519943295; // Math.PI / 180 const c = Math.cos; const a = 0.5 - c((lat2 - lat1) * p) / 2 + c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p)) / 2; return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km }; const formatTravelTime = (minutes: number) => { if (minutes < 60) return `${minutes} phút`; const hours = Math.floor(minutes / 60); const mins = minutes % 60; return mins > 0 ? `${hours} giờ ${mins} phút` : `${hours} giờ`; }; export const ItineraryTimeline = () => { const { currentTour, legs, optimizeRouting, userRole, activeLegId, setActiveLegId, addLeg, updateLeg, deleteLeg } = useTourStore(); const toggleComplete = async (locationId: string) => { // Gọi API PATCH /api/v1/locations/:id để cập nhật trạng thái console.log("Toggle status for location:", locationId); }; const handleAddLeg = async () => { const note = window.prompt("Nhập ghi chú cho chặng mới:", `Chặng ${legs.length + 1}`); if (note && currentTour) { await addLeg(currentTour.id, { note }); } }; const handleEditLeg = async (leg: any) => { const note = window.prompt("Sửa ghi chú chặng:", leg.note || ""); if (note !== null) { await updateLeg(leg.id, { note }); } }; const handleDeleteLeg = async (legId: string) => { if (window.confirm("Bạn có chắc chắn muốn xóa chặng này?")) { try { await deleteLeg(legId); } catch (err: any) { alert(err.message); } } }; const activeLeg = legs.find(l => l.id === activeLegId) || legs[0]; return (
{/* Tầng 1: Horizontal Leg Picker (6.3-A) */}
{legs.map((leg) => ( ))} {['OWNER', 'MANAGER'].includes(userRole || '') && ( )}
{activeLeg && (
{/* Leg Info Summary */} {(() => { const leg = activeLeg; const totalDwellMinutes = leg.locations.reduce((acc: number, loc: any) => { if (loc.plannedStart && loc.plannedEnd) { return acc + differenceInMinutes(parseISO(loc.plannedEnd), parseISO(loc.plannedStart)); } return acc; }, 0); return ( <> {/* Leg Header */}
{leg.note || `Chi tiết Chặng ${leg.sequence}`}
{['OWNER', 'MANAGER'].includes(userRole || '') && ( <> )} {leg.totalDistance !== undefined && (
{leg.totalDistance} km
~ {formatTravelTime(Math.round((leg.totalDistance / 35) * 60))}
)} {totalDwellMinutes > 0 && (
Dừng: {formatTravelTime(totalDwellMinutes)}
)}
{['OWNER', 'MANAGER'].includes(userRole || '') && legs.length > 0 && ( )}
{/* Vertical Line for the whole leg */}
{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; // km/h const travelTimeMinutes = distanceToNext ? Math.round((distanceToNext / averageSpeed) * 60) : null; const dwellMinutes = (location.plannedStart && location.plannedEnd) ? differenceInMinutes(parseISO(location.plannedEnd), parseISO(location.plannedStart)) : 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 (
{/* Timeline Node */}
{/* Card Content */}
{isStartPoint && ( Điểm bắt đầu )} {isEndPoint && ( Điểm kết thúc )}

{location.name}

{location.address}
{dwellMinutes !== null && (
Thời gian dừng: {formatTravelTime(dwellMinutes)}
)}
{location.plannedStart ? format(parseISO(location.plannedStart), 'HH:mm') : '--:--'}
{location.status === 'COMPLETED' && location.actualStart && (
Thực tế: {format(parseISO(location.actualStart), 'HH:mm')}
)}
{/* Logic tính toán độ lệch thời gian */}
{distanceToNext !== null && travelTimeMinutes !== null && (
{distanceToNext.toFixed(2)} km ~ {formatTravelTime(travelTimeMinutes)}
)}
); })}
{leg.note && (

* {leg.note}

)} ); })()}
)}
); };