Skip to content

Commit dfc1666

Browse files
authored
COMP-551:Violation Ticket (#515)
* COMP-551:Violation Ticket UI Fixes * COMP-551:Violation Ticket Linting issue fixes * Restorative Justice UI Fix * COMP-551:Violation Ticket Code review changes
1 parent b416b0a commit dfc1666

File tree

3 files changed

+104
-174
lines changed

3 files changed

+104
-174
lines changed

compliance-web/src/components/App/Inspections/Profile/Enforcements/RestorativeJustice/RestorativeJusticeUpdateModal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ const RestorativeJusticeUpdateModal: FC<RestorativeJusticeUpdateModalProps> = ({
119119
type="text"
120120
fullWidth
121121
multiline
122-
rows={1}
122+
minRows={1}
123123
disabled={isReadonlyMode}
124124
/>
125125
</Box>

compliance-web/src/components/App/Inspections/Profile/Enforcements/ViolationTicket/ViolationTicketCreateModal.tsx

Lines changed: 42 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -3,34 +3,42 @@ import { FormProvider, useForm } from "react-hook-form";
33
import * as yup from "yup";
44
import { yupResolver } from "@hookform/resolvers/yup";
55
import { useQueryClient } from "@tanstack/react-query";
6-
import { Box, DialogContent, Typography } from "@mui/material";
7-
import ModalTitleBar from "@/components/Shared/Modals/ModalTitleBar";
8-
import ModalActions from "@/components/Shared/Modals/ModalActions";
9-
import ControlledAutoComplete from "@/components/Shared/Controlled/ControlledAutoComplete";
10-
import ControlledTextField from "@/components/Shared/Controlled/ControlledTextField";
11-
import { InspectionRequirement } from "@/models/InspectionRequirement";
12-
import { BCDesignTokens } from "epic.theme";
13-
import { useModal } from "@/store/modalStore";
6+
import EnforcementModal from "@/components/App/Inspections/Profile/Enforcements/EnforcementModal";
147
import {
158
baseEnforcementSchema,
169
getDefaultFormValues,
1710
ENFORCEMENT_MESSAGES,
11+
BaseEnforcementFormType,
1812
} from "@/components/App/Inspections/Profile/Enforcements/EnforcementUtils";
13+
import ControlledTextField from "@/components/Shared/Controlled/ControlledTextField";
1914
import { useCreateViolationTicket } from "@/hooks/useViolationTickets";
2015
import {
2116
ViolationTicket,
2217
ViolationTicketAPIData,
2318
} from "@/models/ViolationTicket";
2419
import { Inspection } from "@/models/Inspection";
20+
import { InspectionRequirement } from "@/models/InspectionRequirement";
2521
import { notify } from "@/store/snackbarStore";
26-
import ViolationTicketUpdateModal from "./ViolationTicketUpdateModal";
27-
import { MODAL_WIDTHS } from "@/utils/constants";
22+
2823
const violationTicketSchema = baseEnforcementSchema.shape({
2924
ticket_number: yup.string().required("Ticket Number is required").trim(),
3025
});
3126

3227
type ViolationTicketFormType = yup.InferType<typeof violationTicketSchema>;
3328

29+
const ViolationTicketFormFields = () => {
30+
return (
31+
<ControlledTextField
32+
name="ticket_number"
33+
label="Ticket #"
34+
placeholder="Enter ticket number"
35+
type="text"
36+
fullWidth
37+
sx={{ mt: 2 }}
38+
/>
39+
);
40+
};
41+
3442
type ViolationTicketCreateModalProps = {
3543
inspectionData: Inspection;
3644
requirementsList: InspectionRequirement[];
@@ -45,13 +53,12 @@ const ViolationTicketCreateModal: FC<ViolationTicketCreateModalProps> = ({
4553
onSubmit,
4654
}) => {
4755
const queryClient = useQueryClient();
48-
const { setOpen: setModalOpen, setClose: setModalClose } = useModal();
4956

5057
const defaultValues = useMemo(() => {
51-
const baseValues = getDefaultFormValues(requirement, false);
58+
const baseValues = getDefaultFormValues(requirement, false, undefined);
5259
return {
5360
...baseValues,
54-
ticket_number: undefined,
61+
ticket_number: "",
5562
};
5663
}, [requirement]);
5764

@@ -61,8 +68,7 @@ const ViolationTicketCreateModal: FC<ViolationTicketCreateModalProps> = ({
6168
defaultValues,
6269
});
6370

64-
const { reset, handleSubmit, watch } = methods;
65-
const selectedRequirements = watch("requirements") as InspectionRequirement[];
71+
const { reset } = methods;
6672

6773
useEffect(() => {
6874
reset(defaultValues);
@@ -72,123 +78,45 @@ const ViolationTicketCreateModal: FC<ViolationTicketCreateModalProps> = ({
7278
queryClient.invalidateQueries({
7379
queryKey: ["inspection-violation-tickets", inspectionData.id],
7480
});
75-
notify.success(ENFORCEMENT_MESSAGES.VIOLATION_TICKET_CREATED(data.vt_number || ""));
81+
notifyAndSubmit(data);
82+
};
7683

77-
setModalClose();
78-
79-
setTimeout(() => {
80-
setModalOpen({
81-
content: (
82-
<ViolationTicketUpdateModal
83-
violationTicket={data}
84-
inspectionData={inspectionData}
85-
onSuccess={(updatedData) => {
86-
onSubmit(updatedData);
87-
}}
88-
/>
89-
),
90-
width: MODAL_WIDTHS.VIOLATION_TICKET
91-
});
92-
}, 100);
84+
const notifyAndSubmit = (data: ViolationTicket) => {
85+
notify.success(ENFORCEMENT_MESSAGES.VIOLATION_TICKET_CREATED(data.vt_number || ""));
86+
onSubmit(data);
9387
};
9488

9589
const { mutate: createViolationTicket, isPending: isPendingViolationTicket } =
9690
useCreateViolationTicket(onSuccess);
9791

98-
const handleSubmitForm = useCallback(
99-
(data: ViolationTicketFormType) => {
100-
92+
const handleBaseSubmit = useCallback(
93+
(data: BaseEnforcementFormType) => {
94+
// Get the ticket number from the form context since EnforcementModal only handles base schema
95+
const ticketNumber = methods.getValues("ticket_number") || "";
96+
10197
const violationTicketData: ViolationTicketAPIData = {
10298
inspection_id: inspectionData?.id ?? 0,
103-
inspection_requirement_ids: (
104-
data.requirements as InspectionRequirement[]
105-
).map((requirement) => requirement.id),
106-
ticket_number: data.ticket_number || "",
99+
inspection_requirement_ids: (data.requirements as InspectionRequirement[]).map((requirement) => requirement.id),
100+
ticket_number: ticketNumber,
107101
};
108102

109103
createViolationTicket({
110104
violationTicket: violationTicketData,
111105
});
112106
},
113-
[createViolationTicket, inspectionData]
107+
[createViolationTicket, inspectionData, methods]
114108
);
115109

116-
const handleCancel = () => {
117-
setModalClose();
118-
};
119-
120110
return (
121111
<FormProvider {...methods}>
122-
<form onSubmit={handleSubmit(handleSubmitForm)}>
123-
<ModalTitleBar title="Create Violation Ticket" />
124-
<DialogContent dividers sx={{ p: 0 }}>
125-
<Box sx={{ p: "1rem 1.5rem" }}>
126-
<ControlledAutoComplete
127-
name="requirements"
128-
label="Select Requirements"
129-
options={requirementsList ?? []}
130-
getOptionLabel={(option) => {
131-
return `Requirement ${option.sort_order}`;
132-
}}
133-
getOptionKey={(option) => option.id}
134-
isOptionEqualToValue={(option, value) => option.id === value.id}
135-
fullWidth
136-
multiple
137-
disabled={!requirementsList?.length}
138-
sx={{ mb: 2 }}
139-
/>
140-
{selectedRequirements?.map((requirement) => (
141-
<Box
142-
key={requirement.id}
143-
sx={{
144-
display: "flex",
145-
flexDirection: "column",
146-
gap: 1,
147-
p: 1.5,
148-
mb: 1.5,
149-
borderRadius: BCDesignTokens.layoutBorderRadiusMedium,
150-
background: BCDesignTokens.surfaceColorBackgroundLightBlue,
151-
}}
152-
>
153-
<Typography variant="caption" fontWeight={700}>
154-
Requirement {requirement.sort_order}
155-
</Typography>
156-
<Typography variant="subtitle2">
157-
{requirement.summary}
158-
</Typography>
159-
</Box>
160-
))}
161-
<ControlledTextField
162-
name="ticket_number"
163-
label="Ticket #"
164-
placeholder="Enter ticket number"
165-
type="text"
166-
fullWidth
167-
sx={{ mb: 2 }}
168-
/>
169-
</Box>
170-
{selectedRequirements?.length > 1 && (
171-
<Box
172-
sx={{
173-
p: "1rem 1.5rem",
174-
backgroundColor: BCDesignTokens.supportSurfaceColorWarning,
175-
borderTop: `1px solid ${BCDesignTokens.supportBorderColorWarning}`,
176-
}}
177-
>
178-
<Typography variant="body2" color="warning.main">
179-
<strong>Note:</strong> You have selected multiple requirements. A single Violation Ticket will be created for all selected requirements.
180-
</Typography>
181-
</Box>
182-
)}
183-
</DialogContent>
184-
<ModalActions
185-
onSecondaryAction={handleCancel}
186-
onPrimaryAction={handleSubmit(handleSubmitForm)}
187-
isLoading={isPendingViolationTicket}
188-
primaryActionButtonText="Create"
189-
secondaryActionButtonText="Cancel"
190-
/>
191-
</form>
112+
<EnforcementModal
113+
requirementsList={requirementsList}
114+
requirement={requirement}
115+
title="Create Violation Ticket"
116+
onSubmit={handleBaseSubmit}
117+
isLoading={isPendingViolationTicket}
118+
additionalFormFields={<ViolationTicketFormFields />}
119+
/>
192120
</FormProvider>
193121
);
194122
};

compliance-web/src/components/App/Inspections/Profile/Enforcements/ViolationTicket/ViolationTicketUpdateModal.tsx

Lines changed: 61 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { Box } from "@mui/material";
1+
import { Box, DialogContent } from "@mui/material";
2+
import { AttachMoneyRounded } from "@mui/icons-material";
23
import { FC, useCallback, useEffect, useMemo } from "react";
34
import { FormProvider, useForm } from "react-hook-form";
45
import * as yup from "yup";
@@ -146,65 +147,66 @@ const ViolationTicketUpdateModal: FC<ViolationTicketUpdateModalProps> = ({
146147
<FormProvider {...methods}>
147148
<form onSubmit={handleSubmit(handleSubmitForm)}>
148149
<ModalTitleBar title="Violation Ticket" />
149-
<Box sx={{ p: "1rem 1.5rem" }}>
150-
151-
152-
<Box sx={{ display: "flex", gap: 1 }}>
153-
<ControlledDateField
154-
name="date_issued"
155-
label="Date Issued"
156-
sx={{ width: "50%" }}
157-
disabled={isReadonlyMode}
158-
/>
159-
<ControlledTextField
160-
name="ticket_number"
161-
label="Ticket #"
162-
placeholder="Enter ticket number"
163-
sx={{ width: "100%" }}
164-
disabled
165-
/>
166-
167-
</Box>
168-
169-
<Box sx={{ display: "flex", gap: 1 }}>
170-
171-
<ControlledTextField
172-
name="fine_amount"
173-
label="Fine Amount"
174-
type="number"
175-
sx={{ width: "100%" }}
176-
inputProps={{
177-
min: 0,
178-
step: 0.01,
179-
}}
180-
InputProps={{
181-
startAdornment: <span style={{ color: 'rgba(0, 0, 0, 0.38)', marginRight: '2px' }}>$</span>,
182-
}}
183-
disabled={isReadonlyMode}
184-
/>
185-
<ControlledAutoComplete
186-
name="status"
187-
label="Status"
188-
options={statusOptions}
189-
getOptionLabel={(option) => option.name}
190-
isOptionEqualToValue={(option, value) => option.id === value.id}
191-
placeholder="Select status"
192-
sx={{ width: "100%" }}
193-
disabled={isReadonlyMode}
194-
/>
195-
150+
<DialogContent dividers sx={{ p: 0 }}>
151+
<Box sx={{ p: "1rem 1.5rem" }}>
152+
<Box sx={{ display: "flex", gap: 1 }}>
153+
<ControlledDateField
154+
name="date_issued"
155+
label="Date Issued"
156+
sx={{ width: "50%" }}
157+
isRequired={true}
158+
disabled={isReadonlyMode}
159+
/>
160+
<ControlledTextField
161+
name="ticket_number"
162+
label="Ticket #"
163+
placeholder="Enter ticket number"
164+
sx={{ width: "100%" }}
165+
disabled
166+
/>
167+
</Box>
168+
169+
<Box sx={{ display: "flex", gap: 1 }}>
170+
<ControlledTextField
171+
name="fine_amount"
172+
label="Fine Amount"
173+
type="number"
174+
sx={{ width: "100%" }}
175+
inputProps={{
176+
min: 0,
177+
step: 0.01,
178+
}}
179+
InputProps={{
180+
startAdornment: <AttachMoneyRounded sx={{
181+
mr: 0.2,
182+
color: "#9F9D9C",
183+
}} />,
184+
}}
185+
isRequired={true}
186+
disabled={isReadonlyMode}
187+
/>
188+
<ControlledAutoComplete
189+
name="status"
190+
label="Status"
191+
options={statusOptions}
192+
getOptionLabel={(option) => option.name}
193+
isOptionEqualToValue={(option, value) => option.id === value.id}
194+
placeholder="Select status"
195+
sx={{ width: "100%" }}
196+
disabled={isReadonlyMode}
197+
/>
198+
</Box>
199+
<Box sx={{ display: "flex", gap: 1 }}>
200+
<ControlledDateField
201+
name="status_date"
202+
label="Status Date"
203+
sx={{ width: "100%" }}
204+
isRequired={true}
205+
disabled={isReadonlyMode}
206+
/>
207+
</Box>
196208
</Box>
197-
<Box sx={{ display: "flex", gap: 1 }}>
198-
<ControlledDateField
199-
name="status_date"
200-
label="Status Date"
201-
sx={{ width: "100%" }}
202-
disabled={isReadonlyMode}
203-
/>
204-
205-
</Box>
206-
207-
</Box>
209+
</DialogContent>
208210
{!isReadonlyMode && (
209211
<ModalActions
210212
onSecondaryAction={handleCancel}

0 commit comments

Comments
 (0)