diff --git a/frontend/package-lock.json b/frontend/package-lock.json index a8cc84a3..bae02004 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -37,6 +37,7 @@ "react-router-dom": "^6.26.2", "react-router-hash-link": "^2.4.3", "react-scripts": "^5.0.1", + "react-select": "^5.8.1", "reactjs-popup": "^2.0.6" }, "devDependencies": { @@ -2568,6 +2569,135 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz", + "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.2.0", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.13.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz", + "integrity": "sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.13.3", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.3.tgz", + "integrity": "sha512-lIsdU6JNrmYfJ5EbUCf4xW1ovy5wKQ2CkPRM4xogziOxH1nXxBSjpC9YqbFAP7circxMfYp+6x676BqWcEiixg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/cache": "^11.13.0", + "@emotion/serialize": "^1.3.1", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.2.tgz", + "integrity": "sha512-grVnMvVPK9yUVE6rkKfAJlYZgo0cu3l9iMC77V7DW6E1DUIrU68pSEXRmFZFOFB1QFo57TncmOcvcbMDWsL4yA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.1", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz", + "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.1.tgz", + "integrity": "sha512-BymCXzCG3r72VKJxaYVwOXATqXIZ85cuvg0YOUDxMGNrKc1DJRZk8MgV5wyXRyEayIMd4FuXJIUgTBXvDNW5cA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -4778,6 +4908,15 @@ "@types/react-router-dom": "^5.3.0" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", + "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -9121,6 +9260,12 @@ "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -9811,6 +9956,21 @@ "he": "bin/he" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -16470,6 +16630,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/react-select": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.8.1.tgz", + "integrity": "sha512-RT1CJmuc+ejqm5MPgzyZujqDskdvB9a9ZqrdnVLsvAHjJ3Tj0hELnLeVPQlmYdVKCdCpxanepl6z7R5KhXhWzg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.0", + "@emotion/cache": "^11.4.0", + "@emotion/react": "^11.8.1", + "@floating-ui/dom": "^1.0.1", + "@types/react-transition-group": "^4.4.0", + "memoize-one": "^6.0.0", + "prop-types": "^15.6.0", + "react-transition-group": "^4.3.0", + "use-isomorphic-layout-effect": "^1.1.2" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/reactjs-popup": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/reactjs-popup/-/reactjs-popup-2.0.6.tgz", @@ -17944,6 +18141,12 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, "node_modules/sucrase": { "version": "3.35.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", @@ -18955,6 +19158,20 @@ "requires-port": "^1.0.0" } }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 1635b617..076c339a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -33,6 +33,7 @@ "react-router-dom": "^6.26.2", "react-router-hash-link": "^2.4.3", "react-scripts": "^5.0.1", + "react-select": "^5.8.1", "reactjs-popup": "^2.0.6" }, "scripts": { @@ -71,4 +72,4 @@ "eslint-plugin-react-hooks": "^4.6.0", "prettier": "2.8.8" } -} \ No newline at end of file +} diff --git a/frontend/src/components/FormElements/FormElements.tsx b/frontend/src/components/FormElements/FormElements.tsx index b3d522e5..ba12cadf 100644 --- a/frontend/src/components/FormElements/FormElements.tsx +++ b/frontend/src/components/FormElements/FormElements.tsx @@ -1,6 +1,14 @@ import React, { SelectHTMLAttributes } from 'react'; import cn from 'classnames'; import styles from './formelements.module.css'; +import Select, { ActionMeta, Props as SelectProps } from 'react-select'; +import { + Control, + RegisterOptions, + useController, + Path, + FieldValues, +} from 'react-hook-form'; type LabelType = React.DetailedHTMLProps< React.LabelHTMLAttributes, @@ -73,3 +81,61 @@ export const Button = React.forwardRef( ); } ); + +type Option = { + id: string; + name: string; +}; + +type SelectOption = { + value: string; + label: string; +}; + +type SelectComponentProps = SelectProps & { + control: Control; + name: Path; + datalist: Option[]; + className?: string; + rules?: RegisterOptions; +}; + +export const SelectComponent = ({ + control, + name, + datalist, + className, + rules, + ...rest +}: SelectComponentProps) => { + const { + field: { onChange, value, ref, ...inputProps }, + } = useController({ + name, + control, + rules, + }); + + const transformedOptions = datalist.map((data) => ({ + value: data.id, + label: data.name, + })); + + const selectedOption = transformedOptions.find( + (option) => option.value === value + ); + + return ( + + )) ) : ( -

Loading...

+

Loading...

)} {errors.driver?.type === 'required' && ( -

- Please select a driver -

+

Please select a driver

)}
diff --git a/frontend/src/components/RideModal/Pages/RideTimes.tsx b/frontend/src/components/RideModal/Pages/RideTimes.tsx index 86d4f6e5..aa6bbb35 100644 --- a/frontend/src/components/RideModal/Pages/RideTimes.tsx +++ b/frontend/src/components/RideModal/Pages/RideTimes.tsx @@ -166,7 +166,10 @@ const RideTimesPage: React.FC = ({ watch, } = methods; const watchRepeats = watch('repeats'); - + const repeatOptions = Object.values(RepeatValues).map((value) => ({ + id: value, + name: value, + })); useEffect(() => { setIsRepeating(watchRepeats !== RepeatValues.DoesNotRepeat); }, [watchRepeats]); @@ -205,13 +208,13 @@ const RideTimesPage: React.FC = ({ diff --git a/frontend/src/components/RideModal/Pages/RiderInfo.tsx b/frontend/src/components/RideModal/Pages/RiderInfo.tsx index 55afb52f..03b80aee 100644 --- a/frontend/src/components/RideModal/Pages/RiderInfo.tsx +++ b/frontend/src/components/RideModal/Pages/RiderInfo.tsx @@ -3,7 +3,12 @@ import { useForm } from 'react-hook-form'; import cn from 'classnames'; import { ObjectType, Location, Rider } from '../../../types'; import { ModalPageProps } from '../../Modal/types'; -import { Button, Input, Label } from '../../FormElements/FormElements'; +import { + Button, + Input, + Label, + SelectComponent, +} from '../../FormElements/FormElements'; import styles from '../ridemodal.module.css'; import { useRiders } from '../../../context/RidersContext'; import { useLocations } from '../../../context/LocationsContext'; @@ -16,6 +21,7 @@ interface FormData { const RiderInfoPage = ({ formData, onBack, onSubmit }: ModalPageProps) => { const { + control, register, handleSubmit, formState: { errors }, @@ -31,12 +37,20 @@ const RiderInfoPage = ({ formData, onBack, onSubmit }: ModalPageProps) => { const [locationToId, setLocationToId] = useState({}); const { locations } = useLocations(); const { riders } = useRiders(); - - const beforeSubmit = ({ name, pickupLoc, dropoffLoc }: FormData) => { - const rider = nameToId[name.toLowerCase()]; + const beforeSubmit = ({ name, pickupLoc, dropoffLoc }: ObjectType) => { const startLocation = locationToId[pickupLoc] ?? pickupLoc; const endLocation = locationToId[dropoffLoc] ?? dropoffLoc; - onSubmit({ rider, startLocation, endLocation }); + /**Payload needed because the form is registered to expect rider instead of name + * If name passed straightaway it results in the database receiving an empty field for rider + * + */ + const payload = { + rider: name, + startLocation, + endLocation, + }; + console.log(payload); + onSubmit(payload); }; useEffect(() => { @@ -59,7 +73,7 @@ const RiderInfoPage = ({ formData, onBack, onSubmit }: ModalPageProps) => {
- { validate: (name: string) => nameToId[name.toLowerCase()] !== undefined, })} + /> */} + + name="name" + datalist={Object.entries(nameToId).map(([name, id]) => ({ + id, + name, + }))} + isSearchable={true} + control={control} + rules={{ required: 'Rider name is required' }} /> + {errors.name &&

Rider not found

} {riders.map((r) => ( @@ -84,13 +109,14 @@ const RiderInfoPage = ({ formData, onBack, onSubmit }: ModalPageProps) => { - + {errors.pickupLoc && (

Please enter a location

)} @@ -104,18 +130,12 @@ const RiderInfoPage = ({ formData, onBack, onSubmit }: ModalPageProps) => { - { - const pickupLoc = getValues('pickupLoc'); - return pickupLoc !== dropoffLoc; - }, - })} + {errors.dropoffLoc?.type === 'required' && (

Please enter a location

diff --git a/frontend/src/components/RideModal/RideModal.tsx b/frontend/src/components/RideModal/RideModal.tsx index 34c65ad1..22e97a7d 100644 --- a/frontend/src/components/RideModal/RideModal.tsx +++ b/frontend/src/components/RideModal/RideModal.tsx @@ -16,8 +16,54 @@ type RideModalProps = { editSingle?: boolean; }; +const getRideData = (ride: Ride | undefined) => { + if (ride) { + let rideData: ObjectType = { + date: format_date(ride.startTime), + pickupTime: moment(ride.startTime).format('kk:mm'), + dropoffTime: moment(ride.endTime).format('kk:mm'), + rider: `${ride.rider.firstName} ${ride.rider.lastName}`, + pickupLoc: ride.startLocation.id + ? ride.startLocation.name + : ride.startLocation.address, + dropoffLoc: ride.endLocation.id + ? ride.endLocation.name + : ride.endLocation.address, + }; + if (ride.recurring) { + let repeats; + let days; + const startDay = moment(ride.startTime).weekday(); + + if (ride.recurringDays!.length === 5) { + repeats = RepeatValues.Daily; + } else if ( + ride.recurringDays!.length === 1 && + ride.recurringDays![0] === startDay + ) { + repeats = RepeatValues.Weekly; + } else { + repeats = RepeatValues.Custom; + const numToDay = ['', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri']; + days = ride.recurringDays!.reduce((prev, curr) => { + return { ...prev, [numToDay[curr]]: '1' }; + }, {} as ObjectType); + } + + rideData = { + ...rideData, + repeats, + days, + endDate: format_date(ride.endDate), + }; + } + return rideData; + } + return {}; +}; + const RideModal = ({ open, close, ride, editSingle }: RideModalProps) => { - const originalRideData = getRideData(); + const originalRideData = getRideData(ride); const [formData, setFormData] = useState(originalRideData); const [isOpen, setIsOpen] = useState(open !== undefined ? open : false); const [currentPage, setCurrentPage] = useState(0); @@ -25,54 +71,6 @@ const RideModal = ({ open, close, ride, editSingle }: RideModalProps) => { const { showToast } = useToast(); const { refreshRides } = useRides(); - // using function instead of const so the function can be hoisted and - // not get in the way of the state and hooks - function getRideData() { - if (ride) { - let rideData: ObjectType = { - date: format_date(ride.startTime), - pickupTime: moment(ride.startTime).format('kk:mm'), - dropoffTime: moment(ride.endTime).format('kk:mm'), - rider: `${ride.rider.firstName} ${ride.rider.lastName}`, - pickupLoc: ride.startLocation.id - ? ride.startLocation.name - : ride.startLocation.address, - dropoffLoc: ride.endLocation.id - ? ride.endLocation.name - : ride.endLocation.address, - }; - if (ride.recurring) { - let repeats; - let days; - const startDay = moment(ride.startTime).weekday(); - - if (ride.recurringDays!.length === 5) { - repeats = RepeatValues.Daily; - } else if ( - ride.recurringDays!.length === 1 && - ride.recurringDays![0] === startDay - ) { - repeats = RepeatValues.Weekly; - } else { - repeats = RepeatValues.Custom; - const numToDay = ['', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri']; - days = ride.recurringDays!.reduce((prev, curr) => { - return { ...prev, [numToDay[curr]]: '1' }; - }, {} as ObjectType); - } - - rideData = { - ...rideData, - repeats, - days, - endDate: format_date(ride.endDate), - }; - } - return rideData; - } - return {}; - } - const goNextPage = () => setCurrentPage((p) => p + 1); const goPrevPage = () => setCurrentPage((p) => p - 1); @@ -100,19 +98,6 @@ const RideModal = ({ open, close, ride, editSingle }: RideModalProps) => { const submitData = () => setIsSubmitted(true); - /** - * Converts a ride that repeats into a number array representation used by - * the internal representation of a ride - * - * @param date a string representation of the ride start date - * @param repeats an enum representing how often this ride repeats: Daily, - * Weekly, or Custom - * @param days Used if the ride repeats on custom days. An object that - * maps days (Mon, Tue, Wed, Thur, Fri) to strings, where the string value is - * non-empty if the ride repeats on that day - * @returns a number array containing the days of the week where the ride repeats, - * with Monday represented as 1, Tuesday represented as 2, etc. - */ const getRecurringDays = ( date: string, repeats: RepeatValues, @@ -176,8 +161,6 @@ const RideModal = ({ open, close, ride, editSingle }: RideModalProps) => { }; } - console.log(rideData); - if (ride) { // scheduled ride if (ride.type === 'active') { @@ -205,10 +188,16 @@ const RideModal = ({ open, close, ride, editSingle }: RideModalProps) => { closeModal(); showToast(ride ? 'Ride edited.' : 'Ride added.', ToastStatus.SUCCESS); } - }, [closeModal, formData, isSubmitted, ride]); + }, [ + closeModal, + formData, + isSubmitted, + ride, + editSingle, + refreshRides, + showToast, + ]); - // have to do a ternary operator on the entire modal - // because otherwise the pages would show up wrongly return ride ? ( <> { ) : ( <> - {/* only have a button if this modal is not controlled by a table */} {!open && } button { - margin: 0 0.75rem; +.label { + display: block; + margin-bottom: 0.5rem; + font-weight: 600; + color: #333; } -.rideTime { +/* Input styles */ +input[type='date'], +input[type='time'], +select { + width: 100%; + padding: 0.75rem; + font-size: 1rem; + border: 1px solid #ced4da; + border-radius: 4px; + background-color: #fff; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} + +input[type='date']:focus, +input[type='time']:focus, +select:focus { + outline: none; + border-color: #80bdff; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} + +/* Styled select */ +select { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23333' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14L2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 0.75rem center; + background-size: 12px; + padding-right: 2.5rem; +} + +/* RideTimes and DriverPage shared styles */ +.rideTime, +.drivers { display: grid; grid-template-columns: repeat(2, 1fr); - grid-gap: 1rem; -} - -.col1 { - grid-column: 1; + grid-gap: 1.25rem; } +.col1, .col2 { - grid-column: 2; + display: flex; + flex-direction: column; } .colSpan { - grid-column: 1/-1; -} - -.select { - border: none; - border-radius: 0.25rem; - font-size: 1rem; - padding: 0.25rem; - background-color: #eae9e9; + grid-column: 1 / -1; } +/* Day selector styles */ .day { - border-radius: 40%; - height: 1.25rem; - width: 1.25rem; - border: none; + border-radius: 50%; + height: 2.25rem; + width: 2.25rem; + border: 2px solid #6c757d; padding: 0; cursor: pointer; - margin: 0 0.125rem; - background-color: rgba(0, 0, 0, 0.1); + margin: 0 0.25rem; + background-color: transparent; + color: #6c757d; + font-weight: 600; + transition: all 0.2s ease-in-out; } .day:focus { - box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.5); + outline: none; + box-shadow: 0 0 0 3px rgba(108, 117, 125, 0.5); } .daySelected { - background-color: black; + background-color: #000; color: white; + border-color: #000; } +/* DriverPage specific styles */ .drivers { display: flex; - flex-wrap: wrap; - max-width: 30rem; - justify-content: center; + flex-direction: column; + max-height: 300px; + overflow-y: auto; + border: 1px solid #ced4da; + border-radius: 4px; + background-color: #f8f9fa; } .driver { display: flex; - flex-direction: column; align-items: center; - margin: 0 0.625rem; + padding: 1rem; + border-bottom: 1px solid #e9ecef; + transition: background-color 0.2s ease; +} + +.driver:last-child { + border-bottom: none; +} + +.driver:hover { + background-color: #e9ecef; } .driverLabel { - margin: 0; + flex-grow: 1; + margin-left: 1rem; + font-size: 1rem; + color: #333; + cursor: pointer; } .driverRadio { - margin: 0; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + width: 20px; + height: 20px; + border: 2px solid #ced4da; + border-radius: 50%; + outline: none; + transition: all 0.2s ease; +} + +.driverRadio:checked { + border-color: #000; + background-color: #000; + box-shadow: inset 0 0 0 4px #fff; } .driverRadio:focus { - outline: 3px solid black; + box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.25), inset 0 0 0 4px #fff; } -.rider { - display: grid; - grid-template-columns: repeat(2, 1fr); - grid-template-rows: repeat(2, 1fr); - column-gap: 1rem; - row-gap: 1rem; +/* Button styles */ +.btnContainer { + display: flex; + justify-content: space-between; + margin-top: auto; } -.name { - grid-column: 1/3; - grid-row: 1; +.btnContainer button { + padding: 0.75rem 1.5rem; + font-size: 1rem; + border-radius: 4px; + cursor: pointer; + transition: all 0.2s ease; + background-color: #000; + color: #fff; + border: none; } -.nameInput { - font-size: 1.5rem; - width: 100%; +.btnContainer button:hover { + background-color: #333; } -.label { - display: block; +.btnContainer button[type='button'] { + background-color: #fff; + color: #000; + border: 1px solid #000; +} + +.btnContainer button[type='button']:hover { + background-color: #000; + color: #fff; +} + +/* Loading state */ +.loading { + text-align: center; + color: #6c757d; + font-style: italic; +} + +/* Responsive adjustments */ +@media (max-width: 500px) { + .rideTime, + .drivers { + grid-template-columns: 1fr; + } + + .btnContainer { + flex-direction: column; + gap: 1rem; + } + + .btnContainer button { + width: 100%; + } } diff --git a/package-lock.json b/package-lock.json index c63f577d..7d8a8bea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1066,9 +1066,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", + "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", "dev": true, "funding": [ { @@ -1086,8 +1086,8 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", + "caniuse-lite": "^1.0.30001663", + "electron-to-chromium": "^1.5.28", "node-releases": "^2.0.18", "update-browserslist-db": "^1.1.0" }, @@ -1128,9 +1128,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001663", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001663.tgz", - "integrity": "sha512-o9C3X27GLKbLeTYZ6HBOLU1tsAcBZsLis28wrVzddShCS16RujjHp9GDHKZqrB3meE0YjhawvMFsGb/igqiPzA==", + "version": "1.0.30001664", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz", + "integrity": "sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==", "dev": true, "funding": [ { @@ -1345,9 +1345,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.27", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.27.tgz", - "integrity": "sha512-o37j1vZqCoEgBuWWXLHQgTN/KDKe7zwpiY5CPeq2RvUqOyJw9xnrULzZAEVQ5p4h+zjMk7hgtOoPdnLxr7m/jw==", + "version": "1.5.30", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.30.tgz", + "integrity": "sha512-sXI35EBN4lYxzc/pIGorlymYNzDBOqkSlVRe6MkgBsW/hW1tpC/HDJ2fjG7XnjakzfLEuvdmux0Mjs6jHq4UOA==", "dev": true, "license": "ISC" }, @@ -1627,9 +1627,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.11.1.tgz", - "integrity": "sha512-EwcbfLOhwVMAfatfqLecR2yv3dE5+kQ8kx+Rrt0DvDXEVwW86KQ/xbMDQhtp5l42VXukD5SOF8mQQHbaNtO0CQ==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", "dev": true, "license": "MIT", "dependencies": { @@ -1726,9 +1726,9 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.36.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.36.1.tgz", - "integrity": "sha512-/qwbqNXZoq+VP30s1d4Nc1C5GTxjJQjk4Jzs4Wq2qzxFM7dSmuG2UkIjg2USMLh3A/aVcUNrK7v0J5U1XEGGwA==", + "version": "7.37.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.0.tgz", + "integrity": "sha512-IHBePmfWH5lKhJnJ7WB1V+v/GolbB0rjS8XYVCSQCZKaQCAUhMoVoOEn1Ef8Z8Wf0a7l8KTJvuZg5/e4qrZ6nA==", "dev": true, "license": "MIT", "dependencies": { @@ -4029,9 +4029,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "dev": true, "funding": [ { @@ -4049,8 +4049,8 @@ ], "license": "MIT", "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js"