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

(feat) O3-4335: Enable multiple filters on the appointments app dashboard #1449

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

Samstar10
Copy link
Contributor

@Samstar10 Samstar10 commented Jan 17, 2025

Requirements

  • This PR has a title that briefly describes the work done including the ticket number. If there is a ticket, make sure your PR title includes a conventional commit label. See existing PR titles for inspiration.
  • My work conforms to the OpenMRS 3.0 Styleguide and design documentation.
  • My work includes tests or is validated by existing tests.

Summary

This PR introduces a new feature that allows a user to filter appointments by multiple service types at once in the Appointments app dashboard.

Screenshots

Screenshot 2025-01-17 at 12 59 58

Related Issue

https://openmrs.atlassian.net/browse/O3-4335

Other

@paulsonder
Copy link

paulsonder commented Jan 20, 2025

Hi @Samstar10 - this looks good to me based on the screenshot.

Do the numbers update in the metrics cards? E.g. 'Scheduled appointments'?

@Samstar10
Copy link
Contributor Author

Hi @Samstar10 - this looks good to me based on the screenshot.

Do the numbers update in the metrics cards? E.g. 'Scheduled appointments'?

Yes they do @paulsonder

{items.map((item) => (
<React.Fragment key={item.uuid}>
<MenuItemSelectable
key={item.uuid}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
key={item.uuid}

The parent React Fragment already has a key, so we can rid of this duplicate one.

aria-label={t('filterByServiceType', 'Filter by service type')}
id="serviceMenu"
>
{items.map((item) => (
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{items.map((item) => (
{items.map((item, i) => (

onChange={() => handleMenuItemChange(item.uuid)}
onClick={(e) => e.stopPropagation()}
/>
<MenuItemDivider />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
<MenuItemDivider />
{i < items.length - 1 && <MenuItemDivider />}

So we don't render a divider after the last menuitem.

Comment on lines +62 to +64
useEffect(() => {
onChange?.('');
}, [])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
useEffect(() => {
onChange?.('');
}, [])
useEffect(() => {
onChange?.([]);
}, [onChange])

Shouldn't this be initialized with an empty array? Also, we need to add the onChange function to the effect's dependency array.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@denniskigen if we initialise the onChange function using an empty array the table becomes reset to an empty state rather than showing all categories on initialisation as is intended.

onChange?: (evt) => void;
}

const AppointmentsHeader: React.FC<AppointmentHeaderProps> = ({ title, appointmentServiceType, onChange }) => {
const { t } = useTranslation();
const { selectedDate, setSelectedDate } = useContext(SelectedDateContext);
const { serviceTypes } = useAppointmentServices();

const items = [{ name: 'All', uuid: '' }, ...serviceTypes];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this All option redundant now that we're in a multi-select context? My assumption is that in a multi-select, "no selection" effectively means "show all". Having an "All" option creates ambiguous states: what happens when "All" and an individual service are selected?

I think it's more intuitive to allow the user to just select the specific services they want to filter by.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@denniskigen when the "All" option is selected, it resets all other filters selected and also when a filter is selected the "All" is automatically unselected. This is convenient for when a user selects multiple filters and wants to go back to seeing all the categories, they don't have to go back and unselect all the selected categories but instead they can just click "All" and have everything reset. So when a filter is selected, the All is automatically unselected and vice versa.

const items = [{ name: 'All', uuid: '' }, ...serviceTypes];
const [selectedItems, setSelectedItems] = useState([items[0]]);

const handleMenuItemChange = (itemUuid: string) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const handleMenuItemChange = (itemUuid: string) => {
const handleMenuItemChange = useCallback(itemUuid: string) => {

Let's wrap this in a useCallback hook.

@denniskigen
Copy link
Member

denniskigen commented Jan 28, 2025

@paulsonder @ciaranduffy I'm not entirely sure that the MenuButton is the correct Carbon component to use for this table filter.

On the one hand, it makes sense to use it if the goal is to hide extra actions in limited screen space:

CleanShot 2025-01-28 at 1  14 37@2x

But it doesn't make sense if the goal is to display a list of filters:

CleanShot 2025-01-28 at 1  16 30@2x

The MultiSelect component is better suited for displaying a list of filters, but its not ideal for situations where space is limited.

So, I'm unsure which component is the best fit. To add another consideration, this problem has been solved in a different way in the service queues page where a list of filters are displayed in the table header.

CleanShot 2025-01-28 at 1  21 30@2x

One specific aspect I like about the approach there is the proximity of the filters to the table for the following reasons:

  • It gives users immediate context - filters are placed directly above the table, creating a clear visual hierarchy. Users instinctively understand that these controls affect the data below.
  • It reduces cognitive load - no need to search for filters in the page header. Everything related to the table data is grouped together.
  • It enables faster iteration - users can adjust filters and immediately see the results without scrolling or navigating to another part of the UI.

Do you think we should consider adopting this approach for the appointments page?

@Samstar10
Copy link
Contributor Author

@paulsonder @ciaranduffy I'm not entirely sure that the MenuButton is the correct Carbon component to use for this table filter.

On the one hand, it makes sense to use it if the goal is to hide extra actions in limited screen space:

CleanShot 2025-01-28 at 1  14 37@2x

But it doesn't make sense if the goal is to display a list of filters:

CleanShot 2025-01-28 at 1  16 30@2x

The MultiSelect component is better suited for displaying a list of filters, but its not ideal for situations where space is limited.

So, I'm unsure which component is the best fit. To add another consideration, this problem has been solved in a different way in the service queues page where a list of filters are displayed in the table header.

CleanShot 2025-01-28 at 1  21 30@2x

One specific aspect I like about the approach there is the proximity of the filters to the table for the following reasons:

  • It gives users immediate context - filters are placed directly above the table, creating a clear visual hierarchy. Users instinctively understand that these controls affect the data below.
  • It reduces cognitive load - no need to search for filters in the page header. Everything related to the table data is grouped together.
  • It enables faster iteration - users can adjust filters and immediately see the results without scrolling or navigating to another part of the UI.

Do you think we should consider adopting this approach for the appointments page?

@denniskigen should I wait for response on this before I push the requested changes?

@ciaranduffy
Copy link

Thanks for your patience while we got around to this @Samstar10 and thank you @denniskigen for catching this point. You're absolutely correct to say that the MenuButton isn't the correct component here and the component you've pointed to in the Service Queues would in fact be more suitable. That component is https://carbondesignsystem.com/components/dropdown/usage/ and it's the inline variant.

Hope that's helpful and helps you move this forward

@Samstar10
Copy link
Contributor Author

Thanks for your patience while we got around to this @Samstar10 and thank you @denniskigen for catching this point. You're absolutely correct to say that the MenuButton isn't the correct component here and the component you've pointed to in the Service Queues would in fact be more suitable. That component is https://carbondesignsystem.com/components/dropdown/usage/ and it's the inline variant.

Hope that's helpful and helps you move this forward

Hello @ciaranduffy, the dropdown component does not allow selection of multiple items and wouldn't be able to meet the feature requirements.

@ciaranduffy
Copy link

@Samstar10 wouldn't you just use the multi-select variant of the same component then?

I think this pattern could be improved though. Carbon's multi-select component doesn't do a great job of showing you what filters are applied, only that two (or more) filters are applied, but that's something that could be improved in a future iteration, I think.

@Samstar10
Copy link
Contributor Author

@Samstar10 wouldn't you just use the multi-select variant of the same component then?

I think this pattern could be improved though. Carbon's multi-select component doesn't do a great job of showing you what filters are applied, only that two (or more) filters are applied, but that's something that could be improved in a future iteration, I think.

Alright @ciaranduffy, that sounds goods to me. I'll get it done. Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants