Coursemology/coursemology2

View on GitHub
client/app/bundles/course/discussion/topics/components/cards/TopicCard.tsx

Summary

Maintainability
D
1 day
Test Coverage
import { FC, lazy, Suspense, useEffect, useState } from 'react';
import { defineMessages } from 'react-intl';
import {
  CheckCircleOutline,
  PendingOutlined,
  ScheduleOutlined,
} from '@mui/icons-material';
import { Card, CardContent, CardHeader, Typography } from '@mui/material';
import { CommentStatusTypes, CommentTopicEntity } from 'types/course/comments';

import Link from 'lib/components/core/Link';
import { getCourseUserURL } from 'lib/helpers/url-builders';
import { getCourseId } from 'lib/helpers/url-helpers';
import { useAppDispatch, useAppSelector } from 'lib/hooks/store';
import useTranslation from 'lib/hooks/useTranslation';

import { updatePending, updateRead } from '../../operations';
import { getAllCommentPostMiniEntities } from '../../selectors';

import CodaveriCommentCard from './CodaveriCommentCard';
import CommentCard from './CommentCard';

const CommentField = lazy(
  () =>
    import(
      /* webpackChunkName: "discussionComment" */ '../fields/CommentField'
    ),
);

interface TopicCardProps {
  topic: CommentTopicEntity;
}

const translations = defineMessages({
  byCreator: {
    id: 'course.discussion.topics.TopicCard.byCreator',
    defaultMessage: 'Created by <link>{creatorName}</link>',
  },
  pendingStatus: {
    id: 'course.discussion.topics.TopicCard.pendingStatus',
    defaultMessage: 'Unmark as Pending',
  },
  notPendingStatus: {
    id: 'course.discussion.topics.TopicCard.notPendingStatus',
    defaultMessage: 'Mark as Pending',
  },
  unreadStatus: {
    id: 'course.discussion.topics.TopicCard.unreadStatus',
    defaultMessage: 'Mark as Read',
  },
  loadingStatus: {
    id: 'course.discussion.topics.TopicCard.loading',
    defaultMessage: 'Loading...',
  },
  loadingComment: {
    id: 'course.discussion.topics.TopicCard.loadingComment',
    defaultMessage: 'Loading comment field...',
  },
});

