department-of-veterans-affairs/vets-website

View on GitHub
src/applications/check-in/components/pages/AppointmentDetails/index.jsx

Summary

Maintainability
F
4 days
Test Coverage
import React, { useMemo, useState, useLayoutEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useTranslation, Trans } from 'react-i18next';
import isValid from 'date-fns/isValid';
import PropTypes from 'prop-types';
// eslint-disable-next-line import/no-unresolved
import { recordEvent } from '@department-of-veterans-affairs/platform-monitoring/exports';

import { createAnalyticsSlug } from '../../../utils/analytics';
import { useFormRouting } from '../../../hooks/useFormRouting';
import { setForm } from '../../../actions/universal';
import { makeSelectVeteranData, makeSelectApp } from '../../../selectors';
import { makeSelectFeatureToggles } from '../../../utils/selectors/feature-toggles';

import {
  appointmentIcon,
  clinicName,
  findAppointment,
  findUpcomingAppointment,
  getAppointmentId,
} from '../../../utils/appointment';
import { useStorage } from '../../../hooks/useStorage';
import { ELIGIBILITY } from '../../../utils/appointment/eligibility';
import { APP_NAMES, phoneNumbers } from '../../../utils/appConstants';

import Wrapper from '../../layout/Wrapper';
import BackButton from '../../BackButton';
import ActionLink from '../../ActionLink';
import AppointmentMessage from '../../AppointmentDisplay/AppointmentMessage';
import AddressBlock from '../../AddressBlock';
import ExternalLink from '../../ExternalLink';
import PrepareContent from '../../PrepareContent';

