WikiEducationFoundation/WikiEduDashboard

View on GitHub
app/assets/javascripts/components/admin_notes/notes_row.jsx

Summary

Maintainability
C
7 hrs
Test Coverage
F
1%
import React, { useState, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { deleteAdminNoteFromList, saveUpdatedAdminCourseNote } from '../../actions/admin_course_notes_action';
import { initiateConfirm } from '../../actions/confirm_actions';
import { format, toDate, parseISO } from 'date-fns';
import TextAreaInput from '../common/text_area_input';

const NotesRow = ({ notesList }) => {
  // State variables to keep track of the currently edited note and the note with expanded text
  const [editNoteId, setEditNoteId] = useState(null);
  const [showNoteTextId, setShowNoteTextId] = useState(null);
  const [noteText, setNoteText] = useState('');
  const [noteTitle, setNoteTitle] = useState('');

  // State for the live message when the admin panel modal opens
  const [liveMessage, setLiveMessage] = useState('');

  const dispatch = useDispatch();

  // Function to handle note deletion
  const deleteNote = (noteId) => {
    const onConfirmDelete = () => {
      dispatch(deleteAdminNoteFromList(noteId));
    };
    const confirmMessage = I18n.t('notes.delete_note_confirmation');
    dispatch(initiateConfirm({ confirmMessage, onConfirm: onConfirmDelete }));
  };

  // Function to update the note text
  const updateNoteText = (_, value) => {
    setNoteText(value);
  };

  // Function to update the note title
  const updateNoteTitle = (_, value) => {
    setNoteTitle(value);
  };

  // Function to handle clicking on a table row
  const onNotesTableRowClickHandler = (noteId) => {
    if (noteId !== showNoteTextId) {
      setShowNoteTextId(noteId);
      setEditNoteId(null);
    } else {
      setShowNoteTextId(null);
      setEditNoteId(null);
    }
  };

  // Function to handle clicking on the edit note button
  const onNotesEditButtonClickHandler = (noteId, title, text) => {
    setEditNoteId(noteId);
    setShowNoteTextId(noteId);
    setNoteTitle(title);
    setNoteText(text);
  };

  // Function to handle clicking on the save note button
  const onNotesEditSaveButtonClickHandler = (noteId) => {
    dispatch(saveUpdatedAdminCourseNote({ id: noteId, title: noteTitle, text: noteText }));
    setEditNoteId(null);
  };

  // Function to handle clicking on the cancel note button
  const onNotesEditCancelButtonClickHandler = () => {
     setEditNoteId(null);
  };

  // Handles Enter key to toggle note expansion in the table row
  const handleKeyDown = (event, noteId) => {
    if (event.key === 'Enter' && event.target.tagName === 'TR') {
      event.preventDefault(); // Prevent the default action to avoid unintended scrolling
      onNotesTableRowClickHandler(noteId);
    }
  };

  useEffect(() => {
    if (editNoteId) {
      setLiveMessage(I18n.t('notes.admin.aria_label.note_edit_mode', { title: noteTitle }));
    }
  }, [editNoteId]);

  // Render a table row for each note
  return (
    <>
      {notesList.map((note) => {
        const isEditing = note.id === editNoteId;
        const isShowingText = note.id === showNoteTextId;
        const rowClassName = isShowingText ? 'row--border' : '';

        // Render the edit button or cancel button based on the editing state
        const notesEditButton = !isEditing ? (
          <button
            className="tooltip-trigger admin-focus-highlight"
            aria-label={I18n.t('notes.admin.aria_label.note_edit_button_focused', { title: note.title })}
            onClick={() => onNotesEditButtonClickHandler(note.id, note.title, note.text)}
          >
            <span className="icon admin-note-edit-icon"/>
            <span className="tooltip new">
              <p>{I18n.t('notes.edit_note')}</p>
            </span>
          </button>
        ) : (
          <button
            className="tooltip-trigger cancel--note admin-focus-highlight"
            aria-label={I18n.t('notes.admin.aria_label.note_edit_cancel_button_focused', { title: note.title })}
            onClick={() => onNotesEditCancelButtonClickHandler()}
          >
            <span className="icon admin-note-cancel-icon" />
            <div className="tooltip cancel--note">
              <p>{I18n.t('notes.cancel_edit_note')}</p>
            </div>
          </button>
        );

        // Render the save button
        const notesEditSaveButton = (
          <button
            className="tooltip-trigger post--note admin-focus-highlight"
            onClick={() => onNotesEditSaveButtonClickHandler(note.id)}
            aria-label={I18n.t('notes.admin.aria_label.note_edit_save_button_focused', { title: noteTitle })}
          >
            <span className="icon admin-note-post-icon" />
            <div className="tooltip post--note">
              <p>{I18n.t('notes.save_note')}</p>
            </div>
          </button>
        );

        return (
          <React.Fragment key={note.id}>
            <tr
              className="students table__admin-note__row"
              onClick={() => onNotesTableRowClickHandler(note.id)}
              onKeyDown={e => handleKeyDown(e, note.id)}
              tabIndex="0"
              aria-label={isShowingText
                ? I18n.t('notes.admin.aria_label.collapse_note', { title: note.title })
                : I18n.t('notes.admin.aria_label.expand_note', { title: note.title })}
            >
              <td className={rowClassName} onClick={e => (isEditing ? e.stopPropagation() : null)}>
                <TextAreaInput
                  onChange={updateNoteTitle}
                  value={isEditing ? noteTitle : note.title}
                  placeholder={'Note Title'}
                  valueKey={'key'}
                  editable={isEditing}
                  markdown
                  autoExpand
                />
              </td>
              <td className={rowClassName}>{note.edited_by}</td>
              <td className={rowClassName}>
                {format(toDate(parseISO(note.updated_at)), 'PPPP p')}
              </td>
              <td className={`${rowClassName} admin-note__edit`} onClick={e => e.stopPropagation()}>
                {notesEditButton}
              </td>
              <td className={`${rowClassName} admin-note__delete`} onClick={e => e.stopPropagation()}>
                {!isEditing ? (
                  <button
                    className="tooltip-trigger admin-focus-highlight"
                    aria-label={I18n.t('notes.admin.aria_label.note_delete_button_focused', { title: note.title })}
                    onClick={() => deleteNote(note.id)}
                  >
                    <span className="icon admin-note-delete-icon" />
                    <span className="tooltip new">
                      <p>{I18n.t('notes.delete_note')}</p>
                    </span>
                  </button>
                ) : (
                  notesEditSaveButton
                )}
              </td>
            </tr>
            {isShowingText && (
              <tr
                className="table__admin-note__th"
                tabIndex="0"
                aria-label={I18n.t('notes.admin.aria_label.note_details_focused', { title: note.title })}
              >
                <th colSpan="5" tabIndex="0">
                  <TextAreaInput
                    onChange={updateNoteText}
                    value={isEditing ? noteText : note.text}
                    placeholder={'Note Text'}
                    valueKey={'key'}
                    editable={isEditing}
                    markdown
                    autoExpand
                  />
                </th>
              </tr>
            )}
          </React.Fragment>
        );
      })}

      {/* Announcement for screen readers */}
      <tr className="sr-only">
        <td>
          <div aria-live="assertive" aria-atomic="true">
            {liveMessage}
          </div>
        </td>
      </tr>
    </>
  );
};

export default NotesRow;