🚧 POC: pagination in sites table

This commit is contained in:
Fred KISSIE
2026-01-29 05:07:27 +01:00
parent c89c1a03da
commit 01a2820390
6 changed files with 217 additions and 153 deletions

View File

@@ -74,18 +74,20 @@ const listSitesParamsSchema = z.strictObject({
}); });
const listSitesSchema = z.object({ const listSitesSchema = z.object({
limit: z pageSize: z.coerce
.string() .number<string>() // for prettier formatting
.int()
.positive()
.optional() .optional()
.default("1") .catch(20)
.transform(Number) .default(20),
.pipe(z.int().positive()), page: z.coerce
offset: z .number<string>() // for prettier formatting
.string() .int()
.min(0)
.optional() .optional()
.default("0") .catch(1)
.transform(Number) .default(1)
.pipe(z.int().nonnegative())
}); });
function querySites(orgId: string, accessibleSiteIds: number[]) { function querySites(orgId: string, accessibleSiteIds: number[]) {
@@ -130,7 +132,7 @@ type SiteWithUpdateAvailable = Awaited<ReturnType<typeof querySites>>[0] & {
export type ListSitesResponse = { export type ListSitesResponse = {
sites: SiteWithUpdateAvailable[]; sites: SiteWithUpdateAvailable[];
pagination: { total: number; limit: number; offset: number; }; pagination: { total: number; pageSize: number; page: number };
}; };
registry.registerPath({ registry.registerPath({
@@ -160,7 +162,7 @@ export async function listSites(
) )
); );
} }
const { limit, offset } = parsedQuery.data; const { pageSize, page } = parsedQuery.data;
const parsedParams = listSitesParamsSchema.safeParse(req.params); const parsedParams = listSitesParamsSchema.safeParse(req.params);
if (!parsedParams.success) { if (!parsedParams.success) {
@@ -216,7 +218,9 @@ export async function listSites(
) )
); );
const sitesList = await baseQuery.limit(limit).offset(offset); const sitesList = await baseQuery
.limit(pageSize)
.offset(pageSize * (page - 1));
const totalCountResult = await countQuery; const totalCountResult = await countQuery;
const totalCount = totalCountResult[0].count; const totalCount = totalCountResult[0].count;
@@ -267,8 +271,8 @@ export async function listSites(
sites: sitesWithUpdates, sites: sitesWithUpdates,
pagination: { pagination: {
total: totalCount, total: totalCount,
limit, pageSize,
offset page
} }
}, },
success: true, success: true,

View File

@@ -9,19 +9,30 @@ import { getTranslations } from "next-intl/server";
type SitesPageProps = { type SitesPageProps = {
params: Promise<{ orgId: string }>; params: Promise<{ orgId: string }>;
searchParams: Promise<Record<string, string>>;
}; };
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
export default async function SitesPage(props: SitesPageProps) { export default async function SitesPage(props: SitesPageProps) {
const params = await props.params; const params = await props.params;
const searchParams = new URLSearchParams(await props.searchParams);
let sites: ListSitesResponse["sites"] = []; let sites: ListSitesResponse["sites"] = [];
let pagination: ListSitesResponse["pagination"] = {
total: 0,
page: 1,
pageSize: 20
};
try { try {
const res = await internal.get<AxiosResponse<ListSitesResponse>>( const res = await internal.get<AxiosResponse<ListSitesResponse>>(
`/org/${params.orgId}/sites`, `/org/${params.orgId}/sites?${searchParams.toString()}`,
await authCookieHeader() await authCookieHeader()
); );
sites = res.data.data.sites; const responseData = res.data.data;
sites = responseData.sites;
pagination = responseData.pagination;
} catch (e) {} } catch (e) {}
const t = await getTranslations(); const t = await getTranslations();
@@ -67,7 +78,17 @@ export default async function SitesPage(props: SitesPageProps) {
<SitesBanner /> <SitesBanner />
<SitesTable sites={siteRows} orgId={params.orgId} /> <SitesTable
sites={siteRows}
orgId={params.orgId}
pagination={{
pageCount: Math.ceil(
pagination.total / pagination.pageSize
),
pageIndex: pagination.page - 1,
pageSize: pagination.pageSize
}}
/>
</> </>
); );
} }

View File

