Skip to content

Commit

Permalink
Merge pull request #812 from samuel-coutinho/feb-17-add-useAssessment…
Browse files Browse the repository at this point in the history
…Answers

Create useAssessmentAnswers to fetch Assessment questions and answers.
  • Loading branch information
MuhammadKhalilzadeh authored Feb 20, 2025
2 parents 614f30d + 2dbfce3 commit 9ede67f
Show file tree
Hide file tree
Showing 13 changed files with 576 additions and 489 deletions.
149 changes: 149 additions & 0 deletions Clients/src/application/hooks/useAssessmentAnswers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { useEffect, useState, useCallback } from "react";
import { getAllEntities } from "../repository/entity.repository";

interface AssessmentProps {
assessmentId: string | null | undefined;
}

export interface Question {
id: number;
subtopicId: string;
questionText: string;
answerType: string;
evidenceFileRequired: boolean;
hint: string;
isRequired: boolean;
priorityLevel: "high priority" | "medium priority" | "low priority";
evidenceFiles?: File[];
answer: string;
}

export interface Subtopic {
id: number;
topicId: number;
name: string;
questions: Question[];
}

export interface Topic {
id: number;
assessmentId: string;
title: string;
subtopics: Subtopic[];
}

interface ApiQuestion {
id: number;
subtopic_id: string;
question: string;
answer_type: string;
evidence_file_required: boolean;
hint: string;
is_required: boolean;
priority_level: "high priority" | "medium priority" | "low priority";
evidence_files?: File[];
answer: string;
}

interface ApiSubtopic {
id: number;
topic_id: number;
name: string;
questions: ApiQuestion[];
}

interface ApiTopic {
id: number;
assessment_id: string;
title: string;
subTopics: ApiSubtopic[];
}

interface ApiResponse {
data: {
message: {
topics: ApiTopic[];
}
}
}

const convertResponseAttributes = (response: ApiResponse): Topic[] => {
const responseTopics = response?.data?.message?.topics;

const topics = responseTopics.map((topic: ApiTopic) => {
return {
id: topic.id,
assessmentId: topic.assessment_id,
title: topic.title,
subtopics: topic.subTopics.map((subtopic: ApiSubtopic) => {
return {
id: subtopic.id,
topicId: subtopic.topic_id,
name: subtopic.name,
questions: subtopic.questions.map((question: ApiQuestion) => {
return {
id: question.id,
subtopicId: question.subtopic_id,
questionText: question.question,
answerType: question.answer_type,
evidenceFileRequired: question.evidence_file_required,
hint: question.hint,
isRequired: question.is_required,
priorityLevel: question.priority_level,
evidenceFiles: question.evidence_files || [],
answer: question.answer,
};
}),
};
}),
};
});

return topics
};

