diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6a007ee..3c0cbd6 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,6 +16,7 @@ "jwt-decode": "^4.0.0", "leaflet": "^1.9.4", "react": "^18.3.1", + "react-datepicker": "^7.4.0", "react-dom": "^18.3.1", "react-icons": "^5.3.0", "react-leaflet": "^4.2.1", @@ -2177,6 +2178,14 @@ "node": ">=0.8" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/code-block-writer": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz", @@ -2362,6 +2371,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -5239,6 +5257,47 @@ "node": ">=0.10.0" } }, + "node_modules/react-datepicker": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-7.4.0.tgz", + "integrity": "sha512-vSSok4DTZ9/Os8O4HjZLxh4SZVFU6dQvoCX6mfbNdBqMsBBdzftrvMz0Nb4UUVVbgj9o8PfX84K3/31oPrTqmg==", + "dependencies": { + "@floating-ui/react": "^0.26.23", + "clsx": "^2.1.1", + "date-fns": "^3.6.0", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17 || ^18", + "react-dom": "^16.9.0 || ^17 || ^18" + } + }, + "node_modules/react-datepicker/node_modules/@floating-ui/react": { + "version": "0.26.24", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.24.tgz", + "integrity": "sha512-2ly0pCkZIGEQUq5H8bBK0XJmc1xIK/RM3tvVzY3GBER7IOD1UgmC2Y2tjj4AuS+TC+vTE1KJv2053290jua0Sw==", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/react-datepicker/node_modules/@floating-ui/react-dom": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 9b32f06..0907ab9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -19,6 +19,7 @@ "jwt-decode": "^4.0.0", "leaflet": "^1.9.4", "react": "^18.3.1", + "react-datepicker": "^7.4.0", "react-dom": "^18.3.1", "react-icons": "^5.3.0", "react-leaflet": "^4.2.1", diff --git a/frontend/src/Pages/booking.jsx b/frontend/src/Pages/booking.jsx index 07dbc73..08bf9bb 100644 --- a/frontend/src/Pages/booking.jsx +++ b/frontend/src/Pages/booking.jsx @@ -2,200 +2,205 @@ import React, { useState, useEffect } from 'react'; import { IoCalendarOutline, IoArrowBack } from 'react-icons/io5'; import { useNavigate } from 'react-router-dom'; import axios from 'axios'; - +import DatePicker from 'react-datepicker'; // Import DatePicker +import 'react-datepicker/dist/react-datepicker.css'; // Import default styles const BookingPage = () => { - const [station, setStation] = useState(''); // Holds the typed input - const [selectedStation, setSelectedStation] = useState(null); // Holds the selected station - const [date, setDate] = useState(''); - const [services, setServices] = useState([ - { id: 'cloak', name: 'Cloak Room Booking', availability: 0 }, - { id: 'wheelchair', name: 'Wheelchair Booking', availability: 0 }, - { id: 'coolie', name: 'Coolie Booking', availability: 0 } - ]); - const [stationSuggestions, setStationSuggestions] = useState([]); // Holds station suggestions - const [loading, setLoading] = useState(false); - const [error, setError] = useState(''); // Track errors - const [noResults, setNoResults] = useState(false); // Track if no stations are found - const navigate = useNavigate(); - - // Fetch stations suggestions as the user types - const fetchStationSuggestions = async (query) => { - try { - const response = await axios.get(`http://localhost:3000/station/`); - - if (response.data.length > 0) { - setStationSuggestions(response.data); // The response should include station _id and name - setNoResults(false); // Reset no results flag - } else { - setStationSuggestions([]); - setNoResults(true); // Set flag if no matching stations are found - } - } catch (err) { - setError("Error fetching station suggestions. Please try again."); - setNoResults(false); // Clear no results if there’s an error - } - }; - - - // Function to fetch service availability data from the backend - const fetchServiceData = async (stationId) => { - try { - setLoading(true); - const response = await axios.get(`http://localhost:3000/station/${stationId}/bookings`); - const { coolieBookings, wheelchairBookings, cloakroomBookings } = response.data; - - // Update the availability in the services array - setServices([ - { id: 'cloak', name: 'Cloak Room Booking', availability: cloakroomBookings.length }, - { id: 'wheelchair', name: 'Wheelchair Booking', availability: wheelchairBookings.length }, - { id: 'coolie', name: 'Coolie Booking', availability: coolieBookings.length } - ]); - - setLoading(false); - } catch (err) { - setError('Error fetching service data. Please try again.'); - setLoading(false); - } - }; - - // Fetch service availability when the selected station is updated - useEffect(() => { - if (selectedStation) { - fetchServiceData(selectedStation._id); // Assuming station ID is _id - } - }, [selectedStation]); - - // Handle station input change - const handleStationInputChange = (e) => { - const value = e.target.value; - setStation(value); - setError(''); // Clear error message on input change - - if (value.length > 2) { - // Fetch station suggestions when input has more than 2 characters - fetchStationSuggestions(value); + useEffect(() => { + document.title = 'Station Saarthi | Booking'; + }, []); + + const [station, setStation] = useState(''); // Holds the typed input + const [selectedStation, setSelectedStation] = useState(null); // Holds the selected station + const [date, setDate] = useState(null); // Initial date state for DatePicker + const [services, setServices] = useState([ + { id: 'cloak', name: 'Cloak Room Booking', availability: 0 }, + { id: 'wheelchair', name: 'Wheelchair Booking', availability: 0 }, + { id: 'coolie', name: 'Coolie Booking', availability: 0 } + ]); + const [stationSuggestions, setStationSuggestions] = useState([]); // Holds station suggestions + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); // Track errors + const [noResults, setNoResults] = useState(false); // Track if no stations are found + const navigate = useNavigate(); + + // Fetch stations suggestions as the user types + const fetchStationSuggestions = async (query) => { + try { + const response = await axios.get(`http://localhost:3000/station/`); + + if (response.data.length > 0) { + setStationSuggestions(response.data); // The response should include station _id and name + setNoResults(false); // Reset no results flag } else { - setStationSuggestions([]); // Clear suggestions if input is too short - setNoResults(false); // Clear no results if input is too short + setStationSuggestions([]); + setNoResults(true); // Set flag if no matching stations are found } - }; - - // Handle station selection from suggestions - const handleStationSelect = (station) => { - setSelectedStation(station); // Set the selected station - setStation(station.name); // Update the input value with the selected station's name - setStationSuggestions([]); // Clear the suggestions after selection - setError(''); // Clear any previous errors - }; - - return ( -
-
- -
-
- + } catch (err) { + setError("Error fetching station suggestions. Please try again."); + setNoResults(false); // Clear no results if there’s an error + } + }; + + // Function to fetch service availability data from the backend + const fetchServiceData = async (stationId) => { + try { + setLoading(true); + const response = await axios.get(`http://localhost:3000/station/${stationId}/bookings`); + const { coolieBookings, wheelchairBookings, cloakroomBookings } = response.data; + + // Update the availability in the services array + setServices([ + { id: 'cloak', name: 'Cloak Room Booking', availability: cloakroomBookings.length }, + { id: 'wheelchair', name: 'Wheelchair Booking', availability: wheelchairBookings.length }, + { id: 'coolie', name: 'Coolie Booking', availability: coolieBookings.length } + ]); + + setLoading(false); + } catch (err) { + setError('Error fetching service data. Please try again.'); + setLoading(false); + } + }; + + // Fetch service availability when the selected station is updated + useEffect(() => { + if (selectedStation) { + fetchServiceData(selectedStation._id); // Assuming station ID is _id + } + }, [selectedStation]); + + // Handle station input change + const handleStationInputChange = (e) => { + const value = e.target.value; + setStation(value); + setError(''); // Clear error message on input change + + if (value.length > 2) { + // Fetch station suggestions when input has more than 2 characters + fetchStationSuggestions(value); + } else { + setStationSuggestions([]); // Clear suggestions if input is too short + setNoResults(false); // Clear no results if input is too short + } + }; + + // Handle station selection from suggestions + const handleStationSelect = (station) => { + setSelectedStation(station); // Set the selected station + setStation(station.name); // Update the input value with the selected station's name + setStationSuggestions([]); // Clear the suggestions after selection + setError(''); // Clear any previous errors + }; + + return ( +
+
+ +
+
+ +
+ + {/* Station Input */} +
+

+ Station Services +

+ +
+ + + {/* Station suggestions dropdown */} + {stationSuggestions.length > 0 && ( +
    + {stationSuggestions.map((suggestion) => ( +
  • handleStationSelect(suggestion)} + className="px-4 py-2 hover:bg-blue-100 cursor-pointer" + > + {suggestion.name} +
  • + ))} +
+ )} + + {/* Handle no results found */} + {noResults && ( +

No stations found matching your search.

+ )} + + {/* Handle errors */} + {error && ( +

{error}

+ )}
- -
-