@@ -1,50 +0,0 @@
"use client";
import { ColumnDef } from "@tanstack/react-table";
import { DataTable } from "@app/components/ui/data-table";
import { useTranslations } from "next-intl";
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[];
data: TData[];
createSite?: () => void;
onRefresh?: () => void;
isRefreshing?: boolean;
columnVisibility?: Record<string, boolean>;
enableColumnVisibility?: boolean;
}
export function SitesDataTable<TData, TValue>({
columns,
data,
createSite,
onRefresh,
isRefreshing,
columnVisibility,
enableColumnVisibility
}: DataTableProps<TData, TValue>) {
const t = useTranslations();
return (
<DataTable
columns={columns}
data={data}
persistPageSize="sites-table"
title={t("sites")}
searchPlaceholder={t("searchSitesProgress")}
searchColumn="name"
onAdd={createSite}
addButtonText={t("siteAdd")}
onRefresh={onRefresh}
isRefreshing={isRefreshing}
defaultSort={{
id: "name",
desc: false
}}
columnVisibility={columnVisibility}
enableColumnVisibility={enableColumnVisibility}
stickyLeftColumn="name"
stickyRightColumn="actions"
/>
);
}

View File

@@ -1,10 +1,14 @@
"use client"; "use client";
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog"; import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import { SitesDataTable } from "@app/components/SitesDataTable";
import { Badge } from "@app/components/ui/badge"; import { Badge } from "@app/components/ui/badge";
import { Button } from "@app/components/ui/button"; import { Button } from "@app/components/ui/button";
import { ExtendedColumnDef } from "@app/components/ui/data-table"; import {
DataTable,
ExtendedColumnDef,
type DataTablePaginationState
} from "@app/components/ui/data-table";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@@ -26,7 +30,7 @@ import {
} from "lucide-react"; } from "lucide-react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/navigation"; import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState, useTransition } from "react"; import { useEffect, useState, useTransition } from "react";
export type SiteRow = { export type SiteRow = {
@@ -48,15 +52,21 @@ export type SiteRow = {
type SitesTableProps = { type SitesTableProps = {
sites: SiteRow[]; sites: SiteRow[];
pagination: DataTablePaginationState;
orgId: string; orgId: string;
}; };
export default function SitesTable({ sites, orgId }: SitesTableProps) { export default function SitesTable({
sites,
orgId,
pagination
}: SitesTableProps) {
const router = useRouter(); const router = useRouter();
const searchParams = useSearchParams();
const pathname = usePathname();
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [selectedSite, setSelectedSite] = useState<SiteRow | null>(null); const [selectedSite, setSelectedSite] = useState<SiteRow | null>(null);
const [rows, setRows] = useState<SiteRow[]>(sites);
const [isRefreshing, startTransition] = useTransition(); const [isRefreshing, startTransition] = useTransition();
const api = createApiClient(useEnvContext()); const api = createApiClient(useEnvContext());
@@ -87,10 +97,6 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
.then(() => { .then(() => {
router.refresh(); router.refresh();
setIsDeleteModalOpen(false); setIsDeleteModalOpen(false);
const newRows = rows.filter((row) => row.id !== siteId);
setRows(newRows);
}); });
}; };
@@ -413,6 +419,11 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
} }
]; ];
console.log({
sites,
pagination
});
return ( return (
<> <>
{selectedSite && ( {selectedSite && (
@@ -429,27 +440,50 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
</div> </div>
} }
buttonText={t("siteConfirmDelete")} buttonText={t("siteConfirmDelete")}
onConfirm={async () => deleteSite(selectedSite!.id)} onConfirm={async () =>
startTransition(() => deleteSite(selectedSite!.id))
}
string={selectedSite.name} string={selectedSite.name}
title={t("siteDelete")} title={t("siteDelete")}
/> />
)} )}
<SitesDataTable <DataTable
columns={columns} columns={columns}
data={rows} data={sites}
createSite={() => persistPageSize="sites-table"
router.push(`/${orgId}/settings/sites/create`) title={t("sites")}
} searchPlaceholder={t("searchSitesProgress")}
manualFiltering
pagination={pagination}
onPaginationChange={(newPage) => {
console.log({
newPage
});
const sp = new URLSearchParams(searchParams);
sp.set("page", (newPage.pageIndex + 1).toString());
sp.set("pageSize", newPage.pageSize.toString());
startTransition(() =>
router.push(`${pathname}?${sp.toString()}`)
);
}}
onAdd={() => router.push(`/${orgId}/settings/sites/create`)}
addButtonText={t("siteAdd")}
onRefresh={() => startTransition(refreshData)} onRefresh={() => startTransition(refreshData)}
isRefreshing={isRefreshing} isRefreshing={isRefreshing}
defaultSort={{
id: "name",
desc: false
}}
columnVisibility={{ columnVisibility={{
niceId: false, niceId: false,
nice: false, nice: false,
exitNode: false, exitNode: false,
address: false address: false
}} }}
enableColumnVisibility={true} enableColumnVisibility
stickyLeftColumn="name"
stickyRightColumn="actions"
/> />
</> </>
); );

