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

Enhance admin note visibility and improve accessibility for quick actions #5968

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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 @@ -49,7 +49,7 @@ const NotesCreator = ({ noteTitle, setTitle, noteText, setText }) => {
</div>
</div>
{/* Aria-live region for screen reader announcements */}
<div aria-live="assertive" aria-atomic="true" className="sr-only">
<div aria-live="assertive" aria-atomic="true" className="sr-admin-note-only">
{liveMessage}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Cookies } from 'react-cookie-consent';

const NotesModalTrigger = ({ setIsModalOpen, notesList, setNoteFetchTimestamp }) => {
const [notificationCount, setNotificationCount] = useState(0);

const onClickAdminButton = () => {
setIsModalOpen('adminNotePanel');
setNotificationCount(0);
setNoteFetchTimestamp();
};

useEffect(() => {
// Retrieve the last fetch timestamp from the cookie
const userLastFetchTimestamp = Cookies.get('lastFetchAdminNoteTimestamp');

// Check if the cookie value is a valid number
const parsedTimestamp = isFinite(userLastFetchTimestamp) ? parseInt(userLastFetchTimestamp) : 0;

// Calculate the count of new notes
const newAdminNoteCount = notesList.filter(note => new Date(note.updated_at) > new Date(parsedTimestamp)).length;

// Update the notification count state
setNotificationCount(newAdminNoteCount);
}, [notesList]);

// Accessible message for screen readers, indicating whether there are new admin notes or none available
const notesAriaLabel = !notesList.length ? I18n.t('notes.admin.aria_label.no_notes_available')
: I18n.t('notes.admin.aria_label.new_notes_message', { count: notificationCount, plural: notificationCount > 1 ? 's' : '' });

return (
<div className="admin-notes-modal-trigger">
<button
onClick={onClickAdminButton}
className="button admin-focus-highlight admin-action-button"
aria-haspopup="dialog"
aria-label={notesAriaLabel}
>
{I18n.t('notes.admin.button_text')}
</button>
{
(notificationCount > 0) && (
<div className="icon-notification_admin--badge">
{notificationCount}
</div>
)}
</div>
);
};

// Define PropTypes
NotesModalTrigger.propTypes = {
setIsModalOpen: PropTypes.func.isRequired, // Expecting a function, required
setNoteFetchTimestamp: PropTypes.func.isRequired,
notesList: PropTypes.arrayOf(PropTypes.object).isRequired // Expecting an array of objects, required
};

export default NotesModalTrigger;
28 changes: 18 additions & 10 deletions app/assets/javascripts/components/admin_notes/notes_panel.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React, { useEffect, useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { Cookies } from 'react-cookie-consent';
import { fetchAllAdminCourseNotes, createAdminCourseNote } from '../../actions/admin_course_notes_action';
import NotesList from './notes_list';
import NotesCreator from './notes_creator';
import NotesModalTrigger from './notes_modal_trigger';

const NotesPanel = () => {
// State variables for managing the modal and note creation
Expand All @@ -23,6 +25,18 @@ const NotesPanel = () => {
// Get the dispatch function from the Redux store
const dispatch = useDispatch();

// Updates the cookie timestamp to track when notes were last fetched or created.
const setNoteFetchTimestamp = () => {
// Set the current timestamp as a cookie when the user fetches notes or create notes
const currentTimestamp = Date.now();

// Set the expiration date to 10 years from now
const expires = new Date();
expires.setFullYear(expires.getFullYear() + 10);

Cookies.set('lastFetchAdminNoteTimestamp', currentTimestamp, { expires: expires });
};

// Fetch all course notes when the component mounts
useEffect(() => {
// Define a function to fetch the course notes
Expand Down Expand Up @@ -76,6 +90,8 @@ const NotesPanel = () => {

setText('');
setTitle('');
// Set the cookie timestamp after note creation to prevent the admin from receiving redundant notifications for notes they’ve created
setNoteFetchTimestamp();
};

// Close the modal and deactivate note creation
Expand All @@ -86,15 +102,7 @@ const NotesPanel = () => {

// Conditionally render a button if modalType is null
if (!isModalOpen) {
return (
<button
onClick={() => setIsModalOpen('adminNotePanel')}
className="button admin-focus-highlight"
aria-haspopup="dialog"
>
{I18n.t('notes.admin.button_text')}
</button>
);
return (<NotesModalTrigger setIsModalOpen={setIsModalOpen} notesList={notesList} setNoteFetchTimestamp={setNoteFetchTimestamp}/>);
}

return (
Expand Down Expand Up @@ -163,7 +171,7 @@ const NotesPanel = () => {
</div>

{/* Announcement for screen readers */}
<div aria-live="assertive" aria-atomic="true" className="sr-only">
<div aria-live="assertive" aria-atomic="true" className="sr-admin-note-only">
{liveMessage}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ const NotesRow = ({ notesList }) => {
})}

{/* Announcement for screen readers */}
<tr className="sr-only">
<tr className="sr-admin-note-only">
<td>
<div aria-live="assertive" aria-atomic="true">
{liveMessage}
Expand Down
Empty file.
2 changes: 0 additions & 2 deletions app/assets/javascripts/components/common/list.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,8 @@ const List = ({
if (elements.length === 0) {
let emptyMessage;
if (!loading) {
// eslint-disable-next-line
let noneMessage = none_message;
if (typeof noneMessage === 'undefined' || noneMessage === null) {
// eslint-disable-next-line
noneMessage = I18n.t(`${table_key}.none`);
}
emptyMessage = <span>{noneMessage}</span>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const AdminQuickActions = ({ course, current_user, persistCourse, greetSt
<NoDetailsText />
)}
<button
className="button"
className="button mark-as-review"
onClick={() => {
course.last_reviewed = {
username: current_user.username,
Expand Down
28 changes: 26 additions & 2 deletions app/assets/stylesheets/modules/_admin_note.styl
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,18 @@
top -7px
left -16px

.admin-action-button
width 144px

.admin-notes-modal-trigger
margin-left 5px

.admin-focus-highlight:focus
outline 2px solid #000
outline-offset 2px

//Admin note Icon Styles

.admin-note-edit-icon
background url('../images/pencil.svg') center no-repeat
padding 11px
Expand Down Expand Up @@ -151,8 +158,25 @@
background-size 75%
padding 15px


.sr-only
.icon-notification_admin--badge
background-color #e34444
color #fff
position relative
display block
padding 1px 2px
min-width 22px
max-width 25px
height 20px
left 208px
top -49px
border 1px solid #212121
border-radius 25px
text-align center
font-size 16px
font-weight 400
line-height 1

.sr-admin-note-only
position absolute
width 1px
height 1px
Expand Down
5 changes: 5 additions & 0 deletions app/assets/stylesheets/modules/_overview.styl
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,8 @@
margin 5px 25% 0 25%
padding 5px
background-color $warning-background

.mark-as-review
max-width 146px
max-height 43px
padding 10px
2 changes: 2 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1839,6 +1839,8 @@ en:
button_text: "Admin Notes"
header_text: "Admin Notes Panel"
aria_label:
no_notes_available: "No admin notes are available in the panel. Press the return key to open the Admin Notes Panel."
new_notes_message: "%{count} new admin note%{plural} available."
notes_panel_opened: "The admin notes panel modal has opened. You can press the Escape key to close the panel."
close_admin: "Close the admin note panel modal"
note_action_button: "Notes action button"
Expand Down
Loading