github.com/minio/console@v1.4.1/web-app/src/screens/Console/Common/FormComponents/DaysSelector/DaysSelector.tsx (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2021 MinIO, Inc.
     3  //
     4  // This program is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Affero General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // This program is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  // GNU Affero General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Affero General Public License
    15  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16  
    17  import React, { useEffect, useState } from "react";
    18  import { DateTime } from "luxon";
    19  import { Box, InputBox, InputLabel, LinkIcon } from "mds";
    20  
    21  const DAY_SECONDS = 86400;
    22  const HOUR_SECONDS = 3600;
    23  const HOUR_MINUTES = 60;
    24  
    25  interface IDaysSelector {
    26    id: string;
    27    initialDate: Date;
    28    maxSeconds: number;
    29    label: string;
    30    entity: string;
    31    onChange: (newDate: string, isValid: boolean) => void;
    32  }
    33  
    34  const calculateNewTime = (
    35    initialDate: Date,
    36    days: number,
    37    hours: number,
    38    minutes: number,
    39  ) => {
    40    return DateTime.fromJSDate(initialDate).plus({
    41      hours: hours + days * 24,
    42      minutes,
    43    }); // Lump days into hours to avoid daylight savings causing issues
    44  };
    45  
    46  const DaysSelector = ({
    47    id,
    48    initialDate,
    49    label,
    50    maxSeconds,
    51    entity,
    52    onChange,
    53  }: IDaysSelector) => {
    54    const maxDays = Math.floor(maxSeconds / DAY_SECONDS);
    55    const maxHours = Math.floor((maxSeconds % DAY_SECONDS) / HOUR_SECONDS);
    56    const maxMinutes = Math.floor((maxSeconds % HOUR_SECONDS) / HOUR_MINUTES);
    57  
    58    const [selectedDays, setSelectedDays] = useState<number>(0);
    59    const [selectedHours, setSelectedHours] = useState<number>(0);
    60    const [selectedMinutes, setSelectedMinutes] = useState<number>(0);
    61    const [validDate, setValidDate] = useState<boolean>(true);
    62    const [dateSelected, setDateSelected] = useState<DateTime>(DateTime.now());
    63  
    64    // Set initial values
    65    useEffect(() => {
    66      setSelectedDays(maxDays);
    67      setSelectedHours(maxHours);
    68      setSelectedMinutes(maxMinutes);
    69    }, [maxDays, maxHours, maxMinutes]);
    70  
    71    useEffect(() => {
    72      if (
    73        !isNaN(selectedHours) &&
    74        !isNaN(selectedDays) &&
    75        !isNaN(selectedMinutes)
    76      ) {
    77        setDateSelected(
    78          calculateNewTime(
    79            initialDate,
    80            selectedDays,
    81            selectedHours,
    82            selectedMinutes,
    83          ),
    84        );
    85      }
    86    }, [initialDate, selectedDays, selectedHours, selectedMinutes]);
    87  
    88    useEffect(() => {
    89      if (validDate) {
    90        const formattedDate = dateSelected.toFormat("yyyy-MM-dd HH:mm:ss");
    91        onChange(formattedDate.split(" ").join("T"), true);
    92      } else {
    93        onChange("0000-00-00", false);
    94      }
    95    }, [dateSelected, onChange, validDate]);
    96  
    97    // Basic validation for inputs
    98    useEffect(() => {
    99      let valid = true;
   100  
   101      if (
   102        selectedDays < 0 ||
   103        selectedDays > 7 ||
   104        selectedDays > maxDays ||
   105        isNaN(selectedDays)
   106      ) {
   107        valid = false;
   108      }
   109  
   110      if (selectedHours < 0 || selectedHours > 23 || isNaN(selectedHours)) {
   111        valid = false;
   112      }
   113  
   114      if (selectedMinutes < 0 || selectedMinutes > 59 || isNaN(selectedMinutes)) {
   115        valid = false;
   116      }
   117  
   118      if (selectedDays === maxDays) {
   119        if (selectedHours > maxHours) {
   120          valid = false;
   121        }
   122  
   123        if (selectedHours === maxHours) {
   124          if (selectedMinutes > maxMinutes) {
   125            valid = false;
   126          }
   127        }
   128      }
   129  
   130      if (selectedDays <= 0 && selectedHours <= 0 && selectedMinutes <= 0) {
   131        valid = false;
   132      }
   133  
   134      setValidDate(valid);
   135    }, [
   136      dateSelected,
   137      maxDays,
   138      maxHours,
   139      maxMinutes,
   140      onChange,
   141      selectedDays,
   142      selectedHours,
   143      selectedMinutes,
   144    ]);
   145  
   146    const extraStyles = {
   147      "& .textBoxContainer": {
   148        minWidth: 0,
   149      },
   150      "& input": {
   151        textAlign: "center" as const,
   152        paddingRight: 10,
   153        paddingLeft: 10,
   154        width: 40,
   155      },
   156    };
   157  
   158    return (
   159      <Box className={"inputItem"}>
   160        <Box
   161          sx={{
   162            display: "flex",
   163            alignItems: "center",
   164            marginBottom: 5,
   165          }}
   166        >
   167          <InputLabel htmlFor={id}>{label}</InputLabel>
   168        </Box>
   169        <Box
   170          sx={{
   171            display: "flex",
   172            alignItems: "flex-start",
   173            justifyContent: "space-evenly",
   174            gap: 10,
   175            "& .reverseInput": {
   176              flexFlow: "row-reverse",
   177              "& > label": {
   178                fontWeight: 400,
   179                marginLeft: 15,
   180                marginRight: 25,
   181              },
   182            },
   183          }}
   184        >
   185          <Box>
   186            <InputBox
   187              id={id}
   188              className={`reverseInput removeArrows`}
   189              type="number"
   190              min="0"
   191              max="7"
   192              label="Days"
   193              name={id}
   194              onChange={(e) => {
   195                setSelectedDays(parseInt(e.target.value));
   196              }}
   197              value={selectedDays.toString()}
   198              sx={extraStyles}
   199              noLabelMinWidth
   200            />
   201          </Box>
   202          <Box>
   203            <InputBox
   204              id={id}
   205              className={`reverseInput removeArrows`}
   206              type="number"
   207              min="0"
   208              max="23"
   209              label="Hours"
   210              name={id}
   211              onChange={(e) => {
   212                setSelectedHours(parseInt(e.target.value));
   213              }}
   214              value={selectedHours.toString()}
   215              sx={extraStyles}
   216              noLabelMinWidth
   217            />
   218          </Box>
   219          <Box>
   220            <InputBox
   221              id={id}
   222              className={`reverseInput removeArrows`}
   223              type="number"
   224              min="0"
   225              max="59"
   226              label="Minutes"
   227              name={id}
   228              onChange={(e) => {
   229                setSelectedMinutes(parseInt(e.target.value));
   230              }}
   231              value={selectedMinutes.toString()}
   232              sx={extraStyles}
   233              noLabelMinWidth
   234            />
   235          </Box>
   236        </Box>
   237        <Box
   238          sx={{
   239            display: "flex",
   240            alignItems: "center",
   241            justifyContent: "flex-start",
   242            marginTop: 25,
   243            marginLeft: 10,
   244            marginBottom: 15,
   245            "& .validityText": {
   246              fontSize: 14,
   247              marginTop: 15,
   248              display: "flex",
   249              alignItems: "center",
   250              justifyContent: "center",
   251              "@media (max-width: 900px)": {
   252                flexFlow: "column",
   253              },
   254              "& > .min-icon": {
   255                color: "#5E5E5E",
   256                width: 15,
   257                height: 15,
   258                marginRight: 10,
   259              },
   260            },
   261            "& .validTill": {
   262              fontWeight: "bold",
   263              marginLeft: 15,
   264            },
   265            "& .invalidDurationText": {
   266              marginTop: 15,
   267              display: "flex",
   268              color: "red",
   269              fontSize: 11,
   270            },
   271          }}
   272        >
   273          {validDate ? (
   274            <div className={"validityText"}>
   275              <LinkIcon />
   276              <div>{entity} will be available until:</div>{" "}
   277              <div className={"validTill"}>
   278                {dateSelected.toFormat("MM/dd/yyyy HH:mm:ss ZZZZ")}
   279              </div>
   280            </div>
   281          ) : (
   282            <div className={"invalidDurationText"}>
   283              Please select a valid duration.
   284            </div>
   285          )}
   286        </Box>
   287      </Box>
   288    );
   289  };
   290  
   291  export default DaysSelector;