Add ExplorerCard
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import { Card, CardHeader, CardContent, Typography, Box } from "@mui/material";
|
||||
import React, { FC, ReactElement, ReactEventHandler } from "react";
|
||||
import { ExplorerLineChart, IExplorerLineChartData } from "./ExplorerLineChart";
|
||||
import {
|
||||
ExplorerProgressBar,
|
||||
IExplorerProgressBarProps,
|
||||
} from "./ExplorerProgressBar";
|
||||
|
||||
interface ICardUpDownPriceLineProps {
|
||||
percentage: number;
|
||||
@@ -10,8 +15,8 @@ const CardUpDownPriceLine = (
|
||||
): ReactElement => {
|
||||
const { percentage, priceWentUp } = props;
|
||||
return (
|
||||
<Box>
|
||||
<Typography>{percentage} (24H)</Typography>
|
||||
<Box mb={3}>
|
||||
<Typography sx={{ color: "#00CA33" }}>{percentage}% (24H)</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -23,10 +28,10 @@ interface ICardTitlePriceProps {
|
||||
const CardTitlePrice = (props: ICardTitlePriceProps): React.ReactNode => {
|
||||
const { price, upDownLine } = props;
|
||||
return (
|
||||
<Box>
|
||||
<Box>
|
||||
<Box display={"flex"} flexDirection={"column"} alignItems={"flex-end"}>
|
||||
<Box display={"flex"} justifyContent={"space-between"} width={"100%"}>
|
||||
<Typography>NYM</Typography>
|
||||
<Typography>{price}</Typography>
|
||||
<Typography>${price}</Typography>
|
||||
</Box>
|
||||
<CardUpDownPriceLine {...upDownLine} />
|
||||
</Box>
|
||||
@@ -39,10 +44,17 @@ interface ICardDataRowsProps {
|
||||
const CardDataRows = (props: ICardDataRowsProps): React.ReactNode => {
|
||||
const { rows } = props;
|
||||
return (
|
||||
<Box>
|
||||
<Box mb={3}>
|
||||
{rows.map((row, i) => {
|
||||
return (
|
||||
<Box key={i}>
|
||||
<Box
|
||||
key={i}
|
||||
paddingTop={2}
|
||||
paddingBottom={2}
|
||||
display={"flex"}
|
||||
justifyContent={"space-between"}
|
||||
borderBottom={i === 0 ? "1px solid #fff" : "none"}
|
||||
>
|
||||
<Typography>{row.key}</Typography>
|
||||
<Typography>{row.value}</Typography>
|
||||
</Box>
|
||||
@@ -58,8 +70,8 @@ type ContentCardProps = {
|
||||
upDownLine?: ICardUpDownPriceLineProps;
|
||||
titlePrice?: ICardTitlePriceProps;
|
||||
dataRows?: ICardDataRowsProps;
|
||||
graph?: React.ReactNode;
|
||||
progressLineGraph?: React.ReactNode;
|
||||
graph?: Array<IExplorerLineChartData>;
|
||||
progressBar?: IExplorerProgressBarProps;
|
||||
paragraph?: string;
|
||||
onClick?: ReactEventHandler;
|
||||
};
|
||||
@@ -71,16 +83,36 @@ export const ExplorerCard: FC<ContentCardProps> = ({
|
||||
upDownLine,
|
||||
dataRows,
|
||||
graph,
|
||||
progressLineGraph,
|
||||
progressBar,
|
||||
paragraph,
|
||||
onClick,
|
||||
}) => (
|
||||
<Card onClick={onClick} sx={{ height: "100%" }}>
|
||||
<CardContent>
|
||||
{overTitle && <Typography>{overTitle}</Typography>}
|
||||
{title && <Typography>{title}</Typography>}
|
||||
{overTitle && (
|
||||
<Typography fontSize={14} mb={3}>
|
||||
{overTitle}
|
||||
</Typography>
|
||||
)}
|
||||
{title && (
|
||||
<Typography fontSize={24} mb={!upDownLine ? 3 : 0}>
|
||||
{title}
|
||||
</Typography>
|
||||
)}
|
||||
{upDownLine && <CardUpDownPriceLine {...upDownLine} />}
|
||||
{titlePrice && <CardTitlePrice {...titlePrice} />}
|
||||
{dataRows && <CardDataRows {...dataRows} />}
|
||||
{graph && (
|
||||
<Box mb={3}>
|
||||
<ExplorerLineChart data={graph} />
|
||||
</Box>
|
||||
)}
|
||||
{progressBar && (
|
||||
<Box mb={3}>
|
||||
<ExplorerProgressBar {...progressBar} />
|
||||
</Box>
|
||||
)}
|
||||
{paragraph && <Typography>{paragraph}</Typography>}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ const LineChart = dynamic(
|
||||
}
|
||||
);
|
||||
|
||||
interface IExplorerLineChartData {
|
||||
export interface IExplorerLineChartData {
|
||||
date_utc: string;
|
||||
greenLineNumericData: number;
|
||||
purpleLineNumericData: number;
|
||||
@@ -29,7 +29,7 @@ interface ILineAxes {
|
||||
data: Array<IAxes>;
|
||||
}
|
||||
|
||||
export const PacketsLineChart = ({
|
||||
export const ExplorerLineChart = ({
|
||||
data,
|
||||
}: {
|
||||
data: Array<IExplorerLineChartData>;
|
||||
@@ -60,14 +60,14 @@ export const PacketsLineChart = ({
|
||||
data.map((item: any) => {
|
||||
const axesGreenLineData: IAxes = {
|
||||
x: new Date(item.date_utc),
|
||||
y: item.numericData1,
|
||||
y: item.greenLineNumericData,
|
||||
};
|
||||
|
||||
greenLineData.data.push(axesGreenLineData);
|
||||
|
||||
const axesPurpleLineData: IAxes = {
|
||||
x: new Date(item.date_utc),
|
||||
y: item.numericData2,
|
||||
y: item.purpleLineNumericData,
|
||||
};
|
||||
|
||||
purpleLineData.data.push(axesPurpleLineData);
|
||||
@@ -102,8 +102,8 @@ export const PacketsLineChart = ({
|
||||
enableSlices="x"
|
||||
margin={{
|
||||
bottom: 24,
|
||||
left: 36,
|
||||
right: 20,
|
||||
left: 16,
|
||||
right: 16,
|
||||
top: 20,
|
||||
}}
|
||||
theme={{
|
||||
@@ -129,7 +129,7 @@ export const PacketsLineChart = ({
|
||||
type: "time",
|
||||
format: "%Y-%m-%d",
|
||||
}}
|
||||
yScale={{ min: 150000000, type: "linear" }}
|
||||
yScale={{ min: 1, type: "linear" }}
|
||||
xFormat="time:%Y-%m-%d"
|
||||
axisLeft={{
|
||||
legendOffset: 12,
|
||||
@@ -140,7 +140,7 @@ export const PacketsLineChart = ({
|
||||
axisBottom={{
|
||||
format: "%b %d",
|
||||
legendOffset: -12,
|
||||
tickValues: "every 2 days",
|
||||
tickValues: "every day",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import * as React from "react";
|
||||
import Box from "@mui/material/Box";
|
||||
import LinearProgress from "@mui/material/LinearProgress";
|
||||
import { Typography } from "@mui/material";
|
||||
|
||||
export interface IExplorerProgressBarProps {
|
||||
title?: string;
|
||||
start: string; // Start timestamp as ISO 8601 string
|
||||
end: string; // End timestamp as ISO 8601 string
|
||||
showEpoch: boolean;
|
||||
}
|
||||
|
||||
export const ExplorerProgressBar = (props: IExplorerProgressBarProps) => {
|
||||
const { start, end, showEpoch, title } = props;
|
||||
const [progress, setProgress] = React.useState(0);
|
||||
|
||||
React.useEffect(() => {
|
||||
// Parse the timestamps
|
||||
const startTime = new Date(start).getTime();
|
||||
const endTime = new Date(end).getTime();
|
||||
|
||||
// Validate timestamps
|
||||
if (isNaN(startTime) || isNaN(endTime)) {
|
||||
console.error("Invalid start or end timestamp:", { start, end });
|
||||
return;
|
||||
}
|
||||
|
||||
// Function to calculate progress
|
||||
const calculateProgress = () => {
|
||||
const currentTime = Date.now();
|
||||
if (currentTime < startTime) {
|
||||
return 0;
|
||||
}
|
||||
if (currentTime >= endTime) {
|
||||
return 100;
|
||||
}
|
||||
const elapsed = currentTime - startTime;
|
||||
const total = endTime - startTime;
|
||||
return (elapsed / total) * 100;
|
||||
};
|
||||
|
||||
// Set initial progress and start timer
|
||||
setProgress(calculateProgress());
|
||||
const timer = setInterval(() => {
|
||||
setProgress(calculateProgress());
|
||||
}, 60000); // Update every minute (60000 milliseconds)
|
||||
|
||||
// Cleanup on unmount
|
||||
return () => {
|
||||
clearInterval(timer);
|
||||
};
|
||||
}, [start, end]);
|
||||
|
||||
return (
|
||||
<Box sx={{ width: "100%" }}>
|
||||
{title && <Typography mb={2}>{title}</Typography>}
|
||||
|
||||
<LinearProgress variant="determinate" value={progress} />
|
||||
{showEpoch && (
|
||||
<Box mt={2}>
|
||||
<Typography>Start: {new Date(start).toLocaleString()}</Typography>
|
||||
<Typography>End: {new Date(end).toLocaleString()}</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,26 +1,90 @@
|
||||
'use client'
|
||||
"use client";
|
||||
|
||||
import React, { useEffect } from 'react'
|
||||
import { Box, Grid, Link, Typography } from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
import OpenInNewIcon from '@mui/icons-material/OpenInNew'
|
||||
import { PeopleAlt } from '@mui/icons-material'
|
||||
import { Title } from '@/app/components/Title'
|
||||
import { StatsCard } from '@/app/components/StatsCard'
|
||||
import { MixnodesSVG } from '@/app/icons/MixnodesSVG'
|
||||
import { Icons } from '@/app/components/Icons'
|
||||
import { GatewaysSVG } from '@/app/icons/GatewaysSVG'
|
||||
import { ValidatorsSVG } from '@/app/icons/ValidatorsSVG'
|
||||
import { ContentCard } from '@/app/components/ContentCard'
|
||||
import { WorldMap } from '@/app/components/WorldMap'
|
||||
import { BIG_DIPPER } from '@/app/api/constants'
|
||||
import { formatNumber } from '@/app/utils'
|
||||
import { useMainContext } from './context/main'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import React, { useEffect } from "react";
|
||||
import { Box, Grid, Link, Typography } from "@mui/material";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
|
||||
import { PeopleAlt } from "@mui/icons-material";
|
||||
import { Title } from "@/app/components/Title";
|
||||
import { StatsCard } from "@/app/components/StatsCard";
|
||||
import { MixnodesSVG } from "@/app/icons/MixnodesSVG";
|
||||
import { Icons } from "@/app/components/Icons";
|
||||
import { GatewaysSVG } from "@/app/icons/GatewaysSVG";
|
||||
import { ValidatorsSVG } from "@/app/icons/ValidatorsSVG";
|
||||
import { ContentCard } from "@/app/components/ContentCard";
|
||||
import { WorldMap } from "@/app/components/WorldMap";
|
||||
import { BIG_DIPPER } from "@/app/api/constants";
|
||||
import { formatNumber } from "@/app/utils";
|
||||
import { useMainContext } from "./context/main";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { ExplorerCard } from "./components/ExplorerCard";
|
||||
|
||||
// type ContentCardProps = {
|
||||
// overTitle?: string;
|
||||
// title?: string;
|
||||
// upDownLine?: ICardUpDownPriceLineProps;
|
||||
// titlePrice?: ICardTitlePriceProps;
|
||||
// dataRows?: ICardDataRowsProps;
|
||||
// graph?: Array<IExplorerLineChartData>;
|
||||
// progressBar?: IExplorerProgressBarProps;
|
||||
// paragraph?: string;
|
||||
// onClick?: ReactEventHandler;
|
||||
// };
|
||||
|
||||
const explorerCard = {
|
||||
overTitle: "SINGLE",
|
||||
title: "SINGLE",
|
||||
upDownLine: {
|
||||
percentage: 10,
|
||||
priceWentUp: true,
|
||||
},
|
||||
titlePrice: {
|
||||
price: 1.15,
|
||||
upDownLine: {
|
||||
percentage: 10,
|
||||
priceWentUp: true,
|
||||
},
|
||||
},
|
||||
dataRows: {
|
||||
rows: [
|
||||
{ key: "Market cap", value: "$ 1000000" },
|
||||
{ key: "24H VOL", value: "$ 1000000" },
|
||||
],
|
||||
},
|
||||
graph: [
|
||||
{
|
||||
date_utc: "2024-11-20",
|
||||
greenLineNumericData: 10,
|
||||
purpleLineNumericData: 5,
|
||||
},
|
||||
{
|
||||
date_utc: "2024-11-21",
|
||||
greenLineNumericData: 12,
|
||||
purpleLineNumericData: 6,
|
||||
},
|
||||
{
|
||||
date_utc: "2024-11-22",
|
||||
greenLineNumericData: 9,
|
||||
purpleLineNumericData: 5,
|
||||
},
|
||||
{
|
||||
date_utc: "2024-11-23",
|
||||
greenLineNumericData: 11,
|
||||
purpleLineNumericData: 4,
|
||||
},
|
||||
],
|
||||
progressBar: {
|
||||
title: "Current NGM epoch",
|
||||
start: "2024-11-24T13:26:19Z",
|
||||
end: "2024-11-24T14:26:19Z",
|
||||
showEpoch: true,
|
||||
},
|
||||
paragraph: "Additional line",
|
||||
};
|
||||
|
||||
const PageOverview = () => {
|
||||
const theme = useTheme()
|
||||
const router = useRouter()
|
||||
const theme = useTheme();
|
||||
const router = useRouter();
|
||||
|
||||
const {
|
||||
summaryOverview,
|
||||
@@ -29,7 +93,7 @@ const PageOverview = () => {
|
||||
block,
|
||||
countryData,
|
||||
serviceProviders,
|
||||
} = useMainContext()
|
||||
} = useMainContext();
|
||||
return (
|
||||
<Box component="main" sx={{ flexGrow: 1 }}>
|
||||
<Grid>
|
||||
@@ -40,19 +104,22 @@ const PageOverview = () => {
|
||||
<Grid container spacing={3}>
|
||||
{summaryOverview && (
|
||||
<>
|
||||
<Grid item xs={12} md={4}>
|
||||
<ExplorerCard {...explorerCard} />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={4}>
|
||||
<StatsCard
|
||||
onClick={() => router.push('/network-components/mixnodes')}
|
||||
onClick={() => router.push("/network-components/mixnodes")}
|
||||
title="Mixnodes"
|
||||
icon={<MixnodesSVG />}
|
||||
count={summaryOverview.data?.mixnodes.count || ''}
|
||||
count={summaryOverview.data?.mixnodes.count || ""}
|
||||
errorMsg={summaryOverview?.error}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={4}>
|
||||
<StatsCard
|
||||
onClick={() =>
|
||||
router.push('/network-components/mixnodes?status=active')
|
||||
router.push("/network-components/mixnodes?status=active")
|
||||
}
|
||||
title="Active nodes"
|
||||
icon={<Icons.Mixnodes.Status.Active />}
|
||||
@@ -66,7 +133,7 @@ const PageOverview = () => {
|
||||
<Grid item xs={12} md={4}>
|
||||
<StatsCard
|
||||
onClick={() =>
|
||||
router.push('/network-components/mixnodes?status=standby')
|
||||
router.push("/network-components/mixnodes?status=standby")
|
||||
}
|
||||
title="Standby nodes"
|
||||
color={
|
||||
@@ -82,9 +149,9 @@ const PageOverview = () => {
|
||||
{gateways && (
|
||||
<Grid item xs={12} md={4}>
|
||||
<StatsCard
|
||||
onClick={() => router.push('/network-components/gateways')}
|
||||
onClick={() => router.push("/network-components/gateways")}
|
||||
title="Gateways"
|
||||
count={gateways?.data?.length || ''}
|
||||
count={gateways?.data?.length || ""}
|
||||
errorMsg={gateways?.error}
|
||||
icon={<GatewaysSVG />}
|
||||
/>
|
||||
@@ -94,7 +161,7 @@ const PageOverview = () => {
|
||||
<Grid item xs={12} md={4}>
|
||||
<StatsCard
|
||||
onClick={() =>
|
||||
router.push('/network-components/service-providers')
|
||||
router.push("/network-components/service-providers")
|
||||
}
|
||||
title="Service providers"
|
||||
icon={<PeopleAlt />}
|
||||
@@ -108,7 +175,7 @@ const PageOverview = () => {
|
||||
<StatsCard
|
||||
onClick={() => window.open(`${BIG_DIPPER}/validators`)}
|
||||
title="Validators"
|
||||
count={validators?.data?.count || ''}
|
||||
count={validators?.data?.count || ""}
|
||||
errorMsg={validators?.error}
|
||||
icon={<ValidatorsSVG />}
|
||||
/>
|
||||
@@ -150,7 +217,7 @@ const PageOverview = () => {
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
export default PageOverview
|
||||
export default PageOverview;
|
||||
|
||||
@@ -9,15 +9,16 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mui/x-data-grid": "7.1.1",
|
||||
"@mui/x-date-pickers": "7.1.1",
|
||||
"@nivo/line": "^0.88.0",
|
||||
"@nymproject/nym-validator-client": "0.18.0",
|
||||
"@nymproject/react": "^1.0.0",
|
||||
"@nymproject/nym-validator-client": "1.2.0",
|
||||
"material-react-table": "^2.12.1",
|
||||
"next": "14.1.4",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-error-boundary": "^4.0.13",
|
||||
"material-react-table": "^2.12.1",
|
||||
"@mui/x-date-pickers": "7.1.1",
|
||||
"@mui/x-data-grid": "7.1.1"
|
||||
"react-error-boundary": "^4.0.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20",
|
||||
|
||||
Reference in New Issue
Block a user