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

CBIIT / crdc-datahub-ui / 17132131774

21 Aug 2025 03:52PM UTC coverage: 77.592% (+1.7%) from 75.941%
17132131774

Pull #806

github

web-flow
Merge 6b88b37d9 into c10ceac73
Pull Request #806: Submission Request Excel Import & Export CRDCDH-3033, CRDCDH-3045, CRDCDH-3063

4841 of 5322 branches covered (90.96%)

Branch coverage included in aggregate %.

3122 of 3394 new or added lines in 32 files covered. (91.99%)

7 existing lines in 3 files now uncovered.

28996 of 38287 relevant lines covered (75.73%)

1856.98 hits per line

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

10.76
/src/content/questionnaire/sections/C.tsx
1
import { parseForm } from "@jalik/form-parser";
1✔
2
import { AutocompleteChangeReason, styled } from "@mui/material";
1✔
3
import { FC, SyntheticEvent, useEffect, useRef, useState } from "react";
1✔
4
import { Link } from "react-router-dom";
1✔
5

6
import { useFormContext } from "../../../components/Contexts/FormContext";
1✔
7
import CustomAutocomplete from "../../../components/Questionnaire/CustomAutocomplete";
1✔
8
import FormContainer from "../../../components/Questionnaire/FormContainer";
1✔
9
import FormGroupCheckbox from "../../../components/Questionnaire/FormGroupCheckbox";
1✔
10
import LabelCheckbox from "../../../components/Questionnaire/LabelCheckbox";
1✔
11
import SectionGroup from "../../../components/Questionnaire/SectionGroup";
1✔
12
import SelectInput from "../../../components/Questionnaire/SelectInput";
1✔
13
import SwitchInput from "../../../components/Questionnaire/SwitchInput";
1✔
14
import TextInput from "../../../components/Questionnaire/TextInput";
1✔
15
import accessTypesOptions from "../../../config/AccessTypesConfig";
1✔
16
import cancerTypeOptions, { CUSTOM_CANCER_TYPES } from "../../../config/CancerTypesConfig";
1✔
17
import SectionMetadata from "../../../config/SectionMetadata";
1✔
18
import speciesOptions from "../../../config/SpeciesConfig";
1✔
19
import useFormMode from "../../../hooks/useFormMode";
1✔
20
import {
1✔
21
  isValidInRange,
22
  filterPositiveIntegerString,
23
  combineQuestionnaireData,
24
} from "../../../utils";
25

26
const StyledLink = styled(Link)({
1✔
27
  color: "#005A9E",
1✔
28
  fontSize: "16px",
1✔
29
  fontWeight: 700,
1✔
30
  lineHeight: "19.6px",
1✔
31
  marginLeft: "10px",
1✔
32
});
1✔
33

34
const GPAList = () => (
1✔
35
  <StyledLink
×
36
    to="https://sharing.nih.gov/genomic-data-sharing-policy/resources/contacts-and-help#gds_support"
×
37
    target="_blank"
×
38
    rel="noopener noreferrer"
×
39
  >
40
    View GPA List
41
  </StyledLink>
×
42
);
43

44
const AccessTypesDescription = styled("span")(() => ({
1✔
45
  fontWeight: 400,
×
46
}));
1✔
47

48
/**
49
 * Form Section C View
50
 *
51
 * @param {FormSectionProps} props
52
 * @returns {JSX.Element}
53
 */
