• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CBIIT / crdc-datahub-ui / 16006182009

01 Jul 2025 05:32PM UTC coverage: 62.703% (-8.6%) from 71.278%
16006182009

Pull #756

github

web-flow
Merge pull request #755 from CBIIT/revert-omb-date

revert: OMB expiration update
Pull Request #756: Sync 3.4.0 with 3.3.0

3560 of 6102 branches covered (58.34%)

Branch coverage included in aggregate %.

4920 of 7422 relevant lines covered (66.29%)

227.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

6.59
/src/components/Questionnaire/TableFileTypeAndExtensionInput.tsx
1
import React, { FC, useEffect, useState, useRef } from "react";
2
import {
3
  Autocomplete,
4
  TextField,
5
  TableCell,
6
  Tooltip,
7
  TooltipProps,
8
  styled,
9
  Paper,
10
} from "@mui/material";
11
import dropdownArrowsIcon from "../../assets/icons/dropdown_arrows.svg";
12
import { fileTypeExtensions } from "../../config/FileTypeConfig";
13
import useFormMode from "../../hooks/useFormMode";
14

15
const DropdownArrowsIcon = styled("div")(() => ({
2✔
16
  backgroundImage: `url(${dropdownArrowsIcon})`,
17
  backgroundSize: "contain",
18
  backgroundRepeat: "no-repeat",
19
  width: "9.17px",
20
  height: "18px",
21
}));
22

23
type Props = {
24
  inputID: string;
25
  typeValue: string;
26
  extensionValue: string;
27
  options: string[];
28
  name: string;
29
  required?: boolean;
30
  helpText?: string;
31
  placeholder?: string;
32
  disableClearable?: boolean;
33
  onChange?: (e: React.SyntheticEvent, v: string, r: string) => void;
34
};
35

36
const StyledTooltip = styled((props: TooltipProps) => (
2✔
37
  <Tooltip classes={{ popper: props.className }} {...props} />
×
38
))(() => ({
×
39
  "& .MuiTooltip-tooltip": {
40
    marginTop: "4px !important",
41
    color: "#C93F08",
42
    background: "#FFFFFF",
43
    border: "1px solid #2B528B",
44
  },
45
  "& .MuiTooltip-arrow": {
46
    color: "#2B528B",
47
  },
48
}));
49

50
const StyledTableCell = styled(TableCell)(() => ({
2✔
51
  borderTop: "1px solid #6B7294 !important",
52
  borderRight: "1px solid #6B7294 !important",
53
  borderBottom: "none!important",
54
  borderLeft: "none!important",
55
  padding: "0",
56
  "& .MuiStack-root": {
57
    width: "auto",
58
  },
59
}));
60

61
const StyledPaper = styled(Paper)(() => ({
2✔
62
  borderRadius: "8px",
63
  border: "1px solid #6B7294",
64
  marginTop: "2px",
65
  "& .MuiAutocomplete-listbox": {
66
    padding: 0,
67
  },
68
  "& .MuiAutocomplete-option[aria-selected='true']": {
69
    color: "#083A50",
70
    background: "#FFFFFF",
71
  },
72
  "& .MuiAutocomplete-option": {
73
    padding: "0 10px",
74
    height: "35px",
75
    color: "#083A50",
76
    background: "#FFFFFF",
77
  },
78
  "& .MuiAutocomplete-option:hover": {
79
    backgroundColor: "#3E7E6D",
80
    color: "#FFFFFF",
81
  },
82
  "& .MuiAutocomplete-option.Mui-focused": {
83
    backgroundColor: "#3E7E6D !important",
84
    color: "#FFFFFF",
85
  },
86
}));
87

