Skip to content

Commit b90deec

Browse files
authored
Merge pull request #285 from nitheesh-aot/approval-persist
Approval persist
2 parents a587bf5 + 1d3db7a commit b90deec

File tree

10 files changed

+203
-38
lines changed

10 files changed

+203
-38
lines changed

compliance-api/src/compliance_api/schemas/inspection_approval.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def validate_fields(
8989
"invalid": f"Not a valid datetime. Expected format: {INPUT_DATE_TIME_FORMAT}."
9090
},
9191
),
92-
"date_respose": fields.DateTime(
92+
"date_response": fields.DateTime(
9393
format=INPUT_DATE_TIME_FORMAT,
9494
metadata={
9595
"description": "The date when the actual response received from proponent in ISO 8601 format."

compliance-web/src/components/App/Inspections/Profile/Reports/OfficerSteppr/OfficerStepper.tsx

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ import { useState } from "react";
44
import PreliminaryReview from "./PreliminaryReview";
55
import RegPartyResponse from "./RegPartyResponse";
66
import IRVersionSelect from "./IRVersionSelect";
7+
import { useUpdateIRApproval } from "@/hooks/useInspectionReports";
8+
import { useReportStore } from "../reportStore";
9+
import {
10+
InspectionRecordApprovalPayload,
11+
IRApproval,
12+
} from "@/models/IRApproval";
13+
import { notify } from "@/store/snackbarStore";
714

815
const steps = [
916
"Preliminary Review",
@@ -12,6 +19,12 @@ const steps = [
1219
];
1320

1421
export default function OfficerStepper() {
22+
const {
23+
inspectionData,
24+
inspectionReportsData,
25+
irApprovalsData,
26+
setIRApprovalsData,
27+
} = useReportStore();
1528
const [activeStep, setActiveStep] = useState(0);
1629

1730
const handleNext = () => {
@@ -26,6 +39,28 @@ export default function OfficerStepper() {
2639
setActiveStep((prevActiveStep) => prevActiveStep - 1);
2740
};
2841

42+
const onSuccess = (data: IRApproval) => {
43+
setIRApprovalsData([data]);
44+
notify.success("Inspection record approval updated");
45+
};
46+
47+
const { mutateAsync: updateIRApprovalStatusAsync } =
48+
useUpdateIRApproval(onSuccess);
49+
50+
const onUpdateIRApprovalStep = async (
51+
approvalPayloads: InspectionRecordApprovalPayload[]
52+
) => {
53+
for (const approvalPayload of approvalPayloads) {
54+
await updateIRApprovalStatusAsync({
55+
inspectionId: inspectionData?.id ?? 0,
56+
inspectionRecordId: inspectionReportsData?.id ?? 0,
57+
approvalId: irApprovalsData?.[0]?.id ?? 0,
58+
approvalPayload,
59+
});
60+
}
61+
handleNext();
62+
};
63+
2964
return (
3065
<Box
3166
sx={{
@@ -53,9 +88,18 @@ export default function OfficerStepper() {
5388
})}
5489
</Stepper>
5590
<Box sx={{ p: 2 }}>
56-
{activeStep === 0 && <PreliminaryReview onNext={handleNext} />}
91+
{activeStep === 0 && (
92+
<PreliminaryReview
93+
onUpdateIRApprovalStep={onUpdateIRApprovalStep}
94+
nextStep={handleNext}
95+
/>
96+
)}
5797
{activeStep === 1 && (
58-
<RegPartyResponse onNext={handleNext} onBack={handleBack} />
98+
<RegPartyResponse
99+
onUpdateIRApprovalStep={onUpdateIRApprovalStep}
100+
onNext={handleNext}
101+
onBack={handleBack}
102+
/>
59103
)}
60104
{activeStep === 2 && (
61105
<IRVersionSelect onNext={handleNext} onBack={handleBack} />

compliance-web/src/components/App/Inspections/Profile/Reports/OfficerSteppr/PreliminaryReview.tsx

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import dayjs from "dayjs";
66
import { FormProvider, useForm } from "react-hook-form";
77
import * as yup from "yup";
88
import ControlledDateField from "@/components/Shared/Controlled/ControlledDateField";
9+
import { InspectionRecordApprovalPayload } from "@/models/IRApproval";
10+
import dateUtils from "@/utils/dateUtils";
11+
import { useReportStore } from "../reportStore";
912

1013
const preliminaryReviewFormSchema = yup.object().shape({
1114
dateSent: yup
@@ -28,21 +31,54 @@ const initFormData: PreliminaryReviewSchemaType = {
2831
};
2932

3033
type PreliminaryReviewProps = {
31-
onNext: () => void;
34+
onUpdateIRApprovalStep: (
35+
approvalPayloads: InspectionRecordApprovalPayload[]
36+
) => void;
37+
nextStep: () => void;
3238
};
3339

34-
const PreliminaryReview: React.FC<PreliminaryReviewProps> = ({ onNext }) => {
40+
const PreliminaryReview: React.FC<PreliminaryReviewProps> = ({
41+
onUpdateIRApprovalStep,
42+
nextStep,
43+
}) => {
44+
const { irApprovalsData } = useReportStore();
45+
3546
const defaultValues = useMemo<PreliminaryReviewSchemaType>(() => {
47+
const currentApproval = irApprovalsData?.[0];
48+
if (currentApproval) {
49+
return {
50+
dateSent: currentApproval.date_report_sent
51+
? dayjs(currentApproval.date_report_sent)
52+
: (undefined as unknown as Dayjs),
53+
dueDate: currentApproval.date_expected_return
54+
? dayjs(currentApproval.date_expected_return)
55+
: (undefined as unknown as Dayjs),
56+
};
57+
}
3658
return initFormData;
37-
}, []);
59+
}, [irApprovalsData]);
3860

3961
const methods = useForm<PreliminaryReviewSchemaType>({
4062
resolver: yupResolver(preliminaryReviewFormSchema),
4163
mode: "onBlur",
4264
defaultValues,
4365
});
4466

45-
const { handleSubmit, watch, setValue, clearErrors } = methods;
67+
const {
68+
handleSubmit,
69+
watch,
70+
setValue,
71+
clearErrors,
72+
reset,
73+
formState: { isDirty },
74+
} = methods;
75+
76+
// Reset form with defaultValues when they change
77+
useEffect(() => {
78+
if (defaultValues.dateSent || defaultValues.dueDate) {
79+
reset(defaultValues);
80+
}
81+
}, [defaultValues, reset]);
4682

4783
// Watch for changes in dateSent
4884
const dateSent = watch("dateSent");
@@ -60,10 +96,29 @@ const PreliminaryReview: React.FC<PreliminaryReviewProps> = ({ onNext }) => {
6096
}, [dateSent, setValue, clearErrors]);
6197

6298
const onSubmitHandler = (data: PreliminaryReviewSchemaType) => {
63-
// eslint-disable-next-line no-console
64-
console.log(data);
65-
// TODO: Update approvals with data
66-
onNext();
99+
if (isDirty) {
100+
// Create an array to hold the approval payloads
101+
const approvalPayloads: InspectionRecordApprovalPayload[] = [
102+
{
103+
field_name: "date_report_sent",
104+
value: dateUtils.dateToISO(data.dateSent),
105+
},
106+
];
107+
108+
const daysDifference = data.dueDate.diff(data.dateSent, "day");
109+
110+
// Only add dueDate to the payload if it's different from the default (more than 5 days)
111+
if (daysDifference !== 5) {
112+
approvalPayloads.push({
113+
field_name: "date_expected_return",
114+
value: dateUtils.dateToISO(data.dueDate),
115+
});
116+
}
117+
118+
onUpdateIRApprovalStep(approvalPayloads);
119+
} else {
120+
nextStep();
121+
}
67122
};
68123

69124
return (
@@ -96,6 +151,7 @@ const PreliminaryReview: React.FC<PreliminaryReviewProps> = ({ onNext }) => {
96151
label="Due Date"
97152
height="2rem"
98153
isRequired={true}
154+
minDate={dateSent ?? undefined}
99155
/>
100156
</Box>
101157
<Button variant="outlined" size="small" type="submit">

compliance-web/src/components/App/Inspections/Profile/Reports/OfficerSteppr/RegPartyResponse.tsx

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import { yupResolver } from "@hookform/resolvers/yup";
22
import { Box, Button, Typography } from "@mui/material";
3-
import { useMemo } from "react";
3+
import { useEffect, useMemo } from "react";
44
import { FormProvider, useForm } from "react-hook-form";
55
import * as yup from "yup";
6-
import { Dayjs } from "dayjs";
6+
import dayjs, { Dayjs } from "dayjs";
77
import ControlledDateField from "@/components/Shared/Controlled/ControlledDateField";
88
import ControlledRadioButtonGroup from "@/components/Shared/Controlled/ControlledRadioButtonGroup";
9+
import { InspectionRecordApprovalPayload } from "@/models/IRApproval";
10+
import { useReportStore } from "../reportStore";
11+
import dateUtils from "@/utils/dateUtils";
912

1013
const regPartyResponseFormSchema = yup.object().shape({
1114
responseReceived: yup.string().required("Response selection is required"),
@@ -27,33 +30,70 @@ const initFormData: RegPartyResponseSchemaType = {
2730
};
2831

2932
type RegPartyResponseProps = {
33+
onUpdateIRApprovalStep: (
34+
approvalPayloads: InspectionRecordApprovalPayload[]
35+
) => void;
3036
onNext: () => void;
3137
onBack: () => void;
3238
};
3339

3440
const RegPartyResponse: React.FC<RegPartyResponseProps> = ({
41+
onUpdateIRApprovalStep,
3542
onNext,
3643
onBack,
3744
}) => {
45+
const { irApprovalsData } = useReportStore();
46+
3847
const defaultValues = useMemo<RegPartyResponseSchemaType>(() => {
48+
const currentApproval = irApprovalsData?.[0];
49+
if (currentApproval) {
50+
return {
51+
responseReceived: currentApproval.date_response ? "yes" : "no",
52+
responseDate: currentApproval.date_response
53+
? dayjs(currentApproval.date_response)
54+
: (undefined as unknown as Dayjs),
55+
};
56+
}
3957
return initFormData;
40-
}, []);
58+
}, [irApprovalsData]);
4159

4260
const methods = useForm<RegPartyResponseSchemaType>({
4361
resolver: yupResolver(regPartyResponseFormSchema),
4462
mode: "onBlur",
4563
defaultValues,
4664
});
4765

48-
const { handleSubmit, watch } = methods;
66+
const {
67+
handleSubmit,
68+
watch,
69+
reset,
70+
formState: { isDirty },
71+
} = methods;
72+
73+
// Reset form with defaultValues when they change
74+
useEffect(() => {
75+
if (defaultValues.responseReceived || defaultValues.responseDate) {
76+
reset(defaultValues);
77+
}
78+
}, [defaultValues, reset]);
4979

5080
const responseReceived = watch("responseReceived");
5181

5282
const onSubmitHandler = (data: RegPartyResponseSchemaType) => {
53-
// eslint-disable-next-line no-console
54-
console.log(data);
55-
// TODO: Update approvals with data
56-
onNext();
83+
if (isDirty) {
84+
const approvalPayloads: InspectionRecordApprovalPayload[] = [
85+
{
86+
field_name: "date_response",
87+
value:
88+
data.responseReceived === "yes" && data.responseDate
89+
? dateUtils.dateToISO(data.responseDate)
90+
: "",
91+
},
92+
];
93+
onUpdateIRApprovalStep(approvalPayloads);
94+
} else {
95+
onNext();
96+
}
5797
};
5898

5999
return (
@@ -73,7 +113,8 @@ const RegPartyResponse: React.FC<RegPartyResponseProps> = ({
73113
sx={{
74114
display: "flex",
75115
flexDirection: "row",
76-
justifyContent: responseReceived === "yes" ? "space-between" : "flex-end",
116+
justifyContent:
117+
responseReceived === "yes" ? "space-between" : "flex-end",
77118
alignItems: "center",
78119
gap: 1,
79120
mt: 2,

compliance-web/src/components/App/Inspections/Profile/Reports/ReportTopSection.tsx

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export default function ReportTopSection() {
2121
const currentUser = useCurrentLoggedInUser();
2222

2323
const {
24+
queryClient,
2425
inspectionData,
2526
inspectionReportsData,
2627
irApprovalsData,
@@ -40,6 +41,12 @@ export default function ReportTopSection() {
4041
);
4142
}, [staffData, currentUser]);
4243

44+
const refetchInspectionReportsData = () => {
45+
queryClient.invalidateQueries({
46+
queryKey: ["inspection-reports", inspectionData?.id],
47+
});
48+
};
49+
4350
const handleSendForApproval = () => {
4451
setOpen({
4552
content: (
@@ -48,19 +55,21 @@ export default function ReportTopSection() {
4855
onSubmit={(message) => {
4956
notify.success(message);
5057
setClose();
58+
refetchInspectionReportsData();
5159
}}
5260
/>
5361
),
5462
});
5563
};
5664

57-
const onSuccess = (data: IRApproval) => {
65+
const onApprovalSuccess = (data: IRApproval) => {
5866
setIRApprovalsData([data]);
5967
notify.success("Approval status updated");
68+
refetchInspectionReportsData();
6069
};
6170

6271
const { mutate: updateIRApprovalStatus } =
63-
useUpdateIRApprovalStatus(onSuccess);
72+
useUpdateIRApprovalStatus(onApprovalSuccess);
6473

6574
const handleApproval = (isApprove: boolean) => {
6675
const currentUserId =
@@ -82,8 +91,11 @@ export default function ReportTopSection() {
8291

8392
const isDisableApprovalButton = useMemo(() => {
8493
return (
85-
inspectionReportsData?.ir_progress ===
86-
IRProgressEnum.PRELIMINARY_DEPUTY_REVIEW && !isCurrentUserApprover
94+
[
95+
IRProgressEnum.PRELIMINARY_DEPUTY_REVIEW,
96+
IRProgressEnum.FINAL_DEPUTY_REVIEW,
97+
].includes(inspectionReportsData?.ir_progress as IRProgressEnum) &&
98+
!isCurrentUserApprover
8799
);
88100
}, [inspectionReportsData, isCurrentUserApprover]);
89101

@@ -96,15 +108,20 @@ export default function ReportTopSection() {
96108

97109
const isShowApprovalButtons = useMemo(() => {
98110
return (
99-
inspectionReportsData?.ir_progress ===
100-
IRProgressEnum.PRELIMINARY_DEPUTY_REVIEW && isCurrentUserApprover
111+
[
112+
IRProgressEnum.PRELIMINARY_DEPUTY_REVIEW,
113+
IRProgressEnum.FINAL_DEPUTY_REVIEW,
114+
].includes(inspectionReportsData?.ir_progress as IRProgressEnum) &&
115+
isCurrentUserApprover
101116
);
102117
}, [inspectionReportsData, isCurrentUserApprover]);
103118

104119
const isShowOfficerStepper = useMemo(() => {
105-
return (
106-
inspectionReportsData?.ir_progress === IRProgressEnum.PRELIMINARY_APPROVED
107-
);
120+
return [
121+
IRProgressEnum.PRELIMINARY_APPROVED,
122+
IRProgressEnum.FINAL_APPROVED,
123+
IRProgressEnum.HOLDER_PRELIMINARY_REVIEW,
124+
].includes(inspectionReportsData?.ir_progress as IRProgressEnum);
108125
}, [inspectionReportsData]);
109126

110127
return (
@@ -117,7 +134,9 @@ export default function ReportTopSection() {
117134
alignItems: "center",
118135
}}
119136
>
120-
<Typography variant="h6">Preliminary IR</Typography>
137+
<Typography variant="h6">
138+
{inspectionReportsData?.ir_status?.name} IR
139+
</Typography>
121140
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
122141
{isShowSendForApprovalButton && (
123142
<Button

0 commit comments

Comments
 (0)