main #26
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { motion, AnimatePresence } from 'motion/react';
|
import { motion, AnimatePresence } from 'motion/react';
|
||||||
import {
|
import {
|
||||||
MapPin,
|
MapPin,
|
||||||
@@ -7,11 +7,12 @@ import {
|
|||||||
Share2,
|
Share2,
|
||||||
Download,
|
Download,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
|
Loader2,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '../components/ui/button';
|
import { Button } from '../components/ui/button';
|
||||||
import { Card, CardContent } from '../components/ui/card';
|
import { Card, CardContent } from '../components/ui/card';
|
||||||
import { ImageWithFallback } from '../components/figma/ImageWithFallback';
|
import { ImageWithFallback } from '../components/figma/ImageWithFallback';
|
||||||
import { useCreateMagicItineraryMutation, useGetItineraryDetailsByIdQuery } from '../Redux/services/itinerary.service';
|
import { useDownloadItineraryQuery, useGetItineraryDetailsByIdQuery } from '../Redux/services/itinerary.service';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import Navbar from '../components/Navbar';
|
import Navbar from '../components/Navbar';
|
||||||
@@ -26,6 +27,37 @@ const ItinerarySummaryPage = () => {
|
|||||||
|
|
||||||
const { itineraryId } = useParams()
|
const { itineraryId } = useParams()
|
||||||
const { data: itineraryDetails, isLoading: itineraryDetailsLoading } = useGetItineraryDetailsByIdQuery(itineraryId);
|
const { data: itineraryDetails, isLoading: itineraryDetailsLoading } = useGetItineraryDetailsByIdQuery(itineraryId);
|
||||||
|
// Download logic using standard query with manual trigger
|
||||||
|
const [shouldDownload, setShouldDownload] = useState(false);
|
||||||
|
const { data: pdfBlob, isFetching: isDownloading, refetch } = useDownloadItineraryQuery
|
||||||
|
(itineraryId!, {
|
||||||
|
skip: !shouldDownload || !itineraryId,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (shouldDownload && pdfBlob) {
|
||||||
|
// Create download link
|
||||||
|
const url = window.URL.createObjectURL(pdfBlob);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.download = `itinerary-${itineraryId}.pdf`;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
toast.success('Itinerary downloaded successfully!');
|
||||||
|
setShouldDownload(false); // reset trigger
|
||||||
|
}
|
||||||
|
}, [pdfBlob, shouldDownload, itineraryId]);
|
||||||
|
|
||||||
|
const handleDownloadItinerary = useCallback(() => {
|
||||||
|
if (!itineraryId) {
|
||||||
|
toast.error('Itinerary ID not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setShouldDownload(true);
|
||||||
|
refetch(); // manually trigger the download query
|
||||||
|
}, [itineraryId, refetch]);
|
||||||
|
|
||||||
const generatedItinerary = itineraryDetails ?? null;
|
const generatedItinerary = itineraryDetails ?? null;
|
||||||
const days = generatedItinerary?.days ?? [];
|
const days = generatedItinerary?.days ?? [];
|
||||||
@@ -35,305 +67,312 @@ const ItinerarySummaryPage = () => {
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background">
|
<div className="min-h-screen bg-background">
|
||||||
{/* Navbar */}
|
{/* Navbar */}
|
||||||
<Navbar
|
<Navbar
|
||||||
/>
|
/>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
className="space-y-8 max-w-3xl mx-auto mt-25"
|
className="space-y-8 max-w-3xl mx-auto mt-25"
|
||||||
>
|
>
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<div className="text-center space-y-1">
|
<div className="text-center space-y-1">
|
||||||
<h1 className="font-merchant text-3xl md:text-4xl lg:text-5xl leading-tight">
|
<h1 className="font-merchant text-3xl md:text-4xl lg:text-5xl leading-tight">
|
||||||
<span className="font-normal">Your</span>
|
<span className="font-normal">Your</span>
|
||||||
</h1>
|
</h1>
|
||||||
<h1 className="font-merchant text-3xl md:text-4xl lg:text-5xl leading-tight">
|
<h1 className="font-merchant text-3xl md:text-4xl lg:text-5xl leading-tight">
|
||||||
<span className="font-bold text-primary italic">{generatedItinerary?.title}</span>
|
<span className="font-bold text-primary italic">{generatedItinerary?.title}</span>
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Trip Details Card */}
|
{/* Trip Details Card */}
|
||||||
<div className="relative overflow-hidden rounded-2xl border border-gray-100 shadow-sm">
|
<div className="relative overflow-hidden rounded-2xl border border-gray-100 shadow-sm">
|
||||||
{/* Background Image */}
|
{/* Background Image */}
|
||||||
<div className="relative h-40 md:h-48">
|
<div className="relative h-40 md:h-48">
|
||||||
<ImageWithFallback
|
<ImageWithFallback
|
||||||
src={generatedItinerary?.cityBanner}
|
src={generatedItinerary?.cityBanner}
|
||||||
alt={generatedItinerary?.city}
|
alt={generatedItinerary?.city}
|
||||||
className="w-full h-full object-cover"
|
className="w-full h-full object-cover"
|
||||||
/>
|
/>
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/30 to-transparent" />
|
<div className="absolute inset-0 bg-gradient-to-t from-black/70 via-black/30 to-transparent" />
|
||||||
<div className="absolute bottom-4 left-5 right-5">
|
<div className="absolute bottom-4 left-5 right-5">
|
||||||
<p className="font-poppins text-xs font-medium text-white/70 uppercase tracking-wider mb-1">Your Trip</p>
|
<p className="font-poppins text-xs font-medium text-white/70 uppercase tracking-wider mb-1">Your Trip</p>
|
||||||
<h3 className="font-merchant text-2xl md:text-3xl text-white leading-snug font-semibold">{generatedItinerary?.city}</h3>
|
<h3 className="font-merchant text-2xl md:text-3xl text-white leading-snug font-semibold">{generatedItinerary?.city}</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* Stats Row */}
|
||||||
|
<div className="grid grid-cols-3 divide-x divide-gray-100 bg-white">
|
||||||
|
<div className="flex flex-col items-center py-4">
|
||||||
|
<span className="font-merchant text-2xl text-primary">{generatedItinerary?.totalDays}</span>
|
||||||
|
<span className="font-poppins text-xs font-normal text-gray-500 mt-0.5">Days</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center py-4">
|
||||||
|
<span className="font-merchant text-2xl text-primary">{generatedItinerary?.totalStops}</span>
|
||||||
|
<span className="font-poppins text-xs font-normal text-gray-500 mt-0.5">Stops</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col items-center py-4">
|
||||||
|
<span className="font-merchant text-2xl text-primary">{generatedItinerary?.days[0]?.date}</span>
|
||||||
|
<span className="font-poppins text-xs font-normal text-gray-500 mt-0.5">Start Date</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Stats Row */}
|
|
||||||
<div className="grid grid-cols-3 divide-x divide-gray-100 bg-white">
|
|
||||||
<div className="flex flex-col items-center py-4">
|
|
||||||
<span className="font-merchant text-2xl text-primary">{generatedItinerary?.totalDays}</span>
|
|
||||||
<span className="font-poppins text-xs font-normal text-gray-500 mt-0.5">Days</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col items-center py-4">
|
|
||||||
<span className="font-merchant text-2xl text-primary">{generatedItinerary?.totalStops}</span>
|
|
||||||
<span className="font-poppins text-xs font-normal text-gray-500 mt-0.5">Stops</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col items-center py-4">
|
|
||||||
<span className="font-merchant text-2xl text-primary">{generatedItinerary?.days[0]?.date}</span>
|
|
||||||
<span className="font-poppins text-xs font-normal text-gray-500 mt-0.5">Start Date</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Share & Download Buttons */}
|
{/* Share & Download Buttons */}
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex-1 border-2 border-primary/20 text-primary hover:bg-primary/5 font-poppins font-medium rounded-xl py-3"
|
className="flex-1 border-2 border-primary/20 text-primary hover:bg-primary/5 font-poppins font-medium rounded-xl py-3"
|
||||||
>
|
|
||||||
<Share2 className="w-4 h-4 mr-2" />
|
|
||||||
Share
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="flex-1 border-2 border-primary/20 text-primary hover:bg-primary/5 font-poppins font-medium rounded-xl py-3"
|
|
||||||
>
|
|
||||||
<Download className="w-4 h-4 mr-2" />
|
|
||||||
Download
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* View Toggle */}
|
|
||||||
<div className="flex justify-center">
|
|
||||||
<div className="bg-gray-100 p-1 rounded-full inline-flex">
|
|
||||||
<button
|
|
||||||
onClick={() => setViewMode('daily')}
|
|
||||||
className={`px-6 py-2.5 rounded-full font-poppins font-medium text-sm transition-all ${viewMode === 'daily'
|
|
||||||
? 'bg-white shadow-sm text-gray-900'
|
|
||||||
: 'text-gray-500 hover:text-gray-700'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
Daily View
|
<Share2 className="w-4 h-4 mr-2" />
|
||||||
</button>
|
Share
|
||||||
<button
|
</Button>
|
||||||
onClick={() => setViewMode('summary')}
|
<Button
|
||||||
className={`px-6 py-2.5 rounded-full font-poppins font-medium text-sm transition-all ${viewMode === 'summary'
|
onClick={handleDownloadItinerary}
|
||||||
? 'bg-white shadow-sm text-gray-900'
|
disabled={isDownloading}
|
||||||
: 'text-gray-500 hover:text-gray-700'
|
variant="outline"
|
||||||
}`}
|
className="flex-1 border-2 border-primary/20 text-primary hover:bg-primary/5 font-poppins font-medium rounded-xl py-3"
|
||||||
>
|
>
|
||||||
Summary
|
{isDownloading ? (
|
||||||
</button>
|
<Loader2 className="w-5 h-5 mr-2 animate-spin" />
|
||||||
</div>
|
) : (
|
||||||
</div>
|
<Download className="w-5 h-5 mr-2" />
|
||||||
|
|
||||||
{/* Daily View */}
|
|
||||||
{viewMode === 'daily' && (
|
|
||||||
<div className="space-y-6">
|
|
||||||
{/* Day Tabs */}
|
|
||||||
<div className="flex items-center gap-2 overflow-x-auto pb-2">
|
|
||||||
{days?.map((day: any) => (
|
|
||||||
<button
|
|
||||||
key={day.dayNumber}
|
|
||||||
onClick={() => setSelectedDayTab(day.dayNumber)}
|
|
||||||
className={`px-5 py-2.5 rounded-xl whitespace-nowrap font-poppins text-base transition-all ${selectedDayTab === day.dayNumber
|
|
||||||
? 'text-primary font-semibold bg-primary/10 border border-primary/20'
|
|
||||||
: 'text-gray-400 font-medium hover:text-gray-600'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Day {day.dayNumber}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
{days?.length > 4 && (
|
|
||||||
<button className="p-2 text-gray-400 hover:text-gray-600">
|
|
||||||
<ChevronRight className="w-4 h-4" />
|
|
||||||
</button>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Activities for selected day */}
|
Download
|
||||||
{selectedDayPlan && (
|
</Button>
|
||||||
<AnimatePresence mode="wait">
|
|
||||||
<motion.div key={`day-${selectedDayTab}`} initial={{ opacity: 0, x: 10 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -10 }} transition={{ duration: 0.3 }} className="space-y-8">
|
|
||||||
{selectedDayPlan?.items?.map((activity: any, actIndex: number) => {
|
|
||||||
const activityKey = `day${selectedDayPlan.day}-act${actIndex}`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<motion.div
|
|
||||||
key={actIndex}
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
transition={{ duration: 0.4, delay: actIndex * 0.08 }}
|
|
||||||
className="space-y-4"
|
|
||||||
>
|
|
||||||
{/* Time Label */}
|
|
||||||
<p className="font-poppins text-sm font-medium text-gray-500 text-center uppercase tracking-wider">
|
|
||||||
{activity.timeSlot}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{/* Activity Card */}
|
|
||||||
<Card className="overflow-hidden border border-gray-100 shadow-sm hover:shadow-lg transition-shadow duration-300 rounded-2xl">
|
|
||||||
<CardContent className="p-0">
|
|
||||||
{/* Image */}
|
|
||||||
<div className="relative h-56 md:h-64 bg-gray-200">
|
|
||||||
<ImageWithFallback
|
|
||||||
src={activity.imageUrl}
|
|
||||||
alt={activity.title}
|
|
||||||
className="w-full h-full object-cover"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* TODO: Get Directions Badge redirect it to lat,long */}
|
|
||||||
<div className="absolute bottom-3 left-3">
|
|
||||||
<button className="flex items-center gap-1.5 bg-primary text-white px-4 py-2 rounded-full font-poppins text-xs font-semibold shadow-lg">
|
|
||||||
<MapPin className="w-3.5 h-3.5" />
|
|
||||||
Get Directions
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Content */}
|
|
||||||
<div className="p-5 space-y-3">
|
|
||||||
<h4 className="font-poppins text-lg font-semibold text-gray-900 leading-snug">
|
|
||||||
{activity.title}
|
|
||||||
</h4>
|
|
||||||
<p className="font-poppins text-sm font-normal text-gray-500 leading-relaxed">
|
|
||||||
{activity.locationName}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{/* Category Tags */}
|
|
||||||
<div className="flex flex-wrap gap-2">
|
|
||||||
{activity.categories?.map((cat: string, ci: number) => (
|
|
||||||
<span
|
|
||||||
key={ci}
|
|
||||||
className="font-poppins text-xs font-medium px-3 py-1.5 rounded-full border border-primary/20 text-primary bg-primary/5"
|
|
||||||
>
|
|
||||||
{cat}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Bullet Points */}
|
|
||||||
<div className="space-y-1.5 pt-1">
|
|
||||||
|
|
||||||
<div className="flex items-baseline gap-2">
|
|
||||||
<span className="text-primary flex-shrink-0 text-sm leading-relaxed">•</span>
|
|
||||||
<span className="font-poppins text-sm font-normal text-gray-600 leading-relaxed">{activity.description}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</motion.div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</motion.div>
|
|
||||||
</AnimatePresence>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Summary View */}
|
{/* View Toggle */}
|
||||||
{viewMode === 'summary' && (
|
<div className="flex justify-center">
|
||||||
<div className="space-y-6">
|
<div className="bg-gray-100 p-1 rounded-full inline-flex">
|
||||||
{days?.map((day: any, dayIndex: number) => {
|
<button
|
||||||
const dayDate = days[0]?.date
|
onClick={() => setViewMode('daily')}
|
||||||
? new Date(
|
className={`px-6 py-2.5 rounded-full font-poppins font-medium text-sm transition-all ${viewMode === 'daily'
|
||||||
new Date(days[0].date).setDate(
|
? 'bg-white shadow-sm text-gray-900'
|
||||||
new Date(days[0].date).getDate() + dayIndex
|
: 'text-gray-500 hover:text-gray-700'
|
||||||
)
|
}`}
|
||||||
).toLocaleDateString('en-AU', {
|
>
|
||||||
day: '2-digit',
|
Daily View
|
||||||
month: '2-digit',
|
</button>
|
||||||
year: 'numeric',
|
<button
|
||||||
})
|
onClick={() => setViewMode('summary')}
|
||||||
: '';
|
className={`px-6 py-2.5 rounded-full font-poppins font-medium text-sm transition-all ${viewMode === 'summary'
|
||||||
|
? 'bg-white shadow-sm text-gray-900'
|
||||||
|
: 'text-gray-500 hover:text-gray-700'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Summary
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
// ✅ Find the matching summary for this day
|
{/* Daily View */}
|
||||||
const daySummary = summaries.find((s: any) => s.dayNumber === day.dayNumber);
|
{viewMode === 'daily' && (
|
||||||
|
<div className="space-y-6">
|
||||||
|
{/* Day Tabs */}
|
||||||
|
<div className="flex items-center gap-2 overflow-x-auto pb-2">
|
||||||
|
{days?.map((day: any) => (
|
||||||
|
<button
|
||||||
|
key={day.dayNumber}
|
||||||
|
onClick={() => setSelectedDayTab(day.dayNumber)}
|
||||||
|
className={`px-5 py-2.5 rounded-xl whitespace-nowrap font-poppins text-base transition-all ${selectedDayTab === day.dayNumber
|
||||||
|
? 'text-primary font-semibold bg-primary/10 border border-primary/20'
|
||||||
|
: 'text-gray-400 font-medium hover:text-gray-600'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
Day {day.dayNumber}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
{days?.length > 4 && (
|
||||||
|
<button className="p-2 text-gray-400 hover:text-gray-600">
|
||||||
|
<ChevronRight className="w-4 h-4" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
return (
|
{/* Activities for selected day */}
|
||||||
<div key={dayIndex} className="border-l-4 border-primary/20 pl-5 space-y-3">
|
{selectedDayPlan && (
|
||||||
{/* Day Header */}
|
<AnimatePresence mode="wait">
|
||||||
<div className="flex items-center justify-between">
|
<motion.div key={`day-${selectedDayTab}`} initial={{ opacity: 0, x: 10 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: -10 }} transition={{ duration: 0.3 }} className="space-y-8">
|
||||||
<h3 className="font-poppins text-lg font-semibold text-gray-900">
|
{selectedDayPlan?.items?.map((activity: any, actIndex: number) => {
|
||||||
Day {day.dayNumber}:
|
const activityKey = `day${selectedDayPlan.day}-act${actIndex}`;
|
||||||
</h3>
|
|
||||||
<div className="flex items-center gap-1.5 text-primary">
|
|
||||||
<Calendar className="w-3.5 h-3.5" />
|
|
||||||
<span className="font-poppins text-sm font-medium">{dayDate}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Activity List */}
|
|
||||||
<div className="space-y-2">
|
|
||||||
{daySummary?.items?.map((item: any, actIndex: number) => {
|
|
||||||
const activityKey = `summary-day${day.dayNumber}-act${actIndex}`;
|
|
||||||
const isExpanded = selectedActivity === activityKey;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={actIndex} className="bg-gray-50 rounded-xl overflow-hidden">
|
<motion.div
|
||||||
<div
|
key={actIndex}
|
||||||
className="flex items-center justify-between px-4 py-3 cursor-pointer hover:bg-gray-100 transition-colors"
|
initial={{ opacity: 0, y: 20 }}
|
||||||
onClick={() =>
|
animate={{ opacity: 1, y: 0 }}
|
||||||
setSelectedActivity(isExpanded ? null : activityKey)
|
transition={{ duration: 0.4, delay: actIndex * 0.08 }}
|
||||||
}
|
className="space-y-4"
|
||||||
>
|
>
|
||||||
<p className="font-poppins text-sm font-medium text-gray-800">
|
{/* Time Label */}
|
||||||
{item.timeSlot}: {item.title}
|
<p className="font-poppins text-sm font-medium text-gray-500 text-center uppercase tracking-wider">
|
||||||
</p>
|
{activity.timeSlot}
|
||||||
<ChevronDown
|
</p>
|
||||||
className={`w-4 h-4 text-gray-400 transition-transform ${isExpanded ? 'rotate-180' : ''}`}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<AnimatePresence>
|
{/* Activity Card */}
|
||||||
{isExpanded && (
|
<Card className="overflow-hidden border border-gray-100 shadow-sm hover:shadow-lg transition-shadow duration-300 rounded-2xl">
|
||||||
<motion.div
|
<CardContent className="p-0">
|
||||||
initial={{ height: 0, opacity: 0 }}
|
{/* Image */}
|
||||||
animate={{ height: 'auto', opacity: 1 }}
|
<div className="relative h-56 md:h-64 bg-gray-200">
|
||||||
exit={{ height: 0, opacity: 0 }}
|
<ImageWithFallback
|
||||||
transition={{ duration: 0.3, ease: 'easeInOut' }}
|
src={activity.imageUrl}
|
||||||
>
|
alt={activity.title}
|
||||||
<div className="px-4 pb-4 space-y-2">
|
className="w-full h-full object-cover"
|
||||||
<div className="flex items-baseline gap-2">
|
/>
|
||||||
<span className="text-primary flex-shrink-0 text-sm leading-relaxed">•</span>
|
|
||||||
<span className="font-poppins text-sm font-normal text-gray-600 leading-relaxed">
|
{/* TODO: Get Directions Badge redirect it to lat,long */}
|
||||||
{item.description}
|
<div className="absolute bottom-3 left-3">
|
||||||
</span>
|
<button className="flex items-center gap-1.5 bg-primary text-white px-4 py-2 rounded-full font-poppins text-xs font-semibold shadow-lg">
|
||||||
</div>
|
|
||||||
<button className="flex items-center gap-1.5 mt-2 bg-primary text-white px-4 py-2 rounded-full font-poppins text-xs font-semibold">
|
|
||||||
<MapPin className="w-3.5 h-3.5" />
|
<MapPin className="w-3.5 h-3.5" />
|
||||||
Get directions
|
Get Directions
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</div>
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
{/* Content */}
|
||||||
</div>
|
<div className="p-5 space-y-3">
|
||||||
|
<h4 className="font-poppins text-lg font-semibold text-gray-900 leading-snug">
|
||||||
|
{activity.title}
|
||||||
|
</h4>
|
||||||
|
<p className="font-poppins text-sm font-normal text-gray-500 leading-relaxed">
|
||||||
|
{activity.locationName}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Category Tags */}
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{activity.categories?.map((cat: string, ci: number) => (
|
||||||
|
<span
|
||||||
|
key={ci}
|
||||||
|
className="font-poppins text-xs font-medium px-3 py-1.5 rounded-full border border-primary/20 text-primary bg-primary/5"
|
||||||
|
>
|
||||||
|
{cat}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bullet Points */}
|
||||||
|
<div className="space-y-1.5 pt-1">
|
||||||
|
|
||||||
|
<div className="flex items-baseline gap-2">
|
||||||
|
<span className="text-primary flex-shrink-0 text-sm leading-relaxed">•</span>
|
||||||
|
<span className="font-poppins text-sm font-normal text-gray-600 leading-relaxed">{activity.description}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</motion.div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</motion.div>
|
||||||
</div>
|
</AnimatePresence>
|
||||||
);
|
)}
|
||||||
})}
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Bottom Action */}
|
{/* Summary View */}
|
||||||
<div className="flex justify-center pt-4 pb-8">
|
{viewMode === 'summary' && (
|
||||||
<Button
|
<div className="space-y-6">
|
||||||
onClick={() => navigate('/create-itinerary')}
|
{days?.map((day: any, dayIndex: number) => {
|
||||||
className="w-full font-poppins font-semibold px-8 py-3 rounded-xl bg-primary hover:bg-primary/90 text-white shadow-md shadow-primary/20"
|
const dayDate = days[0]?.date
|
||||||
>
|
? new Date(
|
||||||
Create Another Itinerary
|
new Date(days[0].date).setDate(
|
||||||
</Button>
|
new Date(days[0].date).getDate() + dayIndex
|
||||||
</div>
|
)
|
||||||
</motion.div>
|
).toLocaleDateString('en-AU', {
|
||||||
<Footer
|
day: '2-digit',
|
||||||
/>
|
month: '2-digit',
|
||||||
</div>
|
year: 'numeric',
|
||||||
|
})
|
||||||
|
: '';
|
||||||
|
|
||||||
|
// ✅ Find the matching summary for this day
|
||||||
|
const daySummary = summaries.find((s: any) => s.dayNumber === day.dayNumber);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={dayIndex} className="border-l-4 border-primary/20 pl-5 space-y-3">
|
||||||
|
{/* Day Header */}
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<h3 className="font-poppins text-lg font-semibold text-gray-900">
|
||||||
|
Day {day.dayNumber}:
|
||||||
|
</h3>
|
||||||
|
<div className="flex items-center gap-1.5 text-primary">
|
||||||
|
<Calendar className="w-3.5 h-3.5" />
|
||||||
|
<span className="font-poppins text-sm font-medium">{dayDate}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Activity List */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
{daySummary?.items?.map((item: any, actIndex: number) => {
|
||||||
|
const activityKey = `summary-day${day.dayNumber}-act${actIndex}`;
|
||||||
|
const isExpanded = selectedActivity === activityKey;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div key={actIndex} className="bg-gray-50 rounded-xl overflow-hidden">
|
||||||
|
<div
|
||||||
|
className="flex items-center justify-between px-4 py-3 cursor-pointer hover:bg-gray-100 transition-colors"
|
||||||
|
onClick={() =>
|
||||||
|
setSelectedActivity(isExpanded ? null : activityKey)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<p className="font-poppins text-sm font-medium text-gray-800">
|
||||||
|
{item.timeSlot}: {item.title}
|
||||||
|
</p>
|
||||||
|
<ChevronDown
|
||||||
|
className={`w-4 h-4 text-gray-400 transition-transform ${isExpanded ? 'rotate-180' : ''}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AnimatePresence>
|
||||||
|
{isExpanded && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ height: 0, opacity: 0 }}
|
||||||
|
animate={{ height: 'auto', opacity: 1 }}
|
||||||
|
exit={{ height: 0, opacity: 0 }}
|
||||||
|
transition={{ duration: 0.3, ease: 'easeInOut' }}
|
||||||
|
>
|
||||||
|
<div className="px-4 pb-4 space-y-2">
|
||||||
|
<div className="flex items-baseline gap-2">
|
||||||
|
<span className="text-primary flex-shrink-0 text-sm leading-relaxed">•</span>
|
||||||
|
<span className="font-poppins text-sm font-normal text-gray-600 leading-relaxed">
|
||||||
|
{item.description}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<button className="flex items-center gap-1.5 mt-2 bg-primary text-white px-4 py-2 rounded-full font-poppins text-xs font-semibold">
|
||||||
|
<MapPin className="w-3.5 h-3.5" />
|
||||||
|
Get directions
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Bottom Action */}
|
||||||
|
<div className="flex justify-center pt-4 pb-8">
|
||||||
|
<Button
|
||||||
|
onClick={() => navigate('/create-itinerary')}
|
||||||
|
className="w-full font-poppins font-semibold px-8 py-3 rounded-xl bg-primary hover:bg-primary/90 text-white shadow-md shadow-primary/20"
|
||||||
|
>
|
||||||
|
Create Another Itinerary
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
<Footer
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user