88
const StyledAutocomplete = styled(Autocomplete)(({ readOnly }: { readOnly?: boolean }) => ({
2✔
89
  "& .MuiInputBase-root": {
90
    backgroundColor: readOnly ? "#E5EEF4" : "#FFFFFF",
×
91
    "&.MuiAutocomplete-inputRoot.MuiInputBase-root": {
92
      display: "flex",
93
      alignItems: "center",
94
      padding: 0,
95
    },
96
    "& .MuiInputBase-input": {
97
      fontWeight: 400,
98
      fontSize: "16px",
99
      fontFamily: "'Nunito', 'Rubik', sans-serif",
100
      padding: "10px 12px 10px 12px !important",
101
      color: readOnly ? "#083A50" : "initial",
×
102
      cursor: readOnly ? "not-allowed !important" : "initial",
×
103
    },
104
    "& ::placeholder": {
105
      color: "#87878C",
106
      fontWeight: 400,
107
      opacity: 1,
108
    },
109
    "& .MuiAutocomplete-endAdornment": {
110
      right: "8px",
111
    },
112
    "&.Mui-focused": {
113
      boxShadow:
114
        "2px 2px 2px 1px rgba(38, 184, 147, 0.10), -2px -2px 2px 1px rgba(38, 184, 147, 0.20)",
115
    },
116
  },
117
}));
118

119
/**
120
 * Generates a generic autocomplete select box with a label and help text
121
 *
122
 * @param {Props} props
123
 * @returns {JSX.Element}
124
 */
