+
+ The CWMS Checkboxes component provides a group of checkboxes that
+ integrates with CWMS forms for multi-selection data collection. Uses
+ the full Groundwork checkbox content API with CWMS-specific
+ extensions.
+
+
+
+ Each item in the content array accepts the following properties. The
+ component passes through all standard Groundwork checkbox properties and
+ adds CWMS-specific extensions.
+
+
+
+ );
+}
+
+export { CWMSCheckboxesDocs };
+export default CWMSCheckboxesDocs;
diff --git a/docs/src/pages/docs/forms/cwms-dropdown.jsx b/docs/src/pages/docs/forms/cwms-dropdown.jsx
new file mode 100644
index 0000000..beefb02
--- /dev/null
+++ b/docs/src/pages/docs/forms/cwms-dropdown.jsx
@@ -0,0 +1,244 @@
+import { Text, Code } from "@usace/groundwork";
+import PropsTable from "../../components/props-table";
+import {
+ CWMSDropdown,
+ FormWrapper,
+} from "@usace-watermanagement/groundwork-water";
+import { Code as CodeBlock } from "../../components/code";
+import DocsPage from "../_docs-wrapper";
+import Divider from "../../components/divider";
+
+const componentProps = [
+ {
+ name: "name",
+ type: "string",
+ default: "undefined",
+ desc: "The name attribute for the dropdown.",
+ },
+ {
+ name: "options",
+ type: "array",
+ default: "[]",
+ desc: "Array of string options for the dropdown.",
+ },
+ {
+ name: "value",
+ type: "string",
+ default: "undefined",
+ desc: "The controlled selected value.",
+ },
+ {
+ name: "defaultValue",
+ type: "string",
+ default: "undefined",
+ desc: "The default selected value when uncontrolled.",
+ },
+ {
+ name: "placeholder",
+ type: "string",
+ default: "undefined",
+ desc: "Placeholder text when no option is selected.",
+ },
+ {
+ name: "tsid",
+ type: "string",
+ default: "undefined",
+ desc: "The time series ID for CWMS data association.",
+ },
+ {
+ name: "units",
+ type: "string",
+ default: "EN",
+ desc: "Unit system (EN for English, SI for metric).",
+ },
+ {
+ name: "disable",
+ type: "boolean",
+ default: "false",
+ desc: "Whether the dropdown is disabled.",
+ },
+ {
+ name: "readonly",
+ type: "boolean",
+ default: "false",
+ desc: "Whether the dropdown is read-only.",
+ },
+ {
+ name: "invalid",
+ type: "boolean",
+ default: "false",
+ desc: "Whether the dropdown is in an invalid state.",
+ },
+ {
+ name: "onChange",
+ type: "function",
+ default: "undefined",
+ desc: "Callback function when selection changes.",
+ },
+ {
+ name: "style",
+ type: "object",
+ default: "undefined",
+ desc: "Custom styles to apply to the dropdown.",
+ },
+];
+
+function CWMSDropdownDocs() {
+ return (
+
+
+
+ The CWMS Dropdown component provides a select dropdown that integrates
+ with CWMS forms for single-selection data collection.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {`import { CWMSDropdown } from "@usace-watermanagement/groundwork-water";
+
+// Simple string options
+
+
+// With default value
+
+
+// With placeholder
+`}
+
+
+
+
+ When used within a FormWrapper, CWMSDropdown automatically registers
+ with the form context for data collection and submission to CWMS.
+
+
+
+ {`import { CWMSDropdown } from "@usace-watermanagement/groundwork-water";
+import { FormWrapper } from "@usace-watermanagement/groundwork-water";
+
+
+
+
+
+`}
+
+
+
+
+
+
+
+ );
+}
+
+export { CWMSDropdownDocs };
+export default CWMSDropdownDocs;
diff --git a/docs/src/pages/docs/forms/cwms-input-table.jsx b/docs/src/pages/docs/forms/cwms-input-table.jsx
new file mode 100644
index 0000000..8c1bad2
--- /dev/null
+++ b/docs/src/pages/docs/forms/cwms-input-table.jsx
@@ -0,0 +1,274 @@
+import { Text, Code } from "@usace/groundwork";
+import PropsTable from "../../components/props-table";
+import { CWMSInputTable, FormWrapper } from "@usace-watermanagement/groundwork-water";
+import { Code as CodeBlock } from "../../components/code";
+import DocsPage from "../_docs-wrapper";
+import Divider from "../../components/divider";
+
+const componentProps = [
+ {
+ name: "tsids",
+ type: "array",
+ default: "[]",
+ desc: "Array of time series IDs for column headers.",
+ },
+ {
+ name: "timeoffsets",
+ type: "array",
+ default: "[0]",
+ desc: "Array of time offsets in seconds for row timestamps.",
+ },
+ {
+ name: "defaultValues",
+ type: "object",
+ default: "{}",
+ desc: "Object with default values keyed by 'tsid_offset'.",
+ },
+ {
+ name: "showTimestamps",
+ type: "boolean",
+ default: "true",
+ desc: "Whether to show timestamp column.",
+ },
+ {
+ name: "precision",
+ type: "number",
+ default: "2",
+ desc: "Number of decimal places for numeric values.",
+ },
+ {
+ name: "units",
+ type: "string",
+ default: "EN",
+ desc: "Unit system (EN for English, SI for metric).",
+ },
+ {
+ name: "disable",
+ type: "boolean",
+ default: "false",
+ desc: "Whether all inputs are disabled.",
+ },
+ {
+ name: "readonly",
+ type: "boolean",
+ default: "false",
+ desc: "Whether all inputs are read-only.",
+ },
+ {
+ name: "invalid",
+ type: "boolean",
+ default: "false",
+ desc: "Whether the table is in an invalid state.",
+ },
+ {
+ name: "onChange",
+ type: "function",
+ default: "undefined",
+ desc: "Callback function when any input value changes.",
+ },
+ {
+ name: "style",
+ type: "object",
+ default: "undefined",
+ desc: "Custom styles to apply to the table.",
+ },
+];
+
+function CWMSInputTableDocs() {
+ // const currentTime = Date.now() / 1000;
+
+ return (
+
+
+
+ The CWMS Input Table component provides a matrix of input fields for
+ entering multiple time series values across different time offsets.
+ Its ideal for bulk data entry where you need to input values for
+ multiple parameters at different time points.
+
+
+
+
+
+
+
+
+
+
+
+ {`import { CWMSInputTable } from "@usace-watermanagement/groundwork-water";
+
+`}
+
+
+
+
+ You can provide default values for specific cells using the tsid_offset
+ key format.
+
+
+
+
+
+
+
+
+
+ {``}
+
+
+
+
+ When used within a FormWrapper, CWMSInputTable automatically registers
+ with the form context for bulk data submission to CWMS.
+
+
+
+ {`import { CWMSInputTable } from "@usace-watermanagement/groundwork-water";
+import { FormWrapper } from "@usace-watermanagement/groundwork-water";
+
+
+
+`}
+
+
+
+
+ You can hide the timestamp column for a more compact view.
+
+
+
+
+
+ );
+}
+
+export { CWMSInputTableDocs };
+export default CWMSInputTableDocs;
diff --git a/docs/src/pages/docs/forms/cwms-input.jsx b/docs/src/pages/docs/forms/cwms-input.jsx
new file mode 100644
index 0000000..4f52161
--- /dev/null
+++ b/docs/src/pages/docs/forms/cwms-input.jsx
@@ -0,0 +1,216 @@
+import { Text, Code } from "@usace/groundwork";
+import PropsTable from "../../components/props-table";
+import { CWMSInput, FormWrapper } from "@usace-watermanagement/groundwork-water";
+import { Code as CodeBlock } from "../../components/code";
+import DocsPage from "../_docs-wrapper";
+import Divider from "../../components/divider";
+
+const componentProps = [
+ {
+ name: "name",
+ type: "string",
+ default: "undefined",
+ desc: "The name attribute for the input field.",
+ },
+ {
+ name: "value",
+ type: "string",
+ default: "undefined",
+ desc: "The controlled value of the input.",
+ },
+ {
+ name: "defaultValue",
+ type: "string",
+ default: "undefined",
+ desc: "The default value for the input when uncontrolled.",
+ },
+ {
+ name: "type",
+ type: "string",
+ default: "text",
+ desc: "The input type (text, number, email, password, etc.).",
+ },
+ {
+ name: "placeholder",
+ type: "string",
+ default: "undefined",
+ desc: "Placeholder text for the input.",
+ },
+ {
+ name: "tsid",
+ type: "string",
+ default: "undefined",
+ desc: "The time series ID for CWMS data association.",
+ },
+ {
+ name: "precision",
+ type: "number",
+ default: "2",
+ desc: "Number of decimal places for numeric values.",
+ },
+ {
+ name: "offset",
+ type: "number",
+ default: "0",
+ desc: "Time offset in seconds for data timestamps.",
+ },
+ {
+ name: "units",
+ type: "string",
+ default: "EN",
+ desc: "Unit system (EN for English, SI for metric).",
+ },
+ {
+ name: "disable",
+ type: "boolean",
+ default: "false",
+ desc: "Whether the input is disabled.",
+ },
+ {
+ name: "readonly",
+ type: "boolean",
+ default: "false",
+ desc: "Whether the input is read-only.",
+ },
+ {
+ name: "invalid",
+ type: "boolean",
+ default: "false",
+ desc: "Whether the input is in an invalid state.",
+ },
+ {
+ name: "onChange",
+ type: "function",
+ default: "undefined",
+ desc: "Callback function when the input value changes.",
+ },
+ {
+ name: "style",
+ type: "object",
+ default: "undefined",
+ desc: "Custom styles to apply to the input.",
+ },
+];
+
+function CWMSInputDocs() {
+ return (
+
+
+
+ The CWMS Input component is a wrapper around the standard Groundwork
+ Input component that integrates with CWMS forms for data collection
+ and submission. It supports various input types and can be associated
+ with CWMS time series data.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {`import { CWMSInput } from "@usace-watermanagement/groundwork-water";
+
+
+
+
+
+`}
+
+
+
+
+ When used within a FormWrapper, CWMSInput automatically registers with
+ the form context for data collection and submission to CWMS.
+
+
+
+ {`import { CWMSInput } from "@usace-watermanagement/groundwork-water";
+import { FormWrapper } from "@usace-watermanagement/groundwork-water";
+
+
+
+
+`}
+
+
+
+
+
+
+
+
+
+
+ {``}
+
+
+
+
+ Component API - {``}
+
+
+
+ );
+}
+
+export { CWMSInputDocs };
+export default CWMSInputDocs;
diff --git a/docs/src/pages/docs/forms/cwms-spreadsheet.jsx b/docs/src/pages/docs/forms/cwms-spreadsheet.jsx
new file mode 100644
index 0000000..7a3e268
--- /dev/null
+++ b/docs/src/pages/docs/forms/cwms-spreadsheet.jsx
@@ -0,0 +1,331 @@
+import { Text, Code } from "@usace/groundwork";
+import PropsTable from "../../components/props-table";
+import { CWMSSpreadsheet, FormWrapper } from "@usace-watermanagement/groundwork-water";
+import { Code as CodeBlock } from "../../components/code";
+import DocsPage from "../_docs-wrapper";
+import Divider from "../../components/divider";
+
+const componentProps = [
+ {
+ name: "columns",
+ type: "array",
+ default: "[]",
+ desc: "Array of column definitions with {key, label, type, placeholder} structure.",
+ },
+ {
+ name: "rows",
+ type: "number",
+ default: "10",
+ desc: "Number of rows in the spreadsheet.",
+ },
+ {
+ name: "defaultData",
+ type: "array",
+ default: "[]",
+ desc: "Array of arrays containing default cell values.",
+ },
+ {
+ name: "showRowNumbers",
+ type: "boolean",
+ default: "true",
+ desc: "Whether to show row numbers.",
+ },
+ {
+ name: "showColumnHeaders",
+ type: "boolean",
+ default: "true",
+ desc: "Whether to show column headers.",
+ },
+ {
+ name: "resizable",
+ type: "boolean",
+ default: "false",
+ desc: "Whether columns are resizable (future feature).",
+ },
+ {
+ name: "tsid",
+ type: "string",
+ default: "undefined",
+ desc: "The time series ID for CWMS data association.",
+ },
+ {
+ name: "precision",
+ type: "number",
+ default: "2",
+ desc: "Number of decimal places for numeric values.",
+ },
+ {
+ name: "units",
+ type: "string",
+ default: "EN",
+ desc: "Unit system (EN for English, SI for metric).",
+ },
+ {
+ name: "disable",
+ type: "boolean",
+ default: "false",
+ desc: "Whether all cells are disabled.",
+ },
+ {
+ name: "readonly",
+ type: "boolean",
+ default: "false",
+ desc: "Whether all cells are read-only.",
+ },
+ {
+ name: "onChange",
+ type: "function",
+ default: "undefined",
+ desc: "Callback function when any cell value changes.",
+ },
+ {
+ name: "style",
+ type: "object",
+ default: "undefined",
+ desc: "Custom styles to apply to the container.",
+ },
+];
+
+function CWMSSpreadsheetDocs() {
+ return (
+
+
+
+ The CWMS Spreadsheet component provides an Excel-like grid interface for data entry.
+ It supports keyboard navigation, copy/paste operations, and can handle large datasets
+ with multiple columns and rows.
+
+
+ Excel-like Features:
+
+
+
Multi-cell selection: Click and drag to select multiple cells
+
Range selection: Shift+Click to select a range
+
Keyboard selection: Shift+Arrow keys to extend selection
+
Copy multiple cells: Select cells and press Ctrl+C to copy as tab-separated values
+
Paste from Excel: Copy from Excel/Google Sheets and paste directly
+
Select all: Ctrl+A to select entire spreadsheet
+
Arrow keys - Navigate between cells
+
Enter - Move down to next row
+
Tab / Shift+Tab - Move forward/backward through cells
+
Escape - Clear selection
+
Delete - Clear selected cells
+
+
+
+
+
+
+
+
+
+
+
+ {`import { CWMSSpreadsheet } from "@usace-watermanagement/groundwork-water";
+
+`}
+
+
+
+
+ Pre-populate the spreadsheet with default values.
+
+
+
+
+
+
+
+
+
+ {``}
+
+
+
+
+ You can create a spreadsheet with simple column labels like A, B, C, etc.
+
+
+
+
+
+
+
+
+
+ {`// Creates a 6-column spreadsheet with labeled columns
+`}
+
+
+
+
+ When used within a FormWrapper, CWMSSpreadsheet can submit tabular data to CWMS.
+
+
+
+ {`import { CWMSSpreadsheet } from "@usace-watermanagement/groundwork-water";
+import { FormWrapper } from "@usace-watermanagement/groundwork-water";
+
+
+
+`}
+
+
+
+
+
+
+ );
+}
+
+export { CWMSSpreadsheetDocs };
+export default CWMSSpreadsheetDocs;
\ No newline at end of file
diff --git a/docs/src/pages/docs/forms/cwms-textarea.jsx b/docs/src/pages/docs/forms/cwms-textarea.jsx
new file mode 100644
index 0000000..fd505f1
--- /dev/null
+++ b/docs/src/pages/docs/forms/cwms-textarea.jsx
@@ -0,0 +1,213 @@
+import { Text, Code } from "@usace/groundwork";
+import PropsTable from "../../components/props-table";
+import { CWMSTextarea, FormWrapper } from "@usace-watermanagement/groundwork-water";
+import { Code as CodeBlock } from "../../components/code";
+import DocsPage from "../_docs-wrapper";
+import Divider from "../../components/divider";
+
+const componentProps = [
+ {
+ name: "name",
+ type: "string",
+ default: "undefined",
+ desc: "The name attribute for the textarea.",
+ },
+ {
+ name: "value",
+ type: "string",
+ default: "undefined",
+ desc: "The controlled value of the textarea.",
+ },
+ {
+ name: "defaultValue",
+ type: "string",
+ default: "undefined",
+ desc: "The default value for the textarea when uncontrolled.",
+ },
+ {
+ name: "rows",
+ type: "number",
+ default: "3",
+ desc: "Number of visible text lines.",
+ },
+ {
+ name: "tsid",
+ type: "string",
+ default: "undefined",
+ desc: "The time series ID for CWMS data association.",
+ },
+ {
+ name: "precision",
+ type: "number",
+ default: "2",
+ desc: "Number of decimal places for numeric values.",
+ },
+ {
+ name: "offset",
+ type: "number",
+ default: "0",
+ desc: "Time offset in seconds for data timestamps.",
+ },
+ {
+ name: "units",
+ type: "string",
+ default: "EN",
+ desc: "Unit system (EN for English, SI for metric).",
+ },
+ {
+ name: "disable",
+ type: "boolean",
+ default: "false",
+ desc: "Whether the textarea is disabled.",
+ },
+ {
+ name: "readonly",
+ type: "boolean",
+ default: "false",
+ desc: "Whether the textarea is read-only.",
+ },
+ {
+ name: "invalid",
+ type: "boolean",
+ default: "false",
+ desc: "Whether the textarea is in an invalid state.",
+ },
+ {
+ name: "onChange",
+ type: "function",
+ default: "undefined",
+ desc: "Callback function when the textarea value changes.",
+ },
+ {
+ name: "style",
+ type: "object",
+ default: "undefined",
+ desc: "Custom styles to apply to the textarea.",
+ },
+];
+
+function CWMSTextareaDocs() {
+ return (
+
+
+
+ The CWMS Textarea component is a wrapper around the standard Groundwork Textarea
+ component that integrates with CWMS forms for multi-line text input and data submission.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {`import { CWMSTextarea } from "@usace-watermanagement/groundwork-water";
+
+
+
+
+
+`}
+
+
+
+
+ When used within a FormWrapper, CWMSTextarea automatically registers with the form
+ context for data collection and submission to CWMS.
+
+
+
+ {`import { CWMSTextarea } from "@usace-watermanagement/groundwork-water";
+import { FormWrapper } from "@usace-watermanagement/groundwork-water";
+
+
+
+
+`}
+
+
+
+
+
+
+
+
+
+
+ {``}
+
+
+
+
+ Component API - {``}
+
+
+
+ );
+}
+
+export { CWMSTextareaDocs };
+export default CWMSTextareaDocs;
\ No newline at end of file
diff --git a/docs/src/pages/docs/forms/form-wrapper.jsx b/docs/src/pages/docs/forms/form-wrapper.jsx
new file mode 100644
index 0000000..cbe5da2
--- /dev/null
+++ b/docs/src/pages/docs/forms/form-wrapper.jsx
@@ -0,0 +1,1064 @@
+import { useState, useEffect } from "react";
+import { Text, Code, Input, Button } from "@usace/groundwork";
+import PropsTable from "../../components/props-table";
+import {
+ FormWrapper,
+ CWMSInput,
+ CWMSTextarea,
+ CWMSDropdown,
+ CWMSCheckboxes,
+ AuthProvider,
+ useAuth,
+ createCwmsLoginAuthMethod,
+ createKeycloakAuthMethod,
+} from "@usace-watermanagement/groundwork-water";
+import { Code as CodeBlock } from "../../components/code";
+import DocsPage from "../_docs-wrapper";
+import Divider from "../../components/divider";
+
+const componentProps = [
+ {
+ name: "office",
+ type: "string",
+ default: "required",
+ desc: "USACE office symbol (e.g., 'SWT', 'NWD').",
+ },
+ {
+ name: "cdaUrl",
+ type: "string",
+ default: "undefined",
+ desc: "URL for CWMS Data API. If not provided, uses the URL from CdaUrlProvider.",
+ },
+ {
+ name: "unit",
+ type: "string",
+ default: "EN",
+ desc: "Unit system for data submission (EN for English, SI for metric).",
+ },
+ {
+ name: "children",
+ type: "ReactNode",
+ default: "required",
+ desc: "Form input components to be wrapped.",
+ },
+ {
+ name: "onSubmit",
+ type: "function",
+ default: "undefined",
+ desc: "Custom submit handler. Receives (formData, event). If not provided, submits to CWMS.",
+ },
+ {
+ name: "onReset",
+ type: "function",
+ default: "undefined",
+ desc: "Custom reset handler. Called after form inputs are reset.",
+ },
+ {
+ name: "submitText",
+ type: "string",
+ default: "Submit",
+ desc: "Text for the submit button.",
+ },
+ {
+ name: "resetText",
+ type: "string",
+ default: "Reset",
+ desc: "Text for the reset button.",
+ },
+ {
+ name: "showButtons",
+ type: "boolean",
+ default: "true",
+ desc: "Whether to show submit and reset buttons.",
+ },
+ {
+ name: "className",
+ type: "string",
+ default: "''",
+ desc: "Additional CSS classes for the form element.",
+ },
+ {
+ name: "style",
+ type: "object",
+ default: "undefined",
+ desc: "Custom styles for the form element.",
+ },
+];
+
+// Inner component that uses auth context
+function FormWrapperDocsContent({ authMethod, testCdaUrl, setTestCdaUrl }) {
+ const [formData, setFormData] = useState(null);
+ const [testOffice, setTestOffice] = useState("SWT");
+ const [testMode, setTestMode] = useState(false);
+ const [submissionResult, setSubmissionResult] = useState(null);
+ const [showAuthModal, setShowAuthModal] = useState(false);
+ const [authCredentials, setAuthCredentials] = useState({ username: "", password: "" });
+ const [authLoading, setAuthLoading] = useState(false);
+ const [authError, setAuthError] = useState(null);
+
+ const auth = useAuth();
+
+ // Listen for inline auth modal trigger
+ useEffect(() => {
+ if (authMethod === "inline") {
+ const handleShowModal = () => {
+ setShowAuthModal(true);
+ };
+
+ window.addEventListener('show-inline-auth-modal', handleShowModal);
+
+ return () => {
+ window.removeEventListener('show-inline-auth-modal', handleShowModal);
+ };
+ }
+ }, [authMethod]);
+
+ const handleEnableTestMode = () => {
+ if (!auth.isAuthenticated) {
+ if (authMethod === "inline") {
+ // Show authentication modal for inline auth
+ setShowAuthModal(true);
+ } else {
+ // Use the auth provider's login method for other auth types
+ auth.login();
+ }
+ setTestMode(true);
+ } else {
+ setTestMode(true);
+ }
+ };
+
+ const handleInlineAuth = async () => {
+ setAuthLoading(true);
+ setAuthError(null);
+
+ try {
+ // Attempt basic auth with the CDA endpoint
+ const response = await fetch(`${testCdaUrl}/auth/keys`, {
+ method: 'GET',
+ headers: {
+ 'Authorization': 'Basic ' + btoa(`${authCredentials.username}:${authCredentials.password}`)
+ },
+ credentials: 'include'
+ });
+
+ if (response.ok) {
+ // Authentication successful for inline method
+ setShowAuthModal(false);
+
+ // For inline auth, update the auth provider's state
+ if (authMethod === "inline") {
+ // The auth provider needs to be notified of successful auth
+ // We'll handle this through a custom mechanism
+ window.dispatchEvent(new CustomEvent('inline-auth-success', {
+ detail: { username: authCredentials.username }
+ }));
+ }
+
+ setAuthCredentials({ username: "", password: "" });
+ setSubmissionResult({
+ status: "success",
+ message: `Successfully authenticated as ${authCredentials.username}`
+ });
+ } else {
+ setAuthError("Invalid credentials. Please try again.");
+ }
+ } catch (error) {
+ setAuthError("Authentication failed. Please check your credentials and try again.");
+ } finally {
+ setAuthLoading(false);
+ }
+ };
+
+ return (
+
+
+
+ The FormWrapper component provides a context for CWMS form inputs,
+ handling data collection, validation, and submission to the CWMS Data
+ API. It automatically manages form state and provides styled
+ submit/reset buttons.
+
+
+ All CWMS input components (CWMSInput, CWMSTextarea, CWMSDropdown,
+ etc.) must be wrapped in a FormWrapper to function properly.
+
+
+ )}
+
+
+
+ Test the form submission functionality with your configured authentication method.
+ Configure the auth method above, then enable test mode below.
+
+
+
+
+ ⚠ Not authenticated - submissions may fail
+
+
+
+ )
+ )}
+
+
+ {testMode && (
+
+
+ Test Mode Active: Form will attempt to submit
+ data to {testCdaUrl}
+
+
+ Note: Ensure you have proper authentication and permissions for
+ the target CDA.
+
+
+ )}
+
+
+
+
+ A simple form with various input types.{" "}
+ {testMode
+ ? "Submissions will go to your configured CDA."
+ : "This is a demo-only form."}
+
+
+ {
+ console.log("Form submitted:", data);
+ setFormData(data);
+
+ if (testMode) {
+ setSubmissionResult({
+ status: "pending",
+ message: "Submitting to CDA...",
+ });
+
+ try {
+ // If onSubmit is provided without preventing default,
+ // FormWrapper will still submit to CWMS
+ if (!e.defaultPrevented) {
+ setSubmissionResult({
+ status: "info",
+ message:
+ "Form data prepared for submission. Check console for details.",
+ });
+ }
+ } catch (error) {
+ setSubmissionResult({
+ status: "error",
+ message: `Submission failed: ${error.message}`,
+ });
+ }
+ }
+ }}
+ >
+
+
+
+
+
+ {formData && (
+
+
+
+
+
+ The formData passed to onSubmit is an array of objects, one for each
+ registered input:
+
+
+
+ {`// Structure of formData array passed to onSubmit
+[
+ {
+ tsid: "LOCATION.Stage.Inst.0.0.USGS-raw",
+ values: [123.45],
+ units: "ft",
+ precision: 2,
+ offset: 0,
+ order: 1
+ },
+ {
+ tsid: "LOCATION.Flow.Inst.0.0.USGS-raw",
+ values: [5000],
+ units: "cfs",
+ precision: 0,
+ offset: 0,
+ order: 1
+ }
+]`}
+
+
+
+
+ For production CWMS submissions, you'll need to set up authentication.
+ Here's how:
+
+
+
+
+ Authentication Options: The test mode above demonstrates inline authentication
+ that keeps you on the same page. For production applications, you can use either:
+
+
+
Inline authentication with Basic Auth headers (as shown in test mode)
+
OAuth redirect flow using createCwmsLoginAuthMethod (requires redirect)
+
Keycloak or other SSO providers for enterprise authentication
+
+
+
+
+ {`import { AuthProvider, createCwmsLoginAuthMethod } from "@usace-watermanagement/groundwork-water";
+
+// Wrap your app with AuthProvider
+function App() {
+ const cwmsAuthMethod = createCwmsLoginAuthMethod({
+ authUrl: "https://cwms-data.usace.army.mil/cwms-data/auth",
+ authCheckUrl: "https://cwms-data.usace.army.mil/cwms-data/auth/keys",
+ });
+
+ return (
+
+ {/* Your app components */}
+
+
+ );
+}
+
+// In your form component, use the auth context
+function YourFormComponent() {
+ const auth = useAuth();
+
+ if (!auth.isAuthenticated) {
+ return ;
+ }
+
+ return (
+
+ {/* Your form inputs */}
+
+ );
+}`}
+
+
+
+
+
+ Always provide an office prop - it's required for CWMS
+ submissions
+
+
+ Use cdaUrl if not using a global CdaUrlProvider
+
+
+ Associate inputs with tsid for automatic CWMS integration
+
+
+ Provide custom onSubmit for validation or preprocessing
+
+
+ Use showButtons={false} when integrating with existing
+ forms
+
+
+ The form automatically prevents double submissions during processing
+
+
+ All CWMS input components automatically register with the FormWrapper
+ context
+
+
+ For production use, always wrap your app with{" "}
+ AuthProvider for secure CWMS submissions
+
+
+ Test your forms with the interactive test mode above before deploying
+
+
+ {authMethod === "inline" && "✅ Inline auth keeps you on this page with a login modal"}
+ {authMethod === "cwms" && "🔄 CWMS Login will redirect to OAuth login page and back"}
+ {authMethod === "keycloak" && "🔐 Keycloak SSO provides enterprise authentication"}
+
+
+
+
+
+
+
+
+ );
+}
+
+export { FormWrapperDocs };
+export default FormWrapperDocs;
diff --git a/docs/src/pages/docs/forms/index.jsx b/docs/src/pages/docs/forms/index.jsx
new file mode 100644
index 0000000..a2e6bf5
--- /dev/null
+++ b/docs/src/pages/docs/forms/index.jsx
@@ -0,0 +1,42 @@
+import { Text } from "@usace/groundwork";
+import DocsPage from "../_docs-wrapper";
+import Divider from "../../components/divider";
+
+function FormsDocs() {
+ return (
+
+
+
+ The Forms section provides components for building data entry forms that integrate
+ with the CWMS Data API. These components handle data collection, validation, and
+ submission to CWMS time series.
+
+
+
+
+
+ Start with the FormWrapper component to create a form container, then add any of the
+ CWMS input components for data collection. The FormWrapper handles all the CWMS integration
+ automatically.
+
+
+
+
+
FormWrapper - Container component that provides form context and CWMS submission
+
CWMSInput - Standard text and number input field
+
CWMSTextarea - Multi-line text input
+
CWMSCheckboxes - Multiple selection checkboxes
+
CWMSDropdown - Single selection dropdown
+
CWMSInputTable - Matrix of inputs for time series data
+
CWMSSpreadsheet - Excel-like spreadsheet with copy/paste support
+
+
+
+ Navigate through the Forms menu to explore each component's documentation, examples, and API reference.
+
+
+ );
+}
+
+export { FormsDocs };
+export default FormsDocs;
\ No newline at end of file
diff --git a/lib/components/data/forms/CWMSForm.jsx b/lib/components/data/forms/CWMSForm.jsx
index e0fff0a..15a8f3a 100644
--- a/lib/components/data/forms/CWMSForm.jsx
+++ b/lib/components/data/forms/CWMSForm.jsx
@@ -2,9 +2,21 @@ import React, { createContext, useContext, useRef } from "react";
import { TimeSeriesApi } from "cwmsjs";
import { useCdaConfig } from "../helpers/cda";
-const FormContext = createContext();
+export const FormContext = createContext();
-export function FormWrapper({ office, unit = "EN", cdaUrl, children }) {
+export function FormWrapper({
+ office,
+ unit = "EN",
+ cdaUrl,
+ children,
+ onSubmit,
+ onReset,
+ submitText = "Submit",
+ resetText = "Reset",
+ showButtons = true,
+ className = "",
+ style
+}) {
const inputsRef = useRef([]);
const config = useCdaConfig("v2", cdaUrl);
const ts_api = new TimeSeriesApi(config);
@@ -16,39 +28,132 @@ export function FormWrapper({ office, unit = "EN", cdaUrl, children }) {
const handleSubmit = async (e) => {
e.preventDefault();
- for (const input of inputsRef.current) {
- try {
- const response = await ts_api.postTimeSeries({
- timeSeries: {
- name: input.tsid,
- officeId: office,
- values: input.getValues(),
- units: input.units,
- },
- });
- const result = await response.json();
- console.log("API result:", result);
- } catch (err) {
- console.error("API call failed:", err);
+ // Collect all form data
+ const formData = inputsRef.current.map(input => ({
+ tsid: input.tsid,
+ values: input.getValues(),
+ units: input.units,
+ precision: input.precision,
+ offset: input.offset,
+ order: input.order,
+ }));
+
+ // Call custom onSubmit if provided
+ if (onSubmit) {
+ await onSubmit(formData, e);
+ } else {
+ // Default submit behavior - send to CWMS
+ for (const input of inputsRef.current) {
+ if (input.tsid) {
+ try {
+ const response = await ts_api.postTimeSeries({
+ timeSeries: {
+ name: input.tsid,
+ officeId: office,
+ values: input.getValues(),
+ units: input.units,
+ },
+ });
+ const result = await response.json();
+ console.log("API result:", result);
+ } catch (err) {
+ console.error("API call failed:", err);
+ }
+ }
}
}
};
- const handleReset = () => {
+ const handleReset = (e) => {
+ if (e) e.preventDefault();
+
inputsRef.current.forEach((input) => {
- input.reset();
+ if (input.reset) {
+ input.reset();
+ }
});
+
+ // Call custom onReset if provided
+ if (onReset) {
+ onReset(e);
+ }
+ };
+
+ const formStyle = {
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '1rem',
+ ...style
+ };
+
+ const buttonContainerStyle = {
+ display: 'flex',
+ gap: '0.75rem',
+ marginTop: '1.5rem',
+ paddingTop: '1rem',
+ borderTop: '1px solid #e5e7eb'
};
return (
-
);
-}
+}
\ No newline at end of file
diff --git a/lib/components/data/forms/inputs/CWMSCheckboxes.jsx b/lib/components/data/forms/inputs/CWMSCheckboxes.jsx
new file mode 100644
index 0000000..21d7c5d
--- /dev/null
+++ b/lib/components/data/forms/inputs/CWMSCheckboxes.jsx
@@ -0,0 +1,120 @@
+import React, { useEffect, useContext } from "react";
+import { Checkboxes } from "@usace/groundwork";
+import { FormContext } from "../../forms/CWMSForm";
+
+function CWMSCheckboxes({
+ style,
+ legend,
+ content = [],
+ onChange,
+}) {
+ const { registerInput } = useContext(FormContext);
+
+ // Register each checkbox item that has a tsid
+ useEffect(() => {
+ if (registerInput) {
+ content.forEach((item) => {
+ if (item.tsid) {
+ const checkboxRef = {
+ tsid: item.tsid,
+ precision: item.precision || 2,
+ offset: item.offset || 0,
+ order: item.order || 1,
+ AllowMissingData: item.AllowMissingData !== undefined ? item.AllowMissingData : true,
+ loadNearest: item.loadNearest || "prev",
+ readonly: item.readonly || false,
+ units: item.units || "EN",
+ getValues: () => {
+ // Get the current checked state
+ const element = document.getElementById(item.id);
+ return element ? [element.checked] : [false];
+ },
+ reset: () => {
+ const element = document.getElementById(item.id);
+ if (element) {
+ element.checked = item.defaultChecked || false;
+ // Trigger onChange if provided
+ if (item.onChange) {
+ item.onChange({ target: element });
+ }
+ }
+ },
+ };
+ registerInput(checkboxRef);
+ }
+ });
+ }
+ }, [registerInput, content]);
+
+ // Process content to add CWMS-specific handling
+ const processedContent = content.map((item) => {
+ // Create a new object with all the original properties
+ const processedItem = {
+ ...item,
+ // Override disabled if readonly or disabled is set at item level
+ disabled: item.disabled || item.readonly || item.disable,
+ };
+
+ // Wrap the original onChange to include CWMS handling if needed
+ if (item.onChange) {
+ const originalOnChange = item.onChange;
+ processedItem.onChange = (e) => {
+ // Call the original onChange
+ originalOnChange(e);
+
+ // Call the parent onChange if provided
+ if (onChange) {
+ // Collect all checked values
+ const checkedValues = content
+ .filter((contentItem) => {
+ if (contentItem.id === item.id) {
+ return e.target.checked;
+ }
+ const element = document.getElementById(contentItem.id);
+ return element ? element.checked : false;
+ })
+ .map(contentItem => contentItem.value || contentItem.label || contentItem.id);
+
+ onChange(checkedValues);
+ }
+ };
+ }
+
+ return processedItem;
+ });
+
+ return (
+
+ );
+}
+
+// Extended checkbox item interface for documentation
+// Each item in content array can have:
+// - All standard Groundwork checkbox properties:
+// - id: string (required) - Unique identifier
+// - label: string - Display label
+// - description: string - Additional description text
+// - defaultChecked: boolean - Initial checked state
+// - onChange: function - Handler for changes
+// - disabled: boolean - Disabled state
+// - inputProps: object - Props for the input element
+// - labelProps: object - Props for the label element
+//
+// - Additional CWMS properties:
+// - tsid: string - Time series ID for CWMS data
+// - value: string - Value when checked (defaults to label or id)
+// - readonly: boolean - Make checkbox read-only
+// - disable: boolean - Alias for disabled
+// - precision: number - Decimal precision for numeric values
+// - offset: number - Time offset in seconds
+// - order: number - Sort order
+// - AllowMissingData: boolean - Allow missing data points
+// - loadNearest: string - Load nearest data point strategy
+// - units: string - Unit system (EN/SI)
+
+export default CWMSCheckboxes;
+export { CWMSCheckboxes };
\ No newline at end of file
diff --git a/lib/components/data/forms/inputs/CWMSDropdown.jsx b/lib/components/data/forms/inputs/CWMSDropdown.jsx
new file mode 100644
index 0000000..fd816f3
--- /dev/null
+++ b/lib/components/data/forms/inputs/CWMSDropdown.jsx
@@ -0,0 +1,92 @@
+import React, { useEffect, useState, useContext } from "react";
+import { Dropdown } from "@usace/groundwork";
+import { FormContext } from "../../forms/CWMSForm";
+
+function CWMSDropdown({
+ style,
+ disable,
+ invalid,
+ name,
+ defaultValue,
+ value,
+ options,
+ tsid,
+ precision,
+ offset,
+ order,
+ AllowMissingData,
+ loadNearest,
+ readonly,
+ units,
+ onChange,
+ placeholder,
+}) {
+ const { registerInput } = useContext(FormContext);
+ const [selectedValue, setSelectedValue] = useState(
+ defaultValue || value || ""
+ );
+
+ const dropdownRef = {
+ tsid,
+ precision: precision || 2,
+ offset: offset || 0,
+ order: order || 1,
+ AllowMissingData: AllowMissingData !== undefined ? AllowMissingData : true,
+ loadNearest: loadNearest || "prev",
+ readonly: readonly || false,
+ units: units || "EN",
+ getValues: () => [selectedValue],
+ reset: () => setSelectedValue(defaultValue || ""),
+ };
+
+ useEffect(() => {
+ if (registerInput) {
+ registerInput(dropdownRef);
+ }
+ }, [registerInput]);
+
+ const handleChange = (newValue) => {
+ setSelectedValue(newValue);
+ if (onChange) {
+ onChange(newValue);
+ }
+ };
+
+ // Convert options to proper format if needed
+ let dropdownOptions = options;
+ if (Array.isArray(options) && options.length > 0) {
+ if (typeof options[0] === 'string') {
+ // Convert string array to option elements
+ dropdownOptions = [
+ placeholder && ,
+ ...options.map((opt) => (
+
+ ))
+ ].filter(Boolean);
+ } else if (typeof options[0] === 'object' && options[0].text !== undefined) {
+ // Convert object array to option elements
+ dropdownOptions = options.map((opt) => (
+
+ ));
+ }
+ }
+
+ return (
+ handleChange(e.target.value)}
+ />
+ );
+}
+
+export default CWMSDropdown;
+export { CWMSDropdown };
\ No newline at end of file
diff --git a/lib/components/data/tables/inputs/CWMSInput.jsx b/lib/components/data/forms/inputs/CWMSInput.jsx
similarity index 82%
rename from lib/components/data/tables/inputs/CWMSInput.jsx
rename to lib/components/data/forms/inputs/CWMSInput.jsx
index 6e64461..92bf7c0 100644
--- a/lib/components/data/tables/inputs/CWMSInput.jsx
+++ b/lib/components/data/forms/inputs/CWMSInput.jsx
@@ -2,7 +2,7 @@ import React, { useEffect, useState, useContext } from "react";
import { Input } from "@usace/groundwork";
import { FormContext } from "../../forms/CWMSForm";
-export function CWMSInput({
+function CWMSInput({
style,
disable,
invalid,
@@ -27,11 +27,11 @@ export function CWMSInput({
const inputRef = {
tsid,
precision: precision || 2,
- offset: timeoffset || 0,
+ offset: offset || 0,
order: order || 1,
- AllowMissingData: true,
- loadNearest: "prev",
- readonly: false,
+ AllowMissingData: AllowMissingData !== undefined ? AllowMissingData : true,
+ loadNearest: loadNearest || "prev",
+ readonly: readonly || false,
units: units || "EN",
getValues: () => [inputValue],
reset: () => setInputValue(defaultValue || ""),
@@ -63,3 +63,6 @@ export function CWMSInput({
/>
);
}
+
+export default CWMSInput;
+export { CWMSInput };
diff --git a/lib/components/data/forms/inputs/CWMSInputTable.jsx b/lib/components/data/forms/inputs/CWMSInputTable.jsx
new file mode 100644
index 0000000..c26abaf
--- /dev/null
+++ b/lib/components/data/forms/inputs/CWMSInputTable.jsx
@@ -0,0 +1,125 @@
+import React, { useEffect, useState, useContext } from "react";
+import { Input } from "@usace/groundwork";
+import { FormContext } from "../../forms/CWMSForm";
+
+function CWMSInputTable({
+ style,
+ disable,
+ invalid,
+ tsids = [],
+ timeoffsets = [],
+ precision,
+ order,
+ AllowMissingData,
+ loadNearest,
+ readonly,
+ units,
+ onChange,
+ showTimestamps = true,
+ defaultValues = {},
+}) {
+ const { registerInput } = useContext(FormContext);
+ const [matrixData, setMatrixData] = useState(defaultValues);
+
+ const tableRef = {
+ tsids,
+ precision: precision || 2,
+ timeoffsets: timeoffsets || [0],
+ order: order || 1,
+ AllowMissingData: AllowMissingData !== undefined ? AllowMissingData : true,
+ loadNearest: loadNearest || "prev",
+ readonly: readonly || false,
+ units: units || "EN",
+ getValues: () => matrixData,
+ reset: () => setMatrixData(defaultValues),
+ };
+
+ useEffect(() => {
+ if (registerInput) {
+ registerInput(tableRef);
+ }
+ }, [registerInput]);
+
+ const handleInputChange = (tsid, offset, value) => {
+ const key = `${tsid}_${offset}`;
+ const updatedData = {
+ ...matrixData,
+ [key]: value,
+ };
+ setMatrixData(updatedData);
+ if (onChange) {
+ onChange(updatedData);
+ }
+ };
+
+ const formatTimestamp = (offset) => {
+ const currentTime = new Date();
+ const offsetTime = new Date(currentTime.getTime() + offset * 1000);
+ return offsetTime.toISOString().replace('T', ' ').split('.')[0];
+ };
+
+ return (
+
+
+ The CWMS Checkboxes component provides a group of checkboxes that
+ integrates with CWMS forms for multi-selection data collection. Uses
+ the full Groundwork checkbox content API with CWMS-specific
+ extensions.
+
+
+
+ Each item in the content array accepts the following properties. The
+ component passes through all standard Groundwork checkbox properties and
+ adds CWMS-specific extensions.
+
+
+
+ );
+}
+
+export { CWMSCheckboxesDocs };
+export default CWMSCheckboxesDocs;
diff --git a/docs/src/pages/docs/forms/cwms-dropdown.jsx b/docs/src/pages/docs/forms/cwms-dropdown.jsx
new file mode 100644
index 0000000..beefb02
--- /dev/null
+++ b/docs/src/pages/docs/forms/cwms-dropdown.jsx
@@ -0,0 +1,244 @@
+import { Text, Code } from "@usace/groundwork";
+import PropsTable from "../../components/props-table";
+import {
+ CWMSDropdown,
+ FormWrapper,
+} from "@usace-watermanagement/groundwork-water";
+import { Code as CodeBlock } from "../../components/code";
+import DocsPage from "../_docs-wrapper";
+import Divider from "../../components/divider";
+
+const componentProps = [
+ {
+ name: "name",
+ type: "string",
+ default: "undefined",
+ desc: "The name attribute for the dropdown.",
+ },
+ {
+ name: "options",
+ type: "array",
+ default: "[]",
+ desc: "Array of string options for the dropdown.",
+ },
+ {
+ name: "value",
+ type: "string",
+ default: "undefined",
+ desc: "The controlled selected value.",
+ },
+ {
+ name: "defaultValue",
+ type: "string",
+ default: "undefined",
+ desc: "The default selected value when uncontrolled.",
+ },
+ {
+ name: "placeholder",
+ type: "string",
+ default: "undefined",
+ desc: "Placeholder text when no option is selected.",
+ },
+ {
+ name: "tsid",
+ type: "string",
+ default: "undefined",
+ desc: "The time series ID for CWMS data association.",
+ },
+ {
+ name: "units",
+ type: "string",
+ default: "EN",
+ desc: "Unit system (EN for English, SI for metric).",
+ },
+ {
+ name: "disable",
+ type: "boolean",
+ default: "false",
+ desc: "Whether the dropdown is disabled.",
+ },
+ {
+ name: "readonly",
+ type: "boolean",
+ default: "false",
+ desc: "Whether the dropdown is read-only.",
+ },
+ {
+ name: "invalid",
+ type: "boolean",
+ default: "false",
+ desc: "Whether the dropdown is in an invalid state.",
+ },
+ {
+ name: "onChange",
+ type: "function",
+ default: "undefined",
+ desc: "Callback function when selection changes.",
+ },
+ {
+ name: "style",
+ type: "object",
+ default: "undefined",
+ desc: "Custom styles to apply to the dropdown.",
+ },
+];
+
+function CWMSDropdownDocs() {
+ return (
+
+
+
+ The CWMS Dropdown component provides a select dropdown that integrates
+ with CWMS forms for single-selection data collection.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {`import { CWMSDropdown } from "@usace-watermanagement/groundwork-water";
+
+// Simple string options
+
+
+// With default value
+
+
+// With placeholder
+`}
+
+
+
+
+ When used within a FormWrapper, CWMSDropdown automatically registers
+ with the form context for data collection and submission to CWMS.
+
+
+
+ {`import { CWMSDropdown } from "@usace-watermanagement/groundwork-water";
+import { FormWrapper } from "@usace-watermanagement/groundwork-water";
+
+
+
+
+
+`}
+
+
+
+
+
+
+
+ );
+}
+
+export { CWMSDropdownDocs };
+export default CWMSDropdownDocs;
diff --git a/docs/src/pages/docs/forms/cwms-input-table.jsx b/docs/src/pages/docs/forms/cwms-input-table.jsx
new file mode 100644
index 0000000..8c1bad2
--- /dev/null
+++ b/docs/src/pages/docs/forms/cwms-input-table.jsx
@@ -0,0 +1,274 @@
+import { Text, Code } from "@usace/groundwork";
+import PropsTable from "../../components/props-table";
+import { CWMSInputTable, FormWrapper } from "@usace-watermanagement/groundwork-water";
+import { Code as CodeBlock } from "../../components/code";
+import DocsPage from "../_docs-wrapper";
+import Divider from "../../components/divider";
+
+const componentProps = [
+ {
+ name: "tsids",
+ type: "array",
+ default: "[]",
+ desc: "Array of time series IDs for column headers.",
+ },
+ {
+ name: "timeoffsets",
+ type: "array",
+ default: "[0]",
+ desc: "Array of time offsets in seconds for row timestamps.",
+ },
+ {
+ name: "defaultValues",
+ type: "object",
+ default: "{}",
+ desc: "Object with default values keyed by 'tsid_offset'.",
+ },
+ {
+ name: "showTimestamps",
+ type: "boolean",
+ default: "true",
+ desc: "Whether to show timestamp column.",
+ },
+ {
+ name: "precision",
+ type: "number",
+ default: "2",
+ desc: "Number of decimal places for numeric values.",
+ },
+ {
+ name: "units",
+ type: "string",
+ default: "EN",
+ desc: "Unit system (EN for English, SI for metric).",
+ },
+ {
+ name: "disable",
+ type: "boolean",
+ default: "false",
+ desc: "Whether all inputs are disabled.",
+ },
+ {
+ name: "readonly",
+ type: "boolean",
+ default: "false",
+ desc: "Whether all inputs are read-only.",
+ },
+ {
+ name: "invalid",
+ type: "boolean",
+ default: "false",
+ desc: "Whether the table is in an invalid state.",
+ },
+ {
+ name: "onChange",
+ type: "function",
+ default: "undefined",
+ desc: "Callback function when any input value changes.",
+ },
+ {
+ name: "style",
+ type: "object",
+ default: "undefined",
+ desc: "Custom styles to apply to the table.",
+ },
+];
+
+function CWMSInputTableDocs() {
+ // const currentTime = Date.now() / 1000;
+
+ return (
+
+
+
+ The CWMS Input Table component provides a matrix of input fields for
+ entering multiple time series values across different time offsets.
+ Its ideal for bulk data entry where you need to input values for
+ multiple parameters at different time points.
+
+
+
+
+
+
+
+
+
+
+
+ {`import { CWMSInputTable } from "@usace-watermanagement/groundwork-water";
+
+`}
+
+
+
+
+ You can provide default values for specific cells using the tsid_offset
+ key format.
+
+
+
+
+
+
+
+
+
+ {``}
+
+
+
+
+ When used within a FormWrapper, CWMSInputTable automatically registers
+ with the form context for bulk data submission to CWMS.
+
+
+
+ {`import { CWMSInputTable } from "@usace-watermanagement/groundwork-water";
+import { FormWrapper } from "@usace-watermanagement/groundwork-water";
+
+
+
+`}
+
+
+
+
+ You can hide the timestamp column for a more compact view.
+
+
+
+
+
+ );
+}
+
+export { CWMSInputTableDocs };
+export default CWMSInputTableDocs;
diff --git a/docs/src/pages/docs/forms/cwms-input.jsx b/docs/src/pages/docs/forms/cwms-input.jsx
new file mode 100644
index 0000000..4f52161
--- /dev/null
+++ b/docs/src/pages/docs/forms/cwms-input.jsx
@@ -0,0 +1,216 @@
+import { Text, Code } from "@usace/groundwork";
+import PropsTable from "../../components/props-table";
+import { CWMSInput, FormWrapper } from "@usace-watermanagement/groundwork-water";
+import { Code as CodeBlock } from "../../components/code";
+import DocsPage from "../_docs-wrapper";
+import Divider from "../../components/divider";
+
+const componentProps = [
+ {
+ name: "name",
+ type: "string",
+ default: "undefined",
+ desc: "The name attribute for the input field.",
+ },
+ {
+ name: "value",
+ type: "string",
+ default: "undefined",
+ desc: "The controlled value of the input.",
+ },
+ {
+ name: "defaultValue",
+ type: "string",
+ default: "undefined",
+ desc: "The default value for the input when uncontrolled.",
+ },
+ {
+ name: "type",
+ type: "string",
+ default: "text",
+ desc: "The input type (text, number, email, password, etc.).",
+ },
+ {
+ name: "placeholder",
+ type: "string",
+ default: "undefined",
+ desc: "Placeholder text for the input.",
+ },
+ {
+ name: "tsid",
+ type: "string",
+ default: "undefined",
+ desc: "The time series ID for CWMS data association.",
+ },
+ {
+ name: "precision",
+ type: "number",
+ default: "2",
+ desc: "Number of decimal places for numeric values.",
+ },
+ {
+ name: "offset",
+ type: "number",
+ default: "0",
+ desc: "Time offset in seconds for data timestamps.",
+ },
+ {
+ name: "units",
+ type: "string",
+ default: "EN",
+ desc: "Unit system (EN for English, SI for metric).",
+ },
+ {
+ name: "disable",
+ type: "boolean",
+ default: "false",
+ desc: "Whether the input is disabled.",
+ },
+ {
+ name: "readonly",
+ type: "boolean",
+ default: "false",
+ desc: "Whether the input is read-only.",
+ },
+ {
+ name: "invalid",
+ type: "boolean",
+ default: "false",
+ desc: "Whether the input is in an invalid state.",
+ },
+ {
+ name: "onChange",
+ type: "function",
+ default: "undefined",
+ desc: "Callback function when the input value changes.",
+ },
+ {
+ name: "style",
+ type: "object",
+ default: "undefined",
+ desc: "Custom styles to apply to the input.",
+ },
+];
+
+function CWMSInputDocs() {
+ return (
+
+
+
+ The CWMS Input component is a wrapper around the standard Groundwork
+ Input component that integrates with CWMS forms for data collection
+ and submission. It supports various input types and can be associated
+ with CWMS time series data.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {`import { CWMSInput } from "@usace-watermanagement/groundwork-water";
+
+
+
+
+
+`}
+
+
+
+
+ When used within a FormWrapper, CWMSInput automatically registers with
+ the form context for data collection and submission to CWMS.
+
+
+
+ {`import { CWMSInput } from "@usace-watermanagement/groundwork-water";
+import { FormWrapper } from "@usace-watermanagement/groundwork-water";
+
+
+
+
+`}
+
+
+
+
+
+
+
+
+
+
+ {``}
+
+
+
+
+ Component API - {``}
+
+
+
+ );
+}
+
+export { CWMSInputDocs };
+export default CWMSInputDocs;
diff --git a/docs/src/pages/docs/forms/cwms-spreadsheet.jsx b/docs/src/pages/docs/forms/cwms-spreadsheet.jsx
new file mode 100644
index 0000000..7a3e268
--- /dev/null
+++ b/docs/src/pages/docs/forms/cwms-spreadsheet.jsx
@@ -0,0 +1,331 @@
+import { Text, Code } from "@usace/groundwork";
+import PropsTable from "../../components/props-table";
+import { CWMSSpreadsheet, FormWrapper } from "@usace-watermanagement/groundwork-water";
+import { Code as CodeBlock } from "../../components/code";
+import DocsPage from "../_docs-wrapper";
+import Divider from "../../components/divider";
+
+const componentProps = [
+ {
+ name: "columns",
+ type: "array",
+ default: "[]",
+ desc: "Array of column definitions with {key, label, type, placeholder} structure.",
+ },
+ {
+ name: "rows",
+ type: "number",
+ default: "10",
+ desc: "Number of rows in the spreadsheet.",
+ },
+ {
+ name: "defaultData",
+ type: "array",
+ default: "[]",
+ desc: "Array of arrays containing default cell values.",
+ },
+ {
+ name: "showRowNumbers",
+ type: "boolean",
+ default: "true",
+ desc: "Whether to show row numbers.",
+ },
+ {
+ name: "showColumnHeaders",
+ type: "boolean",
+ default: "true",
+ desc: "Whether to show column headers.",
+ },
+ {
+ name: "resizable",
+ type: "boolean",
+ default: "false",
+ desc: "Whether columns are resizable (future feature).",
+ },
+ {
+ name: "tsid",
+ type: "string",
+ default: "undefined",
+ desc: "The time series ID for CWMS data association.",
+ },
+ {
+ name: "precision",
+ type: "number",
+ default: "2",
+ desc: "Number of decimal places for numeric values.",
+ },
+ {
+ name: "units",
+ type: "string",
+ default: "EN",
+ desc: "Unit system (EN for English, SI for metric).",
+ },
+ {
+ name: "disable",
+ type: "boolean",
+ default: "false",
+ desc: "Whether all cells are disabled.",
+ },
+ {
+ name: "readonly",
+ type: "boolean",
+ default: "false",
+ desc: "Whether all cells are read-only.",
+ },
+ {
+ name: "onChange",
+ type: "function",
+ default: "undefined",
+ desc: "Callback function when any cell value changes.",
+ },
+ {
+ name: "style",
+ type: "object",
+ default: "undefined",
+ desc: "Custom styles to apply to the container.",
+ },
+];
+
+function CWMSSpreadsheetDocs() {
+ return (
+
+
+
+ The CWMS Spreadsheet component provides an Excel-like grid interface for data entry.
+ It supports keyboard navigation, copy/paste operations, and can handle large datasets
+ with multiple columns and rows.
+
+
+ Excel-like Features:
+
+
+
Multi-cell selection: Click and drag to select multiple cells
+
Range selection: Shift+Click to select a range
+
Keyboard selection: Shift+Arrow keys to extend selection
+
Copy multiple cells: Select cells and press Ctrl+C to copy as tab-separated values
+
Paste from Excel: Copy from Excel/Google Sheets and paste directly
+
Select all: Ctrl+A to select entire spreadsheet
+
Arrow keys - Navigate between cells
+
Enter - Move down to next row
+
Tab / Shift+Tab - Move forward/backward through cells
+
Escape - Clear selection
+
Delete - Clear selected cells
+
+
+
+
+
+
+
+
+
+
+
+ {`import { CWMSSpreadsheet } from "@usace-watermanagement/groundwork-water";
+
+`}
+
+
+
+
+ Pre-populate the spreadsheet with default values.
+
+
+
+
+
+
+
+
+
+ {``}
+
+
+
+
+ You can create a spreadsheet with simple column labels like A, B, C, etc.
+
+
+
+
+
+
+
+
+
+ {`// Creates a 6-column spreadsheet with labeled columns
+`}
+
+
+
+
+ When used within a FormWrapper, CWMSSpreadsheet can submit tabular data to CWMS.
+
+
+
+ {`import { CWMSSpreadsheet } from "@usace-watermanagement/groundwork-water";
+import { FormWrapper } from "@usace-watermanagement/groundwork-water";
+
+
+
+`}
+
+
+
+
+
+
+ );
+}
+
+export { CWMSSpreadsheetDocs };
+export default CWMSSpreadsheetDocs;
\ No newline at end of file
diff --git a/docs/src/pages/docs/forms/cwms-textarea.jsx b/docs/src/pages/docs/forms/cwms-textarea.jsx
new file mode 100644
index 0000000..fd505f1
--- /dev/null
+++ b/docs/src/pages/docs/forms/cwms-textarea.jsx
@@ -0,0 +1,213 @@
+import { Text, Code } from "@usace/groundwork";
+import PropsTable from "../../components/props-table";
+import { CWMSTextarea, FormWrapper } from "@usace-watermanagement/groundwork-water";
+import { Code as CodeBlock } from "../../components/code";
+import DocsPage from "../_docs-wrapper";
+import Divider from "../../components/divider";
+
+const componentProps = [
+ {
+ name: "name",
+ type: "string",
+ default: "undefined",
+ desc: "The name attribute for the textarea.",
+ },
+ {
+ name: "value",
+ type: "string",
+ default: "undefined",
+ desc: "The controlled value of the textarea.",
+ },
+ {
+ name: "defaultValue",
+ type: "string",
+ default: "undefined",
+ desc: "The default value for the textarea when uncontrolled.",
+ },
+ {
+ name: "rows",
+ type: "number",
+ default: "3",
+ desc: "Number of visible text lines.",
+ },
+ {
+ name: "tsid",
+ type: "string",
+ default: "undefined",
+ desc: "The time series ID for CWMS data association.",
+ },
+ {
+ name: "precision",
+ type: "number",
+ default: "2",
+ desc: "Number of decimal places for numeric values.",
+ },
+ {
+ name: "offset",
+ type: "number",
+ default: "0",
+ desc: "Time offset in seconds for data timestamps.",
+ },
+ {
+ name: "units",
+ type: "string",
+ default: "EN",
+ desc: "Unit system (EN for English, SI for metric).",
+ },
+ {
+ name: "disable",
+ type: "boolean",
+ default: "false",
+ desc: "Whether the textarea is disabled.",
+ },
+ {
+ name: "readonly",
+ type: "boolean",
+ default: "false",
+ desc: "Whether the textarea is read-only.",
+ },
+ {
+ name: "invalid",
+ type: "boolean",
+ default: "false",
+ desc: "Whether the textarea is in an invalid state.",
+ },
+ {
+ name: "onChange",
+ type: "function",
+ default: "undefined",
+ desc: "Callback function when the textarea value changes.",
+ },
+ {
+ name: "style",
+ type: "object",
+ default: "undefined",
+ desc: "Custom styles to apply to the textarea.",
+ },
+];
+
+function CWMSTextareaDocs() {
+ return (
+
+
+
+ The CWMS Textarea component is a wrapper around the standard Groundwork Textarea
+ component that integrates with CWMS forms for multi-line text input and data submission.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {`import { CWMSTextarea } from "@usace-watermanagement/groundwork-water";
+
+
+
+
+
+`}
+
+
+
+
+ When used within a FormWrapper, CWMSTextarea automatically registers with the form
+ context for data collection and submission to CWMS.
+
+
+
+ {`import { CWMSTextarea } from "@usace-watermanagement/groundwork-water";
+import { FormWrapper } from "@usace-watermanagement/groundwork-water";
+
+
+
+
+`}
+
+
+
+
+
+
+
+
+
+
+ {``}
+
+
+
+
+ Component API - {``}
+
+
+
+ );
+}
+
+export { CWMSTextareaDocs };
+export default CWMSTextareaDocs;
\ No newline at end of file
diff --git a/docs/src/pages/docs/forms/form-wrapper.jsx b/docs/src/pages/docs/forms/form-wrapper.jsx
new file mode 100644
index 0000000..cbe5da2
--- /dev/null
+++ b/docs/src/pages/docs/forms/form-wrapper.jsx
@@ -0,0 +1,1064 @@
+import { useState, useEffect } from "react";
+import { Text, Code, Input, Button } from "@usace/groundwork";
+import PropsTable from "../../components/props-table";
+import {
+ FormWrapper,
+ CWMSInput,
+ CWMSTextarea,
+ CWMSDropdown,
+ CWMSCheckboxes,
+ AuthProvider,
+ useAuth,
+ createCwmsLoginAuthMethod,
+ createKeycloakAuthMethod,
+} from "@usace-watermanagement/groundwork-water";
+import { Code as CodeBlock } from "../../components/code";
+import DocsPage from "../_docs-wrapper";
+import Divider from "../../components/divider";
+
+const componentProps = [
+ {
+ name: "office",
+ type: "string",
+ default: "required",
+ desc: "USACE office symbol (e.g., 'SWT', 'NWD').",
+ },
+ {
+ name: "cdaUrl",
+ type: "string",
+ default: "undefined",
+ desc: "URL for CWMS Data API. If not provided, uses the URL from CdaUrlProvider.",
+ },
+ {
+ name: "unit",
+ type: "string",
+ default: "EN",
+ desc: "Unit system for data submission (EN for English, SI for metric).",
+ },
+ {
+ name: "children",
+ type: "ReactNode",
+ default: "required",
+ desc: "Form input components to be wrapped.",
+ },
+ {
+ name: "onSubmit",
+ type: "function",
+ default: "undefined",
+ desc: "Custom submit handler. Receives (formData, event). If not provided, submits to CWMS.",
+ },
+ {
+ name: "onReset",
+ type: "function",
+ default: "undefined",
+ desc: "Custom reset handler. Called after form inputs are reset.",
+ },
+ {
+ name: "submitText",
+ type: "string",
+ default: "Submit",
+ desc: "Text for the submit button.",
+ },
+ {
+ name: "resetText",
+ type: "string",
+ default: "Reset",
+ desc: "Text for the reset button.",
+ },
+ {
+ name: "showButtons",
+ type: "boolean",
+ default: "true",
+ desc: "Whether to show submit and reset buttons.",
+ },
+ {
+ name: "className",
+ type: "string",
+ default: "''",
+ desc: "Additional CSS classes for the form element.",
+ },
+ {
+ name: "style",
+ type: "object",
+ default: "undefined",
+ desc: "Custom styles for the form element.",
+ },
+];
+
+// Inner component that uses auth context
+function FormWrapperDocsContent({ authMethod, testCdaUrl, setTestCdaUrl }) {
+ const [formData, setFormData] = useState(null);
+ const [testOffice, setTestOffice] = useState("SWT");
+ const [testMode, setTestMode] = useState(false);
+ const [submissionResult, setSubmissionResult] = useState(null);
+ const [showAuthModal, setShowAuthModal] = useState(false);
+ const [authCredentials, setAuthCredentials] = useState({ username: "", password: "" });
+ const [authLoading, setAuthLoading] = useState(false);
+ const [authError, setAuthError] = useState(null);
+
+ const auth = useAuth();
+
+ // Listen for inline auth modal trigger
+ useEffect(() => {
+ if (authMethod === "inline") {
+ const handleShowModal = () => {
+ setShowAuthModal(true);
+ };
+
+ window.addEventListener('show-inline-auth-modal', handleShowModal);
+
+ return () => {
+ window.removeEventListener('show-inline-auth-modal', handleShowModal);
+ };
+ }
+ }, [authMethod]);
+
+ const handleEnableTestMode = () => {
+ if (!auth.isAuthenticated) {
+ if (authMethod === "inline") {
+ // Show authentication modal for inline auth
+ setShowAuthModal(true);
+ } else {
+ // Use the auth provider's login method for other auth types
+ auth.login();
+ }
+ setTestMode(true);
+ } else {
+ setTestMode(true);
+ }
+ };
+
+ const handleInlineAuth = async () => {
+ setAuthLoading(true);
+ setAuthError(null);
+
+ try {
+ // Attempt basic auth with the CDA endpoint
+ const response = await fetch(`${testCdaUrl}/auth/keys`, {
+ method: 'GET',
+ headers: {
+ 'Authorization': 'Basic ' + btoa(`${authCredentials.username}:${authCredentials.password}`)
+ },
+ credentials: 'include'
+ });
+
+ if (response.ok) {
+ // Authentication successful for inline method
+ setShowAuthModal(false);
+
+ // For inline auth, update the auth provider's state
+ if (authMethod === "inline") {
+ // The auth provider needs to be notified of successful auth
+ // We'll handle this through a custom mechanism
+ window.dispatchEvent(new CustomEvent('inline-auth-success', {
+ detail: { username: authCredentials.username }
+ }));
+ }
+
+ setAuthCredentials({ username: "", password: "" });
+ setSubmissionResult({
+ status: "success",
+ message: `Successfully authenticated as ${authCredentials.username}`
+ });
+ } else {
+ setAuthError("Invalid credentials. Please try again.");
+ }
+ } catch (error) {
+ setAuthError("Authentication failed. Please check your credentials and try again.");
+ } finally {
+ setAuthLoading(false);
+ }
+ };
+
+ return (
+
+
+
+ The FormWrapper component provides a context for CWMS form inputs,
+ handling data collection, validation, and submission to the CWMS Data
+ API. It automatically manages form state and provides styled
+ submit/reset buttons.
+
+
+ All CWMS input components (CWMSInput, CWMSTextarea, CWMSDropdown,
+ etc.) must be wrapped in a FormWrapper to function properly.
+
+
+ )}
+
+
+
+ Test the form submission functionality with your configured authentication method.
+ Configure the auth method above, then enable test mode below.
+
+
+
+
+ ⚠ Not authenticated - submissions may fail
+
+
+
+ )
+ )}
+
+
+ {testMode && (
+
+
+ Test Mode Active: Form will attempt to submit
+ data to {testCdaUrl}
+
+
+ Note: Ensure you have proper authentication and permissions for
+ the target CDA.
+
+
+ )}
+
+
+
+
+ A simple form with various input types.{" "}
+ {testMode
+ ? "Submissions will go to your configured CDA."
+ : "This is a demo-only form."}
+
+
+ {
+ console.log("Form submitted:", data);
+ setFormData(data);
+
+ if (testMode) {
+ setSubmissionResult({
+ status: "pending",
+ message: "Submitting to CDA...",
+ });
+
+ try {
+ // If onSubmit is provided without preventing default,
+ // FormWrapper will still submit to CWMS
+ if (!e.defaultPrevented) {
+ setSubmissionResult({
+ status: "info",
+ message:
+ "Form data prepared for submission. Check console for details.",
+ });
+ }
+ } catch (error) {
+ setSubmissionResult({
+ status: "error",
+ message: `Submission failed: ${error.message}`,
+ });
+ }
+ }
+ }}
+ >
+
+
+
+
+
+ {formData && (
+
+
+
+
+
+ The formData passed to onSubmit is an array of objects, one for each
+ registered input:
+
+
+
+ {`// Structure of formData array passed to onSubmit
+[
+ {
+ tsid: "LOCATION.Stage.Inst.0.0.USGS-raw",
+ values: [123.45],
+ units: "ft",
+ precision: 2,
+ offset: 0,
+ order: 1
+ },
+ {
+ tsid: "LOCATION.Flow.Inst.0.0.USGS-raw",
+ values: [5000],
+ units: "cfs",
+ precision: 0,
+ offset: 0,
+ order: 1
+ }
+]`}
+
+
+
+
+ For production CWMS submissions, you'll need to set up authentication.
+ Here's how:
+
+
+
+
+ Authentication Options: The test mode above demonstrates inline authentication
+ that keeps you on the same page. For production applications, you can use either:
+
+
+
Inline authentication with Basic Auth headers (as shown in test mode)
+
OAuth redirect flow using createCwmsLoginAuthMethod (requires redirect)
+
Keycloak or other SSO providers for enterprise authentication
+
+
+
+
+ {`import { AuthProvider, createCwmsLoginAuthMethod } from "@usace-watermanagement/groundwork-water";
+
+// Wrap your app with AuthProvider
+function App() {
+ const cwmsAuthMethod = createCwmsLoginAuthMethod({
+ authUrl: "https://cwms-data.usace.army.mil/cwms-data/auth",
+ authCheckUrl: "https://cwms-data.usace.army.mil/cwms-data/auth/keys",
+ });
+
+ return (
+
+ {/* Your app components */}
+
+
+ );
+}
+
+// In your form component, use the auth context
+function YourFormComponent() {
+ const auth = useAuth();
+
+ if (!auth.isAuthenticated) {
+ return ;
+ }
+
+ return (
+
+ {/* Your form inputs */}
+
+ );
+}`}
+
+
+
+
+
+ Always provide an office prop - it's required for CWMS
+ submissions
+
+
+ Use cdaUrl if not using a global CdaUrlProvider
+
+
+ Associate inputs with tsid for automatic CWMS integration
+
+
+ Provide custom onSubmit for validation or preprocessing
+
+
+ Use showButtons={false} when integrating with existing
+ forms
+
+
+ The form automatically prevents double submissions during processing
+
+
+ All CWMS input components automatically register with the FormWrapper
+ context
+
+
+ For production use, always wrap your app with{" "}
+ AuthProvider for secure CWMS submissions
+
+
+ Test your forms with the interactive test mode above before deploying
+
+
+ {authMethod === "inline" && "✅ Inline auth keeps you on this page with a login modal"}
+ {authMethod === "cwms" && "🔄 CWMS Login will redirect to OAuth login page and back"}
+ {authMethod === "keycloak" && "🔐 Keycloak SSO provides enterprise authentication"}
+
+
+
+
+
+
+
+
+ );
+}
+
+export { FormWrapperDocs };
+export default FormWrapperDocs;
diff --git a/docs/src/pages/docs/forms/index.jsx b/docs/src/pages/docs/forms/index.jsx
new file mode 100644
index 0000000..a2e6bf5
--- /dev/null
+++ b/docs/src/pages/docs/forms/index.jsx
@@ -0,0 +1,42 @@
+import { Text } from "@usace/groundwork";
+import DocsPage from "../_docs-wrapper";
+import Divider from "../../components/divider";
+
+function FormsDocs() {
+ return (
+
+
+
+ The Forms section provides components for building data entry forms that integrate
+ with the CWMS Data API. These components handle data collection, validation, and
+ submission to CWMS time series.
+
+
+
+
+
+ Start with the FormWrapper component to create a form container, then add any of the
+ CWMS input components for data collection. The FormWrapper handles all the CWMS integration
+ automatically.
+
+
+
+
+
FormWrapper - Container component that provides form context and CWMS submission
+
CWMSInput - Standard text and number input field
+
CWMSTextarea - Multi-line text input
+
CWMSCheckboxes - Multiple selection checkboxes
+
CWMSDropdown - Single selection dropdown
+
CWMSInputTable - Matrix of inputs for time series data
+
CWMSSpreadsheet - Excel-like spreadsheet with copy/paste support
+
+
+
+ Navigate through the Forms menu to explore each component's documentation, examples, and API reference.
+
+
+ );
+}
+
+export { FormsDocs };
+export default FormsDocs;
\ No newline at end of file
diff --git a/lib/components/data/forms/CWMSForm.jsx b/lib/components/data/forms/CWMSForm.jsx
index e0fff0a..15a8f3a 100644
--- a/lib/components/data/forms/CWMSForm.jsx
+++ b/lib/components/data/forms/CWMSForm.jsx
@@ -2,9 +2,21 @@ import React, { createContext, useContext, useRef } from "react";
import { TimeSeriesApi } from "cwmsjs";
import { useCdaConfig } from "../helpers/cda";
-const FormContext = createContext();
+export const FormContext = createContext();
-export function FormWrapper({ office, unit = "EN", cdaUrl, children }) {
+export function FormWrapper({
+ office,
+ unit = "EN",
+ cdaUrl,
+ children,
+ onSubmit,
+ onReset,
+ submitText = "Submit",
+ resetText = "Reset",
+ showButtons = true,
+ className = "",
+ style
+}) {
const inputsRef = useRef([]);
const config = useCdaConfig("v2", cdaUrl);
const ts_api = new TimeSeriesApi(config);
@@ -16,39 +28,132 @@ export function FormWrapper({ office, unit = "EN", cdaUrl, children }) {
const handleSubmit = async (e) => {
e.preventDefault();
- for (const input of inputsRef.current) {
- try {
- const response = await ts_api.postTimeSeries({
- timeSeries: {
- name: input.tsid,
- officeId: office,
- values: input.getValues(),
- units: input.units,
- },
- });
- const result = await response.json();
- console.log("API result:", result);
- } catch (err) {
- console.error("API call failed:", err);
+ // Collect all form data
+ const formData = inputsRef.current.map(input => ({
+ tsid: input.tsid,
+ values: input.getValues(),
+ units: input.units,
+ precision: input.precision,
+ offset: input.offset,
+ order: input.order,
+ }));
+
+ // Call custom onSubmit if provided
+ if (onSubmit) {
+ await onSubmit(formData, e);
+ } else {
+ // Default submit behavior - send to CWMS
+ for (const input of inputsRef.current) {
+ if (input.tsid) {
+ try {
+ const response = await ts_api.postTimeSeries({
+ timeSeries: {
+ name: input.tsid,
+ officeId: office,
+ values: input.getValues(),
+ units: input.units,
+ },
+ });
+ const result = await response.json();
+ console.log("API result:", result);
+ } catch (err) {
+ console.error("API call failed:", err);
+ }
+ }
}
}
};
- const handleReset = () => {
+ const handleReset = (e) => {
+ if (e) e.preventDefault();
+
inputsRef.current.forEach((input) => {
- input.reset();
+ if (input.reset) {
+ input.reset();
+ }
});
+
+ // Call custom onReset if provided
+ if (onReset) {
+ onReset(e);
+ }
+ };
+
+ const formStyle = {
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '1rem',
+ ...style
+ };
+
+ const buttonContainerStyle = {
+ display: 'flex',
+ gap: '0.75rem',
+ marginTop: '1.5rem',
+ paddingTop: '1rem',
+ borderTop: '1px solid #e5e7eb'
};
return (
-
);
-}
+}
\ No newline at end of file
diff --git a/lib/components/data/forms/inputs/CWMSCheckboxes.jsx b/lib/components/data/forms/inputs/CWMSCheckboxes.jsx
new file mode 100644
index 0000000..21d7c5d
--- /dev/null
+++ b/lib/components/data/forms/inputs/CWMSCheckboxes.jsx
@@ -0,0 +1,120 @@
+import React, { useEffect, useContext } from "react";
+import { Checkboxes } from "@usace/groundwork";
+import { FormContext } from "../../forms/CWMSForm";
+
+function CWMSCheckboxes({
+ style,
+ legend,
+ content = [],
+ onChange,
+}) {
+ const { registerInput } = useContext(FormContext);
+
+ // Register each checkbox item that has a tsid
+ useEffect(() => {
+ if (registerInput) {
+ content.forEach((item) => {
+ if (item.tsid) {
+ const checkboxRef = {
+ tsid: item.tsid,
+ precision: item.precision || 2,
+ offset: item.offset || 0,
+ order: item.order || 1,
+ AllowMissingData: item.AllowMissingData !== undefined ? item.AllowMissingData : true,
+ loadNearest: item.loadNearest || "prev",
+ readonly: item.readonly || false,
+ units: item.units || "EN",
+ getValues: () => {
+ // Get the current checked state
+ const element = document.getElementById(item.id);
+ return element ? [element.checked] : [false];
+ },
+ reset: () => {
+ const element = document.getElementById(item.id);
+ if (element) {
+ element.checked = item.defaultChecked || false;
+ // Trigger onChange if provided
+ if (item.onChange) {
+ item.onChange({ target: element });
+ }
+ }
+ },
+ };
+ registerInput(checkboxRef);
+ }
+ });
+ }
+ }, [registerInput, content]);
+
+ // Process content to add CWMS-specific handling
+ const processedContent = content.map((item) => {
+ // Create a new object with all the original properties
+ const processedItem = {
+ ...item,
+ // Override disabled if readonly or disabled is set at item level
+ disabled: item.disabled || item.readonly || item.disable,
+ };
+
+ // Wrap the original onChange to include CWMS handling if needed
+ if (item.onChange) {
+ const originalOnChange = item.onChange;
+ processedItem.onChange = (e) => {
+ // Call the original onChange
+ originalOnChange(e);
+
+ // Call the parent onChange if provided
+ if (onChange) {
+ // Collect all checked values
+ const checkedValues = content
+ .filter((contentItem) => {
+ if (contentItem.id === item.id) {
+ return e.target.checked;
+ }
+ const element = document.getElementById(contentItem.id);
+ return element ? element.checked : false;
+ })
+ .map(contentItem => contentItem.value || contentItem.label || contentItem.id);
+
+ onChange(checkedValues);
+ }
+ };
+ }
+
+ return processedItem;
+ });
+
+ return (
+
+ );
+}
+
+// Extended checkbox item interface for documentation
+// Each item in content array can have:
+// - All standard Groundwork checkbox properties:
+// - id: string (required) - Unique identifier
+// - label: string - Display label
+// - description: string - Additional description text
+// - defaultChecked: boolean - Initial checked state
+// - onChange: function - Handler for changes
+// - disabled: boolean - Disabled state
+// - inputProps: object - Props for the input element
+// - labelProps: object - Props for the label element
+//
+// - Additional CWMS properties:
+// - tsid: string - Time series ID for CWMS data
+// - value: string - Value when checked (defaults to label or id)
+// - readonly: boolean - Make checkbox read-only
+// - disable: boolean - Alias for disabled
+// - precision: number - Decimal precision for numeric values
+// - offset: number - Time offset in seconds
+// - order: number - Sort order
+// - AllowMissingData: boolean - Allow missing data points
+// - loadNearest: string - Load nearest data point strategy
+// - units: string - Unit system (EN/SI)
+
+export default CWMSCheckboxes;
+export { CWMSCheckboxes };
\ No newline at end of file
diff --git a/lib/components/data/forms/inputs/CWMSDropdown.jsx b/lib/components/data/forms/inputs/CWMSDropdown.jsx
new file mode 100644
index 0000000..fd816f3
--- /dev/null
+++ b/lib/components/data/forms/inputs/CWMSDropdown.jsx
@@ -0,0 +1,92 @@
+import React, { useEffect, useState, useContext } from "react";
+import { Dropdown } from "@usace/groundwork";
+import { FormContext } from "../../forms/CWMSForm";
+
+function CWMSDropdown({
+ style,
+ disable,
+ invalid,
+ name,
+ defaultValue,
+ value,
+ options,
+ tsid,
+ precision,
+ offset,
+ order,
+ AllowMissingData,
+ loadNearest,
+ readonly,
+ units,
+ onChange,
+ placeholder,
+}) {
+ const { registerInput } = useContext(FormContext);
+ const [selectedValue, setSelectedValue] = useState(
+ defaultValue || value || ""
+ );
+
+ const dropdownRef = {
+ tsid,
+ precision: precision || 2,
+ offset: offset || 0,
+ order: order || 1,
+ AllowMissingData: AllowMissingData !== undefined ? AllowMissingData : true,
+ loadNearest: loadNearest || "prev",
+ readonly: readonly || false,
+ units: units || "EN",
+ getValues: () => [selectedValue],
+ reset: () => setSelectedValue(defaultValue || ""),
+ };
+
+ useEffect(() => {
+ if (registerInput) {
+ registerInput(dropdownRef);
+ }
+ }, [registerInput]);
+
+ const handleChange = (newValue) => {
+ setSelectedValue(newValue);
+ if (onChange) {
+ onChange(newValue);
+ }
+ };
+
+ // Convert options to proper format if needed
+ let dropdownOptions = options;
+ if (Array.isArray(options) && options.length > 0) {
+ if (typeof options[0] === 'string') {
+ // Convert string array to option elements
+ dropdownOptions = [
+ placeholder && ,
+ ...options.map((opt) => (
+
+ ))
+ ].filter(Boolean);
+ } else if (typeof options[0] === 'object' && options[0].text !== undefined) {
+ // Convert object array to option elements
+ dropdownOptions = options.map((opt) => (
+
+ ));
+ }
+ }
+
+ return (
+ handleChange(e.target.value)}
+ />
+ );
+}
+
+export default CWMSDropdown;
+export { CWMSDropdown };
\ No newline at end of file
diff --git a/lib/components/data/tables/inputs/CWMSInput.jsx b/lib/components/data/forms/inputs/CWMSInput.jsx
similarity index 82%
rename from lib/components/data/tables/inputs/CWMSInput.jsx
rename to lib/components/data/forms/inputs/CWMSInput.jsx
index 6e64461..92bf7c0 100644
--- a/lib/components/data/tables/inputs/CWMSInput.jsx
+++ b/lib/components/data/forms/inputs/CWMSInput.jsx
@@ -2,7 +2,7 @@ import React, { useEffect, useState, useContext } from "react";
import { Input } from "@usace/groundwork";
import { FormContext } from "../../forms/CWMSForm";
-export function CWMSInput({
+function CWMSInput({
style,
disable,
invalid,
@@ -27,11 +27,11 @@ export function CWMSInput({
const inputRef = {
tsid,
precision: precision || 2,
- offset: timeoffset || 0,
+ offset: offset || 0,
order: order || 1,
- AllowMissingData: true,
- loadNearest: "prev",
- readonly: false,
+ AllowMissingData: AllowMissingData !== undefined ? AllowMissingData : true,
+ loadNearest: loadNearest || "prev",
+ readonly: readonly || false,
units: units || "EN",
getValues: () => [inputValue],
reset: () => setInputValue(defaultValue || ""),
@@ -63,3 +63,6 @@ export function CWMSInput({
/>
);
}
+
+export default CWMSInput;
+export { CWMSInput };
diff --git a/lib/components/data/forms/inputs/CWMSInputTable.jsx b/lib/components/data/forms/inputs/CWMSInputTable.jsx
new file mode 100644
index 0000000..c26abaf
--- /dev/null
+++ b/lib/components/data/forms/inputs/CWMSInputTable.jsx
@@ -0,0 +1,125 @@
+import React, { useEffect, useState, useContext } from "react";
+import { Input } from "@usace/groundwork";
+import { FormContext } from "../../forms/CWMSForm";
+
+function CWMSInputTable({
+ style,
+ disable,
+ invalid,
+ tsids = [],
+ timeoffsets = [],
+ precision,
+ order,
+ AllowMissingData,
+ loadNearest,
+ readonly,
+ units,
+ onChange,
+ showTimestamps = true,
+ defaultValues = {},
+}) {
+ const { registerInput } = useContext(FormContext);
+ const [matrixData, setMatrixData] = useState(defaultValues);
+
+ const tableRef = {
+ tsids,
+ precision: precision || 2,
+ timeoffsets: timeoffsets || [0],
+ order: order || 1,
+ AllowMissingData: AllowMissingData !== undefined ? AllowMissingData : true,
+ loadNearest: loadNearest || "prev",
+ readonly: readonly || false,
+ units: units || "EN",
+ getValues: () => matrixData,
+ reset: () => setMatrixData(defaultValues),
+ };
+
+ useEffect(() => {
+ if (registerInput) {
+ registerInput(tableRef);
+ }
+ }, [registerInput]);
+
+ const handleInputChange = (tsid, offset, value) => {
+ const key = `${tsid}_${offset}`;
+ const updatedData = {
+ ...matrixData,
+ [key]: value,
+ };
+ setMatrixData(updatedData);
+ if (onChange) {
+ onChange(updatedData);
+ }
+ };
+
+ const formatTimestamp = (offset) => {
+ const currentTime = new Date();
+ const offsetTime = new Date(currentTime.getTime() + offset * 1000);
+ return offsetTime.toISOString().replace('T', ' ').split('.')[0];
+ };
+
+ return (
+
- The FormWrapper component provides a context for CWMS form inputs,
- handling data collection, validation, and submission to the CWMS Data
- API. It automatically manages form state and provides styled
- submit/reset buttons.
+ The FormWrapper component provides a context for CWMS form inputs, handling
+ data collection, validation, and submission to the CWMS Data API. It
+ automatically manages form state and provides styled submit/reset buttons.
- All CWMS input components (CWMSInput, CWMSTextarea, CWMSDropdown,
- etc.) must be wrapped in a FormWrapper to function properly.
+ All CWMS input components (CWMSInput, CWMSTextarea, CWMSDropdown, etc.) must
+ be wrapped in a FormWrapper to function properly.
@@ -193,41 +212,45 @@ function FormWrapperDocsContent({ authMethod, testCdaUrl, setTestCdaUrl }) {
Enter your CWMS credentials to enable form submissions to {testCdaUrl}
-
+