source: my-react-app/src/components/ReservationHistory.js@ c44c5ed

main
Last change on this file since c44c5ed was c44c5ed, checked in by Aleksandar Panovski <apano77@…>, 2 weeks ago

Fixed bugs and fully developed menu now. Readonly added.

  • Property mode set to 100644
File size: 12.8 KB
Line 
1import React, { useEffect, useState } from "react";
2import { jwtDecode } from "jwt-decode";
3import axios from "axios";
4import 'bootstrap/dist/css/bootstrap.min.css';
5import 'bootstrap/dist/js/bootstrap.bundle.min';
6
7const ReservationHistory = () => {
8 const [reservations, setReservations] = useState([]);
9 const [filteredReservations, setFilteredReservations] = useState([]);
10 const [selectedReservation, setSelectedReservation] = useState(null);
11 const [loading, setLoading] = useState(true);
12 const [error, setError] = useState(null);
13
14 const [restaurantFilter, setRestaurantFilter] = useState("");
15 const [tableFilter, setTableFilter] = useState("");
16 const [partySizeFilter, setPartySizeFilter] = useState("");
17 const [statusFilter, setStatusFilter] = useState("");
18 const [cancellationReasonFilter, setCancellationReasonFilter] = useState("");
19 const [startDate, setStartDate] = useState("");
20 const [endDate, setEndDate] = useState("");
21
22 useEffect(() => {
23 const fetchReservations = async () => {
24 try {
25 const token = localStorage.getItem("token");
26 if (!token) {
27 setError("User not authenticated.");
28 setLoading(false);
29 return;
30 }
31
32 const decodedToken = jwtDecode(token);
33 const userId = decodedToken.iss;
34
35 const response = await axios.get(
36 `http://localhost:8081/api/past-reservations/${userId}`,
37 {
38 headers: { Authorization: `Bearer ${token}` },
39 }
40 );
41
42 setReservations(response.data);
43 setFilteredReservations(response.data);
44 } catch (err) {
45 setError("Failed to fetch reservations.");
46 } finally {
47 setLoading(false);
48 }
49 };
50
51 fetchReservations();
52 }, []);
53
54 useEffect(() => {
55 let tempReservations = reservations;
56
57 if (restaurantFilter) {
58 tempReservations = tempReservations.filter(res =>
59 res.restaurant?.name.toLowerCase().includes(restaurantFilter.toLowerCase())
60 );
61 }
62 if (tableFilter) {
63 tempReservations = tempReservations.filter(res =>
64 res.table?.id?.toString() === tableFilter
65 );
66 }
67 if (partySizeFilter) {
68 tempReservations = tempReservations.filter(res =>
69 res.partySize?.toString() === partySizeFilter
70 );
71 }
72 if (statusFilter) {
73 tempReservations = tempReservations.filter(res =>
74 res.status.toLowerCase().includes(statusFilter.toLowerCase())
75 );
76 }
77 if (cancellationReasonFilter) {
78 tempReservations = tempReservations.filter(res =>
79 (res.cancellationReason || "None").toLowerCase().includes(cancellationReasonFilter.toLowerCase())
80 );
81 }
82 if (startDate && endDate) {
83 const start = new Date(startDate);
84 const end = new Date(endDate);
85
86 tempReservations = tempReservations.filter(res => {
87 const reservationDate = new Date(res.reservationDateTime);
88 return reservationDate >= start && reservationDate <= end;
89 });
90 }
91
92 setFilteredReservations(tempReservations);
93 }, [
94 restaurantFilter,
95 tableFilter,
96 partySizeFilter,
97 statusFilter,
98 cancellationReasonFilter,
99 startDate,
100 endDate,
101 reservations
102 ]);
103
104 const resetFilters = () => {
105 setRestaurantFilter("");
106 setTableFilter("");
107 setPartySizeFilter("");
108 setStatusFilter("");
109 setCancellationReasonFilter("");
110 setStartDate("");
111 setEndDate("");
112 };
113
114 if (loading) return <div>Loading...</div>;
115 if (error) return <div className="alert alert-danger">{error}</div>;
116
117 const formatDateTime = (dateString) => {
118 if (!dateString) return "N/A";
119 const date = new Date(dateString);
120 return date.toLocaleString("en-GB", {
121 day: "2-digit",
122 month: "short",
123 year: "numeric",
124 hour: "2-digit",
125 minute: "2-digit",
126 });
127 };
128
129 return (
130 <div className="container mt-5">
131 <h3 className="mb-4 text-center">Past Reservations</h3>
132
133 <div className="row mb-4 align-items-end">
134 <div className="col-md-2">
135 <input
136 type="text"
137 className="form-control"
138 placeholder="Filter by Restaurant"
139 value={restaurantFilter}
140 onChange={(e) => setRestaurantFilter(e.target.value)}
141 />
142 </div>
143 <div className="col-md-2">
144 <input
145 type="number"
146 className="form-control"
147 placeholder="Filter by Table ID"
148 value={tableFilter}
149 onChange={(e) => setTableFilter(e.target.value)}
150 />
151 </div>
152 <div className="col-md-4 d-flex gap-2">
153 <input
154 type="date"
155 className="form-control"
156 value={startDate}
157 onChange={(e) => setStartDate(e.target.value)}
158 />
159 <input
160 type="date"
161 className="form-control"
162 value={endDate}
163 onChange={(e) => setEndDate(e.target.value)}
164 />
165 </div>
166 <div className="col-md-2">
167 <input
168 type="number"
169 className="form-control"
170 placeholder="Party Size"
171 value={partySizeFilter}
172 onChange={(e) => setPartySizeFilter(e.target.value)}
173 />
174 </div>
175 <div className="col-md-2">
176 <select
177 value={statusFilter}
178 onChange={(e) => setStatusFilter(e.target.value)}
179 className="form-control"
180 >
181 <option value="">Filter by Status</option>
182 <option value="successful">Successful</option>
183 <option value="canceled">Canceled</option>
184 </select>
185 </div>
186 <div className="col-md-2 mt-2">
187 <input
188 type="text"
189 className="form-control"
190 placeholder="Cancellation Reason"
191 value={cancellationReasonFilter}
192 onChange={(e) => setCancellationReasonFilter(e.target.value)}
193 />
194 </div>
195 <div className="col-md-2 mt-2">
196 <button
197 onClick={resetFilters}
198 className="btn btn-outline-secondary w-100"
199 >
200 Reset Filters
201 </button>
202 </div>
203 </div>
204
205 <div className="table-responsive">
206 <table className="table table-striped table-hover table-bordered align-middle">
207 <thead className="table-dark text-center">
208 <tr>
209 <th>#</th>
210 <th>Restaurant</th>
211 <th>Table</th>
212 <th>Check In Date</th>
213 <th>Reserved on</th>
214 <th>Party Size</th>
215 <th>Special Requests</th>
216 <th>Status</th>
217 <th>Cancellation Reason</th>
218 </tr>
219 </thead>
220 <tbody>
221 {filteredReservations.length > 0 ? (
222 filteredReservations.map((res, index) => (
223 <tr key={res.id} onClick={() => setSelectedReservation(res)} style={{ cursor: "pointer" }}>
224 <td>{index + 1}</td>
225 <td>{res.restaurant?.name || "N/A"}</td>
226 <td>{res.table?.id || "N/A"}</td>
227 <td>{formatDateTime(res.checkInDate)}</td>
228 <td>{new Date(res.reservationDateTime).toLocaleString()}</td>
229 <td>{res.partySize}</td>
230 <td>{res.specialRequests || "None"}</td>
231 <td>{res.status}</td>
232 <td>{res.cancellationReason || "None"}</td>
233 </tr>
234 ))
235 ) : (
236 <tr>
237 <td colSpan="9" className="text-center">No reservations found.</td>
238 </tr>
239 )}
240 </tbody>
241 </table>
242 </div>
243
244 {/* Modal for reservation details */}
245 {selectedReservation && (
246 <div className="modal fade show d-block" tabIndex="-1" style={{ backgroundColor: "rgba(0,0,0,0.5)" }} onClick={() => setSelectedReservation(null)}>
247 <div className="modal-dialog modal-lg" onClick={(e) => e.stopPropagation()}>
248 <div className="modal-content">
249 <div className="modal-header">
250 <h5 className="modal-title">Reservation Details</h5>
251 <button type="button" className="btn-close" onClick={() => setSelectedReservation(null)}></button>
252 </div>
253 <div className="modal-body">
254 <h6>🍽️ Restaurant Info</h6>
255 <p><strong>Name:</strong> {selectedReservation.restaurant?.name}</p>
256 <p><strong>Address:</strong> {selectedReservation.restaurant?.address}</p>
257 <p><strong>Phone:</strong> {selectedReservation.restaurant?.phone}</p>
258 <p><strong>Rating:</strong> {selectedReservation.restaurant?.rating} ⭐</p>
259 <p><strong>Cuisine:</strong> {selectedReservation.restaurant?.cuisineType}</p>
260 <p><strong>Hours:</strong> {selectedReservation.restaurant?.operatingHours}</p>
261 {selectedReservation.restaurant?.website && (
262 <p><strong>Website:</strong> <a href={`https://${selectedReservation.restaurant.website}`} target="_blank" rel="noopener noreferrer">{selectedReservation.restaurant.website}</a></p>
263 )}
264
265 <hr />
266
267 <h6>🪑 Table Info</h6>
268 <p><strong>Location:</strong> {selectedReservation.table?.location}</p>
269 <p><strong>Capacity:</strong> {selectedReservation.table?.capacity}</p>
270 <p><strong>Smoking Area:</strong> {selectedReservation.table?.smokingArea ? "Yes" : "No"}</p>
271 <p><strong>Description:</strong> {selectedReservation.table?.description}</p>
272 <p><strong>Reservation Duration:</strong> {selectedReservation.table?.reservationDurationHours} hours</p>
273
274 <hr />
275
276 <h6>📋 Reservation Info</h6>
277 <p><strong>Check-In Date:</strong> {formatDateTime(selectedReservation.checkInDate)}</p>
278 <p><strong>Reserved on:</strong> {new Date(selectedReservation.reservationDateTime).toLocaleString()}</p>
279 <p><strong>Party Size:</strong> {selectedReservation.partySize}</p>
280 <p><strong>Special Requests:</strong> {selectedReservation.specialRequests || "None"}</p>
281 <p><strong>Status:</strong> {selectedReservation.status}</p>
282 <p><strong>Cancellation Reason:</strong> {selectedReservation.cancellationReason || "None"}</p>
283 </div>
284 <div className="modal-footer">
285 <button className="btn btn-secondary" onClick={() => setSelectedReservation(null)}>Close</button>
286 </div>
287 </div>
288 </div>
289 </div>
290 )}
291 </div>
292 );
293};
294
295export default ReservationHistory;
Note: See TracBrowser for help on using the repository browser.