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) Add text-to-speech voice call support for ticket numbers in queue screen #1317

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

Conversation

FelixKiprotich350
Copy link

@FelixKiprotich350 FelixKiprotich350 commented Sep 17, 2024

…queues

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 feature aims at voice calling ticket number in Service Queue by using Text-to-Speech web api

Screenshots

https://drive.google.com/file/d/1i_mW3YTR6XcOHsdrGbUiQ0bLMz8M8Ju5/view?usp=sharing

Related Issue

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

Other

Comment on lines 26 to 46
function readTicket(queue, speaker) {
if ('speechSynthesis' in window) {
const message = new SpeechSynthesisUtterance();
const utterance =
'Ticket Number: ' +
queue.ticketNumber.split('-')[0].split('') +
', - ' +
queue.ticketNumber.split('-')[1].split('') +
', Please Proceed To Room, ' +
queue.room;
message.rate = 1;
message.pitch = 1;
message.text = utterance;
return new Promise((resolve) => {
message.onend = resolve;
speaker.speak(message);
});
} else {
return Promise.resolve();
}
}
Copy link
Member

Choose a reason for hiding this comment

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

This could be moved into the component function body and memoized with a useCallback:

  const readTicket = useCallback((queue) => {
    if ('speechSynthesis' in window) {
      const message = new SpeechSynthesisUtterance();
      const [prefix, suffix] = queue.ticketNumber.split('-');
      const utterance = `Ticket Number: ${prefix.split('')}, - ${suffix.split('')}, Please Proceed To Room, ${queue.room}`;
      message.rate = 1;
      message.pitch = 1;
      message.text = utterance;
      return new Promise<void>((resolve) => {
        message.onend = resolve;
        speaker.speak(message);
      });
    }
    return Promise.resolve();
  }, [speaker]);

Also, if the SpeechSynthesisUtterance API is locale-aware (it probably is), we should consider internationalising utterance using something like:

	 const utterance = t(
          'ticketAnnouncement',
          'Ticket number: {{prefix}}, - {{suffix}}, please proceed to room {{room}}',
          {
            prefix: prefix.split('').join(' '),
            suffix: suffix.split('').join(' '),
            room: queue.room,
          },
        );

}

