diff --git a/src/pages/internal/metrics/[metricId]/[regionSlug]/index.tsx b/src/pages/internal/metrics/[metricId]/[regionSlug]/index.tsx new file mode 100644 index 0000000..e0a4e69 --- /dev/null +++ b/src/pages/internal/metrics/[metricId]/[regionSlug]/index.tsx @@ -0,0 +1,205 @@ +import { + Breadcrumbs, + CircularProgress, + Container, + Link, + Paper, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, +} from "@mui/material"; +import type { GetStaticPaths, GetStaticProps, NextPage } from "next"; +import { ParsedUrlQuery } from "querystring"; + +import { + MetricData, + MetricLineChart, + MetricLineThresholdChart, + isoDateOnlyString, + useData, +} from "@actnowcoalition/actnow.js"; + +import { cms } from "../../../../../cms"; +import { PageMetaTags } from "../../../../../components/SocialMetaTags"; +import { getRegionFromSlugStrict } from "src/pages/us/[regionSlug]"; +import { metricCatalog } from "src/utils/metrics"; +import { regions } from "src/utils/regions"; +import { getRegionSlug } from "src/utils/routing"; + +const MetricPage: NextPage<{ regionId?: string; metricId?: string }> = ({ + regionId, + metricId, +}) => { + metricId = metricId ?? metricCatalog.metrics[0].id; + regionId = regionId ?? regions.all[0].regionId; + + const metric = metricCatalog.getMetric(metricId); + const region = regions.findByRegionIdStrict(regionId); + const metricFullName = metric.extendedName + ? `${metric.name}: ${metric.extendedName}` + : metric.name; + + // TODO(michael): Fetching timeseries for all regions could be expensive. We might want to make this optional. + const { data, error } = useData(region, metric, /*includeTimeseries=*/ true); + + const thresholds = metric.categoryThresholds; + const categories = metric.categorySet?.categories; + + const haveTimeseries = data?.hasTimeseries() && data?.timeseries?.hasData(); + const useThresholdChart = thresholds?.length && categories; + + return ( + <> + + + {/* TODO: Implement a basic version in the packages repo */} + + + Home + + + Internal + + + Metrics + + + {metricFullName} + + + {region.fullName} + + + + Metric ID: {metric.id} + Metric Name: {metricFullName} + Region: {region.fullName} + {error && ( + + Failed to fetch data: +
error
+ + More details may be available in the console. + +
+ )} + + {data && ( + + Data + + <>Current value: {data?.formatValue()} + {haveTimeseries && ( + + + Chart + + {useThresholdChart ? ( + + ) : ( + + )} + + + Raw Data + + + + )} + + + )} + + {!data && !error && } +
+ + ); +}; + +const DataTable = ({ data }: { data: MetricData }) => { + return ( + + + + + Date + Value + + + + {data.timeseries.points.map((row) => ( + + + {isoDateOnlyString(row.date)} + + + {data.metric.formatValue(row.value)} + + + ))} + +
+
+ ); +}; + +interface MetricRegionPageParams extends ParsedUrlQuery { + regionSlug: string; + metricId: string; +} + +export const getStaticProps: GetStaticProps = async ({ params }) => { + const { metricId, regionSlug } = params as MetricRegionPageParams; + const region = getRegionFromSlugStrict(regionSlug); + return { props: { metricId, regionId: region.regionId } }; +}; + +export const getStaticPaths: GetStaticPaths = async () => { + // Prerendering pages for all metric+region combinations is slow and can cause + // failed builds. So we use fallback rendering. + return { paths: [], fallback: true }; + + // const paths = regions.all + // .map((region) => + // metricCatalog.metrics.map((metric) => ({ + // params: { regionSlug: getRegionSlug(region), metricId: metric.id }, + // })) + // ) + // .flat(); + // return { paths, fallback: false }; +}; + +export default MetricPage; diff --git a/src/pages/internal/metrics/[metricId]/index.tsx b/src/pages/internal/metrics/[metricId]/index.tsx new file mode 100644 index 0000000..5a805d7 --- /dev/null +++ b/src/pages/internal/metrics/[metricId]/index.tsx @@ -0,0 +1,192 @@ +import { + Breadcrumbs, + CircularProgress, + Container, + Link, + Paper, + Stack, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, +} from "@mui/material"; +import type { GetStaticPaths, GetStaticProps, NextPage } from "next"; +import { ParsedUrlQuery } from "querystring"; + +import { + Metric, + MetricData, + MultiRegionMultiMetricDataStore, + isoDateOnlyString, + useDataForRegionsAndMetrics, +} from "@actnowcoalition/actnow.js"; + +import { cms } from "../../../../cms"; +import { PageMetaTags } from "../../../../components/SocialMetaTags"; +import { metricCatalog } from "src/utils/metrics"; +import { regions } from "src/utils/regions"; +import { getRegionSlug } from "src/utils/routing"; + +const MetricPage: NextPage<{ metricId: string }> = ({ metricId }) => { + const metric = metricCatalog.getMetric(metricId); + const metricFullName = metric.extendedName + ? `${metric.name}: ${metric.extendedName}` + : metric.name; + + // TODO(michael): Fetching timeseries could be expensive. We might want to make this optional. + const { data, error } = useDataForRegionsAndMetrics( + regions.all, + [metric], + /*includeTimeseries=*/ true + ); + + return ( + <> + + + {/* TODO: Implement a basic version in the packages repo */} + + + Home + + + Internal + + + Metrics + + + {metricFullName} + + + + {metric.id} + Name: {metric.name} + Extended name: {metric.extendedName} + + Categories:{" "} + {!metric.categoryValues ? "N/A" : metric.categoryValues.join(", ")} + + + + Definition + +
{JSON.stringify(metric, null, 2)}
+ + + Data Available + + {error && ( + + Failed to fetch data: +
error
+ + More details may be available in the console. + +
+ )} + {data ? ( + + ) : ( + + )} +
+ + ); +}; + +const RegionDataTable = ({ + data, + metric, +}: { + data: MultiRegionMultiMetricDataStore; + metric: Metric; +}) => { + return ( + + + + + Region + Value + Min Date + Max Date + Length + + + + {regions.all.map((region) => ( + + ))} + +
+
+ ); +}; + +const RegionDataRow = ({ data }: { data: MetricData }) => { + const ts = data.timeseries; + return ( + + + + {data.region.fullName} + + + + {data.currentValue ? data.formatValue() : "no data"} + + {!ts.hasData() ? ( + no timeseries + ) : ( + <> + {isoDateOnlyString(ts.minDate)} + {isoDateOnlyString(ts.maxDate)} + {ts.length} + + )} + + ); +}; + +interface MetricPageParams extends ParsedUrlQuery { + metricId: string; +} + +export const getStaticProps: GetStaticProps = async ({ params }) => { + const { metricId } = params as MetricPageParams; + return { props: { metricId } }; +}; + +export const getStaticPaths: GetStaticPaths = async () => { + const paths = metricCatalog.metrics.map((metric) => ({ + params: { metricId: metric.id }, + })); + return { paths, fallback: false }; +}; + +export default MetricPage; diff --git a/src/pages/internal/metrics.tsx b/src/pages/internal/metrics/index.tsx similarity index 61% rename from src/pages/internal/metrics.tsx rename to src/pages/internal/metrics/index.tsx index d98df86..9f7b4e3 100644 --- a/src/pages/internal/metrics.tsx +++ b/src/pages/internal/metrics/index.tsx @@ -1,15 +1,9 @@ -import { useState } from "react"; - import { Breadcrumbs, - Button, Card, - CardActions, CardContent, Container, Link, - Modal, - Paper, Stack, Typography, } from "@mui/material"; @@ -17,8 +11,8 @@ import type { NextPage } from "next"; import { Metric, useMetricCatalog } from "@actnowcoalition/actnow.js"; -import { cms } from "../../cms"; -import { PageMetaTags } from "../../components/SocialMetaTags"; +import { cms } from "../../../cms"; +import { PageMetaTags } from "../../../components/SocialMetaTags"; import { MetricId } from "src/utils/metrics"; const MetricsDirectory: NextPage = () => { @@ -50,7 +44,7 @@ const MetricsDirectory: NextPage = () => { {metricCatalog.metrics.map((metricItem: Metric | MetricId) => { const metric = metricCatalog.getMetric(metricItem); return ( - + ); })} @@ -58,43 +52,19 @@ const MetricsDirectory: NextPage = () => { ); }; -const MetricCard = ({ metric }: { metric: Metric }) => { - const [modalOpen, setModalOpen] = useState(false); - +const MetricInfo = ({ metric }: { metric: Metric }) => { return ( - {metric.name} + {metric.name} ID: {metric.id} Name: {metric.name} Extended name: {metric.extendedName} - - Categories:{" "} - {!metric.categoryValues ? "N/A" : metric.categoryValues.join(", ")} - - - - setModalOpen(false)}> - - {metric.name} -
{JSON.stringify(metric, null, 2)}
-
-
-
); };