import React, { useCallback, useEffect, useReducer, useRef, useState } from 'react'; import dayjs from 'dayjs'; import { useTranslation } from 'react-i18next'; import { ContentSwitcher, Switch } from '@carbon/react'; import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'; import { ExtensionSlot, Extension, useConnectedExtensions, type ConnectedExtension, type ConfigObject, useLayoutType, isDesktop, } from '@openmrs/esm-framework'; import { useSelectedDateContext } from '../../hooks/selected-date-context'; import styles from './scheduled-appointments.scss'; dayjs.extend(isSameOrBefore); interface ScheduledAppointmentsProps { appointmentServiceTypes?: Array; } type DateType = 'pastDate' | 'today' | 'futureDate'; const scheduledAppointmentsPanelsSlot = 'scheduled-appointments-panels-slot'; const ScheduledAppointments: React.FC = ({ appointmentServiceTypes }) => { const { t } = useTranslation(); const { selectedDate } = useSelectedDateContext(); const layout = useLayoutType(); const responsiveSize = isDesktop(layout) ? 'sm' : 'md'; // added to prevent auto-removal of translations for dynamic keys // t('checkedIn', 'Checked in'); // t('expected', 'Expected'); const [currentTab, setCurrentTab] = useState(null); const [dateType, setDateType] = useState('today'); const scheduledAppointmentPanels = useConnectedExtensions(scheduledAppointmentsPanelsSlot); const { allowedExtensions, showExtension, hideExtension } = useAllowedExtensions(); const shouldShowPanel = useCallback( (panel: Omit) => allowedExtensions[panel.name] ?? false, [allowedExtensions], ); useEffect(() => { const dayjsDate = dayjs(selectedDate); const now = dayjs(); if (dayjsDate.isBefore(now, 'date')) { setDateType('pastDate'); } else if (dayjsDate.isAfter(now, 'date')) { setDateType('futureDate'); } else { setDateType('today'); } }, [selectedDate]); useEffect(() => { // This is intended to cover two things: // 1. If no current tab is set, set it to the first allowed tab // 2. If a current tab is set, but the tab is no longer allowed in this context, set it to the // first allowed tab if (allowedExtensions && (currentTab === null || !allowedExtensions[currentTab])) { for (const extension of Object.getOwnPropertyNames(allowedExtensions)) { if (allowedExtensions[extension]) { setCurrentTab(extension); break; } } } }, [allowedExtensions, currentTab]); const panelsToShow = scheduledAppointmentPanels.filter(shouldShowPanel); return ( <> setCurrentTab(name)} selectedIndex={panelsToShow.findIndex((panel) => panel.name == currentTab) ?? 0} selectionMode="manual" > {panelsToShow.map((panel) => ( ))} {(extension) => { return ( ); }} ); }; function useAllowedExtensions() { const [allowedExtensions, dispatch] = useReducer( (state: Record, action: { type: 'show_extension' | 'hide_extension'; extension: string }) => { let addedState = {} as Record; switch (action.type) { case 'show_extension': addedState[action.extension] = true; break; case 'hide_extension': addedState[action.extension] = false; break; } return { ...state, ...addedState }; }, {}, ); return { allowedExtensions: allowedExtensions as Readonly>, showExtension: (extension: string) => dispatch({ type: 'show_extension', extension }), hideExtension: (extension: string) => dispatch({ type: 'hide_extension', extension }), }; } function ExtensionWrapper({ extension, currentTab, appointmentServiceTypes, date, dateType, showExtensionTab, hideExtensionTab, }: { extension: ConnectedExtension; currentTab: string; appointmentServiceTypes: Array; date: string; dateType: DateType; showExtensionTab: (extension: string) => void; hideExtensionTab: (extension: string) => void; }) { const currentConfig = useRef(null); const currentDateType = useRef(dateType); // This use effect hook controls whether the tab for this extension should render useEffect(() => { if ( currentConfig.current === null || (currentConfig.current !== null && !shallowEqual(currentConfig.current, extension.config)) || currentDateType.current !== dateType ) { currentConfig.current = extension.config; currentDateType.current = dateType; shouldDisplayExtensionTab(extension?.config, dateType) ? showExtensionTab(extension.name) : hideExtensionTab(extension.name); } }, [extension, dateType, showExtensionTab, hideExtensionTab]); return (
); } function shouldDisplayExtensionTab(config: ConfigObject | undefined, dateType: DateType): boolean { if (!config) { return false; } switch (dateType) { case 'futureDate': { let showForFuture = config.showForFutureDate; if (showForFuture === undefined) { if (config.status === 'Scheduled' || config.status === 'Cancelled') { showForFuture = true; } else { showForFuture = false; } } return showForFuture; } case 'pastDate': { let showForPast = config.showForPastDate; if (showForPast === undefined) { // Completed, missed, and cancelled should show for past dates if (config.status === 'Completed' || config.status === 'Missed' || config.status === 'Cancelled') { showForPast = true; } else { showForPast = false; } } return showForPast; } case 'today': { let showForToday = config.showForToday; if (showForToday === undefined) { if (config.status === 'Missed') { showForToday = false; } else { showForToday = true; } } return showForToday; } default: return false; } } function shallowEqual(objA: object, objB: object) { if (Object.is(objA, objB)) { return true; } if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { return false; } const objAKeys = Object.getOwnPropertyNames(objA); const objBKeys = Object.getOwnPropertyNames(objB); return objAKeys.length === objBKeys.length && objAKeys.every((key) => objA[key] === objB[key]); } export default ScheduledAppointments;