- Station Services -

- - {/* Station Input */} -
- - + +
+ setDate(date)} // Update date state + dateFormat="dd/MM/yyyy" // Set your desired date format + placeholderText="DD/MM/YY" className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300" + popperClassName="z-50" // Adjust z-index of the date picker + /> + - {/* Station suggestions dropdown */} - {stationSuggestions.length > 0 && ( -
    - {stationSuggestions.map((suggestion) => ( -
  • handleStationSelect(suggestion)} - className="px-4 py-2 hover:bg-blue-100 cursor-pointer" - > - {suggestion.name} -
  • - ))} -
- )} - - {/* Handle no results found */} - {noResults && ( -

No stations found matching your search.

- )} - - {/* Handle errors */} - {error && ( -

{error}

- )} -
- - {/* Date input */} -
- -
- setDate(e.target.value)} - placeholder="DD/MM/YY" - className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 transition duration-300" - /> - -
- - {/* Render Services */} - {loading ? ( -

Loading services...

- ) : error && !stationSuggestions.length ? ( -

{error}

- ) : ( -
- {services.map((service) => ( -
-
-

{service.name}

- - - Available: {service.availability} - -
- -
- ))} -
- )}
+ + {/* Render Services */} + {loading ? ( +

Loading services...

+ ) : ( +
+ {services.map((service) => ( +
+
+

{service.name}

+ + + Available: {service.availability} + +
+ +
+ ))} +
+ )}
- ); - }; - - export default BookingPage; - \ No newline at end of file +
+ ); +}; + +export default BookingPage;