From 3912d7fb42365a25fc6bf295599e1b5312b62ece Mon Sep 17 00:00:00 2001 From: "K. Allagbe" Date: Thu, 12 Dec 2024 18:27:13 -0500 Subject: [PATCH] issue #360: submit button --- docs/label-data-validation/flowchart.md | 15 ++- src/app/label-data-validation/[id]/page.tsx | 17 +-- src/app/label-data-validation/page.tsx | 119 +++++++++--------- src/components/Header.tsx | 2 +- src/components/LabelDataValidator.tsx | 13 +- src/components/OrganizationsForm.tsx | 2 +- src/components/StyledSkeleton.tsx | 2 +- .../HorizontalNonLinearStepper.test.tsx | 6 + src/components/stepper.tsx | 29 +++-- .../{ => client}/__tests__/common.test.ts | 14 +-- src/utils/{ => client}/common.ts | 3 +- src/utils/{ => client}/useBreakpoints.ts | 0 12 files changed, 117 insertions(+), 105 deletions(-) rename src/utils/{ => client}/__tests__/common.test.ts (99%) rename src/utils/{ => client}/common.ts (98%) rename src/utils/{ => client}/useBreakpoints.ts (100%) diff --git a/docs/label-data-validation/flowchart.md b/docs/label-data-validation/flowchart.md index b1a28fb0..d9cd5744 100644 --- a/docs/label-data-validation/flowchart.md +++ b/docs/label-data-validation/flowchart.md @@ -7,13 +7,13 @@ flowchart TD Start([Start]) --> LabelDataValidation["/label-data-validation"] LabelDataValidation --> ImagesUploaded{Images Uploaded?} ImagesUploaded -->|No| HomePage[Home] --> End([End]) - ImagesUploaded -->|Yes| GetExtract[GET /extract] + ImagesUploaded -->|Yes| DisplayUploadedImages["Display Uploaded Images"] --> GetExtract[GET /extract] GetExtract --> Success{Success?} Success -->|No| End Success -->|Yes| PostInspections[POST /inspections] PostInspections --> SaveSuccess{Success?} SaveSuccess -->|No| DisplayData[Display Data] --> End - SaveSuccess -->|Yes| StoreData[Store Data] --> LabelDataWithID["/label-data-validation/id"] --> End + SaveSuccess -->|Yes| LabelDataWithID["go /label-data-validation/id"] --> End ``` ## /label-data-validation/id @@ -22,13 +22,12 @@ flowchart TD flowchart TD Start([Start]) --> LabelDataValidationID["/label-data-validation/id"] LabelDataValidationID --> ImagesUploaded{Images Uploaded?} - ImagesUploaded -->|No| HomePage["/home"] - ImagesUploaded -->|Yes| DataInStore{Data in store?} - DataInStore -->|No| GetInspections[GET /inspections/id] - DataInStore -->|Yes| DisplayData["Display Data"] + ImagesUploaded -->|No| GetInspections[GET /inspections/id] + ImagesUploaded -->|Yes| DisplayUploadedImages["Display Uploaded Images"] + DisplayUploadedImages --> GetInspections GetInspections --> FetchSuccess{Success?} - FetchSuccess -->|No| HomePage - FetchSuccess -->|Yes| DisplayData + FetchSuccess -->|No| HomePage["/home"] + FetchSuccess -->|Yes| DisplayData["Display Data"] DisplayData --> End([End]) HomePage --> End ``` diff --git a/src/app/label-data-validation/[id]/page.tsx b/src/app/label-data-validation/[id]/page.tsx index b63927b6..028d7406 100644 --- a/src/app/label-data-validation/[id]/page.tsx +++ b/src/app/label-data-validation/[id]/page.tsx @@ -3,7 +3,7 @@ import LabelDataValidator from "@/components/LabelDataValidator"; import useAlertStore from "@/stores/alertStore"; import useUploadedFilesStore from "@/stores/fileStore"; import { DEFAULT_LABEL_DATA } from "@/types/types"; -import { mapInspectionToLabelData } from "@/utils/common"; +import { mapInspectionToLabelData } from "@/utils/client/common"; import { Inspection } from "@/utils/server/backend"; import axios from "axios"; import { useRouter } from "next/navigation"; @@ -16,31 +16,23 @@ export default function Page({ params }: { params: { id: string } }) { const { uploadedFiles } = useUploadedFilesStore(); const { showAlert } = useAlertStore(); const router = useRouter(); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(true); const [labelData, setLabelData] = useState(DEFAULT_LABEL_DATA); const [inspection, setInspection] = useState(null); useEffect(() => { - // if (uploadedFiles.length === 0) { - // showAlert("No files uploaded.", "error"); - // router.push("/"); - // return; - // } - if (!validate(id)) { showAlert("Invalid id.", "error"); router.push("/"); return; } - const username = ""; + const username = "frontend"; const password = ""; const authHeader = "Basic " + btoa(`${username}:${password}`); const controller = new AbortController(); const signal = controller.signal; - setLoading(true); - axios .get(`/api/inspections/${id}`, { headers: { Authorization: authHeader }, @@ -59,6 +51,8 @@ export default function Page({ params }: { params: { id: string } }) { } else { console.error(error); setLoading(false); + showAlert("Failed to fetch inspection.", "error"); + router.push("/"); } }); // not disabling loading in finally() in case of strict mode abort @@ -78,6 +72,7 @@ export default function Page({ params }: { params: { id: string } }) { labelData={labelData} setLabelData={setLabelData} loading={loading} + inspectionId={id} /> ); } diff --git a/src/app/label-data-validation/page.tsx b/src/app/label-data-validation/page.tsx index e1914d85..41cca581 100644 --- a/src/app/label-data-validation/page.tsx +++ b/src/app/label-data-validation/page.tsx @@ -6,7 +6,7 @@ import { DEFAULT_LABEL_DATA } from "@/types/types"; import { mapLabelDataOutputToLabelData, processAxiosError, -} from "@/utils/common"; +} from "@/utils/client/common"; import { Inspection, LabelDataOutput } from "@/utils/server/backend"; import axios from "axios"; import { useRouter } from "next/navigation"; @@ -15,7 +15,7 @@ import { useEffect, useState } from "react"; function LabelDataValidationPage() { const { uploadedFiles } = useUploadedFilesStore(); const [labelData, setLabelData] = useState(DEFAULT_LABEL_DATA); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(true); const { showAlert } = useAlertStore(); const router = useRouter(); @@ -29,69 +29,64 @@ function LabelDataValidationPage() { const controller = new AbortController(); const signal = controller.signal; - const extractAndSave = async () => { - setLoading(true); - const formData = new FormData(); + setLoading(true); + const formData = new FormData(); - uploadedFiles.forEach((fileUploaded) => { - const file = fileUploaded.getFile(); - formData.append("files", file); - }); - - const username = ""; - const password = ""; - const authHeader = "Basic " + btoa(`${username}:${password}`); + uploadedFiles.forEach((fileUploaded) => { + const file = fileUploaded.getFile(); + formData.append("files", file); + }); - axios - .post("/api/extract-label-data", formData, { - headers: { Authorization: authHeader }, - signal, - }) - .then(async (response) => { - const labelDataOutput: LabelDataOutput = response.data; - const labelData = mapLabelDataOutputToLabelData(labelDataOutput); + const username = "frontend"; + const password = ""; + const authHeader = "Basic " + btoa(`${username}:${password}`); + axios + .post("/api/extract-label-data", formData, { + headers: { Authorization: authHeader }, + signal, + }) + .then(async (response) => { + const labelDataOutput: LabelDataOutput = response.data; + const labelData = mapLabelDataOutputToLabelData(labelDataOutput); - formData.append("labelData", JSON.stringify(labelDataOutput)); - axios - .post("/api/inspections", formData, { - headers: { Authorization: authHeader }, - signal, - }) - .then((response) => { - const inspection: Inspection = response.data; - router.push(`/label-data-validation/${inspection.inspection_id}`); - return null; - }) - .catch((error) => { - if (axios.isCancel(error)) { - console.log("Request canceled"); - } else { - showAlert( - `Label data initial save failed: ${processAxiosError(error)}`, - "error", - ); - setLoading(false); - } - }) - .finally(() => { - setLabelData(labelData); - // not disabling loading here in case of strict mode abort - }); - }) - .catch((error) => { - if (axios.isCancel(error)) { - console.log("Request canceled"); - } else { - showAlert( - `Label data extraction failed: ${processAxiosError(error)}`, - "error", - ); - setLoading(false); - } - }); - }; - - extractAndSave(); + formData.append("labelData", JSON.stringify(labelDataOutput)); + axios + .post("/api/inspections", formData, { + headers: { Authorization: authHeader }, + signal, + }) + .then((response) => { + const inspection: Inspection = response.data; + router.push(`/label-data-validation/${inspection.inspection_id}`); + return null; + }) + .catch((error) => { + if (axios.isCancel(error)) { + console.log("Request canceled"); + } else { + showAlert( + `Label data initial save failed: ${processAxiosError(error)}`, + "error", + ); + setLoading(false); + } + }) + .finally(() => { + setLabelData(labelData); + // not disabling loading here in case of strict mode abort + }); + }) + .catch((error) => { + if (axios.isCancel(error)) { + console.log("Request canceled"); + } else { + showAlert( + `Label data extraction failed: ${processAxiosError(error)}`, + "error", + ); + setLoading(false); + } + }); return () => { controller.abort(); // avoids react strict mode double fetch diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 386a248e..4eae2151 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,6 +1,6 @@ "use client"; import UserMenu from "@/components/UserMenu"; -import useBreakpoints from "@/utils/useBreakpoints"; +import useBreakpoints from "@/utils/client/useBreakpoints"; import AccountCircleIcon from "@mui/icons-material/AccountCircle"; import MenuIcon from "@mui/icons-material/Menu"; import { diff --git a/src/components/LabelDataValidator.tsx b/src/components/LabelDataValidator.tsx index 8cd81e3a..eb55b405 100644 --- a/src/components/LabelDataValidator.tsx +++ b/src/components/LabelDataValidator.tsx @@ -12,8 +12,8 @@ import { StepStatus, } from "@/components/stepper"; import { FormComponentProps, LabelData } from "@/types/types"; -import { checkFieldArray, checkFieldRecord } from "@/utils/common"; -import useBreakpoints from "@/utils/useBreakpoints"; +import { checkFieldArray, checkFieldRecord } from "@/utils/client/common"; +import useBreakpoints from "@/utils/client/useBreakpoints"; import { Box, Container, Typography } from "@mui/material"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -23,6 +23,7 @@ interface LabelDataValidatorProps { files: File[]; labelData: LabelData; setLabelData: React.Dispatch>; + inspectionId?: string; } function LabelDataValidator({ @@ -30,6 +31,7 @@ function LabelDataValidator({ files, labelData, setLabelData, + inspectionId, }: LabelDataValidatorProps) { const { t } = useTranslation("labelDataValidator"); const imageFiles = files; @@ -111,6 +113,10 @@ function LabelDataValidator({ ), ]; + const submit = () => { + console.log("inspectionId:", inspectionId, "labelData", labelData); + }; + useEffect(() => { const verified = labelData.organizations.every((org) => checkFieldRecord(org), @@ -173,6 +179,7 @@ function LabelDataValidator({ stepStatuses={steps.map((step) => step.stepStatus)} activeStep={activeStep} setActiveStep={setActiveStep} + submit={submit} /> )} @@ -195,6 +202,7 @@ function LabelDataValidator({ stepStatuses={steps.map((step) => step.stepStatus)} activeStep={activeStep} setActiveStep={setActiveStep} + submit={submit} /> )} @@ -218,6 +226,7 @@ function LabelDataValidator({ stepStatuses={steps.map((step) => step.stepStatus)} activeStep={activeStep} setActiveStep={setActiveStep} + submit={submit} /> diff --git a/src/components/OrganizationsForm.tsx b/src/components/OrganizationsForm.tsx index e35b8d4c..ebd23af0 100644 --- a/src/components/OrganizationsForm.tsx +++ b/src/components/OrganizationsForm.tsx @@ -4,7 +4,7 @@ import { LabelData, Organization, } from "@/types/types"; -import { checkFieldRecord } from "@/utils/common"; +import { checkFieldRecord } from "@/utils/client/common"; import AddIcon from "@mui/icons-material/Add"; import DeleteIcon from "@mui/icons-material/Delete"; import DoneAllIcon from "@mui/icons-material/DoneAll"; diff --git a/src/components/StyledSkeleton.tsx b/src/components/StyledSkeleton.tsx index 063e6486..ef2b737b 100644 --- a/src/components/StyledSkeleton.tsx +++ b/src/components/StyledSkeleton.tsx @@ -13,7 +13,7 @@ const StyledSkeleton: React.FC = ({ return ( = ({ }) => { const [activeStep, setActiveStep] = useState(initialActiveStep); const [stepStatuses] = useState(initialStepStatuses); + // create a mock submit function + const submit = jest.fn(); return ( <> @@ -32,12 +34,14 @@ const TestStepperWrapper: React.FC = ({ activeStep={activeStep} setActiveStep={setActiveStep} stepStatuses={stepStatuses} + submit={submit} /> ); @@ -142,4 +146,6 @@ describe("HorizontalNonLinearStepper with StepperControls", () => { const renderedSteps = container.querySelectorAll(".MuiStepButton-root"); expect(renderedSteps).toHaveLength(2); }); + + }); diff --git a/src/components/stepper.tsx b/src/components/stepper.tsx index e76321bd..306996ab 100644 --- a/src/components/stepper.tsx +++ b/src/components/stepper.tsx @@ -17,6 +17,7 @@ export interface StepperProps { stepStatuses: StepStatus[]; activeStep: number; setActiveStep: React.Dispatch>; + submit: () => void; } export const HorizontalNonLinearStepper: React.FC = ({ @@ -39,7 +40,7 @@ export const HorizontalNonLinearStepper: React.FC = ({ }); } }, [activeStep, stepRefs]); - + return ( @@ -68,31 +69,37 @@ export const HorizontalNonLinearStepper: React.FC = ({ export const StepperControls: React.FC = ({ stepTitles, activeStep, + stepStatuses, setActiveStep, + submit, }) => { const stepsTotal = stepTitles.length; - const handleNext = () => { - setActiveStep((prev) => Math.min(prev + 1, stepsTotal - 1)); - }; - - const handleBack = () => { - setActiveStep((prev) => Math.max(prev - 1, 0)); - }; - return ( +