diff --git a/src/components/DataSubmissions/GenericTable.tsx b/src/components/DataSubmissions/GenericTable.tsx index ad2998cd..0d3fd8f3 100644 --- a/src/components/DataSubmissions/GenericTable.tsx +++ b/src/components/DataSubmissions/GenericTable.tsx @@ -3,6 +3,7 @@ import { Table, TableBody, TableCell, + TableCellProps, TableContainer, TableHead, TablePagination, @@ -118,7 +119,8 @@ export type Column = { renderValue: (a: T, user: User) => string | boolean | number | React.ReactNode; field?: keyof T; default?: true; - minWidth?: string; + sortDisabled?: boolean; + sx?: TableCellProps["sx"]; }; export type FetchListing = { @@ -138,6 +140,7 @@ type Props = { total: number; loading?: boolean; noContentText?: string; + defaultRowsPerPage?: number; setItemKey?: (item: T, index: number) => string; onFetchData?: (params: FetchListing, force: boolean) => void; onOrderChange?: (order: Order) => void; @@ -151,6 +154,7 @@ const DataSubmissionBatchTable = ({ total = 0, loading, noContentText, + defaultRowsPerPage = 10, setItemKey, onFetchData, onOrderChange, @@ -163,7 +167,7 @@ const DataSubmissionBatchTable = ({ columns.find((c) => c.default) || columns.find((c) => c.field) ); const [page, setPage] = useState(0); - const [perPage, setPerPage] = useState(10); + const [perPage, setPerPage] = useState(defaultRowsPerPage); useEffect(() => { fetchData(); @@ -221,8 +225,8 @@ const DataSubmissionBatchTable = ({ {columns.map((col: Column) => ( - - {col.field ? ( + + {col.field && !col.sortDisabled ? ( void; +}>({}); + +export default QCResultsContext; diff --git a/src/content/dataSubmissions/DataSubmission.tsx b/src/content/dataSubmissions/DataSubmission.tsx index 97ea907f..5bec53a7 100644 --- a/src/content/dataSubmissions/DataSubmission.tsx +++ b/src/content/dataSubmissions/DataSubmission.tsx @@ -229,7 +229,9 @@ const columns: Column[] = [ renderValue: (data) => (data?.createdAt ? `${FormatDate(data.createdAt, "MM-DD-YYYY [at] hh:mm A")}` : ""), field: "createdAt", default: true, - minWidth: "240px" + sx: { + minWidth: "240px" + } }, /* TODO: Error Count removed for MVP2-M2. Will be re-added in the future */ ]; diff --git a/src/content/dataSubmissions/ErrorDialog.tsx b/src/content/dataSubmissions/ErrorDialog.tsx new file mode 100644 index 00000000..7132705f --- /dev/null +++ b/src/content/dataSubmissions/ErrorDialog.tsx @@ -0,0 +1,155 @@ +import { Button, Dialog, DialogProps, IconButton, Stack, Typography, styled } from "@mui/material"; +import { ReactComponent as CloseIconSvg } from "../../assets/icons/close_icon.svg"; +import { FormatDate } from "../../utils"; + +const StyledDialog = styled(Dialog)({ + "& .MuiDialog-paper": { + maxWidth: "none", + width: "731px !important", + padding: "38px 42px 68px", + borderRadius: "8px", + border: "2px solid #E25C22", + background: "linear-gradient(0deg, #F2F6FA 0%, #F2F6FA 100%), #2E4D7B", + boxShadow: "0px 4px 45px 0px rgba(0, 0, 0, 0.40)", + }, +}); + +const StyledCloseDialogButton = styled(IconButton)(() => ({ + position: 'absolute', + right: "21px", + top: "11px", + padding: "10px", + "& svg": { + color: "#44627C" + } +})); + +const StyledCloseButton = styled(Button)({ + display: "flex", + width: "128px", + height: "42px", + padding: "12px 60px", + justifyContent: "center", + alignItems: "center", + borderRadius: "8px", + border: "1px solid #000", + color: "#000", + textAlign: "center", + fontFamily: "'Nunito', 'Rubik', sans-serif", + fontSize: "16px", + fontStyle: "normal", + fontWeight: "700", + lineHeight: "24px", + letterSpacing: "0.32px", + textTransform: "none", + alignSelf: "center", + marginTop: "45px", + "&:hover": { + background: "transparent", + border: "1px solid #000", + } +}); + +const StyledHeader = styled(Typography)({ + color: "#929292", + fontFamily: "'Nunito Sans', 'Rubik', sans-serif", + fontSize: "13px", + fontStyle: "normal", + fontWeight: "400", + lineHeight: "27px", + letterSpacing: "0.5px", + textTransform: "uppercase", + marginBottom: "2px" +}); + +const StyledTitle = styled(Typography)({ + color: "#E25C22", + fontFamily: "'Nunito Sans', 'Rubik', sans-serif", + fontSize: "35px", + fontStyle: "normal", + fontWeight: "900", + lineHeight: "30px", + marginBottom: "60px" +}); + +const StyledSubtitle = styled(Typography)({ + color: "#453D3D", + fontFamily: "'Public Sans', sans-serif", + fontSize: "14px", + fontStyle: "normal", + fontWeight: 700, + lineHeight: "20px", + letterSpacing: "0.14px", + textTransform: "uppercase", +}); + +const StyledErrorItem = styled(Typography)({ + color: "#131313", + fontFamily: "'Public Sans', sans-serif", + fontSize: "16px", + fontStyle: "normal", + fontWeight: 400, + lineHeight: "22px", +}); + +const StyledErrorDetails = styled(Stack)({ + padding: "10px", +}); + +type Props = { + header?: string; + title?: string; + closeText?: string; + errors: ErrorMessage[]; + uploadedDate: string; + onClose?: () => void; +} & Omit; + +const ErrorDialog = ({ + header, + title, + closeText = "Close", + errors, + uploadedDate, + onClose, + open, + ...rest +}: Props) => { + const handleCloseDialog = () => { + if (typeof onClose === "function") { + onClose(); + } + }; + + return ( + + + + + + {header} + + + {title} + + + + {`${errors?.length || 0} ${errors?.length === 1 ? "COUNT" : "COUNTS"} ON ${FormatDate(uploadedDate, "M/D/YYYY", "N/A")}:`} + + + {errors?.map((error: ErrorMessage, idx: number) => ( + // eslint-disable-next-line react/no-array-index-key + + {`${idx + 1}. ${error.description}`} + + ))} + + + + {closeText} + + + ); +}; + +export default ErrorDialog; diff --git a/src/content/dataSubmissions/QualityControl.tsx b/src/content/dataSubmissions/QualityControl.tsx index eab9a33d..ed865662 100644 --- a/src/content/dataSubmissions/QualityControl.tsx +++ b/src/content/dataSubmissions/QualityControl.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from "react"; +import { useMemo, useRef, useState } from "react"; import { useLazyQuery } from "@apollo/client"; import { useParams } from "react-router-dom"; import { isEqual } from "lodash"; @@ -6,6 +6,8 @@ import { Box, Button, styled } from "@mui/material"; import { SUBMISSION_QC_RESULTS, submissionQCResultsResp } from "../../graphql"; import GenericTable, { Column, FetchListing, TableMethods } from "../../components/DataSubmissions/GenericTable"; import { FormatDate } from "../../utils"; +import ErrorDialog from "./ErrorDialog"; +import QCResultsContext from "./Contexts/QCResultsContext"; const StyledErrorDetailsButton = styled(Button)({ display: "inline", @@ -14,7 +16,7 @@ const StyledErrorDetailsButton = styled(Button)({ fontSize: "16px", fontStyle: "normal", fontWeight: 600, - lineHeight: "19.6px", + lineHeight: "19px", padding: 0, textDecorationLine: "underline", textTransform: "none", @@ -24,70 +26,22 @@ const StyledErrorDetailsButton = styled(Button)({ }, }); -const testData: QCResult[] = [ - { - submissionID: "c4366aab-8adf-41e9-9432-864b2101231d", - nodeType: "Participant", - batchID: "123a5678-8adf-41e9-9432-864b2108191d", - nodeID: "123a5678-8adf-41e9-9432-864b2108191d", - CRDC_ID: "123a5678-8adf-41e9-9432-864b2108191d", - severity: "Error", - description: [ - { - title: "Incorrect control vocabulary.", - description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Eget duis at tellus at urna condimentum mattis. Eget nunc scelerisque viverra mauris in aliquam sem.", - }, - { - title: "Missing required field.", - description: "Elit eget gravida cum sociis natoque. Risus quis varius quam quisque id diam vel quam. Senectus et netus et malesuada fames ac turpis egestas. Scelerisque eu ultrices vitae auctor eu augue ut.", - }, - { - title: "Value not in the range.", - description: "Consectetur adipiscing elit pellentesque habitant morbi tristique senectus. Nec ullamcorper sit amet risus. Faucibus in ornare quam viverra orci sagittis. Venenatis urna cursus eget nunc.", - }, - ], - uploadedDate: "2023-11-08T19:39:15.469Z", - }, - { - submissionID: "c4366aab-8adf-41e9-9432-864b2101231d", - nodeType: "Participant", - batchID: "123a5678-8adf-41e9-9432-864b2108191d", - nodeID: "123a5678-8adf-41e9-9432-864b2108191d", - CRDC_ID: "123a5678-8adf-41e9-9432-864b2108191d", - severity: "Error", - description: [ - { - title: "Missing required field.", - description: "Elit eget gravida cum sociis natoque. Risus quis varius quam quisque id diam vel quam. Senectus et netus et malesuada fames ac turpis egestas. Scelerisque eu ultrices vitae auctor eu augue ut.", - }, - ], - uploadedDate: "2023-11-08T19:39:15.469Z", - }, - { - submissionID: "c4366aab-8adf-41e9-9432-864b2101231d", - nodeType: "Participant", - batchID: "123a5678-8adf-41e9-9432-864b2108191d", - nodeID: "123a5678-8adf-41e9-9432-864b2108191d", - CRDC_ID: "123a5678-8adf-41e9-9432-864b2108191d", - severity: "Error", - description: [ - { - title: "Value not in the range.", - description: "Consectetur adipiscing elit pellentesque habitant morbi tristique senectus. Nec ullamcorper sit amet risus. Faucibus in ornare quam viverra orci sagittis. Venenatis urna cursus eget nunc.", - }, - { - title: "Incorrect control vocabulary.", - description: "Elit eget gravida cum sociis natoque. Risus quis varius quam quisque id diam vel quam. Senectus et netus et malesuada fames ac turpis egestas. Scelerisque eu ultrices vitae auctor eu augue ut.", - }, - ], - uploadedDate: "2023-11-08T19:39:15.469Z", - }, -]; +const StyledNodeType = styled(Box)({ + display: "flex", + alignItems: "center", + textTransform: "capitalize" +}); + +const StyledSeverity = styled(Box)({ + minHeight: 76.5, + display: "flex", + alignItems: "center", +}); const columns: Column[] = [ { label: "Type", - renderValue: (data) => data?.nodeType, + renderValue: (data) => {data?.nodeType}, field: "nodeType", }, { @@ -107,33 +61,41 @@ const columns: Column[] = [ }, { label: "Severity", - renderValue: (data) => {data?.severity}, + renderValue: (data) => {data?.severity}, field: "severity", }, { - label: "Submitted Date", + label: "Uploaded Date", renderValue: (data) => (data?.uploadedDate ? `${FormatDate(data.uploadedDate, "MM-DD-YYYY [at] hh:mm A")}` : ""), field: "uploadedDate", default: true }, { - label: "Description", + label: "Reasons", renderValue: (data) => data?.description?.length > 0 && ( - <> - {data?.description[0].title} - {" "} - {}} - variant="text" - disableRipple - disableTouchRipple - disableFocusRipple - > - See details - - + + {({ handleOpenErrorDialog }) => ( + <> + {data.description[0]?.title} + {" "} + handleOpenErrorDialog && handleOpenErrorDialog(data)} + variant="text" + disableRipple + disableTouchRipple + disableFocusRipple + > + See details + + + )} + ), field: "description", + sortDisabled: true, + sx: { + minWidth: "260px", + } }, ]; @@ -143,9 +105,11 @@ const QualityControl = () => { const [loading, setLoading] = useState(false); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [error, setError] = useState(null); - const [data, setData] = useState(testData); + const [data, setData] = useState([]); const [prevData, setPrevData] = useState>(null); - const [totalData, setTotalData] = useState(testData.length); + const [totalData, setTotalData] = useState(0); + const [openErrorDialog, setOpenErrorDialog] = useState(false); + const [selectedRow, setSelectedRow] = useState(null); const tableRef = useRef(null); const [submissionQCResults] = useLazyQuery(SUBMISSION_QC_RESULTS, { @@ -192,18 +156,37 @@ const QualityControl = () => { } }; + const handleOpenErrorDialog = (data: QCResult) => { + setOpenErrorDialog(true); + setSelectedRow(data); + }; + + const providerValue = useMemo(() => ({ + handleOpenErrorDialog + }), [handleOpenErrorDialog]); + return ( <> - `${idx}_${item.batchID}_${item.nodeID}`} - onFetchData={handleFetchQCResults} + + `${idx}_${item.batchID}_${item.nodeID}`} + onFetchData={handleFetchQCResults} + /> + + setOpenErrorDialog(false)} + header="Data Submission" + title="Reasons" + errors={selectedRow?.description} + uploadedDate={selectedRow?.uploadedDate} /> - {/* Error Dialog */} ); }; diff --git a/src/graphql/submissionQCResults.ts b/src/graphql/submissionQCResults.ts index 3b84cc86..3a021f81 100644 --- a/src/graphql/submissionQCResults.ts +++ b/src/graphql/submissionQCResults.ts @@ -15,16 +15,19 @@ export const query = gql` orderBy: $orderBy sortDirection: $sortDirection ) { - submissionID - nodeType - batchID - nodeID - CRDC_ID - severity - uploadedDate - description { - title - description + total + results { + submissionID + nodeType + batchID + nodeID + CRDC_ID + severity + uploadedDate + description { + title + description + } } } }