Changeset f5b256e
- Timestamp:
- 04/28/25 14:20:18 (3 weeks ago)
- Branches:
- main
- Children:
- deea3c4
- Parents:
- 8ca35dc
- Files:
-
- 6 added
- 6 deleted
- 15 edited
Legend:
- Unmodified
- Added
- Removed
-
my-react-app/package-lock.json
r8ca35dc rf5b256e 16 16 "jwt-decode": "^4.0.0", 17 17 "react": "^18.2.0", 18 "react-bootstrap-datetimepicker": "^0.0.22", 18 19 "react-dom": "^18.2.0", 19 20 "react-router-dom": "^6.22.0", … … 5769 5770 } 5770 5771 }, 5772 "node_modules/babel-runtime": { 5773 "version": "5.8.38", 5774 "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.38.tgz", 5775 "integrity": "sha512-KpgoA8VE/pMmNCrnEeeXqFG24TIH11Z3ZaimIhJWsin8EbfZy3WzFKUTIan10ZIDgRVvi9EkLbruJElJC9dRlg==", 5776 "dependencies": { 5777 "core-js": "^1.0.0" 5778 } 5779 }, 5780 "node_modules/babel-runtime/node_modules/core-js": { 5781 "version": "1.2.7", 5782 "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", 5783 "integrity": "sha512-ZiPp9pZlgxpWRu0M+YWbm6+aQ84XEfH1JRXvfOc/fILWI0VKhLC2LX13X1NYq4fULzLMq7Hfh43CSo2/aIaUPA==", 5784 "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js." 5785 }, 5771 5786 "node_modules/balanced-match": { 5772 5787 "version": "1.0.2", … … 6167 6182 "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", 6168 6183 "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==" 6184 }, 6185 "node_modules/classnames": { 6186 "version": "2.5.1", 6187 "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", 6188 "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" 6169 6189 }, 6170 6190 "node_modules/clean-css": { … … 12722 12742 "bin": { 12723 12743 "mkdirp": "bin/cmd.js" 12744 } 12745 }, 12746 "node_modules/moment": { 12747 "version": "2.30.1", 12748 "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", 12749 "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", 12750 "engines": { 12751 "node": "*" 12724 12752 } 12725 12753 }, … … 14895 14923 "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" 14896 14924 }, 14925 "node_modules/react-bootstrap-datetimepicker": { 14926 "version": "0.0.22", 14927 "resolved": "https://registry.npmjs.org/react-bootstrap-datetimepicker/-/react-bootstrap-datetimepicker-0.0.22.tgz", 14928 "integrity": "sha512-1va2drwnGtjBtK38TKHS7cJ2lYjjPDYOJwbUaam1ctpdxhPbYLyjg8ahrflLjqIQQCsUTmPJg0W4NopbUoGKlw==", 14929 "dependencies": { 14930 "babel-runtime": "^5.6.18", 14931 "classnames": "^2.1.2", 14932 "moment": "^2.8.2" 14933 }, 14934 "peerDependencies": { 14935 "react": ">=0.14" 14936 } 14937 }, 14897 14938 "node_modules/react-dev-utils": { 14898 14939 "version": "12.0.1", -
my-react-app/package.json
r8ca35dc rf5b256e 11 11 "jwt-decode": "^4.0.0", 12 12 "react": "^18.2.0", 13 "react-bootstrap-datetimepicker": "^0.0.22", 13 14 "react-dom": "^18.2.0", 14 15 "react-router-dom": "^6.22.0", -
my-react-app/src/App.js
r8ca35dc rf5b256e 1 1 import {BrowserRouter as Router, Navigate, Route, Routes, useNavigate} from 'react-router-dom'; 2 2 3 import Customers from './components/Customers'; 3 4 import Layout from "./components/Layout"; … … 17 18 import AppContent from "./components/AppContent"; 18 19 import ReservationHistory from "./components/ReservationHistory"; 20 import AuthContent from "./components/AuthContent"; 19 21 20 22 const ProtectedRoute = ({ element, isAuthenticated }) => { … … 23 25 24 26 const App = () => { 25 const [isAuthenticated, setIsAuthenticated] = React.useState(false);26 27 React.useEffect(() => {27 const [isAuthenticated, setIsAuthenticated] = useState(false); 28 29 useEffect(() => { 28 30 const token = localStorage.getItem('token'); 29 31 if (token) { … … 60 62 const navigate = useNavigate(); 61 63 62 const todayDate = new Date().toISOString().split('T')[0]; // Get today's date in 'YYYY-MM-DD' format64 const todayDate = new Date().toISOString().split('T')[0]; 63 65 64 66 const [date, setDate] = useState(todayDate); … … 80 82 const isToday = selectedDate.toDateString() === today.toDateString(); 81 83 82 // Determine the start hour and minute83 84 let startHour = 9; 84 85 let startMinute = 0; … … 86 87 const currentHour = today.getHours(); 87 88 const currentMinute = today.getMinutes(); 88 // If current time is later than 09:00, start from the current hour and minute89 89 if (currentHour > 9 || (currentHour === 9 && currentMinute >= 0)) { 90 90 startHour = currentHour; … … 93 93 } 94 94 95 // Create the start time and end time96 95 const startTime = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate(), startHour, startMinute); 97 96 const endTime = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate(), 23, 30); 98 97 99 // Generate time slots from start time to end time in 15-minute intervals100 98 const slots = []; 101 99 let currentTime = new Date(startTime); … … 106 104 } 107 105 108 // Update the timeSlots state109 106 setTimeSlots(slots); 110 107 } 111 108 }, [date]); 109 110 const handleGoToRestaurant = (restaurantId) => { 111 navigate(`/restaurants/${restaurantId}`); 112 }; 112 113 113 114 const handleDateChange = (e) => { … … 134 135 if (selectedTime) { 135 136 const [selectedHours, selectedMinutes] = selectedTime.split(":"); 136 // Check if selectedHours and selectedMinutes are valid numbers137 137 if (!isNaN(selectedHours) && !isNaN(selectedMinutes)) { 138 138 const dateTime = new Date(Date.UTC(year, month - 1, day, selectedHours, selectedMinutes)); … … 141 141 } 142 142 } else { 143 // Find the first available time slot after the current time144 143 const now = new Date(); 145 const currentTime = now.getHours() * 60 + now.getMinutes(); // Current time in minutes144 const currentTime = now.getHours() * 60 + now.getMinutes(); 146 145 const nextSlot = timeSlots.find(slot => { 147 146 const [hours, minutes] = slot.split(":"); 148 const slotTime = parseInt(hours) * 60 + parseInt(minutes); // Time of the slot in minutes147 const slotTime = parseInt(hours) * 60 + parseInt(minutes); 149 148 return slotTime > currentTime; 150 149 }); 151 150 152 // If no slot is found after the current time, use the first slot of the day153 151 formattedDateTime = nextSlot ? `${date} ${nextSlot}` : `${date} ${timeSlots[0]}`; 154 152 } … … 160 158 }; 161 159 162 console.log("Data to be submitted:");163 console.log(data);164 165 160 try { 166 161 const response = await axios.post('http://localhost:8081/api/search', data); 167 162 const filteredRestaurants = response.data; 168 163 setFilteredRestaurants(filteredRestaurants); 169 console.log(filteredRestaurants) ;164 console.log(filteredRestaurants) 170 165 setShowCuisineSearch(false); 171 // Handle response accordingly172 166 } catch (error) { 173 167 console.error('Error:', error); … … 179 173 try { 180 174 const response = await axios.post(`http://localhost:8081/api/search/shortcut/${cuisineName}`, cuisineName); 181 console.log(response.data);182 175 setFilteredRestaurants(response.data) 176 console.log(response.data) 183 177 } catch (error) { 184 178 console.error('Error searching by cuisine:', error); … … 186 180 setShowCuisineSearch(false); 187 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}`; 188 224 189 225 const handleTimeSlotClick = (table, timeSlot, restaurant) => { … … 192 228 const restaurantId = restaurant.restaurantId; 193 229 194 const encodedTableNumber = encodeURI (tableNumber);230 const encodedTableNumber = encodeURIComponent(tableNumber); 195 231 const encodedTimeSlot = encodeURIComponent(formattedTimeSlot); 196 232 const encodedRestaurantId = encodeURIComponent(restaurantId); 197 233 198 234 navigate(`/reservationConfirmation/${encodedTableNumber}/${encodedTimeSlot}/${encodedRestaurantId}`); 199 } 235 }; 200 236 201 237 const renderTimeSlots = (tablesList, restaurant) => { 202 const [year, month, day] = date.split("-"); 203 const [hours, minutes] = selectedTime.split(":"); 204 const dateTime = new Date(year, month - 1, day, hours, minutes); // month is zero-based 205 206 let timestamp = dateTime.getTime(); 207 if (isNaN(timestamp)) { 208 timestamp = Date.now(); 209 } 210 211 let renderedTimeSlots = {}; // Object to store rendered slots for each table capacity 212 213 return tablesList.flatMap(table => { 214 // Render capacity header when encountering a new capacity 215 if (!renderedTimeSlots[table.capacity] && numPeople <= table.capacity) { 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]) { 216 249 renderedTimeSlots[table.capacity] = 0; 217 250 return ( 218 251 <div key={table.capacity}> 219 <h3>Table for: {table.capacity}</h3> 220 {table.timeSlots.map((timeSlot, index) => { 221 let timeSlotTime = new Date(timeSlot).getTime(); 222 223 const tableCapacity = table.capacity; 224 // Check if the time slot is after the current time, numPeople is less than or equal to tableCapacity, and limit to 5 slots 225 if (timeSlotTime >= timestamp && numPeople <= tableCapacity && renderedTimeSlots[tableCapacity] < 5) { 226 renderedTimeSlots[tableCapacity]++; 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]++; 227 258 const timeSlotDateTime = new Date(timeSlot); 228 const formattedDateTime = timeSlotDateTime.toLocaleString(); // Format for both date and time 259 const formattedTime = timeSlotDateTime.toLocaleTimeString([], { 260 hour: '2-digit', 261 minute: '2-digit' 262 }); 229 263 230 264 return ( 231 <button key={index} className="btn btn-primary me-2 mb-2" onClick={() => handleTimeSlotClick(table, timeSlot, restaurant)}> 232 {formattedDateTime} {/* Display both date and time */} 265 <button 266 key={index} 267 className="btn btn-primary me-2 mb-2" 268 onClick={() => handleTimeSlotClick(table, timeSlot, restaurant)} 269 > 270 {formattedTime} {} 233 271 </button> 234 272 ); 273 <br/> 235 274 } else { 236 return null; // Render nothing if the condition is not met275 return null; 237 276 } 238 277 })} … … 240 279 ); 241 280 } else { 242 // If capacity has been rendered, return null to avoid duplicate rendering243 281 return null; 244 282 } 245 283 }); 246 } 247 248 249 250 // Rest of your component code... 251 252 const today = new Date(); 253 const year = today.getFullYear(); 254 const month = String(today.getMonth() + 1).padStart(2, '0'); 255 const day = String(today.getDate()).padStart(2, '0'); 256 const formattedDate = `${year}-${month}-${day}`; 284 }; 257 285 258 286 … … 286 314 placeholder="Restaurant or Cuisine" 287 315 aria-label="Search" 288 value={searchValue} // Set the value of the input field289 onChange={handleInputChange} // Call the event handler on change316 value={searchValue} 317 onChange={handleInputChange} 290 318 /> 291 319 </div> … … 299 327 <div className="card-body"> 300 328 <RestaurantInfo key={restaurant.id} restaurant={restaurant}/> 301 <p>Available time slots</p>329 {/*<p>Available time slots</p>*/} 302 330 <div className="d-flex flex-wrap"> 303 {renderTimeSlots(restaurant.tablesList.flatMap((table) => table), restaurant)} 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 )} 304 338 </div> 339 <button 340 className="btn btn-secondary" 341 onClick={() => handleGoToRestaurant(restaurant.restaurantId)} 342 > 343 Go to Restaurant 344 </button> 305 345 </div> 306 346 </div> … … 308 348 </div> 309 349 310 311 350 {showCuisineSearch && ( 312 351 <div className="mb-3"> 313 352 <h2 className="display-2">Search by cuisine type</h2> 314 <ul className="list-group"> 315 {cuisineTypes.map((cuisine, index) => ( 316 <li key={index} className="list-group-item"> 317 <button type="button" className="btn btn-outline-primary" 318 onClick={() => handleSearchByCuisine(cuisine)}> 319 {cuisine} 320 </button> 321 </li> 322 ))} 323 </ul> 324 </div>)} 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 )} 325 365 </form> 326 366 </div> -
my-react-app/src/axios_helper.js
r8ca35dc rf5b256e 5 5 6 6 export const getAuthToken = () => { 7 return window.localStorage.getItem(" auth_token");7 return window.localStorage.getItem("token"); 8 8 } 9 9 … … 17 17 method: method, 18 18 url: url, 19 data: data 19 data: data ? JSON.stringify(data) : null, 20 headers 20 21 }) 21 22 } -
my-react-app/src/components/AppContent.js
r8ca35dc rf5b256e 15 15 isAuthenticated: false, 16 16 user: null, 17 loading: false // Add loading state17 loading: false 18 18 }; 19 19 } … … 29 29 30 30 fetchUserDetails = (token) => { 31 console.log("Fetch ");31 console.log("Fetching User Details..."); 32 32 axios.get("/api/user") 33 33 .then((response) => { 34 this.setState({ user: response.data, componentToShow: "restaurants", isAuthenticated: true }); 34 this.setState({ 35 user: response.data, 36 componentToShow: "restaurants", 37 isAuthenticated: true 38 }); 35 39 }) 36 40 .catch((error) => { 37 41 console.error("Failed to fetch user details:", error); 38 this.logout();39 42 }); 40 43 }; … … 52 55 onLogin = (e, email, password) => { 53 56 e.preventDefault(); 54 // After successful login, save the token 57 55 58 axios.post("/api/login", { email, password }) 56 59 .then((response) => { 57 60 const token = response.data.token; 58 localStorage.setItem('token', token); // Save the token59 console.log( token);60 this.setAuthToken(token); // Set the token for future requests61 this. setState({ componentToShow: "restaurants", isAuthenticated: true });61 localStorage.setItem('token', token); 62 console.log("Login Token:", token); 63 this.setAuthToken(token); 64 this.fetchUserDetails(token); 62 65 }) 63 66 .catch((error) => { … … 65 68 this.setState({ componentToShow: "welcome" }); 66 69 }); 67 68 70 }; 69 71 … … 93 95 94 96 render() { 97 if (this.state.isAuthenticated) { 98 return ( 99 <div> 100 <Buttons login={this.login} logout={this.logout} /> 101 <AuthContent /> 102 </div> 103 ); 104 } 105 95 106 return ( 96 107 <div> 97 108 <Buttons login={this.login} logout={this.logout} /> 98 109 {this.state.componentToShow === "welcome" && <WelcomeContent />} 99 {this.state.componentToShow === "restaurants" && <AuthContent />}100 110 {this.state.componentToShow === "login" && <LoginForm onLogin={this.onLogin} onRegister={this.onRegister} />} 101 {this.state.loading && <div>Loading...</div>} {/* Show loading state */}102 111 </div> 103 112 ); 113 // return ( 114 // <div> 115 // <Buttons login={this.login} logout={this.logout} /> 116 // {this.state.componentToShow === "welcome" && <WelcomeContent />} 117 // {this.state.componentToShow === "restaurants" && <AuthContent />} 118 // {this.state.componentToShow === "login" && <LoginForm onLogin={this.onLogin} onRegister={this.onRegister} />} 119 // {this.state.loading && <div>Loading...</div>} {/* Show loading state */} 120 // </div> 121 // ); 104 122 } 105 123 } -
my-react-app/src/components/AuthContent.js
r8ca35dc rf5b256e 10 10 } 11 11 12 // componentDidMount() { 13 // request( 14 // "GET", 15 // "/api/restaurants", 16 // [] 17 // ).then((response) => { 18 // this.setState({ data: response.data }); 19 // }); 20 // } 12 21 componentDidMount() { 13 request( 14 "GET", 15 "/api/restaurants", 16 [] 17 ).then((response) => { 18 this.setState({ data: response.data }); 19 }); 22 const token = localStorage.getItem('token'); 23 console.log("Stored Token on Reload:", token); 24 25 if (token) { 26 this.setAuthToken(token); 27 this.fetchUserDetails(token); 28 } else { 29 console.log("No token found. Redirecting to login."); 30 this.setState({ componentToShow: "login", isAuthenticated: false }); 31 } 20 32 } 21 33 -
my-react-app/src/components/Header.js
r8ca35dc rf5b256e 1 import React from 'react';1 import React, {useEffect, useState} from 'react'; 2 2 import { Link, useNavigate } from 'react-router-dom'; 3 import {jwtDecode} from "jwt-decode"; 4 import axios from "axios"; 3 5 4 6 const Header = () => { 5 7 const navigate = useNavigate(); 6 7 // Check if the user is logged in by looking for a token in localStorage 8 const [user, setUser] = useState({}); 8 9 const isLoggedIn = localStorage.getItem('token'); 9 10 10 11 const handleLogout = () => { 11 // Clear the token from localStorage 12 localStorage.removeItem('token'); 13 // Redirect to the home page or login page after logging out 14 navigate('/login'); 12 localStorage.removeItem("token"); 13 localStorage.removeItem("user"); 14 setUser(null); 15 navigate("/login"); 16 15 17 }; 18 19 useEffect(() => { 20 const fetchUser = async () => { 21 try { 22 const token = localStorage.getItem("token"); 23 if (!token) return; 24 25 const storedUser = localStorage.getItem("user"); 26 if (storedUser) { 27 setUser(JSON.parse(storedUser)); 28 return; 29 } 30 31 const decodedToken = jwtDecode(token); 32 const userId = decodedToken.iss; 33 34 const { data } = await axios.get(`http://localhost:8081/api/user/${userId}`); 35 setUser(data); 36 localStorage.setItem("user", JSON.stringify(data)); 37 } catch (error) { 38 console.error("Error fetching user:", error); 39 } 40 }; 41 fetchUser(); 42 }, [isLoggedIn]); 16 43 17 44 return ( … … 37 64 <form className="form-inline mt-2 mt-md-0 ml-3"> 38 65 {isLoggedIn ? ( 39 <button className="btn btn-outline-danger my-2 my-sm-0" onClick={handleLogout}>Logout</button> 66 <button className="btn btn-outline-danger ml-3" onClick={handleLogout}> 67 Logout {user?.firstName} 68 </button> 40 69 ) : ( 41 <Link className="btn btn-outline-info m y-2 my-sm-0" to="/login">Login</Link>70 <Link className="btn btn-outline-info ml-3" to="/login">Login</Link> 42 71 )} 43 72 </form> -
my-react-app/src/components/Login.js
r8ca35dc rf5b256e 22 22 const { token } = response.data; 23 23 24 // Store token securely (consider httpOnly cookies)25 24 localStorage.setItem('token', token); 26 25 27 26 navigate("/") 28 27 } catch (error) { 29 // Handle login failure30 28 console.error('Login failed:', error); 31 29 setError('Login failed. Please check your credentials and try again.'); -
my-react-app/src/components/ReservationConfirmation.js
r8ca35dc rf5b256e 4 4 import { useNavigate } from 'react-router-dom'; 5 5 import {jwtDecode} from "jwt-decode"; 6 import {request} from "../axios_helper"; 7 import restaurants from "./Restaurants"; 6 8 7 9 const ReservationConfirmation = () => { … … 9 11 10 12 const [restaurant, setRestaurant] = useState({}); 13 const [user, setUser] = useState({}); 11 14 const [table, setTable] = useState({}); 12 15 const [reservationDateTime, setReservationDateTime] = useState(''); … … 15 18 const { tableNumber, timeSlot, restaurantId } = useParams(); 16 19 20 const adjustedTimeSlot = new Date(new Date(timeSlot).getTime() + 60 * 60 * 1000).toISOString(); 17 21 useEffect(() => { 18 const fetch TableDetails = async () => {22 const fetchDetails = async () => { 19 23 try { 20 24 const tableResponse = await axios.get(`http://localhost:8081/api/tables/${tableNumber}`); … … 23 27 const restaurantResponse = await axios.get(`http://localhost:8081/api/restaurants/${restaurantId}`); 24 28 setRestaurant(restaurantResponse.data); 29 30 const token = localStorage.getItem("token"); 31 if (!token) { 32 console.error("No token found"); 33 return; 34 } 35 const decodedToken = jwtDecode(token); 36 const userId = decodedToken.iss; 37 38 const userResponse = await axios.get(`http://localhost:8081/api/user/${userId}`); 39 setUser(userResponse.data); 25 40 } catch (error) { 26 console.error('Error fetching table details:', error);41 console.error('Error fetching table or restaurant details:', error); 27 42 } 28 43 }; 29 fetch TableDetails();44 fetchDetails(); 30 45 }, [tableNumber, restaurantId]); 31 46 … … 33 48 e.preventDefault(); 34 49 35 if (!restaurant || !table) { 36 console.error("Restaurant or table is missing."); 37 return; 38 } 50 const payload = { 51 reservationID: 0, 52 userEmail: user.email, 53 rating: parseFloat(restaurant.rating) || null, 54 tableNumber: parseInt(table.id, 10), 55 restaurant: restaurant, 56 reservationDateTime: adjustedTimeSlot, 57 partySize: parseInt(partySize, 10), 58 status: 'Reserved', 59 specialRequests: specialRequests.trim(), 60 paymentStatus: 'Pending', 61 }; 62 39 63 40 64 try { 41 const token = localStorage.getItem("token"); 42 if (!token) { 43 console.error("No token found"); 44 return; 65 const response = await axios.post('http://localhost:8081/api/reservations', payload); 66 console.log('Reservation created successfully:', response.data); 67 navigate("/reservations") 68 } catch (error) { 69 if (error.response) { 70 alert('The selected time slot is no longer available. Please choose another time.'); 71 } else { 72 alert('Network error. Please check your internet connection.'); 45 73 } 46 47 // Decode the token to get the user email48 const decodedToken = jwtDecode(token);49 console.log(decodedToken)50 const userId = decodedToken.iss;// Assuming the email is part of the decoded JWT token51 52 const response = await axios.post(53 `http://localhost:8081/api/reservations/${userId}`,54 {55 restaurant: restaurant,56 table: table,57 checkInTime: timeSlot,58 partySize: partySize,59 specialRequests: specialRequests60 },61 {62 headers: {63 Authorization: `Bearer ${token}` // Include the token here64 }65 }66 );67 68 navigate("/reservations");69 } catch (error) {70 console.error("Error creating reservation:", error);71 74 } 72 75 }; 73 76 77 const calculateCheckOutTime = (checkInTime) => { 78 const checkIn = new Date(checkInTime); 79 checkIn.setHours(checkIn.getHours() + 2); 80 return checkIn.toISOString(); 81 }; 82 74 83 const initialRemainingTime = localStorage.getItem('remainingTime') || 300; 75 const [remainingTime, setRemainingTime] = useState(parseInt(initialRemainingTime ));84 const [remainingTime, setRemainingTime] = useState(parseInt(initialRemainingTime, 10)); 76 85 77 86 useEffect(() => { … … 79 88 setRemainingTime((prevTime) => { 80 89 const newTime = prevTime - 1; 81 localStorage.setItem('remainingTime', newTime.toString()); // Update remaining time in localStorage90 localStorage.setItem('remainingTime', newTime.toString()); 82 91 return newTime; 83 92 }); … … 88 97 89 98 useEffect(() => { 90 // Reset remaining time if it reaches zero91 99 if (remainingTime <= 0) { 92 100 localStorage.removeItem('remainingTime'); 93 // Optionally, handle releasing the hold on the table 101 alert("Time has expired. Please try reserving again."); 102 navigate('/restaurants'); // Redirect or take necessary action 94 103 } 95 }, [remainingTime ]);104 }, [remainingTime, navigate]); 96 105 97 106 const formatTime = (timeInSeconds) => { … … 99 108 const seconds = timeInSeconds % 60; 100 109 return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`; 110 }; 111 112 const formatTimeSlot = (timeSlot) => { 113 const utcDate = new Date(timeSlot); 114 const localDate = new Date(utcDate.toLocaleString("en-US", { timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone })); 115 const formattedDate = localDate.toLocaleDateString(); 116 const formattedTime = localDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); 117 return `${formattedDate} - ${formattedTime}`; 101 118 }; 102 119 … … 114 131 <h5 className="card-title">Reservation Details</h5> 115 132 <p className="card-text"> 116 <strong>Restaurant:</strong> {restaurant.name || 'Loading...'} <br/> 117 <strong>Cuisine type:</strong> {restaurant.cuisineType || 'Loading...'} <br/> 118 <strong>Selected Time Slot:</strong> {timeSlot} <br/> 119 <strong>Party size:</strong> <input type="number" max={table.capacity} 120 value={partySize} 121 onChange={(e) => setPartySize(e.target.value)}/> 122 <strong>Table size:</strong> {table.capacity} <br/> 123 <strong>Special Requests:</strong> <input type="text" value={specialRequests} 124 onChange={(e) => setSpecialRequests(e.target.value)}/><br/> 133 <strong>Restaurant:</strong> {restaurant.name || 'Loading...'} <br /> 134 <strong>Cuisine type:</strong> {restaurant.cuisineType || 'Loading...'} <br /> 135 <strong>Selected Time Slot:</strong> {formatTimeSlot(timeSlot)} <br /> 136 <strong>Party size:</strong>{' '} 137 <input 138 type="number" 139 max={table.capacity} 140 value={partySize} 141 onChange={(e) => setPartySize(e.target.value)} 142 /> 143 <strong>Table size:</strong> {table.capacity} <br /> 144 <strong>Special Requests:</strong>{' '} 145 <input 146 type="text" 147 value={specialRequests} 148 onChange={(e) => setSpecialRequests(e.target.value)} 149 /> 150 <br /> 125 151 </p> 126 152 <p className="card-text text-success"> 127 <strong>Check-in Time: grace period of 15 minutes +- the slot, for more call the 128 restaurant</strong><br/> 153 <strong> 154 Check-in Time: Grace period of 15 minutes +/- the slot. For more information, call the restaurant. 155 </strong> 156 <br /> 129 157 </p> 130 158 </div> 131 159 <div className="card-footer"> 132 160 <button type="submit" className="btn btn-primary">Submit</button> 133 </div> 134 <div className="card-footer"> 135 <a href="/restaurants" className="btn btn-primary">Back to Restaurants</a> 136 </div> 137 <div className="card-footer"> 138 <a href="/" className="btn btn-primary">Back to Home</a> 161 <a href="/restaurants" className="btn btn-secondary mx-2">Back to Restaurants</a> 162 <a href="/" className="btn btn-secondary">Back to Home</a> 139 163 </div> 140 164 </form> 141 165 </div> 142 166 </div> 143 144 167 </div> 145 168 </div> -
my-react-app/src/components/ReservationEdit.js
r8ca35dc rf5b256e 7 7 const ReservationEdit = () => { 8 8 const navigate = useNavigate(); 9 9 const { reservationId } = useParams(); 10 10 const [isLoading, setIsLoading] = useState(true); 11 const { reservationId } = useParams(); // Extract reservationId from URL params12 11 const [formData, setFormData] = useState({}); 12 const [tableNumber, setTableNumber] = useState({}); 13 13 const [table, setTable] = useState({}); 14 14 const [restaurant, setRestaurant] = useState({}); 15 const [restaurantId, setRestaurantId] = useState({}); 15 16 const [timeSlots, setTimeSlots] = useState([]); 16 17 const [filteredTimeSlots, setFilteredTimeSlots] = useState([]); 17 18 const [checkInTime, setCheckInTime] = useState([]); 19 const [tableReservations, setTableReservations] = useState([]); 20 21 const timeSlotInterval = 15; 22 23 const [selectedDate, setSelectedDate] = useState(''); 24 const [selectedTime, setSelectedTime] = useState(''); 25 const [timeOptions, setTimeOptions] = useState([]); 18 26 19 27 useEffect(() => { … … 22 30 setIsLoading(true); 23 31 const response = await axios.get(`http://localhost:8081/api/reservations/${reservationId}`); 24 setCheckInTime(response.data.checkInTime) 32 console.log(response) 33 setCheckInTime(response.data.reservationDateTime); 25 34 setFormData(response.data); 26 setTable(response.data.table); 27 setRestaurant(response.data.restaurant); 28 setTimeSlots(response.data.table.timeSlots); 35 setRestaurant(response.data.restaurantName); 36 setRestaurantId(response.data.restaurantId); 37 38 setTableNumber(response.data.tableNumber); 39 const tableResponse = await axios.get(`http://localhost:8081/api/tables/${response.data.tableNumber}`); 40 setTable(tableResponse.data) 41 29 42 setIsLoading(false); 30 43 } catch (error) { … … 32 45 } 33 46 }; 47 34 48 fetchReservation(); 35 49 }, [reservationId]); 36 50 37 51 useEffect(() => { 38 if (!table || !restaurant) return; // If table or restaurant is not loaded, return early 39 40 const currentTime = new Date(); 41 42 const filteredSlots = timeSlots.filter(timeSlot => new Date(timeSlot) >= currentTime); 43 setFilteredTimeSlots(filteredSlots); 44 }, [table, restaurant]); 52 const fetchTableReservations = async () => { 53 try { 54 const response = await axios.get(`http://localhost:8081/api/table-reservations/${table.tableId}`); 55 setTableReservations(response.data); 56 setIsLoading(false); 57 } catch (error) { 58 console.error('Error fetching table reservations:', error); 59 } 60 }; 61 62 if (table?.tableId) { 63 fetchTableReservations(); 64 } 65 }, [table]); 66 67 const generateTimeSlots = (operatingHours, interval) => { 68 const slots = []; 69 const [startTimeStr, endTimeStr] = operatingHours.split('-'); 70 const [startHours, startMinutes] = startTimeStr.split(':').map(Number); 71 const [endHours, endMinutes] = endTimeStr.split(':').map(Number); 72 73 const startTime = new Date(); 74 startTime.setHours(startHours, startMinutes, 0, 0); 75 76 const endTime = new Date(); 77 endTime.setHours(endHours, endMinutes, 0, 0); 78 79 let currentTime = startTime; 80 while (currentTime <= endTime) { 81 slots.push(new Date(currentTime).toISOString()); 82 currentTime = new Date(currentTime.getTime() + interval * 60000); 83 } 84 85 return slots; 86 }; 87 88 const generateTimeOptions = (operatingHours) => { 89 const { startTime, endTime } = parseOperatingHours(operatingHours); 90 const now = new Date(); 91 92 const selectedDateObj = new Date(selectedDate); 93 const isToday = selectedDateObj.toDateString() === now.toDateString(); 94 const isTomorrow = selectedDateObj > now && selectedDateObj.getDate() === now.getDate() + 1; 95 96 let currentTime; 97 98 if (isToday) { 99 currentTime = roundToNext15Minutes(new Date()); 100 } else { 101 currentTime = new Date(startTime); 102 } 103 104 const options = []; 105 while (currentTime <= endTime) { 106 options.push(currentTime.toTimeString().slice(0, 5)); 107 currentTime = new Date(currentTime.getTime() + 15 * 60 * 1000); 108 } 109 110 return options; 111 }; 112 113 useEffect(() => { 114 const operatingHours = table?.restaurant?.operatingHours || "09:00-00:00"; 115 const allTimeSlots = generateTimeSlots(operatingHours, timeSlotInterval); 116 117 const availableSlots = allTimeSlots.filter((slot) => 118 !tableReservations.includes(slot) 119 ); 120 121 setFilteredTimeSlots(availableSlots); 122 }, [tableReservations, table]); 45 123 46 124 const handleInputChange = (e) => { 47 125 const { name, value } = e.target; 48 126 49 // Check if the changed input is the time slot select element 50 if (name === 'selectedTimeSlot') { 51 // Update the formData with the selected time slot value 52 setFormData({ ...formData, checkInTime: value }); 127 if (name === 'partySize') { 128 const valueAsNumber = Math.min(value, table?.capacity); 129 setFormData(prevState => ({ 130 ...prevState, 131 [name]: valueAsNumber 132 })); 53 133 } else { 54 // For other input fields, update formData as usual 55 setFormData({ ...formData, [name]: value }); 56 } 57 }; 134 setFormData(prevState => ({ 135 ...prevState, 136 [name]: value 137 })); 138 } 139 }; 140 58 141 59 142 const handleSubmit = async (e) => { … … 68 151 69 152 const decodedToken = jwtDecode(token); 70 console.log(decodedToken)71 153 const userId = decodedToken.iss; 72 154 73 await axios.post(`http://localhost:8081/api/reservations/${reservationId}/${userId}`, formData); 74 75 navigate(`/reservations`) 155 const updatedReservationData = { 156 ...formData, 157 reservationDateTime: `${selectedDate}T${selectedTime}`, 158 checkInTime: checkInTime, 159 reservationID: reservationId, 160 }; 161 await axios.post( 162 `http://localhost:8081/api/reservations/${reservationId}/${userId}`, 163 updatedReservationData, 164 { 165 headers: { 166 'Content-Type': 'application/json', 167 } 168 } 169 ); 170 console.log(updatedReservationData) 171 172 navigate(`/reservations`); 76 173 } catch (error) { 77 174 console.error('Error updating reservation:', error); … … 86 183 }; 87 184 88 const formatCurrentTimeSlot = (timeSlot) => { 89 const date = new Date(timeSlot); 90 const formattedDate = date.toLocaleDateString('en-GB'); // Format date as YYYY-MM-DD 91 const formattedTime = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); // Format time as HH:MM 92 return `${formattedDate} ${formattedTime}`; 93 }; 185 const today = new Date(); 186 const year = today.getFullYear(); 187 const month = String(today.getMonth() + 1).padStart(2, '0'); 188 const day = String(today.getDate()).padStart(2, '0'); 189 const formattedDate = `${year}-${month}-${day}`; 190 191 const parseOperatingHours = (operatingHours) => { 192 const [start, end] = operatingHours.split('-'); 193 return { 194 startTime: new Date(`1970-01-01T${start}:00`), 195 endTime: new Date(`1970-01-01T${end}:00`) 196 }; 197 }; 198 199 useEffect(() => { 200 if (formData?.restaurant?.operatingHours && selectedDate) { 201 const options = generateTimeOptions(formData.restaurant.operatingHours); 202 setTimeOptions(options); 203 } 204 }, [restaurant, selectedDate]); 205 206 // useEffect(() => { 207 // if (checkInTime) { 208 // const checkInDateObj = new Date(checkInTime); 209 // setSelectedDate(checkInDateObj.toISOString().split("T")[0]); 210 // setSelectedTime(checkInDateObj.toTimeString().slice(0, 5)); 211 // } 212 // }, [checkInTime]); 94 213 95 214 return ( 96 215 <div className="container"> 97 {isLoading ? ( // Conditional rendering based on loading state216 {isLoading ? ( 98 217 <p>Loading...</p> 99 218 ) : ( … … 101 220 <h1>Edit Reservation</h1> 102 221 <div className="card-body"> 103 <h2 >104 { restaurant.name} <StarRating key={restaurant.id} rating={restaurant.rating}/>222 <h2 className="card-title"> 223 {formData.restaurant.name} <StarRating key={formData.restaurant.id} rating={formData.restaurant.rating} /> 105 224 </h2> 106 <p className="card-text">{ restaurant.cuisineType}</p>107 <p className="card-text">{ restaurant.operatingHours}</p>108 <p className="card-text">Ul. { restaurant.address}</p>109 <br />225 <p className="card-text">{formData.restaurant.name}</p> 226 <p className="card-text">{formData.restaurant.operatingHours}</p> 227 <p className="card-text">Ul. {formData.restaurant.address}</p> 228 <br /> 110 229 </div> 111 230 <form onSubmit={handleSubmit}> 112 231 <div className="mb-3"> 113 <label htmlFor="checkInTime" className="form-label">Check-in Time</label> 114 <select className="form-select mt-2" aria-label="Select Time Slot" 115 name="selectedTimeSlot" // Add name attribute 116 onChange={handleInputChange}> 117 <option value="">Select Time Slot</option> 118 {filteredTimeSlots.map((timeSlot, index) => ( 119 <option key={index} value={timeSlot}>{formatTimeSlot(timeSlot)}</option> 232 <label>Select Date:</label> 233 <input 234 type="date" 235 className="form-control mt-2" 236 onChange={(event) => setSelectedDate(event.target.value)} 237 value={selectedDate} 238 min={formattedDate} 239 /> 240 241 <label>Select Time:</label> 242 <select 243 className="form-select mt-2" 244 onChange={(event) => setSelectedTime(event.target.value)} 245 value={selectedTime} 246 disabled={!selectedDate} 247 > 248 <option value="">Select Time</option> 249 {timeOptions.map((time, index) => ( 250 <option key={index} value={time}> 251 {time} 252 </option> 120 253 ))} 121 254 </select> 122 <label className=".text-danger">Current check in time: {formatTimeSlot(checkInTime)}</label> 255 <label className="text-danger"> 256 Current check-in time: {formatTimeSlot(checkInTime)} 257 </label> 123 258 </div> 124 259 <div className="mb-3"> 125 260 <label htmlFor="partySize" className="form-label">Party Size</label> 126 <input type="number" className="form-control" id="partySize" name="partySize" 127 max={table.capacity} 128 value={formData.partySize || ''} onChange={handleInputChange}/> 261 <input 262 type="number" 263 className="form-control" 264 id="partySize" 265 name="partySize" 266 max={table?.capacity} 267 min={1} 268 value={formData.partySize || ''} 269 onChange={handleInputChange} 270 /> 271 <label className="text-danger"> 272 Table capacity: {table?.capacity} 273 </label> 129 274 </div> 130 275 <div className="mb-3"> 131 <label htmlFor="specialRequests" className="form-label">Special Requests</label> 132 <input type="text" className="form-control" id="specialRequests" name="specialRequests" 133 value={formData.specialRequests || ''} onChange={handleInputChange}/> 276 <label htmlFor="specialRequests" className="form-label">Special Requests</label> 277 <input 278 type="text" 279 className="form-control" 280 id="specialRequests" 281 name="specialRequests" 282 value={formData.specialRequests || ''} 283 onChange={handleInputChange} 284 /> 134 285 </div> 135 286 <button type="submit" className="btn btn-primary">Submit</button> … … 139 290 </div> 140 291 ); 141 142 292 }; 143 293 -
my-react-app/src/components/ReservationHistory.js
r8ca35dc rf5b256e 5 5 const ReservationHistory = () => { 6 6 const [reservations, setReservations] = useState([]); 7 const [filteredReservations, setFilteredReservations] = useState([]); 7 8 const [loading, setLoading] = useState(true); 8 9 const [error, setError] = useState(null); 10 11 const [restaurantFilter, setRestaurantFilter] = useState(""); 12 const [tableFilter, setTableFilter] = useState(""); 13 const [partySizeFilter, setPartySizeFilter] = useState(""); 14 const [statusFilter, setStatusFilter] = useState(""); 15 const [cancellationReasonFilter, setCancellationReasonFilter] = useState(""); 16 17 const [startDate, setStartDate] = useState(""); 18 const [endDate, setEndDate] = useState(""); 9 19 10 20 useEffect(() => { … … 29 39 30 40 setReservations(response.data); 41 setFilteredReservations(response.data); 31 42 } catch (err) { 32 43 setError("Failed to fetch reservations."); … … 39 50 }, []); 40 51 52 useEffect(() => { 53 let tempReservations = reservations; 54 55 if (restaurantFilter) { 56 tempReservations = tempReservations.filter(res => 57 res.restaurant?.name.toLowerCase().includes(restaurantFilter.toLowerCase()) 58 ); 59 } 60 if (tableFilter) { 61 tempReservations = tempReservations.filter(res => 62 res.table?.id?.toString() === tableFilter 63 ); 64 } 65 if (partySizeFilter) { 66 tempReservations = tempReservations.filter(res => 67 res.partySize?.toString() === partySizeFilter 68 ); 69 } 70 if (statusFilter) { 71 tempReservations = tempReservations.filter(res => 72 res.status.toLowerCase().includes(statusFilter.toLowerCase()) 73 ); 74 } 75 if (cancellationReasonFilter) { 76 tempReservations = tempReservations.filter(res => 77 (res.cancellationReason || "None").toLowerCase().includes(cancellationReasonFilter.toLowerCase()) 78 ); 79 } 80 if (startDate && endDate) { 81 const start = new Date(startDate); 82 const end = new Date(endDate); 83 84 tempReservations = tempReservations.filter(res => { 85 const reservationDate = new Date(res.reservationDateTime); 86 return reservationDate >= start && reservationDate <= end; 87 }); 88 } 89 90 setFilteredReservations(tempReservations); 91 }, [ 92 restaurantFilter, 93 tableFilter, 94 partySizeFilter, 95 statusFilter, 96 cancellationReasonFilter, 97 startDate, 98 endDate, 99 reservations 100 ]); 101 102 const resetFilters = () => { 103 setRestaurantFilter(""); 104 setTableFilter(""); 105 setPartySizeFilter(""); 106 setStatusFilter(""); 107 setCancellationReasonFilter(""); 108 setStartDate(""); 109 setEndDate(""); 110 }; 111 41 112 if (loading) return <div>Loading...</div>; 42 113 if (error) return <div className="alert alert-danger">{error}</div>; … … 44 115 return ( 45 116 <div className="container mt-5"> 46 <h3>Past Reservations</h3> 47 <table className="table table-bordered"> 48 <thead className="thead-dark"> 49 <tr> 50 <th>#</th> 51 <th>Restaurant</th> 52 <th>Table</th> 53 <th>Date & Time</th> 54 <th>Party Size</th> 55 <th>Special Requests</th> 56 <th>Status</th> 57 <th>Cancellation Reason</th> 58 </tr> 59 </thead> 60 <tbody> 61 {reservations.map((res, index) => ( 62 <tr key={res.id}> 63 <td>{index + 1}</td> 64 <td>{res.restaurant?.name || "N/A"}</td> 65 <td>{res.table?.id || "N/A"}</td> 66 <td>{new Date(res.reservationDateTime).toLocaleString()}</td> 67 <td>{res.partySize}</td> 68 <td>{res.specialRequests || "None"}</td> 69 <td>{res.status}</td> 70 <td>{res.cancellationReason || "None"}</td> 117 <h3 className="mb-4 text-center">Past Reservations</h3> 118 119 <div className="row mb-4 align-items-end"> 120 <div className="col-md-2"> 121 <input 122 type="text" 123 className="form-control" 124 placeholder="Filter by Restaurant" 125 value={restaurantFilter} 126 onChange={(e) => setRestaurantFilter(e.target.value)} 127 /> 128 </div> 129 <div className="col-md-2"> 130 <input 131 type="number" 132 className="form-control" 133 placeholder="Filter by Table ID" 134 value={tableFilter} 135 onChange={(e) => setTableFilter(e.target.value)} 136 /> 137 </div> 138 <div className="col-md-4 d-flex gap-2"> 139 <input 140 type="date" 141 className="form-control" 142 placeholder="Start date" 143 value={startDate} 144 onChange={(e) => setStartDate(e.target.value)} 145 /> 146 <input 147 type="date" 148 className="form-control" 149 placeholder="End date" 150 value={endDate} 151 onChange={(e) => setEndDate(e.target.value)} 152 /> 153 </div> 154 <div className="col-md-2"> 155 <input 156 type="number" 157 className="form-control" 158 placeholder="Filter by Party Size" 159 value={partySizeFilter} 160 onChange={(e) => setPartySizeFilter(e.target.value)} 161 /> 162 </div> 163 <div className="col-md-2"> 164 <select 165 value={statusFilter} 166 onChange={(e) => setStatusFilter(e.target.value)} 167 className="form-control" 168 > 169 <option value="">Filter by status</option> 170 <option value="successful">Successful</option> 171 <option value="canceled">Canceled</option> 172 </select> 173 </div> 174 <div className="col-md-2 mt-2"> 175 <input 176 type="text" 177 className="form-control" 178 placeholder="Filter by Cancellation Reason" 179 value={cancellationReasonFilter} 180 onChange={(e) => setCancellationReasonFilter(e.target.value)} 181 /> 182 </div> 183 <div className="col-md-2 mt-2"> 184 <button 185 onClick={resetFilters} 186 className="btn btn-outline-secondary w-100" 187 > 188 Reset Filters 189 </button> 190 </div> 191 </div> 192 193 <div className="table-responsive"> 194 <table className="table table-striped table-hover table-bordered align-middle"> 195 <thead className="table-dark text-center"> 196 <tr> 197 <th>#</th> 198 <th>Restaurant</th> 199 <th>Table</th> 200 <th>Date & Time</th> 201 <th>Party Size</th> 202 <th>Special Requests</th> 203 <th>Status</th> 204 <th>Cancellation Reason</th> 71 205 </tr> 72 ))} 73 </tbody> 74 </table> 206 </thead> 207 <tbody> 208 {filteredReservations.length > 0 ? ( 209 filteredReservations.map((res, index) => ( 210 <tr key={res.id}> 211 <td>{index + 1}</td> 212 <td>{res.restaurant?.name || "N/A"}</td> 213 <td>{res.table?.id || "N/A"}</td> 214 <td>{new Date(res.reservationDateTime).toLocaleString()}</td> 215 <td>{res.partySize}</td> 216 <td>{res.specialRequests || "None"}</td> 217 <td>{res.status}</td> 218 <td>{res.cancellationReason || "None"}</td> 219 </tr> 220 )) 221 ) : ( 222 <tr> 223 <td colSpan="8" className="text-center">No reservations found.</td> 224 </tr> 225 )} 226 </tbody> 227 </table> 228 </div> 75 229 </div> 76 230 ); -
my-react-app/src/components/Reservations.js
r8ca35dc rf5b256e 20 20 } 21 21 const decodedToken = jwtDecode(token); 22 console.log(decodedToken)23 22 const userId = decodedToken.iss; 24 23 … … 49 48 } 50 49 }; 51 52 53 50 return ( 54 51 <div className="container"> 55 <h2>Reservations</h2>56 52 <div className="row"> 57 {reservations.map(reservation => ( 58 <div key={reservation.reservationID} className="col-md-4 mb-4"> 59 <div className="card"> 60 <div className="card-body"> 61 <div className="card-title">Reservation ID: {reservation.reservationID}</div> 62 <div className="card-text">Restaurant: {reservation.restaurant.name}</div> 63 <div className="card-text">Table Number: {reservation.table.id}</div> 64 {/* Format reservation date and time */} 65 <div className="card-text">Reservation 66 Date: {new Date(reservation.checkInTime).toLocaleDateString('en-US', { 67 weekday: 'long', 68 year: 'numeric', 69 month: 'long', 70 day: 'numeric' 71 })}</div> 72 <div className="card-text">Reservation 73 Time: {new Date(reservation.checkInTime).toLocaleTimeString('en-US', { 74 hour: 'numeric', 75 minute: 'numeric', 76 hour12: true 77 })}</div> 78 {/* End of formatted date and time */} 79 <div className="card-text">Party Size: {reservation.partySize}</div> 80 <div className="card-text text-danger">Special 81 Requests: {reservation.specialRequests}</div> 82 <div className="card-text">Status: {reservation.status}</div> 83 <div className="card-text text-danger">Grace period of 15 minutes +-</div> 84 <br/> 85 <div className="row"> 86 <div className="col"> 87 <button className="danger text-bg-warning border-0" 88 onClick={() => handleEditReservation(reservation.reservationID)}>Edit 89 Reservation 53 {reservations.length === 0 ? ( 54 <div className="text-center mt-5"> 55 <h4>No active reservations</h4> 56 <p>Looking for a place to dine? Check out our <a href="/restaurants" className="text-primary">restaurants</a>.</p> 57 </div> 58 ) : ( 59 reservations.map(reservation => ( 60 <div key={reservation.reservationID} className="col-md-4 mb-4"> 61 <div className="card h-100"> 62 <div className="card-body"> 63 <h5 className="card-title">Reservation ID: {reservation.reservationID}</h5> 64 <p className="card-text">Restaurant: {reservation?.restaurant.name || "Not specified"}</p> 65 <p className="card-text">Table Number: {reservation?.tableNumber || "Not specified"}</p> 66 <p className="card-text"> 67 Reservation Date: {reservation.checkInTime ? 68 new Date(reservation.checkInTime).toLocaleDateString('en-US', { 69 weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' 70 }) : 71 "Not specified"} 72 </p> 73 <p className="card-text"> 74 Reservation Time: {reservation.checkInTime ? 75 new Date(reservation.checkInTime).toLocaleTimeString('en-US', { 76 hour: 'numeric', minute: 'numeric', hour12: true 77 }) : 78 "Not specified"} 79 </p> 80 <p className="card-text">Reservation made on: {reservation.reservationDateTime ? 81 new Date(reservation.reservationDateTime).toLocaleTimeString('en-US', { 82 hour: 'numeric', minute: 'numeric', hour12: true 83 }) : 84 "Not specified"} {reservation.reservationDateTime ? 85 new Date(reservation.reservationDateTime).toLocaleDateString('en-US', { 86 weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' 87 }) : 88 "Not specified"} </p> 89 <p className="card-text">Party Size: {reservation.partySize || "Not specified"}</p> 90 <p className="card-text text-danger">Special Requests: {reservation.specialRequests || "None"}</p> 91 <p className="card-text">Status: {reservation.status || "Pending"}</p> 92 <p className="card-text text-danger">Grace period of 15 minutes +-</p> 93 <div className="d-flex justify-content-between mt-3"> 94 <button 95 className="btn btn-warning" 96 onClick={() => handleEditReservation(reservation.reservationID)}> 97 Edit Reservation 90 98 </button> 91 </div> 92 <div className="col"> 93 <button className="danger text-bg-danger border-0" 94 onClick={() => handleCancelReservation(reservation.reservationID)}>Cancel 95 Reservation 99 <button 100 className="btn btn-danger" 101 onClick={() => handleCancelReservation(reservation.reservationID)}> 102 Cancel Reservation 96 103 </button> 97 104 </div> … … 99 106 </div> 100 107 </div> 101 </div>102 ) )}108 )) 109 )} 103 110 </div> 104 111 </div> 105 112 ); 106 p107 108 113 }; 109 114 110 115 export default Reservations; 116 -
my-react-app/src/components/RestaurantContext.js
r8ca35dc rf5b256e 17 17 }; 18 18 19 fetchRestaurants() .then(r => console.log(fetchRestaurants()));19 fetchRestaurants(); 20 20 }, []); 21 21 -
my-react-app/src/components/RestaurantDetails.js
r8ca35dc rf5b256e 6 6 import StarRating from "./StarRating"; 7 7 8 8 9 const RestaurantDetails = () => { 9 10 const navigate = useNavigate(); 11 const { id } = useParams(); 10 12 11 const { id } = useParams();12 13 const [restaurant, setRestaurant] = useState(null); 13 14 const [selectedTableId, setSelectedTableId] = useState(''); 14 const [selectedTimeSlot, setSelectedTimeSlot] = useState(''); 15 const [selectedDate, setSelectedDate] = useState(''); 16 const [selectedTime, setSelectedTime] = useState(''); 17 const [timeOptions, setTimeOptions] = useState([]); 15 18 const [selectedTable, setSelectedTable] = useState(null); 16 const [filteredTimeSlots, setFilteredTimeSlots] = useState([]);17 const [selectedCapacity, setSelectedCapacity] = useState(''); // Define selectedCapacity state18 19 19 20 useEffect(() => { … … 32 33 33 34 useEffect(() => { 34 if (!selectedTableId) return; // If no table is selected, return early35 if (!selectedTableId) return; 35 36 36 37 const fetchTableDetails = async () => { … … 46 47 }, [selectedTableId]); 47 48 48 useEffect(() => { 49 if (!selectedTable || !restaurant) return; // If table or restaurant is not loaded, return early 49 const today = new Date(); 50 const year = today.getFullYear(); 51 const month = String(today.getMonth() + 1).padStart(2, '0'); 52 const day = String(today.getDate()).padStart(2, '0'); 53 const formattedDate = `${year}-${month}-${day}`; 54 const parseOperatingHours = (operatingHours) => { 55 const [start, end] = operatingHours.split('-'); 56 const [startHour, startMinute] = start.split(':').map(Number); 57 const [endHour, endMinute] = end.split(':').map(Number); 50 58 51 // Filter time slots based on the selected table 52 const currentTime = new Date(); 53 const filteredSlots = selectedTable.timeSlots.filter(timeSlot => new Date(timeSlot) >= currentTime); 54 setFilteredTimeSlots(filteredSlots); 55 }, [selectedTable, restaurant]); 59 const startTime = new Date(); 60 startTime.setHours(startHour, startMinute, 0, 0); 56 61 62 const endTime = new Date(); 63 endTime.setHours(endHour < startHour ? endHour + 24 : endHour, endMinute, 0, 0); 57 64 58 const formatTimeSlot = (timeSlot) => { 59 const date = new Date(timeSlot); 60 const formattedDate = date.toLocaleDateString(); 61 const formattedTime = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); 62 return `${formattedDate} - ${formattedTime}`; 65 return { startTime, endTime }; 63 66 }; 64 67 65 const handleTableSelect = (event) => { 66 const selectedTableId = event.target.value; 67 setSelectedTableId(selectedTableId); 68 const generateTimeOptions = (operatingHours) => { 69 const { startTime, endTime } = parseOperatingHours(operatingHours); 70 const now = new Date(); 71 72 const selectedDateObj = new Date(selectedDate); 73 const isToday = selectedDateObj.toDateString() === now.toDateString(); 74 75 let currentTime = isToday ? roundToNext15Minutes(new Date()) : new Date(startTime); 76 77 const options = []; 78 while (currentTime <= endTime) { 79 options.push(currentTime.toTimeString().slice(0, 5)); 80 currentTime = new Date(currentTime.getTime() + 15 * 60 * 1000); 81 } 82 83 return options; 68 84 }; 69 85 70 const handleTimeSlotSelect = (event) => { 71 const selectedTimeSlot = event.target.value; 72 setSelectedTimeSlot(selectedTimeSlot); 86 useEffect(() => { 87 if (restaurant && selectedDate) { 88 const options = generateTimeOptions(restaurant.operatingHours); 89 setTimeOptions(options); 90 } 91 }, [restaurant, selectedDate]); 92 93 const handleTableSelect = (event) => { 94 setSelectedTableId(event.target.value); 73 95 }; 74 96 75 const handleReservationConfirmation = ( restaurant) => {76 const tableNumber = selectedTableId;77 const formattedTimeSlot = selectedTimeSlot;78 const restaurantId = restaurant.restaurantId;97 const handleReservationConfirmation = () => { 98 const encodedTableId = encodeURIComponent(selectedTableId); 99 const encodedDateTime = encodeURIComponent(`${selectedDate}T${selectedTime}`); 100 const encodedRestaurantId = encodeURIComponent(restaurant.restaurantId); 79 101 80 const encodedTableNumber = encodeURIComponent(tableNumber); 81 const encodedTimeSlot = encodeURIComponent(formattedTimeSlot); 82 const encodedRestaurantId = encodeURIComponent(restaurantId); 83 84 navigate(`/reservationConfirmation/${encodedTableNumber}/${encodedTimeSlot}/${encodedRestaurantId}`); 102 navigate(`/reservationConfirmation/${encodedTableId}/${encodedDateTime}/${encodedRestaurantId}`); 85 103 }; 86 104 105 const roundToNext15Minutes = (date) => { 106 const minutes = date.getMinutes(); 107 const remainder = minutes % 15; 108 if (remainder === 0) return date; 109 110 date.setMinutes(minutes + 15 - remainder); 111 date.setSeconds(0, 0); 112 return date; 113 }; 87 114 88 115 return ( … … 91 118 <> 92 119 <h2 className="card-title"> 93 {restaurant.name} <StarRating key={restaurant.id} rating={restaurant.rating} />120 {restaurant.name} <StarRating key={restaurant.id} rating={restaurant.rating} /> 94 121 </h2> 95 122 <div className="restaurant-details"> … … 98 125 <p>Address: {restaurant.address}</p> 99 126 <p>Phone: {restaurant.phone}</p> 100 <p>Website: <a href={restaurant.website}>{restaurant.website}</a></p> 127 <p> 128 Website: <a href={restaurant.website}>{restaurant.website}</a> 129 </p> 101 130 <p>Social Media Links: {restaurant.socialMediaLinks}</p> 102 131 103 132 <label>Select Table:</label> 104 <select className="form-select" aria-label="Select Table" onChange={handleTableSelect} 105 value={selectedTableId}> 133 <select 134 className="form-select" 135 aria-label="Select Table" 136 onChange={handleTableSelect} 137 value={selectedTableId} 138 > 106 139 <option value="">Select Table</option> 107 {restaurant.tablesList.map((table, index) => ( 108 <option key={index} 109 value={table.id}>{`Capacity: ${table.capacity} - ${table.location}`}</option> 140 {restaurant.tablesList.map((table) => ( 141 <option key={table.id} value={table.id}> 142 {`Capacity: ${table.capacity} - ${table.location}`} 143 </option> 110 144 ))} 111 145 </select> 146 112 147 {selectedTable && ( 113 148 <> 114 <label>Select Time Slot:</label> 115 <select className="form-select mt-2" aria-label="Select Time Slot" onChange={handleTimeSlotSelect}> 116 <option value="">Select Time Slot</option> 117 {filteredTimeSlots.map((timeSlot, index) => ( 118 <option key={index} value={timeSlot}>{formatTimeSlot(timeSlot)}</option> 149 <label>Select Date:</label> 150 <input 151 type="date" 152 className="form-control mt-2" 153 onChange={(event) => setSelectedDate(event.target.value)} 154 value={selectedDate} 155 min={formattedDate} 156 onKeyDown={(e) => e.preventDefault()} 157 /> 158 {!selectedDate && <p style={{ color: "red" }}>Please select a valid date.</p>} 159 160 <label>Select Time:</label> 161 <select 162 className="form-select mt-2" 163 onChange={(event) => setSelectedTime(event.target.value)} 164 value={selectedTime} 165 disabled={!selectedDate} 166 > 167 <option value="">Select Time</option> 168 {timeOptions.map((time, index) => ( 169 <option key={index} value={time}> 170 {time} 171 </option> 119 172 ))} 120 173 </select> 121 174 </> 122 175 )} 123 <br/> 124 {/* Add a button to trigger reservation confirmation */} 125 <button className="btn btn-primary" onClick={() => handleReservationConfirmation(restaurant)}> 176 177 <br /> 178 <button 179 className="btn btn-primary" 180 onClick={handleReservationConfirmation} 181 disabled={!selectedTableId || !selectedDate || !selectedTime} 182 > 126 183 Confirm Reservation 127 184 </button> -
my-react-app/src/components/Restaurants.js
r8ca35dc rf5b256e 4 4 import { useNavigate } from 'react-router-dom'; 5 5 import {RestaurantContext} from "./RestaurantContext"; 6 import {Alert} from "bootstrap"; 7 8 const parseTime = (timeString) => { 9 const [hours, minutes] = timeString.trim().split(':').map(Number); 10 return new Date().setHours(hours, minutes, 0, 0); 11 }; 12 13 const roundToNextQuarter = (date) => { 14 const minutes = date.getMinutes(); 15 const roundedMinutes = Math.floor(minutes / 15) * 15; 16 date.setMinutes(roundedMinutes, 0, 0); 17 return date; 18 }; 19 20 const shouldMoveToNextDay = (currentTime, endTime) => { 21 return (endTime - currentTime) <= 2 * 60 * 60 * 1000; 22 }; 6 23 7 24 const Restaurants = () => { … … 14 31 }, [restaurantContext]); 15 32 33 34 const generateTimeSlots = (operatingHours) => { 35 const timeSlots = []; 36 const [startTimeStr, endTimeStr] = operatingHours.split('-').map((time) => time.trim()); 37 38 const startTime = parseTime(startTimeStr); 39 let endTime = parseTime(endTimeStr); 40 41 const currentTime = new Date().getTime(); 42 if (shouldMoveToNextDay(currentTime, endTime)) { 43 endTime += 24 * 60 * 60 * 1000; 44 } 45 46 let currentTimeSlot = new Date(startTime); 47 currentTimeSlot = roundToNextQuarter(currentTimeSlot); 48 49 while (currentTimeSlot.getTime() < endTime) { 50 timeSlots.push(currentTimeSlot.toISOString()); 51 currentTimeSlot.setMinutes(currentTimeSlot.getMinutes() + 15); 52 } 53 54 return timeSlots; 55 }; 56 16 57 const handleDetailClick = (restaurantId) => { 17 58 navigate(`/restaurants/${restaurantId}`); 18 } 59 }; 19 60 20 61 const handleTimeSlotClick = (table, timeSlot, restaurant) => { … … 23 64 const restaurantId = restaurant.restaurantId; 24 65 25 const encodedTableNumber = encodeURI (tableNumber);66 const encodedTableNumber = encodeURIComponent(tableNumber); 26 67 const encodedTimeSlot = encodeURIComponent(formattedTimeSlot); 27 68 const encodedRestaurantId = encodeURIComponent(restaurantId); 28 69 29 70 navigate(`/reservationConfirmation/${encodedTableNumber}/${encodedTimeSlot}/${encodedRestaurantId}`); 30 } 71 }; 31 72 32 73 const renderTimeSlots = (tablesList, restaurant) => { 33 74 const currentTime = new Date().getTime(); 34 let renderedTimeSlots = {}; // Object to store rendered slots for each table capacity75 let renderedTimeSlots = {}; 35 76 36 return tablesList.flatMap(table => { 37 // Render capacity header when encountering a new capacity 77 if (tablesList.length === 0) { 78 return <p>No tables available for reservations at this restaurant.</p>; 79 } 80 81 return tablesList.flatMap((table) => { 82 const tableTimeSlots = generateTimeSlots(restaurant.operatingHours); 83 38 84 if (!renderedTimeSlots[table.capacity]) { 39 85 renderedTimeSlots[table.capacity] = 0; 40 86 return ( 41 87 <div key={table.capacity}> 42 <h3>Table for : {table.capacity}</h3>43 {table .timeSlots.map((timeSlot, index) => {88 <h3>Table for {table.capacity} guests</h3> 89 {tableTimeSlots.map((timeSlot, index) => { 44 90 const timeSlotTime = new Date(timeSlot).getTime(); 45 const tableCapacity = table.capacity;46 91 47 // Check if the time slot is after the current time and limit to 3 slots 48 if (timeSlotTime > currentTime && renderedTimeSlots[tableCapacity] < 3) { 49 renderedTimeSlots[tableCapacity]++; 92 if (timeSlotTime > currentTime && renderedTimeSlots[table.capacity] < 3) { 93 renderedTimeSlots[table.capacity]++; 50 94 const timeSlotDateTime = new Date(timeSlot); 51 const formattedTime = timeSlotDateTime.toLocaleTimeString(); 52 const formattedDateTime = timeSlotDateTime.toLocaleString(); // Format for both date and time 95 const formattedTime = timeSlotDateTime.toLocaleTimeString([], { 96 hour: '2-digit', 97 minute: '2-digit' 98 }); 53 99 54 100 return ( 55 <button key={index} className="btn btn-primary me-2 mb-2" onClick={() => handleTimeSlotClick(table, timeSlot, restaurant)}> 56 {formattedDateTime} {/* Display both date and time */} 101 <button 102 key={index} 103 className="btn btn-primary me-2 mb-2" 104 onClick={() => handleTimeSlotClick(table, timeSlot, restaurant)} 105 > 106 {formattedTime} {} 57 107 </button> 58 108 ); 59 109 } else { 60 return null; // Render nothing if the condition is not met110 return null; 61 111 } 62 112 })} … … 64 114 ); 65 115 } else { 66 // If capacity has been rendered, return null to avoid duplicate rendering67 116 return null; 68 117 } 69 118 }); 70 71 } 72 119 }; 73 120 74 121 return ( … … 77 124 <div className="row"> 78 125 {restaurants.map((restaurant) => ( 79 <div key={restaurant. id} className="col-md-4 mb-4">126 <div key={restaurant.restaurantId} className="col-md-4 mb-4"> 80 127 <div className="card"> 81 128 <div className="card-body"> 82 129 <h5 className="card-title"> 83 {restaurant.name} <StarRating key={restaurant.id} rating={restaurant.rating}/>130 {restaurant.name} 84 131 </h5> 85 132 <p className="card-text">{restaurant.cuisineType}</p> 86 133 <p className="card-text">{restaurant.operatingHours}</p> 87 134 <p className="card-text">Ul. {restaurant.address}</p> 88 <div className="d-flex flex-wrap"> 89 {renderTimeSlots(restaurant.tablesList.flatMap((table) => table), restaurant)} 90 </div> 135 136 {restaurant.tablesList && restaurant.tablesList.length > 0 ? ( 137 <div className="d-flex flex-wrap"> 138 {renderTimeSlots(restaurant.tablesList, restaurant)} 139 </div> 140 ) : ( 141 <p>No tables available for reservations at this restaurant.</p> 142 )} 91 143 </div> 92 <button onClick={() => handleDetailClick(restaurant.restaurantId)} 93 className="btn btn-primary">View Details144 <button onClick={() => handleDetailClick(restaurant.restaurantId)} className="btn btn-primary"> 145 View Details 94 146 </button> 95 147 </div>
Note:
See TracChangeset
for help on using the changeset viewer.