From 93febff5e8b6fa5895ce0f15fd76486a90816fa8 Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 27 Feb 2022 16:15:11 -0500 Subject: [PATCH] Include footer on the table --- .../Model/DataValidationException.php | 4 +- .../Api/Application/Users/UserController.php | 16 +---- resources/scripts/api/http.ts | 25 +++++++ ...sersContainerV2.tsx => UsersContainer.tsx} | 69 ++++++++++++++----- resources/scripts/routers/AdminRouter.tsx | 4 +- 5 files changed, 84 insertions(+), 34 deletions(-) rename resources/scripts/components/admin/users/{UsersContainerV2.tsx => UsersContainer.tsx} (64%) diff --git a/app/Exceptions/Model/DataValidationException.php b/app/Exceptions/Model/DataValidationException.php index 1118081e5..bcdce68f0 100644 --- a/app/Exceptions/Model/DataValidationException.php +++ b/app/Exceptions/Model/DataValidationException.php @@ -43,7 +43,7 @@ class DataValidationException extends PterodactylException implements HttpExcept * * @return int */ - public function getStatusCode() + public function getStatusCode(): int { return 500; } @@ -51,7 +51,7 @@ class DataValidationException extends PterodactylException implements HttpExcept /** * @return array */ - public function getHeaders() + public function getHeaders(): array { return []; } diff --git a/app/Http/Controllers/Api/Application/Users/UserController.php b/app/Http/Controllers/Api/Application/Users/UserController.php index 2b0d20508..570c54fc6 100644 --- a/app/Http/Controllers/Api/Application/Users/UserController.php +++ b/app/Http/Controllers/Api/Application/Users/UserController.php @@ -20,23 +20,15 @@ use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; class UserController extends ApplicationApiController { - private UserCreationService $creationService; - private UserDeletionService $deletionService; - private UserUpdateService $updateService; - /** * UserController constructor. */ public function __construct( - UserCreationService $creationService, - UserDeletionService $deletionService, - UserUpdateService $updateService + protected UserCreationService $creationService, + protected UserDeletionService $deletionService, + protected UserUpdateService $updateService ) { parent::__construct(); - - $this->creationService = $creationService; - $this->deletionService = $deletionService; - $this->updateService = $updateService; } /** @@ -84,8 +76,6 @@ class UserController extends ApplicationApiController * Revocation errors are returned under the 'revocation_errors' key in the response * meta. If there are no errors this is an empty array. * - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function update(UpdateUserRequest $request, User $user): array diff --git a/resources/scripts/api/http.ts b/resources/scripts/api/http.ts index 5189a3e68..0558555db 100644 --- a/resources/scripts/api/http.ts +++ b/resources/scripts/api/http.ts @@ -1,5 +1,6 @@ import axios, { AxiosInstance } from 'axios'; import { store } from '@/state'; +import { Model } from '@/api/definitions'; const http: AxiosInstance = axios.create({ timeout: 20000, @@ -88,6 +89,20 @@ export interface FractalResponseList { data: FractalResponseData[]; } +export interface FractalPaginatedResponse extends FractalResponseList { + meta: { + pagination: { + total: number; + count: number; + /* eslint-disable camelcase */ + per_page: number; + current_page: number; + total_pages: number; + /* eslint-enable camelcase */ + }; + }; +} + export interface PaginatedResult { items: T[]; pagination: PaginationDataSet; @@ -150,3 +165,13 @@ export const withQueryBuilderParams = (data?: QueryBuilderParams): Record Model; + +export const toPaginatedSet = ( + response: FractalPaginatedResponse, + transformer: T, +): PaginatedResult> => ({ + items: response.data.map(transformer) as ReturnType[], + pagination: getPaginationSet(response.meta.pagination), + }); diff --git a/resources/scripts/components/admin/users/UsersContainerV2.tsx b/resources/scripts/components/admin/users/UsersContainer.tsx similarity index 64% rename from resources/scripts/components/admin/users/UsersContainerV2.tsx rename to resources/scripts/components/admin/users/UsersContainer.tsx index 32f41ed52..8a37813d3 100644 --- a/resources/scripts/components/admin/users/UsersContainerV2.tsx +++ b/resources/scripts/components/admin/users/UsersContainer.tsx @@ -1,5 +1,12 @@ import React, { Fragment, useEffect, useState } from 'react'; -import http from '@/api/http'; +import http, { + FractalPaginatedResponse, + PaginatedResult, + PaginationDataSet, + QueryBuilderParams, + toPaginatedSet, + withQueryBuilderParams, +} from '@/api/http'; import { UUID } from '@/api/definitions'; import { Transformers, User } from '@definitions/admin'; import { Transition } from '@/components/elements/transitions'; @@ -7,31 +14,58 @@ import { LockOpenIcon, PlusIcon, SupportIcon, TrashIcon } from '@heroicons/react import { Button } from '@/components/elements/button/index'; import { Checkbox, InputField } from '@/components/elements/inputs'; import UserTableRow from '@/components/admin/users/UserTableRow'; +import useSWR, { SWRConfiguration, SWRResponse } from 'swr'; +import { AxiosError } from 'axios'; -const UsersContainerV2 = () => { - const [ users, setUsers ] = useState([]); +const useGetUsers = ( + params?: QueryBuilderParams<'id' | 'email' | 'uuid'>, + config?: SWRConfiguration, +): SWRResponse, AxiosError> => { + return useSWR>([ '/api/application/users', params ], async () => { + const { data } = await http.get( + '/api/application/users', + { params: withQueryBuilderParams(params) }, + ); + + return toPaginatedSet(data, Transformers.toUser); + }, config || { revalidateOnMount: true, revalidateOnFocus: false }); +}; + +const PaginationFooter = ({ pagination, span }: { span: number; pagination: PaginationDataSet }) => { + const start = (pagination.currentPage - 1) * pagination.perPage; + const end = ((pagination.currentPage - 1) * pagination.perPage) + pagination.count; + + return ( + + + +

+ Showing {Math.max(start, 1)} to  + {end} of  + {pagination.total} results. +

+ + + + ); +}; + +const UsersContainer = () => { + const { data: users } = useGetUsers(); const [ selected, setSelected ] = useState([]); useEffect(() => { document.title = 'Admin | Users'; }, []); - useEffect(() => { - http.get('/api/application/users') - .then(({ data }) => { - setUsers(data.data.map(Transformers.toUser)); - }) - .catch(console.error); - }, []); - const onRowChange = (user: User, checked: boolean) => { setSelected((state) => { return checked ? [ ...state, user.uuid ] : selected.filter((uuid) => uuid !== user.uuid); }); }; - const selectAllChecked = users.length > 0 && selected.length > 0; - const onSelectAll = () => setSelected((state) => state.length > 0 ? [] : users.map(({ uuid }) => uuid)); + const selectAllChecked = users && users.items.length > 0 && selected.length > 0; + const onSelectAll = () => setSelected((state) => state.length > 0 ? [] : users?.items.map(({ uuid }) => uuid) || []); return (
@@ -44,7 +78,7 @@ const UsersContainerV2 = () => {
@@ -61,7 +95,7 @@ const UsersContainerV2 = () => {
@@ -87,7 +121,7 @@ const UsersContainerV2 = () => { - {users.map(user => ( + {users?.items.map(user => ( { /> ))} + {users && }
); }; -export default UsersContainerV2; +export default UsersContainer; diff --git a/resources/scripts/routers/AdminRouter.tsx b/resources/scripts/routers/AdminRouter.tsx index d006c0551..d27471215 100644 --- a/resources/scripts/routers/AdminRouter.tsx +++ b/resources/scripts/routers/AdminRouter.tsx @@ -45,7 +45,7 @@ import { import CollapsedIcon from '@/assets/images/pterodactyl.svg'; import Sidebar from '@/components/admin/Sidebar'; import useUserPersistedState from '@/plugins/useUserPersistedState'; -import UsersContainerV2 from '@/components/admin/users/UsersContainerV2'; +import UsersContainer from '@/components/admin/users/UsersContainer'; const AdminRouter = ({ location, match }: RouteComponentProps) => { const email = useStoreState((state: State) => state.user.data!.email); @@ -132,7 +132,7 @@ const AdminRouter = ({ location, match }: RouteComponentProps) => { - +