Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reading room date picker #608

Merged
merged 12 commits into from
Feb 27, 2024
3 changes: 2 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ REACT_APP_REQUEST_BROKER_BASEURL=http://rac-vch.ad.rockarchive.org:8001/api
REACT_APP_LOCALSTORAGE_KEY=DIMESMyList
REACT_APP_MINIMAP_KEY=DIMESMinimapIntro
REACT_APP_S3_BASEURL=https://iiif.rockarch.org
REACT_APP_CAPTCHA_SITE_KEY=6LdQiSkTAAA
REACT_APP_CAPTCHA_SITE_KEY=6LdQiSkTAAA
REACT_APP_ENABLE_READING_ROOM_SELECT=false
chryslovelace marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 2 additions & 1 deletion .env.deploy
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ REACT_APP_REQUEST_BROKER_BASEURL=${REQUEST_BROKER_BASEURL}
REACT_APP_LOCALSTORAGE_KEY=DIMESMyList
REACT_APP_MINIMAP_KEY=DIMESMinimapIntro
REACT_APP_S3_BASEURL=${AWS_BUCKET_BASEURL}
REACT_APP_CAPTCHA_SITE_KEY=${CAPTCHA_SITE_KEY}
REACT_APP_CAPTCHA_SITE_KEY=${CAPTCHA_SITE_KEY}
REACT_APP_ENABLE_READING_ROOM_SELECT=false
chryslovelace marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ DIMES uses a [macro implementation](https://lingui.dev/guides/message-extraction
to present translated strings.
5. Commit updated code to the GitHub repository.

### Aeon Reading Room Integration

Available dates for reading rooms can be pulled from Aeon via the request broker by setting the `REACT_APP_ENABLE_READING_ROOM_SELECT` environment variable. Not setting this environment variable or leaving it blank will disable this feature.
chryslovelace marked this conversation as resolved.
Show resolved Hide resolved

## License

This code is released under an [MIT License](LICENSE).
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@testing-library/user-event": "^14.2.0",
"axios": "^0.27.2",
"classnames": "^2.2.6",
"date-fns": "^2.30.0",
"downshift": "^6.1.7",
"formik": "^2.1.5",
"mirador": "^3.3.0",
Expand All @@ -20,6 +21,7 @@
"react": "^16.13.1",
"react-accessible-dropdown-menu-hook": "^3.2.0",
"react-aria-live": "^2.0.5",
"react-datepicker": "^4.20.0",
"react-dom": "^16.13.1",
"react-google-recaptcha": "^2.1.0",
"react-helmet": "^6.1.0",
Expand Down
4 changes: 2 additions & 2 deletions src/components/Inputs/__tests__/Inputs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ it('renders checkbox props correctly', () => {

it('renders date props correctly', () => {
act(() => {
render(<I18nApp ReactComponent={<DateInput label='Select a date' id='1' />} />, container)
render(<I18nApp ReactComponent={<DateInput label='Select a date' id='1' handleChange={jest.fn()} />} />, container)
})

const label = document.querySelector('label')
expect(label.textContent).toBe('Select a date')

const input = document.querySelector('.dp__input')
const input = document.querySelector('.dp__wrapper')
expect(input.id).toBe('1')
})

Expand Down
66 changes: 21 additions & 45 deletions src/components/Inputs/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import React from 'react'
import React, {useEffect, useState} from 'react'
import PropTypes from 'prop-types'
import {
DatePicker,
DatePickerInput,
DatePickerMonth,
DatePickerTable,
DatePickerButton,
DatePickerCalendar} from '@reecelucas/react-datepicker'
import DatePicker from 'react-datepicker'
import {useSelect} from 'downshift'
import MaterialIcon from '../MaterialIcon'
import classnames from 'classnames'
Expand Down Expand Up @@ -62,54 +56,36 @@ CheckBoxInput.defaultProps = {
checked: true,
}

export const DateInput = props => (
export const DateInput = ({className, defaultDate, handleChange, helpText, id, label, ...props}) => {
const [startDate, setStartDate] = useState(defaultDate || new Date())

useEffect(() => {
handleChange(startDate)
}, [startDate, setStartDate])

return (
<>
<label htmlFor={id}>{label}</label>
<DatePicker
className='mb-2'
initialDate={new Date()}
minDate={new Date()}
onSelect={date => props.handleChange(date)}>
<label htmlFor={props.id}>{props.label}</label>
<DatePickerInput
className='dp__input'
dateFormat={'MM/dd/yyyy'}
id={props.id}
name={props.name} />
<DatePickerCalendar className='dp__calendar py-20 px-20'>
<div className='dp__top-bar mb-12'>
<DatePickerButton
className='dp__button py-2 px-6'
aria-label={t({
comment: 'Aria label for Calendar button',
message: 'Switch to the previous month.'
})}
updateMonth={({ prev }) => prev()} >
<MaterialIcon icon='west' />
</DatePickerButton>
<DatePickerMonth className='dp__month px-16 py-0' />
<DatePickerButton
className='dp__button py-2 px-6'
aria-label={t({
comment: 'Aria label for Calendar button',
message: 'Switch to the next month.'
})}
updateMonth={({ next }) => next()} >
<MaterialIcon icon='east' />
</DatePickerButton>
</div>
<DatePickerTable className='dp__table' />
</DatePickerCalendar>
className={className || 'dp__wrapper'}
selected={startDate}
showTimeSelect='true'
onChange={date => setStartDate(date)}
dateFormat="yyyy-MM-dd h:mm aa"
id={id}
{...props}>
</DatePicker>
{props.helpText && <p className='input__help-text' aria-describedby={`desc-${props.id}`}>{props.helpText}</p>}
{helpText && <p className='input__help-text' aria-describedby={`desc-${id}`}>{helpText}</p>}
</>
)
)}

DateInput.propTypes = {
className: PropTypes.string,
handleChange: PropTypes.func,
helpText: PropTypes.string,
id: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
defaultDate: PropTypes.instanceOf(Date),
};


Expand Down
1 change: 1 addition & 0 deletions src/components/ModalMyList/__tests__/ModalMyList.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ beforeEach(() => {
return Promise.reject(new Error('not found'))
}
})
axios.get.mockImplementation((url) => Promise.resolve({data:[]}))
})

