import { debounce } from 'debounce'; import type { ChangeEvent, MouseEvent, ReactNode } from 'react'; import { useCallback, useState } from 'react'; import tw, { styled } from 'twin.macro'; import type { ListContext as TableHooks } from '@/api/admin'; import type { PaginatedResult, PaginationDataSet } from '@/api/http'; import { TableCheckbox } from '@/components/admin/AdminCheckbox'; import Input from '@/components/elements/Input'; import InputSpinner from '@/components/elements/InputSpinner'; import Spinner from '@/components/elements/Spinner'; export function useTableHooks(initialState?: T | (() => T)): TableHooks { const [page, setPage] = useState(1); const [filters, setFilters] = useState(initialState || null); const [sort, setSortState] = useState(null); const [sortDirection, setSortDirection] = useState(false); const setSort = (newSort: string | null) => { if (sort === newSort) { setSortDirection(!sortDirection); } else { setSortState(newSort); setSortDirection(false); } }; return { page, setPage, filters, setFilters, sort, setSort, sortDirection, setSortDirection }; } export const TableHeader = ({ name, onClick, direction, }: { name?: string; onClick?: (e: MouseEvent) => void; direction?: number | null; }) => { if (!name) { return ; } return ( {name} {direction !== undefined ? (
{direction === null || direction === 1 ? ( ) : null} {direction === null || direction === 2 ? ( ) : null}
) : null}
); }; export const TableHead = ({ children }: { children: ReactNode }) => { return ( {children} ); }; export const TableBody = ({ children }: { children: ReactNode }) => { return {children}; }; export const TableRow = ({ children }: { children: ReactNode }) => { return {children}; }; interface Props { data?: PaginatedResult; onPageSelect: (page: number) => void; children: ReactNode; } const PaginationButton = styled.button<{ active?: boolean }>` ${tw`relative items-center px-3 py-1 -ml-px text-sm font-normal leading-5 transition duration-150 ease-in-out border border-neutral-500 focus:z-10 focus:outline-none focus:border-primary-300 inline-flex`}; ${props => props.active ? tw`bg-neutral-500 text-neutral-50` : tw`bg-neutral-600 text-neutral-200 hover:text-neutral-50`}; `; const PaginationArrow = styled.button` ${tw`relative inline-flex items-center px-1 py-1 text-sm font-medium leading-5 transition duration-150 ease-in-out border border-neutral-500 bg-neutral-600 text-neutral-400 hover:text-neutral-50 focus:z-10 focus:outline-none focus:border-primary-300`}; &:disabled { ${tw`bg-neutral-700`} } &:hover:disabled { ${tw`text-neutral-400 cursor-default`}; } `; export function Pagination({ data, onPageSelect, children }: Props) { let pagination: PaginationDataSet; if (data === undefined) { pagination = { total: 0, count: 0, perPage: 0, currentPage: 1, totalPages: 1, }; } else { pagination = data.pagination; } const setPage = (page: number) => { if (page < 1 || page > pagination.totalPages) { return; } onPageSelect(page); }; const isFirstPage = pagination.currentPage === 1; const isLastPage = pagination.currentPage >= pagination.totalPages; const pages = []; if (pagination.totalPages < 7) { for (let i = 1; i <= pagination.totalPages; i++) { pages.push(i); } } else { // Don't ask me how this works, all I know is that this code will always have 7 items in the pagination, // and keeps the current page centered if it is not too close to the start or end. let start = Math.max(pagination.currentPage - 3, 1); const end = Math.min( pagination.totalPages, pagination.currentPage + (pagination.currentPage < 4 ? 7 - pagination.currentPage : 3), ); while (start !== 1 && end - start !== 6) { start--; } for (let i = start; i <= end; i++) { pages.push(i); } } return ( <> {children}

Showing{' '} {(pagination.currentPage - 1) * pagination.perPage + (pagination.total > 0 ? 1 : 0)} {' '} to{' '} {(pagination.currentPage - 1) * pagination.perPage + pagination.count} {' '} of {pagination.total} results

{isFirstPage && isLastPage ? null : (
)}
); } export const Loading = () => { return (
); }; export const NoItems = ({ className }: { className?: string }) => { return (
{'No

No items could be found, it's almost like they are hiding.

); }; interface Params { checked: boolean; onSelectAllClick: (e: ChangeEvent) => void; onSearch?: (query: string) => Promise; children: ReactNode; } export const ContentWrapper = ({ checked, onSelectAllClick, onSearch, children }: Params) => { const [loading, setLoading] = useState(false); const [inputText, setInputText] = useState(''); const search = useCallback( debounce((query: string) => { if (onSearch === undefined) { return; } setLoading(true); onSearch(query).then(() => setLoading(false)); }, 200), [], ); return ( <>
{ setInputText(e.currentTarget.value); search(e.currentTarget.value); }} />
{children} ); }; export default ({ children }: { children: ReactNode }) => { return (
{children}
); };