diff --git a/packages/esm-appointments-app/src/appointments.component.tsx b/packages/esm-appointments-app/src/appointments.component.tsx index 07e9ed07b0..418067669c 100644 --- a/packages/esm-appointments-app/src/appointments.component.tsx +++ b/packages/esm-appointments-app/src/appointments.component.tsx @@ -28,7 +28,7 @@ const Appointments: React.FC = () => { return ( <> - + diff --git a/packages/esm-appointments-app/src/appointments/common-components/appointments-table.component.tsx b/packages/esm-appointments-app/src/appointments/common-components/appointments-table.component.tsx index 8390951fab..998ed2c1fb 100644 --- a/packages/esm-appointments-app/src/appointments/common-components/appointments-table.component.tsx +++ b/packages/esm-appointments-app/src/appointments/common-components/appointments-table.component.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import dayjs from 'dayjs'; import isToday from 'dayjs/plugin/isToday'; @@ -8,6 +8,7 @@ import { DataTable, DataTableSkeleton, Layer, + MultiSelect, OverflowMenu, OverflowMenuItem, Pagination, @@ -39,7 +40,8 @@ import { import { EmptyState } from '../../empty-state/empty-state.component'; import { exportAppointmentsToSpreadsheet } from '../../helpers/excel'; import { useTodaysVisits } from '../../hooks/useTodaysVisits'; -import { type Appointment } from '../../types'; +import { type Appointment, AppointmentStatus } from '../../types'; +import { useAppointmentServices } from '../../hooks/useAppointmentService'; import { type ConfigObject } from '../../config-schema'; import { getPageSizes, useAppointmentSearchResults } from '../utils'; import AppointmentActions from './appointments-actions.component'; @@ -65,9 +67,43 @@ const AppointmentsTable: React.FC = ({ const { t } = useTranslation(); const [pageSize, setPageSize] = useState(25); const [searchString, setSearchString] = useState(''); + const [selectedServices, setSelectedServices] = useState([]); + const [selectedStatuses, setSelectedStatuses] = useState([]); const config = useConfig(); const { appointmentsTableColumns } = config; - const searchResults = useAppointmentSearchResults(appointments, searchString); + const { serviceTypes: allServices } = useAppointmentServices(); + const serviceOptions = useMemo(() => { + return ( + allServices + ?.map((service) => ({ + id: service.uuid, + label: service.name, + })) + .sort((a, b) => a.label.localeCompare(b.label)) ?? [] + ); + }, [allServices]); + + const statusOptions = useMemo(() => { + return Object.values(AppointmentStatus).map((status) => ({ + id: status, + label: t(status.toLowerCase(), status), + })); + }, [t]); + + // Filter appointments by service and status + const filteredAppointments = useMemo(() => { + let filtered = appointments; + if (selectedServices.length > 0) { + filtered = filtered.filter((appointment) => selectedServices.includes(appointment.service.uuid)); + } + if (selectedStatuses.length > 0) { + filtered = filtered.filter((appointment) => selectedStatuses.includes(appointment.status)); + } + + return filtered; + }, [appointments, selectedServices, selectedStatuses]); + + const searchResults = useAppointmentSearchResults(filteredAppointments, searchString); const { results, goTo, currentPage } = usePagination(searchResults, pageSize); const { customPatientChartUrl, patientIdentifierType } = useConfig(); const { visits } = useTodaysVisits(); @@ -141,6 +177,36 @@ const AppointmentsTable: React.FC = ({

{`${t(tableHeading)} ${t('appointments', 'Appointments')}`}

+
+
+ (item ? item.label : '')} + label={t('filterByService', 'Filter by Service')} + onChange={({ selectedItems }) => { + const selectedUuids = selectedItems.map((item) => item.id); + setSelectedServices(selectedUuids); + }} + selectedItems={serviceOptions.filter((item) => selectedServices.includes(item.id))} + size={responsiveSize} + type="inline" + /> + (item ? item.label : '')} + label={t('filterByStatus', 'Filter by Status')} + onChange={({ selectedItems }) => { + const selectedStatusValues = selectedItems.map((item) => item.id); + setSelectedStatuses(selectedStatusValues); + }} + selectedItems={statusOptions.filter((item) => selectedStatuses.includes(item.id))} + size={responsiveSize} + type="inline" + /> +
+
* { + flex: 1; + min-width: 200px; +} + +// Responsive design for mobile +@media (max-width: 672px) { + .filterRow { + flex-direction: column; + } + + .filterRow > * { + min-width: 100%; + } +} diff --git a/packages/esm-appointments-app/src/appointments/scheduled/appointments-list.component.tsx b/packages/esm-appointments-app/src/appointments/scheduled/appointments-list.component.tsx index ff7fbb8f6b..9b1d7c8530 100644 --- a/packages/esm-appointments-app/src/appointments/scheduled/appointments-list.component.tsx +++ b/packages/esm-appointments-app/src/appointments/scheduled/appointments-list.component.tsx @@ -1,5 +1,4 @@ import React, { useMemo } from 'react'; -import { filterByServiceType } from '../utils'; import { useAppointmentList } from '../../hooks/useAppointmentList'; import AppointmentsTable from '../common-components/appointments-table.component'; @@ -20,18 +19,16 @@ const AppointmentsList: React.FC = ({ }) => { const { appointmentList, isLoading } = useAppointmentList(status, date); - const appointmentsFilteredByServiceType = filterByServiceType(appointmentList, appointmentServiceTypes).map( - (appointment) => ({ - id: appointment.uuid, - ...appointment, - }), - ); + const appointmentsWithId = appointmentList.map((appointment) => ({ + id: appointment.uuid, + ...appointment, + })); const activeAppointments = useMemo(() => { return excludeCancelledAppointments - ? appointmentsFilteredByServiceType.filter((appointment) => appointment.status !== 'Cancelled') - : appointmentsFilteredByServiceType; - }, [excludeCancelledAppointments, appointmentsFilteredByServiceType]); + ? appointmentsWithId.filter((appointment) => appointment.status !== 'Cancelled') + : appointmentsWithId; + }, [excludeCancelledAppointments, appointmentsWithId]); return ( = ({ appointme return ( <> - setCurrentTab(name)} - selectedIndex={panelsToShow.findIndex((panel) => panel.name == currentTab) ?? 0} - selectionMode="manual"> - {panelsToShow.map((panel) => ( - - ))} - + {/* Hide the ContentSwitcher UI but keep the logic */} +
+ setCurrentTab(name)} + selectedIndex={panelsToShow.findIndex((panel) => panel.name == currentTab) ?? 0} + selectionMode="manual"> + {panelsToShow.map((panel) => ( + + ))} + +
{(extension) => { @@ -97,6 +100,7 @@ const ScheduledAppointments: React.FC = ({ appointme extension={extension} hideExtensionTab={hideExtension} showExtensionTab={showExtension} + panelsToShow={panelsToShow} /> ); }} @@ -138,6 +142,7 @@ function ExtensionWrapper({ dateType, showExtensionTab, hideExtensionTab, + panelsToShow, }: { extension: ConnectedExtension; currentTab: string; @@ -146,6 +151,7 @@ function ExtensionWrapper({ dateType: DateType; showExtensionTab: (extension: string) => void; hideExtensionTab: (extension: string) => void; + panelsToShow: Array; }) { const currentConfig = useRef(null); const currentDateType = useRef(dateType); @@ -164,17 +170,16 @@ function ExtensionWrapper({ : hideExtensionTab(extension.name); } }, [extension, dateType, showExtensionTab, hideExtensionTab]); + // Only show the first extension to avoid duplicate tables + const isFirstExtension = panelsToShow.length > 0 && extension.name === panelsToShow[0].name; return ( -
+
diff --git a/packages/esm-appointments-app/src/header/appointments-header.component.tsx b/packages/esm-appointments-app/src/header/appointments-header.component.tsx index 69cbc521a0..8c117cedbd 100644 --- a/packages/esm-appointments-app/src/header/appointments-header.component.tsx +++ b/packages/esm-appointments-app/src/header/appointments-header.component.tsx @@ -1,32 +1,18 @@ -import React, { useCallback, useMemo } from 'react'; +import React from 'react'; import dayjs from 'dayjs'; import { useTranslation } from 'react-i18next'; -import { MultiSelect } from '@carbon/react'; import { PageHeader, PageHeaderContent, AppointmentsPictogram, OpenmrsDatePicker } from '@openmrs/esm-framework'; import { omrsDateFormat } from '../constants'; -import { useAppointmentServices } from '../hooks/useAppointmentService'; -import { useAppointmentsStore, setSelectedDate, setAppointmentServiceTypes } from '../store'; +import { useAppointmentsStore, setSelectedDate } from '../store'; import styles from './appointments-header.scss'; interface AppointmentHeaderProps { title: string; - showServiceTypeFilter?: boolean; } -const AppointmentsHeader: React.FC = ({ title, showServiceTypeFilter }) => { +const AppointmentsHeader: React.FC = ({ title }) => { const { t } = useTranslation(); - const { selectedDate, appointmentServiceTypes } = useAppointmentsStore(); - const { serviceTypes } = useAppointmentServices(); - - const handleChangeServiceTypeFilter = useCallback(({ selectedItems }) => { - const selectedUuids = selectedItems.map((item) => item.id); - setAppointmentServiceTypes(selectedUuids); - }, []); - - const serviceTypeOptions = useMemo( - () => serviceTypes?.map((item) => ({ id: item.uuid, label: item.name })) ?? [], - [serviceTypes], - ); + const { selectedDate } = useAppointmentsStore(); return ( @@ -39,17 +25,6 @@ const AppointmentsHeader: React.FC = ({ title, showServi onChange={(date) => setSelectedDate(dayjs(date).startOf('day').format(omrsDateFormat))} value={dayjs(selectedDate).toDate()} /> - {showServiceTypeFilter && ( - (item ? item.label : '')} - label={t('filterAppointmentsByServiceType', 'Filter appointments by service type')} - onChange={handleChangeServiceTypeFilter} - type="inline" - selectedItems={serviceTypeOptions.filter((item) => appointmentServiceTypes.includes(item.id))} - /> - )}
);