afterEach(() => {
Expand Down
127 changes: 101 additions & 26 deletions src/components/ModalMyList/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { ModalSavedItemList } from '../ModalSavedItem'
import { getFormattedDate } from '../Helpers'
import './styles.scss'
import { Plural, Trans, select, t } from '@lingui/macro'
import axios from 'axios'
import { addBusinessDays, parse, parseISO, startOfDay, isWithinInterval } from 'date-fns'


const SubmitListInput = ({ submitList }) => {
Expand Down Expand Up @@ -347,7 +349,97 @@ EmailModal.propTypes = {
toggleModal: PropTypes.func.isRequired,
}

export const ReadingRoomRequestModal = props => (
const ReadingRoomSelect = ({ readingRooms }) => {
const { setFieldValue } = useFormikContext();
const [site, setSite] = useState('');

const ReadingRoomLocations = readingRooms.map(readingRoom => ({
value: readingRoom.sites[0],
label: readingRoom.name,
}));
ReadingRoomLocations.unshift({
value: "",
label: t({message: "Please select a reading room"}),
});

useEffect(() => {
setFieldValue('site', site);
}, [site, setFieldValue]);

return (
<div className='form-group'>
<SelectInput
className='select__modal'
id='site'
label={t({message: 'Select Reading Room Location'})}
name='site'
onChange={({ selectedItem }) => setSite(selectedItem.value)}
options={ReadingRoomLocations}
required={true}
selectedItem={site || ''} />
<ErrorMessage
id='site-error'
name='site'
component='div'
className='modal-form__error' />
</div>
)
}

const ReadingRoomDateInput = ({ readingRoom }) => {
const { setFieldValue } = useFormikContext();

return (
<div className='form-group'>
<Field
component={DateInput}
handleChange={date => setFieldValue('scheduledDate', date)}
helpText={t({
comment: 'Helptext for scheduling date.',
message: 'Enter the date of your research visit (mm/dd/yyyy)'
})}
id='scheduledDate'
label={t({
comment: 'Label for scheduling date',
message: 'Scheduled Date *'
})}
type='date'
defaultDate={addBusinessDays(new Date(), readingRoom?.policies[0]?.appointmentMinLeadDays || 1)}
minDate={addBusinessDays(new Date(), readingRoom?.policies[0]?.appointmentMinLeadDays || 1)}
filterDate={!!process.env.REACT_APP_ENABLE_READING_ROOM_SELECT ? date => readingRoom?.openHours.some(x => x.dayOfWeek === date.getDay()) : null}
filterTime={!!process.env.REACT_APP_ENABLE_READING_ROOM_SELECT ? date => {
if (readingRoom === undefined) return false;
const hours = readingRoom.openHours.find(x => x.dayOfWeek === date.getDay());
return isWithinInterval(date, {
start: parse(hours.openTime, "HH:mm:ss", date),
end: parse(hours.closeTime, "HH:mm:ss", date),
});
} : null}
excludeDateIntervals={readingRoom?.closures.map(closure => ({
start: startOfDay(parseISO(closure.startDate)),
end: startOfDay(parseISO(closure.endDate)),
}))} />
<ErrorMessage
id='scheduledDate-error'
name='scheduledDate'
component='div'
className='modal-form__error' />
</div>
)
}

export const ReadingRoomRequestModal = props => {
const [aeonReadingRooms, setAeonReadingRooms] = useState([]);

useEffect(() => {
if (!!process.env.REACT_APP_ENABLE_READING_ROOM_SELECT) {
axios.get(`${process.env.REACT_APP_REQUEST_BROKER_BASEURL}/reading-rooms`).then(response => {
setAeonReadingRooms(response.data);
});
}
}, []); // empty deps array means it runs once

return (
<ModalMyList
appElement={props.appElement}
title='Request in Reading Room'
Expand All @@ -365,6 +457,9 @@ export const ReadingRoomRequestModal = props => (
comment: 'Missing Scheduled Date error',
message: 'Please provide the date of your research visit.'
});
if (!!process.env.REACT_APP_ENABLE_READING_ROOM_SELECT && !values.site) errors.site = t({
message: 'Please select a location of a reading room.'
})
if (!values.recaptcha) errors.recaptcha = t({
message: 'Please complete this field.'
});
Expand All @@ -383,7 +478,7 @@ export const ReadingRoomRequestModal = props => (
setSubmitting(false);
}}
>
{({ errors, isSubmitting, setFieldValue, touched }) => (
{({ errors, isSubmitting, setFieldValue, touched, values }) => (
<Form>
<SubmitListInput submitList={props.submitList} />
<ErrorMessage
Expand All @@ -393,29 +488,9 @@ export const ReadingRoomRequestModal = props => (
})}
component='div'
className='input__error' />
<div className='form-group mx-0'>
<Field
component={DateInput}
handleChange={date => setFieldValue('scheduledDate', date)}
helpText={t({
comment: 'Helptext for scheduling date.',
message: 'Enter the date of your research visit (mm/dd/yyyy)'
})}
id='scheduledDate'
label={t({
comment: 'Label for scheduling date',
message: 'Scheduled Date *'
})}
type='date' />
<ErrorMessage
id='scheduledDate-error'
name={t({
comment: 'Name for scheduling date error',
message: 'scheduledDate'
})}
component='div'
className='input__error' />
</div>
{!!process.env.REACT_APP_ENABLE_READING_ROOM_SELECT && <ReadingRoomSelect readingRooms={aeonReadingRooms} />}
<ReadingRoomDateInput
readingRoom={aeonReadingRooms.find(room => room.sites[0] === values.site)} />
<FormGroup
label={t({
comment: 'Label for RAC staff message Form',
Expand Down Expand Up @@ -465,7 +540,7 @@ export const ReadingRoomRequestModal = props => (
</Formik>
}
/>
)
)}

ReadingRoomRequestModal.propTypes = {
appElement: PropTypes.object,
Expand Down
2 changes: 2 additions & 0 deletions src/components/PageMyList/__tests__/PageMyList.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ it('renders props correctly', async () => {
axios.get.mockImplementation((url) => {
if (url.includes('status')) {
return Promise.resolve({ data: { pong: true } })
} else if (url.includes('reading-rooms')) {
return Promise.resolve({data:[]})
} else {
return Promise.reject(new Error('not found'))
}
Expand Down
Loading