const useAssessmentAnswers = ({assessmentId}: AssessmentProps) => {
const [topics, setTopics] = useState<Topic[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

const fetchAssessmentAnswers = useCallback(async ({controller}: { controller: AbortController }) => {
if (!controller) return;

setIsLoading(true);
setError(null);

try {
const response = await getAllEntities({routeUrl: `/assessments/getAnswers/${assessmentId}`})
if (response?.data?.message?.topics?.length > 0) {
const topics = convertResponseAttributes(response);
setTopics(topics);
} else {
setError("No assessment answers found for this project.")
}
} catch (error) {
console.error('An error occurred:', error);
if (error instanceof Error) {
setError(error.message);
} else {
setError(String(error));
}
} finally {
setIsLoading(false);
}
}, [assessmentId]);

useEffect(() => {
const controller = new AbortController();
// Fetch assessment answers only if assessmentId is provided
if (assessmentId) {
fetchAssessmentAnswers({ controller });
}
return () => controller.abort();
}, [assessmentId, fetchAssessmentAnswers]);


return {topics, isLoading, error}
}

export default useAssessmentAnswers
33 changes: 33 additions & 0 deletions Clients/src/application/tools/alertUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Handles the display of an alert by setting the alert properties and
* automatically clearing the alert after a specified timeout.
*
* @param {HandleAlertProps} props - The properties for handling the alert.
* @param {string} props.variant - The variant of the alert (e.g., success, error).
* @param {string} props.body - The body message of the alert.
* @param {string} props.title - The title of the alert.
* @param {React.Dispatch<React.SetStateAction<AlertProps | null>>} props.setAlert - The function to set the alert state.
* @returns {() => void} A function to clear the timeout for the alert.
*/
import { AlertProps } from "../../presentation/components/Alert";

interface HandleAlertProps extends AlertProps {
setAlert: React.Dispatch<React.SetStateAction<AlertProps | null>>;
}

const ALERT_TIMEOUT = 2500

const handleAlert = ({ variant, body, title, setAlert }: HandleAlertProps) => {
setAlert({
variant,
title,
body,
});
const timeoutId = setTimeout(() => {
setAlert(null)
}, ALERT_TIMEOUT)
return () => clearTimeout(timeoutId)
};


export { handleAlert }
47 changes: 16 additions & 31 deletions Clients/src/presentation/components/AddNewVendorRiskForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import { apiServices } from "../../../infrastructure/api/networkServices";
import { VerifyWiseContext } from "../../../application/contexts/VerifyWise.context";
import { useSearchParams } from "react-router-dom";
import useUsers from "../../../application/hooks/useUsers";
import { handleAlert } from "../../../application/tools/alertUtils";
import { AlertProps } from "../Alert";

const Alert = lazy(() => import("../Alert"));

interface RiskSectionProps {
closePopup: () => void;
onSuccess: () => void;
popupStatus: string;
popupStatus: string;
}

interface FormValues {
Expand All @@ -43,12 +45,6 @@ const initialState: FormValues = {
riskDescription: "",
};

interface AlertProps {
variant: "success" | "info" | "warning" | "error";
title?: string;
body: string;
}

/**
* `AddNewVendorRiskForm` is a functional component that renders a form for adding a new vendor risk.
* It includes fields for vendor name, action owner, risk name, review date, and risk description.
Expand Down Expand Up @@ -96,17 +92,13 @@ interface AlertProps {
const AddNewVendorRiskForm: FC<RiskSectionProps> = ({ closePopup, onSuccess, popupStatus }) => {
const theme = useTheme();
const { inputValues, dashboardValues } = useContext(VerifyWiseContext);

const [searchParams] = useSearchParams();
const projectId = searchParams.get("projectId");

const [values, setValues] = useState<FormValues>(initialState);
const [errors, setErrors] = useState<FormErrors>({});
const [alert, setAlert] = useState<{
variant: "success" | "info" | "warning" | "error";
title?: string;
body: string;
} | null>(null);
const [alert, setAlert] = useState<AlertProps | null>(null);
const { users } = useUsers();

const handleDateChange = (newDate: Dayjs | null) => {
Expand Down Expand Up @@ -166,17 +158,6 @@ const AddNewVendorRiskForm: FC<RiskSectionProps> = ({ closePopup, onSuccess, pop
return Object.keys(newErrors).length === 0;
};

const handleAlert = ({ variant, body, title }: AlertProps) => {
setAlert({
variant,
title,
body,
});
setTimeout(() => {
setAlert(null);
}, 2500);
};

const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (validateForm()) {
Expand All @@ -188,7 +169,7 @@ const AddNewVendorRiskForm: FC<RiskSectionProps> = ({ closePopup, onSuccess, pop
"owner": values.actionOwner,
"risk_level": "High Risk", // Need to remove the field
"review_date": values.reviewDate,
// "description": values.riskDescription
// "description": values.riskDescription
}

console.log(formData)
Expand All @@ -197,39 +178,43 @@ const AddNewVendorRiskForm: FC<RiskSectionProps> = ({ closePopup, onSuccess, pop
try {
const response = await apiServices.put("/vendorRisks/" + inputValues.id, formData);
console.log(response)
if (response.status === 202) {
if (response.status === 202) {
onSuccess();
closePopup();
}else{
handleAlert({
variant: "error",
body: "Error occurs while updating the risk.",
setAlert
});
}
} catch (error) {
handleAlert({
variant: "error",
body: error + " occurs while sending a request.",
setAlert
});
}
}else{
console.log(formData)
try {
const response = await apiServices.post("/vendorRisks", formData);
console.log(response)
if (response.status === 201) {
if (response.status === 201) {
onSuccess();
closePopup();
}else{
handleAlert({
variant: "error",
body: "Error occurs while creating a risk.",
setAlert
});
}
} catch (error) {
handleAlert({
variant: "error",
body: error + " occurs while sending a request.",
setAlert
});
}
}
Expand All @@ -248,15 +233,15 @@ const AddNewVendorRiskForm: FC<RiskSectionProps> = ({ closePopup, onSuccess, pop
// riskData
const currentRiskData: FormValues = {
...initialState,
riskName: inputValues.risk_name ?? "",
reviewDate: inputValues.review_date ? dayjs(inputValues.review_date).toISOString() : "",
riskName: inputValues.risk_name ?? "",
reviewDate: inputValues.review_date ? dayjs(inputValues.review_date).toISOString() : "",
vendorName: parseInt(inputValues.vendor_name) ?? 0,
actionOwner: parseInt(inputValues.owner) ?? 0,
riskDescription: inputValues.risk_description ?? "",
riskDescription: inputValues.risk_description ?? "",
};
setValues(currentRiskData);
}
}, [popupStatus])
}, [popupStatus])

return (
<Stack>
Expand Down
6 changes: 3 additions & 3 deletions Clients/src/presentation/components/Alert/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ import singleTheme from "../../themes/v1SingleTheme";
* @property {boolean} [hasIcon] - Whether to display an icon in the alert. Defaults to true.
* @property {() => void} onClick - Callback function to handle click events.
*/
interface AlertProps {
export interface AlertProps {
variant: "success" | "info" | "warning" | "error";
title?: string;
body: string;
isToast: boolean;
isToast?: boolean;
hasIcon?: boolean;
onClick: () => void;
onClick?: () => void;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion Clients/src/presentation/components/FileUpload/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const FileUploadComponent = ({onClose, onHeightChange, topicId = 0, assessmentsV

const wrapperRef = useRef<HTMLDivElement>(null);

const [fileList, setFileList] = useState<FileProps[]>(assessmentsValues[topicId].file || []);
const [fileList, setFileList] = useState<FileProps[]>(assessmentsValues[topicId]?.file || []);

const onDragEnter = () => wrapperRef?.current?.classList.add('dragover');

Expand Down
4 changes: 2 additions & 2 deletions Clients/src/presentation/components/FileUpload/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AssessmentValue } from "../../pages/Assessment/NewAssessment/AllAssessments";
import { Topic } from "../../../application/hooks/useAssessmentAnswers";

export interface FileUploadProps {
open: boolean;
Expand All @@ -15,7 +15,7 @@ export interface FileUploadProps {
topicId?: number;
isSubtopic?: boolean;
setAssessmentsValue?: (value: any) => void;
assessmentsValues?: Record<number, AssessmentValue>;
assessmentsValues?: Topic[];
}

export interface FileProps {
Expand Down
Loading

0 comments on commit 9ede67f

Please sign in to comment.