♻️ some refactors

This commit is contained in:
Fred KISSIE
2025-12-02 19:08:35 +01:00
parent 06a31bb716
commit c93ab34021
4 changed files with 79 additions and 267 deletions

View File

@@ -27,7 +27,6 @@ import {
import { useEnvContext } from "@app/hooks/useEnvContext"; import { useEnvContext } from "@app/hooks/useEnvContext";
import { toast } from "@app/hooks/useToast"; import { toast } from "@app/hooks/useToast";
import { createApiClient, formatAxiosError } from "@app/lib/api"; import { createApiClient, formatAxiosError } from "@app/lib/api";
import { ListSitesResponse } from "@server/routers/site";
import { import {
ColumnFiltersState, ColumnFiltersState,
flexRender, flexRender,
@@ -36,28 +35,26 @@ import {
getPaginationRowModel, getPaginationRowModel,
getSortedRowModel, getSortedRowModel,
SortingState, SortingState,
useReactTable, useReactTable
VisibilityState
} from "@tanstack/react-table"; } from "@tanstack/react-table";
import { import {
ArrowUpDown, ArrowUpDown,
ArrowUpRight, ArrowUpRight,
CheckCircle2,
Clock,
Columns, Columns,
MoreHorizontal, MoreHorizontal,
Plus, Plus,
RefreshCw, RefreshCw,
Search, Search
XCircle
} 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 { useRouter } from "next/navigation";
import { useEffect, useState, useTransition } from "react"; import { useState, useTransition } from "react";
import CreateInternalResourceDialog from "@app/components/CreateInternalResourceDialog"; import CreateInternalResourceDialog from "@app/components/CreateInternalResourceDialog";
import EditInternalResourceDialog from "@app/components/EditInternalResourceDialog"; import EditInternalResourceDialog from "@app/components/EditInternalResourceDialog";
import { useStoredColumnVisibility } from "@app/hooks/useStoredColumnVisibility";
import { useStoredPageSize } from "@app/hooks/useStoredPageSize";
import { siteQueries } from "@app/lib/queries"; import { siteQueries } from "@app/lib/queries";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
@@ -113,88 +110,6 @@ type ClientResourcesTableProps = {
}; };
}; };
const STORAGE_KEYS = {
PAGE_SIZE: "datatable-page-size",
COLUMN_VISIBILITY: "datatable-column-visibility",
getTablePageSize: (tableId?: string) =>
tableId ? `datatable-${tableId}-page-size` : STORAGE_KEYS.PAGE_SIZE,
getTableColumnVisibility: (tableId?: string) =>
tableId
? `datatable-${tableId}-column-visibility`
: STORAGE_KEYS.COLUMN_VISIBILITY
};
const getStoredPageSize = (tableId?: string, defaultSize = 20): number => {
if (typeof window === "undefined") return defaultSize;
try {
const key = STORAGE_KEYS.getTablePageSize(tableId);
const stored = localStorage.getItem(key);
if (stored) {
const parsed = parseInt(stored, 10);
if (parsed > 0 && parsed <= 1000) {
return parsed;
}
}
} catch (error) {
console.warn("Failed to read page size from localStorage:", error);
}
return defaultSize;
};
const setStoredPageSize = (pageSize: number, tableId?: string): void => {
if (typeof window === "undefined") return;
try {
const key = STORAGE_KEYS.getTablePageSize(tableId);
localStorage.setItem(key, pageSize.toString());
} catch (error) {
console.warn("Failed to save page size to localStorage:", error);
}
};
const getStoredColumnVisibility = (
tableId?: string,
defaultVisibility?: Record<string, boolean>
): Record<string, boolean> => {
if (typeof window === "undefined") return defaultVisibility || {};
try {
const key = STORAGE_KEYS.getTableColumnVisibility(tableId);
const stored = localStorage.getItem(key);
if (stored) {
const parsed = JSON.parse(stored);
// Validate that it's an object
if (typeof parsed === "object" && parsed !== null) {
return parsed;
}
}
} catch (error) {
console.warn(
"Failed to read column visibility from localStorage:",
error
);
}
return defaultVisibility || {};
};
const setStoredColumnVisibility = (
visibility: Record<string, boolean>,
tableId?: string
): void => {
if (typeof window === "undefined") return;
try {
const key = STORAGE_KEYS.getTableColumnVisibility(tableId);
localStorage.setItem(key, JSON.stringify(visibility));
} catch (error) {
console.warn(
"Failed to save column visibility to localStorage:",
error
);
}
};
export default function ClientResourcesTable({ export default function ClientResourcesTable({
internalResources, internalResources,
orgId, orgId,
@@ -207,10 +122,10 @@ export default function ClientResourcesTable({
const api = createApiClient({ env }); const api = createApiClient({ env });
const [internalPageSize, setInternalPageSize] = useState<number>(() => const [internalPageSize, setInternalPageSize] = useStoredPageSize(
getStoredPageSize("internal-resources", 20) "internal-resources",
20
); );
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [selectedInternalResource, setSelectedInternalResource] = const [selectedInternalResource, setSelectedInternalResource] =
@@ -233,10 +148,7 @@ export default function ClientResourcesTable({
const [isRefreshing, startTransition] = useTransition(); const [isRefreshing, startTransition] = useTransition();
const [internalColumnVisibility, setInternalColumnVisibility] = const [internalColumnVisibility, setInternalColumnVisibility] =
useState<VisibilityState>(() => useStoredColumnVisibility("internal-resources", {});
getStoredColumnVisibility("internal-resources", {})
);
const refreshData = async () => { const refreshData = async () => {
try { try {
router.refresh(); router.refresh();
@@ -255,11 +167,14 @@ export default function ClientResourcesTable({
siteId: number siteId: number
) => { ) => {
try { try {
await api.delete( await api
`/org/${orgId}/site/${siteId}/resource/${resourceId}` .delete(`/org/${orgId}/site/${siteId}/resource/${resourceId}`)
); .then(() => {
router.refresh(); startTransition(() => {
setIsDeleteModalOpen(false); router.refresh();
setIsDeleteModalOpen(false);
});
});
} catch (e) { } catch (e) {
console.error(t("resourceErrorDelete"), e); console.error(t("resourceErrorDelete"), e);
toast({ toast({
@@ -515,19 +430,6 @@ export default function ClientResourcesTable({
} }
}); });
const handleInternalPageSizeChange = (newPageSize: number) => {
setInternalPageSize(newPageSize);
setStoredPageSize(newPageSize, "internal-resources");
};
// Persist column visibility changes to localStorage
useEffect(() => {
setStoredColumnVisibility(
internalColumnVisibility,
"internal-resources"
);
}, [internalColumnVisibility]);
return ( return (
<> <>
{selectedInternalResource && ( {selectedInternalResource && (
@@ -700,7 +602,7 @@ export default function ClientResourcesTable({
<div className="mt-4"> <div className="mt-4">
<DataTablePagination <DataTablePagination
table={internalTable} table={internalTable}
onPageSizeChange={handleInternalPageSizeChange} onPageSizeChange={setInternalPageSize}
/> />
</div> </div>
</CardContent> </CardContent>

View File

@@ -138,8 +138,10 @@ export default function MachineClientsTable({
}); });
}) })
.then(() => { .then(() => {
router.refresh(); startTransition(() => {
setIsDeleteModalOpen(false); router.refresh();
setIsDeleteModalOpen(false);
});
}); });
}; };

View File

@@ -1,60 +1,23 @@
"use client"; "use client";
import { import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
flexRender, import CopyToClipboard from "@app/components/CopyToClipboard";
getCoreRowModel, import { DataTablePagination } from "@app/components/DataTablePagination";
useReactTable, import { Button } from "@app/components/ui/button";
getPaginationRowModel, import { Card, CardContent, CardHeader } from "@app/components/ui/card";
SortingState,
getSortedRowModel,
ColumnFiltersState,
getFilteredRowModel,
VisibilityState
} from "@tanstack/react-table";
import { ExtendedColumnDef } from "@app/components/ui/data-table"; import { ExtendedColumnDef } from "@app/components/ui/data-table";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuCheckboxItem DropdownMenuTrigger
} from "@app/components/ui/dropdown-menu"; } from "@app/components/ui/dropdown-menu";
import { Button } from "@app/components/ui/button";
import {
ArrowRight,
ArrowUpDown,
MoreHorizontal,
ShieldOff,
ShieldCheck,
RefreshCw,
Columns,
Plus,
Search,
ChevronDown,
Clock,
CheckCircle2,
XCircle
} from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState, useEffect, useTransition } from "react";
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import { formatAxiosError } from "@app/lib/api";
import { toast } from "@app/hooks/useToast";
import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
import CopyToClipboard from "@app/components/CopyToClipboard";
import { Switch } from "@app/components/ui/switch";
import { AxiosResponse } from "axios";
import { UpdateResourceResponse } from "@server/routers/resource";
import { ListSitesResponse } from "@server/routers/site";
import { useTranslations } from "next-intl";
import { InfoPopup } from "@app/components/ui/info-popup"; import { InfoPopup } from "@app/components/ui/info-popup";
import { Input } from "@app/components/ui/input"; import { Input } from "@app/components/ui/input";
import { DataTablePagination } from "@app/components/DataTablePagination"; import { Switch } from "@app/components/ui/switch";
import { Card, CardContent, CardHeader } from "@app/components/ui/card";
import { import {
Table, Table,
TableBody, TableBody,
@@ -63,6 +26,42 @@ import {
TableHeader, TableHeader,
TableRow TableRow
} from "@app/components/ui/table"; } from "@app/components/ui/table";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { useStoredColumnVisibility } from "@app/hooks/useStoredColumnVisibility";
import { useStoredPageSize } from "@app/hooks/useStoredPageSize";
import { toast } from "@app/hooks/useToast";
import { createApiClient, formatAxiosError } from "@app/lib/api";
import { UpdateResourceResponse } from "@server/routers/resource";
import {
ColumnFiltersState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
SortingState,
useReactTable
} from "@tanstack/react-table";
import { AxiosResponse } from "axios";
import {
ArrowRight,
ArrowUpDown,
CheckCircle2,
ChevronDown,
Clock,
Columns,
MoreHorizontal,
Plus,
RefreshCw,
Search,
ShieldCheck,
ShieldOff,
XCircle
} from "lucide-react";
import { useTranslations } from "next-intl";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState, useTransition } from "react";
export type TargetHealth = { export type TargetHealth = {
targetId: number; targetId: number;
@@ -153,88 +152,6 @@ type ProxyResourcesTableProps = {
}; };
}; };
const STORAGE_KEYS = {
PAGE_SIZE: "datatable-page-size",
COLUMN_VISIBILITY: "datatable-column-visibility",
getTablePageSize: (tableId?: string) =>
tableId ? `datatable-${tableId}-page-size` : STORAGE_KEYS.PAGE_SIZE,
getTableColumnVisibility: (tableId?: string) =>
tableId
? `datatable-${tableId}-column-visibility`
: STORAGE_KEYS.COLUMN_VISIBILITY
};
const getStoredPageSize = (tableId?: string, defaultSize = 20): number => {
if (typeof window === "undefined") return defaultSize;
try {
const key = STORAGE_KEYS.getTablePageSize(tableId);
const stored = localStorage.getItem(key);
if (stored) {
const parsed = parseInt(stored, 10);
if (parsed > 0 && parsed <= 1000) {
return parsed;
}
}
} catch (error) {
console.warn("Failed to read page size from localStorage:", error);
}
return defaultSize;
};
const setStoredPageSize = (pageSize: number, tableId?: string): void => {
if (typeof window === "undefined") return;
try {
const key = STORAGE_KEYS.getTablePageSize(tableId);
localStorage.setItem(key, pageSize.toString());
} catch (error) {
console.warn("Failed to save page size to localStorage:", error);
}
};
const getStoredColumnVisibility = (
tableId?: string,
defaultVisibility?: Record<string, boolean>
): Record<string, boolean> => {
if (typeof window === "undefined") return defaultVisibility || {};
try {
const key = STORAGE_KEYS.getTableColumnVisibility(tableId);
const stored = localStorage.getItem(key);
if (stored) {
const parsed = JSON.parse(stored);
// Validate that it's an object
if (typeof parsed === "object" && parsed !== null) {
return parsed;
}
}
} catch (error) {
console.warn(
"Failed to read column visibility from localStorage:",
error
);
}
return defaultVisibility || {};
};
const setStoredColumnVisibility = (
visibility: Record<string, boolean>,
tableId?: string
): void => {
if (typeof window === "undefined") return;
try {
const key = STORAGE_KEYS.getTableColumnVisibility(tableId);
localStorage.setItem(key, JSON.stringify(visibility));
} catch (error) {
console.warn(
"Failed to save column visibility to localStorage:",
error
);
}
};
export default function ProxyResourcesTable({ export default function ProxyResourcesTable({
resources, resources,
orgId, orgId,
@@ -247,10 +164,10 @@ export default function ProxyResourcesTable({
const api = createApiClient({ env }); const api = createApiClient({ env });
const [proxyPageSize, setProxyPageSize] = useState<number>(() => const [proxyPageSize, setProxyPageSize] = useStoredPageSize(
getStoredPageSize("proxy-resources", 20) "proxy-resources",
20
); );
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
const [selectedResource, setSelectedResource] = const [selectedResource, setSelectedResource] =
useState<ResourceRow | null>(); useState<ResourceRow | null>();
@@ -265,10 +182,7 @@ export default function ProxyResourcesTable({
const [isRefreshing, startTransition] = useTransition(); const [isRefreshing, startTransition] = useTransition();
const [proxyColumnVisibility, setProxyColumnVisibility] = const [proxyColumnVisibility, setProxyColumnVisibility] =
useState<VisibilityState>(() => useStoredColumnVisibility("proxy-resources", {});
getStoredColumnVisibility("proxy-resources", {})
);
const refreshData = () => { const refreshData = () => {
try { try {
router.refresh(); router.refresh();
@@ -292,8 +206,10 @@ export default function ProxyResourcesTable({
}); });
}) })
.then(() => { .then(() => {
router.refresh(); startTransition(() => {
setIsDeleteModalOpen(false); router.refresh();
setIsDeleteModalOpen(false);
});
}); });
}; };
@@ -734,16 +650,6 @@ export default function ProxyResourcesTable({
} }
}); });
const handleProxyPageSizeChange = (newPageSize: number) => {
setProxyPageSize(newPageSize);
setStoredPageSize(newPageSize, "proxy-resources");
};
// Persist column visibility changes to localStorage
useEffect(() => {
setStoredColumnVisibility(proxyColumnVisibility, "proxy-resources");
}, [proxyColumnVisibility]);
return ( return (
<> <>
{selectedResource && ( {selectedResource && (
@@ -913,7 +819,7 @@ export default function ProxyResourcesTable({
<div className="mt-4"> <div className="mt-4">
<DataTablePagination <DataTablePagination
table={proxyTable} table={proxyTable}
onPageSizeChange={handleProxyPageSizeChange} onPageSizeChange={setProxyPageSize}
/> />
</div> </div>
</CardContent> </CardContent>

View File

@@ -130,8 +130,10 @@ export default function UserDevicesTable({ userClients }: ClientTableProps) {
}); });
}) })
.then(() => { .then(() => {
router.refresh(); startTransition(() => {
setIsDeleteModalOpen(false); router.refresh();
setIsDeleteModalOpen(false);
});
}); });
}; };