Coursemology/coursemology2

View on GitHub
client/app/bundles/course/survey/pages/SurveyResults/TextResponseResults.jsx

Summary

Maintainability
A
2 hrs
Test Coverage
import { Component } from 'react';
import { defineMessages, FormattedMessage } from 'react-intl';
import {
  Button,
  CardContent,
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableRow,
} from '@mui/material';
import PropTypes from 'prop-types';

import Link from 'lib/components/core/Link';

const styles = {
  expandableThreshold: 10,
  expandToggleStyle: {
    display: 'flex',
    justifyContent: 'center',
  },
  wrapText: {
    whiteSpace: 'normal',
    wordWrap: 'break-word',
  },
};

const translations = defineMessages({
  serial: {
    id: 'course.survey.TextResponseResults.serial',
    defaultMessage: 'S/N',
  },
  respondent: {
    id: 'course.survey.TextResponseResults.respondent',
    defaultMessage: 'Respondent',
  },
  responses: {
    id: 'course.survey.TextResponseResults.responses',
    defaultMessage: 'Responses',
  },
  showResponses: {
    id: 'course.survey.TextResponseResults.showResponses',
    defaultMessage:
      'Show Responses ({quantity}/{total} responded{phantoms, plural, \
      =0 {} one {, {phantoms} Phantom} other {, {phantoms} Phantoms}})',
  },
  hideResponses: {
    id: 'course.survey.TextResponseResults.hideResponses',
    defaultMessage: 'Hide Responses',
  },
  phantomStudentName: {
    id: 'course.survey.TextResponseResults.phantomStudentName',
    defaultMessage: '{name} (Phantom)',
  },
});

class TextResponseResults extends Component {
  static renderStudentName(answer) {
    return (
      <Link to={answer.response_path}>
        {answer.phantom ? (
          <FormattedMessage
            {...translations.phantomStudentName}
            values={{ name: answer.course_user_name }}
          />
        ) : (
          answer.course_user_name
        )}
      </Link>
    );
  }

  static renderTextResultsTable(answers, anonymous) {
    return (
      <Table>
        <TableHead>
          <TableRow>
            <TableCell colSpan={2}>
              <FormattedMessage {...translations.serial} />
            </TableCell>
            {anonymous ? null : (
              <TableCell colSpan={5}>
                <FormattedMessage {...translations.respondent} />
              </TableCell>
            )}
            <TableCell colSpan={15}>
              <FormattedMessage {...translations.responses} />
            </TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {answers.map((answer, index) => (
            <TableRow key={answer.id}>
              <TableCell colSpan={2}>{index + 1}</TableCell>
              {anonymous ? null : (
                <TableCell colSpan={5} style={styles.wrapText}>
                  {TextResponseResults.renderStudentName(answer)}
                </TableCell>
              )}
              <TableCell colSpan={15} style={styles.wrapText}>
                {answer.text_response}
              </TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    );
  }

  constructor(props) {
    super(props);

    const expandable = props.answers.length > styles.expandableThreshold;
    this.state = {
      expandable,
      expanded: !expandable,
    };
  }

  renderExpandToggle(values) {
    if (!this.state.expandable) {
      return null;
    }
    const labelTranslation = this.state.expanded
      ? 'hideResponses'
      : 'showResponses';
    return (
      <CardContent style={styles.expandToggleStyle}>
        <Button
          onClick={() =>
            this.setState((state) => ({ expanded: !state.expanded }))
          }
          variant="outlined"
        >
          <FormattedMessage
            {...translations[labelTranslation]}
            values={values}
          />
        </Button>
      </CardContent>
    );
  }

  render() {
    const { includePhantoms, answers, anonymous } = this.props;
    const filteredAnswers = includePhantoms
      ? answers
      : answers.filter((answer) => !answer.phantom);
    const nonEmptyAnswers = filteredAnswers.filter(
      (answer) =>
        answer.text_response && answer.text_response.trim().length > 0,
    );
    const validPhantomResponses = includePhantoms
      ? nonEmptyAnswers.filter((answer) => answer.phantom)
      : [];
    const toggle = this.renderExpandToggle({
      total: filteredAnswers.length,
      quantity: nonEmptyAnswers.length,
      phantoms: validPhantomResponses.length,
    });

    return (
      <>
        {toggle}
        {this.state.expanded &&
          TextResponseResults.renderTextResultsTable(
            nonEmptyAnswers,
            anonymous,
          )}
        {this.state.expanded && toggle}
      </>
    );
  }
}

TextResponseResults.propTypes = {
  includePhantoms: PropTypes.bool.isRequired,
  anonymous: PropTypes.bool.isRequired,
  answers: PropTypes.arrayOf(
    PropTypes.shape({
      id: PropTypes.number,
      course_user_id: PropTypes.number,
      course_user_name: PropTypes.string,
      phantom: PropTypes.bool,
      question_option_ids: PropTypes.arrayOf(PropTypes.number),
    }),
  ),
};

export default TextResponseResults;