Calculate the number of occurrences of class hours at any time and judge the conflict situation

background

 

The whole is composed of four parts, registration time, registration cycle, class time, class cycle 

By selecting the registration time, registration cycle, and class time, the total training class hours in the training cycle are calculated, and when the class time conflicts, a reminder is given.

need:

 

  • Registration time (date + hour and minute), dates before today are not available
  • Training period (date), the date before the registration end time is not optional, if the registration time is not selected, the date before today is not optional
  • Class time: every Monday to every Sunday, time: 24 hours optional, you can add more than one class time, one class time is one class
  • The registration start time must be greater than the current time
  • The start time of the training cycle must be greater than the registration time
  • The class time cannot conflict, and the start time or end time of any two classes is equal to the class conflict.
  • Calculate the total class hours within the range of the training period.

design thinking

After selecting the training period, calculate the number of occurrences in each week within the training period, then traverse the class time list, use the day of the week in the list as the key to match, when the matching is successful, add the number of occurrences of the week to the total During class.

Judgment of time conflicts, by converting time into numbers, comparing numbers by size, and judging whether there are numbers that are equal or overlapped by double loops.

interface construction

Custom Component Packaging

The class time is composed of a selection box and a time selection box, so we only need to encapsulate these two components to form a new selectTime component.

SelectTime 

import { Button, Form, Input, Select, TimePicker } from 'antd';
import moment from 'moment';
import React, { useEffect, useState } from 'react';
const { Option } = Select;

const SelectTime = ({ value = {}, onChange, }) => {
  const options = [{ value: 1, label: "每周一" }, { value: 2, label: "每周二" }, { value: 3, label: "每周三" }, { value: 4, label: "每周四" }, { value: 5, label: "每周五" }, { value: 6, label: "每周六" }, { value: 7, label: "每周日" }]
  const [week, setWeek] = useState();
  const [timeRange, setTimeRange] = useState([]);

  const triggerChange = (changedValue) => {
    onChange?.({
      week,
      timeRange,
      ...value,
      ...changedValue,
    });
  };
  const onNumberChange = (value) => {
    setWeek(value);
    triggerChange({
      week: value,
    });
  };
  const onCurrencyChange = (newTimeRange) => {
    setTimeRange(newTimeRange);
    triggerChange({
      timeRange: newTimeRange,
    });
  };
  return (
    <>
      <Select
        style={
   
   {
          width: 100,
          margin: '0 8px',
        }}
        options={options}
        allowClear={true}
        placeholder={"每周几"}
        onChange={onNumberChange}
        value={value.week || week}
      >

      </Select>
      <TimePicker.RangePicker format={"HH:mm"} onChange={onCurrencyChange} value={value.timeRange || timeRange} />
    </>
  )
}
export default SelectTime;

Multiple additions of components can be achieved by placing the SelectTime  component inside the Form.List. When adding to the second piece of data, since there is no label, the style needs to be adjusted, so formItemLayoutWithOutLabel is introduced.

formItemLayoutWithOutLabel 

  const formItemLayoutWithOutLabel = {
    wrapperCol: {
      xs: {
        span: 24,
        offset: 0,
      },
      sm: {
        span: 20,
        offset: 6,
      },
    },
  };

 formItemLayout 

  const formItemLayout = {
    labelCol: {
      xs: {
        span: 24,
      },
      sm: {
        span: 6,
      },
    },
    wrapperCol: {
      xs: {
        span: 24,
      },
      sm: {
        span: 20,
      },
    },
  };

registration time 

 <FormItem name={"signRange"} label={"报名时间"} rules={[{ required: true }]}>
              <RangePicker
                disabledDate={disabledDate}
                // disabledTime={disabledRangeTime}
                showTime={
   
   {
                  format: 'HH:mm',
                }}
                format="YYYY-MM-DD HH:mm"
                onChange={getEndDate}
              />
            </FormItem>

training cycle 

