From 79bd10d461ca58a972a1c241f20b25105f24afde Mon Sep 17 00:00:00 2001 From: GlenAOT <160973940+GlenAOT@users.noreply.github.com> Date: Tue, 27 May 2025 16:38:30 -0700 Subject: [PATCH 1/5] rename VIEW_ACTIVE_PERMITS to VIEW_LIST_OF_ACTIVE_PERMITS --- .../common/authentication/PermissionMatrix.ts | 2 +- .../components/dashboard/PermitLists.tsx | 35 ++++++++++++++----- frontend/src/routes/Routes.tsx | 2 +- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/frontend/src/common/authentication/PermissionMatrix.ts b/frontend/src/common/authentication/PermissionMatrix.ts index 693154cc1d..40eee4c5ff 100644 --- a/frontend/src/common/authentication/PermissionMatrix.ts +++ b/frontend/src/common/authentication/PermissionMatrix.ts @@ -121,7 +121,7 @@ const MANAGE_PERMITS = { /** * Active Permits tab */ - VIEW_ACTIVE_PERMITS: { + VIEW_LIST_OF_ACTIVE_PERMITS: { allowedBCeIDRoles: ALL_BCeID_ROLES, allowedIDIRRoles: ALL_IDIR_ROLES, }, diff --git a/frontend/src/features/permits/components/dashboard/PermitLists.tsx b/frontend/src/features/permits/components/dashboard/PermitLists.tsx index 5d576d0c06..9b823d414c 100644 --- a/frontend/src/features/permits/components/dashboard/PermitLists.tsx +++ b/frontend/src/features/permits/components/dashboard/PermitLists.tsx @@ -28,14 +28,14 @@ export const PermitLists = React.memo(() => { 0, ); - const showApplicationsInProgressTab = usePermissionMatrix({ + const canViewListOfApplicationsInProgress = usePermissionMatrix({ permissionMatrixKeys: { permissionMatrixFeatureKey: "MANAGE_PERMITS", permissionMatrixFunctionKey: "VIEW_LIST_OF_APPLICATIONS_IN_PROGRESS", }, }); - if (showApplicationsInProgressTab) { + if (canViewListOfApplicationsInProgress) { tabs.push({ label: "Applications in Progress", component: , @@ -43,14 +43,14 @@ export const PermitLists = React.memo(() => { }); } - const showApplicationsInReviewTab = usePermissionMatrix({ + const canViewListOfApplicationsInReview = usePermissionMatrix({ permissionMatrixKeys: { permissionMatrixFeatureKey: "MANAGE_PERMITS", permissionMatrixFunctionKey: "VIEW_LIST_OF_APPLICATIONS_IN_REVIEW", }, }); - if (showApplicationsInReviewTab) { + if (canViewListOfApplicationsInReview) { tabs.push({ label: "Applications in Review", component: , @@ -58,18 +58,35 @@ export const PermitLists = React.memo(() => { }); } - tabs.push( - { + const canViewListOfActivePermits = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "MANAGE_PERMITS", + permissionMatrixFunctionKey: "VIEW_LIST_OF_ACTIVE_PERMITS", + }, + }); + + if (canViewListOfActivePermits) { + tabs.push({ label: "Active Permits", component: , componentKey: PERMIT_TABS.ACTIVE_PERMITS, + }); + } + + const canViewListOfExpiredPermits = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "MANAGE_PERMITS", + permissionMatrixFunctionKey: "VIEW_LIST_OF_EXPIRED_PERMITS", }, - { + }); + + if (canViewListOfExpiredPermits) { + tabs.push({ label: "Expired Permits", component: , componentKey: PERMIT_TABS.EXPIRED_PERMITS, - }, - ); + }); + } const { state: stateFromNavigation } = useLocation(); diff --git a/frontend/src/routes/Routes.tsx b/frontend/src/routes/Routes.tsx index 3dec26dcef..d0127c29b2 100644 --- a/frontend/src/routes/Routes.tsx +++ b/frontend/src/routes/Routes.tsx @@ -370,7 +370,7 @@ export const AppRoutes = () => { } From 4584cce79777d1416f593eb7ffba4df35171ae2e Mon Sep 17 00:00:00 2001 From: GlenAOT <160973940+GlenAOT@users.noreply.github.com> Date: Wed, 28 May 2025 11:14:04 -0700 Subject: [PATCH 2/5] update PermitRowOptions component to handle both IDIR and BCeID users, remove IDIRPermitSearchRowActions component, implement permissions matrix check for relevant components --- .../common/authentication/PermissionMatrix.ts | 2 +- .../components/IDIRPermitSearchResults.tsx | 5 +- .../components/IDIRPermitSearchRowActions.tsx | 218 ---------------- .../components/permit-list/BasePermitList.tsx | 40 +-- .../components/permit-list/Columns.tsx | 170 ++++++------ .../permit-list/PermitRowOptions.tsx | 246 +++++++++++++++--- .../permits/pages/Amend/AmendPermit.tsx | 21 +- .../permits/pages/Void/VoidPermit.tsx | 33 ++- .../pages/Void/components/VoidPermitForm.tsx | 50 ++-- 9 files changed, 377 insertions(+), 408 deletions(-) delete mode 100644 frontend/src/features/idir/search/components/IDIRPermitSearchRowActions.tsx diff --git a/frontend/src/common/authentication/PermissionMatrix.ts b/frontend/src/common/authentication/PermissionMatrix.ts index 703541a6a2..716be36357 100644 --- a/frontend/src/common/authentication/PermissionMatrix.ts +++ b/frontend/src/common/authentication/PermissionMatrix.ts @@ -410,7 +410,7 @@ const GLOBAL_SEARCH = { AMEND_PERMIT: { allowedIDIRRoles: [PC, SA, CTPO] }, VOID_PERMIT: { allowedIDIRRoles: [SA] }, REVOKE_PERMIT: { allowedIDIRRoles: [SA] }, - RESEND: { allowedIDIRRoles: [PC, SA, FIN, CTPO, HQA] }, + RESEND_PERMIT: { allowedIDIRRoles: [PC, SA, FIN, CTPO, HQA] }, /** Search for Inactive Permit */ SEARCH_FOR_INACTIVE_PERMIT: { allowedIDIRRoles: [PC, SA, CTPO, EO] }, diff --git a/frontend/src/features/idir/search/components/IDIRPermitSearchResults.tsx b/frontend/src/features/idir/search/components/IDIRPermitSearchResults.tsx index d4572e8800..492984df1c 100644 --- a/frontend/src/features/idir/search/components/IDIRPermitSearchResults.tsx +++ b/frontend/src/features/idir/search/components/IDIRPermitSearchResults.tsx @@ -18,7 +18,6 @@ import { PermitListItem } from "../../../permits/types/permit"; import { getPermitDataBySearch } from "../api/idirSearch"; import { PermitSearchResultColumnDef } from "../table/PermitSearchResultColumnDef"; import { PERMIT_ACTION_ORIGINS, SearchFields } from "../types/types"; -import { IDIRPermitSearchRowActions } from "./IDIRPermitSearchRowActions"; import { defaultTableInitialStateOptions, defaultTableOptions, @@ -29,6 +28,7 @@ import { ERROR_ROUTES } from "../../../../routes/constants"; import { VEHICLES_URL } from "../../../../common/apiManager/endpoints/endpoints"; import { httpGETRequest } from "../../../../common/apiManager/httpRequestHandler"; import { useSetCompanyHandler } from "../helpers/useSetCompanyHandler"; +import { PermitRowOptions } from "../../../permits/components/permit-list/PermitRowOptions"; /** * Function to decide whether to show row actions icon or not. @@ -205,11 +205,10 @@ export const IDIRPermitSearchResults = memo( if (shouldShowRowActions(idirUserDetails?.userRole)) { return ( - diff --git a/frontend/src/features/idir/search/components/IDIRPermitSearchRowActions.tsx b/frontend/src/features/idir/search/components/IDIRPermitSearchRowActions.tsx deleted file mode 100644 index bb7c233eea..0000000000 --- a/frontend/src/features/idir/search/components/IDIRPermitSearchRowActions.tsx +++ /dev/null @@ -1,218 +0,0 @@ -import { useContext, useState } from "react"; -import { useNavigate } from "react-router-dom"; - -import { OnRouteBCTableRowActions } from "../../../../common/components/table/OnRouteBCTableRowActions"; -import PermitResendDialog from "./PermitResendDialog"; -import { viewReceiptPdf } from "../../../permits/helpers/permitPDFHelper"; -import * as routes from "../../../../routes/constants"; -import { USER_ROLE } from "../../../../common/authentication/types"; -import { useResendPermit } from "../../../permits/hooks/hooks"; -import { SnackBarContext } from "../../../../App"; -import { EmailNotificationType } from "../../../permits/types/EmailNotificationType"; -import { useAttemptAmend } from "../../../permits/hooks/useAttemptAmend"; -import { UnfinishedAmendModal } from "../../../permits/pages/Amend/components/modal/UnfinishedAmendModal"; -import { getDefaultRequiredVal } from "../../../../common/helpers/util"; -import { PermitActionOrigin } from "../types/types"; - -const PERMIT_ACTION_TYPES = { - RESEND: "resend", - VIEW_RECEIPT: "viewReceipt", - AMEND: "amend", - VOID_REVOKE: "voidRevoke", -} as const; - -type PermitActionType = - (typeof PERMIT_ACTION_TYPES)[keyof typeof PERMIT_ACTION_TYPES]; - -const permitActionLabel = (actionType: PermitActionType) => { - switch (actionType) { - case PERMIT_ACTION_TYPES.RESEND: - return "Resend"; - case PERMIT_ACTION_TYPES.VIEW_RECEIPT: - return "View Receipt"; - case PERMIT_ACTION_TYPES.AMEND: - return "Amend"; - case PERMIT_ACTION_TYPES.VOID_REVOKE: - return "Void/Revoke"; - default: - return ""; - } -}; - -interface PermitAction { - action: PermitActionType; - isAuthorized: (isExpired: boolean, userRole?: string) => boolean; -} - -const PERMIT_ACTIONS: PermitAction[] = [ - { - action: PERMIT_ACTION_TYPES.RESEND, - isAuthorized: (_: boolean, userRole?: string) => - userRole === USER_ROLE.PPC_CLERK || - userRole === USER_ROLE.SYSTEM_ADMINISTRATOR || - userRole === USER_ROLE.HQ_ADMINISTRATOR || - userRole === USER_ROLE.FINANCE || - userRole === USER_ROLE.ENFORCEMENT_OFFICER, - }, - { - action: PERMIT_ACTION_TYPES.VIEW_RECEIPT, - isAuthorized: (_: boolean, userRole?: string) => - userRole === USER_ROLE.PPC_CLERK || - userRole === USER_ROLE.SYSTEM_ADMINISTRATOR || - userRole === USER_ROLE.ENFORCEMENT_OFFICER, - }, - { - action: PERMIT_ACTION_TYPES.AMEND, - isAuthorized: (isExpired: boolean, userRole?: string) => - !isExpired && - (userRole === USER_ROLE.PPC_CLERK || - userRole === USER_ROLE.SYSTEM_ADMINISTRATOR), - }, - { - action: PERMIT_ACTION_TYPES.VOID_REVOKE, - isAuthorized: (isExpired: boolean, userRole?: string) => - !isExpired && userRole === USER_ROLE.SYSTEM_ADMINISTRATOR, - }, -]; - -/** - * Returns options for the row actions. - * @param isExpired Has the permit expired? - * @returns Action options that can be performed for the permit. - */ -const getOptions = (isExpired: boolean, userRole?: string) => { - return PERMIT_ACTIONS.filter((action) => - action.isAuthorized(isExpired, userRole), - ).map(({ action }) => ({ - label: permitActionLabel(action), - value: action, - })); -}; - -/** - * Component for row actions on IDIR Search Permit. - */ -export const IDIRPermitSearchRowActions = ({ - permitId, - isPermitInactive, - permitNumber, - userRole, - companyId, - permitActionOrigin, -}: { - /** - * The permit id. - */ - permitId: string; - /** - * Is the permit inactive (voided/superseded/revoked) or expired? - */ - isPermitInactive: boolean; - /** - * The permit number - */ - permitNumber: string; - /** - * The role for the current user (eg. PPCCLERK or EOFFICER) - */ - userRole?: string; - companyId: number; - /** - * The application location from where the permit action (amend / void / revoke) originated - */ - permitActionOrigin: PermitActionOrigin; -}) => { - const [openResendDialog, setOpenResendDialog] = useState(false); - const navigate = useNavigate(); - const resendPermitMutation = useResendPermit(); - const { setSnackBar } = useContext(SnackBarContext); - - const { - choosePermitToAmend, - showUnfinishedModal, - existingAmendmentApplication, - handleCloseModal, - handleStartNewAmendment, - handleContinueAmendment, - } = useAttemptAmend(permitActionOrigin); - - const existingAmendmentCreatedBy = getDefaultRequiredVal( - "", - existingAmendmentApplication?.applicant, - ); - - /** - * Function to handle user selection from the options. - * @param selectedOption The selected option as a string. - */ - const onSelectOption = (selectedOption: string) => { - if (selectedOption === PERMIT_ACTION_TYPES.RESEND) { - setOpenResendDialog(() => true); - } else if (selectedOption === PERMIT_ACTION_TYPES.VIEW_RECEIPT) { - viewReceiptPdf(companyId, permitId, () => - navigate(routes.ERROR_ROUTES.DOCUMENT_UNAVAILABLE), - ); - } else if (selectedOption === PERMIT_ACTION_TYPES.VOID_REVOKE) { - navigate(`${routes.PERMITS_ROUTES.VOID(companyId, permitId)}`); - } else if (selectedOption === PERMIT_ACTION_TYPES.AMEND) { - // Sets the companyId and permitId of the permit to be amended, - // which will in turn look for any existing associated amendment applications, - // which is used to show info in the modal (or not show the modal at all) - choosePermitToAmend(companyId, permitId); - } - }; - - const handleResend = async ( - permitId: string, - email: string, - notificationTypes: EmailNotificationType[], - ) => { - const response = await resendPermitMutation.mutateAsync({ - permitId, - email, - notificationTypes, - }); - - setOpenResendDialog(false); - if (response.status === 201) { - setSnackBar({ - showSnackbar: true, - setShowSnackbar: () => true, - message: "Successfully sent", - alertType: "success", - }); - } else { - navigate(routes.ERROR_ROUTES.UNEXPECTED, { - state: { correlationId: response.headers["x-correlation-id"] }, - }); - } - }; - - return ( - <> - - - setOpenResendDialog(false)} - onResend={handleResend} - companyId={companyId} - permitId={permitId} - permitNumber={permitNumber} - /> - - - - ); -}; diff --git a/frontend/src/features/permits/components/permit-list/BasePermitList.tsx b/frontend/src/features/permits/components/permit-list/BasePermitList.tsx index 5877286a1b..23cfbf9813 100644 --- a/frontend/src/features/permits/components/permit-list/BasePermitList.tsx +++ b/frontend/src/features/permits/components/permit-list/BasePermitList.tsx @@ -16,7 +16,6 @@ import { NoRecordsFound } from "../../../../common/components/table/NoRecordsFou import { getPermits } from "../../apiManager/permitsAPI"; import { PermitListItem } from "../../types/permit"; import { PermitsColumnDefinition } from "./Columns"; -import { PermitRowOptions } from "./PermitRowOptions"; import { useNavigate } from "react-router-dom"; import { ERROR_ROUTES } from "../../../../routes/constants"; import { @@ -24,14 +23,12 @@ import { defaultTableOptions, defaultTableStateOptions, } from "../../../../common/helpers/tableHelper"; -import { IDIRPermitSearchRowActions } from "../../../idir/search/components/IDIRPermitSearchRowActions"; import { hasPermitExpired } from "../../helpers/permitState"; import { isPermitInactive } from "../../types/PermitStatus"; import OnRouteBCContext from "../../../../common/authentication/OnRouteBCContext"; -import { DoesUserHaveRole } from "../../../../common/authentication/util"; -import { IDIR_USER_ROLE } from "../../../../common/authentication/types"; import { applyWhenNotNullable } from "../../../../common/helpers/util"; import { PERMIT_ACTION_ORIGINS } from "../../../idir/search/types/types"; +import { PermitRowOptions } from "./PermitRowOptions"; /** * A permit list component with common functionalities that can be shared by @@ -42,8 +39,7 @@ export const BasePermitList = ({ }: { isExpired?: boolean; }) => { - const { idirUserDetails, companyId: companyIdFromContext } = - useContext(OnRouteBCContext); + const { companyId: companyIdFromContext } = useContext(OnRouteBCContext); const companyId: number = applyWhenNotNullable( (id) => Number(id), @@ -157,31 +153,13 @@ export const BasePermitList = ({ isPermitInactive(row.original.permitStatus); return ( - {DoesUserHaveRole({ - userRole: idirUserDetails?.userRole, - allowedRoles: [ - IDIR_USER_ROLE.PPC_CLERK, - IDIR_USER_ROLE.ENFORCEMENT_OFFICER, - ], - }) ? ( - - ) : ( - { - navigate(ERROR_ROUTES.DOCUMENT_UNAVAILABLE); - }} - /> - )} + ); }, diff --git a/frontend/src/features/permits/components/permit-list/Columns.tsx b/frontend/src/features/permits/components/permit-list/Columns.tsx index 82147304e9..182b52283d 100644 --- a/frontend/src/features/permits/components/permit-list/Columns.tsx +++ b/frontend/src/features/permits/components/permit-list/Columns.tsx @@ -8,87 +8,109 @@ import { formatCellValuetoDatetime } from "../../../../common/helpers/tableHelpe import { CustomActionLink } from "../../../../common/components/links/CustomActionLink"; import { getDefaultRequiredVal } from "../../../../common/helpers/util"; import { getPermitTypeName } from "../../types/PermitType"; +import { usePermissionMatrix } from "../../../../common/authentication/PermissionMatrix"; /** * The column definition for Permits. */ export const PermitsColumnDefinition = ( onDocumentUnavailable: () => void, -): MRT_ColumnDef[] => [ - { - accessorKey: "permitNumber", - id: "permitNumber", - header: "Permit #", - enableSorting: true, - size: 500, - accessorFn: (row) => row.permitNumber, - Cell: (props: { row: any; cell: any }) => { - return ( - <> - viewPermitPdf( - props.row.original.companyId, - props.row.original.permitId, - () => onDocumentUnavailable(), +): MRT_ColumnDef[] => { + const canViewIndividualActivePermitPDF = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "MANAGE_PERMITS", + permissionMatrixFunctionKey: "VIEW_INDIVIDUAL_ACTIVE_PERMIT_PDF", + }, + }); + + return [ + { + accessorKey: "permitNumber", + id: "permitNumber", + header: "Permit #", + enableSorting: true, + size: 500, + accessorFn: (row) => row.permitNumber, + Cell: (props: { row: any; cell: any }) => { + return ( + <> + {canViewIndividualActivePermitPDF ? ( + + viewPermitPdf( + props.row.original.companyId, + props.row.original.permitId, + () => onDocumentUnavailable(), + ) + } + > + {props.cell.getValue()} + + ) : ( + <>{props.cell.getValue()} )} - > - {props.cell.getValue()} - - - - ); + + + ); + }, + }, + { + accessorKey: "permitType", + id: "permitType", + header: "Permit Type", + enableSorting: true, + Cell: (props: { cell: any }) => { + const permitTypeName = getPermitTypeName(props.cell.getValue()); + return ( + + {props.cell.getValue()} + + ); + }, + }, + { + accessorFn: (row) => getDefaultRequiredVal("", row.unitNumber), + id: "unitNumber", + header: "Unit #", + enableSorting: true, + }, + { + accessorKey: "plate", + header: "Plate", + id: "plate", + enableSorting: true, + }, + { + accessorKey: "startDate", + id: "startDate", + header: "Permit Start Date", + enableSorting: true, + Cell: (props: { cell: any }) => { + const formattedDate = formatCellValuetoDatetime( + props.cell.getValue(), + true, + ); + return formattedDate; + }, }, - }, - { - accessorKey: "permitType", - id: "permitType", - header: "Permit Type", - enableSorting: true, - Cell: (props: { cell: any; }) => { - const permitTypeName = getPermitTypeName(props.cell.getValue()) - return - - {props.cell.getValue()} - - - } - }, - { - accessorFn: (row) => getDefaultRequiredVal("", row.unitNumber), - id: "unitNumber", - header: "Unit #", - enableSorting: true, - }, - { - accessorKey: "plate", - header: "Plate", - id: "plate", - enableSorting: true, - }, - { - accessorKey: "startDate", - id: "startDate", - header: "Permit Start Date", - enableSorting: true, - Cell: (props: { cell: any }) => { - const formattedDate = formatCellValuetoDatetime(props.cell.getValue(), true); - return formattedDate; + { + accessorKey: "expiryDate", + header: "Permit End Date", + id: "expiryDate", + enableSorting: true, + Cell: (props: { cell: any }) => { + const formattedDate = formatCellValuetoDatetime( + props.cell.getValue(), + true, + ); + return formattedDate; + }, }, - }, - { - accessorKey: "expiryDate", - header: "Permit End Date", - id: "expiryDate", - enableSorting: true, - Cell: (props: { cell: any }) => { - const formattedDate = formatCellValuetoDatetime(props.cell.getValue(), true); - return formattedDate; + { + accessorKey: "issuer", + id: "issuer", + header: "Issued By", + enableSorting: false, }, - }, - { - accessorKey: "issuer", - id: "issuer", - header: "Issued By", - enableSorting: false, - }, -]; \ No newline at end of file + ]; +}; diff --git a/frontend/src/features/permits/components/permit-list/PermitRowOptions.tsx b/frontend/src/features/permits/components/permit-list/PermitRowOptions.tsx index 621af7bdd3..7747e76d0b 100644 --- a/frontend/src/features/permits/components/permit-list/PermitRowOptions.tsx +++ b/frontend/src/features/permits/components/permit-list/PermitRowOptions.tsx @@ -1,60 +1,232 @@ +import { useContext, useState } from "react"; +import { useNavigate } from "react-router-dom"; + import { OnRouteBCTableRowActions } from "../../../../common/components/table/OnRouteBCTableRowActions"; +import PermitResendDialog from "../../../idir/search/components/PermitResendDialog"; import { viewReceiptPdf } from "../../helpers/permitPDFHelper"; +import * as routes from "../../../../routes/constants"; +import { useResendPermit } from "../../hooks/hooks"; +import { SnackBarContext } from "../../../../App"; +import { EmailNotificationType } from "../../types/EmailNotificationType"; +import { useAttemptAmend } from "../../hooks/useAttemptAmend"; +import { UnfinishedAmendModal } from "../../pages/Amend/components/modal/UnfinishedAmendModal"; +import { getDefaultRequiredVal } from "../../../../common/helpers/util"; +import { PermitActionOrigin } from "../../../idir/search/types/types"; +import { usePermissionMatrix } from "../../../../common/authentication/PermissionMatrix"; -const PERMIT_ACTION_OPTION_TYPES = { +const PERMIT_ACTION_TYPES = { + RESEND: "resend", VIEW_RECEIPT: "viewReceipt", + AMEND: "amend", + VOID_REVOKE: "voidRevoke", } 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.VIEW_RECEIPT) { - return "View Receipt"; - } - - return ""; -}; - -const ALL_OPTIONS = [ - { - label: getOptionLabel(PERMIT_ACTION_OPTION_TYPES.VIEW_RECEIPT), - value: PERMIT_ACTION_OPTION_TYPES.VIEW_RECEIPT, - }, -]; +type PermitActionType = + (typeof PERMIT_ACTION_TYPES)[keyof typeof PERMIT_ACTION_TYPES]; -const getOptions = (isExpired: boolean) => { - if (isExpired) { - return ALL_OPTIONS; +const permitActionLabel = (actionType: PermitActionType) => { + switch (actionType) { + case PERMIT_ACTION_TYPES.RESEND: + return "Resend"; + case PERMIT_ACTION_TYPES.VIEW_RECEIPT: + return "View Receipt"; + case PERMIT_ACTION_TYPES.AMEND: + return "Amend"; + case PERMIT_ACTION_TYPES.VOID_REVOKE: + return "Void/Revoke"; + default: + return ""; } - return ALL_OPTIONS; }; +/** + * Component for row actions on IDIR Search Permit. + */ export const PermitRowOptions = ({ - isExpired, - companyId, permitId, - onDocumentUnavailable, + isPermitInactive, + permitNumber, + companyId, + permitActionOrigin, }: { - isExpired: boolean; - companyId: number; + /** + * The permit id. + */ permitId: string; - onDocumentUnavailable: () => void; + /** + * Is the permit inactive (voided/superseded/revoked) or expired? + */ + isPermitInactive: boolean; + /** + * The permit number + */ + permitNumber: string; + /** + * The company id + */ + companyId: number; + /** + * The application location from where the permit action (amend / void / revoke) originated + */ + permitActionOrigin: PermitActionOrigin; }) => { + const [openResendDialog, setOpenResendDialog] = useState(false); + const navigate = useNavigate(); + const resendPermitMutation = useResendPermit(); + const { setSnackBar } = useContext(SnackBarContext); + + const { + choosePermitToAmend, + showUnfinishedModal, + existingAmendmentApplication, + handleCloseModal, + handleStartNewAmendment, + handleContinueAmendment, + } = useAttemptAmend(permitActionOrigin); + + const existingAmendmentCreatedBy = getDefaultRequiredVal( + "", + existingAmendmentApplication?.applicant, + ); + /** - * Action handler upon a select event. - * @param selectedOption The option that was selected. + * Function to handle user selection from the options. + * @param selectedOption The selected option as a string. */ - const onSelectOptionCallback = (selectedOption: string) => { - if (selectedOption === PERMIT_ACTION_OPTION_TYPES.VIEW_RECEIPT) { - viewReceiptPdf(companyId, permitId, () => onDocumentUnavailable()); + const onSelectOption = (selectedOption: string) => { + if (selectedOption === PERMIT_ACTION_TYPES.RESEND) { + setOpenResendDialog(() => true); + } else if (selectedOption === PERMIT_ACTION_TYPES.VIEW_RECEIPT) { + viewReceiptPdf(companyId, permitId, () => + navigate(routes.ERROR_ROUTES.DOCUMENT_UNAVAILABLE), + ); + } else if (selectedOption === PERMIT_ACTION_TYPES.VOID_REVOKE) { + navigate(`${routes.PERMITS_ROUTES.VOID(companyId, permitId)}`); + } else if (selectedOption === PERMIT_ACTION_TYPES.AMEND) { + // Sets the companyId and permitId of the permit to be amended, + // which will in turn look for any existing associated amendment applications, + // which is used to show info in the modal (or not show the modal at all) + choosePermitToAmend(companyId, permitId); + } + }; + + const handleResend = async ( + permitId: string, + email: string, + notificationTypes: EmailNotificationType[], + ) => { + const response = await resendPermitMutation.mutateAsync({ + permitId, + email, + notificationTypes, + }); + + setOpenResendDialog(false); + if (response.status === 201) { + setSnackBar({ + showSnackbar: true, + setShowSnackbar: () => true, + message: "Successfully sent", + alertType: "success", + }); + } else { + navigate(routes.ERROR_ROUTES.UNEXPECTED, { + state: { correlationId: response.headers["x-correlation-id"] }, + }); } }; + const canResendPermit = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "GLOBAL_SEARCH", + permissionMatrixFunctionKey: "RESEND_PERMIT", + }, + }); + + const canViewPermitReceipt = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "MANAGE_PERMITS", + permissionMatrixFunctionKey: "VIEW_PERMIT_RECEIPT", + }, + }); + + const canAmendPermit = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "GLOBAL_SEARCH", + permissionMatrixFunctionKey: "AMEND_PERMIT", + }, + }); + + const canVoidPermit = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "GLOBAL_SEARCH", + permissionMatrixFunctionKey: "VOID_PERMIT", + }, + }); + + interface PermitAction { + action: PermitActionType; + isAuthorized: (isExpired: boolean) => boolean; + } + + const PERMIT_ACTIONS: PermitAction[] = [ + { + action: PERMIT_ACTION_TYPES.RESEND, + isAuthorized: () => canResendPermit, + }, + { + action: PERMIT_ACTION_TYPES.VIEW_RECEIPT, + isAuthorized: () => canViewPermitReceipt, + }, + { + action: PERMIT_ACTION_TYPES.AMEND, + isAuthorized: (isExpired: boolean) => !isExpired && canAmendPermit, + }, + { + action: PERMIT_ACTION_TYPES.VOID_REVOKE, + isAuthorized: (isExpired: boolean) => !isExpired && canVoidPermit, + }, + ]; + + /** + * Returns options for the row actions. + * @param isExpired Has the permit expired? + * @returns Action options that can be performed for the permit. + */ + const getOptions = (isExpired: boolean) => { + return PERMIT_ACTIONS.filter((action) => + action.isAuthorized(isExpired), + ).map(({ action }) => ({ + label: permitActionLabel(action), + value: action, + })); + }; + return ( - + <> + + + setOpenResendDialog(false)} + onResend={handleResend} + companyId={companyId} + permitId={permitId} + permitNumber={permitNumber} + /> + + + ); }; diff --git a/frontend/src/features/permits/pages/Amend/AmendPermit.tsx b/frontend/src/features/permits/pages/Amend/AmendPermit.tsx index 1164349d19..b2b0de2f89 100644 --- a/frontend/src/features/permits/pages/Amend/AmendPermit.tsx +++ b/frontend/src/features/permits/pages/Amend/AmendPermit.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useContext, useMemo } from "react"; +import { useState, useEffect, useMemo } from "react"; import { Navigate, useLocation, @@ -13,8 +13,6 @@ import { Banner } from "../../../../common/components/dashboard/components/banne import { useMultiStepForm } from "../../hooks/useMultiStepForm"; import { AmendPermitContext } from "./context/AmendPermitContext"; import { Loading } from "../../../../common/pages/Loading"; -import OnRouteBCContext from "../../../../common/authentication/OnRouteBCContext"; -import { USER_ROLE } from "../../../../common/authentication/types"; import { AmendPermitReview } from "./components/AmendPermitReview"; import { AmendPermitFinish } from "./components/AmendPermitFinish"; import { AmendPermitForm } from "./components/AmendPermitForm"; @@ -42,6 +40,7 @@ import { SEARCH_ENTITIES, } from "../../../idir/search/types/types"; import { PERMIT_TABS } from "../../types/PermitTabs"; +import { usePermissionMatrix } from "../../../../common/authentication/PermissionMatrix"; export const AMEND_PERMIT_STEPS = { Amend: "Amend", @@ -76,12 +75,6 @@ const isAmendable = (permit: Permit) => { ); }; -const isAmendableByUser = (role?: string) => { - return ( - role === USER_ROLE.PPC_CLERK || role === USER_ROLE.SYSTEM_ADMINISTRATOR - ); -}; - const searchRoute = `${IDIR_ROUTES.SEARCH_RESULTS}?searchEntity=${SEARCH_ENTITIES.PERMIT}` + `&searchByFilter=${SEARCH_BY_FILTERS.PERMIT_NUMBER}&searchString=`; @@ -98,7 +91,6 @@ export const AmendPermit = () => { ); const permitId = getDefaultRequiredVal("", permitIdParam); - const { idirUserDetails } = useContext(OnRouteBCContext); const navigate = useNavigate(); // Query for permit data whenever this page is rendered, for the permit id @@ -232,7 +224,14 @@ export const AmendPermit = () => { return ; } - if (!isAmendableByUser(idirUserDetails?.userRole)) { + const canAmendPermit = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "GLOBAL_SEARCH", + permissionMatrixFunctionKey: "AMEND_PERMIT", + }, + }); + + if (!canAmendPermit) { return ; } diff --git a/frontend/src/features/permits/pages/Void/VoidPermit.tsx b/frontend/src/features/permits/pages/Void/VoidPermit.tsx index c62a8d4b92..9fec78ce3d 100644 --- a/frontend/src/features/permits/pages/Void/VoidPermit.tsx +++ b/frontend/src/features/permits/pages/Void/VoidPermit.tsx @@ -1,5 +1,5 @@ import { Navigate, useNavigate, useParams } from "react-router-dom"; -import { useState, useEffect, useContext, useMemo } from "react"; +import { useState, useEffect, useMemo } from "react"; import { VoidPermitForm } from "./components/VoidPermitForm"; import { usePermitDetailsQuery } from "../../hooks/hooks"; @@ -10,17 +10,19 @@ import { VoidPermitContext } from "./context/VoidPermitContext"; import { ERROR_ROUTES, IDIR_ROUTES } from "../../../../routes/constants"; import { VoidPermitFormData } from "./types/VoidPermit"; import { FinishVoid } from "./FinishVoid"; -import OnRouteBCContext from "../../../../common/authentication/OnRouteBCContext"; -import { USER_ROLE } from "../../../../common/authentication/types"; import { isPermitInactive } from "../../types/PermitStatus"; import { Permit } from "../../types/permit"; -import { applyWhenNotNullable, getDefaultRequiredVal } from "../../../../common/helpers/util"; +import { + applyWhenNotNullable, + getDefaultRequiredVal, +} from "../../../../common/helpers/util"; import { Breadcrumb } from "../../../../common/components/breadcrumb/Breadcrumb"; import { hasPermitExpired } from "../../helpers/permitState"; import { SEARCH_BY_FILTERS, SEARCH_ENTITIES, } from "../../../idir/search/types/types"; +import { usePermissionMatrix } from "../../../../common/authentication/PermissionMatrix"; const searchRoute = `${IDIR_ROUTES.SEARCH_RESULTS}?searchEntity=${SEARCH_ENTITIES.PERMIT}` + @@ -35,20 +37,18 @@ const isVoidable = (permit: Permit) => { export const VoidPermit = () => { const navigate = useNavigate(); - const { - permitId: permitIdParam, - companyId: companyIdParam, - } = useParams(); + const { permitId: permitIdParam, companyId: companyIdParam } = useParams(); - const companyId: number = applyWhenNotNullable(id => Number(id), companyIdParam, 0); + const companyId: number = applyWhenNotNullable( + (id) => Number(id), + companyIdParam, + 0, + ); const permitId = getDefaultRequiredVal("", permitIdParam); const [currentLink, setCurrentLink] = useState(0); const getBannerText = () => currentLink === 0 ? "Void Permit" : "Finish Voiding"; - // Must be SYSADMIN to access this page - const { idirUserDetails } = useContext(OnRouteBCContext); - const permitQuery = usePermitDetailsQuery(companyId, permitId); const permit = permitQuery.data; @@ -116,8 +116,15 @@ export const VoidPermit = () => { [voidPermitData], ); + const canVoidPermit = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "GLOBAL_SEARCH", + permissionMatrixFunctionKey: "VOID_PERMIT", + }, + }); + // If user is not SYSADMIN, show unauthorized page - if (idirUserDetails?.userRole !== USER_ROLE.SYSTEM_ADMINISTRATOR) { + if (!canVoidPermit) { return ; } diff --git a/frontend/src/features/permits/pages/Void/components/VoidPermitForm.tsx b/frontend/src/features/permits/pages/Void/components/VoidPermitForm.tsx index 49bc960013..1b922bc70e 100644 --- a/frontend/src/features/permits/pages/Void/components/VoidPermitForm.tsx +++ b/frontend/src/features/permits/pages/Void/components/VoidPermitForm.tsx @@ -31,6 +31,7 @@ import { } from "../../../../../common/helpers/util"; import { CustomFormComponent } from "../../../../../common/components/form/CustomFormComponents"; +import { usePermissionMatrix } from "../../../../../common/authentication/PermissionMatrix"; const FEATURE = ORBC_FORM_FEATURES.VOID_PERMIT; @@ -94,6 +95,13 @@ export const VoidPermitForm = ({ next(); }; + const canRevokePermit = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "GLOBAL_SEARCH", + permissionMatrixFunctionKey: "REVOKE_PERMIT", + }, + }); + const handleOpenRevokeDialog = () => { setOpenRevokeDialog(true); }; @@ -196,30 +204,32 @@ export const VoidPermitForm = ({ /> -
-
-
Revoke this permit?
+ {canRevokePermit && ( +
+
+
Revoke this permit?
-
-
- Revoking a permit is a severe action that{" "} - - cannot be reversed. - {" "} - There are{" "} - no refunds for - revoked permits. -
+
+
+ Revoking a permit is a severe action that{" "} + + cannot be reversed. + {" "} + There are{" "} + no refunds{" "} + for revoked permits. +
- + +
-
+ )}
From 377c54defc8edeac9dc303afe1d9649e08c896b8 Mon Sep 17 00:00:00 2001 From: GlenAOT <160973940+GlenAOT@users.noreply.github.com> Date: Wed, 28 May 2025 14:48:19 -0700 Subject: [PATCH 3/5] PR review changes --- .../components/IDIRPermitSearchResults.tsx | 35 +++++++ .../components/permit-list/BasePermitList.tsx | 47 +++++++++- .../components/permit-list/Columns.tsx | 11 +-- .../permit-list/PermitRowOptions.tsx | 94 ++++--------------- .../permits/helpers/getPermitActionLabel.ts | 19 ++++ .../permits/helpers/getPermitActionOptions.ts | 22 +++++ .../permits/types/PermitActionType.ts | 9 ++ 7 files changed, 152 insertions(+), 85 deletions(-) create mode 100644 frontend/src/features/permits/helpers/getPermitActionLabel.ts create mode 100644 frontend/src/features/permits/helpers/getPermitActionOptions.ts create mode 100644 frontend/src/features/permits/types/PermitActionType.ts diff --git a/frontend/src/features/idir/search/components/IDIRPermitSearchResults.tsx b/frontend/src/features/idir/search/components/IDIRPermitSearchResults.tsx index 492984df1c..948625b38b 100644 --- a/frontend/src/features/idir/search/components/IDIRPermitSearchResults.tsx +++ b/frontend/src/features/idir/search/components/IDIRPermitSearchResults.tsx @@ -29,6 +29,7 @@ import { VEHICLES_URL } from "../../../../common/apiManager/endpoints/endpoints" import { httpGETRequest } from "../../../../common/apiManager/httpRequestHandler"; import { useSetCompanyHandler } from "../helpers/useSetCompanyHandler"; import { PermitRowOptions } from "../../../permits/components/permit-list/PermitRowOptions"; +import { usePermissionMatrix } from "../../../../common/authentication/PermissionMatrix"; /** * Function to decide whether to show row actions icon or not. @@ -148,6 +149,34 @@ export const IDIRPermitSearchResults = memo( return initialData; }; + const canResendPermit = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "GLOBAL_SEARCH", + permissionMatrixFunctionKey: "RESEND_PERMIT", + }, + }); + + const canViewPermitReceipt = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "MANAGE_PERMITS", + permissionMatrixFunctionKey: "VIEW_PERMIT_RECEIPT", + }, + }); + + const canAmendPermit = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "GLOBAL_SEARCH", + permissionMatrixFunctionKey: "AMEND_PERMIT", + }, + }); + + const canVoidPermit = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "GLOBAL_SEARCH", + permissionMatrixFunctionKey: "VOID_PERMIT", + }, + }); + const table = useMaterialReactTable({ ...defaultTableOptions, data: getFilteredData(data?.items ?? []), @@ -211,6 +240,12 @@ export const IDIRPermitSearchResults = memo( permitId={row.original.permitId} companyId={row.original.companyId} permitActionOrigin={PERMIT_ACTION_ORIGINS.GLOBAL_SEARCH} + permissions={{ + canAmendPermit, + canResendPermit, + canViewPermitReceipt, + canVoidPermit, + }} />
); diff --git a/frontend/src/features/permits/components/permit-list/BasePermitList.tsx b/frontend/src/features/permits/components/permit-list/BasePermitList.tsx index 23cfbf9813..28e47834f9 100644 --- a/frontend/src/features/permits/components/permit-list/BasePermitList.tsx +++ b/frontend/src/features/permits/components/permit-list/BasePermitList.tsx @@ -29,6 +29,7 @@ import OnRouteBCContext from "../../../../common/authentication/OnRouteBCContext import { applyWhenNotNullable } from "../../../../common/helpers/util"; import { PERMIT_ACTION_ORIGINS } from "../../../idir/search/types/types"; import { PermitRowOptions } from "./PermitRowOptions"; +import { usePermissionMatrix } from "../../../../common/authentication/PermissionMatrix"; /** * A permit list component with common functionalities that can be shared by @@ -96,10 +97,46 @@ export const BasePermitList = ({ const { data, isError, isPending, isRefetching } = permitsQuery; + const canViewIndividualActivePermitPDF = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "MANAGE_PERMITS", + permissionMatrixFunctionKey: "VIEW_INDIVIDUAL_ACTIVE_PERMIT_PDF", + }, + }); + + const canResendPermit = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "GLOBAL_SEARCH", + permissionMatrixFunctionKey: "RESEND_PERMIT", + }, + }); + + const canViewPermitReceipt = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "MANAGE_PERMITS", + permissionMatrixFunctionKey: "VIEW_PERMIT_RECEIPT", + }, + }); + + const canAmendPermit = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "GLOBAL_SEARCH", + permissionMatrixFunctionKey: "AMEND_PERMIT", + }, + }); + + const canVoidPermit = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "GLOBAL_SEARCH", + permissionMatrixFunctionKey: "VOID_PERMIT", + }, + }); + const table = useMaterialReactTable({ ...defaultTableOptions, - columns: PermitsColumnDefinition(() => - navigate(ERROR_ROUTES.DOCUMENT_UNAVAILABLE), + columns: PermitsColumnDefinition( + () => navigate(ERROR_ROUTES.DOCUMENT_UNAVAILABLE), + canViewIndividualActivePermitPDF, ), data: data?.items ?? [], enableRowSelection: false, @@ -159,6 +196,12 @@ export const BasePermitList = ({ permitId={row.original.permitId} companyId={row.original.companyId} permitActionOrigin={PERMIT_ACTION_ORIGINS.ACTIVE_PERMITS} + permissions={{ + canAmendPermit, + canResendPermit, + canViewPermitReceipt, + canVoidPermit, + }} /> ); diff --git a/frontend/src/features/permits/components/permit-list/Columns.tsx b/frontend/src/features/permits/components/permit-list/Columns.tsx index 182b52283d..0449e6f9cd 100644 --- a/frontend/src/features/permits/components/permit-list/Columns.tsx +++ b/frontend/src/features/permits/components/permit-list/Columns.tsx @@ -8,21 +8,14 @@ import { formatCellValuetoDatetime } from "../../../../common/helpers/tableHelpe import { CustomActionLink } from "../../../../common/components/links/CustomActionLink"; import { getDefaultRequiredVal } from "../../../../common/helpers/util"; import { getPermitTypeName } from "../../types/PermitType"; -import { usePermissionMatrix } from "../../../../common/authentication/PermissionMatrix"; /** * The column definition for Permits. */ export const PermitsColumnDefinition = ( onDocumentUnavailable: () => void, + enableLink: boolean, ): MRT_ColumnDef[] => { - const canViewIndividualActivePermitPDF = usePermissionMatrix({ - permissionMatrixKeys: { - permissionMatrixFeatureKey: "MANAGE_PERMITS", - permissionMatrixFunctionKey: "VIEW_INDIVIDUAL_ACTIVE_PERMIT_PDF", - }, - }); - return [ { accessorKey: "permitNumber", @@ -34,7 +27,7 @@ export const PermitsColumnDefinition = ( Cell: (props: { row: any; cell: any }) => { return ( <> - {canViewIndividualActivePermitPDF ? ( + {enableLink ? ( viewPermitPdf( diff --git a/frontend/src/features/permits/components/permit-list/PermitRowOptions.tsx b/frontend/src/features/permits/components/permit-list/PermitRowOptions.tsx index 7747e76d0b..aee076d597 100644 --- a/frontend/src/features/permits/components/permit-list/PermitRowOptions.tsx +++ b/frontend/src/features/permits/components/permit-list/PermitRowOptions.tsx @@ -12,32 +12,8 @@ import { useAttemptAmend } from "../../hooks/useAttemptAmend"; import { UnfinishedAmendModal } from "../../pages/Amend/components/modal/UnfinishedAmendModal"; import { getDefaultRequiredVal } from "../../../../common/helpers/util"; import { PermitActionOrigin } from "../../../idir/search/types/types"; -import { usePermissionMatrix } from "../../../../common/authentication/PermissionMatrix"; - -const PERMIT_ACTION_TYPES = { - RESEND: "resend", - VIEW_RECEIPT: "viewReceipt", - AMEND: "amend", - VOID_REVOKE: "voidRevoke", -} as const; - -type PermitActionType = - (typeof PERMIT_ACTION_TYPES)[keyof typeof PERMIT_ACTION_TYPES]; - -const permitActionLabel = (actionType: PermitActionType) => { - switch (actionType) { - case PERMIT_ACTION_TYPES.RESEND: - return "Resend"; - case PERMIT_ACTION_TYPES.VIEW_RECEIPT: - return "View Receipt"; - case PERMIT_ACTION_TYPES.AMEND: - return "Amend"; - case PERMIT_ACTION_TYPES.VOID_REVOKE: - return "Void/Revoke"; - default: - return ""; - } -}; +import { PERMIT_ACTION_TYPES } from "../../types/PermitActionType"; +import { getPermitActionOptions } from "../../helpers/getPermitActionOptions"; /** * Component for row actions on IDIR Search Permit. @@ -48,6 +24,7 @@ export const PermitRowOptions = ({ permitNumber, companyId, permitActionOrigin, + permissions, }: { /** * The permit id. @@ -69,6 +46,15 @@ export const PermitRowOptions = ({ * The application location from where the permit action (amend / void / revoke) originated */ permitActionOrigin: PermitActionOrigin; + /** + * An object containing the relevant permission matrix checks for each action + */ + permissions: { + canResendPermit: boolean; + canViewPermitReceipt: boolean; + canAmendPermit: boolean; + canVoidPermit: boolean; + }; }) => { const [openResendDialog, setOpenResendDialog] = useState(false); const navigate = useNavigate(); @@ -136,40 +122,14 @@ export const PermitRowOptions = ({ } }; - const canResendPermit = usePermissionMatrix({ - permissionMatrixKeys: { - permissionMatrixFeatureKey: "GLOBAL_SEARCH", - permissionMatrixFunctionKey: "RESEND_PERMIT", - }, - }); - - const canViewPermitReceipt = usePermissionMatrix({ - permissionMatrixKeys: { - permissionMatrixFeatureKey: "MANAGE_PERMITS", - permissionMatrixFunctionKey: "VIEW_PERMIT_RECEIPT", - }, - }); - - const canAmendPermit = usePermissionMatrix({ - permissionMatrixKeys: { - permissionMatrixFeatureKey: "GLOBAL_SEARCH", - permissionMatrixFunctionKey: "AMEND_PERMIT", - }, - }); - - const canVoidPermit = usePermissionMatrix({ - permissionMatrixKeys: { - permissionMatrixFeatureKey: "GLOBAL_SEARCH", - permissionMatrixFunctionKey: "VOID_PERMIT", - }, - }); - - interface PermitAction { - action: PermitActionType; - isAuthorized: (isExpired: boolean) => boolean; - } + const { + canResendPermit, + canViewPermitReceipt, + canAmendPermit, + canVoidPermit, + } = permissions; - const PERMIT_ACTIONS: PermitAction[] = [ + const permitActions = [ { action: PERMIT_ACTION_TYPES.RESEND, isAuthorized: () => canResendPermit, @@ -188,25 +148,11 @@ export const PermitRowOptions = ({ }, ]; - /** - * Returns options for the row actions. - * @param isExpired Has the permit expired? - * @returns Action options that can be performed for the permit. - */ - const getOptions = (isExpired: boolean) => { - return PERMIT_ACTIONS.filter((action) => - action.isAuthorized(isExpired), - ).map(({ action }) => ({ - label: permitActionLabel(action), - value: action, - })); - }; - return ( <> diff --git a/frontend/src/features/permits/helpers/getPermitActionLabel.ts b/frontend/src/features/permits/helpers/getPermitActionLabel.ts new file mode 100644 index 0000000000..b0e2b1cb78 --- /dev/null +++ b/frontend/src/features/permits/helpers/getPermitActionLabel.ts @@ -0,0 +1,19 @@ +import { + PERMIT_ACTION_TYPES, + PermitActionType, +} from "../types/PermitActionType"; + +export const getPermitActionLabel = (actionType: PermitActionType) => { + switch (actionType) { + case PERMIT_ACTION_TYPES.RESEND: + return "Resend"; + case PERMIT_ACTION_TYPES.VIEW_RECEIPT: + return "View Receipt"; + case PERMIT_ACTION_TYPES.AMEND: + return "Amend"; + case PERMIT_ACTION_TYPES.VOID_REVOKE: + return "Void/Revoke"; + default: + return ""; + } +}; diff --git a/frontend/src/features/permits/helpers/getPermitActionOptions.ts b/frontend/src/features/permits/helpers/getPermitActionOptions.ts new file mode 100644 index 0000000000..9e5285a702 --- /dev/null +++ b/frontend/src/features/permits/helpers/getPermitActionOptions.ts @@ -0,0 +1,22 @@ +import { PermitActionType } from "../types/PermitActionType"; +import { getPermitActionLabel } from "./getPermitActionLabel"; + +/** + * Returns options for the row actions. + * @param isExpired Has the permit expired? + * @returns Action options that can be performed for the permit. + */ +export const getPermitActionOptions = ( + permitActions: { + action: PermitActionType; + isAuthorized: (isAuthorized: boolean) => boolean; + }[], + isExpired: boolean, +) => { + return permitActions + .filter((action) => action.isAuthorized(isExpired)) + .map(({ action }) => ({ + label: getPermitActionLabel(action), + value: action, + })); +}; diff --git a/frontend/src/features/permits/types/PermitActionType.ts b/frontend/src/features/permits/types/PermitActionType.ts new file mode 100644 index 0000000000..f0a8934beb --- /dev/null +++ b/frontend/src/features/permits/types/PermitActionType.ts @@ -0,0 +1,9 @@ +export const PERMIT_ACTION_TYPES = { + RESEND: "resend", + VIEW_RECEIPT: "viewReceipt", + AMEND: "amend", + VOID_REVOKE: "voidRevoke", +} as const; + +export type PermitActionType = + (typeof PERMIT_ACTION_TYPES)[keyof typeof PERMIT_ACTION_TYPES]; From a3704a1377e3823f422139d3e55dd079015c63fe Mon Sep 17 00:00:00 2001 From: GlenAOT <160973940+GlenAOT@users.noreply.github.com> Date: Thu, 29 May 2025 11:43:14 -0700 Subject: [PATCH 4/5] add permissions matrix check for VIEW_INDIVIDUAL_EXPIRED_PERMIT_PDF and VIEW_EXPIRED_PERMIT_RECEIPT --- .../components/permit-list/BasePermitList.tsx | 17 +++++++++++++++++ .../permits/components/permit-list/Columns.tsx | 7 ++++++- .../components/permit-list/PermitRowOptions.tsx | 6 +++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/frontend/src/features/permits/components/permit-list/BasePermitList.tsx b/frontend/src/features/permits/components/permit-list/BasePermitList.tsx index 28e47834f9..6a703e7801 100644 --- a/frontend/src/features/permits/components/permit-list/BasePermitList.tsx +++ b/frontend/src/features/permits/components/permit-list/BasePermitList.tsx @@ -104,6 +104,13 @@ export const BasePermitList = ({ }, }); + const canViewIndividualExpiredPermitPDF = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "MANAGE_PERMITS", + permissionMatrixFunctionKey: "VIEW_INDIVIDUAL_EXPIRED_PERMIT_PDF", + }, + }); + const canResendPermit = usePermissionMatrix({ permissionMatrixKeys: { permissionMatrixFeatureKey: "GLOBAL_SEARCH", @@ -118,6 +125,13 @@ export const BasePermitList = ({ }, }); + const canViewExpiredPermitReceipt = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "MANAGE_PERMITS", + permissionMatrixFunctionKey: "VIEW_EXPIRED_PERMIT_RECEIPT", + }, + }); + const canAmendPermit = usePermissionMatrix({ permissionMatrixKeys: { permissionMatrixFeatureKey: "GLOBAL_SEARCH", @@ -136,7 +150,9 @@ export const BasePermitList = ({ ...defaultTableOptions, columns: PermitsColumnDefinition( () => navigate(ERROR_ROUTES.DOCUMENT_UNAVAILABLE), + isExpired, canViewIndividualActivePermitPDF, + canViewIndividualExpiredPermitPDF, ), data: data?.items ?? [], enableRowSelection: false, @@ -200,6 +216,7 @@ export const BasePermitList = ({ canAmendPermit, canResendPermit, canViewPermitReceipt, + canViewExpiredPermitReceipt, canVoidPermit, }} /> diff --git a/frontend/src/features/permits/components/permit-list/Columns.tsx b/frontend/src/features/permits/components/permit-list/Columns.tsx index 0449e6f9cd..0e5539bbb2 100644 --- a/frontend/src/features/permits/components/permit-list/Columns.tsx +++ b/frontend/src/features/permits/components/permit-list/Columns.tsx @@ -14,8 +14,13 @@ import { getPermitTypeName } from "../../types/PermitType"; */ export const PermitsColumnDefinition = ( onDocumentUnavailable: () => void, - enableLink: boolean, + isExpired: boolean, + canViewIndividualActivePermitPDF: boolean, + canViewIndividualExpiredPermitPDF: boolean, ): MRT_ColumnDef[] => { + const enableLink = + (!isExpired && canViewIndividualActivePermitPDF) || + (isExpired && canViewIndividualExpiredPermitPDF); return [ { accessorKey: "permitNumber", diff --git a/frontend/src/features/permits/components/permit-list/PermitRowOptions.tsx b/frontend/src/features/permits/components/permit-list/PermitRowOptions.tsx index aee076d597..c40a18844b 100644 --- a/frontend/src/features/permits/components/permit-list/PermitRowOptions.tsx +++ b/frontend/src/features/permits/components/permit-list/PermitRowOptions.tsx @@ -52,6 +52,7 @@ export const PermitRowOptions = ({ permissions: { canResendPermit: boolean; canViewPermitReceipt: boolean; + canViewExpiredPermitReceipt: boolean; canAmendPermit: boolean; canVoidPermit: boolean; }; @@ -125,6 +126,7 @@ export const PermitRowOptions = ({ const { canResendPermit, canViewPermitReceipt, + canViewExpiredPermitReceipt, canAmendPermit, canVoidPermit, } = permissions; @@ -136,7 +138,9 @@ export const PermitRowOptions = ({ }, { action: PERMIT_ACTION_TYPES.VIEW_RECEIPT, - isAuthorized: () => canViewPermitReceipt, + isAuthorized: (isExpired: boolean) => + (!isExpired && canViewPermitReceipt) || + (isExpired && canViewExpiredPermitReceipt), }, { action: PERMIT_ACTION_TYPES.AMEND, From ae3754290039c05816b77c3748aea07f8771bc66 Mon Sep 17 00:00:00 2001 From: GlenAOT <160973940+GlenAOT@users.noreply.github.com> Date: Thu, 29 May 2025 14:02:49 -0700 Subject: [PATCH 5/5] update IDIRPermitSearchResults to inlude canViewExpiredPermitReceipt check --- .../idir/search/components/IDIRPermitSearchResults.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frontend/src/features/idir/search/components/IDIRPermitSearchResults.tsx b/frontend/src/features/idir/search/components/IDIRPermitSearchResults.tsx index 948625b38b..2c8a58fa76 100644 --- a/frontend/src/features/idir/search/components/IDIRPermitSearchResults.tsx +++ b/frontend/src/features/idir/search/components/IDIRPermitSearchResults.tsx @@ -163,6 +163,13 @@ export const IDIRPermitSearchResults = memo( }, }); + const canViewExpiredPermitReceipt = usePermissionMatrix({ + permissionMatrixKeys: { + permissionMatrixFeatureKey: "MANAGE_PERMITS", + permissionMatrixFunctionKey: "VIEW_EXPIRED_PERMIT_RECEIPT", + }, + }); + const canAmendPermit = usePermissionMatrix({ permissionMatrixKeys: { permissionMatrixFeatureKey: "GLOBAL_SEARCH", @@ -244,6 +251,7 @@ export const IDIRPermitSearchResults = memo( canAmendPermit, canResendPermit, canViewPermitReceipt, + canViewExpiredPermitReceipt, canVoidPermit, }} />