fix: TT-69

This commit is contained in:
anik-ghosh-au7 2022-07-15 22:12:08 +05:30
parent eabc943452
commit 8fc52d76dc
4 changed files with 495 additions and 147 deletions

View File

@ -20,7 +20,11 @@ import {
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { FaMinusCircle, FaPlus } from 'react-icons/fa'; import { FaMinusCircle, FaPlus } from 'react-icons/fa';
import { useClient } from 'urql'; import { useClient } from 'urql';
import { ArrayInputOperations } from '../constants'; import {
ArrayInputOperations,
WebhookInputDataFields,
WebhookInputHeaderFields,
} from '../constants';
import { import {
capitalizeFirstLetter, capitalizeFirstLetter,
validateEventName, validateEventName,
@ -28,62 +32,50 @@ import {
} from '../utils'; } from '../utils';
import { AddWebhook } from '../graphql/mutation'; import { AddWebhook } from '../graphql/mutation';
enum INPUT_FIELDS {
EVENT_NAME = 'event_name',
ENDPOINT = 'endpoint',
ENABLED = 'enabled',
HEADERS = 'headers',
}
enum HEADER_FIELDS {
KEY = 'key',
VALUE = 'value',
}
interface headersDataType { interface headersDataType {
[HEADER_FIELDS.KEY]: string; [WebhookInputHeaderFields.KEY]: string;
[HEADER_FIELDS.VALUE]: string; [WebhookInputHeaderFields.VALUE]: string;
} }
interface headersValidatorDataType { interface headersValidatorDataType {
[HEADER_FIELDS.KEY]: boolean; [WebhookInputHeaderFields.KEY]: boolean;
[HEADER_FIELDS.VALUE]: boolean; [WebhookInputHeaderFields.VALUE]: boolean;
} }
const initHeadersData: headersDataType = { const initHeadersData: headersDataType = {
[HEADER_FIELDS.KEY]: '', [WebhookInputHeaderFields.KEY]: '',
[HEADER_FIELDS.VALUE]: '', [WebhookInputHeaderFields.VALUE]: '',
}; };
const initHeadersValidatorData: headersValidatorDataType = { const initHeadersValidatorData: headersValidatorDataType = {
[HEADER_FIELDS.KEY]: true, [WebhookInputHeaderFields.KEY]: true,
[HEADER_FIELDS.VALUE]: true, [WebhookInputHeaderFields.VALUE]: true,
}; };
interface webhookDataType { interface webhookDataType {
[INPUT_FIELDS.EVENT_NAME]: string; [WebhookInputDataFields.EVENT_NAME]: string;
[INPUT_FIELDS.ENDPOINT]: string; [WebhookInputDataFields.ENDPOINT]: string;
[INPUT_FIELDS.ENABLED]: boolean; [WebhookInputDataFields.ENABLED]: boolean;
[INPUT_FIELDS.HEADERS]: headersDataType[]; [WebhookInputDataFields.HEADERS]: headersDataType[];
} }
interface validatorDataType { interface validatorDataType {
[INPUT_FIELDS.EVENT_NAME]: boolean; [WebhookInputDataFields.EVENT_NAME]: boolean;
[INPUT_FIELDS.ENDPOINT]: boolean; [WebhookInputDataFields.ENDPOINT]: boolean;
[INPUT_FIELDS.HEADERS]: headersValidatorDataType[]; [WebhookInputDataFields.HEADERS]: headersValidatorDataType[];
} }
const initWebhookData: webhookDataType = { const initWebhookData: webhookDataType = {
[INPUT_FIELDS.EVENT_NAME]: '', [WebhookInputDataFields.EVENT_NAME]: '',
[INPUT_FIELDS.ENDPOINT]: '', [WebhookInputDataFields.ENDPOINT]: '',
[INPUT_FIELDS.ENABLED]: false, [WebhookInputDataFields.ENABLED]: false,
[INPUT_FIELDS.HEADERS]: [{ ...initHeadersData }], [WebhookInputDataFields.HEADERS]: [{ ...initHeadersData }],
}; };
const initWebhookValidatorData: validatorDataType = { const initWebhookValidatorData: validatorDataType = {
[INPUT_FIELDS.EVENT_NAME]: true, [WebhookInputDataFields.EVENT_NAME]: true,
[INPUT_FIELDS.ENDPOINT]: true, [WebhookInputDataFields.ENDPOINT]: true,
[INPUT_FIELDS.HEADERS]: [{ ...initHeadersValidatorData }], [WebhookInputDataFields.HEADERS]: [{ ...initHeadersValidatorData }],
}; };
const AddWebhookModal = () => { const AddWebhookModal = () => {
@ -100,36 +92,38 @@ const AddWebhookModal = () => {
const inputChangehandler = ( const inputChangehandler = (
inputType: string, inputType: string,
value: any, value: any,
headerInputType: string = HEADER_FIELDS.KEY, headerInputType: string = WebhookInputHeaderFields.KEY,
headerIndex: number = 0 headerIndex: number = 0
) => { ) => {
switch (inputType) { switch (inputType) {
case INPUT_FIELDS.EVENT_NAME: case WebhookInputDataFields.EVENT_NAME:
setWebhook({ ...webhook, [inputType]: value }); setWebhook({ ...webhook, [inputType]: value });
setValidator({ setValidator({
...validator, ...validator,
[INPUT_FIELDS.EVENT_NAME]: validateEventName(value), [WebhookInputDataFields.EVENT_NAME]: validateEventName(value),
}); });
break; break;
case INPUT_FIELDS.ENDPOINT: case WebhookInputDataFields.ENDPOINT:
setWebhook({ ...webhook, [inputType]: value }); setWebhook({ ...webhook, [inputType]: value });
setValidator({ setValidator({
...validator, ...validator,
[INPUT_FIELDS.ENDPOINT]: validateURI(value), [WebhookInputDataFields.ENDPOINT]: validateURI(value),
}); });
break; break;
case INPUT_FIELDS.ENABLED: case WebhookInputDataFields.ENABLED:
setWebhook({ ...webhook, [inputType]: value }); setWebhook({ ...webhook, [inputType]: value });
break; break;
case INPUT_FIELDS.HEADERS: case WebhookInputDataFields.HEADERS:
const updatedHeaders: any = [...webhook[INPUT_FIELDS.HEADERS]]; const updatedHeaders: any = [
...webhook[WebhookInputDataFields.HEADERS],
];
const updatedHeadersValidatorData: any = [ const updatedHeadersValidatorData: any = [
...validator[INPUT_FIELDS.HEADERS], ...validator[WebhookInputDataFields.HEADERS],
]; ];
const otherHeaderInputType = const otherHeaderInputType =
headerInputType === HEADER_FIELDS.KEY headerInputType === WebhookInputHeaderFields.KEY
? HEADER_FIELDS.VALUE ? WebhookInputHeaderFields.VALUE
: HEADER_FIELDS.KEY; : WebhookInputHeaderFields.KEY;
updatedHeaders[headerIndex][headerInputType] = value; updatedHeaders[headerIndex][headerInputType] = value;
updatedHeadersValidatorData[headerIndex][headerInputType] = updatedHeadersValidatorData[headerIndex][headerInputType] =
value.length > 0 value.length > 0
@ -154,34 +148,36 @@ const AddWebhookModal = () => {
case ArrayInputOperations.APPEND: case ArrayInputOperations.APPEND:
setWebhook({ setWebhook({
...webhook, ...webhook,
[INPUT_FIELDS.HEADERS]: [ [WebhookInputDataFields.HEADERS]: [
...(webhook?.[INPUT_FIELDS.HEADERS] || []), ...(webhook?.[WebhookInputDataFields.HEADERS] || []),
{ ...initHeadersData }, { ...initHeadersData },
], ],
}); });
setValidator({ setValidator({
...validator, ...validator,
[INPUT_FIELDS.HEADERS]: [ [WebhookInputDataFields.HEADERS]: [
...(validator?.[INPUT_FIELDS.HEADERS] || []), ...(validator?.[WebhookInputDataFields.HEADERS] || []),
{ ...initHeadersValidatorData }, { ...initHeadersValidatorData },
], ],
}); });
break; break;
case ArrayInputOperations.REMOVE: case ArrayInputOperations.REMOVE:
if (webhook?.[INPUT_FIELDS.HEADERS]?.length) { if (webhook?.[WebhookInputDataFields.HEADERS]?.length) {
const updatedHeaders = [...webhook[INPUT_FIELDS.HEADERS]]; const updatedHeaders = [...webhook[WebhookInputDataFields.HEADERS]];
updatedHeaders.splice(index, 1); updatedHeaders.splice(index, 1);
setWebhook({ setWebhook({
...webhook, ...webhook,
[INPUT_FIELDS.HEADERS]: updatedHeaders, [WebhookInputDataFields.HEADERS]: updatedHeaders,
}); });
} }
if (validator?.[INPUT_FIELDS.HEADERS]?.length) { if (validator?.[WebhookInputDataFields.HEADERS]?.length) {
const updatedHeadersData = [...validator[INPUT_FIELDS.HEADERS]]; const updatedHeadersData = [
...validator[WebhookInputDataFields.HEADERS],
];
updatedHeadersData.splice(index, 1); updatedHeadersData.splice(index, 1);
setValidator({ setValidator({
...validator, ...validator,
[INPUT_FIELDS.HEADERS]: updatedHeadersData, [WebhookInputDataFields.HEADERS]: updatedHeadersData,
}); });
} }
break; break;
@ -192,11 +188,11 @@ const AddWebhookModal = () => {
const validateData = () => { const validateData = () => {
return ( return (
!loading && !loading &&
webhook[INPUT_FIELDS.EVENT_NAME].length > 0 && webhook[WebhookInputDataFields.EVENT_NAME].length > 0 &&
webhook[INPUT_FIELDS.ENDPOINT].length > 0 && webhook[WebhookInputDataFields.ENDPOINT].length > 0 &&
validator[INPUT_FIELDS.EVENT_NAME] && validator[WebhookInputDataFields.EVENT_NAME] &&
validator[INPUT_FIELDS.ENDPOINT] && validator[WebhookInputDataFields.ENDPOINT] &&
!validator[INPUT_FIELDS.HEADERS].some( !validator[WebhookInputDataFields.HEADERS].some(
(headerData: headersValidatorDataType) => (headerData: headersValidatorDataType) =>
!headerData.key || !headerData.value !headerData.key || !headerData.value
) )
@ -205,15 +201,17 @@ const AddWebhookModal = () => {
const saveData = async () => { const saveData = async () => {
if (!validateData()) return; if (!validateData()) return;
setLoading(true); setLoading(true);
let { [INPUT_FIELDS.HEADERS]: _, ...params }: any = webhook; let { [WebhookInputDataFields.HEADERS]: _, ...params }: any = webhook;
if ( if (webhook[WebhookInputDataFields.HEADERS].length) {
webhook[INPUT_FIELDS.HEADERS].length > 0 && const headers = webhook[WebhookInputDataFields.HEADERS].reduce(
webhook[INPUT_FIELDS.HEADERS][0][HEADER_FIELDS.KEY] (acc, data) => {
) { return data.key ? { ...acc, [data.key]: data.value } : acc;
const headers = webhook[INPUT_FIELDS.HEADERS].reduce((acc, data) => { },
return { ...acc, [data.key]: data.value }; {}
}, {}); );
params[INPUT_FIELDS.HEADERS] = headers; if (Object.keys(headers).length) {
params[WebhookInputDataFields.HEADERS] = headers;
}
} }
const res = await client.mutation(AddWebhook, { params }).toPromise(); const res = await client.mutation(AddWebhook, { params }).toPromise();
if (res.error) { if (res.error) {
@ -234,7 +232,7 @@ const AddWebhookModal = () => {
}); });
setWebhook({ setWebhook({
...initWebhookData, ...initWebhookData,
[INPUT_FIELDS.HEADERS]: [{ ...initHeadersData }], [WebhookInputDataFields.HEADERS]: [{ ...initHeadersData }],
}); });
setValidator({ ...initWebhookValidatorData }); setValidator({ ...initWebhookValidatorData });
onClose(); onClose();
@ -279,11 +277,11 @@ const AddWebhookModal = () => {
pr="4.5rem" pr="4.5rem"
type="text" type="text"
placeholder="user.login" placeholder="user.login"
value={webhook[INPUT_FIELDS.EVENT_NAME]} value={webhook[WebhookInputDataFields.EVENT_NAME]}
isInvalid={!validator[INPUT_FIELDS.EVENT_NAME]} isInvalid={!validator[WebhookInputDataFields.EVENT_NAME]}
onChange={(e) => onChange={(e) =>
inputChangehandler( inputChangehandler(
INPUT_FIELDS.EVENT_NAME, WebhookInputDataFields.EVENT_NAME,
e.currentTarget.value e.currentTarget.value
) )
} }
@ -304,11 +302,11 @@ const AddWebhookModal = () => {
pr="4.5rem" pr="4.5rem"
type="text" type="text"
placeholder="https://domain.com/webhook" placeholder="https://domain.com/webhook"
value={webhook[INPUT_FIELDS.ENDPOINT]} value={webhook[WebhookInputDataFields.ENDPOINT]}
isInvalid={!validator[INPUT_FIELDS.ENDPOINT]} isInvalid={!validator[WebhookInputDataFields.ENDPOINT]}
onChange={(e) => onChange={(e) =>
inputChangehandler( inputChangehandler(
INPUT_FIELDS.ENDPOINT, WebhookInputDataFields.ENDPOINT,
e.currentTarget.value e.currentTarget.value
) )
} }
@ -329,11 +327,11 @@ const AddWebhookModal = () => {
</Text> </Text>
<Switch <Switch
size="md" size="md"
isChecked={webhook[INPUT_FIELDS.ENABLED]} isChecked={webhook[WebhookInputDataFields.ENABLED]}
onChange={() => onChange={() =>
inputChangehandler( inputChangehandler(
INPUT_FIELDS.ENABLED, WebhookInputDataFields.ENABLED,
!webhook[INPUT_FIELDS.ENABLED] !webhook[WebhookInputDataFields.ENABLED]
) )
} }
/> />
@ -364,7 +362,8 @@ const AddWebhookModal = () => {
</Flex> </Flex>
</Flex> </Flex>
<Flex flexDirection="column" maxH={220} overflowY="scroll"> <Flex flexDirection="column" maxH={220} overflowY="scroll">
{webhook[INPUT_FIELDS.HEADERS]?.map((headerData, index) => ( {webhook[WebhookInputDataFields.HEADERS]?.map(
(headerData, index) => (
<Flex <Flex
key={`header-data-${index}`} key={`header-data-${index}`}
justifyContent="center" justifyContent="center"
@ -374,17 +373,17 @@ const AddWebhookModal = () => {
<Input <Input
type="text" type="text"
placeholder="key" placeholder="key"
value={headerData[HEADER_FIELDS.KEY]} value={headerData[WebhookInputHeaderFields.KEY]}
isInvalid={ isInvalid={
!validator[INPUT_FIELDS.HEADERS][index]?.[ !validator[WebhookInputDataFields.HEADERS][index]?.[
HEADER_FIELDS.KEY WebhookInputHeaderFields.KEY
] ]
} }
onChange={(e) => onChange={(e) =>
inputChangehandler( inputChangehandler(
INPUT_FIELDS.HEADERS, WebhookInputDataFields.HEADERS,
e.target.value, e.target.value,
HEADER_FIELDS.KEY, WebhookInputHeaderFields.KEY,
index index
) )
} }
@ -397,17 +396,17 @@ const AddWebhookModal = () => {
<Input <Input
type="text" type="text"
placeholder="value" placeholder="value"
value={headerData[HEADER_FIELDS.VALUE]} value={headerData[WebhookInputHeaderFields.VALUE]}
isInvalid={ isInvalid={
!validator[INPUT_FIELDS.HEADERS][index]?.[ !validator[WebhookInputDataFields.HEADERS][index]?.[
HEADER_FIELDS.VALUE WebhookInputHeaderFields.VALUE
] ]
} }
onChange={(e) => onChange={(e) =>
inputChangehandler( inputChangehandler(
INPUT_FIELDS.HEADERS, WebhookInputDataFields.HEADERS,
e.target.value, e.target.value,
HEADER_FIELDS.VALUE, WebhookInputHeaderFields.VALUE,
index index
) )
} }
@ -428,7 +427,8 @@ const AddWebhookModal = () => {
</InputRightElement> </InputRightElement>
</InputGroup> </InputGroup>
</Flex> </Flex>
))} )
)}
</Flex> </Flex>
</Flex> </Flex>
</ModalBody> </ModalBody>

View File

@ -153,3 +153,18 @@ export const envSubViews = {
ADMIN_SECRET: 'admin-secret', ADMIN_SECRET: 'admin-secret',
DB_CRED: 'db-cred', DB_CRED: 'db-cred',
}; };
export enum WebhookInputDataFields {
ID = 'id',
EVENT_NAME = 'event_name',
ENDPOINT = 'endpoint',
ENABLED = 'enabled',
HEADERS = 'headers',
}
export enum WebhookInputHeaderFields {
KEY = 'key',
VALUE = 'value',
}
export const pageLimits: number[] = [5, 10, 15];

View File

@ -101,3 +101,23 @@ export const EmailVerificationQuery = `
} }
} }
`; `;
export const WebhooksDataQuery = `
query getWebhooksData {
_webhooks{
webhooks{
id
event_name
endpoint
enabled
headers
}
pagination{
limit
page
offset
total
}
}
}
`;

View File

@ -1,8 +1,109 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import { Box, Flex, Text } from '@chakra-ui/react'; import { useClient } from 'urql';
import {
Box,
Button,
Center,
Flex,
IconButton,
Menu,
MenuButton,
MenuItem,
MenuList,
NumberDecrementStepper,
NumberIncrementStepper,
NumberInput,
NumberInputField,
NumberInputStepper,
Select,
Spinner,
Table,
TableCaption,
Tag,
Tbody,
Td,
Text,
Th,
Thead,
Tooltip,
Tr,
} from '@chakra-ui/react';
import {
FaAngleDoubleLeft,
FaAngleDoubleRight,
FaAngleDown,
FaAngleLeft,
FaAngleRight,
FaExclamationCircle,
} from 'react-icons/fa';
import AddWebhookModal from '../components/AddWebhookModal'; import AddWebhookModal from '../components/AddWebhookModal';
import { pageLimits, WebhookInputDataFields } from '../constants';
import { WebhooksDataQuery } from '../graphql/queries';
interface paginationPropTypes {
limit: number;
page: number;
offset: number;
total: number;
maxPages: number;
}
interface webhookDataTypes {
[WebhookInputDataFields.ID]: string;
[WebhookInputDataFields.EVENT_NAME]: string;
[WebhookInputDataFields.ENDPOINT]: string;
[WebhookInputDataFields.ENABLED]: boolean;
[WebhookInputDataFields.HEADERS]?: Record<string, string>;
}
const Webhooks = () => { const Webhooks = () => {
const client = useClient();
const [loading, setLoading] = useState<boolean>(false);
const [webhookData, setWebhookData] = useState<webhookDataTypes[]>([]);
const [paginationProps, setPaginationProps] = useState<paginationPropTypes>({
limit: 5,
page: 1,
offset: 0,
total: 0,
maxPages: 1,
});
const getMaxPages = (pagination: paginationPropTypes) => {
const { limit, total } = pagination;
if (total > 1) {
return total % limit === 0
? total / limit
: parseInt(`${total / limit}`) + 1;
} else return 1;
};
const fetchWebookData = async () => {
setLoading(true);
const res = await client.query(WebhooksDataQuery).toPromise();
if (res.data?._webhooks) {
const { pagination, webhooks } = res.data?._webhooks;
const maxPages = getMaxPages(pagination);
if (webhooks?.length) {
setWebhookData(webhooks);
setPaginationProps({ ...paginationProps, ...pagination, maxPages });
} else {
if (paginationProps.page !== 1) {
setPaginationProps({
...paginationProps,
...pagination,
maxPages,
page: 1,
});
}
}
}
setLoading(false);
};
useEffect(() => {
fetchWebookData();
}, []);
const paginationHandler = (value: Record<string, number>) => {
setPaginationProps({ ...paginationProps, ...value });
};
console.log('webhookData ==>> ', webhookData);
return ( return (
<Box m="5" py="5" px="10" bg="white" rounded="md"> <Box m="5" py="5" px="10" bg="white" rounded="md">
<Flex margin="2% 0" justifyContent="space-between" alignItems="center"> <Flex margin="2% 0" justifyContent="space-between" alignItems="center">
@ -11,6 +112,218 @@ const Webhooks = () => {
</Text> </Text>
<AddWebhookModal /> <AddWebhookModal />
</Flex> </Flex>
{!loading ? (
webhookData.length ? (
<Table variant="simple">
<Thead>
<Tr>
<Th>Event Name</Th>
<Th>Endpoint</Th>
<Th>Enabled</Th>
<Th>Headers</Th>
<Th>Actions</Th>
</Tr>
</Thead>
<Tbody>
{webhookData.map((webhook: webhookDataTypes, index: number) => (
<Tr
key={webhook[WebhookInputDataFields.ID]}
style={{ fontSize: 14 }}
>
<Td maxW="300">
{webhook[WebhookInputDataFields.EVENT_NAME]}
</Td>
<Td>{webhook[WebhookInputDataFields.ENDPOINT]}</Td>
<Td>
<Tag
size="sm"
variant="outline"
colorScheme={
webhook[WebhookInputDataFields.ENABLED]
? 'green'
: 'yellow'
}
>
{webhook[WebhookInputDataFields.ENABLED].toString()}
</Tag>
</Td>
<Td>
{
Object.keys(webhook[WebhookInputDataFields.HEADERS] || {})
?.length
}
</Td>
<Td>
<Menu>
<MenuButton as={Button} variant="unstyled" size="sm">
<Flex
justifyContent="space-between"
alignItems="center"
>
<Text fontSize="sm" fontWeight="light">
Menu
</Text>
<FaAngleDown style={{ marginLeft: 10 }} />
</Flex>
</MenuButton>
<MenuList>
<MenuItem onClick={() => {}}>Edit</MenuItem>
<MenuItem onClick={() => {}}>Delete</MenuItem>
<MenuItem onClick={() => {}}>View Logs</MenuItem>
</MenuList>
</Menu>
</Td>
</Tr>
))}
</Tbody>
{(paginationProps.maxPages > 1 || paginationProps.total >= 5) && (
<TableCaption>
<Flex
justifyContent="space-between"
alignItems="center"
m="2% 0"
>
<Flex flex="1">
<Tooltip label="First Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: 1,
})
}
isDisabled={paginationProps.page <= 1}
mr={4}
icon={<FaAngleDoubleLeft />}
/>
</Tooltip>
<Tooltip label="Previous Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.page - 1,
})
}
isDisabled={paginationProps.page <= 1}
icon={<FaAngleLeft />}
/>
</Tooltip>
</Flex>
<Flex
flex="8"
justifyContent="space-evenly"
alignItems="center"
>
<Text mr={8}>
Page{' '}
<Text fontWeight="bold" as="span">
{paginationProps.page}
</Text>{' '}
of{' '}
<Text fontWeight="bold" as="span">
{paginationProps.maxPages}
</Text>
</Text>
<Flex alignItems="center">
<Text flexShrink="0">Go to page:</Text>{' '}
<NumberInput
ml={2}
mr={8}
w={28}
min={1}
max={paginationProps.maxPages}
onChange={(value) =>
paginationHandler({
page: parseInt(value),
})
}
value={paginationProps.page}
>
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</Flex>
<Select
w={32}
value={paginationProps.limit}
onChange={(e) =>
paginationHandler({
page: 1,
limit: parseInt(e.target.value),
})
}
>
{pageLimits.map((pageSize) => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</Select>
</Flex>
<Flex flex="1">
<Tooltip label="Next Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.page + 1,
})
}
isDisabled={
paginationProps.page >= paginationProps.maxPages
}
icon={<FaAngleRight />}
/>
</Tooltip>
<Tooltip label="Last Page">
<IconButton
aria-label="icon button"
onClick={() =>
paginationHandler({
page: paginationProps.maxPages,
})
}
isDisabled={
paginationProps.page >= paginationProps.maxPages
}
ml={4}
icon={<FaAngleDoubleRight />}
/>
</Tooltip>
</Flex>
</Flex>
</TableCaption>
)}
</Table>
) : (
<Flex
flexDirection="column"
minH="25vh"
justifyContent="center"
alignItems="center"
>
<Center w="50px" marginRight="1.5%">
<FaExclamationCircle style={{ color: '#f0f0f0', fontSize: 70 }} />
</Center>
<Text
fontSize="2xl"
paddingRight="1%"
fontWeight="bold"
color="#d9d9d9"
>
No Data
</Text>
</Flex>
)
) : (
<Center minH="25vh">
<Spinner />
</Center>
)}
</Box> </Box>
); );
}; };