54
const FormSectionC: FC<FormSectionProps> = ({ SectionOption, refs }: FormSectionProps) => {
1✔
55
  const {
×
56
    data: { questionnaireData: data },
×
57
  } = useFormContext();
×
58
  const { readOnlyInputs } = useFormMode();
×
59
  const formContainerRef = useRef<HTMLDivElement>();
×
60
  const formRef = useRef<HTMLFormElement>();
×
61
  const { getFormObjectRef } = refs;
×
62
  const { C: SectionCMetadata } = SectionMetadata;
×
63

64
  const [cancerTypes, setCancerTypes] = useState<string[]>(data.cancerTypes || []);
×
65
  const [otherCancerTypes, setOtherCancerTypes] = useState<string>(data.otherCancerTypes);
×
66
  const [otherCancerTypesEnabled, setOtherCancerTypesEnabled] = useState<boolean>(
×
67
    data.otherCancerTypesEnabled
×
68
  );
×
69
  const [otherSpecies, setOtherSpecies] = useState<string>(data.otherSpeciesOfSubjects);
×
70
  const [otherSpeciesEnabled, setOtherSpeciesEnabled] = useState<boolean>(data.otherSpeciesEnabled);
×
71
  const [isDbGapRegistered, setIsdbGaPRegistered] = useState<boolean>(
×
72
    data.study?.isDbGapRegistered
×
73
  );
×
74
  const [dbGaPPPHSNumber, setDbGaPPPHSNumber] = useState<string>(data.study?.dbGaPPPHSNumber);
×
75
  const [GPAName, setGPAName] = useState<string>(data.study?.GPAName);
×
76

77
  const getFormObject = (): FormObject | null => {
×
78
    if (!formRef.current) {
×
79
      return null;
×
80
    }
×
81

82
    const formObject = parseForm(formRef.current, { nullify: false });
×
NEW
83
    const combinedData: QuestionnaireData = combineQuestionnaireData(data, formObject);
×
84

85
    combinedData.numberOfParticipants = parseInt(formObject.numberOfParticipants, 10) || null;
×
86

87
    return { ref: formRef, data: combinedData };
×
88
  };
×
89

90
  const handleCancerTypesChange = (
×
91
    e: SyntheticEvent,
×
92
    newValue: string[],
×
93
    reason: AutocompleteChangeReason
×
94
  ) => {
×
95
    // If N/A was previously selected, then remove N/A
96
    if (cancerTypes.includes(CUSTOM_CANCER_TYPES.NOT_APPLICABLE)) {
×
97
      newValue = newValue.filter((option) => option !== CUSTOM_CANCER_TYPES.NOT_APPLICABLE);
×
98
      // If N/A is newly selected, then unselect all other options
99
    } else if (newValue.includes(CUSTOM_CANCER_TYPES.NOT_APPLICABLE)) {
×
100
      newValue = [CUSTOM_CANCER_TYPES.NOT_APPLICABLE];
×
101
    }
×
102

103
    if (newValue?.includes(CUSTOM_CANCER_TYPES.NOT_APPLICABLE)) {
×
104
      setOtherCancerTypes("");
×
105
      setOtherCancerTypesEnabled(false);
×
106
    }
×
107

108
    setCancerTypes(newValue);
×
109
  };
×
110

111
  const handleOtherCancerTypesCheckboxChange = (
×
112
    event: React.ChangeEvent<HTMLInputElement>,
×
113
    checked: boolean
×
114
  ) => {
×
115
    if (!checked) {
×
116
      setOtherCancerTypes("");
×
117
    }
×
118

119
    setOtherCancerTypesEnabled(checked);
×
120
  };
×
121

122
  const handleOtherSpeciesCheckboxChange = (
×
123
    event: React.ChangeEvent<HTMLInputElement>,
×
124
    checked: boolean
×
125
  ) => {
×
126
    if (!checked) {
×
127
      setOtherSpecies("");
×
128
    }
×
129

130
    setOtherSpeciesEnabled(checked);
×
131
  };
×
132

133
  const handleIsDbGapRegisteredChange = (e, checked: boolean) => {
×
134
    setIsdbGaPRegistered(checked);
×
135
    if (!checked) {
×
136
      setDbGaPPPHSNumber("");
×
137
    }
×
138
  };
×
139

140
  useEffect(() => {
×
141
    getFormObjectRef.current = getFormObject;
×
142
  }, [refs]);
×
143

144
  useEffect(() => {
×
145
    formContainerRef.current?.scrollIntoView({ block: "start" });
×
146
  }, []);
×
147

NEW
148
  useEffect(() => {
×
NEW
149
    setCancerTypes(data?.cancerTypes || []);
×
NEW
150
  }, [data?.cancerTypes]);
×
151

NEW
152
  useEffect(() => {
×
NEW
153
    setOtherCancerTypes(data?.otherCancerTypes);
×
NEW
154
  }, [data?.otherCancerTypes]);
×
155

NEW
156
  useEffect(() => {
×
NEW
157
    setOtherCancerTypesEnabled(data?.otherCancerTypesEnabled);
×
NEW
158
  }, [data?.otherCancerTypesEnabled]);
×
159

NEW
160
  useEffect(() => {
×
NEW
161
    setOtherSpecies(data?.otherSpeciesOfSubjects);
×
NEW
162
  }, [data?.otherSpeciesOfSubjects]);
×
163

NEW
164
  useEffect(() => {
×
NEW
165
    setOtherSpeciesEnabled(data?.otherSpeciesEnabled);
×
NEW
166
  }, [data?.otherSpeciesEnabled]);
×
167

NEW
168
  useEffect(() => {
×
NEW
169
    setIsdbGaPRegistered(data?.study?.isDbGapRegistered);
×
NEW
170
  }, [data?.study?.isDbGapRegistered]);
×
171

NEW
172
  useEffect(() => {
×
NEW
173
    setDbGaPPPHSNumber(data?.study?.dbGaPPPHSNumber);
×
NEW
174
  }, [data?.study?.dbGaPPPHSNumber]);
×
175

NEW
176
  useEffect(() => {
×
NEW
177
    setGPAName(data?.study?.GPAName);
×
NEW
178
  }, [data?.study?.GPAName]);
×
179

180
  return (
×
181
    <FormContainer ref={formContainerRef} formRef={formRef} description={SectionOption.title}>
×
182
      {/* Data Access Section */}
183
      <SectionGroup
×
184
        title={SectionCMetadata.sections.DATA_ACCESS.title}
×
185
        description={SectionCMetadata.sections.DATA_ACCESS.description}
×
186
      >
187
        <FormGroupCheckbox
×
188
          idPrefix="section-c-access-types"
×
189
          label={
×
190
            <>
×
191
              Access Types <AccessTypesDescription>(Select all that apply):</AccessTypesDescription>
×
192
            </>
×
193
          }
194
          name="accessTypes"
×
195
          options={accessTypesOptions}
×
196
          value={data.accessTypes}
×
197
          gridWidth={12}
×
198
          required
×
199
          readOnly={readOnlyInputs}
×
200
        />
201
      </SectionGroup>
×
202

203
      {/* dbGaP Registration section */}
204
      <SectionGroup
×
205
        title={SectionCMetadata.sections.DBGAP_REGISTRATION.title}
×
206
        description={SectionCMetadata.sections.DBGAP_REGISTRATION.description}
×
207
      >
208
        <SwitchInput
×
209
          id="section-c-dbGaP-registration"
×
210
          label="Has your study been registered in dbGaP?"
×
211
          name="study[isDbGapRegistered]"
×
212
          required
×
213
          value={isDbGapRegistered}
×
214
          onChange={handleIsDbGapRegisteredChange}
×
215
          isBoolean
×
216
          readOnly={readOnlyInputs}
×
217
        />
218
        <TextInput
×
219
          id="section-c-if-yes-provide-dbgap-phs-number"
×
220
          label="If yes, provide dbGaP PHS number with the version number"
×
221
          name="study[dbGaPPPHSNumber]"
×
222
          value={dbGaPPPHSNumber}
×
223
          onChange={(e) => setDbGaPPPHSNumber(e.target.value || "")}
×
224
          maxLength={50}
×
225
          placeholder='Ex/ "phs002529.v1.p1". 50 characters allowed'
×
226
          gridWidth={12}
×
227
          readOnly={readOnlyInputs || !isDbGapRegistered}
×
228
          required={isDbGapRegistered}
×
229
        />
230

231
        <TextInput
×
232
          id="section-c-genomic-program-administrator-name"
×
233
          label="GPA Name"
×
234
          labelEndAdornment={<GPAList />}
×
235
          name="study[GPAName]"
×
236
          value={GPAName}
×
237
          onChange={(e) => setGPAName(e.target.value || "")}
×
238
          placeholder="Enter GPA Name, if applicable"
×
239
          tooltipText="Provide information on the Genomic Program Administrator (GPA) who registered the study on dbGaP."
×
240
          gridWidth={12}
×
241
          readOnly={readOnlyInputs}
×
242
        />
243
      </SectionGroup>
×
244

245
      {/* Cancer Types Section */}
246
      <SectionGroup
×
247
        title={SectionCMetadata.sections.CANCER_TYPES.title}
×
248
        description={SectionCMetadata.sections.CANCER_TYPES.description}
×
249
      >
250
        <CustomAutocomplete
×
251
          multiple
×
252
          options={cancerTypeOptions}
×
253
          disableClearable
×
254
          id="section-c-cancer-types"
×
255
          label="Cancer types (select all that apply)"
×
256
          name="cancerTypes"
×
257
          placeholder="Select cancer types"
×
258
          value={Array.isArray(cancerTypes) ? cancerTypes : []}
×
259
          onChange={handleCancerTypesChange}
×
260
          tagText={(value) => `${value.length} Cancer Types selected`}
×
261
          readOnly={readOnlyInputs}
×
262
          disableCloseOnSelect
×
263
        />
264
        <TextInput
×
265
          id="section-c-other-cancer-types"
×
266
          key={`other_cancer_types_${cancerTypes?.toString()}`}
×
267
          label="Other cancer type(s)"
×
268
          tooltipText='Enter additional Cancer Types, separated by pipes ("|").'
×
269
          labelStartAdornment={
×
270
            <LabelCheckbox
×
271
              idPrefix="section-c-other-cancer-types-enabled"
×
272
              name="otherCancerTypesEnabled"
×
273
              checked={otherCancerTypesEnabled}
×
274
              onChange={handleOtherCancerTypesCheckboxChange}
×
275
              readOnly={cancerTypes.includes(CUSTOM_CANCER_TYPES.NOT_APPLICABLE) || readOnlyInputs}
×
276
              inputProps={{ "aria-label": "Toggle Other cancer type(s)" }}
×
277
            />
278
          }
279
          name="otherCancerTypes"
×
280
          placeholder="Specify other cancer type(s)"
×
281
          value={otherCancerTypes}
×
282
          onChange={(e) => setOtherCancerTypes(e.target.value || "")}
×
283
          maxLength={1000}
×
284
          required={otherCancerTypesEnabled}
×
285
          readOnly={!otherCancerTypesEnabled || readOnlyInputs}
×
286
        />
287

288
        <TextInput
×
289
          id="section-c-pre-cancer-types"
×
290
          label="Pre-Cancer types (provide all that apply)"
×
291
          tooltipText='Enter additional Pre-Cancer Types, separated by pipes ("|").'
×
292
          name="preCancerTypes"
×
293
          placeholder="Provide pre-cancer types"
×
294
          value={data.preCancerTypes}
×
295
          maxLength={500}
×
296
          readOnly={readOnlyInputs}
×
297
        />
298
      </SectionGroup>
×
299

300
      {/* Subjects/Species Section */}
301
      <SectionGroup title={SectionCMetadata.sections.SUBJECTS.title}>
×
302
        <SelectInput
×
303
          id="section-c-species-of-subjects"
×
304
          label="Species of subjects (choose all that apply)"
×
305
          name="species"
×
306
          options={speciesOptions.map((option) => ({
×
307
            label: option,
×
308
            value: option,
×
309
          }))}
×
310
          placeholder="Select species"
×
311
          value={data.species}
×
312
          multiple
×
313
          required
×
314
          readOnly={readOnlyInputs}
×
315
        />
316
        <TextInput
×
317
          id="section-c-other-species-of-subjects"
×
318
          label="Other Specie(s) involved"
×
319
          tooltipText='Enter additional Species, separated by pipes ("|").'
×
320
          labelStartAdornment={
×
321
            <LabelCheckbox
×
322
              idPrefix="section-c-other-cancer-types-enabled"
×
323
              name="otherSpeciesEnabled"
×
324
              checked={otherSpeciesEnabled}
×
325
              onChange={handleOtherSpeciesCheckboxChange}
×
326
              readOnly={readOnlyInputs}
×
327
              inputProps={{ "aria-label": "Toggle Other Specie(s) involved" }}
×
328
            />
329
          }
330
          name="otherSpeciesOfSubjects"
×
331
          placeholder="Specify all other species (max of 500 characters)"
×
332
          value={otherSpecies}
×
333
          onChange={(e) => setOtherSpecies(e.target.value || "")}
×
334
          maxLength={500}
×
335
          required={otherSpeciesEnabled}
×
336
          readOnly={!otherSpeciesEnabled || readOnlyInputs}
×
337
        />
338
        <TextInput
×
339
          id="section-c-number-of-subjects-included-in-the-submission"
×
340
          label="Number of subjects included in the submission"
×
341
          name="numberOfParticipants"
×
342
          placeholder="##"
×
343
          type="text"
×
344
          value={data.numberOfParticipants}
×
345
          filter={filterPositiveIntegerString}
×
346
          validate={(input: string) => isValidInRange(input, 1, 2000000000)} // between 1 and 2bn
×
347
          errorText="Value must be between 1 and 2,000,000,000."
×
348
          maxLength={10}
×
349
          inputProps={
×
350
            {
×
351
              "data-type": "number",
×
352
            } as unknown
×
353
          }
354
          required
×
355
          readOnly={readOnlyInputs}
×
356
        />
357
      </SectionGroup>
×
358
    </FormContainer>
×
359
  );
360
};
×
361

362
export default FormSectionC;
1✔
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