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