From 8c67ae3314017cdabb1a295131b9e58f67acdeb1 Mon Sep 17 00:00:00 2001 From: GlenAOT <160973940+GlenAOT@users.noreply.github.com> Date: Thu, 12 Sep 2024 12:57:04 -0700 Subject: [PATCH 1/5] applications in progress tab, get applications in queue hook, AIP table/list, row actions, modal created --- .../components/banners/InfoBcGovBanner.tsx | 2 +- .../table/OnRouteBCTableRowActions.tsx | 147 +++++++------- .../src/common/constants/bannerMessages.ts | 8 +- .../permits/apiManager/endpoints/endpoints.ts | 5 + .../features/permits/apiManager/permitsAPI.ts | 48 ++++- .../components/dashboard/PermitLists.tsx | 18 ++ .../ApplicationInReviewColumnDefinition.tsx | 111 +++++++++++ .../permit-list/ApplicationInReviewModal.scss | 74 +++++++ .../permit-list/ApplicationInReviewModal.tsx | 54 +++++ .../ApplicationInReviewStatusChip.scss | 17 ++ .../ApplicationInReviewStatusChip.tsx | 65 +++++++ .../permit-list/ApplicationsInReviewList.scss | 24 +++ .../permit-list/ApplicationsInReviewList.tsx | 184 ++++++++++++++++++ .../ApplicationsInReviewRowOptions.tsx | 97 +++++++++ .../components/permit-list/PermitChip.tsx | 13 ++ frontend/src/features/permits/hooks/hooks.ts | 134 +++++++++++-- .../permits/types/ApplicationQueueStatus.ts | 8 + .../permits/types/CaseActivityType.ts | 16 ++ .../features/permits/types/PermitStatus.ts | 1 + .../src/features/permits/types/application.ts | 9 +- frontend/src/features/permits/types/permit.ts | 9 +- .../src/common/constants/permit.constant.ts | 2 + 22 files changed, 950 insertions(+), 96 deletions(-) create mode 100644 frontend/src/features/permits/components/permit-list/ApplicationInReviewColumnDefinition.tsx create mode 100644 frontend/src/features/permits/components/permit-list/ApplicationInReviewModal.scss create mode 100644 frontend/src/features/permits/components/permit-list/ApplicationInReviewModal.tsx create mode 100644 frontend/src/features/permits/components/permit-list/ApplicationInReviewStatusChip.scss create mode 100644 frontend/src/features/permits/components/permit-list/ApplicationInReviewStatusChip.tsx create mode 100644 frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.scss create mode 100644 frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.tsx create mode 100644 frontend/src/features/permits/components/permit-list/ApplicationsInReviewRowOptions.tsx create mode 100644 frontend/src/features/permits/types/ApplicationQueueStatus.ts create mode 100644 frontend/src/features/permits/types/CaseActivityType.ts diff --git a/frontend/src/common/components/banners/InfoBcGovBanner.tsx b/frontend/src/common/components/banners/InfoBcGovBanner.tsx index fbe9a64de..85a25b47f 100644 --- a/frontend/src/common/components/banners/InfoBcGovBanner.tsx +++ b/frontend/src/common/components/banners/InfoBcGovBanner.tsx @@ -6,7 +6,7 @@ export const InfoBcGovBanner = ({ additionalInfo, className, }: { - msg: string; + msg: string | JSX.Element; additionalInfo?: JSX.Element; className?: string; }) => ( diff --git a/frontend/src/common/components/table/OnRouteBCTableRowActions.tsx b/frontend/src/common/components/table/OnRouteBCTableRowActions.tsx index 47aa1a6eb..15959206a 100644 --- a/frontend/src/common/components/table/OnRouteBCTableRowActions.tsx +++ b/frontend/src/common/components/table/OnRouteBCTableRowActions.tsx @@ -24,12 +24,16 @@ export const OnRouteBCTableRowActions = ({ const [anchorEl, setAnchorEl] = useState(null); const isMenuOpen = Boolean(anchorEl); - const actionsButtonPressedClassName = - isMenuOpen ? " onroutebc-table-row-actions__button--pressed" : ""; + const actionsButtonPressedClassName = isMenuOpen + ? " onroutebc-table-row-actions__button--pressed" + : ""; - const handleOpenActionsMenu = useCallback((event: MouseEvent) => { - setAnchorEl(event.currentTarget); - }, []); + const handleOpenActionsMenu = useCallback( + (event: MouseEvent) => { + setAnchorEl(event.currentTarget); + }, + [], + ); const handleCloseActionsMenu = () => { setAnchorEl(null); @@ -42,69 +46,76 @@ export const OnRouteBCTableRowActions = ({ }; return ( -
- - - - - + <> + { + // if there are no options, return null to prevent returning an empty menu + options.length > 0 ? ( +
+ + + + + - - {options.map((option) => ( - - {option.label} - - ))} - -
+ + {options.map((option) => ( + + {option.label} + + ))} + +
+ ) : null + } + ); }; diff --git a/frontend/src/common/constants/bannerMessages.ts b/frontend/src/common/constants/bannerMessages.ts index ea8aaf589..d7bb09f4d 100644 --- a/frontend/src/common/constants/bannerMessages.ts +++ b/frontend/src/common/constants/bannerMessages.ts @@ -11,8 +11,12 @@ export const BANNER_MESSAGES = { POLICY_REMINDER: "The applicant is responsible for ensuring they are following Legislation, policies, standards and guidelines in the operation of a commercial transportation business in British Columbia.", CANNOT_FIND_VEHICLE: "Can't find a vehicle from your inventory?", - ISSUED_PERMIT_NUMBER_7_YEARS: "Enter any Permit No. issued to the above Client No. in the last 7 years", - SELECT_VEHICLES_LOA: "Only vehicles in the Vehicle Inventory can be designated to LOA(s).", + ISSUED_PERMIT_NUMBER_7_YEARS: + "Enter any Permit No. issued to the above Client No. in the last 7 years", + SELECT_VEHICLES_LOA: + "Only vehicles in the Vehicle Inventory can be designated to LOA(s).", SELECT_VEHICLES_LOA_INFO: "If you do not see the vehicle(s) you wish to designate here, please make sure you add them to the client's Vehicle Inventory first and come back to this page.", + REJECTED_APPLICATIONS: + "Rejected applications appear in Applications in Progress.", }; diff --git a/frontend/src/features/permits/apiManager/endpoints/endpoints.ts b/frontend/src/features/permits/apiManager/endpoints/endpoints.ts index 70ea7dce4..b5aa881cd 100644 --- a/frontend/src/features/permits/apiManager/endpoints/endpoints.ts +++ b/frontend/src/features/permits/apiManager/endpoints/endpoints.ts @@ -15,6 +15,11 @@ export const APPLICATIONS_API_ROUTES = { DELETE: (companyId: string) => APPLICATIONS_API_BASE(companyId), }; +export const APPLICATION_QUEUE_API_ROUTES = { + UPDATE_QUEUE_STATUS: (companyId: string, applicationId: string) => + `${APPLICATIONS_API_BASE(companyId)}/${applicationId}/queue/status`, +}; + export const PERMITS_API_ROUTES = { BASE: (companyId: string) => PERMITS_API_BASE(companyId), GET: (companyId: string) => PERMITS_API_BASE(companyId), diff --git a/frontend/src/features/permits/apiManager/permitsAPI.ts b/frontend/src/features/permits/apiManager/permitsAPI.ts index ce4523a1d..a32ca6077 100644 --- a/frontend/src/features/permits/apiManager/permitsAPI.ts +++ b/frontend/src/features/permits/apiManager/permitsAPI.ts @@ -52,6 +52,7 @@ import { } from "../types/application"; import { + APPLICATION_QUEUE_API_ROUTES, APPLICATIONS_API_ROUTES, PAYMENT_API_ROUTES, PERMITS_API_ROUTES, @@ -63,6 +64,7 @@ import { VoidPermitResponseData, } from "../pages/Void/types/VoidPermit"; import { EmailNotificationType } from "../types/EmailNotificationType"; +import { CaseActivityType } from "../types/CaseActivityType"; /** * Create a new application. @@ -116,6 +118,7 @@ const getApplications = async ( orderBy = [], }: PaginationAndFilters, pendingPermitsOnly?: boolean, + applicationsInQueueOnly?: boolean, ): Promise> => { const companyId = getDefaultRequiredVal("", getCompanyIdFromSession()); const applicationsURL = new URL(APPLICATIONS_API_ROUTES.GET(companyId)); @@ -123,10 +126,20 @@ const getApplications = async ( // API pagination index starts at 1. Hence page + 1. applicationsURL.searchParams.set("page", `${page + 1}`); applicationsURL.searchParams.set("take", `${take}`); - applicationsURL.searchParams.set( - "pendingPermits", - `${Boolean(pendingPermitsOnly)}`, - ); + + if (pendingPermitsOnly !== undefined) { + applicationsURL.searchParams.set( + "pendingPermits", + `${Boolean(pendingPermitsOnly)}`, + ); + } + + if (applicationsInQueueOnly !== undefined) { + applicationsURL.searchParams.set( + "applicationsInQueue", + `${Boolean(applicationsInQueueOnly)}`, + ); + } if (searchString) { applicationsURL.searchParams.set("searchString", searchString); @@ -192,6 +205,16 @@ export const getPendingPermits = async ( return await getApplications(paginationFilters, true); }; +/** + * Fetch all applications in queue. + * @return A list of applications in queue (PENDING_REVIEW, IN_REVIEW) + */ +export const getApplicationsInQueue = async ( + paginationFilters: PaginationAndFilters, +): Promise> => { + return await getApplications(paginationFilters, undefined, true); +}; + /** * Fetch application by its permit id. * @param permitId permit id of the application to fetch @@ -615,3 +638,20 @@ export const resendPermit = async ({ replaceEmptyValuesWithNull(data), ); }; + +export const updateApplicationQueueStatus = async ( + applicationId: string, + caseActivityType: CaseActivityType, + comment?: string, +) => { + const companyId = getDefaultRequiredVal("", getCompanyIdFromSession()); + + const response = await httpPOSTRequest( + APPLICATION_QUEUE_API_ROUTES.UPDATE_QUEUE_STATUS(companyId, applicationId), + { + caseActivityType, + ...(comment && { comment }), + }, + ); + return response; +}; diff --git a/frontend/src/features/permits/components/dashboard/PermitLists.tsx b/frontend/src/features/permits/components/dashboard/PermitLists.tsx index aef3afc2e..a92993f16 100644 --- a/frontend/src/features/permits/components/dashboard/PermitLists.tsx +++ b/frontend/src/features/permits/components/dashboard/PermitLists.tsx @@ -5,6 +5,7 @@ import { StartApplicationAction } from "../../pages/Application/components/dashb import { ActivePermitList } from "../permit-list/ActivePermitList"; import { ExpiredPermitList } from "../permit-list/ExpiredPermitList"; import { ApplicationsInProgressList } from "../permit-list/ApplicationsInProgressList"; +import { ApplicationsInReviewList } from "../permit-list/ApplicationsInReviewList"; import { Nullable } from "../../../../common/types/common"; import { usePermissionMatrix } from "../../../../common/authentication/PermissionMatrix"; import { RenderIf } from "../../../../common/components/reusable/RenderIf"; @@ -16,12 +17,28 @@ export const PermitLists = React.memo(() => { setApplicationsInProgressCount(count); }; const tabs = []; + + // TODO move this code back below showApplicationsInProgressTab to restore proper tab order + const showApplicationsInReviewTab = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "MANAGE_PERMITS", + permissionMatrixFunctionKey: "VIEW_LIST_OF_APPLICATIONS_IN_REVIEW", + }, + }); + if (showApplicationsInReviewTab) { + tabs.push({ + label: "Applications in Review", + component: , + }); + } + const showApplicationsInProgressTab = usePermissionMatrix({ permissionMatrixKeys: { permissionMatrixFeatureKey: "MANAGE_PERMITS", permissionMatrixFunctionKey: "VIEW_LIST_OF_APPLICATIONS_IN_PROGRESS", }, }); + if (showApplicationsInProgressTab) { tabs.push({ label: "Applications in Progress", @@ -33,6 +50,7 @@ export const PermitLists = React.memo(() => { ), }); } + tabs.push( { label: "Active Permits", diff --git a/frontend/src/features/permits/components/permit-list/ApplicationInReviewColumnDefinition.tsx b/frontend/src/features/permits/components/permit-list/ApplicationInReviewColumnDefinition.tsx new file mode 100644 index 000000000..4452b05b1 --- /dev/null +++ b/frontend/src/features/permits/components/permit-list/ApplicationInReviewColumnDefinition.tsx @@ -0,0 +1,111 @@ +import { MRT_ColumnDef } from "material-react-table"; + +import { ApplicationListItem } from "../../types/application"; +import { APPLICATIONS_ROUTES } from "../../../../routes/constants"; +import { CustomNavLink } from "../../../../common/components/links/CustomNavLink"; +import { UserRoleType } from "../../../../common/authentication/types"; +import { canUserAccessApplication } from "../../helpers/mappers"; +import { Nullable } from "../../../../common/types/common"; +import { getPermitTypeName } from "../../types/PermitType"; +import { Box, Tooltip } from "@mui/material"; +import { ApplicationInReviewStatusChip } from "./ApplicationInReviewStatusChip"; + +export const ApplicationInReviewColumnDefinition = ( + userRole?: Nullable, +): MRT_ColumnDef[] => [ + { + accessorKey: "applicationNumber", + id: "applicationNumber", + enableSorting: false, + header: "Application #", + accessorFn: (row) => row.applicationNumber, + Cell: (props: { cell: any; row: any }) => { + const permitIdStr = `${props.row.original.permitId}`; + + return canUserAccessApplication( + props.row.original.permitApplicationOrigin, + userRole, + ) ? ( +
+ + {props.cell.getValue()} + + +
+ ) : ( + <> + {props.cell.getValue()} + + + ); + }, + minSize: 320, + }, + { + accessorKey: "permitType", + id: "permitType", + enableSorting: false, + header: "Permit Type", + Cell: (props: { cell: any }) => { + const permitTypeName = getPermitTypeName(props.cell.getValue()); + return ( + + {props.cell.getValue()} + + ); + }, + size: 80, + }, + { + accessorKey: "unitNumber", + id: "unitNumber", + enableSorting: false, + header: "Unit #", + size: 50, + }, + { + accessorKey: "vin", + id: "vin", + enableSorting: false, + header: "VIN", + size: 50, + }, + { + accessorKey: "plate", + id: "plate", + enableSorting: false, + header: "Plate", + size: 50, + }, + { + accessorKey: "startDate", + id: "startDate", + enableSorting: true, + header: "Permit Start Date", + size: 140, + }, + { + accessorKey: "updatedDateTime", + enableSorting: true, + id: "updatedDateTime", + header: "Last Updated", + size: 200, + }, + { + accessorKey: "applicant", + id: "applicant", + header: "Applicant", + enableSorting: false, + size: 200, + }, +]; + +export const ApplicationNotFoundColumnDefinition: MRT_ColumnDef[] = + []; diff --git a/frontend/src/features/permits/components/permit-list/ApplicationInReviewModal.scss b/frontend/src/features/permits/components/permit-list/ApplicationInReviewModal.scss new file mode 100644 index 000000000..c4e3ff0fa --- /dev/null +++ b/frontend/src/features/permits/components/permit-list/ApplicationInReviewModal.scss @@ -0,0 +1,74 @@ +@import "../../../../themes/orbcStyles"; + +.application-in-review-modal { + & &__container { + width: 100%; + display: flex; + flex-direction: column; + } + + &__header { + padding: 2rem 1.5rem; + display: flex; + flex-direction: row; + align-items: center; + background-color: $bc-background-light-grey; + border-bottom: 1px solid $bc-border-grey; + } + + &__title { + font-weight: 600; + font-size: 1.5rem; + margin: 0 0 0 0.5em; + } + + &__body { + padding: 1.5rem; + } + + &__text { + font-size: 1rem; + } + + &__footer { + display: flex; + flex-direction: row; + justify-content: flex-end; + padding: 0 1.5rem 1.5rem 1.5rem; + gap: 1.5rem; + + .remove-users-btn { + &--cancel { + background-color: $bc-background-light-grey; + border: 2px solid transparent; + box-shadow: none; + color: $bc-black; + cursor: pointer; + + &:hover { + background-color: $bc-background-light-grey; + border-color: $bc-text-box-border-grey; + box-shadow: none; + } + } + + &--confirm { + background-color: $bc-red; + box-shadow: none; + color: $white; + cursor: pointer; + font-weight: bold; + + &:hover { + background-color: $bc-messages-red-text; + box-shadow: none; + } + + &:disabled { + background-color: #EBC0C1; + color: $white; + } + } + } + } +} diff --git a/frontend/src/features/permits/components/permit-list/ApplicationInReviewModal.tsx b/frontend/src/features/permits/components/permit-list/ApplicationInReviewModal.tsx new file mode 100644 index 000000000..3ab9eb759 --- /dev/null +++ b/frontend/src/features/permits/components/permit-list/ApplicationInReviewModal.tsx @@ -0,0 +1,54 @@ +import { Button, Dialog } from "@mui/material"; +import "./ApplicationInReviewModal.scss"; + +export const ApplicationInReviewModal = ({ + isOpen, + onCancel, + onConfirm, +}: { + /** + * Boolean to control the open and close state of Dialog box. + */ + isOpen: boolean; + /** + * A callback function on clicking cancel button. + * @returns void + */ + onCancel: () => void; + onConfirm: () => void; +}) => { + return ( + +
+

+ Application Status +

+
+ +
+

+ Application(s) have either been withdrawn or are in review by the + Provincial Permit Centre. +

+
+ +
+ +
+
+ ); +}; diff --git a/frontend/src/features/permits/components/permit-list/ApplicationInReviewStatusChip.scss b/frontend/src/features/permits/components/permit-list/ApplicationInReviewStatusChip.scss new file mode 100644 index 000000000..c9bb6ae56 --- /dev/null +++ b/frontend/src/features/permits/components/permit-list/ApplicationInReviewStatusChip.scss @@ -0,0 +1,17 @@ +@import "../../../../themes/orbcStyles"; + +.permit-chip { + display: inline-block; + margin-left: 0.5rem; + + &--pending-review { + background-color: $bc-messages-blue-background; + color: $bc-primary-blue; + } + + &--in-review { + background-color: $bc-messages-gold-background; + color: $bc-messages-gold-text; + } + +} diff --git a/frontend/src/features/permits/components/permit-list/ApplicationInReviewStatusChip.tsx b/frontend/src/features/permits/components/permit-list/ApplicationInReviewStatusChip.tsx new file mode 100644 index 000000000..38c57682e --- /dev/null +++ b/frontend/src/features/permits/components/permit-list/ApplicationInReviewStatusChip.tsx @@ -0,0 +1,65 @@ +import "./ApplicationInReviewStatusChip.scss"; +import { OnRouteBCChip } from "../../../../common/components/chip/OnRouteBCChip"; + +import { + APPLICATION_QUEUE_STATUSES, + ApplicationQueueStatus, +} from "../../types/ApplicationQueueStatus"; + +/** + * Returns the theme name for the chip based on the permit status. + * If the permit is inactive or expired, a chip has to be displayed + * beside the permit number. + * @param applicationQueueStatus string representing the permit status + * @returns A string representing the theme name for the chip + */ +const getTheme = (applicationQueueStatus?: ApplicationQueueStatus) => { + switch (applicationQueueStatus) { + case APPLICATION_QUEUE_STATUSES.PENDING_REVIEW: + return "pending-review"; + case APPLICATION_QUEUE_STATUSES.IN_REVIEW: + return "in-review"; + case APPLICATION_QUEUE_STATUSES.CLOSED: + return "closed"; + default: + return undefined; + } +}; + +/** + * Returns the text corresponding to the status of a permit. + * @param permitStatus string representing the permit status + * @returns Display text string corresponding to permit status + */ +const getStatusText = ( + applicationQueueStatus?: ApplicationQueueStatus, +): string => { + switch (applicationQueueStatus) { + case APPLICATION_QUEUE_STATUSES.PENDING_REVIEW: + return "Pending Review"; + case APPLICATION_QUEUE_STATUSES.IN_REVIEW: + return "In Review"; + case APPLICATION_QUEUE_STATUSES.CLOSED: + return "Closed"; + default: + return ""; + } +}; +/** + * A simple chip component to be displayed beside the permit number. + */ +export const ApplicationInReviewStatusChip = ({ + applicationQueueStatus, +}: { + applicationQueueStatus?: ApplicationQueueStatus; +}) => { + const chipTheme = getTheme(applicationQueueStatus); + return chipTheme ? ( + + ) : null; +}; + +ApplicationInReviewStatusChip.displayName = "ApplicationInReviewStatusChip"; diff --git a/frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.scss b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.scss new file mode 100644 index 000000000..f162b5da5 --- /dev/null +++ b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.scss @@ -0,0 +1,24 @@ +@use "../list/List"; +@import "../../../../themes/orbcStyles"; + +.applications-in-review-list { + .applications-in-review-banner { + margin-bottom: 1.5rem; + width: 100%; + } + + & &__top-toolbar { + display: flex; + justify-content: flex-end; + margin-bottom: 1.5rem; + } + + & &__row { + &:hover { + // remove MRT row/cell color change on hover + td::after { + background-color: $white + } + } + } +} diff --git a/frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.tsx b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.tsx new file mode 100644 index 000000000..3864b5de1 --- /dev/null +++ b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.tsx @@ -0,0 +1,184 @@ +import { useCallback, useContext, useEffect, useMemo, useState } from "react"; +import { RowSelectionState } from "@tanstack/table-core"; +import { + MRT_ColumnDef, + MaterialReactTable, + useMaterialReactTable, +} from "material-react-table"; + +import "./ApplicationsInReviewList.scss"; +import { ApplicationInReviewColumnDefinition } from "./ApplicationInReviewColumnDefinition"; +import { SnackBarContext } from "../../../../App"; +import { ApplicationListItem } from "../../types/application"; +import { NoRecordsFound } from "../../../../common/components/table/NoRecordsFound"; +import OnRouteBCContext from "../../../../common/authentication/OnRouteBCContext"; +import { + getDefaultNullableVal, + getDefaultRequiredVal, +} from "../../../../common/helpers/util"; +import { UserRoleType } from "../../../../common/authentication/types"; +import { Nullable } from "../../../../common/types/common"; +import { useApplicationsInQueueQuery } from "../../hooks/hooks"; +import { InfoBcGovBanner } from "../../../../common/components/banners/InfoBcGovBanner"; +import { + defaultTableInitialStateOptions, + defaultTableOptions, + defaultTableStateOptions, +} from "../../../../common/helpers/tableHelper"; +import { BANNER_MESSAGES } from "../../../../common/constants/bannerMessages"; +import { MRT_Row } from "material-react-table"; +import { ApplicationsInReviewRowOptions } from "./ApplicationsInReviewRowOptions"; +import { APPLICATION_QUEUE_STATUSES } from "../../types/ApplicationQueueStatus"; + +const getColumns = ( + userRole?: Nullable, +): MRT_ColumnDef[] => { + return ApplicationInReviewColumnDefinition(userRole); +}; + +export const ApplicationsInReviewList = () => { + const { + applicationsInQueueQuery, + pagination, + setPagination, + sorting, + setSorting, + } = useApplicationsInQueueQuery(); + + const { + data: applicationsInQueue, + isError, + isPending, + isFetching, + } = applicationsInQueueQuery; + + console.log(applicationsInQueue); + const [showAIRTable, setShowAIRTable] = useState(false); + + useEffect(() => { + const totalCount = getDefaultRequiredVal( + 0, + applicationsInQueue?.meta?.totalItems, + ); + setShowAIRTable(totalCount > 0); + }, [applicationsInQueue?.meta?.totalItems]); + + const { idirUserDetails, userDetails } = useContext(OnRouteBCContext); + const userRole = getDefaultNullableVal( + idirUserDetails?.userRole, + userDetails?.userRole, + ); + + const snackBar = useContext(SnackBarContext); + + const [rowSelection, setRowSelection] = useState({}); + + const columns = useMemo[]>( + () => getColumns(userRole), + [userRole], + ); + + useEffect(() => { + if (isError) { + snackBar.setSnackBar({ + message: "An unexpected error occurred.", + showSnackbar: true, + setShowSnackbar: () => true, + alertType: "error", + }); + } + }, [isError]); + + const table = useMaterialReactTable({ + ...defaultTableOptions, + columns: columns, + data: getDefaultRequiredVal([], applicationsInQueue?.items), + initialState: { + ...defaultTableInitialStateOptions, + }, + state: { + ...defaultTableStateOptions, + showAlertBanner: isError, + showProgressBars: isFetching, + columnVisibility: { applicationId: true }, + isLoading: isPending, + rowSelection, + pagination, + sorting, + }, + layoutMode: "grid", + displayColumnDefOptions: { + "mrt-row-select": { + size: 10, + }, + "mrt-row-actions": { + header: "", + size: 40, + }, + }, + enableRowActions: true, + enableRowSelection: false, + onRowSelectionChange: useCallback(setRowSelection, [userRole]), + getRowId: (originalRow) => { + const applicationRow = originalRow as ApplicationListItem; + return applicationRow.permitId; + }, + renderTopToolbar: false, + enableGlobalFilter: false, + autoResetPageIndex: false, + manualFiltering: true, + manualPagination: true, + manualSorting: true, + rowCount: getDefaultRequiredVal(0, applicationsInQueue?.meta?.totalItems), + pageCount: getDefaultRequiredVal(0, applicationsInQueue?.meta?.pageCount), + onSortingChange: setSorting, + onPaginationChange: setPagination, + enablePagination: true, + enableBottomToolbar: true, + muiToolbarAlertBannerProps: isError + ? { + color: "error", + children: "Error loading data", + } + : undefined, + muiTableBodyRowProps: { + className: "applications-in-review-list__row", + }, + renderRowActions: useCallback( + ({ row }: { row: MRT_Row }) => { + /* TODO the swagger docs use the term "applicationId" when targetting specific applications + check with Praveen if this is correct as we only have "permitId" or "applicationNumber" + **/ + return ( +
+ +
+ ); + }, + [], + ), + }); + + return ( + <> + {showAIRTable ? ( +
+ + + +
+ ) : ( + + )} + + ); +}; diff --git a/frontend/src/features/permits/components/permit-list/ApplicationsInReviewRowOptions.tsx b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewRowOptions.tsx new file mode 100644 index 000000000..c78c51d94 --- /dev/null +++ b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewRowOptions.tsx @@ -0,0 +1,97 @@ +import { useEffect, useState } from "react"; +import { OnRouteBCTableRowActions } from "../../../../common/components/table/OnRouteBCTableRowActions"; +import { useUpdateApplicationQueueStatusMutation } from "../../hooks/hooks"; +import { CASE_ACTIVITY_TYPES } from "../../types/CaseActivityType"; +import { ApplicationInReviewModal } from "./ApplicationInReviewModal"; +import { useQueryClient } from "@tanstack/react-query"; + +const PERMIT_ACTION_OPTION_TYPES = { + WITHDRAW_APPLICATION: "withdrawApplication", +} as const; + +type PermitActionOptionType = + (typeof PERMIT_ACTION_OPTION_TYPES)[keyof typeof PERMIT_ACTION_OPTION_TYPES]; + +const getOptionLabel = (optionType: PermitActionOptionType): string => { + if (optionType === PERMIT_ACTION_OPTION_TYPES.WITHDRAW_APPLICATION) { + return "Withdraw Application"; + } + + return ""; +}; + +const ALL_OPTIONS = [ + { + label: getOptionLabel(PERMIT_ACTION_OPTION_TYPES.WITHDRAW_APPLICATION), + value: PERMIT_ACTION_OPTION_TYPES.WITHDRAW_APPLICATION, + }, +]; + +const getOptions = (isInReview: boolean) => { + return ALL_OPTIONS.filter((option) => { + // Exclude 'WITHDRAW_APPLICATION' if 'isInReview' is false + if ( + isInReview && + option.value === PERMIT_ACTION_OPTION_TYPES.WITHDRAW_APPLICATION + ) { + return false; + } + return true; + }); +}; + +export const ApplicationsInReviewRowOptions = ({ + isInReview, + permitId, +}: { + isInReview: boolean; + permitId: string; +}) => { + const [isAIRModalOpen, setIsAIRModalOpen] = useState(false); + + const queryClient = useQueryClient(); + + const handleCloseAIRModal = () => { + setIsAIRModalOpen(false); + queryClient.invalidateQueries({ + queryKey: ["applicationsInQueue"], + }); + }; + + const { mutateAsync, isError, error } = + useUpdateApplicationQueueStatusMutation(); + + useEffect(() => { + if (isError && error.response?.status === 422) { + setIsAIRModalOpen(true); + } + }, [isError, error]); + + /** + * Action handler upon a select event. + * @param selectedOption The option that was selected. + */ + const onSelectOptionCallback = (selectedOption: string) => { + if (selectedOption === PERMIT_ACTION_OPTION_TYPES.WITHDRAW_APPLICATION) { + mutateAsync({ + applicationId: permitId, + caseActivityType: CASE_ACTIVITY_TYPES.WITHDRAWN, + }); + } + }; + + return ( + <> + + + + + ); +}; diff --git a/frontend/src/features/permits/components/permit-list/PermitChip.tsx b/frontend/src/features/permits/components/permit-list/PermitChip.tsx index 332b955fb..d418882ac 100644 --- a/frontend/src/features/permits/components/permit-list/PermitChip.tsx +++ b/frontend/src/features/permits/components/permit-list/PermitChip.tsx @@ -5,6 +5,7 @@ import { PERMIT_STATUSES, isPermitInactive, } from "../../types/PermitStatus"; +import { APPLICATION_QUEUE_STATUSES } from "../../types/ApplicationQueueStatus"; /** * Returns the theme name for the chip based on the permit status. @@ -23,6 +24,12 @@ const getTheme = (permitStatus?: string) => { return "superseded"; case PERMIT_EXPIRED: return "expired"; + case APPLICATION_QUEUE_STATUSES.PENDING_REVIEW: + return "pending-review"; + case APPLICATION_QUEUE_STATUSES.IN_REVIEW: + return "in-review"; + case APPLICATION_QUEUE_STATUSES.CLOSED: + return "closed"; default: return undefined; } @@ -43,6 +50,12 @@ const getStatusText = (permitStatus?: string): string => { return "Superseded"; case PERMIT_EXPIRED: return "Expired"; + case APPLICATION_QUEUE_STATUSES.PENDING_REVIEW: + return "Pending Review"; + case APPLICATION_QUEUE_STATUSES.IN_REVIEW: + return "In Review"; + case APPLICATION_QUEUE_STATUSES.CLOSED: + return "Closed"; default: return ""; } diff --git a/frontend/src/features/permits/hooks/hooks.ts b/frontend/src/features/permits/hooks/hooks.ts index 6f99df931..4ba37e7e2 100644 --- a/frontend/src/features/permits/hooks/hooks.ts +++ b/frontend/src/features/permits/hooks/hooks.ts @@ -1,4 +1,4 @@ -import { useState, useEffect } from "react"; +import { useState, useEffect, useContext } from "react"; import { AxiosError } from "axios"; import { MRT_PaginationState, MRT_SortingState } from "material-react-table"; import { @@ -33,7 +33,14 @@ import { getApplicationsInProgress, resendPermit, getPendingPermits, + getApplicationsInQueue, + updateApplicationQueueStatus, } from "../apiManager/permitsAPI"; +import { + CASE_ACTIVITY_TYPES, + CaseActivityType, +} from "../types/CaseActivityType"; +import { SnackBarContext } from "../../../App"; const QUERY_KEYS = { PERMIT_DETAIL: ( @@ -332,10 +339,16 @@ export const useAmendPermit = (companyIdParam?: Nullable) => { queryKey: QUERY_KEYS.PERMIT_DETAIL(data.permitId, companyIdParam), }); queryClient.invalidateQueries({ - queryKey: QUERY_KEYS.AMEND_APPLICATION(data.originalPermitId, companyIdParam), + queryKey: QUERY_KEYS.AMEND_APPLICATION( + data.originalPermitId, + companyIdParam, + ), }); queryClient.invalidateQueries({ - queryKey: QUERY_KEYS.PERMIT_HISTORY(data.originalPermitId, companyIdParam), + queryKey: QUERY_KEYS.PERMIT_HISTORY( + data.originalPermitId, + companyIdParam, + ), }); return { @@ -430,20 +443,28 @@ export const useApplicationsInProgressQuery = () => { }, ]); - const orderBy = sorting.length > 0 ? [ - { - column: sorting.at(0)?.id as string, - descending: Boolean(sorting.at(0)?.desc), - }, - ] : []; + const orderBy = + sorting.length > 0 + ? [ + { + column: sorting.at(0)?.id as string, + descending: Boolean(sorting.at(0)?.desc), + }, + ] + : []; const applicationsInProgressQuery = useQuery({ - queryKey: ["applicationsInProgress", pagination.pageIndex, pagination.pageSize, sorting], + queryKey: [ + "applicationsInProgress", + pagination.pageIndex, + pagination.pageSize, + sorting, + ], queryFn: () => getApplicationsInProgress({ page: pagination.pageIndex, take: pagination.pageSize, - orderBy, + orderBy, }), refetchOnWindowFocus: false, // prevent unnecessary multiple queries on page showing up in foreground refetchOnMount: "always", @@ -488,6 +509,97 @@ export const usePendingPermitsQuery = () => { }; }; +/** + * Hook that fetches applications in queue (PENDING_REVIEW, IN_REVIEW) and manages its pagination state. + * @returns Applications in queue along with pagination state and setter + */ +export const useApplicationsInQueueQuery = () => { + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 10, + }); + + const [sorting, setSorting] = useState([ + { + id: "updatedDateTime", + desc: true, + }, + ]); + + const orderBy = + sorting.length > 0 + ? [ + { + column: sorting.at(0)?.id as string, + descending: Boolean(sorting.at(0)?.desc), + }, + ] + : []; + + const applicationsInQueueQuery = useQuery({ + queryKey: [ + "applicationsInQueue", + pagination.pageIndex, + pagination.pageSize, + sorting, + ], + queryFn: () => + getApplicationsInQueue({ + page: pagination.pageIndex, + take: pagination.pageSize, + orderBy, + }), + refetchOnWindowFocus: false, // prevent unnecessary multiple queries on page showing up in foreground + refetchOnMount: "always", + placeholderData: keepPreviousData, + }); + + return { + applicationsInQueueQuery, + pagination, + setPagination, + sorting, + setSorting, + }; +}; + +export const useUpdateApplicationQueueStatusMutation = () => { + const queryClient = useQueryClient(); + const { setSnackBar } = useContext(SnackBarContext); + + return useMutation({ + mutationFn: (data: { + applicationId: string; + caseActivityType: CaseActivityType; + comment?: string; + }) => { + const { applicationId, caseActivityType, comment } = data; + + return updateApplicationQueueStatus( + applicationId, + caseActivityType, + comment, + ); + }, + onSuccess: (_data, variables) => { + // TODO is it possible to update Applications In Progress counter here? + const { caseActivityType } = variables; + if (caseActivityType === CASE_ACTIVITY_TYPES.WITHDRAWN) { + setSnackBar({ + showSnackbar: true, + setShowSnackbar: () => true, + message: "Withdrawn to Applications in Progress", + alertType: "info", + }); + queryClient.invalidateQueries({ + queryKey: ["applicationsInQueue"], + }); + } + }, + onError: (err: AxiosError) => err, + }); +}; + /** * Hook used for resending a permit. * @returns Mutation object to be used for resending a permit diff --git a/frontend/src/features/permits/types/ApplicationQueueStatus.ts b/frontend/src/features/permits/types/ApplicationQueueStatus.ts new file mode 100644 index 000000000..e995ad16c --- /dev/null +++ b/frontend/src/features/permits/types/ApplicationQueueStatus.ts @@ -0,0 +1,8 @@ +export const APPLICATION_QUEUE_STATUSES = { + PENDING_REVIEW: "PENDING_REVIEW", + IN_REVIEW: "IN_REVIEW", + CLOSED: "CLOSED", +} as const; + +export type ApplicationQueueStatus = + (typeof APPLICATION_QUEUE_STATUSES)[keyof typeof APPLICATION_QUEUE_STATUSES]; diff --git a/frontend/src/features/permits/types/CaseActivityType.ts b/frontend/src/features/permits/types/CaseActivityType.ts new file mode 100644 index 000000000..d5c9bff15 --- /dev/null +++ b/frontend/src/features/permits/types/CaseActivityType.ts @@ -0,0 +1,16 @@ +export const APPLICATION_QUEUE_STATUSES = { + PENDING_REVIEW: "PENDING_REVIEW", + IN_REVIEW: "IN_REVIEW", + CLOSED: "CLOSED", +} as const; + +export type ApplicationQueueStatus = + (typeof APPLICATION_QUEUE_STATUSES)[keyof typeof APPLICATION_QUEUE_STATUSES]; + +export const CASE_ACTIVITY_TYPES = { + APPROVED: "APPROVED", + REJECTED: "REJECTED", + WITHDRAWN: "WITHDRAWN", +}; +export type CaseActivityType = + (typeof CASE_ACTIVITY_TYPES)[keyof typeof CASE_ACTIVITY_TYPES]; diff --git a/frontend/src/features/permits/types/PermitStatus.ts b/frontend/src/features/permits/types/PermitStatus.ts index 55de5ec1b..c54146b2e 100644 --- a/frontend/src/features/permits/types/PermitStatus.ts +++ b/frontend/src/features/permits/types/PermitStatus.ts @@ -5,6 +5,7 @@ export const PERMIT_STATUSES = { IN_PROGRESS: "IN_PROGRESS", IN_CART: "IN_CART", REJECTED: "REJECTED", + IN_QUEUE: "IN_QUEUE", UNDER_REVIEW: "UNDER_REVIEW", WAITING_APPROVAL: "WAITING_APPROVAL", WAITING_PAYMENT: "WAITING_PAYMENT", diff --git a/frontend/src/features/permits/types/application.ts b/frontend/src/features/permits/types/application.ts index 2688b1752..102fb5992 100644 --- a/frontend/src/features/permits/types/application.ts +++ b/frontend/src/features/permits/types/application.ts @@ -7,6 +7,7 @@ import { Nullable } from "../../../common/types/common"; import { PermitApplicationOrigin } from "./PermitApplicationOrigin"; import { PermitApprovalSource } from "./PermitApprovalSource"; import { PermitData } from "./PermitData"; +import { ApplicationQueueStatus } from "./ApplicationQueueStatus"; /** * A partial permit type that consists of all common fields used for a permit. @@ -58,9 +59,8 @@ type TransformPermitData = { /** * Type for response data from fetching Application details. */ -export interface ApplicationResponseData extends TransformPermitData< - ReplaceDayjsWithString ->{}; +export interface ApplicationResponseData + extends TransformPermitData> {} /** * Type for create application request payload. @@ -76,7 +76,7 @@ export interface CreateApplicationRequestData { permitApplicationOrigin?: Nullable; permitData: ReplaceDayjsWithString; comment?: Nullable; -}; +} /** * Type for update application request payload. @@ -107,6 +107,7 @@ export interface ApplicationListItem { unitNumber?: Nullable; vin?: Nullable; plate?: Nullable; + applicationQueueStatus?: ApplicationQueueStatus; } /** diff --git a/frontend/src/features/permits/types/permit.ts b/frontend/src/features/permits/types/permit.ts index fcfa53e72..c85e85c70 100644 --- a/frontend/src/features/permits/types/permit.ts +++ b/frontend/src/features/permits/types/permit.ts @@ -14,10 +14,7 @@ import { PermitApplicationOrigin } from "./PermitApplicationOrigin"; interface PartialPermit extends Omit< Required, - "previousRevision" - | "comment" - | "userGuid" - | "documentId" + "previousRevision" | "comment" | "userGuid" | "documentId" > { previousRevision?: Nullable; comment?: Nullable; @@ -68,7 +65,7 @@ export interface PermitListItem { /** * Type for permit response data from fetching permit details. */ -export interface PermitResponseData extends Permit {}; +export interface PermitResponseData extends Permit {} /** * Type used to describe the response object from various actions performed on permits @@ -82,4 +79,4 @@ export interface PermitsActionResponse { /** * Type used to describe the response object for issuing permits. */ -export interface IssuePermitsResponse extends PermitsActionResponse {}; +export interface IssuePermitsResponse extends PermitsActionResponse {} diff --git a/vehicles/src/common/constants/permit.constant.ts b/vehicles/src/common/constants/permit.constant.ts index c1e5804ae..7d9341c9b 100644 --- a/vehicles/src/common/constants/permit.constant.ts +++ b/vehicles/src/common/constants/permit.constant.ts @@ -11,4 +11,6 @@ export const TROW_MAX_VALID_DURATION = 366; export const PERMIT_TYPES_FOR_QUEUE: readonly PermitType[] = [ PermitType.SINGLE_TRIP_OVERSIZE, + // TODO remove this before push + PermitType.TERM_OVERSIZE, ]; From 106cedf2488ffdd919989544539fd3ab1c3cc637 Mon Sep 17 00:00:00 2001 From: GlenAOT <160973940+GlenAOT@users.noreply.github.com> Date: Thu, 12 Sep 2024 16:32:32 -0700 Subject: [PATCH 2/5] ready for push --- .../features/permits/apiManager/permitsAPI.ts | 14 +- .../components/dashboard/PermitLists.tsx | 28 +-- .../ApplicationInReviewColumnDefinition.tsx | 177 ++++++++---------- .../permit-list/ApplicationsInReviewList.tsx | 22 +-- frontend/src/features/permits/hooks/hooks.ts | 1 - .../src/common/constants/permit.constant.ts | 2 - 6 files changed, 101 insertions(+), 143 deletions(-) diff --git a/frontend/src/features/permits/apiManager/permitsAPI.ts b/frontend/src/features/permits/apiManager/permitsAPI.ts index a32ca6077..e910da276 100644 --- a/frontend/src/features/permits/apiManager/permitsAPI.ts +++ b/frontend/src/features/permits/apiManager/permitsAPI.ts @@ -646,12 +646,18 @@ export const updateApplicationQueueStatus = async ( ) => { const companyId = getDefaultRequiredVal("", getCompanyIdFromSession()); + const data: any = { + caseActivityType, + }; + + // Conditionally include the comment property if it is given as an argument and not an empty string + if (comment && comment.trim() !== "") { + data.comment = [comment]; + } + const response = await httpPOSTRequest( APPLICATION_QUEUE_API_ROUTES.UPDATE_QUEUE_STATUS(companyId, applicationId), - { - caseActivityType, - ...(comment && { comment }), - }, + data, ); return response; }; diff --git a/frontend/src/features/permits/components/dashboard/PermitLists.tsx b/frontend/src/features/permits/components/dashboard/PermitLists.tsx index a92993f16..41f171896 100644 --- a/frontend/src/features/permits/components/dashboard/PermitLists.tsx +++ b/frontend/src/features/permits/components/dashboard/PermitLists.tsx @@ -18,20 +18,6 @@ export const PermitLists = React.memo(() => { }; const tabs = []; - // TODO move this code back below showApplicationsInProgressTab to restore proper tab order - const showApplicationsInReviewTab = usePermissionMatrix({ - permissionMatrixKeys: { - permissionMatrixFeatureKey: "MANAGE_PERMITS", - permissionMatrixFunctionKey: "VIEW_LIST_OF_APPLICATIONS_IN_REVIEW", - }, - }); - if (showApplicationsInReviewTab) { - tabs.push({ - label: "Applications in Review", - component: , - }); - } - const showApplicationsInProgressTab = usePermissionMatrix({ permissionMatrixKeys: { permissionMatrixFeatureKey: "MANAGE_PERMITS", @@ -51,6 +37,20 @@ export const PermitLists = React.memo(() => { }); } + const showApplicationsInReviewTab = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "MANAGE_PERMITS", + permissionMatrixFunctionKey: "VIEW_LIST_OF_APPLICATIONS_IN_REVIEW", + }, + }); + + if (showApplicationsInReviewTab) { + tabs.push({ + label: "Applications in Review", + component: , + }); + } + tabs.push( { label: "Active Permits", diff --git a/frontend/src/features/permits/components/permit-list/ApplicationInReviewColumnDefinition.tsx b/frontend/src/features/permits/components/permit-list/ApplicationInReviewColumnDefinition.tsx index 4452b05b1..cf49b188c 100644 --- a/frontend/src/features/permits/components/permit-list/ApplicationInReviewColumnDefinition.tsx +++ b/frontend/src/features/permits/components/permit-list/ApplicationInReviewColumnDefinition.tsx @@ -1,111 +1,84 @@ import { MRT_ColumnDef } from "material-react-table"; - import { ApplicationListItem } from "../../types/application"; -import { APPLICATIONS_ROUTES } from "../../../../routes/constants"; -import { CustomNavLink } from "../../../../common/components/links/CustomNavLink"; -import { UserRoleType } from "../../../../common/authentication/types"; -import { canUserAccessApplication } from "../../helpers/mappers"; -import { Nullable } from "../../../../common/types/common"; import { getPermitTypeName } from "../../types/PermitType"; import { Box, Tooltip } from "@mui/material"; import { ApplicationInReviewStatusChip } from "./ApplicationInReviewStatusChip"; -export const ApplicationInReviewColumnDefinition = ( - userRole?: Nullable, -): MRT_ColumnDef[] => [ - { - accessorKey: "applicationNumber", - id: "applicationNumber", - enableSorting: false, - header: "Application #", - accessorFn: (row) => row.applicationNumber, - Cell: (props: { cell: any; row: any }) => { - const permitIdStr = `${props.row.original.permitId}`; - - return canUserAccessApplication( - props.row.original.permitApplicationOrigin, - userRole, - ) ? ( -
- +export const ApplicationInReviewColumnDefinition: MRT_ColumnDef[] = + [ + { + accessorKey: "applicationNumber", + id: "applicationNumber", + enableSorting: false, + header: "Application #", + accessorFn: (row) => row.applicationNumber, + Cell: (props: { cell: any; row: any }) => { + return ( + <> {props.cell.getValue()} - - -
- ) : ( - <> - {props.cell.getValue()} - - - ); + + + ); + }, + minSize: 320, }, - minSize: 320, - }, - { - accessorKey: "permitType", - id: "permitType", - enableSorting: false, - header: "Permit Type", - Cell: (props: { cell: any }) => { - const permitTypeName = getPermitTypeName(props.cell.getValue()); - return ( - - {props.cell.getValue()} - - ); + { + accessorKey: "permitType", + id: "permitType", + enableSorting: false, + header: "Permit Type", + Cell: (props: { cell: any }) => { + const permitTypeName = getPermitTypeName(props.cell.getValue()); + return ( + + {props.cell.getValue()} + + ); + }, + size: 80, }, - size: 80, - }, - { - accessorKey: "unitNumber", - id: "unitNumber", - enableSorting: false, - header: "Unit #", - size: 50, - }, - { - accessorKey: "vin", - id: "vin", - enableSorting: false, - header: "VIN", - size: 50, - }, - { - accessorKey: "plate", - id: "plate", - enableSorting: false, - header: "Plate", - size: 50, - }, - { - accessorKey: "startDate", - id: "startDate", - enableSorting: true, - header: "Permit Start Date", - size: 140, - }, - { - accessorKey: "updatedDateTime", - enableSorting: true, - id: "updatedDateTime", - header: "Last Updated", - size: 200, - }, - { - accessorKey: "applicant", - id: "applicant", - header: "Applicant", - enableSorting: false, - size: 200, - }, -]; - -export const ApplicationNotFoundColumnDefinition: MRT_ColumnDef[] = - []; + { + accessorKey: "unitNumber", + id: "unitNumber", + enableSorting: false, + header: "Unit #", + size: 50, + }, + { + accessorKey: "vin", + id: "vin", + enableSorting: false, + header: "VIN", + size: 50, + }, + { + accessorKey: "plate", + id: "plate", + enableSorting: false, + header: "Plate", + size: 50, + }, + { + accessorKey: "startDate", + id: "startDate", + enableSorting: true, + header: "Permit Start Date", + size: 140, + }, + { + accessorKey: "updatedDateTime", + enableSorting: true, + id: "updatedDateTime", + header: "Last Updated", + size: 200, + }, + { + accessorKey: "applicant", + id: "applicant", + header: "Applicant", + enableSorting: false, + size: 200, + }, + ]; diff --git a/frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.tsx b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.tsx index 3864b5de1..f2896a377 100644 --- a/frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.tsx +++ b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewList.tsx @@ -1,7 +1,6 @@ -import { useCallback, useContext, useEffect, useMemo, useState } from "react"; +import { useCallback, useContext, useEffect, useState } from "react"; import { RowSelectionState } from "@tanstack/table-core"; import { - MRT_ColumnDef, MaterialReactTable, useMaterialReactTable, } from "material-react-table"; @@ -16,8 +15,6 @@ import { getDefaultNullableVal, getDefaultRequiredVal, } from "../../../../common/helpers/util"; -import { UserRoleType } from "../../../../common/authentication/types"; -import { Nullable } from "../../../../common/types/common"; import { useApplicationsInQueueQuery } from "../../hooks/hooks"; import { InfoBcGovBanner } from "../../../../common/components/banners/InfoBcGovBanner"; import { @@ -30,12 +27,6 @@ import { MRT_Row } from "material-react-table"; import { ApplicationsInReviewRowOptions } from "./ApplicationsInReviewRowOptions"; import { APPLICATION_QUEUE_STATUSES } from "../../types/ApplicationQueueStatus"; -const getColumns = ( - userRole?: Nullable, -): MRT_ColumnDef[] => { - return ApplicationInReviewColumnDefinition(userRole); -}; - export const ApplicationsInReviewList = () => { const { applicationsInQueueQuery, @@ -52,7 +43,6 @@ export const ApplicationsInReviewList = () => { isFetching, } = applicationsInQueueQuery; - console.log(applicationsInQueue); const [showAIRTable, setShowAIRTable] = useState(false); useEffect(() => { @@ -73,11 +63,6 @@ export const ApplicationsInReviewList = () => { const [rowSelection, setRowSelection] = useState({}); - const columns = useMemo[]>( - () => getColumns(userRole), - [userRole], - ); - useEffect(() => { if (isError) { snackBar.setSnackBar({ @@ -91,7 +76,7 @@ export const ApplicationsInReviewList = () => { const table = useMaterialReactTable({ ...defaultTableOptions, - columns: columns, + columns: ApplicationInReviewColumnDefinition, data: getDefaultRequiredVal([], applicationsInQueue?.items), initialState: { ...defaultTableInitialStateOptions, @@ -146,9 +131,6 @@ export const ApplicationsInReviewList = () => { }, renderRowActions: useCallback( ({ row }: { row: MRT_Row }) => { - /* TODO the swagger docs use the term "applicationId" when targetting specific applications - check with Praveen if this is correct as we only have "permitId" or "applicationNumber" - **/ return (
{ ); }, onSuccess: (_data, variables) => { - // TODO is it possible to update Applications In Progress counter here? const { caseActivityType } = variables; if (caseActivityType === CASE_ACTIVITY_TYPES.WITHDRAWN) { setSnackBar({ diff --git a/vehicles/src/common/constants/permit.constant.ts b/vehicles/src/common/constants/permit.constant.ts index 7d9341c9b..c1e5804ae 100644 --- a/vehicles/src/common/constants/permit.constant.ts +++ b/vehicles/src/common/constants/permit.constant.ts @@ -11,6 +11,4 @@ export const TROW_MAX_VALID_DURATION = 366; export const PERMIT_TYPES_FOR_QUEUE: readonly PermitType[] = [ PermitType.SINGLE_TRIP_OVERSIZE, - // TODO remove this before push - PermitType.TERM_OVERSIZE, ]; From 9ffcbc8018c1d95390a47514fc4ada756a9c7b0a Mon Sep 17 00:00:00 2001 From: GlenAOT <160973940+GlenAOT@users.noreply.github.com> Date: Fri, 13 Sep 2024 11:14:09 -0700 Subject: [PATCH 3/5] initial PR review changes --- .../features/permits/apiManager/permitsAPI.ts | 4 +-- .../permit-list/ApplicationInReviewModal.scss | 36 +------------------ .../ApplicationsInReviewRowOptions.tsx | 16 ++++++--- .../src/common/constants/permit.constant.ts | 2 ++ 4 files changed, 17 insertions(+), 41 deletions(-) diff --git a/frontend/src/features/permits/apiManager/permitsAPI.ts b/frontend/src/features/permits/apiManager/permitsAPI.ts index e910da276..dd9d84127 100644 --- a/frontend/src/features/permits/apiManager/permitsAPI.ts +++ b/frontend/src/features/permits/apiManager/permitsAPI.ts @@ -127,14 +127,14 @@ const getApplications = async ( applicationsURL.searchParams.set("page", `${page + 1}`); applicationsURL.searchParams.set("take", `${take}`); - if (pendingPermitsOnly !== undefined) { + if (typeof pendingPermitsOnly !== "undefined") { applicationsURL.searchParams.set( "pendingPermits", `${Boolean(pendingPermitsOnly)}`, ); } - if (applicationsInQueueOnly !== undefined) { + if (typeof applicationsInQueueOnly !== "undefined") { applicationsURL.searchParams.set( "applicationsInQueue", `${Boolean(applicationsInQueueOnly)}`, diff --git a/frontend/src/features/permits/components/permit-list/ApplicationInReviewModal.scss b/frontend/src/features/permits/components/permit-list/ApplicationInReviewModal.scss index c4e3ff0fa..3b92e3947 100644 --- a/frontend/src/features/permits/components/permit-list/ApplicationInReviewModal.scss +++ b/frontend/src/features/permits/components/permit-list/ApplicationInReviewModal.scss @@ -17,7 +17,7 @@ } &__title { - font-weight: 600; + font-weight: bold; font-size: 1.5rem; margin: 0 0 0 0.5em; } @@ -36,39 +36,5 @@ justify-content: flex-end; padding: 0 1.5rem 1.5rem 1.5rem; gap: 1.5rem; - - .remove-users-btn { - &--cancel { - background-color: $bc-background-light-grey; - border: 2px solid transparent; - box-shadow: none; - color: $bc-black; - cursor: pointer; - - &:hover { - background-color: $bc-background-light-grey; - border-color: $bc-text-box-border-grey; - box-shadow: none; - } - } - - &--confirm { - background-color: $bc-red; - box-shadow: none; - color: $white; - cursor: pointer; - font-weight: bold; - - &:hover { - background-color: $bc-messages-red-text; - box-shadow: none; - } - - &:disabled { - background-color: #EBC0C1; - color: $white; - } - } - } } } diff --git a/frontend/src/features/permits/components/permit-list/ApplicationsInReviewRowOptions.tsx b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewRowOptions.tsx index c78c51d94..1afcf6953 100644 --- a/frontend/src/features/permits/components/permit-list/ApplicationsInReviewRowOptions.tsx +++ b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewRowOptions.tsx @@ -3,6 +3,8 @@ import { OnRouteBCTableRowActions } from "../../../../common/components/table/On import { useUpdateApplicationQueueStatusMutation } from "../../hooks/hooks"; import { CASE_ACTIVITY_TYPES } from "../../types/CaseActivityType"; import { ApplicationInReviewModal } from "./ApplicationInReviewModal"; +import { useNavigate } from "react-router-dom"; +import { ERROR_ROUTES } from "../../../../routes/constants"; import { useQueryClient } from "@tanstack/react-query"; const PERMIT_ACTION_OPTION_TYPES = { @@ -47,10 +49,11 @@ export const ApplicationsInReviewRowOptions = ({ isInReview: boolean; permitId: string; }) => { - const [isAIRModalOpen, setIsAIRModalOpen] = useState(false); - + const navigate = useNavigate(); const queryClient = useQueryClient(); + const [isAIRModalOpen, setIsAIRModalOpen] = useState(false); + const handleCloseAIRModal = () => { setIsAIRModalOpen(false); queryClient.invalidateQueries({ @@ -62,8 +65,13 @@ export const ApplicationsInReviewRowOptions = ({ useUpdateApplicationQueueStatusMutation(); useEffect(() => { - if (isError && error.response?.status === 422) { - setIsAIRModalOpen(true); + if (isError) { + // if the application has already been withdrawn by another user + if (error.response?.status === 422) { + return setIsAIRModalOpen(true); + } + // handle all other errors + navigate(ERROR_ROUTES.UNEXPECTED); } }, [isError, error]); diff --git a/vehicles/src/common/constants/permit.constant.ts b/vehicles/src/common/constants/permit.constant.ts index c1e5804ae..5226e3253 100644 --- a/vehicles/src/common/constants/permit.constant.ts +++ b/vehicles/src/common/constants/permit.constant.ts @@ -11,4 +11,6 @@ export const TROW_MAX_VALID_DURATION = 366; export const PERMIT_TYPES_FOR_QUEUE: readonly PermitType[] = [ PermitType.SINGLE_TRIP_OVERSIZE, + // TODO remove this + PermitType.TERM_OVERSIZE, ]; From c05d7f5cfca2305b55d0a2d633effad8349fcc50 Mon Sep 17 00:00:00 2001 From: GlenAOT <160973940+GlenAOT@users.noreply.github.com> Date: Fri, 13 Sep 2024 16:51:02 -0700 Subject: [PATCH 4/5] extract invalidateQueries logic for applicationsInQueue to separate hook --- .../ApplicationsInReviewRowOptions.tsx | 12 ++++++------ frontend/src/features/permits/hooks/hooks.ts | 16 ++++++++++++---- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/frontend/src/features/permits/components/permit-list/ApplicationsInReviewRowOptions.tsx b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewRowOptions.tsx index 1afcf6953..ceee9c676 100644 --- a/frontend/src/features/permits/components/permit-list/ApplicationsInReviewRowOptions.tsx +++ b/frontend/src/features/permits/components/permit-list/ApplicationsInReviewRowOptions.tsx @@ -1,11 +1,13 @@ import { useEffect, useState } from "react"; import { OnRouteBCTableRowActions } from "../../../../common/components/table/OnRouteBCTableRowActions"; -import { useUpdateApplicationQueueStatusMutation } from "../../hooks/hooks"; +import { + useUpdateApplicationQueueStatusMutation, + useInvalidateApplicationsInQueue, +} from "../../hooks/hooks"; import { CASE_ACTIVITY_TYPES } from "../../types/CaseActivityType"; import { ApplicationInReviewModal } from "./ApplicationInReviewModal"; import { useNavigate } from "react-router-dom"; import { ERROR_ROUTES } from "../../../../routes/constants"; -import { useQueryClient } from "@tanstack/react-query"; const PERMIT_ACTION_OPTION_TYPES = { WITHDRAW_APPLICATION: "withdrawApplication", @@ -50,15 +52,13 @@ export const ApplicationsInReviewRowOptions = ({ permitId: string; }) => { const navigate = useNavigate(); - const queryClient = useQueryClient(); + const { invalidate } = useInvalidateApplicationsInQueue(); const [isAIRModalOpen, setIsAIRModalOpen] = useState(false); const handleCloseAIRModal = () => { setIsAIRModalOpen(false); - queryClient.invalidateQueries({ - queryKey: ["applicationsInQueue"], - }); + invalidate(); }; const { mutateAsync, isError, error } = diff --git a/frontend/src/features/permits/hooks/hooks.ts b/frontend/src/features/permits/hooks/hooks.ts index a50ac965f..bfedb1884 100644 --- a/frontend/src/features/permits/hooks/hooks.ts +++ b/frontend/src/features/permits/hooks/hooks.ts @@ -565,7 +565,7 @@ export const useApplicationsInQueueQuery = () => { }; export const useUpdateApplicationQueueStatusMutation = () => { - const queryClient = useQueryClient(); + const { invalidate } = useInvalidateApplicationsInQueue(); const { setSnackBar } = useContext(SnackBarContext); return useMutation({ @@ -591,15 +591,23 @@ export const useUpdateApplicationQueueStatusMutation = () => { message: "Withdrawn to Applications in Progress", alertType: "info", }); - queryClient.invalidateQueries({ - queryKey: ["applicationsInQueue"], - }); + invalidate(); } }, onError: (err: AxiosError) => err, }); }; +export const useInvalidateApplicationsInQueue = () => { + const queryClient = useQueryClient(); + + return { + invalidate: () => { + queryClient.invalidateQueries({ queryKey: ["applicationsInQueue"] }); + }, + }; +}; + /** * Hook used for resending a permit. * @returns Mutation object to be used for resending a permit From c4543264dd1cc83278a57be952fbb030584fcd5e Mon Sep 17 00:00:00 2001 From: GlenAOT <160973940+GlenAOT@users.noreply.github.com> Date: Fri, 13 Sep 2024 16:52:34 -0700 Subject: [PATCH 5/5] remove TERM_OVERSIZE from PERMIT_TYPES_FOR_QUEUE (this was for development purposes only) --- vehicles/src/common/constants/permit.constant.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/vehicles/src/common/constants/permit.constant.ts b/vehicles/src/common/constants/permit.constant.ts index 5226e3253..c1e5804ae 100644 --- a/vehicles/src/common/constants/permit.constant.ts +++ b/vehicles/src/common/constants/permit.constant.ts @@ -11,6 +11,4 @@ export const TROW_MAX_VALID_DURATION = 366; export const PERMIT_TYPES_FOR_QUEUE: readonly PermitType[] = [ PermitType.SINGLE_TRIP_OVERSIZE, - // TODO remove this - PermitType.TERM_OVERSIZE, ];