diff --git a/web/src/pages/Home/CourtOverview/BarChart.tsx b/web/src/pages/Home/CourtOverview/BarChart.tsx new file mode 100644 index 000000000..d75b707d2 --- /dev/null +++ b/web/src/pages/Home/CourtOverview/BarChart.tsx @@ -0,0 +1,115 @@ +import React, { useCallback } from "react"; +import styled, { useTheme } from "styled-components"; +import { + Chart as ChartJS, + BarElement, + Tooltip, + CategoryScale, + LinearScale, + PointElement, + LineElement, + TimeScale, + ChartOptions, +} from "chart.js"; +import ChartDataLabels from "chartjs-plugin-datalabels"; +import { Bar } from "react-chartjs-2"; +import "chartjs-adapter-moment"; + +ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, TimeScale, Tooltip); +const formatter = new Intl.NumberFormat("en", { notation: "compact" }); + +const BarContainer = styled.div` + height: 220px; + margin-top: 16px; +`; + +ChartJS.register(BarElement); + +export interface IBarChartData { + labels: string[]; + data: number[]; + total: number; +} + +interface IBarChartProps { + chartData: IBarChartData; +} + +const BarChart: React.FC = ({ chartData }) => { + const theme = useTheme(); + const getPercentValue = useCallback( + (value: number) => `${Math.floor((value * 100) / chartData.total)} %`, + [chartData] + ); + + const formatPNKValue = useCallback((value: number) => formatter.format(value), []); + + const tickSize = 5; // suggested, if that many labels can't fit, chart will use even labels + + const options: ChartOptions<"bar"> = { + responsive: true, + maintainAspectRatio: false, + scales: { + x: { + grid: { display: false }, + ticks: { + color: theme.secondaryText, + }, + }, + y: { + grid: { color: theme.stroke, borderDash: [4, 4] }, + ticks: { + color: theme.secondaryText, + stepSize: (chartData.total * tickSize) / 100, + callback: (value) => getPercentValue(value), + }, + max: chartData.total, + }, + }, + plugins: { + datalabels: { + anchor: "end", + align: "top", + offset: -4, + color: theme.primaryText, + font: { + weight: "bold", + }, + formatter: formatPNKValue, + }, + tooltip: { + backgroundColor: theme.whiteBackground, + titleColor: theme.primaryText, + borderColor: theme.stroke, + borderWidth: 1, + displayColors: false, + callbacks: { + label: (context) => getPercentValue(context.parsed.y), + labelTextColor: () => theme.primaryText, + }, + }, + }, + }; + + return ( + + + + ); +}; + +export default BarChart; diff --git a/web/src/pages/Home/CourtOverview/CasesByCourtsChart.tsx b/web/src/pages/Home/CourtOverview/CasesByCourtsChart.tsx index df7e52b05..10e51784c 100644 --- a/web/src/pages/Home/CourtOverview/CasesByCourtsChart.tsx +++ b/web/src/pages/Home/CourtOverview/CasesByCourtsChart.tsx @@ -1,17 +1,5 @@ -import React, { useCallback } from "react"; -import styled, { useTheme } from "styled-components"; - -import { Chart as ChartJS, BarElement } from "chart.js"; -import ChartDataLabels from "chartjs-plugin-datalabels"; -import { Bar } from "react-chartjs-2"; -import "chartjs-adapter-moment"; - -const BarContainer = styled.div` - height: 220px; - margin-top: 16px; -`; - -ChartJS.register(BarElement); +import React from "react"; +import BarChart, { IBarChartData } from "./BarChart"; export type CasesByCourtsChartData = { labels: string[]; cases: number[]; totalCases: number }; @@ -20,82 +8,13 @@ interface ICasesByCourtsChart { } const CasesByCourtsChart: React.FC = ({ data }) => { - const theme = useTheme(); - const getPercentValue = useCallback((value: number) => `${Math.floor((value * 100) / data.totalCases)} %`, [data]); - const tickSize = 5; // this is suggested, if that many labels can't fit, chart will use even labels - - const options = { - responsive: true, - maintainAspectRatio: false, - tooltips: { - position: "nearest", - }, - scales: { - x: { - grid: { display: false }, - ticks: { - color: theme.secondaryText, - }, - }, - y: { - grid: { color: theme.stroke, borderDash: [4, 4] }, - ticks: { - color: theme.secondaryText, - stepSize: (data.totalCases * tickSize) / 100, - callback: (value) => getPercentValue(value), - }, - max: data.totalCases, - }, - }, - plugins: { - datalabels: { - anchor: "end", - align: "top", - offset: -4, - color: theme.primaryText, - font: { - weight: "bold", - }, - }, - tooltip: { - backgroundColor: theme.whiteBackground, - titleColor: theme.primaryText, - borderColor: theme.stroke, - borderWidth: 1, - displayColors: false, - callbacks: { - label: (context) => getPercentValue(context.parsed.y), - labelTextColor: () => theme.primaryText, - }, - }, - }, + const chartData: IBarChartData = { + labels: data.labels, + data: data.cases, + total: data.totalCases, }; - return ( - - { - // eslint-disable-next-line - // @ts-ignore - - } - - ); + return ; }; export default CasesByCourtsChart; diff --git a/web/src/pages/Home/CourtOverview/Chart.tsx b/web/src/pages/Home/CourtOverview/Chart.tsx index 3be709b37..bc1656acf 100644 --- a/web/src/pages/Home/CourtOverview/Chart.tsx +++ b/web/src/pages/Home/CourtOverview/Chart.tsx @@ -12,6 +12,7 @@ import { responsiveSize } from "styles/responsiveSize"; import { StyledSkeleton } from "components/StyledSkeleton"; +import StakedPNKByCourtsChart, { StakedPNKByCourtsChartData } from "./StakedPNKByCourtsChart"; import CasesByCourtsChart, { CasesByCourtsChartData } from "./CasesByCourtsChart"; import TimeSeriesChart from "./TimeSeriesChart"; @@ -28,6 +29,7 @@ const StyledDropdown = styled(DropdownSelect)` const CHART_OPTIONS = [ { text: "Staked PNK", value: "stakedPNK" }, + { text: "Staked PNK per court", value: "stakedPNKPerCourt" }, { text: "Cases", value: "cases" }, { text: "Cases per court", value: "casesPerCourt" }, ]; @@ -81,18 +83,35 @@ const Chart: React.FC = () => { { labels: [], cases: [], totalCases: 0 } ); + const processedStakedPNKData = courtsChartData?.reduce( + (accData: StakedPNKByCourtsChartData, current) => { + return { + labels: [...accData.labels, current.name ?? ""], + stakes: [...accData.stakes, parseFloat(formatUnits(current.stake, 18))], + totalStake: accData.totalStake + parseFloat(formatUnits(current.stake, 18)), + }; + }, + { labels: [], stakes: [], totalStake: 0 } + ); + const ChartComponent = useMemo(() => { switch (chartOption) { case "casesPerCourt": return processedCourtsData ? ( ) : ( - + + ); + case "stakedPNKPerCourt": + return processedStakedPNKData ? ( + + ) : ( + ); default: - return processedData ? : ; + return processedData ? : ; } - }, [processedCourtsData, processedData, chartOption]); + }, [processedCourtsData, processedStakedPNKData, processedData, chartOption]); return ( diff --git a/web/src/pages/Home/CourtOverview/Header.tsx b/web/src/pages/Home/CourtOverview/Header.tsx index c06c981ef..2f5e4d85f 100644 --- a/web/src/pages/Home/CourtOverview/Header.tsx +++ b/web/src/pages/Home/CourtOverview/Header.tsx @@ -1,19 +1,20 @@ import React from "react"; import styled from "styled-components"; +import { responsiveSize } from "styles/responsiveSize"; + import { useNavigate } from "react-router-dom"; import { Button } from "@kleros/ui-components-library"; import Bookmark from "svgs/icons/bookmark.svg"; -import { responsiveSize } from "styles/responsiveSize"; - const StyledHeader = styled.div` display: flex; flex-wrap: wrap; justify-content: space-between; gap: 0 12px; + margin-bottom: ${responsiveSize(16, 0)}; `; const StyledH1 = styled.h1` diff --git a/web/src/pages/Home/CourtOverview/StakedPNKByCourtsChart.tsx b/web/src/pages/Home/CourtOverview/StakedPNKByCourtsChart.tsx new file mode 100644 index 000000000..7873136fa --- /dev/null +++ b/web/src/pages/Home/CourtOverview/StakedPNKByCourtsChart.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import BarChart, { IBarChartData } from "./BarChart"; + +export type StakedPNKByCourtsChartData = { labels: string[]; stakes: number[]; totalStake: number }; + +interface IStakedPNKByCourtsChart { + data: StakedPNKByCourtsChartData; +} + +const StakedPNKByCourtsChart: React.FC = ({ data }) => { + const chartData: IBarChartData = { + labels: data.labels, + data: data.stakes, + total: data.totalStake, + }; + + return ; +}; + +export default StakedPNKByCourtsChart;