diff --git a/backend/src/routes/driver.ts b/backend/src/routes/driver.ts index 5c8e56a..ab5bc4a 100644 --- a/backend/src/routes/driver.ts +++ b/backend/src/routes/driver.ts @@ -228,4 +228,6 @@ router.delete("/delete", async (req: Request, res: Response) => { } }); + + export default router; diff --git a/backend/src/routes/violation.ts b/backend/src/routes/violation.ts index 544fd36..3590088 100644 --- a/backend/src/routes/violation.ts +++ b/backend/src/routes/violation.ts @@ -118,4 +118,38 @@ router.delete("/delete", async (req: Request, res: Response) => { } }); +router.patch("/status-change", async (req: Request, res: Response) => { + try { + const { id } = req.body; + const { rows: violations } = await pool.query( + `SELECT * FROM violations WHERE id = $1`, + [id], + ); + + const foundViolation = await violations[0]; + + if (!foundViolation) { + res.status(404).json({ message: "Violation cannot be found." }); + return; + } + + const updated = await pool.query( + `UPDATE violations SET paid_status = $1 WHERE id = $2 RETURNING *`, + [!foundViolation.paid_status, id], + ); + + if (updated.rowCount === 0) { + res.status(400).json({ message: "Update is not successful." }); + return; + } + + res + .status(200) + .json({ message: "Violation paid status has been updated." }); + } catch (error) { + const errorMessage = (error as Error).message; + res.status(500).json({ title: "Unknown Error", message: errorMessage }); + } +}); + export default router; diff --git a/frontend/src/components/ViolationCard.tsx b/frontend/src/components/ViolationCard.tsx index 758041c..edb03dd 100644 --- a/frontend/src/components/ViolationCard.tsx +++ b/frontend/src/components/ViolationCard.tsx @@ -2,13 +2,15 @@ import { useDeleteViolation } from "../hooks/violation-hooks/useDeleteViolation" import { useState, useRef, useEffect } from "react"; import { Violation } from "../types/datatypes"; import { useEditViolation } from "../hooks/violation-hooks/useEditViolation"; +import useUpdateStatus from "../hooks/useUpdateStatus"; const ViolationCard = ({ violation }: { violation: Violation }) => { const [isMenuOpen, setIsMenuOpen] = useState(false); const [isEditing, setIsEditing] = useState(false); const [editedViolation, setEditedViolation] = useState(violation); const { deleteViolation } = useDeleteViolation(); - const { updateViolation } = useEditViolation() + const { updateViolation } = useEditViolation(); + const { handleUpdateStatus } = useUpdateStatus(); const dropdownRef = useRef(null); const date = new Date(violation.violation_date!); const year = date.getFullYear(); @@ -33,22 +35,28 @@ const ViolationCard = ({ violation }: { violation: Violation }) => { } }; + const handleStatusClick = async () => { + await handleUpdateStatus(violation.id!); + }; + const handleEditViolation = () => { updateViolation(editedViolation); setIsEditing(false); setIsMenuOpen(false); }; - const handleCancelEdit = () => { - if (isEditing){ + const handleCancelEdit = () => { + if (isEditing) { setIsEditing(false); - setIsMenuOpen(false) + setIsMenuOpen(false); } - } + }; useEffect(() => { const handleClickOutside = (event: MouseEvent) => { - if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node) + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) ) { setIsMenuOpen(false); } @@ -68,8 +76,8 @@ const ViolationCard = ({ violation }: { violation: Violation }) => {

Violation

- {isEditing ? ( - { className="text-input" /> ) : ( -

- {violation.violation_type} -

- )} +

+ {violation.violation_type} +

+ )}
@@ -88,7 +96,7 @@ const ViolationCard = ({ violation }: { violation: Violation }) => { Date of Violation {isEditing ? ( - { className="date-input" /> ) : ( -

- {year}/{month}/{day} -

- )} +

+ {year}/{month}/{day} +

+ )}
-

- Description -

+

Description

{isEditing ? ( - { className="text-input" /> ) : ( -

- {violation.description} -

- )} +

+ {violation.description} +

+ )}

Status

-

+ {violation.paid_status ? "Paid" : "Unpaid"} -

+
diff --git a/frontend/src/hooks/useUpdateStatus.ts b/frontend/src/hooks/useUpdateStatus.ts new file mode 100644 index 0000000..d629419 --- /dev/null +++ b/frontend/src/hooks/useUpdateStatus.ts @@ -0,0 +1,42 @@ +import { fetchWithAuth } from "../utils/fetch"; +import useFetchWithAuthExports from "./context-hooks/useFetchWithAuthExports"; +import { BackendMessage } from "../types/response.types"; +import { toast } from "react-toastify"; +import { LoadingContextType } from "../types/loading.types"; +import useLoading from "./context-hooks/useLoading"; + +const useUpdateStatus = () => { + const { navigate, refresh, auth } = useFetchWithAuthExports(); + const { setAppLoading }: LoadingContextType = useLoading(); + + const handleUpdateStatus = async (id: string) => { + setAppLoading!(true); + try { + const response = await fetchWithAuth( + navigate, + refresh, + auth, + "/violation/status-change", + "patch", + { id }, + ); + + if (!response.ok) { + const backendError: BackendMessage = await response.json(); + toast.error(backendError.message); + return; + } + + const backendNotification: BackendMessage = await response.json(); + toast.success(backendNotification.message); + } catch (error) { + const errorMessage = (error as Error).message; + toast.error(errorMessage); + } finally { + setAppLoading!(false); + } + }; + return { handleUpdateStatus }; +}; + +export default useUpdateStatus; diff --git a/frontend/src/pages/AboutPage.tsx b/frontend/src/pages/AboutPage.tsx index 0ba3d0e..112241d 100644 --- a/frontend/src/pages/AboutPage.tsx +++ b/frontend/src/pages/AboutPage.tsx @@ -2,11 +2,11 @@ import Header from "../components/Header"; const AboutPage = () => { return ( -
+
-
+

About Us

diff --git a/frontend/src/pages/AdminHomePage.tsx b/frontend/src/pages/AdminHomePage.tsx index 6c33fc0..595526a 100644 --- a/frontend/src/pages/AdminHomePage.tsx +++ b/frontend/src/pages/AdminHomePage.tsx @@ -50,7 +50,7 @@ const AdminLandingPage = () => { Encode
-
+