|
| 1 | +"use client"; |
| 2 | + |
| 3 | +import { useState } from "react"; |
| 4 | +import { useRouter, useSearchParams } from "next/navigation"; |
| 5 | +import useKey from "@bciers/utils/src/useKey"; |
| 6 | + |
| 7 | +import { UUID } from "crypto"; |
| 8 | +import SingleStepTaskListForm from "@bciers/components/form/SingleStepTaskListForm"; |
| 9 | +import { RJSFSchema, UiSchema } from "@rjsf/utils"; |
| 10 | +import { IChangeEvent } from "@rjsf/core"; |
| 11 | +import { |
| 12 | + OperationInformationFormData, |
| 13 | + OperationInformationPartialFormData, |
| 14 | +} from "./types"; |
| 15 | +import { actionHandler } from "@bciers/actions"; |
| 16 | +import { |
| 17 | + RegistrationPurposes, |
| 18 | + regulatedOperationPurposes, |
| 19 | +} from "apps/registration/app/components/operations/registration/enums"; |
| 20 | +import { |
| 21 | + FormMode, |
| 22 | + FrontEndRoles, |
| 23 | + OperationTypes, |
| 24 | +} from "@bciers/utils/src/enums"; |
| 25 | +import { useSessionRole } from "@bciers/utils/src/sessionUtils"; |
| 26 | +import Note from "@bciers/components/layout/Note"; |
| 27 | +import Link from "next/link"; |
| 28 | +import ConfirmChangeOfFieldModal from "@/registration/app/components/operations/registration/ConfirmChangeOfFieldModal"; |
| 29 | +import { useFileUploadWidget } from "@bciers/components/form/widgets/FileWidget"; |
| 30 | + |
| 31 | +const OperationInformationForm = ({ |
| 32 | + formData, |
| 33 | + operationId, |
| 34 | + schema: initialSchema, |
| 35 | + eioSchema, |
| 36 | + generalSchema, |
| 37 | + uiSchema, |
| 38 | +}: { |
| 39 | + formData: OperationInformationPartialFormData; |
| 40 | + operationId: UUID; |
| 41 | + schema: RJSFSchema; |
| 42 | + eioSchema: RJSFSchema; |
| 43 | + generalSchema: RJSFSchema; |
| 44 | + uiSchema: UiSchema; |
| 45 | +}) => { |
| 46 | + const [error, setError] = useState(undefined); |
| 47 | + const [schema, setSchema] = useState(initialSchema); |
| 48 | + const [confirmedFormData, setConfirmedFormData] = useState(formData); |
| 49 | + const [ |
| 50 | + pendingChangeRegistrationPurpose, |
| 51 | + setPendingChangeRegistrationPurpose, |
| 52 | + ] = useState(""); |
| 53 | + const [isConfirmPurposeChangeModalOpen, setIsConfirmPurposeChangeModalOpen] = |
| 54 | + useState<boolean>(false); |
| 55 | + const [key, resetKey] = useKey(); |
| 56 | + const [formMode, setFormMode] = useState(FormMode.READ_ONLY); |
| 57 | + |
| 58 | + const router = useRouter(); |
| 59 | + // To get the user's role from the session |
| 60 | + const role = useSessionRole(); |
| 61 | + const searchParams = useSearchParams(); |
| 62 | + |
| 63 | + const [fileWidgetContext, submitWithFiles] = useFileUploadWidget(); |
| 64 | + |
| 65 | + const isRedirectedFromContacts = searchParams.get("from_contacts") as string; |
| 66 | + |
| 67 | + function checkMissingRepresentative(data: any) { |
| 68 | + if (data && data.status && data.registration_purpose) { |
| 69 | + return ( |
| 70 | + data.status === "Registered" && |
| 71 | + (!data.operation_representatives || |
| 72 | + data.operation_representatives.length === 0) |
| 73 | + ); |
| 74 | + } else return false; |
| 75 | + } |
| 76 | + const [isMissingRepresentative, setIsMissingRepresentative] = useState( |
| 77 | + checkMissingRepresentative(formData), |
| 78 | + ); |
| 79 | + |
| 80 | + const updateConfirmedFormData = (newPurpose: string) => { |
| 81 | + const isEio = |
| 82 | + newPurpose === RegistrationPurposes.ELECTRICITY_IMPORT_OPERATION; |
| 83 | + const newFormData = { |
| 84 | + ...confirmedFormData, |
| 85 | + registration_purpose: newPurpose, |
| 86 | + // When switching to EIO, set the type to EIO since that's the only valid option |
| 87 | + ...(isEio && { type: OperationTypes.EIO }), |
| 88 | + }; |
| 89 | + setConfirmedFormData(newFormData); |
| 90 | + |
| 91 | + if (isEio) { |
| 92 | + setSchema(eioSchema); |
| 93 | + } else { |
| 94 | + setSchema(generalSchema); |
| 95 | + } |
| 96 | + }; |
| 97 | + |
| 98 | + const handleSubmit = async (data: { |
| 99 | + formData?: OperationInformationFormData; |
| 100 | + }) => { |
| 101 | + setError(undefined); |
| 102 | + const pathToRevalidate = `/operations/${operationId}`; |
| 103 | + |
| 104 | + const response = await submitWithFiles( |
| 105 | + data.formData, |
| 106 | + `registration/operations/${operationId}`, |
| 107 | + "POST", |
| 108 | + `/operations/${operationId}`, |
| 109 | + ); |
| 110 | + |
| 111 | + if (response?.error) { |
| 112 | + // Users get this error when they select a contact that's missing address information. We include a link to the Contacts page because the user has to fix the error from there, not here in the operation form. |
| 113 | + if (response.error.includes("Please return to Contacts")) { |
| 114 | + const splitError = response.error.split("Contacts"); |
| 115 | + response.error = ( |
| 116 | + <> |
| 117 | + {splitError[0]} <Link href={"/contacts"}>Contacts</Link> |
| 118 | + {splitError[1]} |
| 119 | + </> |
| 120 | + ); |
| 121 | + } |
| 122 | + setError(response.error); |
| 123 | + return { error: response.error }; |
| 124 | + } |
| 125 | + |
| 126 | + if (!data.formData?.opted_in_operation) return; |
| 127 | + const response2 = await actionHandler( |
| 128 | + `registration/operations/${operationId}/registration/opted-in-operation-detail`, |
| 129 | + "PUT", |
| 130 | + pathToRevalidate, |
| 131 | + { |
| 132 | + body: JSON.stringify(data.formData?.opted_in_operation), |
| 133 | + }, |
| 134 | + ); |
| 135 | + |
| 136 | + if (response2?.error) { |
| 137 | + setError(response2.error); |
| 138 | + return { error: response2.error }; |
| 139 | + } |
| 140 | + }; |
| 141 | + |
| 142 | + const cancelRegistrationPurposeChange = () => { |
| 143 | + setPendingChangeRegistrationPurpose(""); |
| 144 | + setFormMode(FormMode.EDIT); // Keep form in edit mode after remount |
| 145 | + resetKey(); |
| 146 | + setIsConfirmPurposeChangeModalOpen(false); |
| 147 | + }; |
| 148 | + |
| 149 | + const confirmRegistrationPurposeChange = () => { |
| 150 | + if (pendingChangeRegistrationPurpose !== "") { |
| 151 | + updateConfirmedFormData(pendingChangeRegistrationPurpose); |
| 152 | + setFormMode(FormMode.EDIT); // Keep form in edit mode after remount |
| 153 | + resetKey(); |
| 154 | + setIsConfirmPurposeChangeModalOpen(false); |
| 155 | + } |
| 156 | + setPendingChangeRegistrationPurpose(""); |
| 157 | + }; |
| 158 | + |
| 159 | + const handleSelectedPurposeChange = (newSelectedPurpose: string) => { |
| 160 | + if (newSelectedPurpose && confirmedFormData.registration_purpose) { |
| 161 | + setIsConfirmPurposeChangeModalOpen(true); |
| 162 | + setPendingChangeRegistrationPurpose(newSelectedPurpose); |
| 163 | + } |
| 164 | + }; |
| 165 | + |
| 166 | + return ( |
| 167 | + <> |
| 168 | + {isRedirectedFromContacts && !role.includes("cas_") && ( |
| 169 | + <Note variant="important"> |
| 170 | + To remove the current operation representative, please select a new |
| 171 | + contact to replace them. |
| 172 | + </Note> |
| 173 | + )} |
| 174 | + <ConfirmChangeOfFieldModal |
| 175 | + open={isConfirmPurposeChangeModalOpen} |
| 176 | + onCancel={cancelRegistrationPurposeChange} |
| 177 | + onConfirm={confirmRegistrationPurposeChange} |
| 178 | + modalText={ |
| 179 | + <> |
| 180 | + <div> |
| 181 | + Are you sure you want to change your registration purpose? If you |
| 182 | + proceed, |
| 183 | + </div> |
| 184 | + <ul className="list-disc pl-5 mt-2"> |
| 185 | + <li> |
| 186 | + Some operation information you have entered will be deleted. |
| 187 | + </li> |
| 188 | + <li> |
| 189 | + If this operation’s report is in progress, it will be deleted |
| 190 | + and restarted. |
| 191 | + </li> |
| 192 | + <li>Past years’ reports will be unaffected.</li> |
| 193 | + </ul> |
| 194 | + </> |
| 195 | + } |
| 196 | + confirmButtonText="Change registration purpose" |
| 197 | + /> |
| 198 | + <SingleStepTaskListForm |
| 199 | + key={key} |
| 200 | + allowEdit={!role.includes("cas_")} |
| 201 | + mode={formMode} |
| 202 | + error={error} |
| 203 | + schema={schema} |
| 204 | + uiSchema={uiSchema} |
| 205 | + formData={confirmedFormData ?? {}} |
| 206 | + onSubmit={handleSubmit} |
| 207 | + onChange={(e: IChangeEvent) => { |
| 208 | + const newSelectedPurpose = e.formData?.section3?.registration_purpose; |
| 209 | + if (newSelectedPurpose !== confirmedFormData.registration_purpose) { |
| 210 | + handleSelectedPurposeChange(newSelectedPurpose); |
| 211 | + } |
| 212 | + setIsMissingRepresentative(checkMissingRepresentative(e.formData)); |
| 213 | + }} |
| 214 | + onCancel={() => router.push("/operations")} |
| 215 | + formContext={{ |
| 216 | + operationId, |
| 217 | + isRegulatedOperation: regulatedOperationPurposes.includes( |
| 218 | + confirmedFormData.registration_purpose as RegistrationPurposes, |
| 219 | + ), |
| 220 | + isOptedOut: |
| 221 | + formData.opted_in_operation?.final_reporting_year !== null, |
| 222 | + isCasDirector: role === FrontEndRoles.CAS_DIRECTOR, |
| 223 | + isEio: confirmedFormData.registration_purpose?.match( |
| 224 | + RegistrationPurposes.ELECTRICITY_IMPORT_OPERATION.valueOf(), |
| 225 | + ), |
| 226 | + status: confirmedFormData.status, |
| 227 | + missing_representative_alert: isMissingRepresentative, |
| 228 | + ...fileWidgetContext, |
| 229 | + }} |
| 230 | + /> |
| 231 | + </> |
| 232 | + ); |
| 233 | +}; |
| 234 | + |
| 235 | +export default OperationInformationForm; |
0 commit comments