diff --git a/package.json b/package.json index 5a1a654078335f223c61882baf2da29ce78bc2fc..f1347bb724562704c063aa48e08ad5f5f40ae19c 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@types/react-router-dom": "^5.1.7", "axios": "^0.21.1", "eslint-config-airbnb-typescript": "^12.3.1", + "humps": "^2.0.1", "react": "^17.0.1", "react-dom": "^17.0.1", "react-hook-form": "^6.15.5", @@ -54,6 +55,7 @@ } }, "devDependencies": { + "@types/humps": "^2.0.0", "@typescript-eslint/eslint-plugin": "^4.18.0", "@typescript-eslint/parser": "^4.18.0", "eslint": "^7.2.0", diff --git a/src/api/ResponseProps.ts b/src/api/ResponseProps.ts new file mode 100644 index 0000000000000000000000000000000000000000..c8bcd082ed4da57cade72ab50fc16d6df614bf6f --- /dev/null +++ b/src/api/ResponseProps.ts @@ -0,0 +1,10 @@ +export type ResponseProps = { + id: number; + user: { + email: string; + firstName: string; + lastName: string; + username: string; + }; + memberCardNumber: number; +}; diff --git a/src/api/getSeniorList.ts b/src/api/getSeniorList.ts index d300c33e90ef584b234b5521d052a7a5797260f6..dd6f89fe72543a98720635451bc4e358150ab432 100644 --- a/src/api/getSeniorList.ts +++ b/src/api/getSeniorList.ts @@ -1,4 +1,8 @@ import axios from 'axios'; +import { camelizeKeys } from 'humps'; +import { ResponseProps } from './ResponseProps'; -export const getSeniorList = async (name: string): Promise<string[]> => - axios.get(`/api/web/seniors/by_name/${name}`).then((res) => res.data); +export const getSeniorList = async (name: string): Promise<ResponseProps[]> => + axios + .get(`/api/web/seniors/by_name/${name}?fields=user,id,member_card_number`) + .then((res) => camelizeKeys(res.data) as ResponseProps[]); diff --git a/src/api/getSeniorListByCard.ts b/src/api/getSeniorListByCard.ts new file mode 100644 index 0000000000000000000000000000000000000000..b8307e7424c075cce962fec0bc646835d34a142c --- /dev/null +++ b/src/api/getSeniorListByCard.ts @@ -0,0 +1,13 @@ +import axios from 'axios'; +import { camelizeKeys } from 'humps'; +import { ResponseProps } from './ResponseProps'; + +export const getSeniorListByCard = async ( + card: number, +): Promise<ResponseProps[]> => + axios + .get( + `/api/web/seniors/by_member_card/${card}?fields=user,id,member_card_number`, + ) + .then((res) => camelizeKeys(res.data) as ResponseProps[]) + .catch(() => [] as ResponseProps[]); diff --git a/src/components/Dashboard/ReservationPage/Reservation/ReservationProps.ts b/src/components/Dashboard/ReservationPage/Reservation/ReservationProps.ts index 512c6c9e38e13a4cefe0c6a4f64f0e6a149e6be6..1819723c15e41da04615003a786aea3ef0f40644 100644 --- a/src/components/Dashboard/ReservationPage/Reservation/ReservationProps.ts +++ b/src/components/Dashboard/ReservationPage/Reservation/ReservationProps.ts @@ -3,4 +3,5 @@ export type ReservationProps = { destination: string; time: string; date: string; + senior?: number; }; diff --git a/src/components/Dashboard/ReservationPage/Reservation/SeniorSearch.tsx b/src/components/Dashboard/ReservationPage/Reservation/SeniorSearch.tsx index fea0da2630d2e768f2c99a80ab895fcbaacee240..4863134118c107523d44e5bd1185d9036b36918b 100644 --- a/src/components/Dashboard/ReservationPage/Reservation/SeniorSearch.tsx +++ b/src/components/Dashboard/ReservationPage/Reservation/SeniorSearch.tsx @@ -3,41 +3,71 @@ import React, { FC, useState } from 'react'; import TextField from '@material-ui/core/TextField'; import Autocomplete from '@material-ui/lab/Autocomplete'; import { getSeniorList } from 'api/getSeniorList'; +import { ResponseProps } from 'api/ResponseProps'; +import { Control, Controller } from 'react-hook-form'; +import { ReservationProps } from './ReservationProps'; -export const SeniorSearch: FC = () => { - const [value, setValue] = useState<string | null>(null); - const [inputValue, setInputValue] = useState<string>(''); - const [options, setOptions] = useState<string[]>(['']); +type SeniorSearchProps = { + control: Control<ReservationProps>; +}; + +export const SeniorSearch: FC<SeniorSearchProps> = ({ + control, +}: SeniorSearchProps) => { + const [value, setValue] = useState<ResponseProps | null>(null); + const [seniors, setSeniors] = useState<ResponseProps[]>([]); + const [query, setQuery] = useState<string>(''); + // FIX: Input closing when the data is changed. + // The issue is caused by the stopped re-rendering of react hook form. Consider + // attaching the value manually or to isolate this component. const handleChange = (newValue: string): void => { - setInputValue(newValue); - const MIN_SEARCH_LENGTH = 3; - if (newValue.length >= MIN_SEARCH_LENGTH) { - getSeniorList(newValue).then((list) => console.log(list)); + const MIN_SEARCH_LENGTH = 4; + if (newValue.length >= MIN_SEARCH_LENGTH && value == null) { + getSeniorList(newValue).then((list) => { + if (list.length !== 0) { + setSeniors(list); + } else { + setSeniors([]); + } + }); } }; return ( - <div> - <div>{`value: ${value !== null ? `'${value}'` : 'null'}`}</div> - <div>{`inputValue: '${inputValue}'`}</div> - <br /> - <Autocomplete - value={value} - onChange={(event: any, newValue: string | null) => { - setValue(newValue); - }} - inputValue={inputValue} - onInputChange={(event, newInputValue) => { - handleChange(newInputValue); - }} - id="senior-searcher" - options={options} - style={{ width: 300 }} - renderInput={(params) => ( - <TextField {...params} label="Search a senior" variant="outlined" /> - )} - /> - </div> + <Controller + control={control} + name="senior" + as={({ onChange }) => ( + <Autocomplete + value={value} + id="senior-searcher" + onInputChange={(_, newValue) => handleChange(newValue)} + onChange={(_, val) => { + onChange(val?.id); + setValue(val); + }} + options={seniors} + getOptionSelected={(option, val) => option.id === val.id} + getOptionLabel={(option) => option.user.lastName} + renderOption={(option) => ( + <> + {option.user.firstName} {option.user.lastName}{' '} + {option.user.username} {option.memberCardNumber} + </> + )} + renderInput={(params) => ( + <TextField + {...params} + label="Search a senior" + variant="outlined" + InputProps={{ + ...params.InputProps, + }} + /> + )} + /> + )} + /> ); }; diff --git a/src/components/Dashboard/ReservationPage/ReservationDialog.tsx b/src/components/Dashboard/ReservationPage/ReservationDialog.tsx index ecc04aeeb3d419f9ace1d720f9e4fc219c51ab89..f362876642d3e4fb8126e0e819623cc8c905a509 100644 --- a/src/components/Dashboard/ReservationPage/ReservationDialog.tsx +++ b/src/components/Dashboard/ReservationPage/ReservationDialog.tsx @@ -11,6 +11,7 @@ import { useReservations } from 'hooks/useReservations'; import { ReservationProps } from 'components/Dashboard/ReservationPage/Reservation/ReservationProps'; import { SubmitHandler, useForm } from 'react-hook-form'; import { InputField } from 'components/Auth/InputField/InputField'; +import { SeniorSearch } from './Reservation/SeniorSearch'; type ReservationDialogProps = { handleClose: () => void; @@ -20,7 +21,15 @@ export const ReservationDialog: FC<ReservationDialogProps> = ({ handleClose, isOpen, }: ReservationDialogProps) => { - const { control, handleSubmit } = useForm<ReservationProps>(); + const { control, handleSubmit } = useForm<ReservationProps>({ + mode: 'onSubmit', + defaultValues: { + date: '', + time: '', + destination: '', + departure: '', + }, + }); const reservation = useReservations(); @@ -45,6 +54,7 @@ export const ReservationDialog: FC<ReservationDialogProps> = ({ Write here below the details of your next reservation </DialogContentText> + <SeniorSearch control={control} /> <InputField name="name" label="Name Reservation" diff --git a/yarn.lock b/yarn.lock index 725104bc99c5f46a2bffe4b1cf607cf34e1fc602..1e4c5695a6879cbd3372edc53dadb163d2d17703 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2011,6 +2011,11 @@ resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz#9140779736aa2655635ee756e2467d787cfe8a2a" integrity sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A== +"@types/humps@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@types/humps/-/humps-2.0.0.tgz#9ad07801cd34f9d9ce379fa66f62d3049937d815" + integrity sha512-bP/s9HUT2oTJ0c3XGcHGISwTNs6r8eUPNfU6DuOSU8EHgtHqwvoDEyj76jPhKT/0MszS1PF/hHolxRrHSLYUPQ== + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" @@ -6880,6 +6885,11 @@ human-signals@^1.1.1: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== +humps@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/humps/-/humps-2.0.1.tgz#dd02ea6081bd0568dc5d073184463957ba9ef9aa" + integrity sha1-3QLqYIG9BWjcXQcxhEY5V7qe+ao= + hunspell-spellchecker@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/hunspell-spellchecker/-/hunspell-spellchecker-1.0.2.tgz#a10b0bd2fa00a65ab62a4c6b734ce496d318910e"