diff --git a/.changeset/clear-tables-document.md b/.changeset/clear-tables-document.md new file mode 100644 index 00000000..74ccafeb --- /dev/null +++ b/.changeset/clear-tables-document.md @@ -0,0 +1,5 @@ +--- +"@usace-watermanagement/groundwork-water": patch +--- + +Document pre-fetched CWMSTable data and publish accurate optional prop types. diff --git a/docs/src/pages/docs/tables/index.jsx b/docs/src/pages/docs/tables/index.jsx index 93798082..4044c30c 100644 --- a/docs/src/pages/docs/tables/index.jsx +++ b/docs/src/pages/docs/tables/index.jsx @@ -15,6 +15,40 @@ import { cdaTSHookParams } from "../../../props-declarations/data-hooks"; import { Badge, Text, UsaceBox } from "@usace/groundwork"; import CdaParamsTable from "../../components/cda-params-table.jsx"; +const preloadedTimeseriesParams = [ + { + tsid: "DEMO.Stage.Inst.1Hour.0.Observed", + header: "Stage (ft)", + precision: 2, + }, + { + tsid: "DEMO.Flow.Inst.1Hour.0.Observed", + header: "Flow (cfs)", + precision: 0, + }, +]; + +const preloadedTimeSeries = [ + { + name: "DEMO.Stage.Inst.1Hour.0.Observed", + units: "ft", + values: [ + [Date.parse("2026-08-18T08:00:00-05:00"), 12.34, 0], + [Date.parse("2026-08-18T09:00:00-05:00"), 12.41, 0], + [Date.parse("2026-08-18T10:00:00-05:00"), 12.47, 0], + ], + }, + { + name: "DEMO.Flow.Inst.1Hour.0.Observed", + units: "cfs", + values: [ + [Date.parse("2026-08-18T08:00:00-05:00"), 842, 0], + [Date.parse("2026-08-18T09:00:00-05:00"), 875, 0], + [Date.parse("2026-08-18T10:00:00-05:00"), 901, 0], + ], + }, +]; + function Tables() { const LOOKBACK_HOURS = 24; const tsid = "KEYS.Elev.Inst.1Hour.0.Ccp-Rev"; @@ -47,7 +81,7 @@ function Tables() { precision: 2, }, { - tsid: "SHB.Temp-Air.Inst.0.0.DCP-rev", + tsid: "SHB.Temp-Air.Inst.30Minutes.0.DCP-rev", header: "SHB.Temp-Air (F)", precision: 0, }, @@ -77,12 +111,74 @@ function Tables() { end={cdaParams.end} office={cdaParams.office} timeseriesParams={tableTimeseriesParams} - interval="5" + interval={5} snapTopOfInterval={true} missingString="---" sortAscending={false} tableOptions={{ maxHeight: "45vh", className: "gw-mt-4" }} /> + + + To render data you already have, pass CDA-compatible time-series responses to + inputTSValues. Each + timeseriesParams[].tsid must match a + supplied series name. The value tuples + begin with a Unix timestamp in milliseconds and a value; quality codes and + other CDA metadata may follow. + + + When inputTSValues is supplied, the table + does not make CDA requests, so office, + begin, and + end are not required. + + + + {`const timeseriesParams = [ + { + tsid: "DEMO.Stage.Inst.1Hour.0.Observed", + header: "Stage (ft)", + precision: 2, + }, + { + tsid: "DEMO.Flow.Inst.1Hour.0.Observed", + header: "Flow (cfs)", + precision: 0, + }, +]; + +const inputTSValues = [ + { + name: "DEMO.Stage.Inst.1Hour.0.Observed", + units: "ft", + values: [ + [Date.parse("2026-08-18T08:00:00-05:00"), 12.34, 0], + [Date.parse("2026-08-18T09:00:00-05:00"), 12.41, 0], + [Date.parse("2026-08-18T10:00:00-05:00"), 12.47, 0], + ], + }, + { + name: "DEMO.Flow.Inst.1Hour.0.Observed", + units: "cfs", + values: [ + [Date.parse("2026-08-18T08:00:00-05:00"), 842, 0], + [Date.parse("2026-08-18T09:00:00-05:00"), 875, 0], + [Date.parse("2026-08-18T10:00:00-05:00"), 901, 0], + ], + }, +]; + +`} + The header for the table can be set to an HTML tag or component with line @@ -100,7 +196,7 @@ const tableTimeseriesParams = [ ]`} - Note: Using "\n" will NOT create a line break in the header. + Note: Using "\n" will NOT create a line break in the header.
@@ -136,7 +232,7 @@ default export function Example() { precision: 2, }, { - tsid: "SHB.Temp-Air.Inst.0.0.DCP-rev", + tsid: "SHB.Temp-Air.Inst.30Minutes.0.DCP-rev", header: "SHB.Temp-Air (F)", precision: 0, }, @@ -150,7 +246,7 @@ default export function Example() { end={cdaParams.end} office={cdaParams.office} timeseriesParams={tableTimeseriesParams} - interval="5" + interval={5} snapTopOfInterval={true} missingString="---" sortAscending={false} diff --git a/docs/src/props-declarations/tables.jsx b/docs/src/props-declarations/tables.jsx index 4a0041a1..3200d879 100644 --- a/docs/src/props-declarations/tables.jsx +++ b/docs/src/props-declarations/tables.jsx @@ -183,9 +183,18 @@ const cwmsTableParams = [ }, { name: "inputTSValues", - type: "array", + type: "CWMSTableSeries[]", required: false, - desc: "Previously fetched CWMS time-series responses. When supplied, the table renders these values without making CDA requests and does not require an office.", + desc: ( + <> + Previously fetched CWMS time-series responses shaped as{" "} + {`{ name, units?, values: [[timestamp, value, qualityCode?, ...], ...] }`} + . Each series name must match a{" "} + timeseriesParams[].tsid. When supplied, the table makes no CDA + requests and does not require office, begin, or{" "} + end. + + ), }, { name: "dateTimeTableColumnHeader", diff --git a/docs/vite.config.js b/docs/vite.config.js index a57e3477..81b06f44 100644 --- a/docs/vite.config.js +++ b/docs/vite.config.js @@ -11,6 +11,7 @@ export default defineConfig(({ mode }) => { plugins: [react(), tailwindcss()], base: base, resolve: { + dedupe: ["react", "react-dom", "@tanstack/react-query"], alias: isDevelopment ? [ // During development, alias to the source files for easier debugging diff --git a/lib/components/data/tables/CWMSTable.jsx b/lib/components/data/tables/CWMSTable.jsx index 9db76594..61e52b60 100644 --- a/lib/components/data/tables/CWMSTable.jsx +++ b/lib/components/data/tables/CWMSTable.jsx @@ -35,6 +35,60 @@ function TableMessage({ children, tone = "info" }) { ); } +/** + * A CWMS time-series value tuple. The first two entries are the Unix timestamp in + * milliseconds and the value. CDA responses may include quality codes and other + * metadata in later entries. + * + * @typedef {[number, number | string | null, ...unknown[]]} CWMSTableValue + */ + +/** + * @typedef {Object} CWMSTableSeries + * @property {string} name Fully qualified time-series ID. + * @property {string} [units] Units returned for the series. + * @property {CWMSTableValue[]} values Time-series value tuples. + */ + +/** + * @typedef {Object} CWMSTableTimeseriesParam + * @property {string} tsid Fully qualified time-series ID matching a series name. + * @property {import("react").ReactNode} [header] Column heading. + * @property {number} [precision] Number of decimal places to display. + * @property {number} [rounding] Alias for precision. + */ + +/** + * @typedef {Record & { + * className?: string, + * maxHeight?: string, + * overflowHeight?: string + * }} CWMSTableOptions + */ + +/** + * @typedef {Object} CWMSTableProps + * @property {CWMSTableTimeseriesParam[]} timeseriesParams Series and columns to show. + * @property {string} [office] Owning office used when fetching from CDA. + * @property {string} [unit="EN"] Unit or unit system used when fetching from CDA. + * @property {string} [datum] Elevation datum used when fetching from CDA. + * @property {string} [begin] Beginning of the requested time range. + * @property {string} [end] End of the requested time range. + * @property {string} [timezone] Time zone for begin and end. + * @property {CWMSTableSeries[]} [inputTSValues] Pre-fetched CWMS time-series responses. + * @property {boolean} [trim=true] Whether CDA should trim leading and trailing missing values. + * @property {number | string} [pageSize] Maximum number of values requested from CDA. + * @property {number | string} [interval=1] Display interval in minutes. + * @property {boolean} [snapTopOfInterval=true] Whether interval filtering is clock-aligned. + * @property {boolean} [sortAscending=true] Whether rows are sorted oldest first. + * @property {string} [missingString=""] Text shown for missing values. + * @property {string} [dateFormat="ddd MMM DD HH:mm"] Day.js display format. + * @property {string} [cdaUrl] CDA base URL override. + * @property {string} [dateTimeTableColumnHeader="Date & Time (Local)"] Date column heading. + * @property {CWMSTableOptions} [tableOptions] Virtualized table container options. + */ + +/** @param {CWMSTableProps} props */ export default function CWMSTable({ timeseriesParams, office,