<FormItem name={"trainRange"} label={"培训周期"} rules={[{ required: true }]}>
              <RangePicker disabledDate={disabledDate2} onChange={(value, dateString) => { isIncludeWeeks(dateString[0], dateString[1]); }} />
            </FormItem>

 class time 

 <Form.List
              name="trainingTime"
              initialValue={['1']}
              rules={[
                {
                  validator: async (_, trainingTime) => {
                    if (!trainingTime || trainingTime.length < 1) {
                      return Promise.reject(new Error('至少一条数据'));
                    }
                  },
                },
              ]}
            >
              {(fields, { add, remove }, { errors }) => (
                <>
                  {fields.map((field, index) => (
                    <Form.Item
                      label={index === 0 ? '上课时间' : ''}
                      {...(index === 0 ? formItemLayout : formItemLayoutWithOutLabel)}
                      rules={[{ required: true, message: "请输入上课时间" }]}
                      key={field.key}
                      name={[field.name, 'timeRange']}
                      validateTrigger={['onChange', 'onBlur']}

                    >
                      <Form.Item
                        {...field}
                        validateTrigger={['onChange', 'onBlur']}
                        rules={[
                          {
                            required: true,
                            message: "请输入上课时间",
                          },
                        ]}
                        noStyle
                      >
                        <SelectTime onChange={() => { onChangetime(); }}></SelectTime>
                      </Form.Item>
                      <PlusCircleOutlined style={
   
   { margin: "0 10px" }} onClick={() => add()} />
                      {fields.length > 1 ? (
                        <MinusCircleOutlined
                          className="dynamic-delete-button"
                          onClick={() => {
                            remove(field.name);
                            if (form.getFieldValue('trainRange') !== undefined) {
                              isIncludeWeeks(form.getFieldValue('trainRange')[0], form.getFieldValue('trainRange')[1]);
                            }
                          }}
                        />
                      ) : null}
                    </Form.Item>
                  ))}
                </>
              )}
            </Form.List>

Total hours of training 

 <FormItem name={"totalTrainingHours"} label={"培训总课时"} rules={[{ required: true }]}>
              <Input readOnly style={
   
   { border: 'none', boxShadow: "none" }}></Input>
            </FormItem>

function realization

Registration time and training time limit

Add registration time limit - disabledDate

  const disabledDate = (current) => {
    var time = current.clone();
    return time.startOf('day') < moment().startOf('day');
  };

Add training cycle time limit - disabledDate2

Pay attention to special circumstances: when the registration time is not selected, the limit range is the previous day

  const disabledDate2 = (current) => {
    if (form.getFieldValue('signRange') !== undefined && endDate !== null) {
      return current.format('YYYY-MM-DD') < endDate.format('YYYY-MM-DD');
    } else {
      return current < moment().subtract(1, "days");
    }
  };

When the registration time is selected, the optional time range of the training cycle is limited, because we need to add an onchange event to the registration time - getEndDate

  const getEndDate = () => {
    if (form.getFieldValue('signRange') !== null) {
      endDate = form.getFieldValue('signRange')[1].clone();
      endDate = endDate.add(1, "days")
    }
  }

The total training hours are related to the class time and training time, so when we choose the corresponding training cycle, we should calculate the number of occurrences from Monday to Sunday during this period. Add onchange event - isIncludeWeeks

Put the week of the training period into weeksArr (1 for Monday, ...., 7 for Sunday)

  const isIncludeWeeks = (startDate, endDate) => {
    let beginDateObj = new Date(startDate);
    let endDateObj = new Date(endDate);
    let weeksArr = [];
    while (beginDateObj <= endDateObj) {
      //直到结束日期,跳出循环
      let todayWeekNum = beginDateObj.getDay();
      if (todayWeekNum == 0) {
        //如果为0,则对应“周日”
        todayWeekNum = 7;
      }
      weeksArr.push(todayWeekNum);
      beginDateObj.setTime(beginDateObj.getTime() + 24 * 60 * 60 * 1000); //每次递增1天
    }
    console.log(getEleNums(weeksArr));
    weekNum = getEleNums(weeksArr);
    // setWeekNum(getEleNums(weeksArr));
    canClassTotalTime();
  }

Count the number of occurrences from Monday to Sunday

  const getEleNums = (data) => {
    var map = {}
    for (var i = 0; i < data.length; i++) {
      var key = data[i]
      if (map[key]) {
        map[key] += 1
      } else {
        map[key] = 1
      }
    }
    return map;
  }

