ahbeng/NUSMods

View on GitHub
website/src/views/planner/CustomModuleForm.tsx

Summary

Maintainability
B
4 hrs
Test Coverage
import * as React from 'react';
import { connect } from 'react-redux';

import { CustomModule } from 'types/reducers';
import { Module, ModuleCode } from 'types/modules';
import { State as StoreState } from 'types/state';

import Tooltip from 'views/components/Tooltip/Tooltip';
import { addCustomModule } from 'actions/planner';
import { getModuleCredit, getModuleTitle } from 'utils/planner';
import styles from './CustomModuleForm.scss';

type OwnProps = Readonly<{
  moduleCode: ModuleCode;
  onFinishEditing: () => void;
}>;

type Props = OwnProps &
  Readonly<{
    customInfo: CustomModule | null;
    moduleInfo: Module | null;
    addCustomModule: (moduleCode: ModuleCode, data: CustomModule) => void;
  }>;

export const CustomModuleFormComponent: React.FC<Props> = (props) => {
  // We use an uncontrolled form here because we don't want to update the
  // module title and MCs live
  const inputModuleCredit = React.createRef<HTMLInputElement>();
  const inputTitle = React.createRef<HTMLInputElement>();

  const onSubmit = (evt: React.FormEvent<HTMLFormElement>) => {
    evt.preventDefault();

    const inputModuleCreditCurrent = inputModuleCredit.current;
    const moduleCredit = inputModuleCreditCurrent && inputModuleCreditCurrent.value;
    const inputTitleCurrent = inputTitle.current;
    const title = inputTitleCurrent && inputTitleCurrent.value;

    // Module credit is required, module credit cannot be negative
    if (moduleCredit == null || parseInt(moduleCredit, 10) < 0) return;

    props.addCustomModule(props.moduleCode, {
      moduleCredit: +moduleCredit,
      title,
    });

    props.onFinishEditing();
  };

  const resetCustomInfo = () => {
    const { moduleInfo } = props;
    if (!moduleInfo) return;

    // We don't use props.addCustomModule because we don't want to save the reset
    // immediately in case the user wants to cancel
    if (inputModuleCredit.current) {
      inputModuleCredit.current.value = moduleInfo.moduleCredit;
    }

    if (inputTitle.current) {
      inputTitle.current.value = moduleInfo.title;
    }
  };

  const { moduleCode, moduleInfo, customInfo } = props;

  const plannerModule = { moduleCode, customInfo, moduleInfo };
  const moduleCredit = getModuleCredit(plannerModule);
  const title = getModuleTitle(plannerModule);

  return (
    <form onSubmit={onSubmit}>
      <h3 className={styles.heading}>Edit info for {moduleCode}</h3>

      <div className="form-row">
        <div className="col-md-3">
          <label htmlFor="input-mc">Units</label>
          <input
            ref={inputModuleCredit}
            id="input-mc"
            type="number"
            className="form-control"
            defaultValue={moduleCredit ? String(moduleCredit) : ''}
            required
            min="0"
          />
        </div>
        <div className="col-md-9">
          <label htmlFor="input-title">Title (optional)</label>
          <input
            ref={inputTitle}
            id="input-title"
            type="text"
            className="form-control"
            defaultValue={title || ''}
          />
        </div>
      </div>

      <div className={styles.formAction}>
        <div>
          <button type="submit" className="btn btn-primary">
            Save
          </button>
          <button type="button" className="btn btn-link" onClick={props.onFinishEditing}>
            Cancel
          </button>
        </div>

        {moduleInfo && (
          <Tooltip
            content={`Reset title to "${moduleInfo.title}" and credits to ${moduleInfo.moduleCredit}`}
          >
            <button type="button" className="btn btn-secondary" onClick={resetCustomInfo}>
              Reset Info
            </button>
          </Tooltip>
        )}
      </div>
    </form>
  );
};

const CustomModuleForm = connect(
  (state: StoreState, ownProps: OwnProps) => ({
    customInfo: state.planner.custom[ownProps.moduleCode],
    moduleInfo: state.moduleBank.modules[ownProps.moduleCode],
  }),
  {
    addCustomModule,
  },
)(React.memo(CustomModuleFormComponent));

export default CustomModuleForm;