const TopicCard: FC<TopicCardProps> = (props) => {
  const { topic } = props;
  const { t } = useTranslation();

  const dispatch = useAppDispatch();
  const postListData = useAppSelector(getAllCommentPostMiniEntities).filter(
    (post) => post.topicId === topic.id,
  );
  const [status, setStatus] = useState(CommentStatusTypes.loading);

  useEffect(() => {
    if (topic.topicPermissions.canTogglePending) {
      const isPending = topic.topicSettings.isPending;
      const newStatus = isPending
        ? CommentStatusTypes.pending
        : CommentStatusTypes.notPending;
      setStatus(newStatus);
    } else if (topic.topicPermissions.canMarkAsRead) {
      const isUnread = topic.topicSettings.isUnread;
      const newStatus = isUnread
        ? CommentStatusTypes.unread
        : CommentStatusTypes.read;
      setStatus(newStatus);
    }
  }, [topic]);

  if (postListData.length === 0) {
    return null;
  }

  const onClickPending = (id: number): void => {
    if (status !== CommentStatusTypes.loading) {
      const newStatus =
        status === CommentStatusTypes.pending
          ? CommentStatusTypes.notPending
          : CommentStatusTypes.pending;
      setStatus(CommentStatusTypes.loading);
      dispatch(updatePending(id)).then(() => {
        setStatus(newStatus);
      });
    }
  };

  const onClickRead = (id: number): void => {
    if (status !== CommentStatusTypes.loading) {
      const newStatus =
        status === CommentStatusTypes.read
          ? CommentStatusTypes.unread
          : CommentStatusTypes.read;
      setStatus(CommentStatusTypes.loading);
      dispatch(updateRead(id)).then(() => {
        setStatus(newStatus);
      });
    }
  };

  const updateStatus = (): void => {
    if (status === CommentStatusTypes.unread) {
      setStatus(CommentStatusTypes.read);
    } else if (status === CommentStatusTypes.pending) {
      setStatus(CommentStatusTypes.notPending);
    }
  };

  const renderStatus = (): JSX.Element | null => {
    switch (status) {
      case CommentStatusTypes.loading:
        return (
          <div
            style={{
              display: 'flex',
              alignItems: 'center',
              flexWrap: 'wrap',
            }}
          >
            <PendingOutlined />
            {t(translations.loadingStatus)}
          </div>
        );
      case CommentStatusTypes.pending:
        return (
          <Link
            className="clickable"
            id={`mark-as-pending-${topic.id}`}
            onClick={(): void => onClickPending(topic.id)}
            style={{
              cursor: 'pointer',
              display: 'flex',
              alignItems: 'center',
              flexWrap: 'wrap',
            }}
          >
            <ScheduleOutlined />
            {t(translations.pendingStatus)}
          </Link>
        );
      case CommentStatusTypes.notPending:
        return (
          <Link
            className="clickable"
            id={`unmark-as-pending-${topic.id}`}
            onClick={(): void => onClickPending(topic.id)}
            style={{
              cursor: 'pointer',
              display: 'flex',
              alignItems: 'center',
              flexWrap: 'wrap',
            }}
          >
            <CheckCircleOutline />
            {t(translations.notPendingStatus)}
          </Link>
        );
      case CommentStatusTypes.read:
        return null;
      case CommentStatusTypes.unread:
        return (
          <Link
            className="clickable"
            id={`mark-as-read-${topic.id}`}
            onClick={(): void => onClickRead(topic.id)}
            style={{
              cursor: 'pointer',
              display: 'flex',
              alignItems: 'center',
              flexWrap: 'wrap',
            }}
          >
            <CheckCircleOutline />
            {t(translations.unreadStatus)}
          </Link>
        );
      default:
        return null;
    }
  };

  return (
    <Card>
      <CardHeader
        style={{ paddingBottom: '0px' }}
        subheader={
          <div className="space-y-4">
            <Typography variant="body2">
              {t(translations.byCreator, {
                creatorName: topic.creator.name,
                link: (chunk) => (
                  <Link
                    to={getCourseUserURL(getCourseId(), topic.creator.id)}
                    underline="hover"
                  >
                    {chunk}
                  </Link>
                ),
              })}
            </Typography>

            {renderStatus()}
          </div>
        }
        title={
          <Link
            className={`topic-${topic.id}`}
            id={`topic-${topic.id}-${
              topic.timestamp?.toString().replaceAll(':', '-') ?? ''
            }`}
            to={topic.links.titleLink}
            underline="hover"
            variant="h6"
          >
            {topic.timestamp
              ? `${topic.title}: ${topic.timestamp.toString()}`
              : topic.title}
          </Link>
        }
      />
      <CardContent>
        {topic.content && (
          <Typography
            dangerouslySetInnerHTML={{ __html: topic.content }}
            variant="body2"
          />
        )}
        {postListData.map((post) => {
          return (
            <div key={post.id}>
              {post.codaveriFeedback &&
              post.codaveriFeedback.status === 'pending_review' ? (
                <CodaveriCommentCard post={post} />
              ) : (
                <CommentCard post={post} />
              )}
            </div>
          );
        })}
        {/* Dont need to render the comment field when the last post (which is
          the intended post to be shown) is of codaveri feedback type */}
        {!postListData[postListData.length - 1]?.codaveriFeedback && (
          <Suspense
            fallback={
              <div
                style={{
                  marginTop: 10,
                }}
              >
                {t(translations.loadingComment)}
              </div>
            }
          >
            <CommentField topic={topic} updateStatus={updateStatus} />
          </Suspense>
        )}
      </CardContent>
    </Card>
  );
};

export default TopicCard;