mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-15 17:36:37 +00:00
🚧 wip: paginated tables
This commit is contained in:
@@ -77,7 +77,7 @@ const listSitesSchema = z.object({
|
||||
limit: z
|
||||
.string()
|
||||
.optional()
|
||||
.default("1000")
|
||||
.default("1")
|
||||
.transform(Number)
|
||||
.pipe(z.int().positive()),
|
||||
offset: z
|
||||
@@ -130,7 +130,7 @@ type SiteWithUpdateAvailable = Awaited<ReturnType<typeof querySites>>[0] & {
|
||||
|
||||
export type ListSitesResponse = {
|
||||
sites: SiteWithUpdateAvailable[];
|
||||
pagination: { total: number; limit: number; offset: number };
|
||||
pagination: { total: number; limit: number; offset: number; };
|
||||
};
|
||||
|
||||
registry.registerPath({
|
||||
|
||||
@@ -60,8 +60,6 @@ export default async function SitesPage(props: SitesPageProps) {
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <SitesSplashCard /> */}
|
||||
|
||||
<SettingsSectionTitle
|
||||
title={t("siteManageSites")}
|
||||
description={t("siteDescription")}
|
||||
|
||||
@@ -1,37 +1,33 @@
|
||||
"use client";
|
||||
|
||||
import { Column, ColumnDef } from "@tanstack/react-table";
|
||||
import { ExtendedColumnDef } from "@app/components/ui/data-table";
|
||||
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
|
||||
import { SitesDataTable } from "@app/components/SitesDataTable";
|
||||
import { Badge } from "@app/components/ui/badge";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import { ExtendedColumnDef } from "@app/components/ui/data-table";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger
|
||||
} from "@app/components/ui/dropdown-menu";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import { InfoPopup } from "@app/components/ui/info-popup";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import { toast } from "@app/hooks/useToast";
|
||||
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||
import { parseDataSize } from "@app/lib/dataSize";
|
||||
import { build } from "@server/build";
|
||||
import { Column } from "@tanstack/react-table";
|
||||
import {
|
||||
ArrowRight,
|
||||
ArrowUpDown,
|
||||
ArrowUpRight,
|
||||
Check,
|
||||
MoreHorizontal,
|
||||
X
|
||||
MoreHorizontal
|
||||
} from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { useState, useEffect } from "react";
|
||||
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
|
||||
import { toast } from "@app/hooks/useToast";
|
||||
import { formatAxiosError } from "@app/lib/api";
|
||||
import { createApiClient } from "@app/lib/api";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { parseDataSize } from "@app/lib/dataSize";
|
||||
import { Badge } from "@app/components/ui/badge";
|
||||
import { InfoPopup } from "@app/components/ui/info-popup";
|
||||
import { build } from "@server/build";
|
||||
import { useEffect, useState, useTransition } from "react";
|
||||
|
||||
export type SiteRow = {
|
||||
id: number;
|
||||
@@ -61,22 +57,13 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [selectedSite, setSelectedSite] = useState<SiteRow | null>(null);
|
||||
const [rows, setRows] = useState<SiteRow[]>(sites);
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const [isRefreshing, startTransition] = useTransition();
|
||||
|
||||
const api = createApiClient(useEnvContext());
|
||||
const t = useTranslations();
|
||||
const { env } = useEnvContext();
|
||||
|
||||
// Update local state when props change (e.g., after refresh)
|
||||
useEffect(() => {
|
||||
setRows(sites);
|
||||
}, [sites]);
|
||||
|
||||
const refreshData = async () => {
|
||||
console.log("Data refreshed");
|
||||
setIsRefreshing(true);
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 200));
|
||||
router.refresh();
|
||||
} catch (error) {
|
||||
toast({
|
||||
@@ -84,8 +71,6 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
description: t("refreshError"),
|
||||
variant: "destructive"
|
||||
});
|
||||
} finally {
|
||||
setIsRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -456,7 +441,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||
createSite={() =>
|
||||
router.push(`/${orgId}/settings/sites/create`)
|
||||
}
|
||||
onRefresh={refreshData}
|
||||
onRefresh={() => startTransition(refreshData)}
|
||||
isRefreshing={isRefreshing}
|
||||
columnVisibility={{
|
||||
niceId: false,
|
||||
|
||||
@@ -113,7 +113,7 @@ export const orgQueries = {
|
||||
return res.data.data.clients;
|
||||
}
|
||||
}),
|
||||
users: ({ orgId }: { orgId: string }) =>
|
||||
users: ({ orgId }: { orgId: string; }) =>
|
||||
queryOptions({
|
||||
queryKey: ["ORG", orgId, "USERS"] as const,
|
||||
queryFn: async ({ signal, meta }) => {
|
||||
@@ -124,7 +124,7 @@ export const orgQueries = {
|
||||
return res.data.data.users;
|
||||
}
|
||||
}),
|
||||
roles: ({ orgId }: { orgId: string }) =>
|
||||
roles: ({ orgId }: { orgId: string; }) =>
|
||||
queryOptions({
|
||||
queryKey: ["ORG", orgId, "ROLES"] as const,
|
||||
queryFn: async ({ signal, meta }) => {
|
||||
@@ -136,7 +136,7 @@ export const orgQueries = {
|
||||
}
|
||||
}),
|
||||
|
||||
sites: ({ orgId }: { orgId: string }) =>
|
||||
sites: ({ orgId }: { orgId: string; }) =>
|
||||
queryOptions({
|
||||
queryKey: ["ORG", orgId, "SITES"] as const,
|
||||
queryFn: async ({ signal, meta }) => {
|
||||
@@ -147,7 +147,7 @@ export const orgQueries = {
|
||||
}
|
||||
}),
|
||||
|
||||
domains: ({ orgId }: { orgId: string }) =>
|
||||
domains: ({ orgId }: { orgId: string; }) =>
|
||||
queryOptions({
|
||||
queryKey: ["ORG", orgId, "DOMAINS"] as const,
|
||||
queryFn: async ({ signal, meta }) => {
|
||||
@@ -169,7 +169,7 @@ export const orgQueries = {
|
||||
queryFn: async ({ signal, meta }) => {
|
||||
const res = await meta!.api.get<
|
||||
AxiosResponse<{
|
||||
idps: { idpId: number; name: string }[];
|
||||
idps: { idpId: number; name: string; }[];
|
||||
}>
|
||||
>(
|
||||
build === "saas" || useOrgOnlyIdp
|
||||
@@ -188,23 +188,19 @@ export const logAnalyticsFiltersSchema = z.object({
|
||||
.refine((val) => !isNaN(Date.parse(val)), {
|
||||
error: "timeStart must be a valid ISO date string"
|
||||
})
|
||||
.optional(),
|
||||
.optional().catch(undefined),
|
||||
timeEnd: z
|
||||
.string()
|
||||
.refine((val) => !isNaN(Date.parse(val)), {
|
||||
error: "timeEnd must be a valid ISO date string"
|
||||
})
|
||||
.optional(),
|
||||
resourceId: z
|
||||
.string()
|
||||
.optional()
|
||||
.transform(Number)
|
||||
.pipe(z.int().positive())
|
||||
.optional()
|
||||
.optional().catch(undefined),
|
||||
resourceId: z.coerce.number().optional().catch(undefined)
|
||||
});
|
||||
|
||||
export type LogAnalyticsFilters = z.TypeOf<typeof logAnalyticsFiltersSchema>;
|
||||
|
||||
|
||||
export const logQueries = {
|
||||
requestAnalytics: ({
|
||||
orgId,
|
||||
@@ -234,7 +230,7 @@ export const logQueries = {
|
||||
};
|
||||
|
||||
export const resourceQueries = {
|
||||
resourceUsers: ({ resourceId }: { resourceId: number }) =>
|
||||
resourceUsers: ({ resourceId }: { resourceId: number; }) =>
|
||||
queryOptions({
|
||||
queryKey: ["RESOURCES", resourceId, "USERS"] as const,
|
||||
queryFn: async ({ signal, meta }) => {
|
||||
@@ -244,7 +240,7 @@ export const resourceQueries = {
|
||||
return res.data.data.users;
|
||||
}
|
||||
}),
|
||||
resourceRoles: ({ resourceId }: { resourceId: number }) =>
|
||||
resourceRoles: ({ resourceId }: { resourceId: number; }) =>
|
||||
queryOptions({
|
||||
queryKey: ["RESOURCES", resourceId, "ROLES"] as const,
|
||||
queryFn: async ({ signal, meta }) => {
|
||||
@@ -255,7 +251,7 @@ export const resourceQueries = {
|
||||
return res.data.data.roles;
|
||||
}
|
||||
}),
|
||||
siteResourceUsers: ({ siteResourceId }: { siteResourceId: number }) =>
|
||||
siteResourceUsers: ({ siteResourceId }: { siteResourceId: number; }) =>
|
||||
queryOptions({
|
||||
queryKey: ["SITE_RESOURCES", siteResourceId, "USERS"] as const,
|
||||
queryFn: async ({ signal, meta }) => {
|
||||
@@ -265,7 +261,7 @@ export const resourceQueries = {
|
||||
return res.data.data.users;
|
||||
}
|
||||
}),
|
||||
siteResourceRoles: ({ siteResourceId }: { siteResourceId: number }) =>
|
||||
siteResourceRoles: ({ siteResourceId }: { siteResourceId: number; }) =>
|
||||
queryOptions({
|
||||
queryKey: ["SITE_RESOURCES", siteResourceId, "ROLES"] as const,
|
||||
queryFn: async ({ signal, meta }) => {
|
||||
@@ -276,7 +272,7 @@ export const resourceQueries = {
|
||||
return res.data.data.roles;
|
||||
}
|
||||
}),
|
||||
siteResourceClients: ({ siteResourceId }: { siteResourceId: number }) =>
|
||||
siteResourceClients: ({ siteResourceId }: { siteResourceId: number; }) =>
|
||||
queryOptions({
|
||||
queryKey: ["SITE_RESOURCES", siteResourceId, "CLIENTS"] as const,
|
||||
queryFn: async ({ signal, meta }) => {
|
||||
@@ -287,7 +283,7 @@ export const resourceQueries = {
|
||||
return res.data.data.clients;
|
||||
}
|
||||
}),
|
||||
resourceTargets: ({ resourceId }: { resourceId: number }) =>
|
||||
resourceTargets: ({ resourceId }: { resourceId: number; }) =>
|
||||
queryOptions({
|
||||
queryKey: ["RESOURCES", resourceId, "TARGETS"] as const,
|
||||
queryFn: async ({ signal, meta }) => {
|
||||
@@ -298,7 +294,7 @@ export const resourceQueries = {
|
||||
return res.data.data.targets;
|
||||
}
|
||||
}),
|
||||
resourceWhitelist: ({ resourceId }: { resourceId: number }) =>
|
||||
resourceWhitelist: ({ resourceId }: { resourceId: number; }) =>
|
||||
queryOptions({
|
||||
queryKey: ["RESOURCES", resourceId, "WHITELISTS"] as const,
|
||||
queryFn: async ({ signal, meta }) => {
|
||||
@@ -371,7 +367,7 @@ export const approvalQueries = {
|
||||
}
|
||||
|
||||
const res = await meta!.api.get<
|
||||
AxiosResponse<{ approvals: ApprovalItem[] }>
|
||||
AxiosResponse<{ approvals: ApprovalItem[]; }>
|
||||
>(`/org/${orgId}/approvals?${sp.toString()}`, {
|
||||
signal
|
||||
});
|
||||
@@ -383,7 +379,7 @@ export const approvalQueries = {
|
||||
queryKey: ["APPROVALS", orgId, "COUNT", "pending"] as const,
|
||||
queryFn: async ({ signal, meta }) => {
|
||||
const res = await meta!.api.get<
|
||||
AxiosResponse<{ count: number }>
|
||||
AxiosResponse<{ count: number; }>
|
||||
>(`/org/${orgId}/approvals/count?approvalState=pending`, {
|
||||
signal
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user