Merge pull request 'enable the download itinerary feature in itinerary summary page' (#25) from arya-branch into main
Reviewed-on: #25
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'motion/react';
|
||||
import {
|
||||
MapPin,
|
||||
@@ -7,11 +7,12 @@ import {
|
||||
Share2,
|
||||
Download,
|
||||
ChevronRight,
|
||||
Loader2,
|
||||
} from 'lucide-react';
|
||||
import { Button } from '../components/ui/button';
|
||||
import { Card, CardContent } from '../components/ui/card';
|
||||
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 { useNavigate, useParams } from 'react-router-dom';
|
||||
import Navbar from '../components/Navbar';
|
||||
@@ -26,6 +27,37 @@ const ItinerarySummaryPage = () => {
|
||||
|
||||
const { itineraryId } = useParams()
|
||||
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 days = generatedItinerary?.days ?? [];
|
||||
@@ -35,305 +67,312 @@ const ItinerarySummaryPage = () => {
|
||||
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
{/* Navbar */}
|
||||
<Navbar
|
||||
/>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="space-y-8 max-w-3xl mx-auto mt-25"
|
||||
>
|
||||
{/* Title */}
|
||||
<div className="text-center space-y-1">
|
||||
<h1 className="font-merchant text-3xl md:text-4xl lg:text-5xl leading-tight">
|
||||
<span className="font-normal">Your</span>
|
||||
</h1>
|
||||
<h1 className="font-merchant text-3xl md:text-4xl lg:text-5xl leading-tight">
|
||||
<span className="font-bold text-primary italic">{generatedItinerary?.title}</span>
|
||||
</h1>
|
||||
</div>
|
||||
<div className="min-h-screen bg-background">
|
||||
{/* Navbar */}
|
||||
<Navbar
|
||||
/>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="space-y-8 max-w-3xl mx-auto mt-25"
|
||||
>
|
||||
{/* Title */}
|
||||
<div className="text-center space-y-1">
|
||||
<h1 className="font-merchant text-3xl md:text-4xl lg:text-5xl leading-tight">
|
||||
<span className="font-normal">Your</span>
|
||||
</h1>
|
||||
<h1 className="font-merchant text-3xl md:text-4xl lg:text-5xl leading-tight">
|
||||
<span className="font-bold text-primary italic">{generatedItinerary?.title}</span>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Trip Details Card */}
|
||||
<div className="relative overflow-hidden rounded-2xl border border-gray-100 shadow-sm">
|
||||
{/* Background Image */}
|
||||
<div className="relative h-40 md:h-48">
|
||||
<ImageWithFallback
|
||||
src={generatedItinerary?.cityBanner}
|
||||
alt={generatedItinerary?.city}
|
||||
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 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>
|
||||
<h3 className="font-merchant text-2xl md:text-3xl text-white leading-snug font-semibold">{generatedItinerary?.city}</h3>
|
||||
{/* Trip Details Card */}
|
||||
<div className="relative overflow-hidden rounded-2xl border border-gray-100 shadow-sm">
|
||||
{/* Background Image */}
|
||||
<div className="relative h-40 md:h-48">
|
||||
<ImageWithFallback
|
||||
src={generatedItinerary?.cityBanner}
|
||||
alt={generatedItinerary?.city}
|
||||
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 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>
|
||||
<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>
|
||||
{/* 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 */}
|
||||
<div className="flex gap-3">
|
||||
<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"
|
||||
>
|
||||
<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'
|
||||
}`}
|
||||
{/* Share & Download Buttons */}
|
||||
<div className="flex gap-3">
|
||||
<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"
|
||||
>
|
||||
Daily View
|
||||
</button>
|
||||
<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'
|
||||
}`}
|
||||
<Share2 className="w-4 h-4 mr-2" />
|
||||
Share
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleDownloadItinerary}
|
||||
disabled={isDownloading}
|
||||
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
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 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>
|
||||
{isDownloading ? (
|
||||
<Loader2 className="w-5 h-5 mr-2 animate-spin" />
|
||||
) : (
|
||||
<Download className="w-5 h-5 mr-2" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Activities for selected day */}
|
||||
{selectedDayPlan && (
|
||||
<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>
|
||||
)}
|
||||
Download
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Summary View */}
|
||||
{viewMode === 'summary' && (
|
||||
<div className="space-y-6">
|
||||
{days?.map((day: any, dayIndex: number) => {
|
||||
const dayDate = days[0]?.date
|
||||
? new Date(
|
||||
new Date(days[0].date).setDate(
|
||||
new Date(days[0].date).getDate() + dayIndex
|
||||
)
|
||||
).toLocaleDateString('en-AU', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
})
|
||||
: '';
|
||||
{/* 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
|
||||
</button>
|
||||
<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
|
||||
const daySummary = summaries.find((s: any) => s.dayNumber === day.dayNumber);
|
||||
{/* 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>
|
||||
|
||||
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;
|
||||
{/* Activities for selected day */}
|
||||
{selectedDayPlan && (
|
||||
<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 (
|
||||
<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>
|
||||
<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>
|
||||
|
||||
<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">
|
||||
{/* 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
|
||||
Get Directions
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</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>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
)}
|
||||
</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>
|
||||
{/* Summary View */}
|
||||
{viewMode === 'summary' && (
|
||||
<div className="space-y-6">
|
||||
{days?.map((day: any, dayIndex: number) => {
|
||||
const dayDate = days[0]?.date
|
||||
? new Date(
|
||||
new Date(days[0].date).setDate(
|
||||
new Date(days[0].date).getDate() + dayIndex
|
||||
)
|
||||
).toLocaleDateString('en-AU', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
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