source: my-react-app/src/App.js

main
Last change on this file was e48199a, checked in by Aleksandar Panovski <apano77@…>, 10 days ago

Final version for DB

  • Property mode set to 100644
File size: 15.5 KB
Line 
1import {BrowserRouter as Router, Navigate, Route, Routes, useNavigate} from 'react-router-dom';
2
3import Layout from "./components/Layout";
4import React, {useContext, useEffect, useState} from 'react';
5import ErrorPage from "./components/ErrorPage";
6import Restaurants from "./components/Restaurants";
7import Reservations from "./components/Reservations";
8import RestaurantDetails from "./components/RestaurantDetails";
9import ReservationConfirmation from "./components/ReservationConfirmation";
10import ReservationEdit from "./components/ReservationEdit";
11import axios from "axios";
12import { CuisineContext } from './components/CuisineContext';
13import RestaurantInfo from "./components/RestaurantInfo";
14import AuthForm from "./components/AuthForm";
15import AppContent from "./components/AppContent";
16import ReservationHistory from "./components/ReservationHistory";
17import AuthContent from "./components/AuthContent";
18import MenuList from "./components/MenuList";
19import ReadOnlyMenuList from "./components/ReadOnlyMenuList";
20
21const ProtectedRoute = ({ element, isAuthenticated }) => {
22 return isAuthenticated ? element : <Navigate to="/login" />;
23};
24
25const App = () => {
26 const [isAuthenticated, setIsAuthenticated] = useState(false);
27
28 useEffect(() => {
29 const token = localStorage.getItem('token');
30 if (token) {
31 setIsAuthenticated(true);
32 }
33 }, []);
34
35 return (
36 <Router>
37 <Layout>
38 <Routes>
39 <Route path="/" element={<Home />} />
40 <Route path="/restaurants" element={<ProtectedRoute isAuthenticated={isAuthenticated} element={<Restaurants />} />} />
41 <Route path="/restaurants/:id" element={<ProtectedRoute isAuthenticated={isAuthenticated} element={<RestaurantDetails />} />} />
42 <Route path="/reservations" element={<ProtectedRoute isAuthenticated={isAuthenticated} element={<Reservations />} />} />
43 <Route path="/reservationConfirmation/:tableNumber/:timeSlot/:restaurantId" element={<ProtectedRoute isAuthenticated={isAuthenticated} element={<ReservationConfirmation />} />} />
44 <Route path="/reservations/reservationEdit/:reservationId" element={<ProtectedRoute isAuthenticated={isAuthenticated} element={<ReservationEdit />} />} />
45 <Route path="/reservations-past" element={<ProtectedRoute isAuthenticated={isAuthenticated} element={<ReservationHistory />} />} />
46 <Route path="/login" element={<AuthForm setIsAuthenticated={setIsAuthenticated} />} />
47
48 <Route path="/error" element={<ErrorPage />} />
49 </Routes>
50 </Layout>
51 </Router>
52 );
53};
54
55
56const Home = () => {
57 const navigate = useNavigate();
58
59 const todayDate = new Date().toISOString().split('T')[0];
60
61 const [date, setDate] = useState(todayDate);
62 const [selectedTime, setSelectedTime] = useState('');
63 const [numPeople, setNumPeople] = useState(2);
64 const [searchValue, setSearchValue] = useState('');
65 const [timeSlots, setTimeSlots] = useState([]);
66 let [filteredRestaurants, setFilteredRestaurants] = useState([]);
67
68 const cuisineTypes = useContext(CuisineContext);
69 const [showCuisineSearch, setShowCuisineSearch] = useState(true);
70
71 const [formatedDateTime, setFormatedDateTime] = useState('')
72
73 useEffect(() => {
74 if (date) {
75 const selectedDate = new Date(date);
76 const today = new Date();
77 const isToday = selectedDate.toDateString() === today.toDateString();
78
79 let startHour = 9;
80 let startMinute = 0;
81 if (isToday) {
82 const currentHour = today.getHours();
83 const currentMinute = today.getMinutes();
84 if (currentHour > 9 || (currentHour === 9 && currentMinute >= 0)) {
85 startHour = currentHour;
86 startMinute = Math.ceil(currentMinute / 15) * 15;
87 }
88 }
89
90 const startTime = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate(), startHour, startMinute);
91 const endTime = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate(), 23, 30);
92
93 const slots = [];
94 let currentTime = new Date(startTime);
95 while (currentTime <= endTime) {
96 const option = currentTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false });
97 slots.push(option);
98 currentTime.setMinutes(currentTime.getMinutes() + 15);
99 }
100
101 setTimeSlots(slots);
102 }
103 }, [date]);
104
105 const handleGoToRestaurant = (restaurantId) => {
106 navigate(`/restaurants/${restaurantId}`);
107 };
108
109 const handleDateChange = (e) => {
110 setDate(e.target.value);
111 };
112
113 const handleTimeChange = (e) => {
114 setSelectedTime(e.target.value);
115 };
116
117 const handleNumPeopleChange = (e) => {
118 setNumPeople(e.target.value);
119 };
120
121 const handleInputChange = (event) => {
122 setSearchValue(event.target.value);
123 };
124
125 const handleSubmit = async (e) => {
126 e.preventDefault();
127 const [year, month, day] = date.split("-");
128 let formattedDateTime;
129
130 if (selectedTime) {
131 const [selectedHours, selectedMinutes] = selectedTime.split(":");
132 if (!isNaN(selectedHours) && !isNaN(selectedMinutes)) {
133 const dateTime = new Date(Date.UTC(year, month - 1, day, selectedHours, selectedMinutes));
134 formattedDateTime = dateTime.toISOString().slice(0, 16).replace('T', ' ');
135 setFormatedDateTime(formattedDateTime);
136 }
137 } else {
138 const now = new Date();
139 const currentTime = now.getHours() * 60 + now.getMinutes();
140 const nextSlot = timeSlots.find(slot => {
141 const [hours, minutes] = slot.split(":");
142 const slotTime = parseInt(hours) * 60 + parseInt(minutes);
143 return slotTime > currentTime;
144 });
145
146 formattedDateTime = nextSlot ? `${date} ${nextSlot}` : `${date} ${timeSlots[0]}`;
147 }
148
149 const data = {
150 dateTime: formattedDateTime,
151 partySize: numPeople,
152 search: searchValue
153 };
154
155 try {
156 const response = await axios.post('http://localhost:8081/api/search', data);
157 const filteredRestaurants = response.data;
158 setFilteredRestaurants(filteredRestaurants);
159 console.log(filteredRestaurants)
160 setShowCuisineSearch(false);
161 } catch (error) {
162 console.error('Error:', error);
163 }
164 };
165
166 const handleSearchByCuisine = async (cuisine) => {
167 const cuisineName = cuisine.replace('Searching by cuisine: ', '');
168 try {
169 const response = await axios.post(`http://localhost:8081/api/search/shortcut/${cuisineName}`, cuisineName);
170 setFilteredRestaurants(response.data)
171 console.log(response.data)
172 } catch (error) {
173 console.error('Error searching by cuisine:', error);
174 }
175 setShowCuisineSearch(false);
176 };
177 const parseTime = (timeString) => {
178 const [hours, minutes] = timeString.trim().split(':').map(Number);
179 return new Date().setHours(hours, minutes, 0, 0);
180 };
181 const roundToNextQuarter = (date) => {
182 const minutes = date.getMinutes();
183 const roundedMinutes = Math.floor(minutes / 15) * 15;
184 date.setMinutes(roundedMinutes, 0, 0);
185 return date;
186 };
187
188 const shouldMoveToNextDay = (currentTime, endTime) => {
189 return (endTime - currentTime) <= 2 * 60 * 60 * 1000;
190 };
191 const generateTimeSlots = (operatingHours) => {
192 const timeSlots = [];
193 const [startTimeStr, endTimeStr] = operatingHours.split('-').map((time) => time.trim());
194
195 const startTime = parseTime(startTimeStr);
196 let endTime = parseTime(endTimeStr);
197
198 const currentTime = new Date().getTime();
199 if (shouldMoveToNextDay(currentTime, endTime)) {
200 endTime += 24 * 60 * 60 * 1000;
201 }
202
203 let currentTimeSlot = new Date(startTime);
204 currentTimeSlot = roundToNextQuarter(currentTimeSlot);
205
206 while (currentTimeSlot.getTime() < endTime) {
207 timeSlots.push(currentTimeSlot.toISOString());
208 currentTimeSlot.setMinutes(currentTimeSlot.getMinutes() + 15);
209 }
210
211 return timeSlots;
212 };
213
214 const today = new Date();
215 const year = today.getFullYear();
216 const month = String(today.getMonth() + 1).padStart(2, '0');
217 const day = String(today.getDate()).padStart(2, '0');
218 const formattedDate = `${year}-${month}-${day}`;
219
220 const handleTimeSlotClick = (table, timeSlot, restaurant) => {
221 const tableNumber = table.id;
222 const formattedTimeSlot = timeSlot;
223 const restaurantId = restaurant.restaurantId;
224
225 const encodedTableNumber = encodeURIComponent(tableNumber);
226 const encodedTimeSlot = encodeURIComponent(formattedTimeSlot);
227 const encodedRestaurantId = encodeURIComponent(restaurantId);
228
229 navigate(`/reservationConfirmation/${encodedTableNumber}/${encodedTimeSlot}/${encodedRestaurantId}`);
230 };
231
232 const renderTimeSlots = (tablesList, restaurant) => {
233 const currentTime = new Date().getTime();
234 let renderedTimeSlots = {};
235
236 if (tablesList.length === 0) {
237 return <p>No tables available for reservations at this restaurant.</p>;
238 }
239
240 return tablesList.flatMap((table) => {
241 const tableTimeSlots = generateTimeSlots(restaurant.operatingHours);
242
243 if (!renderedTimeSlots[table.capacity]) {
244 renderedTimeSlots[table.capacity] = 0;
245 return (
246 <div key={table.capacity}>
247 <h3>Table for {table.capacity} guests</h3>
248 {tableTimeSlots.map((timeSlot, index) => {
249 const timeSlotTime = new Date(timeSlot).getTime();
250
251 if (timeSlotTime > currentTime && renderedTimeSlots[table.capacity] < 3) {
252 renderedTimeSlots[table.capacity]++;
253 const timeSlotDateTime = new Date(timeSlot);
254 const formattedTime = timeSlotDateTime.toLocaleTimeString([], {
255 hour: '2-digit',
256 minute: '2-digit'
257 });
258
259 return (
260 <button
261 key={index}
262 className="btn btn-primary me-2 mb-2"
263 onClick={() => handleTimeSlotClick(table, timeSlot, restaurant)}
264 >
265 {formattedTime} {}
266 </button>
267 );
268 <br/>
269 } else {
270 return null;
271 }
272 })}
273 </div>
274 );
275 } else {
276 return null;
277 }
278 });
279 };
280
281
282 return (
283 <div className="container">
284 <h2 className="display-1">Rezerviraj masa</h2>
285 <form className="row g-2 align-items-center" onSubmit={handleSubmit}>
286 <div className="col-auto">
287 <input className="form-control me-2" type="date" value={date} onChange={handleDateChange}
288 min={formattedDate}/>
289 </div>
290 <div className="col-auto">
291 <select className="form-select" onChange={handleTimeChange}>
292 {timeSlots.map((slot, index) => (
293 <option key={index} value={slot}>{slot}</option>
294 ))}
295 </select>
296 </div>
297 <div className="col-auto">
298 <select className="form-select" value={numPeople} onChange={handleNumPeopleChange}>
299 {[...Array(20).keys()].map((num) => (
300 <option key={num + 1} value={num + 1}>{num + 1}</option>
301 ))}
302 </select>
303 </div>
304 <div className="col-auto">
305 <input
306 className="form-control me-2"
307 type="search"
308 name="search"
309 placeholder="Restaurant or Cuisine"
310 aria-label="Search"
311 value={searchValue}
312 onChange={handleInputChange}
313 />
314 </div>
315 <div className="col-auto">
316 <button className="btn btn-outline-success" type="submit">Search</button>
317 </div>
318
319 <div className="border-0">
320 {filteredRestaurants.map((restaurant) => (
321 <div key={restaurant.id} className="card mb-3">
322 <div className="card-body">
323 <div className="row">
324
325 <div className="col-md-4">
326 <RestaurantInfo key={restaurant.id} restaurant={restaurant}/>
327 <div className="d-flex flex-wrap">
328 {restaurant.tablesList && restaurant.tablesList.length > 0 ? (
329 renderTimeSlots(restaurant.tablesList, restaurant)
330 ) : (
331 <p>No tables available for reservations at this restaurant</p>
332 )}
333 </div>
334 <button
335 className="btn btn-secondary mt-3"
336 onClick={() => handleGoToRestaurant(restaurant.restaurantId)}
337 >
338 Go to Restaurant
339 </button>
340 </div>
341
342
343 <div className="col-md-8">
344 <ReadOnlyMenuList restaurantId={restaurant.restaurantId}/>
345 </div>
346 </div>
347 </div>
348 </div>
349 ))}
350 </div>
351
352
353 {showCuisineSearch && (
354 <div className="mb-3">
355 <h2 className="display-2">Search by cuisine type</h2>
356 <ul className="list-group">
357 {cuisineTypes.map((cuisine, index) => (
358 <li key={index} className="list-group-item">
359 <button type="button" className="btn btn-outline-primary"
360 onClick={() => handleSearchByCuisine(cuisine)}>
361 {cuisine}
362 </button>
363 </li>
364 ))}
365 </ul>
366 </div>
367 )}
368 </form>
369 </div>
370 );
371}
372
373export default App;
Note: See TracBrowser for help on using the repository browser.