When the class time is selected, the number of occurrences of the class time in the cycle is calculated, that is, the number of occurrences of the week. Therefore, the calculation of class hours only needs to consider the corresponding day of the week, and the time is used to judge whether the class time conflicts. Add onchange event for class time - onChangetime

For several special cases, it is excluded by if judgment to avoid error reporting.

  • Did not select the day of the week, only selected the class time
  • No class time was selected, only the day of the week was selected
  • No training cycle selected
  // 当选择上课时间时 计算总课时以及判断冲突
  const onChangetime = () => {
    // canClassTotalTime();
    if (form.getFieldValue('trainRange') !== undefined) {
      isIncludeWeeks(form.getFieldValue('trainRange')[0], form.getFieldValue('trainRange')[1]);
    }
    let rangetime = [];
    console.log(form.getFieldValue('trainingTime'));
    if (form.getFieldValue('trainingTime') !== undefined) {
      rangetime = form.getFieldValue('trainingTime').map((item) => {
        if (item.week !== undefined && item.timeRange[0] !== undefined) {
          return {
            week: item.week,
            timeRange: [item.timeRange[0].format('HH:mm'), item.timeRange[1].format('HH:mm')]
          }
        }
      }).filter(item => item !== undefined)
      judgmentTimeConflict(rangetime);
    }
  }

canClassTotalTime 

  const canClassTotalTime = () => {
    let total = 0;
    form.getFieldValue('trainingTime')
    console.log(form.getFieldValue('trainingTime'), weekNum);
    if (form.getFieldValue('trainRange') !== undefined) {
      form.getFieldValue('trainingTime').map((item) => {
        if (item.week !== undefined && weekNum[item.week] !== undefined)
          total += weekNum[item.week];
      })
    }
    console.log('total', total);
    form.setFieldsValue({ totalTrainingHours: total })
  }

Registration time conflict judgment - judgmentTimeConflict

By converting the time into a string and then into a number type, the size comparison of the number type is used to complete the judgment of the range conflict.

Classified discussion of several conflict situations (range A[0]-A[1], range B[0]-B[1])

  • A[0]<A[1]=B[0]<B[1]
  • A[0]<B[0]<A[1]<B[1]
  • A[0]<B[0]<A[1]=B[1]
  • A[0]<B[0]<B[1]<A[1]
  • A[0]=B[0]<B[1]<A[1]
  • B[0]<A[0]<B[1]<A[1]
  • A[0]<B[0]<A[1]=B[0]
  • B[0]<A[0]<A[1]<B[1]

judgmentTimeConflict 

  // 判断时间是否冲突
  const judgmentTimeConflict = (timeRangeList) => {
    let newrange = timeRangeList.map((item) => {
      return {
        week: item.week,
        timeRange: [parseInt(item.timeRange[0].replace(':', '')), parseInt(item.timeRange[1].replace(':', ''))]
      }
    })
    for (let i = 0; i < newrange.length; i++) {
      for (let j = i; j < newrange.length; j++) {
        if (i !== j && newrange[i].week === newrange[j].week) {
          if ((newrange[i].timeRange[1] === newrange[j].timeRange[0]) || (newrange[j].timeRange[1] === newrange[i].timeRange[0])) {
            message.error("上课时间冲突,请重新选择!")
            return false;
          } else if ((newrange[i].timeRange[0] < newrange[j].timeRange[0]) && (newrange[j].timeRange[0] < newrange[i].timeRange[1])) {
            message.error("上课时间冲突,请重新选择!")
            return false;
          } else if ((newrange[i].timeRange[0] < newrange[j].timeRange[1]) && (newrange[j].timeRange[1] < newrange[i].timeRange[1])) {
            message.error("上课时间冲突,请重新选择!")
            return false;
          } else if ((newrange[i].timeRange[0] === newrange[j].timeRange[0]) || (newrange[i].timeRange[1] === newrange[j].timeRange[1])) {
            message.error("上课时间冲突,请重新选择!")
            return false;
          }else if((newrange[i].timeRange[0] > newrange[j].timeRange[0]) && (newrange[i].timeRange[1] < newrange[j].timeRange[1])){
            message.error("上课时间冲突,请重新选择!")
            return false;
          }
        }
      }
    }
  }

Show results

 

Guess you like

Origin blog.csdn.net/weixin_45369856/article/details/130401415