const AppointmentDetails = props => {
  const { router } = props;
  const { t } = useTranslation();
  const dispatch = useDispatch();
  const {
    goToPreviousPage,
    jumpToPage,
    getCurrentPageFromRouter,
  } = useFormRouting(router);
  const page = getCurrentPageFromRouter();
  const selectVeteranData = useMemo(makeSelectVeteranData, []);
  const { appointments, upcomingAppointments } = useSelector(selectVeteranData);
  const selectApp = useMemo(makeSelectApp, []);
  const { app } = useSelector(selectApp);
  const selectFeatureToggles = useMemo(makeSelectFeatureToggles, []);
  const { isMedicationReviewContentEnabled } = useSelector(
    selectFeatureToggles,
  );
  const [appointment, setAppointment] = useState({});
  const [isUpcoming, setIsUpcoming] = useState(false);
  const appointmentDay = new Date(appointment?.startTime);
  const isPhoneAppointment = appointment?.kind === 'phone';
  const isCvtAppointment = appointment?.kind === 'cvt';
  const isVvcAppointment = appointment?.kind === 'vvc';
  const isInPersonAppointment = appointment?.kind === 'clinic';
  const { appointmentId } = router.params;
  const { getPreCheckinComplete } = useStorage(app);

  useLayoutEffect(
    () => {
      if (appointmentId) {
        // TODO: It's not going to find upcoming appoinments in the list of vista appointments until we can figure out which data to use to link the two.
        const activeAppointmentDetails = findAppointment(
          appointmentId,
          appointments,
        );
        if (activeAppointmentDetails) {
          setAppointment(activeAppointmentDetails);
          return;
        }
        const activeUpcomingAppointmentDetails = findUpcomingAppointment(
          appointmentId,
          upcomingAppointments,
        );
        if (activeUpcomingAppointmentDetails) {
          setIsUpcoming(true);
          setAppointment(activeUpcomingAppointmentDetails);
          return;
        }
      }
      // Go back to appointments page if no activeAppointment or not in list.
      jumpToPage('appointments');
    },
    [appointmentId, appointments, upcomingAppointments, jumpToPage],
  );

  const handlePhoneNumberClick = () => {
    recordEvent({
      event: createAnalyticsSlug('details-phone-link-clicked', 'nav', app),
    });
  };

  const action = e => {
    e.preventDefault();
    if (app === APP_NAMES.CHECK_IN) {
      dispatch(
        setForm({
          data: {
            activeAppointmentId: appointmentId,
          },
        }),
      );
      jumpToPage('arrived');
    }
    if (app === APP_NAMES.PRE_CHECK_IN) {
      jumpToPage('contact-information');
    }
  };

  const clinic = appointment && clinicName(appointment);

  let preCheckInSubTitle = (
    <>
      {isMedicationReviewContentEnabled && (
        <h2 className="vads-u-font-size--sm">{t('how-to-check-in')}</h2>
      )}
      <p
        data-testid="in-person-appointment-subtitle"
        className="vads-u-margin--0"
      >
        {isMedicationReviewContentEnabled
          ? t('on-day-of-appointment-we-send-text')
          : t('remember-to-bring-your-insurance-cards-with-you')}
      </p>
    </>
  );
  if (isPhoneAppointment) {
    preCheckInSubTitle = (
      <p data-testid="phone-appointment-subtitle" className="vads-u-margin--0">
        {t('your-provider-will-call-you-at-your-appointment-time')}
      </p>
    );
  }
  if (isCvtAppointment) {
    preCheckInSubTitle = (
      <p data-testid="cvt-appointment-subtitle" className="vads-u-margin--0">
        {t('go-to-facility-for-this-video-appointment', {
          facility: appointment.facility,
        })}
      </p>
    );
  }
  if (isVvcAppointment) {
    preCheckInSubTitle = (
      <p data-testid="vvc-appointment-subtitle" className="vads-u-margin--0">
        {t('you-can-join-your-appointment-by-using-our-appointments-tool')}
      </p>
    );
  }
  const isCanceled = isUpcoming && appointment.status.includes('CANCELLED');
  const appointmentTitle = () => {
    let title = '';
    switch (appointment?.kind) {
      case 'phone':
        title = `${t('phone')} ${t('appointment')}`;
        break;
      case 'cvt':
        title = t('video-appointment-at-facility', {
          facility: appointment.facility,
        });
        break;
      case 'vvc':
        title = t('#-util-capitalize', { value: t('video-appointment') });
        break;
      default:
        title = t('in-person-appointment');
        break;
    }
    if (isCanceled) {
      return `${t('canceled')} ${t('#-util-uncapitalize', { value: title })}`;
    }
    return title;
  };
  let eligibleAppointment = true;
  if (app === APP_NAMES.CHECK_IN) {
    eligibleAppointment = appointment.eligibility === ELIGIBILITY.ELIGIBLE;
  }
  let link = '';
  if (!isUpcoming && eligibleAppointment) {
    link = (
      <div className="vads-u-margin-top--2">
        <ActionLink
          app={app}
          action={action}
          appointmentId={getAppointmentId(appointment)}
        />
      </div>
    );
  }
  return (
    <>
      {Object.keys(appointment).length && (
        <>
          <BackButton
            router={router}
            action={goToPreviousPage}
            prevUrl="#back"
            text={t('back-to-last-screen')}
          />
          <Wrapper
            classNames="appointment-details-page"
            withBackButton
            titleOverride={appointmentTitle()}
          >
            <div className="appointment-details--container vads-u-margin-top--2 vads-u-border--2px vads-u-border-color--gray vads-u-padding-x--2 vads-u-padding-top--4 vads-u-padding-bottom--2">
              <div className="appointment-details--icon">
                {appointmentIcon(appointment)}
              </div>
              <h1
                tabIndex="-1"
                data-testid="header"
                className="vads-u-font-size--h3"
              >
                {appointmentTitle()}
              </h1>
              {!isUpcoming && (
                <>
                  {app === APP_NAMES.PRE_CHECK_IN &&
                    getPreCheckinComplete(window)?.complete &&
                    preCheckInSubTitle}
                  {app === APP_NAMES.CHECK_IN && (
                    <div className="vads-u-margin-x--neg2 vads-u-margin-top--2">
                      <AppointmentMessage
                        appointment={appointment}
                        page={page}
                      />
                    </div>
                  )}
                </>
              )}
              {isCanceled && (
                <va-alert
                  uswds
                  slim
                  status="error"
                  show-icon
                  data-testid="canceled-message"
                  class="vads-u-margin-top--2"
                >
                  <div>
                    <p className="vads-u-margin-top--0">
                      {appointment.status === 'CANCELLED BY PATIENT' && (
                        <span
                          className="vads-u-font-weight--bold"
                          data-testid="canceled-by-patient"
                        >
                          {`${t('you-canceled')} `}
                        </span>
                      )}
                      {appointment.status === 'CANCELLED BY CLINIC' && (
                        <span
                          className="vads-u-font-weight--bold"
                          data-testid="canceled-by-faciity"
                        >
                          {`${t('facility-canceled')} `}
                        </span>
                      )}
                      <Trans
                        i18nKey="if-you-want-to-reschedule"
                        components={[
                          <va-telephone
                            key={phoneNumbers.mainInfo}
                            contact={phoneNumbers.mainInfo}
                          />,
                          <va-telephone
                            key={phoneNumbers.tty}
                            contact={phoneNumbers.tty}
                            tty
                            ariaLabel="7 1 1."
                          />,
                        ]}
                      />
                    </p>
                    <p>
                      <ExternalLink
                        href="https://www.va.gov/health-care/schedule-view-va-appointments/"
                        hrefLang="en"
                      >
                        {t('sign-in-to-schedule')}
                      </ExternalLink>
                    </p>
                    <p>{t('or-talk-staff-if-at-facility')}</p>
                  </div>
                </va-alert>
              )}
              {isUpcoming &&
                !isCanceled && (
                  <va-alert-expandable
                    status="warning"
                    trigger={t('we-cant-show-all-information')}
                    class="vads-u-margin-top--3"
                    data-testid="info-warning"
                  >
                    <p>{t('some-appointment-information-not-available')}</p>
                    {/* Slotted p tags can't have margin for some reason. */}
                    <br />
                    {app === APP_NAMES.PRE_CHECK_IN ? (
                      <p data-testid="pre-check-in-info">
                        {t('find-all-appointment-information-pre-check-in')}
                      </p>
                    ) : (
                      <p data-testid="check-in-info">
                        {t('find-all-appointment-information-check-in')}
                      </p>
                    )}
                  </va-alert-expandable>
                )}
              {app === APP_NAMES.PRE_CHECK_IN &&
                link && (
                  <>
                    <h2 className="vads-u-font-size--sm">
                      {t('review-contact-information')}
                    </h2>
                    {link}
                  </>
                )}
              <div data-testid="appointment-details--when">
                <h2 className="vads-u-font-size--sm">{t('when')}</h2>
                <p
                  className="vads-u-margin--0"
                  data-testid="appointment-details--date-value"
                >
                  {isValid(appointmentDay) &&
                    t('appointment-day', { date: appointmentDay })}
                </p>
              </div>
              {appointment.doctorName && (
                <div data-testid="appointment-details--provider">
                  <h2 className="vads-u-font-size--sm">{t('who')}</h2>
                  <p
                    className="vads-u-margin--0"
                    data-testid="appointment-details--provider-value"
                  >
                    {appointment.doctorName}
                  </p>
                </div>
              )}

              {(isInPersonAppointment || isCvtAppointment) && (
                <div data-testid="appointment-details--where">
                  <h2 className="vads-u-font-size--sm">
                    {t('where-to-attend')}
                  </h2>
                  <p
                    className="vads-u-margin--0"
                    data-testid="appointment-details--facility-value"
                  >
                    {appointment.facility}
                  </p>
                  {appointment.facilityAddress?.street1 && (
                    <div className="vads-u-margin-bottom--2">
                      <AddressBlock
                        address={appointment.facilityAddress}
                        placeName={appointment.facility}
                        showDirections
                      />
                    </div>
                  )}
                </div>
              )}
              {(isPhoneAppointment || isVvcAppointment) && (
                <div data-testid="appointment-details--need-to-make-changes">
                  <h2 className="vads-u-font-size--sm">
                    {t('need-to-make-changes')}
                  </h2>
                  <p className="vads-u-margin-top--0">
                    {t('contact-this-facility')}
                  </p>
                </div>
              )}

              {(isPhoneAppointment || isVvcAppointment) && (
                <div data-testid="appointment-details--facility-info">
                  {appointment.facility && (
                    <p
                      className="vads-u-margin--0"
                      data-testid="appointment-details--facility-value"
                    >
                      {appointment.facility}
                    </p>
                  )}
                  {appointment.facilityAddress?.city &&
                    appointment.facilityAddress?.state && (
                      <p
                        className="vads-u-margin--0"
                        data-testid="appointment-details--facility-address"
                      >
                        {`${appointment.facilityAddress.city}, ${
                          appointment.facilityAddress.state
                        }`}
                      </p>
                    )}
                </div>
              )}
              <div className="vads-u-margin-top--2">
                {clinic && (
                  <p
                    className="vads-u-margin--0"
                    data-testid="appointment-details--clinic-value"
                  >
                    {`${t('clinic')}:`} {clinic}
                  </p>
                )}
                {(isInPersonAppointment || isCvtAppointment) &&
                  appointment.clinicLocation && (
                    <p
                      className="vads-u-margin--0"
                      data-testid="appointment-details--location-value"
                    >
                      {`${t('location')}: ${appointment.clinicLocation}`}
                    </p>
                  )}
                {appointment.clinicPhoneNumber && (
                  <div data-testid="appointment-details--phone">
                    <p
                      className="vads-u-margin--0"
                      data-testid="appointment-details--phone-value"
                    >
                      {`${t('clinic-phone')}: `}
                      <va-telephone
                        onClick={handlePhoneNumberClick}
                        contact={appointment.clinicPhoneNumber}
                      />
                      <br />(
                      <va-telephone
                        contact={phoneNumbers.tty}
                        tty
                        ariaLabel="7 1 1."
                      />
                      )
                    </p>
                  </div>
                )}
              </div>
              {app === APP_NAMES.CHECK_IN && link}
              {app === APP_NAMES.PRE_CHECK_IN && (
                <PrepareContent
                  router={router}
                  smallHeading
                  appointmentCount={1}
                />
              )}
            </div>
          </Wrapper>
        </>
      )}
    </>
  );
};

AppointmentDetails.propTypes = {
  router: PropTypes.object,
};

export default AppointmentDetails;