mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-10-07 21:22:01 +00:00
Moved v3 code from NginxProxyManager/nginx-proxy-manager-3 to NginxProxyManager/nginx-proxy-manager
This commit is contained in:
225
frontend/src/components/Table/Formatters.tsx
Normal file
225
frontend/src/components/Table/Formatters.tsx
Normal file
@@ -0,0 +1,225 @@
|
||||
import { Avatar, Badge, Text, Tooltip } from "@chakra-ui/react";
|
||||
import { RowAction, RowActionsMenu } from "components";
|
||||
import { intl } from "locale";
|
||||
import getNiceDNSProvider from "modules/Acmesh";
|
||||
|
||||
function ActionsFormatter(rowActions: RowAction[]) {
|
||||
const formatCell = (instance: any) => {
|
||||
return <RowActionsMenu data={instance.row.original} actions={rowActions} />;
|
||||
};
|
||||
|
||||
return formatCell;
|
||||
}
|
||||
|
||||
function BooleanFormatter() {
|
||||
const formatCell = ({ value }: any) => {
|
||||
return (
|
||||
<Badge color={value ? "cyan.500" : "red.400"}>
|
||||
{value ? "true" : "false"}
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
return formatCell;
|
||||
}
|
||||
|
||||
function CapabilitiesFormatter() {
|
||||
const formatCell = ({ row, value }: any) => {
|
||||
const style = {} as any;
|
||||
if (row?.original?.isDisabled) {
|
||||
style.textDecoration = "line-through";
|
||||
}
|
||||
|
||||
if (row?.original?.isSystem) {
|
||||
return (
|
||||
<Badge color="orange.400" style={style}>
|
||||
{intl.formatMessage({ id: "capability.system" })}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
if (value?.indexOf("full-admin") !== -1) {
|
||||
return (
|
||||
<Badge color="teal.300" style={style}>
|
||||
{intl.formatMessage({ id: "capability.full-admin" })}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
if (value?.length) {
|
||||
const strs: string[] = [];
|
||||
value.map((c: string) => {
|
||||
strs.push(intl.formatMessage({ id: `capability.${c}` }));
|
||||
return null;
|
||||
});
|
||||
|
||||
return (
|
||||
<Tooltip label={strs.join(", \n")}>
|
||||
<Badge color="cyan.500" style={style}>
|
||||
{intl.formatMessage(
|
||||
{ id: "capability-count" },
|
||||
{ count: value.length },
|
||||
)}
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return formatCell;
|
||||
}
|
||||
|
||||
function CertificateStatusFormatter() {
|
||||
const formatCell = ({ value }: any) => {
|
||||
return (
|
||||
<Badge color={value ? "cyan.500" : "red.400"}>
|
||||
{value
|
||||
? intl.formatMessage({ id: "ready" })
|
||||
: intl.formatMessage({ id: "setup-required" })}
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
return formatCell;
|
||||
}
|
||||
|
||||
function DisabledFormatter() {
|
||||
const formatCell = ({ value, row }: any) => {
|
||||
if (row?.original?.isDisabled) {
|
||||
return (
|
||||
<Text color="red.500">
|
||||
<Tooltip label={intl.formatMessage({ id: "user.disabled" })}>
|
||||
{value}
|
||||
</Tooltip>
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
return formatCell;
|
||||
}
|
||||
|
||||
function DNSProviderFormatter() {
|
||||
const formatCell = ({ value }: any) => {
|
||||
return getNiceDNSProvider(value);
|
||||
};
|
||||
|
||||
return formatCell;
|
||||
}
|
||||
|
||||
function DomainsFormatter() {
|
||||
const formatCell = ({ value }: any) => {
|
||||
if (value?.length > 0) {
|
||||
return (
|
||||
<>
|
||||
{value.map((dom: string, idx: number) => {
|
||||
return (
|
||||
<Badge key={`domain-${idx}`} color="yellow.400">
|
||||
{dom}
|
||||
</Badge>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return <Badge color="red.400">No domains!</Badge>;
|
||||
};
|
||||
|
||||
return formatCell;
|
||||
}
|
||||
|
||||
function GravatarFormatter() {
|
||||
const formatCell = ({ value }: any) => {
|
||||
return <Avatar size="sm" src={value} />;
|
||||
};
|
||||
|
||||
return formatCell;
|
||||
}
|
||||
|
||||
function HostStatusFormatter() {
|
||||
const formatCell = ({ row }: any) => {
|
||||
if (row.original.isDisabled) {
|
||||
return (
|
||||
<Badge color="red.400">{intl.formatMessage({ id: "disabled" })}</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
if (row.original.certificateId) {
|
||||
if (row.original.certificate.status === "provided") {
|
||||
return (
|
||||
<Badge color="green.400">
|
||||
{row.original.sslForced
|
||||
? intl.formatMessage({ id: "https-only" })
|
||||
: intl.formatMessage({ id: "http-https" })}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
if (row.original.certificate.status === "error") {
|
||||
return (
|
||||
<Tooltip label={row.original.certificate.errorMessage}>
|
||||
<Badge color="red.400">{intl.formatMessage({ id: "error" })}</Badge>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Badge color="cyan.400">
|
||||
{intl.formatMessage({
|
||||
id: `certificate.${row.original.certificate.status}`,
|
||||
})}
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Badge color="orange.400">
|
||||
{intl.formatMessage({ id: "http-only" })}
|
||||
</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
return formatCell;
|
||||
}
|
||||
|
||||
function HostTypeFormatter() {
|
||||
const formatCell = ({ value }: any) => {
|
||||
return intl.formatMessage({ id: `host-type.${value}` });
|
||||
};
|
||||
|
||||
return formatCell;
|
||||
}
|
||||
|
||||
function IDFormatter() {
|
||||
const formatCell = ({ value }: any) => {
|
||||
return <span className="text-muted">{value}</span>;
|
||||
};
|
||||
|
||||
return formatCell;
|
||||
}
|
||||
|
||||
function SecondsFormatter() {
|
||||
const formatCell = ({ value }: any) => {
|
||||
return intl.formatMessage({ id: "seconds" }, { seconds: value });
|
||||
};
|
||||
|
||||
return formatCell;
|
||||
}
|
||||
|
||||
export {
|
||||
ActionsFormatter,
|
||||
BooleanFormatter,
|
||||
CapabilitiesFormatter,
|
||||
CertificateStatusFormatter,
|
||||
DisabledFormatter,
|
||||
DNSProviderFormatter,
|
||||
DomainsFormatter,
|
||||
GravatarFormatter,
|
||||
HostStatusFormatter,
|
||||
HostTypeFormatter,
|
||||
IDFormatter,
|
||||
SecondsFormatter,
|
||||
};
|
70
frontend/src/components/Table/RowActionsMenu.tsx
Normal file
70
frontend/src/components/Table/RowActionsMenu.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
import { ReactNode } from "react";
|
||||
|
||||
import {
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
MenuItem,
|
||||
IconButton,
|
||||
} from "@chakra-ui/react";
|
||||
import { FiMoreVertical } from "react-icons/fi";
|
||||
|
||||
// A row action is a single menu item for the actions column
|
||||
export interface RowAction {
|
||||
title: string;
|
||||
onClick: (e: any, data: any) => any;
|
||||
show?: (data: any) => any;
|
||||
disabled?: (data: any) => any;
|
||||
icon?: any;
|
||||
}
|
||||
|
||||
interface RowActionsProps {
|
||||
/**
|
||||
* Row Data
|
||||
*/
|
||||
data: any;
|
||||
/**
|
||||
* Actions
|
||||
*/
|
||||
actions: RowAction[];
|
||||
}
|
||||
function RowActionsMenu({ data, actions }: RowActionsProps) {
|
||||
const elms: ReactNode[] = [];
|
||||
actions.map((action) => {
|
||||
if (!action.show || action.show(data)) {
|
||||
const disabled = action.disabled && action.disabled(data);
|
||||
elms.push(
|
||||
<MenuItem
|
||||
key={`action-${action.title}`}
|
||||
icon={action.icon}
|
||||
isDisabled={disabled}
|
||||
onClick={(e: any) => {
|
||||
action.onClick(e, data);
|
||||
}}>
|
||||
{action.title}
|
||||
</MenuItem>,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
if (!elms.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ textAlign: "right" }}>
|
||||
<Menu>
|
||||
<MenuButton
|
||||
as={IconButton}
|
||||
aria-label="Actions"
|
||||
icon={<FiMoreVertical />}
|
||||
variant="outline"
|
||||
/>
|
||||
<MenuList>{elms}</MenuList>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { RowActionsMenu };
|
57
frontend/src/components/Table/TableHelpers.ts
Normal file
57
frontend/src/components/Table/TableHelpers.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
export interface TablePagination {
|
||||
limit: number;
|
||||
offset: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface TableSortBy {
|
||||
id: string;
|
||||
desc: boolean;
|
||||
}
|
||||
|
||||
export interface TableFilter {
|
||||
id: string;
|
||||
value: any;
|
||||
}
|
||||
|
||||
const tableEvents = {
|
||||
FILTERS_CHANGED: "FILTERS_CHANGED",
|
||||
PAGE_CHANGED: "PAGE_CHANGED",
|
||||
PAGE_SIZE_CHANGED: "PAGE_SIZE_CHANGED",
|
||||
TOTAL_COUNT_CHANGED: "TOTAL_COUNT_CHANGED",
|
||||
SORT_CHANGED: "SORT_CHANGED",
|
||||
};
|
||||
|
||||
const tableEventReducer = (state: any, { type, payload }: any) => {
|
||||
switch (type) {
|
||||
case tableEvents.PAGE_CHANGED:
|
||||
return {
|
||||
...state,
|
||||
offset: payload * state.limit,
|
||||
};
|
||||
case tableEvents.PAGE_SIZE_CHANGED:
|
||||
return {
|
||||
...state,
|
||||
limit: payload,
|
||||
};
|
||||
case tableEvents.TOTAL_COUNT_CHANGED:
|
||||
return {
|
||||
...state,
|
||||
total: payload,
|
||||
};
|
||||
case tableEvents.SORT_CHANGED:
|
||||
return {
|
||||
...state,
|
||||
sortBy: payload,
|
||||
};
|
||||
case tableEvents.FILTERS_CHANGED:
|
||||
return {
|
||||
...state,
|
||||
filters: payload,
|
||||
};
|
||||
default:
|
||||
throw new Error(`Unhandled action type: ${type}`);
|
||||
}
|
||||
};
|
||||
|
||||
export { tableEvents, tableEventReducer };
|
246
frontend/src/components/Table/TableLayout.tsx
Normal file
246
frontend/src/components/Table/TableLayout.tsx
Normal file
@@ -0,0 +1,246 @@
|
||||
import { ReactNode } from "react";
|
||||
|
||||
import {
|
||||
ButtonGroup,
|
||||
Center,
|
||||
Flex,
|
||||
HStack,
|
||||
IconButton,
|
||||
Link,
|
||||
Select,
|
||||
Table,
|
||||
Tbody,
|
||||
Td,
|
||||
Text,
|
||||
Th,
|
||||
Thead,
|
||||
Tr,
|
||||
VStack,
|
||||
} from "@chakra-ui/react";
|
||||
import { TablePagination } from "components";
|
||||
import { intl } from "locale";
|
||||
import {
|
||||
FiChevronsLeft,
|
||||
FiChevronLeft,
|
||||
FiChevronsRight,
|
||||
FiChevronRight,
|
||||
FiChevronDown,
|
||||
FiChevronUp,
|
||||
FiX,
|
||||
} from "react-icons/fi";
|
||||
|
||||
export interface TableLayoutProps {
|
||||
pagination: TablePagination;
|
||||
getTableProps: any;
|
||||
getTableBodyProps: any;
|
||||
headerGroups: any;
|
||||
rows: any;
|
||||
prepareRow: any;
|
||||
gotoPage: any;
|
||||
canPreviousPage: any;
|
||||
previousPage: any;
|
||||
canNextPage: any;
|
||||
setPageSize: any;
|
||||
nextPage: any;
|
||||
pageCount: any;
|
||||
pageOptions: any;
|
||||
visibleColumns: any;
|
||||
setAllFilters: any;
|
||||
state: any;
|
||||
}
|
||||
function TableLayout({
|
||||
pagination,
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
gotoPage,
|
||||
canPreviousPage,
|
||||
previousPage,
|
||||
canNextPage,
|
||||
setPageSize,
|
||||
nextPage,
|
||||
pageCount,
|
||||
pageOptions,
|
||||
visibleColumns,
|
||||
setAllFilters,
|
||||
state,
|
||||
}: TableLayoutProps) {
|
||||
const currentPage = state.pageIndex + 1;
|
||||
const getPageList = () => {
|
||||
const list = [];
|
||||
for (let x = 0; x < pageOptions.length; x++) {
|
||||
list.push(
|
||||
<option
|
||||
key={`table-pagination-page-${x}`}
|
||||
value={x + 1}
|
||||
selected={currentPage === x + 1}>
|
||||
{x + 1}
|
||||
</option>,
|
||||
);
|
||||
}
|
||||
return list;
|
||||
};
|
||||
|
||||
const renderEmpty = (): ReactNode => {
|
||||
return (
|
||||
<Tr>
|
||||
<Td colSpan={visibleColumns.length}>
|
||||
<Center>
|
||||
{state?.filters?.length
|
||||
? intl.formatMessage(
|
||||
{ id: "tables.no-items-with-filters" },
|
||||
{ count: state.filters.length },
|
||||
)
|
||||
: intl.formatMessage({ id: "tables.no-items" })}
|
||||
</Center>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Table {...getTableProps()}>
|
||||
<Thead>
|
||||
{headerGroups.map((headerGroup: any) => (
|
||||
<Tr {...headerGroup.getHeaderGroupProps()}>
|
||||
{headerGroup.headers.map((column: any) => (
|
||||
<Th
|
||||
userSelect="none"
|
||||
className={column.className}
|
||||
isNumeric={column.isNumeric}>
|
||||
<Flex alignItems="center">
|
||||
<HStack mx={6} justifyContent="space-between">
|
||||
<Text
|
||||
{...column.getHeaderProps(
|
||||
column.sortable && column.getSortByToggleProps(),
|
||||
)}>
|
||||
{column.render("Header")}
|
||||
</Text>
|
||||
{column.sortable && column.isSorted ? (
|
||||
column.isSortedDesc ? (
|
||||
<FiChevronDown />
|
||||
) : (
|
||||
<FiChevronUp />
|
||||
)
|
||||
) : null}
|
||||
{column.Filter ? column.render("Filter") : null}
|
||||
</HStack>
|
||||
</Flex>
|
||||
</Th>
|
||||
))}
|
||||
</Tr>
|
||||
))}
|
||||
</Thead>
|
||||
<Tbody {...getTableBodyProps()}>
|
||||
{rows.length
|
||||
? rows.map((row: any) => {
|
||||
prepareRow(row);
|
||||
return (
|
||||
<Tr {...row.getRowProps()}>
|
||||
{row.cells.map((cell: any) => (
|
||||
<Td
|
||||
{...cell.getCellProps([
|
||||
{
|
||||
className: cell.column.className,
|
||||
},
|
||||
])}>
|
||||
{cell.render("Cell")}
|
||||
</Td>
|
||||
))}
|
||||
</Tr>
|
||||
);
|
||||
})
|
||||
: renderEmpty()}
|
||||
</Tbody>
|
||||
</Table>
|
||||
<HStack mx={6} my={4} justifyContent="space-between">
|
||||
<VStack align="left">
|
||||
<Text color="gray.500">
|
||||
{rows.length
|
||||
? intl.formatMessage(
|
||||
{ id: "tables.pagination-counts" },
|
||||
{
|
||||
start: pagination.offset + 1,
|
||||
end: Math.min(
|
||||
pagination.total,
|
||||
pagination.offset + pagination.limit,
|
||||
),
|
||||
total: pagination.total,
|
||||
},
|
||||
)
|
||||
: null}
|
||||
</Text>
|
||||
{state?.filters?.length ? (
|
||||
<Link onClick={() => setAllFilters([])}>
|
||||
<HStack>
|
||||
<FiX display="inline-block" />
|
||||
<Text>
|
||||
{intl.formatMessage(
|
||||
{ id: "tables.clear-all-filters" },
|
||||
{ count: state.filters.length },
|
||||
)}
|
||||
</Text>
|
||||
</HStack>
|
||||
</Link>
|
||||
) : null}
|
||||
</VStack>
|
||||
<nav>
|
||||
<ButtonGroup size="sm" isAttached>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id: "tables.pagination-previous",
|
||||
})}
|
||||
size="sm"
|
||||
icon={<FiChevronsLeft />}
|
||||
isDisabled={!canPreviousPage}
|
||||
onClick={() => gotoPage(0)}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id: "tables.pagination-previous",
|
||||
})}
|
||||
size="sm"
|
||||
icon={<FiChevronLeft />}
|
||||
isDisabled={!canPreviousPage}
|
||||
onClick={() => previousPage()}
|
||||
/>
|
||||
<Select
|
||||
size="sm"
|
||||
variant="filled"
|
||||
borderRadius={0}
|
||||
defaultValue={currentPage}
|
||||
disabled={!canPreviousPage && !canNextPage}
|
||||
aria-label={intl.formatMessage({
|
||||
id: "tables.pagination-select",
|
||||
})}>
|
||||
{getPageList()}
|
||||
</Select>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id: "tables.pagination-next",
|
||||
})}
|
||||
size="sm"
|
||||
icon={<FiChevronRight />}
|
||||
isDisabled={!canNextPage}
|
||||
onClick={() => nextPage()}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label={intl.formatMessage({
|
||||
id: "tables.pagination-next",
|
||||
})}
|
||||
size="sm"
|
||||
icon={<FiChevronsRight />}
|
||||
isDisabled={!canNextPage}
|
||||
onClick={() => gotoPage(pageCount - 1)}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</nav>
|
||||
</HStack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export { TableLayout };
|
142
frontend/src/components/Table/TextFilter.tsx
Normal file
142
frontend/src/components/Table/TextFilter.tsx
Normal file
@@ -0,0 +1,142 @@
|
||||
import {
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
PopoverArrow,
|
||||
IconButton,
|
||||
FormControl,
|
||||
FormErrorMessage,
|
||||
Input,
|
||||
Stack,
|
||||
ButtonGroup,
|
||||
Button,
|
||||
useDisclosure,
|
||||
Select,
|
||||
} from "@chakra-ui/react";
|
||||
import { PrettyButton } from "components";
|
||||
import { Formik, Form, Field } from "formik";
|
||||
import { intl } from "locale";
|
||||
import { validateString } from "modules/Validations";
|
||||
import FocusLock from "react-focus-lock";
|
||||
import { FiFilter } from "react-icons/fi";
|
||||
|
||||
function TextFilter({ column: { filterValue, setFilter } }: any) {
|
||||
const { onOpen, onClose, isOpen } = useDisclosure();
|
||||
|
||||
const onSubmit = (values: any, { setSubmitting }: any) => {
|
||||
setFilter(values);
|
||||
setSubmitting(false);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const clearFilter = () => {
|
||||
setFilter(undefined);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const isFiltered = (): boolean => {
|
||||
return !(typeof filterValue === "undefined" || filterValue === "");
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover
|
||||
isOpen={isOpen}
|
||||
onOpen={onOpen}
|
||||
onClose={onClose}
|
||||
placement="right">
|
||||
<PopoverTrigger>
|
||||
<IconButton
|
||||
variant="unstyled"
|
||||
size="sm"
|
||||
color={isFiltered() ? "orange.400" : ""}
|
||||
icon={<FiFilter />}
|
||||
aria-label="Filter"
|
||||
/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent p={5}>
|
||||
<FocusLock returnFocus persistentFocus={false}>
|
||||
<PopoverArrow />
|
||||
<Formik
|
||||
initialValues={
|
||||
{
|
||||
modifier: filterValue?.modifier || "contains",
|
||||
value: filterValue?.value,
|
||||
} as any
|
||||
}
|
||||
onSubmit={onSubmit}>
|
||||
{({ isSubmitting }) => (
|
||||
<Form>
|
||||
<Stack spacing={4}>
|
||||
<Field name="modifier">
|
||||
{({ field, form }: any) => (
|
||||
<FormControl
|
||||
isRequired
|
||||
isInvalid={
|
||||
form.errors.modifier && form.touched.modifier
|
||||
}>
|
||||
<Select
|
||||
{...field}
|
||||
size="sm"
|
||||
id="modifier"
|
||||
defaultValue="contains">
|
||||
<option value="contains">
|
||||
{intl.formatMessage({ id: "filter.contains" })}
|
||||
</option>
|
||||
<option value="equals">
|
||||
{intl.formatMessage({ id: "filter.exactly" })}
|
||||
</option>
|
||||
<option value="starts">
|
||||
{intl.formatMessage({ id: "filter.starts" })}
|
||||
</option>
|
||||
<option value="ends">
|
||||
{intl.formatMessage({ id: "filter.ends" })}
|
||||
</option>
|
||||
</Select>
|
||||
<FormErrorMessage>{form.errors.name}</FormErrorMessage>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="value" validate={validateString(1, 50)}>
|
||||
{({ field, form }: any) => (
|
||||
<FormControl
|
||||
isRequired
|
||||
isInvalid={form.errors.value && form.touched.value}>
|
||||
<Input
|
||||
{...field}
|
||||
size="sm"
|
||||
placeholder={intl.formatMessage({
|
||||
id: "filter.placeholder",
|
||||
})}
|
||||
autoComplete="off"
|
||||
/>
|
||||
<FormErrorMessage>{form.errors.value}</FormErrorMessage>
|
||||
</FormControl>
|
||||
)}
|
||||
</Field>
|
||||
<ButtonGroup d="flex" justifyContent="flex-end">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={clearFilter}
|
||||
isLoading={isSubmitting}>
|
||||
{intl.formatMessage({
|
||||
id: "filter.clear",
|
||||
})}
|
||||
</Button>
|
||||
<PrettyButton size="sm" isLoading={isSubmitting}>
|
||||
{intl.formatMessage({
|
||||
id: "filter.apply",
|
||||
})}
|
||||
</PrettyButton>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</FocusLock>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
export { TextFilter };
|
5
frontend/src/components/Table/index.ts
Normal file
5
frontend/src/components/Table/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from "./Formatters";
|
||||
export * from "./RowActionsMenu";
|
||||
export * from "./TableHelpers";
|
||||
export * from "./TableLayout";
|
||||
export * from "./TextFilter";
|
130
frontend/src/components/Table/react-table-config.d.ts
vendored
Normal file
130
frontend/src/components/Table/react-table-config.d.ts
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
// See: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react-table#configuration-using-declaration-merging
|
||||
import {
|
||||
UseColumnOrderInstanceProps,
|
||||
UseColumnOrderState,
|
||||
UseExpandedHooks,
|
||||
UseExpandedInstanceProps,
|
||||
UseExpandedOptions,
|
||||
UseExpandedRowProps,
|
||||
UseExpandedState,
|
||||
UseFiltersColumnOptions,
|
||||
UseFiltersColumnProps,
|
||||
UseFiltersInstanceProps,
|
||||
UseFiltersOptions,
|
||||
UseFiltersState,
|
||||
UseGlobalFiltersColumnOptions,
|
||||
UseGlobalFiltersInstanceProps,
|
||||
UseGlobalFiltersOptions,
|
||||
UseGlobalFiltersState,
|
||||
UseGroupByCellProps,
|
||||
UseGroupByColumnOptions,
|
||||
UseGroupByColumnProps,
|
||||
UseGroupByHooks,
|
||||
UseGroupByInstanceProps,
|
||||
UseGroupByOptions,
|
||||
UseGroupByRowProps,
|
||||
UseGroupByState,
|
||||
UsePaginationInstanceProps,
|
||||
UsePaginationOptions,
|
||||
UsePaginationState,
|
||||
UseResizeColumnsColumnOptions,
|
||||
UseResizeColumnsColumnProps,
|
||||
UseResizeColumnsOptions,
|
||||
UseResizeColumnsState,
|
||||
UseRowSelectHooks,
|
||||
UseRowSelectInstanceProps,
|
||||
UseRowSelectOptions,
|
||||
UseRowSelectRowProps,
|
||||
UseRowSelectState,
|
||||
UseRowStateCellProps,
|
||||
UseRowStateInstanceProps,
|
||||
UseRowStateOptions,
|
||||
UseRowStateRowProps,
|
||||
UseRowStateState,
|
||||
UseSortByColumnOptions,
|
||||
UseSortByColumnProps,
|
||||
UseSortByHooks,
|
||||
UseSortByInstanceProps,
|
||||
UseSortByOptions,
|
||||
UseSortByState,
|
||||
} from "react-table";
|
||||
|
||||
declare module "react-table" {
|
||||
// take this file as-is, or comment out the sections that don't apply to your plugin configuration
|
||||
|
||||
export interface TableOptions<
|
||||
D extends Record<string, unknown>,
|
||||
> extends UseExpandedOptions<D>,
|
||||
UseFiltersOptions<D>,
|
||||
UseGlobalFiltersOptions<D>,
|
||||
UseGroupByOptions<D>,
|
||||
UsePaginationOptions<D>,
|
||||
UseResizeColumnsOptions<D>,
|
||||
UseRowSelectOptions<D>,
|
||||
UseRowStateOptions<D>,
|
||||
UseSortByOptions<D>,
|
||||
// note that having Record here allows you to add anything to the options, this matches the spirit of the
|
||||
// underlying js library, but might be cleaner if it's replaced by a more specific type that matches your
|
||||
// feature set, this is a safe default.
|
||||
Record<string, any> {}
|
||||
|
||||
export interface Hooks<
|
||||
D extends Record<string, unknown> = Record<string, unknown>,
|
||||
> extends UseExpandedHooks<D>,
|
||||
UseGroupByHooks<D>,
|
||||
UseRowSelectHooks<D>,
|
||||
UseSortByHooks<D> {}
|
||||
|
||||
export interface TableInstance<
|
||||
D extends Record<string, unknown> = Record<string, unknown>,
|
||||
> extends UseColumnOrderInstanceProps<D>,
|
||||
UseExpandedInstanceProps<D>,
|
||||
UseFiltersInstanceProps<D>,
|
||||
UseGlobalFiltersInstanceProps<D>,
|
||||
UseGroupByInstanceProps<D>,
|
||||
UsePaginationInstanceProps<D>,
|
||||
UseRowSelectInstanceProps<D>,
|
||||
UseRowStateInstanceProps<D>,
|
||||
UseSortByInstanceProps<D> {}
|
||||
|
||||
export interface TableState<
|
||||
D extends Record<string, unknown> = Record<string, unknown>,
|
||||
> extends UseColumnOrderState<D>,
|
||||
UseExpandedState<D>,
|
||||
UseFiltersState<D>,
|
||||
UseGlobalFiltersState<D>,
|
||||
UseGroupByState<D>,
|
||||
UsePaginationState<D>,
|
||||
UseResizeColumnsState<D>,
|
||||
UseRowSelectState<D>,
|
||||
UseRowStateState<D>,
|
||||
UseSortByState<D> {}
|
||||
|
||||
export interface ColumnInterface<
|
||||
D extends Record<string, unknown> = Record<string, unknown>,
|
||||
> extends UseFiltersColumnOptions<D>,
|
||||
UseGlobalFiltersColumnOptions<D>,
|
||||
UseGroupByColumnOptions<D>,
|
||||
UseResizeColumnsColumnOptions<D>,
|
||||
UseSortByColumnOptions<D> {}
|
||||
|
||||
export interface ColumnInstance<
|
||||
D extends Record<string, unknown> = Record<string, unknown>,
|
||||
> extends UseFiltersColumnProps<D>,
|
||||
UseGroupByColumnProps<D>,
|
||||
UseResizeColumnsColumnProps<D>,
|
||||
UseSortByColumnProps<D> {}
|
||||
|
||||
export interface Cell<
|
||||
D extends Record<string, unknown> = Record<string, unknown>,
|
||||
// V = any,
|
||||
> extends UseGroupByCellProps<D>,
|
||||
UseRowStateCellProps<D> {}
|
||||
|
||||
export interface Row<
|
||||
D extends Record<string, unknown> = Record<string, unknown>,
|
||||
> extends UseExpandedRowProps<D>,
|
||||
UseGroupByRowProps<D>,
|
||||
UseRowSelectRowProps<D>,
|
||||
UseRowStateRowProps<D> {}
|
||||
}
|
Reference in New Issue
Block a user