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
RevLine 
[8ca35dc]1import {BrowserRouter as Router, Navigate, Route, Routes, useNavigate} from 'react-router-dom';
[f5b256e]2
[d24f17c]3import Layout from "./components/Layout";
[cfc16a3]4import React, {useContext, useEffect, useState} from 'react';
[d24f17c]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";
[65b6638]11import axios from "axios";
[cfc16a3]12import { CuisineContext } from './components/CuisineContext';
[9293100]13import RestaurantInfo from "./components/RestaurantInfo";
[8ca35dc]14import AuthForm from "./components/AuthForm";
[24819a8]15import AppContent from "./components/AppContent";
[8ca35dc]16import ReservationHistory from "./components/ReservationHistory";
[f5b256e]17import AuthContent from "./components/AuthContent";
[c44c5ed]18import MenuList from "./components/MenuList";
19import ReadOnlyMenuList from "./components/ReadOnlyMenuList";
[8ca35dc]20
21const ProtectedRoute = ({ element, isAuthenticated }) => {
22 return isAuthenticated ? element : <Navigate to="/login" />;
23};
[d24f17c]24
25const App = () => {
[f5b256e]26 const [isAuthenticated, setIsAuthenticated] = useState(false);
[8ca35dc]27
[f5b256e]28 useEffect(() => {
[8ca35dc]29 const token = localStorage.getItem('token');
30 if (token) {
31 setIsAuthenticated(true);
32 }
33 }, []);
34
[d24f17c]35 return (
36 <Router>
37 <Layout>
38 <Routes>
39 <Route path="/" element={<Home />} />
[8ca35dc]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 />} />
[d24f17c]49 </Routes>
50 </Layout>
51 </Router>
52 );
[8ca35dc]53};
[d24f17c]54
55
56const Home = () => {
[e35d0e9]57 const navigate = useNavigate();
58
[f5b256e]59 const todayDate = new Date().toISOString().split('T')[0];
[d24f17c]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([]);
[9293100]66 let [filteredRestaurants, setFilteredRestaurants] = useState([]);
[e35d0e9]67
[cfc16a3]68 const cuisineTypes = useContext(CuisineContext);
[9293100]69 const [showCuisineSearch, setShowCuisineSearch] = useState(true);
[cfc16a3]70
[e35d0e9]71 const [formatedDateTime, setFormatedDateTime] = useState('')
72
[d24f17c]73 useEffect(() => {
74 if (date) {
75 const selectedDate = new Date(date);
76 const today = new Date();
77 const isToday = selectedDate.toDateString() === today.toDateString();
78
[65b6638]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 }
[d24f17c]89
[65b6638]90 const startTime = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate(), startHour, startMinute);
[d24f17c]91 const endTime = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate(), 23, 30);
92
93 const slots = [];
[65b6638]94 let currentTime = new Date(startTime);
[d24f17c]95 while (currentTime <= endTime) {
96 const option = currentTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false });
97 slots.push(option);
[e48199a]98 currentTime.setMinutes(currentTime.getMinutes() + 15);
[d24f17c]99 }
100
101 setTimeSlots(slots);
102 }
103 }, [date]);
104
[f5b256e]105 const handleGoToRestaurant = (restaurantId) => {
106 navigate(`/restaurants/${restaurantId}`);
107 };
108
[d24f17c]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
[65b6638]125 const handleSubmit = async (e) => {
[d24f17c]126 e.preventDefault();
[65b6638]127 const [year, month, day] = date.split("-");
128 let formattedDateTime;
[cfc16a3]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', ' ');
[9293100]135 setFormatedDateTime(formattedDateTime);
[cfc16a3]136 }
[65b6638]137 } else {
138 const now = new Date();
[f5b256e]139 const currentTime = now.getHours() * 60 + now.getMinutes();
[cfc16a3]140 const nextSlot = timeSlots.find(slot => {
141 const [hours, minutes] = slot.split(":");
[f5b256e]142 const slotTime = parseInt(hours) * 60 + parseInt(minutes);
[cfc16a3]143 return slotTime > currentTime;
144 });
145
146 formattedDateTime = nextSlot ? `${date} ${nextSlot}` : `${date} ${timeSlots[0]}`;
[65b6638]147 }
148
149 const data = {
150 dateTime: formattedDateTime,
151 partySize: numPeople,
152 search: searchValue
153 };
154
155 try {
[8ca35dc]156 const response = await axios.post('http://localhost:8081/api/search', data);
[65b6638]157 const filteredRestaurants = response.data;
[9293100]158 setFilteredRestaurants(filteredRestaurants);
[f5b256e]159 console.log(filteredRestaurants)
[9293100]160 setShowCuisineSearch(false);
[65b6638]161 } catch (error) {
162 console.error('Error:', error);
163 }
[d24f17c]164 };
165
[cfc16a3]166 const handleSearchByCuisine = async (cuisine) => {
167 const cuisineName = cuisine.replace('Searching by cuisine: ', '');
168 try {
[8ca35dc]169 const response = await axios.post(`http://localhost:8081/api/search/shortcut/${cuisineName}`, cuisineName);
[9293100]170 setFilteredRestaurants(response.data)
[f5b256e]171 console.log(response.data)
[cfc16a3]172 } catch (error) {
173 console.error('Error searching by cuisine:', error);
174 }
[9293100]175 setShowCuisineSearch(false);
[cfc16a3]176 };
[f5b256e]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}`;
[65b6638]219
[e35d0e9]220 const handleTimeSlotClick = (table, timeSlot, restaurant) => {
221 const tableNumber = table.id;
222 const formattedTimeSlot = timeSlot;
223 const restaurantId = restaurant.restaurantId;
224
[f5b256e]225 const encodedTableNumber = encodeURIComponent(tableNumber);
[e35d0e9]226 const encodedTimeSlot = encodeURIComponent(formattedTimeSlot);
227 const encodedRestaurantId = encodeURIComponent(restaurantId);
228
229 navigate(`/reservationConfirmation/${encodedTableNumber}/${encodedTimeSlot}/${encodedRestaurantId}`);
[f5b256e]230 };
[e35d0e9]231
[9293100]232 const renderTimeSlots = (tablesList, restaurant) => {
[f5b256e]233 const currentTime = new Date().getTime();
234 let renderedTimeSlots = {};
[9293100]235
[f5b256e]236 if (tablesList.length === 0) {
237 return <p>No tables available for reservations at this restaurant.</p>;
[9293100]238 }
239
[f5b256e]240 return tablesList.flatMap((table) => {
241 const tableTimeSlots = generateTimeSlots(restaurant.operatingHours);
[9293100]242
[f5b256e]243 if (!renderedTimeSlots[table.capacity]) {
[9293100]244 renderedTimeSlots[table.capacity] = 0;
245 return (
246 <div key={table.capacity}>
[f5b256e]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]++;
[9293100]253 const timeSlotDateTime = new Date(timeSlot);
[f5b256e]254 const formattedTime = timeSlotDateTime.toLocaleTimeString([], {
255 hour: '2-digit',
256 minute: '2-digit'
257 });
[9293100]258
259 return (
[f5b256e]260 <button
261 key={index}
262 className="btn btn-primary me-2 mb-2"
263 onClick={() => handleTimeSlotClick(table, timeSlot, restaurant)}
264 >
265 {formattedTime} {}
[9293100]266 </button>
267 );
[f5b256e]268 <br/>
[9293100]269 } else {
[f5b256e]270 return null;
[9293100]271 }
272 })}
273 </div>
274 );
275 } else {
276 return null;
277 }
278 });
[f5b256e]279 };
[65b6638]280
[d24f17c]281
282 return (
283 <div className="container">
[cfc16a3]284 <h2 className="display-1">Rezerviraj masa</h2>
[d24f17c]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}
[65b6638]288 min={formattedDate}/>
[d24f17c]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"
[f5b256e]311 value={searchValue}
312 onChange={handleInputChange}
[d24f17c]313 />
314 </div>
315 <div className="col-auto">
316 <button className="btn btn-outline-success" type="submit">Search</button>
317 </div>
[9293100]318
[e35d0e9]319 <div className="border-0">
[9293100]320 {filteredRestaurants.map((restaurant) => (
321 <div key={restaurant.id} className="card mb-3">
322 <div className="card-body">
[c44c5ed]323 <div className="row">
[e48199a]324
[c44c5ed]325 <div className="col-md-4">
326 <RestaurantInfo key={restaurant.id} restaurant={restaurant}/>
[f5b256e]327 <div className="d-flex flex-wrap">
[c44c5ed]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 )}
[f5b256e]333 </div>
[c44c5ed]334 <button
335 className="btn btn-secondary mt-3"
336 onClick={() => handleGoToRestaurant(restaurant.restaurantId)}
337 >
338 Go to Restaurant
339 </button>
340 </div>
341
[e48199a]342
[c44c5ed]343 <div className="col-md-8">
344 <ReadOnlyMenuList restaurantId={restaurant.restaurantId}/>
345 </div>
[9293100]346 </div>
347 </div>
348 </div>
349 ))}
350 </div>
[cfc16a3]351
[c44c5ed]352
[9293100]353 {showCuisineSearch && (
354 <div className="mb-3">
355 <h2 className="display-2">Search by cuisine type</h2>
[f5b256e]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 )}
[d24f17c]368 </form>
369 </div>
370 );
371}
372
373export default App;
Note: See TracBrowser for help on using the repository browser.