SyncM8/syncm8

View on GitHub
client/src/pages/Mate/MatePage.tsx

Summary

Maintainability
A
2 hrs
Test Coverage
/* eslint-disable no-plusplus */
import {
  ExclamationCircleOutlined,
  PlusCircleOutlined,
} from "@ant-design/icons";
import {
  Button,
  Col,
  DatePicker,
  Form,
  Input,
  Modal,
  Row,
  Select,
  Timeline,
  Typography,
} from "antd";
import moment from "moment";
import React, { useState } from "react";

import SyncCard from "../../components/SyncCard/SyncCard";
import { SyncStatusEnum, SyncType } from "../types";
import { initialSyncs, mateName, newSync } from "./mockData";

type MatePageState = {
  isModalVisible: boolean;
  syncs: SyncType[];
  editingSync: SyncType;
};

enum SyncModalEnum {
  TITLE = "Title",
  DATE = "Date",
  DETAILS = "Details",
  SYNC_STATUS = "SyncStatus",
}

let id = newSync.id + 1; // making sure subsequent id's are unique

const { confirm } = Modal;
const { Title } = Typography;

/**
 * MatePage
 * @returns
 */
const MatePage = (): JSX.Element => {
  const [state, setState] = useState<MatePageState>({
    syncs: initialSyncs,
    isModalVisible: false,
    editingSync: newSync,
  });
  const { syncs, isModalVisible, editingSync } = state;

  /**
   * Removes specified sync
   * @param sync
   */
  const removeSync = (sync: SyncType) => {
    confirm({
      title: `Are you sure you want to delete this sync: ${sync.title}?`,
      icon: <ExclamationCircleOutlined />,
      content: "This operation cannot be reversed",
      okText: "Delete",
      okType: "danger",
      cancelText: "No",
      onOk() {
        // TODO server
        setState({
          ...state,
          syncs: state.syncs.filter((s) => s.id !== sync.id),
        });
      },
    });
  };

  /**
   * Open modal to edit specified sync
   * @param sync to be edited
   */
  const openEditSyncModal = (sync: SyncType) => {
    setState({ ...state, isModalVisible: true, editingSync: { ...sync } });
  };

  /**
   * Add a new sync then edit
   */
  const addSync = () => {
    openEditSyncModal({
      id: id++,
      title: "",
      details: "",
      ts: new Date(),
      syncStatus: SyncStatusEnum.COMPLETED,
    });
  };

  /**
   * Handle save on editing sync
   * TODO: save on server
   */
  const handleSaveEditSync = () => {
    const newSyncs = [
      ...syncs.filter((sync) => sync.id !== editingSync.id),
      editingSync,
    ];
    setState({ ...state, syncs: newSyncs, isModalVisible: false });
  };

  /**
   * Turn off editing modal
   */
  const closeEditModal = () => {
    setState({ ...state, isModalVisible: false });
  };

  /**
   * Handle changing form values on editing sync
   * @param label of sync to change
   * @param value new value to replace
   */
  const changeFormValue = (label: SyncModalEnum, value: string) => {
    let newEditingSync = { ...editingSync };
    switch (label) {
      case SyncModalEnum.TITLE:
        newEditingSync = { ...editingSync, title: value };
        break;
      case SyncModalEnum.DATE:
        newEditingSync = { ...editingSync, ts: new Date(value) };
        break;
      case SyncModalEnum.DETAILS:
        newEditingSync = { ...editingSync, details: value };
        break;
      case SyncModalEnum.SYNC_STATUS: {
        const syncStatus = value as SyncStatusEnum;
        if (Object.values(SyncStatusEnum).indexOf(syncStatus) < 0) {
          console.error("Unknown sync status value"); // eslint-disable-line no-console
        }
        newEditingSync = { ...editingSync, syncStatus };
        break;
      }
      default:
        break;
    }
    setState({ ...state, editingSync: newEditingSync });
  };

  const ModalForm = (
    <Modal
      visible={isModalVisible}
      onOk={handleSaveEditSync}
      onCancel={closeEditModal}
      okText="Save"
    >
      <Form layout="vertical">
        <Form.Item label="Title">
          <Input
            value={editingSync.title}
            placeholder="New Title"
            onChange={(e) =>
              changeFormValue(SyncModalEnum.TITLE, e.target.value)
            }
            required
          />
        </Form.Item>
        <Form.Item label="Date">
          <DatePicker
            value={moment.utc(editingSync.ts)} // moment defaults to local time, while JS's Date is UTC
            onChange={(date, dateString) =>
              changeFormValue(SyncModalEnum.DATE, dateString)
            }
          />
        </Form.Item>
        <Form.Item label="Details">
          <Input.TextArea
            autoSize
            value={editingSync.details}
            placeholder="Add details here..."
            onChange={(e) =>
              changeFormValue(SyncModalEnum.DETAILS, e.target.value)
            }
          />
        </Form.Item>
        <Form.Item label="Sync Status">
          <Select
            value={editingSync.syncStatus}
            onChange={(value) =>
              changeFormValue(SyncModalEnum.SYNC_STATUS, value)
            }
          >
            {Object.entries(SyncStatusEnum).map(([syncStatus, value]) => (
              <Select.Option key={syncStatus} value={value}>
                {syncStatus}
              </Select.Option>
            ))}
          </Select>
        </Form.Item>
      </Form>
    </Modal>
  );

  return (
    <>
      {ModalForm}
      <Row>
        <Col span={18}>
          <div
            style={{
              backgroundColor: "#F0F2F5",
              height: "200px",
              padding: "20px",
            }}
          >
            <Title level={5}>M8</Title>
            <Title>{mateName}</Title>
            <Title level={5}>Syncs: Weekly</Title>
          </div>
          <div style={{ backgroundColor: "#F7F8FC", padding: "20px" }}>
            <Row gutter={[24, 24]}>
              {syncs
                .sort((a, b) => b.ts.valueOf() - a.ts.valueOf())
                .map((sync) => (
                  <Col span={6} key={sync.id}>
                    <SyncCard
                      sync={sync}
                      editSync={openEditSyncModal}
                      removeSync={removeSync}
                    />
                  </Col>
                ))}
            </Row>
          </div>
          <div style={{ position: "sticky", bottom: "0", right: "0" }}>
            <Button
              type="primary"
              size="large"
              icon={<PlusCircleOutlined />}
              onClick={addSync}
            >
              Add Sync
            </Button>
          </div>
        </Col>
        <Col
          span={6}
          className="timeline-sidebar"
          style={{ height: "inherit" }}
        >
          <Row justify="center">
            <Col>
              <Title level={2}>Timeline</Title>
            </Col>
          </Row>
          <Row justify="center">
            <Col span={20}>
              <Timeline mode="left" reverse>
                {syncs
                  .sort((a, b) => a.ts.valueOf() - b.ts.valueOf())
                  .map((sync) => (
                    <Timeline.Item
                      key={sync.id}
                      label={sync.ts.toISOString().split("T")[0]}
                    >
                      {sync.title}
                    </Timeline.Item>
                  ))}
              </Timeline>
            </Col>
          </Row>
        </Col>
      </Row>
    </>
  );
};
export default MatePage;