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;