diff --git a/src/components/DataSubmissions/DataSubmissionUpload.tsx b/src/components/DataSubmissions/DataSubmissionUpload.tsx index bf09ccb6..8bfff301 100644 --- a/src/components/DataSubmissions/DataSubmissionUpload.tsx +++ b/src/components/DataSubmissions/DataSubmissionUpload.tsx @@ -30,7 +30,7 @@ const StyledMetadataText = styled(StyledUploadTypeText)(() => ({ const StyledUploadFilesButton = styled(LoadingButton)(() => ({ display: "flex", flexDirection: "column", - padding: "12px 22px", + padding: "12px 20px", justifyContent: "center", alignItems: "center", borderRadius: "8px", @@ -46,7 +46,8 @@ const StyledUploadFilesButton = styled(LoadingButton)(() => ({ textTransform: "none", "&.MuiButtonBase-root": { marginLeft: "auto", - marginRight: "21.5px" + marginRight: "21.5px", + minWidth: "137px", } })); @@ -336,7 +337,7 @@ const DataSubmissionUpload = ({ submitterID, readOnly, onUpload }: Props) => { disableRipple disableTouchRipple > - Upload Files + Upload ); diff --git a/src/components/DataSubmissions/RadioInput.tsx b/src/components/DataSubmissions/RadioInput.tsx index cce79839..80b8dcaa 100644 --- a/src/components/DataSubmissions/RadioInput.tsx +++ b/src/components/DataSubmissions/RadioInput.tsx @@ -3,9 +3,7 @@ import { Grid, FormControl, FormControlLabel, - Radio, RadioGroup, - RadioProps, RadioGroupProps, FormHelperText, Stack, @@ -13,6 +11,7 @@ import { GridProps, } from "@mui/material"; import { updateInputValidity } from "../../utils"; +import StyledRadioButton from '../Questionnaire/StyledRadioButton'; const GridStyled = styled(Grid, { shouldForwardProp: (prop) => prop !== "containerWidth", @@ -47,31 +46,6 @@ const GridStyled = styled(Grid, { }, })); -const BpIcon = styled("span")(() => ({ - borderRadius: "50%", - width: 24, - height: 24, - outline: "6px auto #1D91AB", - "input:hover ~ &": { - outlineOffset: "2px", - }, -})); - -const BpCheckedIcon = styled(BpIcon)({ - backgroundImage: - "linear-gradient(180deg,hsla(0,0%,100%,.1),hsla(0,0%,100%,0))", - "&:before": { - borderRadius: "50%", - display: "block", - marginTop: "4px", - marginLeft: "4px", - width: 16, - height: 16, - backgroundImage: "radial-gradient(#1D91AB, #1D91AB)", - content: '""', - }, -}); - const StyledFormLabel = styled("label")(() => ({ fontWeight: 700, fontSize: "16px", @@ -85,15 +59,6 @@ const StyledAsterisk = styled("span")(() => ({ color: "#D54309", })); -const StyledRadio = styled(Radio)((props) => ({ - "& input": { - cursor: props.readOnly ? "not-allowed" : "pointer", - }, - "& .radio-icon": { - backgroundColor: props.readOnly ? "#D2DFE9 !important" : "initial", - }, -})); - const StyledFormControlLabel = styled(FormControlLabel)(() => ({ "&.MuiFormControlLabel-root": { paddingRight: "10px", @@ -109,22 +74,6 @@ const StyledFormControlLabel = styled(FormControlLabel)(() => ({ }, })); -// Inspired by blueprintjs -const BpRadio = (props: RadioProps) => ( - } - icon={} - inputProps={ - { - "data-type": "auto", - } as unknown - } - {...props} - /> -); - type Option = { label: string; value: string; @@ -231,7 +180,7 @@ const RadioYesNoInput: FC = ({ label={option.label} color="#1D91AB" control={( - } + */ +const ValidationControls: FC = ({ dataSubmission }: Props) => { + const { user } = useAuthContext(); + const [validationType, setValidationType] = useState("Metadata"); + const [uploadType, setUploadType] = useState("New"); + const [isLoading, setIsLoading] = useState(false); + const [isValidating, setIsValidating] = useState(false); + const [validationAlert, setValidationAlert] = useState(null); + + const canValidateData: boolean = useMemo(() => ValidateRoles.includes(user?.role), [user?.role]); + const validateButtonEnabled: boolean = useMemo(() => ValidateStatuses.includes(dataSubmission?.status), [dataSubmission?.status]); + + const [validateSubmission] = useMutation(VALIDATE_SUBMISSION, { + context: { clientName: 'backend' }, + fetchPolicy: 'no-cache' + }); + + const handleValidateFiles = async () => { + setIsLoading(true); + + const { data, errors } = await validateSubmission({ + variables: { + _id: dataSubmission?._id, + types: getTypes(validationType), + scope: uploadType === "New" ? "New" : "All", + } + }); + + if (errors || !data?.validateSubmission?.success) { + setValidationAlert({ message: "Unable to initiate validation process.", severity: "error" }); + setIsValidating(false); + } else { + setValidationAlert({ message: "Validation process is starting; this may take some time. Please wait before initiating another validation.", severity: "success" }); + setIsValidating(true); + } + + // Reset form to default values + setValidationType("Metadata"); + setUploadType("New"); + setIsLoading(false); + setTimeout(() => setValidationAlert(null), 10000); + }; + + const getTypes = (validationType: ValidationType): string[] => { + switch (validationType) { + case "Metadata": + return ["metadata"]; + case "Files": + return ["file"]; + default: + return ["metadata", "file"]; + } + }; + + return ( + + + {validationAlert?.message} + +
+
+
Validation Type:
+
+ setValidationType(val)} row> + } + label="Validate Metadata" + disabled={!canValidateData} + /> + } + label="Validate Data Files" + disabled={!canValidateData} + /> + } + label="Both" + disabled={!canValidateData} + /> + +
+
+
+
Validation Target:
+
+ setUploadType(val)} row> + } + label="New Uploaded Data" + disabled={!canValidateData} + /> + } + label="All Uploaded Data" + disabled={!canValidateData} + /> + +
+
+
+ + {isValidating ? "Validating..." : "Validate"} + +
+ ); +}; + +export default ValidationControls; diff --git a/src/components/Questionnaire/RadioYesNoInput.tsx b/src/components/Questionnaire/RadioYesNoInput.tsx index c839fa43..6332484b 100644 --- a/src/components/Questionnaire/RadioYesNoInput.tsx +++ b/src/components/Questionnaire/RadioYesNoInput.tsx @@ -3,15 +3,14 @@ import { Grid, FormControl, FormControlLabel, - Radio, RadioGroup, - RadioProps, RadioGroupProps, FormHelperText, styled, GridProps, } from "@mui/material"; import { updateInputValidity } from "../../utils"; +import StyledRadioButton from './StyledRadioButton'; const GridStyled = styled(Grid, { shouldForwardProp: (prop) => prop !== "containerWidth", @@ -44,29 +43,6 @@ const GridStyled = styled(Grid, { display: "none !important", }, })); -const BpIcon = styled("span")(() => ({ - borderRadius: "50%", - width: 24, - height: 24, - outline: "6px auto #1D91AB", - "input:hover ~ &": { - outlineOffset: "2px", - }, -})); - -const BpCheckedIcon = styled(BpIcon)({ - backgroundImage: "linear-gradient(180deg,hsla(0,0%,100%,.1),hsla(0,0%,100%,0))", - "&:before": { - borderRadius: "50%", - display: "block", - marginTop: "4px", - marginLeft: "4px", - width: 16, - height: 16, - backgroundImage: "radial-gradient(#1D91AB, #1D91AB)", - content: '""', - }, -}); const StyledFormLabel = styled("label")(() => ({ fontWeight: 700, @@ -82,31 +58,6 @@ const StyledAsterisk = styled("span")(() => ({ color: "#D54309", })); -const StyledRadio = styled(Radio)((props) => ({ - "& input": { - cursor: props.readOnly ? "not-allowed" : "initial", - }, - "& .radio-icon": { - backgroundColor: props.readOnly ? "#E5EEF4 !important" : "initial", - }, -})); - -// Inspired by blueprintjs -const BpRadio = (props: RadioProps) => ( - } - icon={} - inputProps={ - { - "data-type": "auto", - } as unknown - } - {...props} - /> -); - type Props = { label: string; name: string; @@ -176,8 +127,8 @@ const RadioYesNoInput: FC = ({ data-type="string" {...rest} > - } label="Yes" /> - } label="No" /> + } label="Yes" /> + } label="No" /> {(!readOnly && error ? "This field is required" : null) || " "} diff --git a/src/components/Questionnaire/StyledRadioButton.tsx b/src/components/Questionnaire/StyledRadioButton.tsx new file mode 100644 index 00000000..9999f27b --- /dev/null +++ b/src/components/Questionnaire/StyledRadioButton.tsx @@ -0,0 +1,53 @@ +import { + Radio, + RadioProps, + + styled, + } from "@mui/material"; + +const StyledRadio = styled(Radio)((props) => ({ + "& input": { + cursor: props.readOnly ? "not-allowed" : "pointer", + }, + "& .radio-icon": { + background: (props.readOnly || props.disabled) ? "#EEEEEE !important" : "#ffffff", + } + })); + +const BpIcon = styled('span')(() => ({ +borderRadius: '50%', +width: 24, +height: 24, +outline: 'solid 2px #1D91AB', +'input:hover:enabled ~ &': { + outlineOffset: "2px", +}, +})); + +const BpCheckedIcon = styled(BpIcon)({ +'&:before': { + borderRadius: "50%", + display: 'block', + marginTop: "4px", + marginLeft: "4px", + width: 16, + height: 16, + backgroundColor: '#1D91AB', + content: '""', +}, +}); + +const StyledRadioButton = (props: RadioProps) => ( + } + icon={} + inputProps={{ + "data-type": "auto" + } as unknown} + {...props} + /> +); + +export default StyledRadioButton; diff --git a/src/content/dataSubmissions/DataSubmission.tsx b/src/content/dataSubmissions/DataSubmission.tsx index 15776da0..babd393b 100644 --- a/src/content/dataSubmissions/DataSubmission.tsx +++ b/src/content/dataSubmissions/DataSubmission.tsx @@ -16,6 +16,7 @@ import { Typography, styled, } from "@mui/material"; + import { isEqual } from "lodash"; import bannerSvg from "../../assets/dataSubmissions/dashboard_banner.svg"; import summaryBannerSvg from "../../assets/dataSubmissions/summary_banner.png"; @@ -36,6 +37,7 @@ import { FormatDate } from "../../utils"; import DataSubmissionActions from "./DataSubmissionActions"; import QualityControl from "./QualityControl"; import { ReactComponent as CopyIconSvg } from "../../assets/icons/copy_icon_2.svg"; +import ValidationControls from '../../components/DataSubmissions/ValidationControls'; const StyledBanner = styled("div")(({ bannerSrc }: { bannerSrc: string }) => ({ background: `url(${bannerSrc})`, @@ -113,6 +115,7 @@ const StyledCardActions = styled(CardActions, { })); const StyledTabs = styled(Tabs)(() => ({ + background: "#F0FBFD", position: 'relative', "& .MuiTabs-flexContainer": { justifyContent: "center" @@ -237,7 +240,6 @@ const submissionLockedStatuses: SubmissionStatus[] = ["Submitted", "Released", " const DataSubmission = () => { const { submissionId, tab } = useParams(); - const [dataSubmission, setDataSubmission] = useState(null); const [batchFiles, setBatchFiles] = useState([]); const [totalBatchFiles, setTotalBatchFiles] = useState(0); @@ -402,7 +404,7 @@ const DataSubmission = () => { {/* TODO: Widgets removed for MVP2-M2. Will be re-added in the future */} - + ; +}; diff --git a/src/types/Submissions.d.ts b/src/types/Submissions.d.ts index c9d08396..525fc188 100644 --- a/src/types/Submissions.d.ts +++ b/src/types/Submissions.d.ts @@ -191,3 +191,14 @@ type DataRecord = { s3FileInfo: S3FileInfo; // only for "file" types, should be null for other nodes CRDC_ID: string; }; + +type DataValidationResult = { + /** + * Whether the validation action was successfully queued. + */ + success: boolean; + /** + * The message returned by the validation. + */ + message: string; +};