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
RevLine 
[8ca35dc]1import {BrowserRouter as Router, Navigate, Route, Routes, useNavigate} from 'react-router-dom';
[f5b256e]2
[d24f17c]3import Customers from './components/Customers';
4import Layout from "./components/Layout";
[cfc16a3]5import React, {useContext, useEffect, useState} from 'react';
[d24f17c]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";
[65b6638]14import axios from "axios";
[cfc16a3]15import { CuisineContext } from './components/CuisineContext';
[9293100]16import RestaurantInfo from "./components/RestaurantInfo";
[8ca35dc]17import AuthForm from "./components/AuthForm";
[24819a8]18import AppContent from "./components/AppContent";
[8ca35dc]19import ReservationHistory from "./components/ReservationHistory";
[f5b256e]20import AuthContent from "./components/AuthContent";
[8ca35dc]21
22const ProtectedRoute = ({ element, isAuthenticated }) => {
23 return isAuthenticated ? element : <Navigate to="/login" />;
24};
[d24f17c]25
26const App = () => {
[f5b256e]27 const [isAuthenticated, setIsAuthenticated] = useState(false);
[8ca35dc]28
[f5b256e]29 useEffect(() => {
[8ca35dc]30 const token = localStorage.getItem('token');
31 if (token) {
32 setIsAuthenticated(true);
33 }
34 }, []);
35
[d24f17c]36 return (
37 <Router>
38 <Layout>
39 <Routes>
40 <Route path="/" element={<Home />} />
[8ca35dc]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 />} />
[d24f17c]54 </Routes>
55 </Layout>
56 </Router>
57 );
[8ca35dc]58};
[d24f17c]59
60
61const Home = () => {
[e35d0e9]62 const navigate = useNavigate();
63
[f5b256e]64 const todayDate = new Date().toISOString().split('T')[0];
[d24f17c]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([]);
[9293100]71 let [filteredRestaurants, setFilteredRestaurants] = useState([]);
[e35d0e9]72
[cfc16a3]73 const cuisineTypes = useContext(CuisineContext);
[9293100]74 const [showCuisineSearch, setShowCuisineSearch] = useState(true);
[cfc16a3]75
[e35d0e9]76 const [formatedDateTime, setFormatedDateTime] = useState('')
77
[d24f17c]78 useEffect(() => {
79 if (date) {
80 const selectedDate = new Date(date);
81 const today = new Date();
82 const isToday = selectedDate.toDateString() === today.toDateString();
83
[65b6638]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 }
[d24f17c]94
[65b6638]95 const startTime = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate(), startHour, startMinute);
[d24f17c]96 const endTime = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate(), 23, 30);
97
98 const slots = [];
[65b6638]99 let currentTime = new Date(startTime);
[d24f17c]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
[f5b256e]110 const handleGoToRestaurant = (restaurantId) => {
111 navigate(`/restaurants/${restaurantId}`);
112 };
113
[d24f17c]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
[65b6638]130 const handleSubmit = async (e) => {
[d24f17c]131 e.preventDefault();
[65b6638]132 const [year, month, day] = date.split("-");
133 let formattedDateTime;
[cfc16a3]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', ' ');
[9293100]140 setFormatedDateTime(formattedDateTime);
[cfc16a3]141 }
[65b6638]142 } else {
143 const now = new Date();
[f5b256e]144 const currentTime = now.getHours() * 60 + now.getMinutes();
[cfc16a3]145 const nextSlot = timeSlots.find(slot => {
146 const [hours, minutes] = slot.split(":");
[f5b256e]147 const slotTime = parseInt(hours) * 60 + parseInt(minutes);
[cfc16a3]148 return slotTime > currentTime;
149 });
150
151 formattedDateTime = nextSlot ? `${date} ${nextSlot}` : `${date} ${timeSlots[0]}`;
[65b6638]152 }
153
154 const data = {
155 dateTime: formattedDateTime,
156 partySize: numPeople,
157 search: searchValue
158 };
159
160 try {
[8ca35dc]161 const response = await axios.post('http://localhost:8081/api/search', data);
[65b6638]162 const filteredRestaurants = response.data;
[9293100]163 setFilteredRestaurants(filteredRestaurants);
[f5b256e]164 console.log(filteredRestaurants)
[9293100]165 setShowCuisineSearch(false);
[65b6638]166 } catch (error) {
167 console.error('Error:', error);
168 }
[d24f17c]169 };
170
[cfc16a3]171 const handleSearchByCuisine = async (cuisine) => {
172 const cuisineName = cuisine.replace('Searching by cuisine: ', '');
173 try {
[8ca35dc]174 const response = await axios.post(`http://localhost:8081/api/search/shortcut/${cuisineName}`, cuisineName);
[9293100]175 setFilteredRestaurants(response.data)
[f5b256e]176 console.log(response.data)
[cfc16a3]177 } catch (error) {
178 console.error('Error searching by cuisine:', error);
179 }
[9293100]180 setShowCuisineSearch(false);
[cfc16a3]181 };
[f5b256e]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}`;
[65b6638]224
[e35d0e9]225 const handleTimeSlotClick = (table, timeSlot, restaurant) => {
226 const tableNumber = table.id;
227 const formattedTimeSlot = timeSlot;
228 const restaurantId = restaurant.restaurantId;
229
[f5b256e]230 const encodedTableNumber = encodeURIComponent(tableNumber);
[e35d0e9]231 const encodedTimeSlot = encodeURIComponent(formattedTimeSlot);
232 const encodedRestaurantId = encodeURIComponent(restaurantId);
233
234 navigate(`/reservationConfirmation/${encodedTableNumber}/${encodedTimeSlot}/${encodedRestaurantId}`);
[f5b256e]235 };
[e35d0e9]236
[9293100]237 const renderTimeSlots = (tablesList, restaurant) => {
[f5b256e]238 const currentTime = new Date().getTime();
239 let renderedTimeSlots = {};
[9293100]240
[f5b256e]241 if (tablesList.length === 0) {
242 return <p>No tables available for reservations at this restaurant.</p>;
[9293100]243 }
244
[f5b256e]245 return tablesList.flatMap((table) => {
246 const tableTimeSlots = generateTimeSlots(restaurant.operatingHours);
[9293100]247
[f5b256e]248 if (!renderedTimeSlots[table.capacity]) {
[9293100]249 renderedTimeSlots[table.capacity] = 0;
250 return (
251 <div key={table.capacity}>
[f5b256e]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]++;
[9293100]258 const timeSlotDateTime = new Date(timeSlot);
[f5b256e]259 const formattedTime = timeSlotDateTime.toLocaleTimeString([], {
260 hour: '2-digit',
261 minute: '2-digit'
262 });
[9293100]263
264 return (
[f5b256e]265 <button
266 key={index}
267 className="btn btn-primary me-2 mb-2"
268 onClick={() => handleTimeSlotClick(table, timeSlot, restaurant)}
269 >
270 {formattedTime} {}
[9293100]271 </button>
272 );
[f5b256e]273 <br/>
[9293100]274 } else {
[f5b256e]275 return null;
[9293100]276 }
277 })}
278 </div>
279 );
280 } else {
281 return null;
282 }
283 });
[f5b256e]284 };
[65b6638]285
[d24f17c]286
287 return (
288 <div className="container">
[cfc16a3]289 <h2 className="display-1">Rezerviraj masa</h2>
[d24f17c]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}
[65b6638]293 min={formattedDate}/>
[d24f17c]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"
[f5b256e]316 value={searchValue}
317 onChange={handleInputChange}
[d24f17c]318 />
319 </div>
320 <div className="col-auto">
321 <button className="btn btn-outline-success" type="submit">Search</button>
322 </div>
[9293100]323
[e35d0e9]324 <div className="border-0">
[9293100]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}/>
[f5b256e]329 {/*<p>Available time slots</p>*/}
[9293100]330 <div className="d-flex flex-wrap">
[f5b256e]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 )}
[9293100]338 </div>
[f5b256e]339 <button
340 className="btn btn-secondary"
341 onClick={() => handleGoToRestaurant(restaurant.restaurantId)}
342 >
343 Go to Restaurant
344 </button>
[9293100]345 </div>
346 </div>
347 ))}
348 </div>
[cfc16a3]349
[9293100]350 {showCuisineSearch && (
351 <div className="mb-3">
352 <h2 className="display-2">Search by cuisine type</h2>
[f5b256e]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 )}
[d24f17c]365 </form>
366 </div>
367 );
368}
369
370export default App;
Note: See TracBrowser for help on using the repository browser.