125
const TableAutocompleteInput: FC<Props> = ({
2✔
126
  inputID,
127
  typeValue,
128
  extensionValue,
129
  name,
130
  required = false,
×
131
  helpText,
132
  onChange,
133
  ...rest
134
}) => {
135
  const { readOnlyInputs } = useFormMode();
×
136

137
  const [typeVal, setTypeVal] = useState(typeValue);
×
138
  const [extensionVal, setExtensionVal] = useState(extensionValue);
×
139
  const fileTypeRef = useRef<HTMLInputElement>(null);
×
140
  const fileExtensionRef = useRef<HTMLInputElement>(null);
×
141
  const [showFileTypeEror, setShowFileTypeError] = useState<boolean>(false);
×
142
  const [showFileExtensionError, setShowFileExtensionError] = useState<boolean>(false);
×
143

144
  useEffect(() => {
×
145
    const invalid = () => {
×
146
      setShowFileTypeError(true);
×
147
    };
148
    fileTypeRef.current?.addEventListener("invalid", invalid);
×
149
    return () => {
×
150
      fileTypeRef.current?.removeEventListener("invalid", invalid);
×
151
    };
152
  }, [fileTypeRef]);
153

154
  useEffect(() => {
×
155
    const invalid = () => {
×
156
      setShowFileExtensionError(true);
×
157
    };
158

159
    fileExtensionRef.current?.addEventListener("invalid", invalid);
×
160
    return () => {
×
161
      fileExtensionRef.current?.removeEventListener("invalid", invalid);
×
162
    };
163
  }, [fileExtensionRef]);
164

165
  const onTypeValChangeWrapper = (e, v, r) => {
×
166
    setShowFileTypeError(false);
×
167
    v = v || "";
×
168
    if (v === "") {
×
169
      fileTypeRef.current.setCustomValidity("Please specify a file type");
×
170
    } else {
171
      fileTypeRef.current.setCustomValidity("");
×
172
    }
173
    if (typeof onChange === "function") {
×
174
      onChange(e, v, r);
×
175
    }
176

177
    setTypeVal(v);
×
178
  };
179
  const onExtensionValChangeWrapper = (e, v, r) => {
×
180
    setShowFileExtensionError(false);
×
181
    v = v || "";
×
182
    if (v === "") {
×
183
      fileExtensionRef.current.setCustomValidity("Please specify a file extension type");
×
184
    } else {
185
      fileExtensionRef.current.setCustomValidity("");
×
186
    }
187
    if (typeof onChange === "function") {
×
188
      onChange(e, v, r);
×
189
    }
190

191
    setExtensionVal(v);
×
192
  };
193
  const typeTextInputOnChange = (r) => {
×
194
    onTypeValChangeWrapper(null, r.target.value, null);
×
195
  };
196
  const extensionTextInputOnChange = (r) => {
×
197
    onExtensionValChangeWrapper(null, r.target.value, null);
×
198
  };
199

200
  useEffect(() => {
×
201
    onTypeValChangeWrapper(null, typeValue, null);
×
202
  }, [typeValue]);
203

204
  useEffect(() => {
×
205
    onExtensionValChangeWrapper(null, extensionValue, null);
×
206
  }, [extensionValue]);
207

208
  return (
×
209
    <>
210
      <StyledTableCell>
211
        <StyledAutocomplete
212
          sx={{
213
            "& .MuiInputBase-input": {
214
              fontWeight: 400,
215
              fontSize: "16px",
216
              fontFamily: "'Nunito', 'Rubik', sans-serif",
217
              padding: "0 !important",
218
              height: "20px",
219
            },
220
          }}
221
          id={inputID.concat("-type")}
222
          size="small"
223
          value={typeVal || ""}
×
224
          onChange={onTypeValChangeWrapper}
225
          popupIcon={<DropdownArrowsIcon />}
226
          readOnly={readOnlyInputs}
227
          freeSolo
228
          PaperComponent={StyledPaper}
229
          slotProps={{
230
            popper: {
231
              disablePortal: true,
232
              sx: {
233
                top: "-2px !important",
234
                zIndex: "2000",
235
              },
236
              modifiers: [
237
                {
238
                  // disables popper from flipping above the input when out of screen room
239
                  name: "flip",
240
                  enabled: false,
241
                  options: {
242
                    fallbackPlacements: [],
243
                  },
244
                },
245
              ],
246
            },
247
          }}
248
          renderInput={(p) => (
249
            <StyledTooltip
×
250
              title="Missing required field"
251
              arrow
252
              disableHoverListener
253
              disableFocusListener
254
              disableTouchListener
255
              open={showFileTypeEror}
256
              slotProps={{
257
                tooltip: { style: { marginTop: "4px !important" } },
258
              }}
259
            >
260
              <TextField
261
                {...p}
262
                onChange={typeTextInputOnChange}
263
                name={name.concat("[type]")}
264
                required={required}
265
                placeholder={rest.placeholder || "Enter or select a type"}
×
266
                variant="standard"
267
                inputRef={fileTypeRef}
268
                InputProps={{ ...p.InputProps, disableUnderline: true }}
269
                // eslint-disable-next-line react/jsx-no-duplicate-props
270
                inputProps={{
271
                  ...p.inputProps,
272
                  maxLength: 30,
273
                  "aria-label": "File type",
274
                }}
275
              />
276
            </StyledTooltip>
277
          )}
278
          {...rest}
279
        />
280
      </StyledTableCell>
281
      <StyledTableCell>
282
        <StyledAutocomplete
283
          sx={{
284
            "& .MuiInputBase-input": {
285
              fontWeight: 400,
286
              fontSize: "16px",
287
              fontFamily: "'Nunito', 'Rubik', sans-serif",
288
              padding: "0 !important",
289
              height: "20px",
290
            },
291
          }}
292
          id={inputID.concat("-extension")}
293
          size="small"
294
          value={extensionVal || ""}
×
295
          onChange={onExtensionValChangeWrapper}
296
          popupIcon={<DropdownArrowsIcon />}
297
          readOnly={readOnlyInputs}
298
          freeSolo
299
          PaperComponent={StyledPaper}
300
          slotProps={{
301
            popper: {
302
              disablePortal: true,
303
              sx: {
304
                top: "-2px !important",
305
                zIndex: "2000",
306
              },
307
              modifiers: [
308
                {
309
                  // disables popper from flipping above the input when out of screen room
310
                  name: "flip",
311
                  enabled: false,
312
                  options: {
313
                    fallbackPlacements: [],
314
                  },
315
                },
316
              ],
317
            },
318
          }}
319
          renderInput={(p) => (
320
            <StyledTooltip
×
321
              title="Missing required field"
322
              arrow
323
              disableHoverListener
324
              disableFocusListener
325
              disableTouchListener
326
              open={showFileExtensionError}
327
            >
328
              <TextField
329
                {...p}
330
                onChange={extensionTextInputOnChange}
331
                name={name.concat("[extension]")}
332
                required={required}
333
                placeholder={rest.placeholder || "Enter or select an extension"}
×
334
                variant="standard"
335
                inputRef={fileExtensionRef}
336
                InputProps={{ ...p.InputProps, disableUnderline: true }}
337
                // eslint-disable-next-line react/jsx-no-duplicate-props
338
                inputProps={{
339
                  ...p.inputProps,
340
                  maxLength: 10,
341
                  "aria-label": "File extension",
342
                }}
343
              />
344
            </StyledTooltip>
345
          )}
346
          options={fileTypeExtensions[typeVal] || []}
×
347
        />
348
      </StyledTableCell>
349
    </>
350
  );
351
};
352

353
export default TableAutocompleteInput;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc