Fixes on cert request UI

This commit is contained in:
Carlos Monastyrski
2025-12-18 21:47:18 -03:00
parent eb66a20b3a
commit 47f80b5241
5 changed files with 60 additions and 36 deletions

View File

@@ -4297,6 +4297,8 @@ interface ListCertificateRequestsEvent {
limit: number;
search?: string;
status?: string;
count: number;
certificateRequestIds: string[];
};
}

View File

@@ -448,7 +448,9 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
offset: req.query.offset,
limit: req.query.limit,
search: req.query.search,
status: req.query.status
status: req.query.status,
count: certificateRequests.length,
certificateRequestIds: certificateRequests.map((certReq) => certReq.id)
}
}
});

View File

@@ -9,6 +9,7 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
IconButton,
Td,
Tooltip,
Tr
@@ -77,13 +78,15 @@ export const CertificateRequestRow = ({ request, onViewCertificates }: Props) =>
<Td>
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
<Tooltip content="More options">
<FontAwesomeIcon size="lg" icon={faEllipsis} />
</Tooltip>
</div>
<IconButton
variant="plain"
ariaLabel="More options"
className="h-max bg-transparent p-0"
>
<FontAwesomeIcon size="lg" icon={faEllipsis} />
</IconButton>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuContent align="end" sideOffset={3}>
<DropdownMenuItem
onClick={() => request.certificateId && onViewCertificates?.(request.certificateId)}
disabled={!request.certificateId}

View File

