Skip to content

Commit

Permalink
Choose a date & Time - design update (#31897)
Browse files Browse the repository at this point in the history
* started a temp data file, and working through some changes.

* source temp referral data

* added selector and move date selection to redux

* added ability to override the default max days and a little house keeping

* utilize the new overrideMaxDays prop

* added path to choose page

* wrapped in fragment to avoid propType error from FormLayout

* added pseudo routing

* tz string update

* switched to generic values.
  • Loading branch information
brianseek authored Sep 18, 2024
1 parent 95ce1ee commit da33270
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 85 deletions.
24 changes: 14 additions & 10 deletions src/applications/vaos/components/calendar/CalendarWidget.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,15 @@ function getFirstDayOfMonth(momentDate) {
* @param {string} maxDate YYYY-DD-MM
* @returns {string} YYYY-MM
*/
export function getMaxMonth(maxDate) {
export function getMaxMonth(maxDate, overrideMaxDays) {
const defaultMaxMonth = moment()
.add(DEFAULT_MAX_DAYS_AHEAD, 'days')
.format('YYYY-MM');
const maxMonth = moment(maxDate).startOf('month');

if (maxDate && maxMonth.isAfter(defaultMaxMonth)) {
if (maxDate && (maxMonth.isAfter(defaultMaxMonth) || overrideMaxDays)) {
return maxMonth.format('YYYY-MM');
}

// If no available dates array provided, set max to default from now
return defaultMaxMonth;
}
Expand Down Expand Up @@ -235,6 +234,7 @@ function handleNext(onClickNext, months, setMonths) {
* @param {string} props.timezone America/Denver
* @param {Array<string>} props.value
* @param {boolean} [props.showWeekends=false] Whether to show full weekend slots or not
* @param {boolean} [props.overrideMaxDays=false] Disables the default max days value
* @returns {JSX.Element} props.Calendar Calendar Widget
*/
function CalendarWidget({
Expand All @@ -249,6 +249,7 @@ function CalendarWidget({
onChange,
onNextMonth,
onPreviousMonth,
overrideMaxDays = false,
renderOptions,
renderIndicator,
renderSelectedLabel,
Expand All @@ -268,7 +269,7 @@ function CalendarWidget({
return null;
});
const currentDate = moment();
const maxMonth = getMaxMonth(maxDate);
const maxMonth = getMaxMonth(maxDate, overrideMaxDays);
const [months, setMonths] = useState([moment(startMonth || minDate)]);
const exceededMaximumSelections = value.length > maxSelections;
const hasError = (required && showValidation) || exceededMaximumSelections;
Expand Down Expand Up @@ -390,6 +391,7 @@ function CalendarWidget({
}

CalendarWidget.propTypes = {
id: PropTypes.string.isRequired,
availableSlots: PropTypes.arrayOf(
PropTypes.shape({
start: PropTypes.string.isRequired,
Expand All @@ -398,22 +400,24 @@ CalendarWidget.propTypes = {
),
disabled: PropTypes.bool,
disabledMessage: PropTypes.object,
minDate: PropTypes.string,
maxDate: PropTypes.string,
maxSelections: PropTypes.number,
maxSelectionsError: PropTypes.string,
startMonth: PropTypes.string,
onChange: PropTypes.func,
onNextMonth: PropTypes.func,
onPreviousMonth: PropTypes.func,
minDate: PropTypes.string,
overrideMaxDays: PropTypes.bool,
renderIndicator: PropTypes.func,
renderOptions: PropTypes.func,
renderSelectedLabel: PropTypes.func,
required: PropTypes.bool,
requiredMessage: PropTypes.string,
showValidation: PropTypes.bool,
id: PropTypes.string.isRequired,
showWeekends: PropTypes.bool,
startMonth: PropTypes.string,
timezone: PropTypes.string,
value: PropTypes.array,
onChange: PropTypes.func,
onNextMonth: PropTypes.func,
onPreviousMonth: PropTypes.func,
};

export default CalendarWidget;
6 changes: 6 additions & 0 deletions src/applications/vaos/new-appointment/redux/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -400,3 +400,9 @@ export function selectFacilitiesRadioWidget(state) {
export function selectAppointmentSlotsStatus(state) {
return getNewAppointment(state).appointmentSlotsStatus;
}

export function getSelectedDate(state) {
return getFormData(state).selectedDates?.length
? getFormData(state).selectedDates[0]
: '';
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export default function ChooseCommunityCare() {
aria-label={provider.reviewText}
text={provider.reviewText}
data-testid="review-available-appointments-link"
href="provider-choose-date-and-time"
/>
</div>
<hr />
Expand Down
227 changes: 152 additions & 75 deletions src/applications/vaos/referral-appointments/ChooseDateAndTime.jsx
Original file line number Diff line number Diff line change
@@ -1,86 +1,163 @@
import React, { useState } from 'react';
import moment from 'moment';
import React, { useCallback, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { startOfMonth, format } from 'date-fns';
import { useHistory } from 'react-router-dom';
import CalendarWidget from '../components/calendar/CalendarWidget';
import FormLayout from '../new-appointment/components/FormLayout';
// import { onCalendarChange } from "../new-appointment/redux/actions";
// import { useDispatch } from 'react-redux';
import { onCalendarChange } from '../new-appointment/redux/actions';
import FormButtons from '../components/FormButtons';
import { referral } from './temp-data/referral';
import { getSelectedDate } from '../new-appointment/redux/selectors';

export const ChooseDateAndTime = () => {
// const dispatch = useDispatch();
const availableSlots = [
{
end: '2024-07-02T17:00:00Z',
id: '32303',
start: '2024-07-01T10:00:00',
},
{
end: '2024-07-02T18:00:00Z',
id: '23555',
start: '2024-07-02T11:00:00',
},
];
const selectedDates = ['2024-07-02T11:00:00'];
const timezone = 'America/Denver';
const preferredDate = '2024-07-02';
const startMonth = preferredDate
? moment(preferredDate).format('YYYY-MM')
: null;
const history = useHistory();
const selectedDates = useSelector(state => getSelectedDate(state));
const dispatch = useDispatch();
const startMonth = format(startOfMonth(referral.preferredDate), 'yyyy-MM');
const [submitted, setSubmitted] = useState(false);
const pageTitle = 'Choose a date and time';
const latestAvailableSlot = new Date(
Math.max.apply(
null,
referral.slots.map(slot => {
return new Date(slot.start);
}),
),
);
const fullAddress = addressObject => {
let addressString = addressObject.street1;
if (addressObject.street2) {
addressString = `${addressString}, ${addressObject.street2}`;
}
if (addressObject.street3) {
addressString = `${addressString}, ${addressObject.street3}`;
}
addressString = `${addressString}, ${addressObject.city}, ${
addressObject.state
}, ${addressObject.zip}`;
return addressString;
};
const onChange = useCallback(
value => {
dispatch(onCalendarChange(value));
},
[dispatch],
);
const onSubmit = () => {
setSubmitted(true);
if (selectedDates) {
history.push('/confirm-approved');
}
};
const getTzName = name => {
return new Intl.DateTimeFormat('default', {
timeZone: referral.timezone,
timeZoneName: name,
})
.formatToParts()
.find(({ type }) => type === 'timeZoneName').value;
};
const tzLong = getTzName('longGeneric');
const tzShort = getTzName('shortGeneric');
return (
<FormLayout>
<div>
<h1>Choose a date and time</h1>
<p>Physical Therapy</p>
<p>GLA Medical Canter - Southwest</p>

<h1>Physical Therapy of GLA</h1>
<p>111 Medical Lane, Suite 300</p>
<p>Los Angeles, CA 12345</p>
<p>Phone: 555-555-5555</p>

<p>7 minute drive (2 miles)</p>
</div>
<div>
<CalendarWidget
maxSelections={1}
availableSlots={availableSlots}
value={selectedDates}
id="dateTime"
timezone={timezone}
additionalOptions={{
required: true,
}}
// disabled={loadingSlots}
disabledMessage={
<va-loading-indicator
data-testid="loadingIndicator"
set-focus
message="Finding appointment availability..."
/>
}
onChange={null}
onNextMonth={null}
onPreviousMonth={null}
minDate={moment()
.add(1, 'days')
.format('YYYY-MM-DD')}
maxDate={moment()
.add(395, 'days')
.format('YYYY-MM-DD')}
required
requiredMessage="Please choose your preferred date and time for your appointment"
startMonth={startMonth}
showValidation={submitted && !selectedDates?.length}
showWeekends
<FormLayout pageTitle={pageTitle}>
<>
<div>
<h1>{pageTitle}</h1>
<p className="vads-u-font-weight--bold vads-u-font-size--lg vads-u-margin--0">
{referral.providerName}
</p>
<p className="vads-u-margin-top--0">{referral.typeOfCare}</p>
<p className="vads-u-margin--0 vads-u-font-weight--bold">
{referral.orgName}
</p>
<address>
<p className="vads-u-margin--0">
{referral.orgAddress.street1} <br />
{referral.orgAddress.street2 && (
<>
{referral.orgAddress.street2}
<br />
</>
)}
{referral.orgAddress.street3 && (
<>
{referral.orgAddress.street3}
<br />
</>
)}
{referral.orgAddress.city}, {referral.orgAddress.state},{' '}
{referral.orgAddress.zip}
</p>
<div
data-testid="directions-link-wrapper"
className="vads-u-display--flex vads-u-color--link-default"
>
<va-icon
className="vads-u-margin-right--0p5 vads-u-color--link-default"
icon="directions"
size={3}
/>
<a
data-testid="directions-link"
href={`https://maps.google.com?addr=Current+Location&daddr=${fullAddress(
referral.orgAddress,
)}`}
aria-label={`directions to ${referral.orgName}`}
target="_blank"
rel="noreferrer"
>
Directions
</a>
</div>
</address>
<p>Phone: {referral.orgPhone}</p>
<p>
{referral.driveTime} ({referral.driveDistance})
</p>
<p>
Please select an available date and time from the calendar below.
Appointment times are displayed in {`${tzLong} (${tzShort})`}.
</p>
</div>
<div>
<CalendarWidget
maxSelections={1}
availableSlots={referral.slots}
value={[selectedDates]}
id="dateTime"
timezone={referral.timezone}
additionalOptions={{
required: true,
}}
// disabled={loadingSlots}
disabledMessage={
<va-loading-indicator
data-testid="loadingIndicator"
set-focus
message="Finding appointment availability..."
/>
}
onChange={onChange}
onNextMonth={null}
onPreviousMonth={null}
minDate={format(new Date(), 'yyyy-MM-dd')}
maxDate={format(latestAvailableSlot, 'yyyy-MM-dd')}
required
requiredMessage="Please choose your preferred date and time for your appointment"
startMonth={startMonth}
showValidation={submitted && !selectedDates?.length}
showWeekends
overrideMaxDays
/>
</div>
<FormButtons
onBack={() => history.push('/choose-community-care-appointment')}
onSubmit={() => onSubmit()}
// pageChangeInProgress={pageChangeInProgress}
loadingText="Page change in progress"
/>
</div>
<FormButtons
onBack={() => {}}
onSubmit={() => setSubmitted(true)}
// pageChangeInProgress={pageChangeInProgress}
loadingText="Page change in progress"
/>
</>
</FormLayout>
);
};
Expand Down
39 changes: 39 additions & 0 deletions src/applications/vaos/referral-appointments/temp-data/referral.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/* eslint-disable no-plusplus */
const dateFns = require('date-fns');

const getAvailableSlots = (number = 2) => {
const slots = [];
const tomorrow = dateFns.addDays(dateFns.startOfDay(new Date()), 1);
let hourFromNow = 12;
for (let i = 0; i < number; i++) {
const startTime = dateFns.addHours(tomorrow, hourFromNow);
slots.push({
end: dateFns.addMinutes(startTime, 30).toISOString(),
id: Math.floor(Math.random() * 90000) + 10000,
start: startTime.toISOString(),
});
hourFromNow++;
}
return slots;
};
const referral = {
providerName: 'Dr. Face',
typeOfCare: 'Dermatology',
orgName: 'New Skin Technologies',
orgAddress: {
street1: '111 Lori Ln.',
street2: '',
street3: '',
city: 'New York',
state: 'New York',
zip: '10016',
},
orgPhone: '555-867-5309',
driveTime: '7 minute drive',
driveDistance: '8 miles',
slots: getAvailableSlots(),
preferredDate: new Date(),
timezone: 'America/Denver',
};

module.exports = { getAvailableSlots, referral };

0 comments on commit da33270

Please sign in to comment.