Skip to content

Commit 891493a

Browse files
committed
Add TripDetailsPage component; implement trip details fetching and routing
1 parent 9c77e3c commit 891493a

4 files changed

Lines changed: 237 additions & 2 deletions

File tree

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import { useState, useEffect } from 'react';
2+
import { useParams, useNavigate } from 'react-router-dom';
3+
import {
4+
Box,
5+
Paper,
6+
Typography,
7+
Tabs,
8+
Tab,
9+
Button,
10+
Skeleton,
11+
Alert,
12+
Chip,
13+
Stack,
14+
Divider
15+
} from '@mui/material';
16+
import {
17+
AccessTime,
18+
LocationOn,
19+
Edit as EditIcon,
20+
ArrowBack as ArrowBackIcon
21+
} from '@mui/icons-material';
22+
import { tripService } from '../services/tripService';
23+
import dayjs from 'dayjs';
24+
25+
const TabPanel = ({ children, value, index }) => (
26+
<div hidden={value !== index} style={{ padding: '24px 0' }}>
27+
{value === index && children}
28+
</div>
29+
);
30+
31+
const TripDetailsPage = () => {
32+
const { tripId } = useParams();
33+
const navigate = useNavigate();
34+
const [trip, setTrip] = useState(null);
35+
const [loading, setLoading] = useState(true);
36+
const [error, setError] = useState(null);
37+
const [tabValue, setTabValue] = useState(0);
38+
39+
useEffect(() => {
40+
const fetchTripDetails = async () => {
41+
try {
42+
setLoading(true);
43+
const response = await tripService.getTrip(tripId);
44+
console.log('TripDetailsPage received:', response);
45+
46+
if (!response || !response.trip) {
47+
throw new Error('Trip not found');
48+
}
49+
50+
setTrip(response.trip);
51+
} catch (err) {
52+
console.error('Error fetching trip:', err);
53+
setError(err.message || 'Failed to load trip details');
54+
} finally {
55+
setLoading(false);
56+
}
57+
};
58+
59+
if (tripId) {
60+
fetchTripDetails();
61+
}
62+
}, [tripId]);
63+
64+
const handleTabChange = (event, newValue) => {
65+
setTabValue(newValue);
66+
};
67+
68+
if (loading) {
69+
return (
70+
<Box sx={{ maxWidth: 1200, mx: 'auto', p: 3 }}>
71+
<Skeleton variant="text" height={40} width={200} />
72+
<Skeleton variant="rectangular" height={200} sx={{ mt: 2 }} />
73+
</Box>
74+
);
75+
}
76+
77+
if (error) {
78+
return (
79+
<Box sx={{ maxWidth: 1200, mx: 'auto', p: 3 }}>
80+
<Alert
81+
severity="error"
82+
action={
83+
<Button color="inherit" size="small" onClick={() => navigate('/dashboard')}>
84+
Back to Dashboard
85+
</Button>
86+
}
87+
>
88+
{error}
89+
</Alert>
90+
</Box>
91+
);
92+
}
93+
94+
if (!trip) {
95+
return (
96+
<Box sx={{ maxWidth: 1200, mx: 'auto', p: 3 }}>
97+
<Alert severity="error">
98+
Trip not found
99+
</Alert>
100+
</Box>
101+
);
102+
}
103+
104+
return (
105+
<Box sx={{ maxWidth: 1200, mx: 'auto', p: 3 }}>
106+
<Button
107+
startIcon={<ArrowBackIcon />}
108+
onClick={() => navigate('/dashboard')}
109+
sx={{ mb: 3 }}
110+
>
111+
Back to Dashboard
112+
</Button>
113+
114+
<Paper elevation={2} sx={{ p: 3, mb: 3 }}>
115+
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 3 }}>
116+
<Box>
117+
<Typography variant="h4" gutterBottom>
118+
{trip.title}
119+
</Typography>
120+
<Stack direction="row" spacing={2} alignItems="center">
121+
<Chip
122+
icon={<LocationOn />}
123+
label={trip.destination}
124+
color="primary"
125+
variant="outlined"
126+
/>
127+
<Chip
128+
icon={<AccessTime />}
129+
label={`${dayjs(trip.start_date).format('MMM D')} - ${dayjs(trip.end_date).format('MMM D, YYYY')}`}
130+
variant="outlined"
131+
/>
132+
</Stack>
133+
</Box>
134+
<Button
135+
variant="outlined"
136+
startIcon={<EditIcon />}
137+
onClick={() => navigate(`/trips/${tripId}/edit`)}
138+
>
139+
Edit Trip
140+
</Button>
141+
</Box>
142+
143+
<Divider sx={{ my: 2 }} />
144+
145+
<Tabs value={tabValue} onChange={handleTabChange} sx={{ borderBottom: 1, borderColor: 'divider' }}>
146+
<Tab label="Overview" />
147+
<Tab label="Itinerary" />
148+
<Tab label="Expenses" />
149+
<Tab label="Notes" />
150+
</Tabs>
151+
152+
<TabPanel value={tabValue} index={0}>
153+
<Box>
154+
<Typography variant="h6" gutterBottom>
155+
Trip Overview
156+
</Typography>
157+
{/* Add overview content */}
158+
</Box>
159+
</TabPanel>
160+
161+
<TabPanel value={tabValue} index={1}>
162+
<Box>
163+
<Typography variant="h6" gutterBottom>
164+
Itinerary
165+
</Typography>
166+
{/* Add itinerary content */}
167+
</Box>
168+
</TabPanel>
169+
170+
<TabPanel value={tabValue} index={2}>
171+
<Box>
172+
<Typography variant="h6" gutterBottom>
173+
Expenses
174+
</Typography>
175+
{/* Add expenses content */}
176+
</Box>
177+
</TabPanel>
178+
179+
<TabPanel value={tabValue} index={3}>
180+
<Box>
181+
<Typography variant="h6" gutterBottom>
182+
Notes
183+
</Typography>
184+
{/* Add notes content */}
185+
</Box>
186+
</TabPanel>
187+
</Paper>
188+
</Box>
189+
);
190+
};
191+
192+
export default TripDetailsPage;