useEffect(() => {
const ticketsToCallOut = locationFilteredTickets.filter((item) => item.status == 'calling');
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 ticketsToCallOut = locationFilteredTickets.filter((item) => item.status == 'calling');
const ticketsToCallOut = locationFilteredTickets.filter((item) => item.status === 'calling');

Comment on lines 57 to 59
if (typeof mutate === 'function') {
mutate();
}
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
if (typeof mutate === 'function') {
mutate();
}
mutate?.();

@@ -19,13 +73,6 @@ const QueueScreen: React.FC<QueueScreenProps> = () => {
return <div>Error</div>;
Copy link
Member

Choose a reason for hiding this comment

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

We need a better error state here. Consider using the ErrorState component from the framework:

 return <ErrorState error={error} headerTitle={t('queueScreenError', 'Queue screen error')} />;

@denniskigen
Copy link
Member

denniskigen commented Sep 18, 2024

Thanks for taking a stab at this, @FelixKiprotich350. I've left a few suggestions around optimising the code, handling internationalisation, and error states. It'd be great to also extend the QueueScreen test to account for the new functionality. LMK if you need help with that.

@denniskigen denniskigen changed the title (Feat) Added Text-to-speech voice call for ticket numbers in service Queue (feat) Add text-to-speech voice call support for ticket numbers in queue screen Sep 18, 2024
const { activeTickets, isLoading, error, mutate } = useActiveTickets();
const speaker = window.speechSynthesis;
const [isSpeaking, setIsSpeaking] = useState(false);
const selectedLocation = useSelectedQueueLocationUuid();
Copy link
Member

Choose a reason for hiding this comment

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

Looks like we're not using this variable. If so, we should remove it.

Comment on lines 19 to 24
const rowData = locationFilteredTickets.map((ticket, index) => ({
id: `${index}`,
room: ticket.room,
ticketNumber: ticket.ticketNumber,
status: ticket.status,
}));
Copy link
Member

Choose a reason for hiding this comment

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

Consider memoizing rowData:

  const rowData = useMemo(() => 
    activeTickets.map((ticket, index) => ({
      id: `${index}`,
      room: ticket.room,
      ticketNumber: ticket.ticketNumber,
      status: ticket.status,
    })),
  [activeTickets]);

Also, is there something unique in the response that we could use as the id instead of index? Maybe a UUID?

Copy link
Author

Choose a reason for hiding this comment

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

@denniskigen currently the response does not have uuid thats why i opted to use index

const speaker = window.speechSynthesis;
const [isSpeaking, setIsSpeaking] = useState(false);
const selectedLocation = useSelectedQueueLocationUuid();
const locationFilteredTickets = activeTickets;
Copy link
Member

Choose a reason for hiding this comment

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

Did you mean to filter activeTickets by location here? If not, then we probably don't need the alias and we should use activeTickets directly.

Copy link
Author

@FelixKiprotich350 FelixKiprotich350 Sep 18, 2024

Choose a reason for hiding this comment

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

i did this because ill be making a follow up later to filter by location, am waiting for someone to include the locaion uuid in the response so that i can utilize that,,, just like the selectedlocation variable they are unused for now

Copy link
Member

@denniskigen denniskigen Sep 18, 2024

Choose a reason for hiding this comment

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

Best to not include it until then. It's best to keep things as easily understandable as possible.

Copy link
Author

Choose a reason for hiding this comment

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

well gotten

@ibacher
Copy link
Member

ibacher commented Sep 18, 2024

This option needs to be behind some kind of configuration. I don't think it should be enabled by default. I'm also slightly suspicious that embedding this in a frontend module is the right approach. While it works for a small clinic, I don't think it is a great idea for larger clinics. Basically, this is the type of feature that needs some community discussion before it's mergeable, but I like it!

@ojwanganto
Copy link
Contributor

This option needs to be behind some kind of configuration. I don't think it should be enabled by default. I'm also slightly suspicious that embedding this in a frontend module is the right approach. While it works for a small clinic, I don't think it is a great idea for larger clinics. Basically, this is the type of feature that needs some community discussion before it's mergeable, but I like it!

This will be helpful.

@FelixKiprotich350
Copy link
Author

Yea sure ,,, with the community discussions i believe we will have good ideas on how best we can implement this and configure dynamically

@FelixKiprotich350
Copy link
Author

FelixKiprotich350 commented Sep 18, 2024

This option needs to be behind some kind of configuration. I don't think it should be enabled by default. I'm also slightly suspicious that embedding this in a frontend module is the right approach. While it works for a small clinic, I don't think it is a great idea for larger clinics. Basically, this is the type of feature that needs some community discussion before it's mergeable, but I like it!

this is true @ibacher , but how do you see it given that the feature can be used dynamically with the help of some configurations. Given an example of a large clinic/facility with multiple queue locations and service points, where every point or Queue has a screen to call tickets specifically for that point?
Whats your thoughts on this?

@ibacher
Copy link
Member

ibacher commented Sep 18, 2024

@FelixKiprotich350 If I had exact thoughts on how this kind of feature should work, I would've included them. That's why I think we need a community discussion on Talk, because I'm not exactly sure what the best behaviour is here. My guess is that there's a lot of additional layers that are needed, i.e., for a user viewing the queue to select to be a system that automatically calls out tickets so that, e.g., a clinic manager or clinician viewing the queue in an office doesn't need to be bothered. Similarly, with a larger clinic, there may be two or more, e.g., nurse stations working on the same queue, in which case only one should probably be announcing.

But this requires more thought than is ideal to document on a PR in comments, hence, a Talk post would be ideal.

@FelixKiprotich350
Copy link
Author

FelixKiprotich350 commented Sep 18, 2024

@ibacher Agree..... You can guide on how to go about it

if ('speechSynthesis' in window) {
const message = new SpeechSynthesisUtterance();
const [prefix, suffix] = queue.ticketNumber.split('-');
// const utterance = `Ticket Number: ${prefix.split('')}, - ${suffix.split('')}, Please Proceed To Room, ${queue.room}`;
Copy link
Member

Choose a reason for hiding this comment

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

This should be removed

Copy link
Author

Choose a reason for hiding this comment

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

noted

@mogoodrich
Copy link
Member

I tend to agree with @ibacher , thanks... fyi @mseaton @chibongho

@denniskigen
Copy link
Member

Could we wire up some test coverage for this new functionality?

@FelixKiprotich350
Copy link
Author

Could we wire up some test coverage for this new functionality?

yes sure i can do that

@brandones
Copy link
Contributor

Agree with @ibacher that more discussion is needed. Since there's no Talk link posted here I assume there's no Talk thread and will share my thoughts here.

My suggestion would be that there should just be a page for this functionality. If someone wants their computer to make announcements for the queue, they just leave a tab open on that page.

Eventually, and this would need design, the full vision might be a "queue display" page which has a toggle for whether to make audio announcements and which has a button that will open a separate window that is intended to be displayed full-screen on a monitor facing the patients in the waiting room. Like Google Slides "presenter view" feature.

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.

6 participants