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

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

Big change done works with handle_reservation_update() trigger

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