@@ -63,6 +63,7 @@ export const CertificateRequestsSection = ({ onViewCertificateFromRequest }: Pro
const [appliedFilters, setAppliedFilters] = useState<CertificateRequestFilters>({});
const [currentPage, setCurrentPage] = useState(1);
const [perPage, setPerPage] = useState(PAGE_SIZE);
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["issueCertificate"] as const);
@@ -84,15 +85,15 @@ export const CertificateRequestsSection = ({ onViewCertificateFromRequest }: Pro
const queryParams: TListCertificateRequestsParams = useMemo(
() => ({
projectSlug: currentProject?.slug || "",
offset: (currentPage - 1) * PAGE_SIZE,
limit: PAGE_SIZE,
offset: (currentPage - 1) * perPage,
limit: perPage,
sortBy: "createdAt",
sortOrder: "desc",
...(debouncedSearch && { search: debouncedSearch }),
...(appliedFilters.status && { status: appliedFilters.status }),
...(profileIds && { profileIds })
}),
[currentProject?.slug, currentPage, debouncedSearch, appliedFilters.status, profileIds]
[currentProject?.slug, currentPage, perPage, debouncedSearch, appliedFilters.status, profileIds]
);
const {
@@ -130,14 +131,18 @@ export const CertificateRequestsSection = ({ onViewCertificateFromRequest }: Pro
};
const isTableFiltered = useMemo(
() => Boolean(debouncedSearch || appliedFilters.status || appliedProfileIds.length),
[debouncedSearch, appliedFilters.status, appliedProfileIds.length]
() => Boolean(appliedFilters.status || appliedProfileIds.length),
[appliedFilters.status, appliedProfileIds.length]
);
const hasPendingChanges = useMemo(() => {
if (pendingFilters.status !== appliedFilters.status) return true;
if (pendingProfileIds.length !== appliedProfileIds.length) return true;
return pendingProfileIds.some((id, index) => id !== appliedProfileIds[index]);
const pendingStatus = pendingFilters.status ?? undefined;
const appliedStatus = appliedFilters.status ?? undefined;
const statusChanged = pendingStatus !== appliedStatus;
const profileIdsChanged =
JSON.stringify([...pendingProfileIds].sort()) !==
JSON.stringify([...appliedProfileIds].sort());
return statusChanged || profileIdsChanged;
}, [pendingFilters.status, appliedFilters.status, pendingProfileIds, appliedProfileIds]);
return (
@@ -203,7 +208,7 @@ export const CertificateRequestsSection = ({ onViewCertificateFromRequest }: Pro
<button
type="button"
onClick={handleClearFilters}
className="text-primary hover:text-primary-600"
className="cursor-pointer text-primary hover:text-primary-600"
>
Clear filters
</button>
@@ -220,7 +225,7 @@ export const CertificateRequestsSection = ({ onViewCertificateFromRequest }: Pro
<button
type="button"
onClick={handleClearProfiles}
className="text-xs text-primary hover:text-primary-600"
className="cursor-pointer text-xs text-primary hover:text-primary-600"
>
Clear
</button>
@@ -258,7 +263,7 @@ export const CertificateRequestsSection = ({ onViewCertificateFromRequest }: Pro
<button
type="button"
onClick={handleClearStatus}
className="text-xs text-primary hover:text-primary-600"
className="cursor-pointer text-xs text-primary hover:text-primary-600"
>
Clear
</button>
@@ -274,6 +279,8 @@ export const CertificateRequestsSection = ({ onViewCertificateFromRequest }: Pro
}}
placeholder="All events"
className="w-full border-mineshaft-600 bg-mineshaft-700 text-bunker-200"
position="popper"
dropdownContainerClassName="max-w-none"
>
<SelectItem value="all">All events</SelectItem>
<SelectItem value="pending">Pending</SelectItem>
@@ -287,7 +294,7 @@ export const CertificateRequestsSection = ({ onViewCertificateFromRequest }: Pro
onClick={handleApplyFilters}
className="w-full bg-primary font-medium text-black hover:bg-primary-600"
size="sm"
disabled={!hasPendingChanges}
isDisabled={!hasPendingChanges}
>
Apply
</Button>
@@ -349,10 +356,11 @@ export const CertificateRequestsSection = ({ onViewCertificateFromRequest }: Pro
<Pagination
count={certificateRequestsData.totalCount}
page={currentPage}
perPage={PAGE_SIZE}
perPage={perPage}
onChangePage={(page) => setCurrentPage(page)}
onChangePerPage={() => {
onChangePerPage={(newPerPage) => {
setCurrentPage(1);
setPerPage(newPerPage);
}}
/>
</div>

View File

@@ -220,9 +220,17 @@ export const CertificatesTable = ({ handlePopUpOpen, externalFilter }: Props) =>
setPendingProfileIds([]);
};
const isTableFiltered = Boolean(
appliedSearch || appliedFilters.status || appliedProfileIds.length
);
const isTableFiltered = Boolean(appliedFilters.status || appliedProfileIds.length);
const hasFilterChanges = useMemo(() => {
const pendingStatus = pendingFilters.status ?? undefined;
const appliedStatus = appliedFilters.status ?? undefined;
const statusChanged = pendingStatus !== appliedStatus;
const profileIdsChanged =
JSON.stringify([...pendingProfileIds].sort()) !==
JSON.stringify([...appliedProfileIds].sort());
return statusChanged || profileIdsChanged;
}, [pendingFilters.status, appliedFilters.status, pendingProfileIds, appliedProfileIds]);
return (
<div>
@@ -261,7 +269,7 @@ export const CertificatesTable = ({ handlePopUpOpen, externalFilter }: Props) =>
<button
type="button"
onClick={handleClearFilters}
className="text-primary hover:text-primary-600"
className="cursor-pointer text-primary hover:text-primary-600"
>
Clear filters
</button>
@@ -278,7 +286,7 @@ export const CertificatesTable = ({ handlePopUpOpen, externalFilter }: Props) =>
<button
type="button"
onClick={handleClearProfiles}
className="text-xs text-primary hover:text-primary-600"
className="cursor-pointer text-xs text-primary hover:text-primary-600"
>
Clear
</button>
@@ -316,7 +324,7 @@ export const CertificatesTable = ({ handlePopUpOpen, externalFilter }: Props) =>
<button
type="button"
onClick={handleClearStatus}
className="text-xs text-primary hover:text-primary-600"
className="cursor-pointer text-xs text-primary hover:text-primary-600"
>
Clear
</button>
@@ -332,6 +340,8 @@ export const CertificatesTable = ({ handlePopUpOpen, externalFilter }: Props) =>
}}
placeholder="All statuses"
className="w-full border-mineshaft-600 bg-mineshaft-700 text-bunker-200"
position="popper"
dropdownContainerClassName="max-w-none"
>
<SelectItem value="all">All statuses</SelectItem>
<SelectItem value={CertificateStatus.Active}>Active</SelectItem>
@@ -349,10 +359,7 @@ export const CertificatesTable = ({ handlePopUpOpen, externalFilter }: Props) =>
}}
className="w-full bg-primary font-medium text-black hover:bg-primary-600"
size="sm"
disabled={
pendingFilters.status === appliedFilters.status &&
JSON.stringify(pendingProfileIds) === JSON.stringify(appliedProfileIds)
}
isDisabled={!hasFilterChanges}
>
Apply Filters
</Button>
@@ -503,11 +510,13 @@ export const CertificatesTable = ({ handlePopUpOpen, externalFilter }: Props) =>
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
<Tooltip content="More options">
<FontAwesomeIcon size="lg" icon={faEllipsis} />
</Tooltip>
</div>
<IconButton
variant="plain"
ariaLabel="More options"
className="h-max bg-transparent p-0"
>
<FontAwesomeIcon size="lg" icon={faEllipsis} />
</IconButton>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<ProjectPermissionCan