Merge branch 'develop' into feat/fe/statuspage-3
This commit is contained in:
commit
12b14e1938
110 files changed
+3944
-4077
No files matched your search
@@ -1,360 +0,0 @@
|
||||
/* TODO
|
||||
|
||||
Should we delete this?
|
||||
*/
|
||||
|
||||
import PropTypes from "prop-types";
|
||||
import { useState, useEffect } from "react";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import {
|
||||
TableContainer,
|
||||
Paper,
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableBody,
|
||||
TablePagination,
|
||||
Box,
|
||||
Typography,
|
||||
Stack,
|
||||
Button,
|
||||
} from "@mui/material";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { setRowsPerPage } from "../../Features/UI/uiSlice";
|
||||
import LeftArrowDouble from "../../assets/icons/left-arrow-double.svg?react";
|
||||
import RightArrowDouble from "../../assets/icons/right-arrow-double.svg?react";
|
||||
import LeftArrow from "../../assets/icons/left-arrow.svg?react";
|
||||
import RightArrow from "../../assets/icons/right-arrow.svg?react";
|
||||
import SelectorVertical from "../../assets/icons/selector-vertical.svg?react";
|
||||
import "./index.css";
|
||||
/**
|
||||
* Component for pagination actions (first, previous, next, last).
|
||||
*
|
||||
* @component
|
||||
* @param {Object} props
|
||||
* @param {number} props.count - Total number of items.
|
||||
* @param {number} props.page - Current page number.
|
||||
* @param {number} props.rowsPerPage - Number of rows per page.
|
||||
* @param {function} props.onPageChange - Callback function to handle page change.
|
||||
*
|
||||
* @returns {JSX.Element} Pagination actions component.
|
||||
*/
|
||||
const TablePaginationActions = (props) => {
|
||||
const { count, page, rowsPerPage, onPageChange } = props;
|
||||
|
||||
const handleFirstPageButtonClick = (event) => {
|
||||
onPageChange(event, 0);
|
||||
};
|
||||
const handleBackButtonClick = (event) => {
|
||||
onPageChange(event, page - 1);
|
||||
};
|
||||
const handleNextButtonClick = (event) => {
|
||||
onPageChange(event, page + 1);
|
||||
};
|
||||
const handleLastPageButtonClick = (event) => {
|
||||
onPageChange(event, Math.max(0, Math.ceil(count / rowsPerPage) - 1));
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ flexShrink: 0, ml: "24px" }}>
|
||||
<Button
|
||||
variant="group"
|
||||
onClick={handleFirstPageButtonClick}
|
||||
disabled={page === 0}
|
||||
aria-label="first page"
|
||||
>
|
||||
<LeftArrowDouble />
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
onClick={handleBackButtonClick}
|
||||
disabled={page === 0}
|
||||
aria-label="previous page"
|
||||
>
|
||||
<LeftArrow />
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
onClick={handleNextButtonClick}
|
||||
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
|
||||
aria-label="next page"
|
||||
>
|
||||
<RightArrow />
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
onClick={handleLastPageButtonClick}
|
||||
disabled={page >= Math.ceil(count / rowsPerPage) - 1}
|
||||
aria-label="last page"
|
||||
>
|
||||
<RightArrowDouble />
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
TablePaginationActions.propTypes = {
|
||||
count: PropTypes.number.isRequired,
|
||||
page: PropTypes.number.isRequired,
|
||||
rowsPerPage: PropTypes.number.isRequired,
|
||||
onPageChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
/**
|
||||
* BasicTable Component
|
||||
* Renders a table with optional pagination.
|
||||
*
|
||||
* @component
|
||||
* @param {Object} props - Component props.
|
||||
* @param {Object} props.data - Data for the table including columns and rows.
|
||||
* @param {Array} props.data.cols - Array of objects for column headers.
|
||||
* @param {number} props.data.cols[].id - Unique identifier for the column.
|
||||
* @param {string} props.data.cols[].name - Name of the column to display as header.
|
||||
* @param {Array} props.data.rows - Array of row objects.
|
||||
* @param {number} props.data.rows[].id - Unique identifier for the row.
|
||||
* @param {Array} props.data.rows[].data - Array of cell data objects for the row.
|
||||
* @param {number} props.data.rows[].data[].id - Unique identifier for the cell.
|
||||
* @param {JSX.Element} props.data.rows[].data[].data - The content to display in the cell.
|
||||
* @param {function} props.data.rows.data.handleClick - Function to call when the row is clicked.
|
||||
* @param {boolean} [props.paginated=false] - Flag to enable pagination.
|
||||
* @param {boolean} [props.reversed=false] - Flag to enable reverse order.
|
||||
* @param {number} props.rowsPerPage- Number of rows per page (table).
|
||||
* @param {string} props.emptyMessage - Message to display when there is no data.
|
||||
* @example
|
||||
* const data = {
|
||||
* cols: [
|
||||
* { id: 1, name: "First Col" },
|
||||
* { id: 2, name: "Second Col" },
|
||||
* { id: 3, name: "Third Col" },
|
||||
* { id: 4, name: "Fourth Col" },
|
||||
* ],
|
||||
* rows: [
|
||||
* {
|
||||
* id: 1,
|
||||
* data: [
|
||||
* { id: 1, data: <div>Data for Row 1 Col 1</div> },
|
||||
* { id: 2, data: <div>Data for Row 1 Col 2</div> },
|
||||
* { id: 3, data: <div>Data for Row 1 Col 3</div> },
|
||||
* { id: 4, data: <div>Data for Row 1 Col 4</div> },
|
||||
* ],
|
||||
* },
|
||||
* {
|
||||
* id: 2,
|
||||
* data: [
|
||||
* { id: 5, data: <div>Data for Row 2 Col 1</div> },
|
||||
* { id: 6, data: <div>Data for Row 2 Col 2</div> },
|
||||
* { id: 7, data: <div>Data for Row 2 Col 3</div> },
|
||||
* { id: 8, data: <div>Data for Row 2 Col 4</div> },
|
||||
* ],
|
||||
* },
|
||||
* ],
|
||||
* };
|
||||
*
|
||||
* <BasicTable data={data} rows={rows} paginated={true} />
|
||||
*/
|
||||
|
||||
const BasicTable = ({ data, paginated, reversed, table, emptyMessage = "No data" }) => {
|
||||
const DEFAULT_ROWS_PER_PAGE = 5;
|
||||
const theme = useTheme();
|
||||
const dispatch = useDispatch();
|
||||
const uiState = useSelector((state) => state.ui);
|
||||
let rowsPerPage = uiState?.[table]?.rowsPerPage ?? DEFAULT_ROWS_PER_PAGE;
|
||||
const [page, setPage] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
setPage(0);
|
||||
}, [data]);
|
||||
|
||||
const handleChangePage = (event, newPage) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (event) => {
|
||||
dispatch(
|
||||
setRowsPerPage({
|
||||
value: parseInt(event.target.value, 10),
|
||||
table: table,
|
||||
})
|
||||
);
|
||||
setPage(0);
|
||||
};
|
||||
|
||||
let displayData = [];
|
||||
|
||||
if (data && data.rows) {
|
||||
let rows = reversed ? [...data.rows].reverse() : data.rows;
|
||||
displayData = paginated
|
||||
? rows.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
|
||||
: rows;
|
||||
}
|
||||
|
||||
if (!data || !data.cols || !data.rows) {
|
||||
return <div>No data</div>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to calculate the range of displayed rows.
|
||||
* @returns {string}
|
||||
*/
|
||||
const getRange = () => {
|
||||
let start = page * rowsPerPage + 1;
|
||||
let end = Math.min(page * rowsPerPage + rowsPerPage, data.rows.length);
|
||||
return `${start} - ${end}`;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<TableContainer component={Paper}>
|
||||
<Table
|
||||
sx={{
|
||||
"&.MuiTable-root :is(.MuiTableHead-root, .MuiTableBody-root) :is(th, td)": {
|
||||
paddingLeft: theme.spacing(12),
|
||||
},
|
||||
"& th": {
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
color: theme.palette.secondary.contrastText,
|
||||
/*
|
||||
TODO all general styling for th should live here
|
||||
*/
|
||||
},
|
||||
"& td": {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.contrastTextSecondary,
|
||||
/*
|
||||
TODO all general styling for td should live here
|
||||
*/
|
||||
},
|
||||
}}
|
||||
>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{data.cols.map((col) => (
|
||||
<TableCell key={col.id}>{col.name}</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{displayData.map((row) => {
|
||||
return (
|
||||
<TableRow
|
||||
sx={{
|
||||
cursor: row.handleClick ? "pointer" : "default",
|
||||
"&:hover": {
|
||||
filter: "brightness(.75)",
|
||||
opacity: 0.75,
|
||||
transition: "filter 0.3s ease, opacity 0.3s ease",
|
||||
},
|
||||
}}
|
||||
key={row.id}
|
||||
onClick={row.handleClick ? row.handleClick : null}
|
||||
>
|
||||
{row.data.map((cell) => {
|
||||
return <TableCell key={cell.id}>{cell.data}</TableCell>;
|
||||
})}
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
{displayData.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
sx={{ textAlign: "center" }}
|
||||
colSpan={data.cols.length}
|
||||
>
|
||||
{emptyMessage}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{paginated && (
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
px={theme.spacing(4)}
|
||||
sx={{
|
||||
"& p": {
|
||||
color: theme.palette.primary.contrastTextTertiary,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
px={theme.spacing(2)}
|
||||
fontSize={12}
|
||||
sx={{ opacity: 0.7 }}
|
||||
>
|
||||
Showing {getRange()} of {data.rows.length} monitor(s)
|
||||
</Typography>
|
||||
<TablePagination
|
||||
component="div"
|
||||
count={data.rows.length}
|
||||
page={page}
|
||||
onPageChange={handleChangePage}
|
||||
rowsPerPage={rowsPerPage}
|
||||
rowsPerPageOptions={[5, 10, 15, 25]}
|
||||
onRowsPerPageChange={handleChangeRowsPerPage}
|
||||
ActionsComponent={TablePaginationActions}
|
||||
labelRowsPerPage="Rows per page"
|
||||
labelDisplayedRows={({ page, count }) =>
|
||||
`Page ${page + 1} of ${Math.max(0, Math.ceil(count / rowsPerPage))}`
|
||||
}
|
||||
slotProps={{
|
||||
select: {
|
||||
MenuProps: {
|
||||
keepMounted: true,
|
||||
PaperProps: {
|
||||
className: "pagination-dropdown",
|
||||
sx: {
|
||||
mt: 0,
|
||||
mb: theme.spacing(2),
|
||||
},
|
||||
},
|
||||
transformOrigin: { vertical: "bottom", horizontal: "left" },
|
||||
anchorOrigin: { vertical: "top", horizontal: "left" },
|
||||
sx: { mt: theme.spacing(-2) },
|
||||
},
|
||||
inputProps: { id: "pagination-dropdown" },
|
||||
IconComponent: SelectorVertical,
|
||||
sx: {
|
||||
ml: theme.spacing(4),
|
||||
mr: theme.spacing(12),
|
||||
minWidth: theme.spacing(20),
|
||||
textAlign: "left",
|
||||
"&.Mui-focused > div": {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
sx={{
|
||||
mt: theme.spacing(6),
|
||||
color: theme.palette.primary.contrastTextSecondary,
|
||||
"& svg path": {
|
||||
stroke: theme.palette.primary.contrastTextTertiary,
|
||||
strokeWidth: 1.3,
|
||||
},
|
||||
"& .MuiSelect-select": {
|
||||
border: 1,
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
BasicTable.propTypes = {
|
||||
data: PropTypes.object.isRequired,
|
||||
paginated: PropTypes.bool,
|
||||
reversed: PropTypes.bool,
|
||||
rowPerPage: PropTypes.number,
|
||||
table: PropTypes.string,
|
||||
emptyMessage: PropTypes.string,
|
||||
};
|
||||
|
||||
export default BasicTable;
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Box } from "@mui/material";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@emotion/react";
|
||||
const CircularCount = ({ count }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box
|
||||
component="span"
|
||||
color={theme.palette.tertiary.contrastText}
|
||||
border={2}
|
||||
borderColor={theme.palette.accent.main}
|
||||
backgroundColor={theme.palette.tertiary.main}
|
||||
sx={{
|
||||
padding: ".25em .75em",
|
||||
borderRadius: "50rem",
|
||||
fontSize: "12px",
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{count}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
CircularCount.propTypes = {
|
||||
count: PropTypes.number,
|
||||
};
|
||||
|
||||
export default CircularCount;
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Stack, Button } from "@mui/material";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import PropTypes from "prop-types";
|
||||
import SkeletonLayout from "./skeleton";
|
||||
|
||||
const CreateMonitorHeader = ({ isAdmin, shouldRender, path }) => {
|
||||
const navigate = useNavigate();
|
||||
if (!isAdmin) return null;
|
||||
if (!shouldRender) return <SkeletonLayout />;
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="end"
|
||||
alignItems="center"
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
onClick={() => navigate(path)}
|
||||
>
|
||||
Create new
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateMonitorHeader;
|
||||
|
||||
CreateMonitorHeader.propTypes = {
|
||||
isAdmin: PropTypes.bool.isRequired,
|
||||
shouldRender: PropTypes.bool.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
import { Stack, Skeleton } from "@mui/material";
|
||||
|
||||
const SkeletonLayout = () => {
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="end"
|
||||
alignItems="center"
|
||||
>
|
||||
<Skeleton
|
||||
variant="rectangular"
|
||||
width={100}
|
||||
height={36}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SkeletonLayout;
|
||||
@@ -0,0 +1,23 @@
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const Dot = ({ color = "gray", size = "4px" }) => {
|
||||
return (
|
||||
<span
|
||||
style={{
|
||||
content: '""',
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: "50%",
|
||||
backgroundColor: color,
|
||||
opacity: 0.8,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Dot.propTypes = {
|
||||
color: PropTypes.string,
|
||||
size: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Dot;
|
||||
@@ -26,58 +26,69 @@ const Fallback = ({ title, checks, link = "/", isAdmin, vowelStart = false }) =>
|
||||
const mode = useSelector((state) => state.ui.mode);
|
||||
|
||||
return (
|
||||
<Stack
|
||||
className={`fallback__${title.trim().split(" ")[0]}`}
|
||||
alignItems="center"
|
||||
gap={theme.spacing(20)}
|
||||
<Box
|
||||
className="page-speed"
|
||||
position="relative"
|
||||
border={1}
|
||||
borderColor={theme.palette.primary.lowContrast}
|
||||
borderRadius={theme.shape.borderRadius}
|
||||
borderStyle="dashed"
|
||||
backgroundColor={theme.palette.primary.main}
|
||||
overflow="hidden"
|
||||
>
|
||||
{mode === "light" ? (
|
||||
<Skeleton style={{ zIndex: 1 }} />
|
||||
) : (
|
||||
<SkeletonDark style={{ zIndex: 1 }} />
|
||||
)}
|
||||
<Box
|
||||
className="background-pattern-svg"
|
||||
sx={{
|
||||
"& svg g g:last-of-type path": {
|
||||
stroke: theme.palette.primary.lowContrast,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Background style={{ width: "100%" }} />
|
||||
</Box>
|
||||
<Stack
|
||||
gap={theme.spacing(4)}
|
||||
maxWidth={"300px"}
|
||||
zIndex={1}
|
||||
className={`fallback__${title.trim().split(" ")[0]}`}
|
||||
alignItems="center"
|
||||
gap={theme.spacing(20)}
|
||||
>
|
||||
<Typography
|
||||
component="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
{mode === "light" ? (
|
||||
<Skeleton style={{ zIndex: 1 }} />
|
||||
) : (
|
||||
<SkeletonDark style={{ zIndex: 1 }} />
|
||||
)}
|
||||
<Box
|
||||
className="background-pattern-svg"
|
||||
sx={{
|
||||
"& svg g g:last-of-type path": {
|
||||
stroke: theme.palette.primary.lowContrast,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{vowelStart ? "An" : "A"} {title} is used to:
|
||||
</Typography>
|
||||
{checks.map((check, index) => (
|
||||
<Check
|
||||
text={check}
|
||||
key={`${title.trim().split(" ")[0]}-${index}`}
|
||||
outlined={true}
|
||||
/>
|
||||
))}
|
||||
<Background style={{ width: "100%" }} />
|
||||
</Box>
|
||||
<Stack
|
||||
gap={theme.spacing(4)}
|
||||
maxWidth={"300px"}
|
||||
zIndex={1}
|
||||
>
|
||||
<Typography
|
||||
component="h1"
|
||||
marginY={theme.spacing(4)}
|
||||
color={theme.palette.primary.contrastTextTertiary}
|
||||
>
|
||||
{vowelStart ? "An" : "A"} {title} is used to:
|
||||
</Typography>
|
||||
{checks.map((check, index) => (
|
||||
<Check
|
||||
text={check}
|
||||
key={`${title.trim().split(" ")[0]}-${index}`}
|
||||
outlined={true}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
{/* TODO - display a different fallback if user is not an admin*/}
|
||||
{isAdmin && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
sx={{ alignSelf: "center" }}
|
||||
onClick={() => navigate(link)}
|
||||
>
|
||||
Let's create your first{title}
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
{/* TODO - display a different fallback if user is not an admin*/}
|
||||
{isAdmin && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
sx={{ alignSelf: "center" }}
|
||||
onClick={() => navigate(link)}
|
||||
>
|
||||
Let's create your {title}
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import { BaseLabel } from "../Label";
|
||||
const DEFAULT_CODE = 9999; // Default code for unknown status
|
||||
|
||||
const handleStatusCode = (status) => {
|
||||
if (status >= 100 && status < 600) {
|
||||
if (status) {
|
||||
return status;
|
||||
}
|
||||
return DEFAULT_CODE;
|
||||
|
||||
@@ -113,6 +113,14 @@ const Search = ({
|
||||
"& .MuiOutlinedInput-root": {
|
||||
paddingY: 0,
|
||||
},
|
||||
"& .MuiAutocomplete-tag": {
|
||||
// CAIO_REVIEW
|
||||
color: theme.palette.primary.contrastText,
|
||||
backgroundColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
"& .MuiChip-deleteIcon": {
|
||||
color: theme.palette.primary.contrastText, // CAIO_REVIEW
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{error && (
|
||||
@@ -174,6 +182,7 @@ const Search = ({
|
||||
"& .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'], & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true'].Mui-focused, & .MuiAutocomplete-listbox .MuiAutocomplete-option[aria-selected='true']:hover":
|
||||
{
|
||||
backgroundColor: theme.palette.primary.lowContrast,
|
||||
color: "red",
|
||||
},
|
||||
"& li.MuiAutocomplete-option:hover:not([aria-selected='true'])": {
|
||||
color: theme.palette.primary.main,
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { Stack } from "@mui/material";
|
||||
import { Heading } from "../Heading";
|
||||
import CircularCount from "../CircularCount";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import SkeletonLayout from "./skeleton";
|
||||
|
||||
const MonitorCountHeader = ({
|
||||
shouldRender = true,
|
||||
monitorCount,
|
||||
heading,
|
||||
sx,
|
||||
children,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
if (!shouldRender) return <SkeletonLayout />;
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(2)}
|
||||
sx={{ ...sx }}
|
||||
>
|
||||
<Heading component="h2">{heading}</Heading>
|
||||
<CircularCount count={monitorCount} />
|
||||
{children}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
MonitorCountHeader.propTypes = {
|
||||
shouldRender: PropTypes.bool,
|
||||
monitorCount: PropTypes.number,
|
||||
heading: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
sx: PropTypes.object,
|
||||
};
|
||||
|
||||
export default MonitorCountHeader;
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Stack, Skeleton } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
|
||||
const SkeletonLayout = () => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Skeleton
|
||||
variant="text"
|
||||
width={100}
|
||||
height={32}
|
||||
/>
|
||||
<Skeleton
|
||||
variant="circular"
|
||||
width={40}
|
||||
height={40}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SkeletonLayout;
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import PropTypes from "prop-types";
|
||||
import useUtils from "../../Pages/Uptime/utils";
|
||||
import useUtils from "../../Pages/Uptime/Monitors/Hooks/useUtils";
|
||||
|
||||
/**
|
||||
* StatBox Component
|
||||
|
||||
@@ -5,11 +5,12 @@ import { TablePaginationActions } from "./Actions";
|
||||
import SelectorVertical from "../../../assets/icons/selector-vertical.svg?react";
|
||||
|
||||
Pagination.propTypes = {
|
||||
monitorCount: PropTypes.number.isRequired, // Total number of items for pagination.
|
||||
paginationLabel: PropTypes.string, // Label for the pagination.
|
||||
itemCount: PropTypes.number.isRequired, // Total number of items for pagination.
|
||||
page: PropTypes.number.isRequired, // Current page index.
|
||||
rowsPerPage: PropTypes.number.isRequired, // Number of rows displayed per page.
|
||||
handleChangePage: PropTypes.func.isRequired, // Function to handle page changes.
|
||||
handleChangeRowsPerPage: PropTypes.func.isRequired, // Function to handle changes in rows per page.
|
||||
handleChangeRowsPerPage: PropTypes.func, // Function to handle changes in rows per page.
|
||||
};
|
||||
|
||||
const ROWS_PER_PAGE_OPTIONS = [5, 10, 15, 25];
|
||||
@@ -18,6 +19,7 @@ const ROWS_PER_PAGE_OPTIONS = [5, 10, 15, 25];
|
||||
* Pagination component for table navigation with customized styling and behavior.
|
||||
*
|
||||
* @param {object} props - Component properties.
|
||||
* @param {string} props.paginationLabel - Label for the pagination.
|
||||
* @param {number} props.monitorCount - Total number of monitors to paginate.
|
||||
* @param {number} props.page - Current page index (0-based).
|
||||
* @param {number} props.rowsPerPage - Number of rows to display per page.
|
||||
@@ -26,7 +28,8 @@ const ROWS_PER_PAGE_OPTIONS = [5, 10, 15, 25];
|
||||
* @returns {JSX.Element} The Pagination component.
|
||||
*/
|
||||
function Pagination({
|
||||
monitorCount,
|
||||
paginationLabel,
|
||||
itemCount,
|
||||
page,
|
||||
rowsPerPage,
|
||||
handleChangePage,
|
||||
@@ -35,7 +38,7 @@ function Pagination({
|
||||
const theme = useTheme();
|
||||
|
||||
const start = page * rowsPerPage + 1;
|
||||
const end = Math.min(page * rowsPerPage + rowsPerPage, monitorCount);
|
||||
const end = Math.min(page * rowsPerPage + rowsPerPage, itemCount);
|
||||
const range = `${start} - ${end}`;
|
||||
|
||||
return (
|
||||
@@ -51,11 +54,11 @@ function Pagination({
|
||||
variant="body2"
|
||||
sx={{ opacity: 0.7 }}
|
||||
>
|
||||
Showing {range} of {monitorCount} monitor(s)
|
||||
Showing {range} of {itemCount} {paginationLabel}
|
||||
</Typography>
|
||||
<TablePagination
|
||||
component="div"
|
||||
count={monitorCount}
|
||||
count={itemCount}
|
||||
page={page}
|
||||
onPageChange={handleChangePage}
|
||||
rowsPerPage={rowsPerPage}
|
||||
@@ -121,4 +124,4 @@ function Pagination({
|
||||
);
|
||||
}
|
||||
|
||||
export { Pagination };
|
||||
export default Pagination;
|
||||
@@ -82,11 +82,12 @@ const DataTable = ({ headers, data, config = { emptyView: "No data" } }) => {
|
||||
</TableRow>
|
||||
) : (
|
||||
data.map((row) => {
|
||||
const key = row.id || row._id || Math.random();
|
||||
return (
|
||||
<TableRow
|
||||
key={row.id}
|
||||
key={key}
|
||||
sx={config?.rowSX ?? {}}
|
||||
onClick={() => config?.onRowClick(row)}
|
||||
onClick={config?.onRowClick ? () => config.onRowClick(row) : null}
|
||||
>
|
||||
{headers.map((header, index) => {
|
||||
return (
|
||||
|
||||
Reference in new issue
Block a user