diff --git a/resources/scripts/api/getServers.ts b/resources/scripts/api/getServers.ts index 8dd8ed22a..492898f70 100644 --- a/resources/scripts/api/getServers.ts +++ b/resources/scripts/api/getServers.ts @@ -1,13 +1,19 @@ import { rawDataToServerObject, Server } from '@/api/server/getServer'; import http, { getPaginationSet, PaginatedResult } from '@/api/http'; -export default (query?: string, includeAdmin?: boolean): Promise> => { +interface QueryParams { + query?: string; + page?: number; + includeAdmin?: boolean; +} + +export default ({ query, page = 1, includeAdmin = false }: QueryParams): Promise> => { return new Promise((resolve, reject) => { http.get('/api/client', { params: { - include: [ 'allocation' ], type: includeAdmin ? 'all' : undefined, 'filter[name]': query, + page, }, }) .then(({ data }) => resolve({ diff --git a/resources/scripts/components/dashboard/DashboardContainer.tsx b/resources/scripts/components/dashboard/DashboardContainer.tsx index 692ac2ed1..60c18cb12 100644 --- a/resources/scripts/components/dashboard/DashboardContainer.tsx +++ b/resources/scripts/components/dashboard/DashboardContainer.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { Server } from '@/api/server/getServer'; import getServers from '@/api/getServers'; import ServerRow from '@/components/dashboard/ServerRow'; @@ -11,15 +11,17 @@ import Switch from '@/components/elements/Switch'; import tw from 'twin.macro'; import useSWR from 'swr'; import { PaginatedResult } from '@/api/http'; +import Pagination from '@/components/elements/Pagination'; export default () => { const { clearFlashes, clearAndAddHttpError } = useFlash(); + const [ page, setPage ] = useState(1); const { rootAdmin } = useStoreState(state => state.user.data!); - const [ showAdmin, setShowAdmin ] = usePersistedState('show_all_servers', false); + const [ includeAdmin, setIncludeAdmin ] = usePersistedState('show_all_servers', false); const { data: servers, error } = useSWR>( - [ '/api/client/servers', showAdmin ], - () => getServers(undefined, showAdmin) + [ '/api/client/servers', includeAdmin, page ], + () => getServers({ includeAdmin, page }), ); useEffect(() => { @@ -32,26 +34,34 @@ export default () => { {rootAdmin &&

- {showAdmin ? 'Showing all servers' : 'Showing your servers'} + {includeAdmin ? 'Showing all servers' : 'Showing your servers'}

setShowAdmin(s => !s)} + defaultChecked={includeAdmin} + onChange={() => setIncludeAdmin(s => !s)} />
} {!servers ? : - servers.items.length > 0 ? - servers.items.map((server, index) => ( - 0 ? tw`mt-2` : undefined}/> - )) - : -

- There are no servers associated with your account. -

+ + {({ items }) => ( + items.length > 0 ? + items.map((server, index) => ( + 0 ? tw`mt-2` : undefined} + /> + )) + : +

+ There are no servers associated with your account. +

+ )} +
} ); diff --git a/resources/scripts/components/dashboard/ServerRow.tsx b/resources/scripts/components/dashboard/ServerRow.tsx index 39d2278f5..d68744c49 100644 --- a/resources/scripts/components/dashboard/ServerRow.tsx +++ b/resources/scripts/components/dashboard/ServerRow.tsx @@ -1,9 +1,8 @@ import React, { useEffect, useRef, useState } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faServer, faEthernet, faMicrochip, faMemory, faHdd } from '@fortawesome/free-solid-svg-icons'; +import { faEthernet, faHdd, faMemory, faMicrochip, faServer } from '@fortawesome/free-solid-svg-icons'; import { Link } from 'react-router-dom'; import { Server } from '@/api/server/getServer'; -import SpinnerOverlay from '@/components/elements/SpinnerOverlay'; import getServerResourceUsage, { ServerStats } from '@/api/server/getServerResourceUsage'; import { bytesToHuman, megabytesToHuman } from '@/helpers'; import tw from 'twin.macro'; diff --git a/resources/scripts/components/dashboard/search/SearchModal.tsx b/resources/scripts/components/dashboard/search/SearchModal.tsx index 1461b8013..75eff1bfd 100644 --- a/resources/scripts/components/dashboard/search/SearchModal.tsx +++ b/resources/scripts/components/dashboard/search/SearchModal.tsx @@ -57,7 +57,7 @@ export default ({ ...props }: Props) => { setSubmitting(false); clearFlashes('search'); - getServers(term) + getServers({ query: term }) .then(servers => setServers(servers.items.filter((_, index) => index < 5))) .catch(error => { console.error(error); diff --git a/resources/scripts/components/elements/Pagination.tsx b/resources/scripts/components/elements/Pagination.tsx new file mode 100644 index 000000000..1ca625fd4 --- /dev/null +++ b/resources/scripts/components/elements/Pagination.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import { PaginatedResult } from '@/api/http'; +import tw from 'twin.macro'; +import styled from 'styled-components/macro'; +import Button from '@/components/elements/Button'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faAngleDoubleLeft, faAngleDoubleRight } from '@fortawesome/free-solid-svg-icons'; + +interface RenderFuncProps { + items: T[]; + isLastPage: boolean; + isFirstPage: boolean; +} + +interface Props { + data: PaginatedResult; + showGoToLast?: boolean; + showGoToFirst?: boolean; + onPageSelect: (page: number) => void; + children: (props: RenderFuncProps) => React.ReactNode; +} + +const Block = styled(Button)` + ${tw`p-0 w-10 h-10`} + + &:not(:last-of-type) { + ${tw`mr-2`}; + } +`; + +function Pagination ({ data: { items, pagination }, onPageSelect, children }: Props) { + const isFirstPage = pagination.currentPage === 1; + const isLastPage = pagination.currentPage >= pagination.totalPages; + + const pages = []; + + // Start two spaces before the current page. If that puts us before the starting page default + // to the first page as the starting point. + const start = Math.max(pagination.currentPage - 2, 1); + const end = Math.min(pagination.totalPages, pagination.currentPage + 5); + + for (let i = start; i <= end; i++) { + pages.push(i); + } + + return ( + <> + {children({ items, isFirstPage, isLastPage })} + {(pages.length > 1) && +
+ {(pages[0] > 1 && !isFirstPage) && + onPageSelect(1)} + > + + + } + { + pages.map(i => ( + onPageSelect(i)} + > + {i} + + )) + } + {(pages[4] < pagination.totalPages && !isLastPage) && + onPageSelect(pagination.totalPages)} + > + + + } +
+ } + + ); +} + +export default Pagination;