From 844c5687902514b4cb981e57aed5a1f576a5d06c Mon Sep 17 00:00:00 2001 From: Desmond Atikpui Date: Fri, 15 Nov 2024 02:54:40 -0500 Subject: [PATCH] Integration of React-Select into Add Student, proper styling and custom needs --- .../src/components/Modal/RiderModalInfo.tsx | 184 ++++++++++-- .../src/components/Modal/modal.module.css | 162 ++++++++++- .../components/Modal/ridermodal.module.css | 271 ++++++++++++------ frontend/src/types/index.ts | 1 - server/src/models/rider.ts | 1 - 5 files changed, 501 insertions(+), 118 deletions(-) diff --git a/frontend/src/components/Modal/RiderModalInfo.tsx b/frontend/src/components/Modal/RiderModalInfo.tsx index a8e3398cc..137133789 100644 --- a/frontend/src/components/Modal/RiderModalInfo.tsx +++ b/frontend/src/components/Modal/RiderModalInfo.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; -import { useForm, SubmitHandler } from 'react-hook-form'; +import { useForm, SubmitHandler, Controller } from 'react-hook-form'; +import Select, { StylesConfig } from 'react-select'; import cn from 'classnames'; import { Button, Input, Label } from '../FormElements/FormElements'; import styles from './ridermodal.module.css'; @@ -12,11 +13,16 @@ type ModalFormProps = { rider?: Rider; }; +type NeedOption = { + value: Accessibility | string; + label: string; +}; + type FormData = { name: string; netid: string; phoneNumber: string; - needs: Accessibility[]; + needs: NeedOption[]; address: string; joinDate: string; endDate: string; @@ -29,23 +35,99 @@ const RiderModalInfo: React.FC = ({ setFormData, rider, }) => { + const [showCustomInput, setShowCustomInput] = useState(false); + const [customNeed, setCustomNeed] = useState(''); + const { register, + control, formState: { errors }, handleSubmit, getValues, + setValue, } = useForm({ defaultValues: { name: (rider?.firstName ?? '') + (rider?.lastName ?? ''), netid: rider?.email.split('@')[0] ?? '', phoneNumber: rider?.phoneNumber ?? '', - needs: [], + needs: + rider?.accessibility?.map((need) => ({ + value: need as Accessibility, + label: need, + })) ?? [], address: rider?.address ?? '', joinDate: rider?.joinDate ?? '', endDate: rider?.endDate ?? '', }, }); + const customStyles: StylesConfig = { + option: (baseStyles, { data }) => ({ + ...baseStyles, + ...(data.value === 'OTHER' && { + color: '#0066cc', + fontStyle: 'italic', + backgroundColor: '#f8f9fa', + borderTop: '1px solid #e9ecef', + marginTop: '4px', + paddingTop: '8px', + cursor: 'pointer', + display: 'flex', + alignItems: 'center', + '&:before': { + content: '"+"', + marginRight: '8px', + fontSize: '14px', + fontWeight: 'bold', + }, + '&:hover': { + backgroundColor: '#e9ecef', + color: '#004c99', + }, + }), + }), + menu: (baseStyles) => ({ + ...baseStyles, + padding: '4px 0', + }), + }; + + const handleNeedsChange = ( + selectedOptions: readonly NeedOption[] | null, + { action }: any + ) => { + if (selectedOptions?.some((option) => option.value === 'OTHER')) { + const filteredOptions = [ + ...selectedOptions.filter((opt) => opt.value !== 'OTHER'), + ]; + setValue('needs', filteredOptions); + setShowCustomInput(true); + setCustomNeed(''); + } else { + setValue('needs', selectedOptions ? [...selectedOptions] : []); + setShowCustomInput(false); + } + }; + + const handleAddCustomNeed = () => { + if (customNeed.trim()) { + const currentNeeds = getValues('needs') || []; + const newNeed: NeedOption = { + value: customNeed.toUpperCase().replace(/\s+/g, '_'), + label: customNeed.trim(), + }; + + setValue('needs', [...currentNeeds, newNeed]); + setCustomNeed(''); + setShowCustomInput(false); + } + }; + + const handleCancelCustomNeed = () => { + setCustomNeed(''); + setShowCustomInput(false); + }; + const beforeSubmit: SubmitHandler = ({ name, netid, @@ -56,7 +138,7 @@ const RiderModalInfo: React.FC = ({ endDate, }) => { const email = netid ? `${netid}@cornell.edu` : undefined; - const accessibility = needs; + const accessibility = needs.map((option) => option.value.toString()); const nameParts = name.trim().split(/\s+/); const firstName = nameParts.length > 1 ? nameParts.slice(0, -1).join(' ') : nameParts[0]; @@ -74,7 +156,6 @@ const RiderModalInfo: React.FC = ({ }; console.log('Form payload:', payload); - onSubmit(payload); }; @@ -86,7 +167,14 @@ const RiderModalInfo: React.FC = ({ const localUserType = localStorage.getItem('userType'); const isEditing = rider !== undefined; const isStudentEditing = isEditing && localUserType === 'Rider'; - const [needsOption, setNeedsOption] = useState(''); + + const needsOptions: NeedOption[] = [ + ...Object.values(Accessibility).map((value) => ({ + value: value as Accessibility, + label: value, + })), + { value: 'OTHER', label: 'Add Custom Need' }, + ]; return (
@@ -106,6 +194,7 @@ const RiderModalInfo: React.FC = ({ /> {errors.name &&

Name cannot be empty

} +
+
- {/* Replacing SelectComponent with native - {Object.entries(Accessibility).map(([key, value]) => ( - - ))} - - {errors.needs && ( -

Please select at least one need

- )} +
+ ( + + {...field} + value={value} + isMulti + options={needsOptions} + className={styles.customSelect} + classNamePrefix="customSelectValueContainer" + placeholder="Select needs..." + styles={customStyles} + onChange={(newValue, actionMeta) => + handleNeedsChange(newValue, actionMeta) + } + /> + )} + /> + {showCustomInput && ( +
+ setCustomNeed(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + handleAddCustomNeed(); + } + }} + placeholder="Type custom need" + className={styles.customNeedField} + autoFocus + /> +
+ + +
+
+ )} + {errors.needs && ( +

Please select at least one need

+ )} +
@@ -178,11 +311,13 @@ const RiderModalInfo: React.FC = ({ })} type="text" aria-required="true" + style={{ height: '60px' }} /> {errors.address && (

Please enter an address

)}
+

Duration

@@ -202,7 +337,9 @@ const RiderModalInfo: React.FC = ({

Please enter a join date

)}
-

to

+
+

+
+