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

cancel button fixes #571

Merged
merged 2 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Box } from '@mui/material';
import { ReactElement } from 'react';
import { UCAppointmentInformation } from 'ehr-utils';
import { classifyAppointments } from '../helpers';
import { getAppointmentStatusChip } from './AppointmentTableRow';
import { getInPersonAppointmentStatusChip } from './AppointmentTableRow';

export interface AppointmentChip {
appointments: UCAppointmentInformation[];
Expand Down Expand Up @@ -33,7 +33,7 @@ export const AppointmentsStatusChipsCount = ({ appointments }: AppointmentChip):
ORDER_STATUS.indexOf(statusOne) - ORDER_STATUS.indexOf(statusTwo),
)
.map(([status, count]) => (
<Box key={status}>{getAppointmentStatusChip(status, count)}</Box>
<Box key={status}>{getInPersonAppointmentStatusChip(status, count)}</Box>
))}
</Box>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Operation } from 'fast-json-patch';
import { getPatchBinary, getStatusFromExtension } from 'ehr-utils';
import { VisitStatus, STATI } from '../helpers/mappingUtils';
import { useApiClients } from '../hooks/useAppClients';
import { getAppointmentStatusChip } from './AppointmentTableRow';
import { getInPersonAppointmentStatusChip } from './AppointmentTableRow';

const statuses = STATI;

Expand All @@ -16,6 +16,7 @@ export const switchStatus = async (
appointment: Appointment,
encounter: Encounter,
status: VisitStatus,
onStatusChange: (status: VisitStatus) => void,
): Promise<void> => {
if (status === 'unknown') {
throw new Error(`Invalid status: ${status}`);
Expand Down Expand Up @@ -51,16 +52,20 @@ export const switchStatus = async (
}),
],
});

onStatusChange(status);
};

interface AppointmentStatusSwitcherProps {
appointment: Appointment;
encounter: Encounter;
onStatusChange: (status: VisitStatus) => void;
}

