Changeset f5b256e


Ignore:
Timestamp:
04/28/25 14:20:18 (3 weeks ago)
Author:
Aleksandar Panovski <apano77@…>
Branches:
main
Children:
deea3c4
Parents:
8ca35dc
Message:

Big change done works with handle_reservation_update() trigger

Files:
6 added
6 deleted
15 edited

Legend:

Unmodified
Added
Removed
  • my-react-app/package-lock.json

    r8ca35dc rf5b256e  
    1616        "jwt-decode": "^4.0.0",
    1717        "react": "^18.2.0",
     18        "react-bootstrap-datetimepicker": "^0.0.22",
    1819        "react-dom": "^18.2.0",
    1920        "react-router-dom": "^6.22.0",
     
    57695770      }
    57705771    },
     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    },
    57715786    "node_modules/balanced-match": {
    57725787      "version": "1.0.2",
     
    61676182      "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz",
    61686183      "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=="
    61696189    },
    61706190    "node_modules/clean-css": {
     
    1272212742      "bin": {
    1272312743        "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": "*"
    1272412752      }
    1272512753    },
     
    1489514923      "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
    1489614924    },
     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    },
    1489714938    "node_modules/react-dev-utils": {
    1489814939      "version": "12.0.1",
  • my-react-app/package.json

    r8ca35dc rf5b256e  
    1111    "jwt-decode": "^4.0.0",
    1212    "react": "^18.2.0",
     13    "react-bootstrap-datetimepicker": "^0.0.22",
    1314    "react-dom": "^18.2.0",
    1415    "react-router-dom": "^6.22.0",
  • my-react-app/src/App.js

    r8ca35dc rf5b256e  
    11import {BrowserRouter as Router, Navigate, Route, Routes, useNavigate} from 'react-router-dom';
     2
    23import Customers from './components/Customers';
    34import Layout from "./components/Layout";
     
    1718import AppContent from "./components/AppContent";
    1819import ReservationHistory from "./components/ReservationHistory";
     20import AuthContent from "./components/AuthContent";
    1921
    2022const ProtectedRoute = ({ element, isAuthenticated }) => {
     
    2325
    2426const App = () => {
    25     const [isAuthenticated, setIsAuthenticated] = React.useState(false);
    26 
    27     React.useEffect(() => {
     27    const [isAuthenticated, setIsAuthenticated] = useState(false);
     28
     29    useEffect(() => {
    2830        const token = localStorage.getItem('token');
    2931        if (token) {
     
    6062    const navigate = useNavigate();
    6163
    62     const todayDate = new Date().toISOString().split('T')[0]; // Get today's date in 'YYYY-MM-DD' format
     64    const todayDate = new Date().toISOString().split('T')[0];
    6365
    6466    const [date, setDate] = useState(todayDate);
     
    8082            const isToday = selectedDate.toDateString() === today.toDateString();
    8183
    82             // Determine the start hour and minute
    8384            let startHour = 9;
    8485            let startMinute = 0;
     
    8687                const currentHour = today.getHours();
    8788                const currentMinute = today.getMinutes();
    88                 // If current time is later than 09:00, start from the current hour and minute
    8989                if (currentHour > 9 || (currentHour === 9 && currentMinute >= 0)) {
    9090                    startHour = currentHour;
     
    9393            }
    9494
    95             // Create the start time and end time
    9695            const startTime = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate(), startHour, startMinute);
    9796            const endTime = new Date(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate(), 23, 30);
    9897
    99             // Generate time slots from start time to end time in 15-minute intervals
    10098            const slots = [];
    10199            let currentTime = new Date(startTime);
     
    106104            }
    107105
    108             // Update the timeSlots state
    109106            setTimeSlots(slots);
    110107        }
    111108    }, [date]);
     109
     110    const handleGoToRestaurant = (restaurantId) => {
     111        navigate(`/restaurants/${restaurantId}`);
     112    };
    112113
    113114    const handleDateChange = (e) => {
     
    134135        if (selectedTime) {
    135136            const [selectedHours, selectedMinutes] = selectedTime.split(":");
    136             // Check if selectedHours and selectedMinutes are valid numbers
    137137            if (!isNaN(selectedHours) && !isNaN(selectedMinutes)) {
    138138                const dateTime = new Date(Date.UTC(year, month - 1, day, selectedHours, selectedMinutes));
     
    141141            }
    142142        } else {
    143             // Find the first available time slot after the current time
    144143            const now = new Date();
    145             const currentTime = now.getHours() * 60 + now.getMinutes(); // Current time in minutes
     144            const currentTime = now.getHours() * 60 + now.getMinutes();
    146145            const nextSlot = timeSlots.find(slot => {
    147146                const [hours, minutes] = slot.split(":");
    148                 const slotTime = parseInt(hours) * 60 + parseInt(minutes); // Time of the slot in minutes
     147                const slotTime = parseInt(hours) * 60 + parseInt(minutes);
    149148                return slotTime > currentTime;
    150149            });
    151150
    152             // If no slot is found after the current time, use the first slot of the day
    153151            formattedDateTime = nextSlot ? `${date} ${nextSlot}` : `${date} ${timeSlots[0]}`;
    154152        }
     
    160158        };
    161159
    162         console.log("Data to be submitted:");
    163         console.log(data);
    164 
    165160        try {
    166161            const response = await axios.post('http://localhost:8081/api/search', data);
    167162            const filteredRestaurants = response.data;
    168163            setFilteredRestaurants(filteredRestaurants);
    169             console.log(filteredRestaurants);
     164            console.log(filteredRestaurants)
    170165            setShowCuisineSearch(false);
    171             // Handle response accordingly
    172166        } catch (error) {
    173167            console.error('Error:', error);
     
    179173        try {
    180174            const response = await axios.post(`http://localhost:8081/api/search/shortcut/${cuisineName}`, cuisineName);
    181             console.log(response.data);
    182175            setFilteredRestaurants(response.data)
     176            console.log(response.data)
    183177        } catch (error) {
    184178            console.error('Error searching by cuisine:', error);
     
    186180        setShowCuisineSearch(false);
    187181    };
     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}`;
    188224
    189225    const handleTimeSlotClick = (table, timeSlot, restaurant) => {
     
    192228        const restaurantId = restaurant.restaurantId;
    193229
    194         const encodedTableNumber = encodeURI(tableNumber);
     230        const encodedTableNumber = encodeURIComponent(tableNumber);
    195231        const encodedTimeSlot = encodeURIComponent(formattedTimeSlot);
    196232        const encodedRestaurantId = encodeURIComponent(restaurantId);
    197233
    198234        navigate(`/reservationConfirmation/${encodedTableNumber}/${encodedTimeSlot}/${encodedRestaurantId}`);
    199     }
     235    };
    200236
    201237    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]) {
    216249                renderedTimeSlots[table.capacity] = 0;
    217250                return (
    218251                    <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]++;
    227258                                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                                });
    229263
    230264                                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} {}
    233271                                    </button>
    234272                                );
     273                                <br/>
    235274                            } else {
    236                                 return null; // Render nothing if the condition is not met
     275                                return null;
    237276                            }
    238277                        })}
     
    240279                );
    241280            } else {
    242                 // If capacity has been rendered, return null to avoid duplicate rendering
    243281                return null;
    244282            }
    245283        });
    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    };
    257285
    258286
     
    286314                        placeholder="Restaurant or Cuisine"
    287315                        aria-label="Search"
    288                         value={searchValue} // Set the value of the input field
    289                         onChange={handleInputChange} // Call the event handler on change
     316                        value={searchValue}
     317                        onChange={handleInputChange}
    290318                    />
    291319                </div>
     
    299327                            <div className="card-body">
    300328                                <RestaurantInfo key={restaurant.id} restaurant={restaurant}/>
    301                                 <p>Available time slots</p>
     329                                {/*<p>Available time slots</p>*/}
    302330                                <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                                    )}
    304338                                </div>
     339                                <button
     340                                    className="btn btn-secondary"
     341                                    onClick={() => handleGoToRestaurant(restaurant.restaurantId)}
     342                                >
     343                                    Go to Restaurant
     344                                </button>
    305345                            </div>
    306346                        </div>
     
    308348                </div>
    309349
    310 
    311350                {showCuisineSearch && (
    312351                    <div className="mb-3">
    313352                        <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                )}
    325365            </form>
    326366        </div>
  • my-react-app/src/axios_helper.js

    r8ca35dc rf5b256e  
    55
    66export const getAuthToken = () => {
    7     return window.localStorage.getItem("auth_token");
     7    return window.localStorage.getItem("token");
    88}
    99
     
    1717        method: method,
    1818        url: url,
    19         data: data
     19        data: data ? JSON.stringify(data) : null,
     20        headers
    2021    })
    2122}
  • my-react-app/src/components/AppContent.js

    r8ca35dc rf5b256e  
    1515            isAuthenticated: false,
    1616            user: null,
    17             loading: false // Add loading state
     17            loading: false
    1818        };
    1919    }
     
    2929
    3030    fetchUserDetails = (token) => {
    31         console.log("Fetch");
     31        console.log("Fetching User Details...");
    3232        axios.get("/api/user")
    3333            .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                });
    3539            })
    3640            .catch((error) => {
    3741                console.error("Failed to fetch user details:", error);
    38                 this.logout();
    3942            });
    4043    };
     
    5255    onLogin = (e, email, password) => {
    5356        e.preventDefault();
    54         // After successful login, save the token
     57
    5558        axios.post("/api/login", { email, password })
    5659            .then((response) => {
    5760                const token = response.data.token;
    58                 localStorage.setItem('token', token);  // Save the token
    59                 console.log(token);
    60                 this.setAuthToken(token);  // Set the token for future requests
    61                 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);
    6265            })
    6366            .catch((error) => {
     
    6568                this.setState({ componentToShow: "welcome" });
    6669            });
    67 
    6870    };
    6971
     
    9395
    9496    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
    95106        return (
    96107            <div>
    97108                <Buttons login={this.login} logout={this.logout} />
    98109                {this.state.componentToShow === "welcome" && <WelcomeContent />}
    99                 {this.state.componentToShow === "restaurants" && <AuthContent />}
    100110                {this.state.componentToShow === "login" && <LoginForm onLogin={this.onLogin} onRegister={this.onRegister} />}
    101                 {this.state.loading && <div>Loading...</div>} {/* Show loading state */}
    102111            </div>
    103112        );
     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        // );
    104122    }
    105123}
  • my-react-app/src/components/AuthContent.js

    r8ca35dc rf5b256e  
    1010    }
    1111
     12    // componentDidMount() {
     13    //     request(
     14    //         "GET",
     15    //         "/api/restaurants",
     16    //         []
     17    //     ).then((response) => {
     18    //         this.setState({ data: response.data });
     19    //     });
     20    // }
    1221    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        }
    2032    }
    2133
  • my-react-app/src/components/Header.js

    r8ca35dc rf5b256e  
    1 import React from 'react';
     1import React, {useEffect, useState} from 'react';
    22import { Link, useNavigate } from 'react-router-dom';
     3import {jwtDecode} from "jwt-decode";
     4import axios from "axios";
    35
    46const Header = () => {
    57    const navigate = useNavigate();
    6 
    7     // Check if the user is logged in by looking for a token in localStorage
     8    const [user, setUser] = useState({});
    89    const isLoggedIn = localStorage.getItem('token');
    910
    1011    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
    1517    };
     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]);
    1643
    1744    return (
     
    3764                        <form className="form-inline mt-2 mt-md-0 ml-3">
    3865                            {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>
    4069                            ) : (
    41                                 <Link className="btn btn-outline-info my-2 my-sm-0" to="/login">Login</Link>
     70                                <Link className="btn btn-outline-info ml-3" to="/login">Login</Link>
    4271                            )}
    4372                        </form>
  • my-react-app/src/components/Login.js

    r8ca35dc rf5b256e  
    2222            const { token } = response.data;
    2323
    24             // Store token securely (consider httpOnly cookies)
    2524            localStorage.setItem('token', token);
    2625
    2726            navigate("/")
    2827        } catch (error) {
    29             // Handle login failure
    3028            console.error('Login failed:', error);
    3129            setError('Login failed. Please check your credentials and try again.');
  • my-react-app/src/components/ReservationConfirmation.js

    r8ca35dc rf5b256e  
    44import { useNavigate } from 'react-router-dom';
    55import {jwtDecode} from "jwt-decode";
     6import {request} from "../axios_helper";
     7import restaurants from "./Restaurants";
    68
    79const ReservationConfirmation = () => {
     
    911
    1012    const [restaurant, setRestaurant] = useState({});
     13    const [user, setUser] = useState({});
    1114    const [table, setTable] = useState({});
    1215    const [reservationDateTime, setReservationDateTime] = useState('');
     
    1518    const { tableNumber, timeSlot, restaurantId } = useParams();
    1619
     20    const adjustedTimeSlot = new Date(new Date(timeSlot).getTime() + 60 * 60 * 1000).toISOString();
    1721    useEffect(() => {
    18         const fetchTableDetails = async () => {
     22        const fetchDetails = async () => {
    1923            try {
    2024                const tableResponse = await axios.get(`http://localhost:8081/api/tables/${tableNumber}`);
     
    2327                const restaurantResponse = await axios.get(`http://localhost:8081/api/restaurants/${restaurantId}`);
    2428                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);
    2540            } catch (error) {
    26                 console.error('Error fetching table details:', error);
     41                console.error('Error fetching table or restaurant details:', error);
    2742            }
    2843        };
    29         fetchTableDetails();
     44        fetchDetails();
    3045    }, [tableNumber, restaurantId]);
    3146
     
    3348        e.preventDefault();
    3449
    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
    3963
    4064        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.');
    4573            }
    46 
    47             // Decode the token to get the user email
    48             const decodedToken = jwtDecode(token);
    49             console.log(decodedToken)
    50             const userId = decodedToken.iss;// Assuming the email is part of the decoded JWT token
    51 
    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: specialRequests
    60                 },
    61                 {
    62                     headers: {
    63                         Authorization: `Bearer ${token}` // Include the token here
    64                     }
    65                 }
    66             );
    67 
    68             navigate("/reservations");
    69         } catch (error) {
    70             console.error("Error creating reservation:", error);
    7174        }
    7275    };
    7376
     77    const calculateCheckOutTime = (checkInTime) => {
     78        const checkIn = new Date(checkInTime);
     79        checkIn.setHours(checkIn.getHours() + 2);
     80        return checkIn.toISOString();
     81    };
     82
    7483    const initialRemainingTime = localStorage.getItem('remainingTime') || 300;
    75     const [remainingTime, setRemainingTime] = useState(parseInt(initialRemainingTime));
     84    const [remainingTime, setRemainingTime] = useState(parseInt(initialRemainingTime, 10));
    7685
    7786    useEffect(() => {
     
    7988            setRemainingTime((prevTime) => {
    8089                const newTime = prevTime - 1;
    81                 localStorage.setItem('remainingTime', newTime.toString()); // Update remaining time in localStorage
     90                localStorage.setItem('remainingTime', newTime.toString());
    8291                return newTime;
    8392            });
     
    8897
    8998    useEffect(() => {
    90         // Reset remaining time if it reaches zero
    9199        if (remainingTime <= 0) {
    92100            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
    94103        }
    95     }, [remainingTime]);
     104    }, [remainingTime, navigate]);
    96105
    97106    const formatTime = (timeInSeconds) => {
     
    99108        const seconds = timeInSeconds % 60;
    100109        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}`;
    101118    };
    102119
     
    114131                                <h5 className="card-title">Reservation Details</h5>
    115132                                <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 />
    125151                                </p>
    126152                                <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 />
    129157                                </p>
    130158                            </div>
    131159                            <div className="card-footer">
    132160                                <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>
    139163                            </div>
    140164                        </form>
    141165                    </div>
    142166                </div>
    143 
    144167            </div>
    145168        </div>
  • my-react-app/src/components/ReservationEdit.js

    r8ca35dc rf5b256e  
    77const ReservationEdit = () => {
    88    const navigate = useNavigate();
    9 
     9    const { reservationId } = useParams();
    1010    const [isLoading, setIsLoading] = useState(true);
    11     const { reservationId } = useParams(); // Extract reservationId from URL params
    1211    const [formData, setFormData] = useState({});
     12    const [tableNumber, setTableNumber] = useState({});
    1313    const [table, setTable] = useState({});
    1414    const [restaurant, setRestaurant] = useState({});
     15    const [restaurantId, setRestaurantId] = useState({});
    1516    const [timeSlots, setTimeSlots] = useState([]);
    1617    const [filteredTimeSlots, setFilteredTimeSlots] = useState([]);
    1718    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([]);
    1826
    1927    useEffect(() => {
     
    2230                setIsLoading(true);
    2331                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);
    2534                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
    2942                setIsLoading(false);
    3043            } catch (error) {
     
    3245            }
    3346        };
     47
    3448        fetchReservation();
    3549    }, [reservationId]);
    3650
    3751    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]);
    45123
    46124    const handleInputChange = (e) => {
    47125        const { name, value } = e.target;
    48126
    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            }));
    53133        } 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
    58141
    59142    const handleSubmit = async (e) => {
     
    68151
    69152            const decodedToken = jwtDecode(token);
    70             console.log(decodedToken)
    71153            const userId = decodedToken.iss;
    72154
    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`);
    76173        } catch (error) {
    77174            console.error('Error updating reservation:', error);
     
    86183    };
    87184
    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]);
    94213
    95214    return (
    96215        <div className="container">
    97             {isLoading ? ( // Conditional rendering based on loading state
     216            {isLoading ? (
    98217                <p>Loading...</p>
    99218            ) : (
     
    101220                    <h1>Edit Reservation</h1>
    102221                    <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} />
    105224                        </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 />
    110229                    </div>
    111230                    <form onSubmit={handleSubmit}>
    112231                        <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>
    120253                                ))}
    121254                            </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>
    123258                        </div>
    124259                        <div className="mb-3">
    125260                            <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>
    129274                        </div>
    130275                        <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                            />
    134285                        </div>
    135286                        <button type="submit" className="btn btn-primary">Submit</button>
     
    139290        </div>
    140291    );
    141 
    142292};
    143293
  • my-react-app/src/components/ReservationHistory.js

    r8ca35dc rf5b256e  
    55const ReservationHistory = () => {
    66    const [reservations, setReservations] = useState([]);
     7    const [filteredReservations, setFilteredReservations] = useState([]);
    78    const [loading, setLoading] = useState(true);
    89    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("");
    919
    1020    useEffect(() => {
     
    2939
    3040                setReservations(response.data);
     41                setFilteredReservations(response.data);
    3142            } catch (err) {
    3243                setError("Failed to fetch reservations.");
     
    3950    }, []);
    4051
     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
    41112    if (loading) return <div>Loading...</div>;
    42113    if (error) return <div className="alert alert-danger">{error}</div>;
     
    44115    return (
    45116        <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>
    71205                    </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>
    75229        </div>
    76230    );
  • my-react-app/src/components/Reservations.js

    r8ca35dc rf5b256e  
    2020                }
    2121                const decodedToken = jwtDecode(token);
    22                 console.log(decodedToken)
    2322                const userId = decodedToken.iss;
    2423
     
    4948        }
    5049    };
    51 
    52 
    5350    return (
    5451        <div className="container">
    55             <h2>Reservations</h2>
    5652            <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
    9098                                        </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
    96103                                        </button>
    97104                                    </div>
     
    99106                            </div>
    100107                        </div>
    101                     </div>
    102                 ))}
     108                    ))
     109                )}
    103110            </div>
    104111        </div>
    105112    );
    106     p
    107 
    108113};
    109114
    110115export default Reservations;
     116
  • my-react-app/src/components/RestaurantContext.js

    r8ca35dc rf5b256e  
    1717        };
    1818
    19         fetchRestaurants().then(r => console.log(fetchRestaurants()));
     19        fetchRestaurants();
    2020    }, []);
    2121
  • my-react-app/src/components/RestaurantDetails.js

    r8ca35dc rf5b256e  
    66import StarRating from "./StarRating";
    77
     8
    89const RestaurantDetails = () => {
    910    const navigate = useNavigate();
     11    const { id } = useParams();
    1012
    11     const { id } = useParams();
    1213    const [restaurant, setRestaurant] = useState(null);
    1314    const [selectedTableId, setSelectedTableId] = useState('');
    14     const [selectedTimeSlot, setSelectedTimeSlot] = useState('');
     15    const [selectedDate, setSelectedDate] = useState('');
     16    const [selectedTime, setSelectedTime] = useState('');
     17    const [timeOptions, setTimeOptions] = useState([]);
    1518    const [selectedTable, setSelectedTable] = useState(null);
    16     const [filteredTimeSlots, setFilteredTimeSlots] = useState([]);
    17     const [selectedCapacity, setSelectedCapacity] = useState(''); // Define selectedCapacity state
    1819
    1920    useEffect(() => {
     
    3233
    3334    useEffect(() => {
    34         if (!selectedTableId) return; // If no table is selected, return early
     35        if (!selectedTableId) return;
    3536
    3637        const fetchTableDetails = async () => {
     
    4647    }, [selectedTableId]);
    4748
    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);
    5058
    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);
    5661
     62        const endTime = new Date();
     63        endTime.setHours(endHour < startHour ? endHour + 24 : endHour, endMinute, 0, 0);
    5764
    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 };
    6366    };
    6467
    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;
    6884    };
    6985
    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);
    7395    };
    7496
    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);
    79101
    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}`);
    85103    };
    86104
     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    };
    87114
    88115    return (
     
    91118                <>
    92119                    <h2 className="card-title">
    93                         {restaurant.name} <StarRating key={restaurant.id} rating={restaurant.rating}/>
     120                        {restaurant.name} <StarRating key={restaurant.id} rating={restaurant.rating} />
    94121                    </h2>
    95122                    <div className="restaurant-details">
     
    98125                        <p>Address: {restaurant.address}</p>
    99126                        <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>
    101130                        <p>Social Media Links: {restaurant.socialMediaLinks}</p>
    102131
    103132                        <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                        >
    106139                            <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>
    110144                            ))}
    111145                        </select>
     146
    112147                        {selectedTable && (
    113148                            <>
    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>
    119172                                    ))}
    120173                                </select>
    121174                            </>
    122175                        )}
    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                        >
    126183                            Confirm Reservation
    127184                        </button>
  • my-react-app/src/components/Restaurants.js

    r8ca35dc rf5b256e  
    44import { useNavigate } from 'react-router-dom';
    55import {RestaurantContext} from "./RestaurantContext";
     6import {Alert} from "bootstrap";
     7
     8const parseTime = (timeString) => {
     9    const [hours, minutes] = timeString.trim().split(':').map(Number);
     10    return new Date().setHours(hours, minutes, 0, 0);
     11};
     12
     13const 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
     20const shouldMoveToNextDay = (currentTime, endTime) => {
     21    return (endTime - currentTime) <= 2 * 60 * 60 * 1000;
     22};
    623
    724const Restaurants = () => {
     
    1431    }, [restaurantContext]);
    1532
     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
    1657    const handleDetailClick = (restaurantId) => {
    1758        navigate(`/restaurants/${restaurantId}`);
    18     }
     59    };
    1960
    2061    const handleTimeSlotClick = (table, timeSlot, restaurant) => {
     
    2364        const restaurantId = restaurant.restaurantId;
    2465
    25         const encodedTableNumber = encodeURI(tableNumber);
     66        const encodedTableNumber = encodeURIComponent(tableNumber);
    2667        const encodedTimeSlot = encodeURIComponent(formattedTimeSlot);
    2768        const encodedRestaurantId = encodeURIComponent(restaurantId);
    2869
    2970        navigate(`/reservationConfirmation/${encodedTableNumber}/${encodedTimeSlot}/${encodedRestaurantId}`);
    30     }
     71    };
    3172
    3273    const renderTimeSlots = (tablesList, restaurant) => {
    3374        const currentTime = new Date().getTime();
    34         let renderedTimeSlots = {}; // Object to store rendered slots for each table capacity
     75        let renderedTimeSlots = {};
    3576
    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
    3884            if (!renderedTimeSlots[table.capacity]) {
    3985                renderedTimeSlots[table.capacity] = 0;
    4086                return (
    4187                    <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) => {
    4490                            const timeSlotTime = new Date(timeSlot).getTime();
    45                             const tableCapacity = table.capacity;
    4691
    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]++;
    5094                                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                                });
    5399
    54100                                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} {}
    57107                                    </button>
    58108                                );
    59109                            } else {
    60                                 return null; // Render nothing if the condition is not met
     110                                return null;
    61111                            }
    62112                        })}
     
    64114                );
    65115            } else {
    66                 // If capacity has been rendered, return null to avoid duplicate rendering
    67116                return null;
    68117            }
    69118        });
    70 
    71     }
    72 
     119    };
    73120
    74121    return (
     
    77124            <div className="row">
    78125                {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">
    80127                        <div className="card">
    81128                            <div className="card-body">
    82129                                <h5 className="card-title">
    83                                     {restaurant.name} <StarRating key={restaurant.id} rating={restaurant.rating}/>
     130                                    {restaurant.name}
    84131                                </h5>
    85132                                <p className="card-text">{restaurant.cuisineType}</p>
    86133                                <p className="card-text">{restaurant.operatingHours}</p>
    87134                                <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                                )}
    91143                            </div>
    92                             <button onClick={() => handleDetailClick(restaurant.restaurantId)}
    93                                     className="btn btn-primary">View Details
     144                            <button onClick={() => handleDetailClick(restaurant.restaurantId)} className="btn btn-primary">
     145                                View Details
    94146                            </button>
    95147                        </div>
Note: See TracChangeset for help on using the changeset viewer.