improvements: minor ui adjustments/additions and pagination for access request table

This commit is contained in:
Scott Wilson
2025-06-19 09:39:42 -07:00
parent 8fb6063686
commit 12fd063cd5
4 changed files with 77 additions and 26 deletions
+11 -3
View File
@@ -1,12 +1,20 @@
import { IconDefinition } from "@fortawesome/free-brands-svg-icons";
import { faArrowRightToBracket, faEdit } from "@fortawesome/free-solid-svg-icons";
import { PolicyType } from "@app/hooks/api/policies/enums"; import { PolicyType } from "@app/hooks/api/policies/enums";
export const policyDetails: Record<PolicyType, { name: string; className: string }> = { export const policyDetails: Record<
PolicyType,
{ name: string; className: string; icon: IconDefinition }
> = {
[PolicyType.AccessPolicy]: { [PolicyType.AccessPolicy]: {
className: "bg-green/20 text-green", className: "bg-green/20 text-green",
name: "Access Policy" name: "Access Policy",
icon: faArrowRightToBracket
}, },
[PolicyType.ChangePolicy]: { [PolicyType.ChangePolicy]: {
className: "bg-yellow/20 text-yellow", className: "bg-yellow/20 text-yellow",
name: "Change Policy" name: "Change Policy",
icon: faEdit
} }
}; };
@@ -7,6 +7,7 @@ import {
faCheck, faCheck,
faCheckCircle, faCheckCircle,
faChevronDown, faChevronDown,
faClipboardCheck,
faLock, faLock,
faMagnifyingGlass, faMagnifyingGlass,
faPlus, faPlus,
@@ -28,6 +29,7 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
EmptyState, EmptyState,
Input, Input,
Pagination,
Tooltip Tooltip
} from "@app/components/v2"; } from "@app/components/v2";
import { Badge } from "@app/components/v2/Badge"; import { Badge } from "@app/components/v2/Badge";
@@ -39,7 +41,12 @@ import {
useUser, useUser,
useWorkspace useWorkspace
} from "@app/context"; } from "@app/context";
import { usePopUp } from "@app/hooks"; import {
getUserTablePreference,
PreferenceKey,
setUserTablePreference
} from "@app/helpers/userTablePreferences";
import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
import { useGetWorkspaceUsers } from "@app/hooks/api"; import { useGetWorkspaceUsers } from "@app/hooks/api";
import { import {
accessApprovalKeys, accessApprovalKeys,
@@ -59,7 +66,7 @@ const generateRequestText = (request: TAccessApprovalRequest) => {
const { isTemporary } = request; const { isTemporary } = request;
return ( return (
<div className="flex items-center justify-between"> <div className="flex items-center justify-between text-sm">
<div> <div>
Requested {isTemporary ? "temporary" : "permanent"} access to{" "} Requested {isTemporary ? "temporary" : "permanent"} access to{" "}
<code className="mx-1 rounded bg-mineshaft-600 px-1.5 py-0.5 font-mono text-[13px] text-mineshaft-200"> <code className="mx-1 rounded bg-mineshaft-600 px-1.5 py-0.5 font-mono text-[13px] text-mineshaft-200">
@@ -130,7 +137,14 @@ export const AccessApprovalRequest = ({
envSlug: envFilter envSlug: envFilter
}); });
const [searchFilter, setSearchFilter] = useState(""); const { search, setSearch, setPage, page, perPage, setPerPage, offset } = usePagination("", {
initPerPage: getUserTablePreference("accessRequestsTable", PreferenceKey.PerPage, 20)
});
const handlePerPageChange = (newPerPage: number) => {
setPerPage(newPerPage);
setUserTablePreference("accessRequestsTable", PreferenceKey.PerPage, newPerPage);
};
const filteredRequests = useMemo(() => { const filteredRequests = useMemo(() => {
let accessRequests: typeof requests; let accessRequests: typeof requests;
@@ -150,19 +164,27 @@ export const AccessApprovalRequest = ({
request.reviewers.some((reviewer) => reviewer.status === ApprovalStatus.REJECTED) request.reviewers.some((reviewer) => reviewer.status === ApprovalStatus.REJECTED)
); );
return accessRequests?.filter((request) => { return (
const { environmentName, requestedByUser } = request; accessRequests?.filter((request) => {
const { environmentName, requestedByUser } = request;
const searchValue = searchFilter.trim().toLowerCase(); const searchValue = search.trim().toLowerCase();
return ( return (
environmentName?.toLowerCase().includes(searchValue) || environmentName?.toLowerCase().includes(searchValue) ||
`${requestedByUser?.email ?? ""} ${requestedByUser?.firstName ?? ""} ${requestedByUser?.lastName ?? ""}` `${requestedByUser?.email ?? ""} ${requestedByUser?.firstName ?? ""} ${requestedByUser?.lastName ?? ""}`
.toLowerCase() .toLowerCase()
.includes(searchValue) .includes(searchValue)
); );
}); }) ?? []
}, [requests, statusFilter, requestedByFilter, envFilter, searchFilter]); );
}, [requests, statusFilter, requestedByFilter, envFilter, search]);
useResetPageHelper({
totalCount: filteredRequests.length,
offset,
setPage
});
const generateRequestDetails = useCallback( const generateRequestDetails = useCallback(
(request: TAccessApprovalRequest) => { (request: TAccessApprovalRequest) => {
@@ -299,8 +321,8 @@ export const AccessApprovalRequest = ({
</Tooltip> </Tooltip>
</div> </div>
<Input <Input
value={searchFilter} value={search}
onChange={(e) => setSearchFilter(e.target.value)} onChange={(e) => setSearch(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />} leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search approval requests by requesting user or environment..." placeholder="Search approval requests by requesting user or environment..."
className="flex-1" className="flex-1"
@@ -423,7 +445,7 @@ export const AccessApprovalRequest = ({
<EmptyState title="No Requests Match Search" icon={faSearch} /> <EmptyState title="No Requests Match Search" icon={faSearch} />
)} )}
{!!filteredRequests?.length && {!!filteredRequests?.length &&
filteredRequests?.map((request) => { filteredRequests?.slice(offset, perPage * page).map((request) => {
const details = generateRequestDetails(request); const details = generateRequestDetails(request);
return ( return (
@@ -463,21 +485,35 @@ export const AccessApprovalRequest = ({
</div> </div>
</div> </div>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-3">
{request.requestedByUserId === user.id && ( {request.requestedByUserId === user.id && (
<div className="flex items-center gap-1.5 whitespace-nowrap text-xs text-bunker-300"> <div className="flex items-center gap-1.5 whitespace-nowrap text-xs text-bunker-300">
<FontAwesomeIcon icon={faUser} size="sm" /> <FontAwesomeIcon icon={faUser} size="sm" />
<span>Requested By You</span> <span>Requested By You</span>
</div> </div>
)} )}
<Badge className="whitespace-nowrap" variant={details.displayData.type}> <Badge
{details.displayData.label} className="flex items-center gap-1.5 whitespace-nowrap"
variant={details.displayData.type}
>
<FontAwesomeIcon icon={faClipboardCheck} />
<span>{details.displayData.label}</span>
</Badge> </Badge>
</div> </div>
</div> </div>
</div> </div>
); );
})} })}
{Boolean(filteredRequests.length) && (
<Pagination
className="border-none"
count={filteredRequests.length}
page={page}
perPage={perPage}
onChangePage={setPage}
onChangePerPage={handlePerPageChange}
/>
)}
</div> </div>
</div> </div>
{!!policies && ( {!!policies && (
@@ -1,6 +1,7 @@
import { useMemo } from "react"; import { useMemo } from "react";
import { faEdit, faEllipsisV, faTrash } from "@fortawesome/free-solid-svg-icons"; import { faEdit, faEllipsisV, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { import {
@@ -107,8 +108,14 @@ export const ApprovalPolicyRow = ({
<Td>{policy.environment.name}</Td> <Td>{policy.environment.name}</Td>
<Td>{policy.secretPath || "*"}</Td> <Td>{policy.secretPath || "*"}</Td>
<Td> <Td>
<Badge className={policyDetails[policy.policyType].className}> <Badge
{policyDetails[policy.policyType].name} className={twMerge(
policyDetails[policy.policyType].className,
"flex w-min items-center gap-1.5 whitespace-nowrap"
)}
>
<FontAwesomeIcon icon={policyDetails[policy.policyType].icon} />
<span>{policyDetails[policy.policyType].name}</span>
</Badge> </Badge>
</Td> </Td>
<Td> <Td>
@@ -316,7 +316,7 @@ export const SecretApprovalRequest = () => {
if (evt.key === "Enter") setSelectedApprovalId(secretApproval.id); if (evt.key === "Enter") setSelectedApprovalId(secretApproval.id);
}} }}
> >
<div className="mb-1"> <div className="mb-1 text-sm">
<FontAwesomeIcon <FontAwesomeIcon
icon={faCodeBranch} icon={faCodeBranch}
size="sm" size="sm"