gitscope
← marzneshin / Diff · 578311c → 578311c
Changes 578311c..578311c
Patch commit
diff --git a/dashboard/src/components/entity-table/entity-table-provider.ts b/dashboard/src/components/entity-table/entity-table-provider.ts
new file mode 100644
index 0000000..2204475
--- /dev/null
+++ b/dashboard/src/components/entity-table/entity-table-provider.ts
@@ -0,0 +1,18 @@
+import { createContext, useContext } from "react";
+import { UseFilteringReturn } from "./hooks";
+import { Table } from "@tanstack/react-table";
+
+interface EntityTableContextProps<TData> {
+ table: Table<TData>
+ data: TData[]
+ filtering: UseFilteringReturn
+}
+
+export const EntityTableContext = createContext<EntityTableContextProps<any> | null>(null);
+
+export const useEntityTableContext = () => {
+ const ctx = useContext(EntityTableContext);
+ if (!ctx)
+ throw new Error('EntityTable.* component must be rendered as child of EntityTable');
+ return ctx;
+}
diff --git a/dashboard/src/components/entity-table/hooks/index.ts b/dashboard/src/components/entity-table/hooks/index.ts
new file mode 100644
index 0000000..6ae7e1b
--- /dev/null
+++ b/dashboard/src/components/entity-table/hooks/index.ts
@@ -0,0 +1,5 @@
+export * from './use-filtering'
+export * from './use-pagination'
+export * from './use-dialog'
+export * from './use-row-selection'
+export * from './use-entity-query'
diff --git a/dashboard/src/components/entity-table/hooks/use-dialog.ts b/dashboard/src/components/entity-table/hooks/use-dialog.ts
new file mode 100644
index 0000000..eaee78b
--- /dev/null
+++ b/dashboard/src/components/entity-table/hooks/use-dialog.ts
@@ -0,0 +1,9 @@
+import { useState } from "react";
+
+export interface UseDialogProps<T> {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ entity: T | null;
+}
+
+export const useDialog = () => useState<boolean>(false)
diff --git a/dashboard/src/components/entity-table/hooks/use-entity-query.ts b/dashboard/src/components/entity-table/hooks/use-entity-query.ts
new file mode 100644
index 0000000..04ddca5
--- /dev/null
+++ b/dashboard/src/components/entity-table/hooks/use-entity-query.ts
@@ -0,0 +1,17 @@
+import { z } from "zod";
+
+const UseEntityQueryPropsSchema = z.object({
+ page: z.number().gte(1),
+ size: z.number().gte(1).lte(100)
+})
+
+export type UseEntityQueryProps = z.infer<typeof UseEntityQueryPropsSchema>
+
+export type EntityQueryKeyType = { queryKey: [string, number, number] }
+
+interface FetchEntityResult<T> {
+ pageCount: number
+ entity: T[]
+}
+
+export type FetchEntityReturn<T> = Promise<FetchEntityResult<T>>
diff --git a/dashboard/src/components/entity-table/hooks/use-filtering.ts b/dashboard/src/components/entity-table/hooks/use-filtering.ts
new file mode 100644
index 0000000..8a09960
--- /dev/null
+++ b/dashboard/src/components/entity-table/hooks/use-filtering.ts
@@ -0,0 +1,16 @@
+import { ColumnFiltersState, OnChangeFn } from "@tanstack/react-table"
+import { useState } from "react"
+
+export interface UseFilteringType {
+ column: string
+}
+
+export interface UseFilteringReturn extends UseFilteringType {
+ columnFilters: ColumnFiltersState
+ setColumnFilters: OnChangeFn<ColumnFiltersState>
+}
+
+export const useFiltering = ({ column }: UseFilteringType): UseFilteringReturn => {
+ const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
+ return { setColumnFilters, columnFilters, column }
+}
diff --git a/dashboard/src/components/entity-table/hooks/use-pagination.ts b/dashboard/src/components/entity-table/hooks/use-pagination.ts
new file mode 100644
index 0000000..d0f3965
--- /dev/null
+++ b/dashboard/src/components/entity-table/hooks/use-pagination.ts
@@ -0,0 +1,17 @@
+import { useState } from "react";
+
+export function usePagination() {
+ const [pagination, setPagination] = useState({
+ pageSize: 10,
+ pageIndex: 1,
+ });
+ const { pageSize, pageIndex } = pagination;
+
+ return {
+ pageSize,
+ pageIndex,
+ onPaginationChange: setPagination,
+ pagination,
+ skip: pageSize * pageIndex,
+ };
+}
diff --git a/dashboard/src/components/entity-table/hooks/use-row-selection.ts b/dashboard/src/components/entity-table/hooks/use-row-selection.ts
new file mode 100644
index 0000000..106f763
--- /dev/null
+++ b/dashboard/src/components/entity-table/hooks/use-row-selection.ts
@@ -0,0 +1,6 @@
+import { OnChangeFn, RowSelectionState } from "@tanstack/react-table"
+
+export interface UseRowSelectionReturn {
+ setSelectedRow: OnChangeFn<RowSelectionState>
+ selectedRow: RowSelectionState
+}
diff --git a/dashboard/src/components/entity-table/index.tsx b/dashboard/src/components/entity-table/index.tsx
index a01d594..df1a825 100644
--- a/dashboard/src/components/entity-table/index.tsx
+++ b/dashboard/src/components/entity-table/index.tsx
@@ -1,45 +1,46 @@
import { FC, useState } from "react";
-import { DataTable } from "@marzneshin/components";
-
-interface MutationDialogProps<T> {
- open: boolean;
- onOpenChange: (open: boolean) => void;
- entity: T | null;
-}
-
-interface DeleteConfirmationDialogProps<T> {
- open: boolean;
- onOpenChange: (open: boolean) => void;
- entity: T | null;
-}
-
-interface SettingsDialogProps<T> {
- open: boolean;
- onOpenChange: (open: boolean) => void;
- entity: T | null;
-}
+import { Button, DataTableViewOptions } from "@marzneshin/components";
+import { EntityTableContext } from "./entity-table-provider";
+import { EntityDataTable } from "./table"
+import { DataTablePagination } from "./table-pagination"
+import { SortingState, VisibilityState, getCoreRowModel, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, useReactTable } from "@tanstack/react-table";
+import {
+ UseRowSelectionReturn,
+ UseDialogProps,
+ useFiltering,
+ useDialog,
+ usePagination,
+ FetchEntityReturn,
+ EntityQueryKeyType
+} from "./hooks";
+import { useTranslation } from "react-i18next";
+import { TableFiltering } from "./table-filtering";
+import { useQuery } from "@tanstack/react-query";
interface EntityTableProps<T> {
- useQuery: () => { data: T[] };
- MutationDialog: FC<MutationDialogProps<T>>;
- DeleteConfirmationDialog: FC<DeleteConfirmationDialogProps<T>>;
- SettingsDialog: FC<SettingsDialogProps<T | any>>;
- columns: any;
+ fetchEntity: ({ queryKey }: EntityQueryKeyType) => FetchEntityReturn<T>;
+ MutationDialog: FC<UseDialogProps<T>>;
+ DeleteConfirmationDialog: FC<UseDialogProps<T>>;
+ SettingsDialog: FC<UseDialogProps<T | any>>;
+ columnsFn: any;
filteredColumn: string;
+ entityKey: string
+ rowSelection?: UseRowSelectionReturn
}
export function EntityTable<T>({
- useQuery,
+ fetchEntity,
MutationDialog,
DeleteConfirmationDialog,
SettingsDialog,
- columns,
+ columnsFn,
filteredColumn,
+ rowSelection,
+ entityKey,
}: EntityTableProps<T>) {
- const { data } = useQuery();
- const [mutationDialogOpen, setMutationDialogOpen] = useState<boolean>(false);
- const [deleteDialogOpen, setDeleteDialogOpen] = useState<boolean>(false);
- const [settingsDialogOpen, setSettingsDialogOpen] = useState<boolean>(false);
+ const [mutationDialogOpen, setMutationDialogOpen] = useDialog();
+ const [deleteDialogOpen, setDeleteDialogOpen] = useDialog();
+ const [settingsDialogOpen, setSettingsDialogOpen] = useDialog();
const [selectedEntity, selectEntity] = useState<T | null>(null);
const onEdit = (entity: T) => {
@@ -62,8 +63,45 @@ export function EntityTable<T>({
setSettingsDialogOpen(true);
};
+ const { t } = useTranslation()
+ const filtering = useFiltering({ column: filteredColumn })
+ const [sorting, setSorting] = useState<SortingState>([])
+ const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
+ const { onPaginationChange, pageIndex, pageSize } = usePagination();
+
+ const { data } = useQuery({
+ queryFn: fetchEntity,
+ queryKey: [entityKey, pageIndex, pageSize],
+ initialData: { entity: [], pageCount: 1 }
+ });
+
+ const columns = columnsFn({ onEdit, onDelete, onOpen })
+ const table = useReactTable({
+ data: data.entity,
+ columns,
+ manualPagination: true,
+ pageCount: data.pageCount + 1,
+ autoResetPageIndex: false,
+ onPaginationChange,
+ onSortingChange: setSorting,
+ onColumnFiltersChange: filtering.setColumnFilters,
+ getCoreRowModel: getCoreRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ getFilteredRowModel: getFilteredRowModel(),
+ onColumnVisibilityChange: setColumnVisibility,
+ onRowSelectionChange: rowSelection && rowSelection.setSelectedRow,
+ state: {
+ sorting,
+ columnFilters: filtering.columnFilters,
+ columnVisibility,
+ pagination: { pageIndex: pageIndex - 1, pageSize },
+ rowSelection: rowSelection ? rowSelection.selectedRow : {},
+ },
+ })
+
return (
- <div>
+ <EntityTableContext.Provider value={{ table, data: data.entity, filtering }}>
<SettingsDialog
open={settingsDialogOpen}
onOpenChange={setSettingsDialogOpen}
@@ -79,13 +117,23 @@ export function EntityTable<T>({
onOpenChange={setMutationDialogOpen}
entity={selectedEntity}
/>
- <DataTable
- columns={columns({ onEdit, onDelete, onOpen })}
- data={data}
- filteredColumn={filteredColumn}
- onCreate={onCreate}
- onOpen={onOpen}
- />
- </div>
+
+ <div className="flex flex-col">
+ <div className="flex items-center py-4">
+ <TableFiltering />
+ <DataTableViewOptions table={table} />
+ {onCreate && (<Button onClick={onCreate}>{t('create')}</Button>)}
+ </div>
+ <div className="w-full rounded-md border">
+ <EntityDataTable
+ columns={columns}
+ onRowClick={onOpen}
+ />
+ <DataTablePagination />
+ </div>
+ </div>
+ </EntityTableContext.Provider>
);
}
+
+export * from './hooks'
diff --git a/dashboard/src/components/entity-table/table-filtering.tsx b/dashboard/src/components/entity-table/table-filtering.tsx
new file mode 100644
index 0000000..409dac3
--- /dev/null
+++ b/dashboard/src/components/entity-table/table-filtering.tsx
@@ -0,0 +1,22 @@
+
+import { FC } from 'react'
+import { Input } from '@marzneshin/components';
+import { useEntityTableContext } from './entity-table-provider';
+import { useTranslation } from 'react-i18next';
+
+interface TableFilteringProps { }
+
+export const TableFiltering: FC<TableFilteringProps> = () => {
+ const { table, filtering } = useEntityTableContext()
+ const { t } = useTranslation()
+ return (
+ <Input
+ placeholder={t('table.filter-placeholder', { name: filtering.column })}
+ value={(table.getColumn(filtering.column)?.getFilterValue() as string) ?? ""}
+ onChange={(event) =>
+ table.getColumn(filtering.column)?.setFilterValue(event.target.value)
+ }
+ className="max-w-sm"
+ />
+ );
+}
diff --git a/dashboard/src/components/entity-table/table-pagination.tsx b/dashboard/src/components/entity-table/table-pagination.tsx
new file mode 100644
index 0000000..bc8088f
--- /dev/null
+++ b/dashboard/src/components/entity-table/table-pagination.tsx
@@ -0,0 +1,96 @@
+import {
+ ChevronLeftIcon,
+ ChevronRightIcon,
+ DoubleArrowLeftIcon,
+ DoubleArrowRightIcon,
+} from "@radix-ui/react-icons"
+import {
+ Button,
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@marzneshin/components"
+import { useTranslation } from "react-i18next"
+import { useEntityTableContext } from "./entity-table-provider";
+
+export interface DataTablePaginationProps { }
+
+export function DataTablePagination() {
+ const { t } = useTranslation();
+ const { table } = useEntityTableContext()
+ return (
+ <div className="flex justify-between items-center p-2 w-full">
+ {table.options.onRowSelectionChange &&
+ <div className="flex-1 text-sm text-muted-foreground">
+ {table.getFilteredSelectedRowModel().rows.length} {"/"}
+ {table.getFilteredRowModel().rows.length} {t('table.selected')}
+ </div>}
+ <div className="flex justify-between items-center space-x-6 lg:space-x-8 w-full">
+ <div className="flex items-center space-x-2">
+ <p className="text-sm font-medium">{t('table.row-per-page')}</p>
+ <Select
+ value={`${table.getState().pagination.pageSize}`}
+ onValueChange={(value) => {
+ table.setPageSize(Number(value))
+ }}
+ >
+ <SelectTrigger className="h-8 w-[70px]">
+ <SelectValue placeholder={table.getState().pagination.pageSize} />
+ </SelectTrigger>
+ <SelectContent side="top">
+ {[10, 20, 50, 100].map((pageSize) => (
+ <SelectItem key={pageSize} value={`${pageSize}`}>
+ {pageSize}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ </div>
+ <div className="flex justify-center items-center text-sm font-medium w-[100px]">
+ Page {table.getState().pagination.pageIndex + 1} of{" "}
+ {table.getPageCount() - 1}
+ </div>
+ <div className="flex items-center space-x-2">
+ <Button
+ variant="outline"
+ className="hidden p-0 w-8 h-8 lg:flex"
+ onClick={() => table.setPageIndex(1)}
+ disabled={!table.getCanPreviousPage()}
+ >
+ <span className="sr-only">Go to first page</span>
+ <DoubleArrowLeftIcon className="w-4 h-4" />
+ </Button>
+ <Button
+ variant="outline"
+ className="p-0 w-8 h-8"
+ onClick={() => table.previousPage()}
+ disabled={!table.getCanPreviousPage()}
+ >
+ <span className="sr-only">Go to previous page</span>
+ <ChevronLeftIcon className="w-4 h-4" />
+ </Button>
+ <Button
+ variant="outline"
+ className="p-0 w-8 h-8"
+ onClick={() => table.nextPage()}
+ disabled={!table.getCanNextPage()}
+ >
+ <span className="sr-only">Go to next page</span>
+ <ChevronRightIcon className="w-4 h-4" />
+ </Button>
+ <Button
+ variant="outline"
+ className="hidden p-0 w-8 h-8 lg:flex"
+ onClick={() => table.setPageIndex(table.getPageCount() - 1)}
+ disabled={!table.getCanNextPage()}
+ >
+ <span className="sr-only">Go to last page</span>
+ <DoubleArrowRightIcon className="w-4 h-4" />
+ </Button>
+ </div>
+ </div>
+ </div>
+ )
+}
diff --git a/dashboard/src/components/entity-table/table.tsx b/dashboard/src/components/entity-table/table.tsx
new file mode 100644
index 0000000..d0acb8b
--- /dev/null
+++ b/dashboard/src/components/entity-table/table.tsx
@@ -0,0 +1,74 @@
+import {
+ ColumnDef,
+ flexRender,
+} from "@tanstack/react-table"
+
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@marzneshin/components"
+
+import { useTranslation } from "react-i18next"
+import { useEntityTableContext } from "./entity-table-provider"
+
+interface DataTableProps<TData, TValue> {
+ columns: ColumnDef<TData, TValue>[]
+ onRowClick?: (object: TData) => void
+}
+
+export function EntityDataTable<TData, TValue>({
+ columns,
+ onRowClick,
+}: DataTableProps<TData, TValue>) {
+ const { t } = useTranslation();
+ const { table } = useEntityTableContext()
+ return (
+ <Table>
+ <TableHeader>
+ {table.getHeaderGroups().map((headerGroup) => (
+ <TableRow key={headerGroup.id}>
+ {headerGroup.headers.map((header) => {
+ return (
+ <TableHead key={header.id}>
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext()
+ )}
+ </TableHead>
+ )
+ })}
+ </TableRow>
+ ))}
+ </TableHeader>
+ <TableBody>
+ {table.getRowModel().rows?.length ? (
+ table.getRowModel().rows.map((row) => (
+ <TableRow
+ key={row.id}
+ data-state={row.getIsSelected() && "selected"}
+ onClick={() => onRowClick && onRowClick(row.original)}
+ >
+ {row.getVisibleCells().map((cell) => (
+ <TableCell key={cell.id}>
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+ </TableCell>
+ ))}
+ </TableRow>
+ ))
+ ) : (
+ <TableRow>
+ <TableCell colSpan={columns.length} className="h-24 text-center">
+ {t('table.no-result')}
+ </TableCell>
+ </TableRow>
+ )}
+ </TableBody>
+ </Table>
+ )
+}
diff --git a/dashboard/src/features/hosts/tables/inbound-hosts/table.tsx b/dashboard/src/features/hosts/tables/inbound-hosts/table.tsx
index fe5ba71..58aaa95 100644
--- a/dashboard/src/features/hosts/tables/inbound-hosts/table.tsx
+++ b/dashboard/src/features/hosts/tables/inbound-hosts/table.tsx
@@ -57,11 +57,8 @@ export function InboundHostsDataTable<TData, TValue>({
setSelectedInbound,
}: InboundHostsDataTableProps<TData, TValue>) {
const [sorting, setSorting] = useState<SortingState>([])
- const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>(
- []
- )
- const [columnVisibility, setColumnVisibility] =
- useState<VisibilityState>({})
+ const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
+ const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
const table = useReactTable({
data,
columns,
diff --git a/dashboard/src/features/nodes/services/nodes.query.ts b/dashboard/src/features/nodes/services/nodes.query.ts
index 6d284f6..37d9185 100644
--- a/dashboard/src/features/nodes/services/nodes.query.ts
+++ b/dashboard/src/features/nodes/services/nodes.query.ts
@@ -1,19 +1,27 @@
import { NodeType } from "@marzneshin/features/nodes";
import { useQuery } from "@tanstack/react-query";
import { fetch } from "@marzneshin/utils";
+import {
+ EntityQueryKeyType,
+ UseEntityQueryProps
+} from "@marzneshin/components";
-export async function fetchNodes(): Promise<NodeType[]> {
- return fetch('/nodes').then((result) => {
- return result.items;
+export async function fetchNodes({ queryKey }: EntityQueryKeyType): Promise<{ entity: NodeType[], pageCount: number }> {
+ return fetch(`/nodes?page=${queryKey[1]}&size=${queryKey[2]}`).then((result) => {
+ return {
+ entity: result.items,
+ pageCount: result.pages,
+ };
});
}
export const NodesQueryFetchKey = "nodes";
-export const useNodesQuery = () => {
+
+export const useNodesQuery = ({ page, size }: UseEntityQueryProps) => {
return useQuery({
- queryKey: [NodesQueryFetchKey],
+ queryKey: [NodesQueryFetchKey, page, size],
queryFn: fetchNodes,
- initialData: []
+ initialData: { entity: [], pageCount: 0 },
})
}
diff --git a/dashboard/src/features/nodes/tables/table.tsx b/dashboard/src/features/nodes/tables/table.tsx
index f857687..cd39ad8 100644
--- a/dashboard/src/features/nodes/tables/table.tsx
+++ b/dashboard/src/features/nodes/tables/table.tsx
@@ -1,8 +1,8 @@
import { FC } from "react";
import {
- useNodesQuery,
columns,
- NodesSettingsDialog
+ NodesSettingsDialog,
+ fetchNodes,
} from '@marzneshin/features/nodes';
import { MutationDialog } from "../dialogs/mutation.dialog";
import { NodesDeleteConfirmationDialog } from "../dialogs/delete-confirmation.dialog";
@@ -11,12 +11,13 @@ import { EntityTable } from "@marzneshin/components";
export const NodesTable: FC = () => {
return (
<EntityTable
- useQuery={useNodesQuery}
+ fetchEntity={fetchNodes}
MutationDialog={MutationDialog}
DeleteConfirmationDialog={NodesDeleteConfirmationDialog}
SettingsDialog={NodesSettingsDialog}
- columns={columns}
+ columnsFn={columns}
filteredColumn="name"
+ entityKey="nodes"
/>
)
}
diff --git a/dashboard/src/features/services/services/services.query.ts b/dashboard/src/features/services/services/services.query.ts
index 95e8260..2e99511 100644
--- a/dashboard/src/features/services/services/services.query.ts
+++ b/dashboard/src/features/services/services/services.query.ts
@@ -1,6 +1,11 @@
-import { useQuery } from "@tanstack/react-query";
import { fetch } from "@marzneshin/utils";
import { ServiceType } from "../types";
+import {
+ EntityQueryKeyType,
+ FetchEntityReturn,
+ UseEntityQueryProps
+} from "@marzneshin/components";
+import { useQuery } from "@tanstack/react-query";
interface ServiceResponse {
id: number
@@ -11,24 +16,27 @@ interface ServiceResponse {
inbound_ids: number[]
}
-export async function fetchServices(): Promise<ServiceType[]> {
- return fetch('/services').then((result) => {
+export async function fetchServices({ queryKey }: EntityQueryKeyType): FetchEntityReturn<ServiceType> {
+ return fetch(`/services?page=${queryKey[1]}&size=${queryKey[2]}`).then((result) => {
const services: ServiceType[] = result.items.map((fetchedService: ServiceResponse) => {
const service: ServiceType = fetchedService;
service.users = fetchedService.user_ids
service.inbounds = fetchedService.inbound_ids
return service
})
- return services;
+ return {
+ entity: services,
+ pageCount: result.pages
+ };
});
}
export const ServicesQueryFetchKey = "services";
-export const useServicesQuery = () => {
+export const useServicesQuery = ({ page, size }: UseEntityQueryProps) => {
return useQuery({
- queryKey: [ServicesQueryFetchKey],
+ queryKey: [ServicesQueryFetchKey, page, size],
queryFn: fetchServices,
- initialData: []
+ initialData: { entity: [], pageCount: 0 }
})
}
diff --git a/dashboard/src/features/services/tables/services/index.tsx b/dashboard/src/features/services/tables/services/index.tsx
index d8653fb..c225d01 100644
--- a/dashboard/src/features/services/tables/services/index.tsx
+++ b/dashboard/src/features/services/tables/services/index.tsx
@@ -1,8 +1,9 @@
import { FC } from "react";
import {
- useServicesQuery,
MutationDialog,
- ServicesDeleteConfirmationDialog
+ ServicesDeleteConfirmationDialog,
+ ServicesQueryFetchKey,
+ fetchServices
} from '@marzneshin/features/services';
import { columns } from "./columns";
import { ServiceSettingsDialog } from "../../dialogs/settings.dialog";
@@ -11,12 +12,13 @@ import { EntityTable } from "@marzneshin/components";
export const ServicesTable: FC = () => {
return (
<EntityTable
- useQuery={useServicesQuery}
+ fetchEntity={fetchServices}
DeleteConfirmationDialog={ServicesDeleteConfirmationDialog}
SettingsDialog={ServiceSettingsDialog}
MutationDialog={MutationDialog}
- columns={columns}
+ columnsFn={columns}
filteredColumn="name"
+ entityKey={ServicesQueryFetchKey}
/>
)
}
diff --git a/dashboard/src/features/users/dialogs/mutation/fields/services.tsx b/dashboard/src/features/users/dialogs/mutation/fields/services.tsx
index 28e0f2b..e5c43f4 100644
--- a/dashboard/src/features/users/dialogs/mutation/fields/services.tsx
+++ b/dashboard/src/features/users/dialogs/mutation/fields/services.tsx
@@ -24,7 +24,7 @@ export const ServicesField: FC<ServicesFieldProps> = ({
setServices
}) => {
const form = useFormContext();
- const { data } = useServicesQuery();
+ const { data } = useServicesQuery({ page: 1, size: 100 });
const { t } = useTranslation();
const handleToggle = (selectedServiceIds: string[]) => {
@@ -46,7 +46,7 @@ export const ServicesField: FC<ServicesFieldProps> = ({
type="multiple"
>
<ScrollArea className="flex flex-col justify-start px-1 h-[22rem]">
- {data.map(service => (
+ {data.entity.map(service => (
<ToggleGroupItem
key={service.id}
value={String(service.id)}
diff --git a/dashboard/src/features/users/services/users.query.ts b/dashboard/src/features/users/services/users.query.ts
index 7300a1f..1a3809d 100644
--- a/dashboard/src/features/users/services/users.query.ts
+++ b/dashboard/src/features/users/services/users.query.ts
@@ -1,29 +1,33 @@
import { useQuery } from "@tanstack/react-query";
import { fetch } from "@marzneshin/utils";
import { UserType } from "../types";
+import { EntityQueryKeyType, FetchEntityReturn, UseEntityQueryProps } from "@marzneshin/components";
interface UserResponse extends UserType {
service_ids: number[]
services: any[]
}
-export async function fetchUsers(): Promise<UserType[]> {
- return fetch('/users').then((result) => {
+export async function fetchUsers({ queryKey }: EntityQueryKeyType): FetchEntityReturn<UserType> {
+ return fetch(`/users?page=${queryKey[1]}&size=${queryKey[2]}`).then((result) => {
const users: UserType[] = result.items.map((fetchedUser: UserResponse) => {
const user: UserType = fetchedUser;
user.services = fetchedUser.service_ids
return user
})
- return users;
+ return {
+ entity: users,
+ pageCount: result.pages
+ };
});
}
export const UsersQueryFetchKey = "users";
-export const useUsersQuery = () => {
+export const useUsersQuery = ({ page, size }: UseEntityQueryProps) => {
return useQuery({
- queryKey: [UsersQueryFetchKey],
+ queryKey: [UsersQueryFetchKey, page, size],
queryFn: fetchUsers,
- initialData: []
+ initialData: { entity: [], pageCount: 0 }
})
}
diff --git a/dashboard/src/features/users/tables/services/index.tsx b/dashboard/src/features/users/tables/services/index.tsx
index 1e200b0..1161ddb 100644
--- a/dashboard/src/features/users/tables/services/index.tsx
+++ b/dashboard/src/features/users/tables/services/index.tsx
@@ -17,9 +17,10 @@ interface UserServicesTableProps {
user: UserType
}
+// TODO: Implement pagination for row selectable with appliedable action table
export const UserServicesTable: FC<UserServicesTableProps> = ({ user }) => {
const { mutate: updateUser } = useUsersUpdateMutation();
- const { data } = useServicesQuery();
+ const { data } = useServicesQuery({ page: 1, size: 100 });
const [selectedService, setSelectedService] = useState<{ [key: number]: boolean }>({})
const { t } = useTranslation()
@@ -27,7 +28,7 @@ export const UserServicesTable: FC<UserServicesTableProps> = ({ user }) => {
setSelectedService((prevSelected) => {
const updatedSelected: RowSelectionState = { ...prevSelected };
user.services.forEach((serviceId) => {
- data.forEach((fetchedService: ServiceType, i) => {
+ data.entity.forEach((fetchedService: ServiceType, i) => {
if (fetchedService.id === serviceId) {
updatedSelected[i] = true;
}
@@ -40,7 +41,7 @@ export const UserServicesTable: FC<UserServicesTableProps> = ({ user }) => {
const handleApply = useCallback(() => {
const selectedServiceIds = Object.keys(selectedService)
.filter(key => selectedService[parseInt(key)])
- .map(key => data[parseInt(key)].id);
+ .map(key => data.entity[parseInt(key)].id);
updateUser({ ...user, services: selectedServiceIds });
}, [data, selectedService, user, updateUser]);
@@ -50,7 +51,7 @@ export const UserServicesTable: FC<UserServicesTableProps> = ({ user }) => {
<div className="flex flex-col gap-4">
<DataTable
columns={columns}
- data={data}
+ data={data.entity}
filteredColumn='name'
selectedRow={selectedService}
setSelectedRow={setSelectedService}
diff --git a/dashboard/src/features/users/tables/users/index.tsx b/dashboard/src/features/users/tables/users/index.tsx
index b19e7f3..00518c2 100644
--- a/dashboard/src/features/users/tables/users/index.tsx
+++ b/dashboard/src/features/users/tables/users/index.tsx
@@ -1,10 +1,11 @@
import { FC } from "react";
import {
- useUsersQuery,
UsersMutationDialog,
UsersDeleteConfirmationDialog,
- UsersSettingsDialog
+ UsersSettingsDialog,
+ fetchUsers,
+ UsersQueryFetchKey
} from '@marzneshin/features/users';
import { columns } from './columns'
import { EntityTable } from "@marzneshin/components";
@@ -12,12 +13,13 @@ import { EntityTable } from "@marzneshin/components";
export const UsersTable: FC = () => {
return (
<EntityTable
- useQuery={useUsersQuery}
+ fetchEntity={fetchUsers}
MutationDialog={UsersMutationDialog}
DeleteConfirmationDialog={UsersDeleteConfirmationDialog}
SettingsDialog={UsersSettingsDialog}
- columns={columns}
+ columnsFn={columns}
filteredColumn="username"
+ entityKey={UsersQueryFetchKey}
/>
)
}
diff --git a/dashboard/src/routes/_dashboard/users.tsx b/dashboard/src/routes/_dashboard/users.tsx
index 11b79dd..916115c 100644
--- a/dashboard/src/routes/_dashboard/users.tsx
+++ b/dashboard/src/routes/_dashboard/users.tsx
@@ -17,7 +17,7 @@ import { FC } from 'react';
import { useTranslation } from 'react-i18next';
export const UsersPage: FC = () => {
- const { data } = useServicesQuery();
+ const { data } = useServicesQuery({ page: 1, size: 10 });
const { t } = useTranslation();
return (
<Page>
@@ -31,7 +31,7 @@ export const UsersPage: FC = () => {
<UsersTable />
</CardContent>
<CardFooter>
- {(data && data.length === 0) && (
+ {(data && data.pageCount === 0) && (
<Alert>
<ExclamationTriangleIcon className="mr-2" />
<AlertTitle className="font-semibold text-primary">{t('page.users.services-alert.title')}</AlertTitle>