planventure-client/src/routes/routes.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import SignUpPage from '../pages/SignUpPage';
44
import Dashboard from '../pages/Dashboard';
55
import DashboardLayout from '../layouts/DashboardLayout';
66
import NewTripPage from '../pages/NewTripPage';
7+
import TripDetailsPage from '../pages/TripDetailsPage';
78

89
export const publicRoutes = [
910
{
@@ -32,5 +33,9 @@ export const protectedRoutes = [
3233
{
3334
path: '/trips/new',
3435
element: <DashboardLayout><NewTripPage /></DashboardLayout>,
36+
},
37+
{
38+
path: '/trips/:tripId',
39+
element: <DashboardLayout><TripDetailsPage /></DashboardLayout>,
3540
}
3641
];

planventure-client/src/services/api.jsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,16 @@ const handleResponse = async (response) => {
1515
throw new Error('Session expired. Please login again.');
1616
}
1717

18+
if (response.status === 404) {
19+
throw new Error('Trip not found');
20+
}
21+
1822
const data = await response.json();
1923
if (!response.ok) {
20-
throw new Error(data.error || 'Request failed');
24+
throw new Error(data.error || data.message || 'Request failed');
2125
}
26+
27+
console.log('API Response:', data); // Debug log
2228
return data;
2329
};
2430

planventure-client/src/services/tripService.jsx

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,42 @@ export const tripService = {
2828

2929
getTrip: async (tripId) => {
3030
try {
31+
console.log('Fetching trip with ID:', tripId);
3132
const response = await api.get(`/api/trips/${tripId}`);
33+
console.log('Raw trip response:', response);
34+
35+
// If the response itself is the trip data
36+
if (response && response.id) {
37+
return { trip: response };
38+
}
39+
40+
// If the trip is nested in a data or trips property
41+
if (response && (response.data || response.trips)) {
42+
return { trip: response.data || response.trips };
43+
}
44+
45+
throw new Error('Invalid response format from server');
46+
} catch (error) {
47+
console.error('Error in getTrip:', error);
48+
throw new Error(error.message || 'Failed to fetch trip details');
49+
}
50+
},
51+
52+
updateTrip: async (tripId, tripData) => {
53+
try {
54+
const response = await api.put(`/api/trips/${tripId}`, tripData);
55+
return response.data;
56+
} catch (error) {
57+
throw new Error(error.response?.data?.message || 'Failed to update trip');
58+
}
59+
},
60+
61+
deleteTrip: async (tripId) => {
62+
try {
63+
const response = await api.delete(`/api/trips/${tripId}`);
3264
return response.data;
3365
} catch (error) {
34-
throw new Error(error.response?.data?.message || 'Failed to fetch trip');
66+
throw new Error(error.response?.data?.message || 'Failed to delete trip');
3567
}
3668
}
3769
};

0 commit comments

Comments
 (0)