View File

@@ -151,11 +151,20 @@ type DataTableFilter = {
label: string; label: string;
options: FilterOption[]; options: FilterOption[];
multiSelect?: boolean; multiSelect?: boolean;
filterFn: (row: any, selectedValues: (string | number | boolean)[]) => boolean; filterFn: (
row: any,
selectedValues: (string | number | boolean)[]
) => boolean;
defaultValues?: (string | number | boolean)[]; defaultValues?: (string | number | boolean)[];
displayMode?: "label" | "calculated"; // How to display the filter button text displayMode?: "label" | "calculated"; // How to display the filter button text
}; };
export type DataTablePaginationState = PaginationState & {
pageCount: number;
};
export type DataTablePaginationUpdateFn = (newPage: PaginationState) => void;
type DataTableProps<TData, TValue> = { type DataTableProps<TData, TValue> = {
columns: ExtendedColumnDef<TData, TValue>[]; columns: ExtendedColumnDef<TData, TValue>[];
data: TData[]; data: TData[];
@@ -178,6 +187,11 @@ type DataTableProps<TData, TValue> = {
defaultPageSize?: number; defaultPageSize?: number;
columnVisibility?: Record<string, boolean>; columnVisibility?: Record<string, boolean>;
enableColumnVisibility?: boolean; enableColumnVisibility?: boolean;
manualFiltering?: boolean;
onSearch?: (input: string) => void;
searchValue?: string;
pagination?: DataTablePaginationState;
onPaginationChange?: DataTablePaginationUpdateFn;
persistColumnVisibility?: boolean | string; persistColumnVisibility?: boolean | string;
stickyLeftColumn?: string; // Column ID or accessorKey for left sticky column stickyLeftColumn?: string; // Column ID or accessorKey for left sticky column
stickyRightColumn?: string; // Column ID or accessorKey for right sticky column (typically "actions") stickyRightColumn?: string; // Column ID or accessorKey for right sticky column (typically "actions")
@@ -203,7 +217,12 @@ export function DataTable<TData, TValue>({
columnVisibility: defaultColumnVisibility, columnVisibility: defaultColumnVisibility,
enableColumnVisibility = false, enableColumnVisibility = false,
persistColumnVisibility = false, persistColumnVisibility = false,
manualFiltering = false,
pagination: paginationState,
stickyLeftColumn, stickyLeftColumn,
onSearch,
searchValue,
onPaginationChange,
stickyRightColumn stickyRightColumn
}: DataTableProps<TData, TValue>) { }: DataTableProps<TData, TValue>) {
const t = useTranslations(); const t = useTranslations();
@@ -248,22 +267,25 @@ export function DataTable<TData, TValue>({
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>( const [columnVisibility, setColumnVisibility] = useState<VisibilityState>(
initialColumnVisibility initialColumnVisibility
); );
const [pagination, setPagination] = useState<PaginationState>({ const [_pagination, setPagination] = useState<PaginationState>({
pageIndex: 0, pageIndex: 0,
pageSize: pageSize pageSize: pageSize
}); });
const pagination = paginationState ?? _pagination;
const [activeTab, setActiveTab] = useState<string>( const [activeTab, setActiveTab] = useState<string>(
defaultTab || tabs?.[0]?.id || "" defaultTab || tabs?.[0]?.id || ""
); );
const [activeFilters, setActiveFilters] = useState<Record<string, (string | number | boolean)[]>>( const [activeFilters, setActiveFilters] = useState<
() => { Record<string, (string | number | boolean)[]>
const initial: Record<string, (string | number | boolean)[]> = {}; >(() => {
filters?.forEach((filter) => { const initial: Record<string, (string | number | boolean)[]> = {};
initial[filter.id] = filter.defaultValues || []; filters?.forEach((filter) => {
}); initial[filter.id] = filter.defaultValues || [];
return initial; });
} return initial;
); });
// Track initial values to avoid storing defaults on first render // Track initial values to avoid storing defaults on first render
const initialPageSize = useRef(pageSize); const initialPageSize = useRef(pageSize);
@@ -309,7 +331,16 @@ export function DataTable<TData, TValue>({
getFilteredRowModel: getFilteredRowModel(), getFilteredRowModel: getFilteredRowModel(),
onGlobalFilterChange: setGlobalFilter, onGlobalFilterChange: setGlobalFilter,
onColumnVisibilityChange: setColumnVisibility, onColumnVisibilityChange: setColumnVisibility,
onPaginationChange: setPagination, onPaginationChange: onPaginationChange
? (state) => {
const newState =
typeof state === "function" ? state(pagination) : state;
onPaginationChange(newState);
}
: setPagination,
manualFiltering,
manualPagination: !!paginationState,
pageCount: paginationState?.pageCount,
initialState: { initialState: {
pagination: { pagination: {
pageSize: pageSize, pageSize: pageSize,
@@ -477,12 +508,14 @@ export function DataTable<TData, TValue>({
<div className="relative w-full sm:max-w-sm"> <div className="relative w-full sm:max-w-sm">
<Input <Input
placeholder={searchPlaceholder} placeholder={searchPlaceholder}
value={globalFilter ?? ""} value={searchValue ?? globalFilter ?? ""}
onChange={(e) => onChange={(e) => {
table.setGlobalFilter( onSearch
String(e.target.value) ? onSearch(e.currentTarget.value)
) : table.setGlobalFilter(
} String(e.target.value)
);
}}
className="w-full pl-8" className="w-full pl-8"
/> />
<Search className="h-4 w-4 absolute left-2 top-1/2 transform -translate-y-1/2 text-muted-foreground" /> <Search className="h-4 w-4 absolute left-2 top-1/2 transform -translate-y-1/2 text-muted-foreground" />
@@ -490,12 +523,16 @@ export function DataTable<TData, TValue>({
{filters && filters.length > 0 && ( {filters && filters.length > 0 && (
<div className="flex gap-2"> <div className="flex gap-2">
{filters.map((filter) => { {filters.map((filter) => {
const selectedValues = activeFilters[filter.id] || []; const selectedValues =
const hasActiveFilters = selectedValues.length > 0; activeFilters[filter.id] || [];
const displayMode = filter.displayMode || filterDisplayMode; const hasActiveFilters =
const displayText = displayMode === "calculated" selectedValues.length > 0;
? getFilterDisplayText(filter) const displayMode =
: filter.label; filter.displayMode || filterDisplayMode;
const displayText =
displayMode === "calculated"
? getFilterDisplayText(filter)
: filter.label;
return ( return (
<DropdownMenu key={filter.id}> <DropdownMenu key={filter.id}>
@@ -507,37 +544,54 @@ export function DataTable<TData, TValue>({
> >
<Filter className="h-4 w-4 mr-2" /> <Filter className="h-4 w-4 mr-2" />
{displayText} {displayText}
{displayMode === "label" && hasActiveFilters && ( {displayMode === "label" &&
<span className="ml-2 bg-muted text-foreground rounded-full px-2 py-0.5 text-xs"> hasActiveFilters && (
{selectedValues.length} <span className="ml-2 bg-muted text-foreground rounded-full px-2 py-0.5 text-xs">
</span> {
)} selectedValues.length
}
</span>
)}
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-48"> <DropdownMenuContent
align="start"
className="w-48"
>
<DropdownMenuLabel> <DropdownMenuLabel>
{filter.label} {filter.label}
</DropdownMenuLabel> </DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
{filter.options.map((option) => { {filter.options.map(
const isChecked = selectedValues.includes(option.value); (option) => {
return ( const isChecked =
<DropdownMenuCheckboxItem selectedValues.includes(
key={option.id} option.value
checked={isChecked} );
onCheckedChange={(checked) => return (
handleFilterChange( <DropdownMenuCheckboxItem
filter.id, key={option.id}
option.value, checked={
isChecked
}
onCheckedChange={(
checked checked
) ) =>
} handleFilterChange(
onSelect={(e) => e.preventDefault()} filter.id,
> option.value,
{option.label} checked
</DropdownMenuCheckboxItem> )
); }
})} onSelect={(e) =>
e.preventDefault()
}
>
{option.label}
</DropdownMenuCheckboxItem>
);
}
)}
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
); );

View File

@@ -113,7 +113,7 @@ export const orgQueries = {
return res.data.data.clients; return res.data.data.clients;
} }
}), }),
users: ({ orgId }: { orgId: string; }) => users: ({ orgId }: { orgId: string }) =>
queryOptions({ queryOptions({
queryKey: ["ORG", orgId, "USERS"] as const, queryKey: ["ORG", orgId, "USERS"] as const,
queryFn: async ({ signal, meta }) => { queryFn: async ({ signal, meta }) => {
@@ -124,7 +124,7 @@ export const orgQueries = {
return res.data.data.users; return res.data.data.users;
} }
}), }),
roles: ({ orgId }: { orgId: string; }) => roles: ({ orgId }: { orgId: string }) =>
queryOptions({ queryOptions({
queryKey: ["ORG", orgId, "ROLES"] as const, queryKey: ["ORG", orgId, "ROLES"] as const,
queryFn: async ({ signal, meta }) => { queryFn: async ({ signal, meta }) => {
@@ -136,7 +136,7 @@ export const orgQueries = {
} }
}), }),
sites: ({ orgId }: { orgId: string; }) => sites: ({ orgId }: { orgId: string }) =>
queryOptions({ queryOptions({
queryKey: ["ORG", orgId, "SITES"] as const, queryKey: ["ORG", orgId, "SITES"] as const,
queryFn: async ({ signal, meta }) => { queryFn: async ({ signal, meta }) => {
@@ -147,7 +147,7 @@ export const orgQueries = {
} }
}), }),
domains: ({ orgId }: { orgId: string; }) => domains: ({ orgId }: { orgId: string }) =>
queryOptions({ queryOptions({
queryKey: ["ORG", orgId, "DOMAINS"] as const, queryKey: ["ORG", orgId, "DOMAINS"] as const,
queryFn: async ({ signal, meta }) => { queryFn: async ({ signal, meta }) => {
@@ -169,7 +169,7 @@ export const orgQueries = {
queryFn: async ({ signal, meta }) => { queryFn: async ({ signal, meta }) => {
const res = await meta!.api.get< const res = await meta!.api.get<
AxiosResponse<{ AxiosResponse<{
idps: { idpId: number; name: string; }[]; idps: { idpId: number; name: string }[];
}> }>
>( >(
build === "saas" || useOrgOnlyIdp build === "saas" || useOrgOnlyIdp
@@ -188,19 +188,20 @@ export const logAnalyticsFiltersSchema = z.object({
.refine((val) => !isNaN(Date.parse(val)), { .refine((val) => !isNaN(Date.parse(val)), {
error: "timeStart must be a valid ISO date string" error: "timeStart must be a valid ISO date string"
}) })
.optional().catch(undefined), .optional()
.catch(undefined),
timeEnd: z timeEnd: z
.string() .string()
.refine((val) => !isNaN(Date.parse(val)), { .refine((val) => !isNaN(Date.parse(val)), {
error: "timeEnd must be a valid ISO date string" error: "timeEnd must be a valid ISO date string"
}) })
.optional().catch(undefined), .optional()
.catch(undefined),
resourceId: z.coerce.number().optional().catch(undefined) resourceId: z.coerce.number().optional().catch(undefined)
}); });
export type LogAnalyticsFilters = z.TypeOf<typeof logAnalyticsFiltersSchema>; export type LogAnalyticsFilters = z.TypeOf<typeof logAnalyticsFiltersSchema>;
export const logQueries = { export const logQueries = {
requestAnalytics: ({ requestAnalytics: ({
orgId, orgId,
@@ -230,7 +231,7 @@ export const logQueries = {
}; };
export const resourceQueries = { export const resourceQueries = {
resourceUsers: ({ resourceId }: { resourceId: number; }) => resourceUsers: ({ resourceId }: { resourceId: number }) =>
queryOptions({ queryOptions({
queryKey: ["RESOURCES", resourceId, "USERS"] as const, queryKey: ["RESOURCES", resourceId, "USERS"] as const,
queryFn: async ({ signal, meta }) => { queryFn: async ({ signal, meta }) => {
@@ -240,7 +241,7 @@ export const resourceQueries = {
return res.data.data.users; return res.data.data.users;
} }
}), }),
resourceRoles: ({ resourceId }: { resourceId: number; }) => resourceRoles: ({ resourceId }: { resourceId: number }) =>
queryOptions({ queryOptions({
queryKey: ["RESOURCES", resourceId, "ROLES"] as const, queryKey: ["RESOURCES", resourceId, "ROLES"] as const,
queryFn: async ({ signal, meta }) => { queryFn: async ({ signal, meta }) => {
@@ -251,7 +252,7 @@ export const resourceQueries = {
return res.data.data.roles; return res.data.data.roles;
} }
}), }),
siteResourceUsers: ({ siteResourceId }: { siteResourceId: number; }) => siteResourceUsers: ({ siteResourceId }: { siteResourceId: number }) =>
queryOptions({ queryOptions({
queryKey: ["SITE_RESOURCES", siteResourceId, "USERS"] as const, queryKey: ["SITE_RESOURCES", siteResourceId, "USERS"] as const,
queryFn: async ({ signal, meta }) => { queryFn: async ({ signal, meta }) => {
@@ -261,7 +262,7 @@ export const resourceQueries = {
return res.data.data.users; return res.data.data.users;
} }
}), }),
siteResourceRoles: ({ siteResourceId }: { siteResourceId: number; }) => siteResourceRoles: ({ siteResourceId }: { siteResourceId: number }) =>
queryOptions({ queryOptions({
queryKey: ["SITE_RESOURCES", siteResourceId, "ROLES"] as const, queryKey: ["SITE_RESOURCES", siteResourceId, "ROLES"] as const,
queryFn: async ({ signal, meta }) => { queryFn: async ({ signal, meta }) => {
@@ -272,7 +273,7 @@ export const resourceQueries = {
return res.data.data.roles; return res.data.data.roles;
} }
}), }),
siteResourceClients: ({ siteResourceId }: { siteResourceId: number; }) => siteResourceClients: ({ siteResourceId }: { siteResourceId: number }) =>
queryOptions({ queryOptions({
queryKey: ["SITE_RESOURCES", siteResourceId, "CLIENTS"] as const, queryKey: ["SITE_RESOURCES", siteResourceId, "CLIENTS"] as const,
queryFn: async ({ signal, meta }) => { queryFn: async ({ signal, meta }) => {
@@ -283,7 +284,7 @@ export const resourceQueries = {
return res.data.data.clients; return res.data.data.clients;
} }
}), }),
resourceTargets: ({ resourceId }: { resourceId: number; }) => resourceTargets: ({ resourceId }: { resourceId: number }) =>
queryOptions({ queryOptions({
queryKey: ["RESOURCES", resourceId, "TARGETS"] as const, queryKey: ["RESOURCES", resourceId, "TARGETS"] as const,
queryFn: async ({ signal, meta }) => { queryFn: async ({ signal, meta }) => {
@@ -294,7 +295,7 @@ export const resourceQueries = {
return res.data.data.targets; return res.data.data.targets;
} }
}), }),
resourceWhitelist: ({ resourceId }: { resourceId: number; }) => resourceWhitelist: ({ resourceId }: { resourceId: number }) =>
queryOptions({ queryOptions({
queryKey: ["RESOURCES", resourceId, "WHITELISTS"] as const, queryKey: ["RESOURCES", resourceId, "WHITELISTS"] as const,
queryFn: async ({ signal, meta }) => { queryFn: async ({ signal, meta }) => {
@@ -367,7 +368,7 @@ export const approvalQueries = {
} }
const res = await meta!.api.get< const res = await meta!.api.get<
AxiosResponse<{ approvals: ApprovalItem[]; }> AxiosResponse<{ approvals: ApprovalItem[] }>
>(`/org/${orgId}/approvals?${sp.toString()}`, { >(`/org/${orgId}/approvals?${sp.toString()}`, {
signal signal
}); });
@@ -379,7 +380,7 @@ export const approvalQueries = {
queryKey: ["APPROVALS", orgId, "COUNT", "pending"] as const, queryKey: ["APPROVALS", orgId, "COUNT", "pending"] as const,
queryFn: async ({ signal, meta }) => { queryFn: async ({ signal, meta }) => {
const res = await meta!.api.get< const res = await meta!.api.get<
AxiosResponse<{ count: number; }> AxiosResponse<{ count: number }>
>(`/org/${orgId}/approvals/count?approvalState=pending`, { >(`/org/${orgId}/approvals/count?approvalState=pending`, {
signal signal
}); });