export default function AppointmentStatusSwitcher({
appointment,
encounter,
onStatusChange,
}: AppointmentStatusSwitcherProps): ReactElement {
const { fhirClient } = useApiClients();
const [statusLoading, setStatusLoading] = React.useState<boolean>(false);
Expand All @@ -69,7 +74,7 @@ export default function AppointmentStatusSwitcher({
const handleChange = async (event: SelectChangeEvent): Promise<void> => {
const value = event.target.value;
setStatusLoading(true);
await switchStatus(fhirClient, currentAppointment, encounter, value as VisitStatus);
await switchStatus(fhirClient, currentAppointment, encounter, value as VisitStatus, onStatusChange);
const newAppointment = (await fhirClient?.readResource({
resourceType: 'Appointment',
resourceId: appointment.id || '',
Expand Down Expand Up @@ -99,7 +104,7 @@ export default function AppointmentStatusSwitcher({
labelId="status-select-label"
value={getStatusFromExtension(currentAppointment)}
onChange={async (event) => await handleChange(event)}
renderValue={(selected) => getAppointmentStatusChip(selected)}
renderValue={(selected) => getInPersonAppointmentStatusChip(selected)}
sx={{
boxShadow: 'none',
'.MuiOutlinedInput-notchedOutline': { border: 0 },
Expand All @@ -115,7 +120,7 @@ export default function AppointmentStatusSwitcher({
.filter((status) => status !== 'unknown' && status !== 'cancelled')
.map((status) => (
<MenuItem key={status} value={status}>
{getAppointmentStatusChip(status)}
{getInPersonAppointmentStatusChip(status)}
</MenuItem>
))}
</Select>
Expand Down
10 changes: 5 additions & 5 deletions packages/telemed-ehr/app/src/components/AppointmentTableRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const FLAGGED_REASONS_FOR_VISIT: string[] = [
'Allergic reaction',
];

export function getAppointmentStatusChip(status: string, count?: number): ReactElement {
export function getInPersonAppointmentStatusChip(status: string, count?: number): ReactElement {
if (!status) {
return <span>todo1</span>;
}
Expand Down Expand Up @@ -444,17 +444,17 @@ export default function AppointmentTableRow({
}}
>
{isLongWaitingTime && longWaitFlag}
{getStatiForVisitTimeCalculation(appointment.visitStatusHistory, appointment.start).map((statusTemp) => {
{getStatiForVisitTimeCalculation(appointment.visitStatusHistory, appointment.start).map((statusTemp, index) => {
return (
<Box sx={{ display: 'flex', gap: 1 }}>
<Box key={index} sx={{ display: 'flex', gap: 1 }}>
<Typography
variant="body2"
color={theme.palette.getContrastText(theme.palette.background.default)}
style={{ display: 'inline', fontWeight: 700, marginTop: 1 }}
>
{formatMinutes(getDurationOfStatus(statusTemp, appointment, appointment.visitStatusHistory, now))} mins
</Typography>
{getAppointmentStatusChip(statusTemp.label as keyof typeof CHIP_STATUS_MAP)}
{getInPersonAppointmentStatusChip(statusTemp.label as keyof typeof CHIP_STATUS_MAP)}
</Box>
);
})}
Expand Down Expand Up @@ -529,7 +529,7 @@ export default function AppointmentTableRow({
&nbsp;&nbsp;<strong>{start}</strong>
</Typography>
</Box>
<Box mt={1}>{getAppointmentStatusChip(appointment.status)}</Box>
<Box mt={1}>{getInPersonAppointmentStatusChip(appointment.status)}</Box>
</Link>
</Box>
</TableCell>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const AppointmentFooter: FC = () => {
const theme = useTheme();
const [isInviteParticipantOpen, setIsInviteParticipantOpen] = useState(false);

const appointmentAccessibility = useGetAppointmentAccessibility();
const appointmentAccessibility = useGetAppointmentAccessibility('telemedicine');
const { appointment, encounter } = getSelectors(useAppointmentStore, ['appointment', 'encounter']);
const { meetingData } = getSelectors(useVideoCallStore, ['meetingData']);

Expand All @@ -36,7 +36,7 @@ export const AppointmentFooter: FC = () => {
<InviteParticipant modalOpen={isInviteParticipantOpen} onClose={() => setIsInviteParticipantOpen(false)} />
)}
{((appointmentAccessibility.status &&
[ApptStatus.ready, ApptStatus['pre-video']].includes(appointmentAccessibility.status)) ||
[ApptStatus.ready, ApptStatus['pre-video']].includes(appointmentAccessibility.status as ApptStatus)) ||
(appointmentAccessibility.status &&
appointmentAccessibility.status === ApptStatus['on-video'] &&
!meetingData)) && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const AppointmentFooterButton: FC = () => {

const [buttonType, setButtonType] = useState<'assignMe' | 'connectUnassign' | 'reconnect' | null>(null);

const appointmentAccessibility = useGetAppointmentAccessibility();
const appointmentAccessibility = useGetAppointmentAccessibility('telemedicine');

useEffect(() => {
if (appointmentAccessibility.status !== ApptStatus.ready && !appointmentAccessibility.isStatusEditable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
useTheme,
} from '@mui/material';
import { DateTime } from 'luxon';
import { FC, useCallback, useState } from 'react';
import { FC, useCallback, useEffect, useState } from 'react';
import { Link as RouterLink } from 'react-router-dom';
import {
getQuestionnaireResponseByLinkId,
Expand All @@ -28,6 +28,7 @@ import {
AppointmentMessaging,
UCAppointmentInformation,
QuestionnaireLinkIds,
getStatusFromExtension,
} from 'ehr-utils';
import ChatModal from '../../../features/chat/ChatModal';
import { calculatePatientAge } from '../../../helpers/formatDateTime';
Expand All @@ -38,13 +39,15 @@ import EditPatientDialog from '../../components/EditPatientDialog';
import InviteParticipant from '../../components/InviteParticipant';
import { useGetAppointmentAccessibility } from '../../hooks';
import { useAppointmentStore, useGetTelemedAppointmentWithSMSModel } from '../../state';
import { getAppointmentStatusChip, getPatientName, quickTexts } from '../../utils';
import { getPatientName, quickTexts } from '../../utils';
// import { ERX } from './ERX';
import { PastVisits } from './PastVisits';
import { addSpacesAfterCommas } from '../../../helpers/formatString';
import { INTERPRETER_PHONE_NUMBER } from 'ehr-utils';
import { Appointment } from 'fhir/r4';
import AppointmentStatusSwitcher from '../../../components/AppointmentStatusSwitcher';
import { getTelemedAppointmentStatusChip } from '../../utils/getTelemedAppointmentStatusChip';
import { getInPersonAppointmentStatusChip } from '../../../components/AppointmentTableRow';

enum Gender {
'male' = 'Male',
Expand All @@ -57,6 +60,17 @@ interface AppointmentSidePanelProps {
appointmentType: 'telemedicine' | 'in-person';
}

const isInPersonStatusCancelable = (status: string | undefined): boolean => {
if (!status) {
return false;
}
return status === 'proposed' || status === 'pending' || status === 'booked' || status === 'arrived';
};

const isTelemedStatusCancelable = (status: ApptStatus): boolean => {
return status !== ApptStatus.complete && status !== ApptStatus.cancelled && status !== ApptStatus.unsigned;
};

export const AppointmentSidePanel: FC<AppointmentSidePanelProps> = ({ appointmentType }) => {
const theme = useTheme();

Expand Down Expand Up @@ -96,17 +110,28 @@ export const AppointmentSidePanel: FC<AppointmentSidePanelProps> = ({ appointmen
(status) => setIsERXLoading(status),
[setIsERXLoading],
);
const appointmentAccessibility = useGetAppointmentAccessibility();

const isCancellableStatus =
appointmentAccessibility.status !== ApptStatus.complete &&
appointmentAccessibility.status !== ApptStatus.cancelled &&
appointmentAccessibility.status !== ApptStatus.unsigned;
const appointmentAccessibility = useGetAppointmentAccessibility(appointmentType);

const isPractitionerAllowedToCancelThisVisit =
let isCancellableStatus =
appointmentType === 'telemedicine'
? isTelemedStatusCancelable(appointmentAccessibility.status as ApptStatus)
: isInPersonStatusCancelable(appointmentAccessibility.status as string);

const [isPractitionerAllowedToCancelThisVisit, setIsPractitionerAllowedToCancelThisVisit] = useState<boolean>(
// appointmentAccessibility.isPractitionerLicensedInState &&
// appointmentAccessibility.isEncounterAssignedToCurrentPractitioner &&
isCancellableStatus;
isCancellableStatus || false,
);

const onStatusChange = (status: string): void => {
isCancellableStatus = isInPersonStatusCancelable(status);
setIsPractitionerAllowedToCancelThisVisit(isCancellableStatus);
};

useEffect(() => {
setIsPractitionerAllowedToCancelThisVisit(isCancellableStatus);
}, [isCancellableStatus]);

const { data: appointmentMessaging, isFetching } = useGetTelemedAppointmentWithSMSModel(
{
Expand Down Expand Up @@ -161,7 +186,7 @@ export const AppointmentSidePanel: FC<AppointmentSidePanelProps> = ({ appointmen
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center' }}>
{appointmentType === 'telemedicine' &&
getAppointmentStatusChip(mapStatusToTelemed(encounter.status, appointment?.status))}
getTelemedAppointmentStatusChip(mapStatusToTelemed(encounter.status, appointment?.status))}

{appointment?.id && (
<Tooltip title={appointment.id}>
Expand All @@ -179,11 +204,17 @@ export const AppointmentSidePanel: FC<AppointmentSidePanelProps> = ({ appointmen
)}
</Box>

{appointmentType === 'in-person' && isPractitionerAllowedToCancelThisVisit && (
<AppointmentStatusSwitcher appointment={appointment as Appointment} encounter={encounter} />
{appointmentType === 'in-person' && appointmentAccessibility.isStatusEditable && (
<AppointmentStatusSwitcher
appointment={appointment as Appointment}
encounter={encounter}
onStatusChange={onStatusChange}
/>
)}
{!isPractitionerAllowedToCancelThisVisit &&
getAppointmentStatusChip(mapStatusToTelemed(encounter.status, appointment?.status))}
{appointmentType === 'in-person' &&
!appointmentAccessibility.isStatusEditable &&
!!appointment &&
getInPersonAppointmentStatusChip(getStatusFromExtension(appointment as Appointment) as ApptStatus)}

<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Typography variant="h4" color="primary.dark">
Expand Down Expand Up @@ -346,8 +377,11 @@ export const AppointmentSidePanel: FC<AppointmentSidePanelProps> = ({ appointmen
</Box>

<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, alignItems: 'start' }}>
{appointmentAccessibility.status &&
[ApptStatus['pre-video'], ApptStatus['on-video']].includes(appointmentAccessibility.status) && (
{appointmentType === 'telemedicine' &&
appointmentAccessibility.status &&
[ApptStatus['pre-video'], ApptStatus['on-video']].includes(
appointmentAccessibility.status as ApptStatus,
) && (
<Button
size="small"
sx={{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const ReviewAndSignButton: FC = () => {
const { mutateAsync, isLoading } = useChangeTelemedAppointmentStatusMutation();
const [openTooltip, setOpenTooltip] = useState(false);

const appointmentAccessibility = useGetAppointmentAccessibility();
const appointmentAccessibility = useGetAppointmentAccessibility('telemedicine');

const primaryDiagnosis = (chartData?.diagnosis || []).find((item) => item.isPrimary);
const medicalDecision = chartData?.medicalDecision?.text;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { getSelectors } from '../../shared/store/getSelectors';
import { useAppointmentStore } from '../state';
import useOttehrUser from '../../hooks/useOttehrUser';

export const useGetAppointmentAccessibility = (): GetAppointmentAccessibilityDataResult => {
export const useGetAppointmentAccessibility = (
appointmentType: 'telemedicine' | 'in-person',
): GetAppointmentAccessibilityDataResult => {
const { location, encounter, appointment } = getSelectors(useAppointmentStore, [
'location',
'encounter',
Expand All @@ -13,7 +15,7 @@ export const useGetAppointmentAccessibility = (): GetAppointmentAccessibilityDat
const user = useOttehrUser();

return useMemo(
() => getAppointmentAccessibilityData({ location, encounter, appointment, user }),
[location, encounter, appointment, user],
() => getAppointmentAccessibilityData({ location, encounter, appointment, user, appointmentType }),
[location, encounter, appointment, user, appointmentType],
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useAppointmentStore } from '../state';
import { useGetAppointmentAccessibility } from './useGetAppointmentAccessibility';

export const useIsReadOnly = (): void => {
const appointmentAccessibility = useGetAppointmentAccessibility();
const appointmentAccessibility = useGetAppointmentAccessibility('telemedicine');

useEffect(() => {
useAppointmentStore.setState({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,27 @@ import {
allLicensesForPractitioner,
ApptStatus,
checkIsEncounterForPractitioner,
getStatusFromExtension,
mapStatusToTelemed,
PractitionerLicense,
PractitionerQualificationCode,
VisitStatus,
} from 'ehr-utils';

export type GetAppointmentAccessibilityDataProps = {
location?: Location;
encounter: Encounter;
appointment?: Appointment;
user?: OttehrUser;
appointmentType?: string;
};

export type GetAppointmentAccessibilityDataResult = {
allLicenses?: PractitionerLicense[];
availableStates?: string[];
state?: string;
isStateAvailable: boolean;
status?: ApptStatus;
status?: ApptStatus | VisitStatus | undefined;
GiladSchneider marked this conversation as resolved.
Show resolved Hide resolved
isEncounterForPractitioner: boolean;
isStatusEditable: boolean;
isAppointmentReadOnly: boolean;
Expand All @@ -33,16 +36,28 @@ export const getAppointmentAccessibilityData = ({
encounter,
appointment,
user,
appointmentType,
}: GetAppointmentAccessibilityDataProps): GetAppointmentAccessibilityDataResult => {
const allLicenses = user?.profileResource && allLicensesForPractitioner(user.profileResource);
const availableStates = allLicenses?.map((item) => item.state);
const state = location?.address?.state;
const isStateAvailable =
!!state && !!availableStates && availableStates.includes(state as PractitionerQualificationCode);
const status = mapStatusToTelemed(encounter.status, appointment?.status);
const status =
appointmentType === 'telemedicine'
? mapStatusToTelemed(encounter.status, appointment?.status)
: appointment
? getStatusFromExtension(appointment as Appointment)
: undefined;

const isEncounterForPractitioner =
!!user?.profileResource && checkIsEncounterForPractitioner(encounter, user.profileResource);
const isStatusEditable = !!status && ![ApptStatus.complete, ApptStatus.ready].includes(status);

const isStatusEditable =
(!!status &&
appointmentType === 'telemedicine' &&
![ApptStatus.complete, ApptStatus.ready].includes(status as ApptStatus)) ||
(appointmentType === 'in-person' && status !== 'cancelled' && status !== 'fulfilled');

const isAppointmentAvailable = isStateAvailable && (status === ApptStatus.ready || isEncounterForPractitioner);
let isAppointmentReadOnly =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { ReactElement } from 'react';
import { Chip } from '@mui/material';
import { ApptStatus } from 'ehr-utils';

export function getAppointmentStatusChip(status?: ApptStatus, count?: number): ReactElement {
export function getTelemedAppointmentStatusChip(status?: ApptStatus, count?: number): ReactElement {
if (!status) {
return <span>todo1</span>;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/telemed-ehr/app/src/telemed/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from './appointments';
export * from './getAppointmentStatusChip';
export * from './getTelemedAppointmentStatusChip';
export * from './diffInMinutes';
export * from './vitals-helper';
export * from './school-work-excuse.helper';
Expand Down
Loading
Loading