From 6dbc8b5a6cef38e527736c19bf6eb6191d6ba10d Mon Sep 17 00:00:00 2001 From: Jeremy Zilar Date: Tue, 9 Jun 2026 11:56:29 -0400 Subject: [PATCH 01/38] Add DataGrid example page, Glide Data Grid, and supporting packages --- index.html | 2 + package-lock.json | 280 +++++++++- package.json | 5 +- src/App.tsx | 4 +- src/components/AppShell.tsx | 8 + src/pages/example/DataGridPage.tsx | 790 +++++++++++++++++++++++++++++ 6 files changed, 1086 insertions(+), 3 deletions(-) create mode 100644 src/pages/example/DataGridPage.tsx diff --git a/index.html b/index.html index c81ace4b..54661045 100644 --- a/index.html +++ b/index.html @@ -45,6 +45,8 @@
+ +
diff --git a/package-lock.json b/package-lock.json index d3216d25..d90fbf6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@fontsource-variable/geist": "^5.2.9", "@fontsource-variable/outfit": "^5.2.8", "@fontsource-variable/public-sans": "^5.2.7", + "@glideapps/glide-data-grid": "^6.0.3", "@hookform/resolvers": "^5.2.2", "@mapbox/mapbox-gl-draw": "^1.4.3", "@mapbox/mapbox-gl-geocoder": "^5.0.3", @@ -51,10 +52,11 @@ "echarts-for-react": "^3.0.2", "js-sha256": "^0.11.0", "jwt-decode": "^4.0.0", - "lodash": "^4.17.21", + "lodash": "^4.18.1", "lucide-react": "^1.17.0", "mapbox-gl": "^3.0.0", "mapbox-gl-style-switcher": "^1.0.11", + "marked": "^4.3.0", "pako": "^2.1.0", "posthog-js": "^1.363.1", "proj4": "^2.15.0", @@ -66,6 +68,7 @@ "react-input-mask": "^2.0.4", "react-map-gl": "^7.1.7", "react-markdown": "^10.1.0", + "react-responsive-carousel": "^3.2.23", "react-router": "^7.13.1", "shadcn": "^4.10.0", "tailwind-merge": "^3.6.0", @@ -466,6 +469,47 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-proposal-export-namespace-from": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", + "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead.", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.9", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-flow": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.28.6.tgz", @@ -1806,6 +1850,24 @@ "url": "https://github.com/sponsors/ayuhito" } }, + "node_modules/@glideapps/glide-data-grid": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@glideapps/glide-data-grid/-/glide-data-grid-6.0.3.tgz", + "integrity": "sha512-YXKggiNOaEemf0jP0jORq2EQKz+zXms+6mGzZc+q0mLMjmgzzoGLOQC1uYcynXSj1R61bd27JcPFsoH+Gj37Vg==", + "license": "MIT", + "dependencies": { + "@linaria/react": "^4.5.3", + "canvas-hypertxt": "^1.0.3", + "react-number-format": "^5.0.0" + }, + "peerDependencies": { + "lodash": "^4.17.19", + "marked": "^4.0.10", + "react": "^16.12.0 || 17.x || 18.x", + "react-dom": "^16.12.0 || 17.x || 18.x", + "react-responsive-carousel": "^3.2.7" + } + }, "node_modules/@hey-api/codegen-core": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@hey-api/codegen-core/-/codegen-core-0.2.0.tgz", @@ -2228,6 +2290,91 @@ "jsep": "^0.4.0||^1.0.0" } }, + "node_modules/@linaria/core": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/@linaria/core/-/core-4.5.4.tgz", + "integrity": "sha512-vMs/5iU0stxjfbBCxobIgY+wSQx4G8ukNwrhjPVD+6bF9QrTwi5rl0mKaCMxaGMjnfsLRiiM3i+hnWLIEYLdSg==", + "license": "MIT", + "dependencies": { + "@linaria/logger": "^4.5.0", + "@linaria/tags": "^4.5.4", + "@linaria/utils": "^4.5.3" + }, + "engines": { + "node": "^12.16.0 || >=13.7.0" + } + }, + "node_modules/@linaria/logger": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@linaria/logger/-/logger-4.5.0.tgz", + "integrity": "sha512-XdQLk242Cpcsc9a3Cz1ktOE5ysTo2TpxdeFQEPwMm8Z/+F/S6ZxBDdHYJL09srXWz3hkJr3oS2FPuMZNH1HIxw==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "picocolors": "^1.0.0" + }, + "engines": { + "node": "^12.16.0 || >=13.7.0" + } + }, + "node_modules/@linaria/react": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/@linaria/react/-/react-4.5.4.tgz", + "integrity": "sha512-/dhCVCsfdGPfQCPV0q5yy+DDlFXepvfXrw/os2fC+Xo1v9J/9gyiaBBWHzcumauvNNFj8aN6vRkj89fMujPHew==", + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "^1.2.0", + "@linaria/core": "^4.5.4", + "@linaria/tags": "^4.5.4", + "@linaria/utils": "^4.5.3", + "minimatch": "^9.0.3", + "react-html-attributes": "^1.4.6", + "ts-invariant": "^0.10.3" + }, + "engines": { + "node": "^12.16.0 || >=13.7.0" + }, + "peerDependencies": { + "react": ">=16" + } + }, + "node_modules/@linaria/tags": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/@linaria/tags/-/tags-4.5.4.tgz", + "integrity": "sha512-HPxLB6HlJWLi6o8+8lTLegOmDnbMbuzEE+zzunaPZEGSoIIYx8HAv5VbY/sG/zNyxDElk6laiAwEVWN8h5/zxg==", + "license": "MIT", + "dependencies": { + "@babel/generator": "^7.22.9", + "@linaria/logger": "^4.5.0", + "@linaria/utils": "^4.5.3" + }, + "engines": { + "node": "^12.16.0 || >=13.7.0" + } + }, + "node_modules/@linaria/utils": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/@linaria/utils/-/utils-4.5.3.tgz", + "integrity": "sha512-tSpxA3Zn0DKJ2n/YBnYAgiDY+MNvkmzAHrD8R9PKrpGaZ+wz1jQEmE1vGn1cqh8dJyWK0NzPAA8sf1cqa+RmAg==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.22.9", + "@babel/generator": "^7.22.9", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-transform-modules-commonjs": "^7.22.5", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.8", + "@babel/types": "^7.22.5", + "@linaria/logger": "^4.5.0", + "babel-merge": "^3.0.0", + "find-up": "^5.0.0", + "minimatch": "^9.0.3" + }, + "engines": { + "node": "^12.16.0 || >=13.7.0" + } + }, "node_modules/@mapbox/fusspot": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@mapbox/fusspot/-/fusspot-0.4.0.tgz", @@ -11730,6 +11877,29 @@ "node": ">=10" } }, + "node_modules/babel-merge": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/babel-merge/-/babel-merge-3.0.0.tgz", + "integrity": "sha512-eBOBtHnzt9xvnjpYNI5HmaPp/b2vMveE5XggzqHnQeHJ8mFIBrBv6WZEVIj5jJ2uwTItkqKo9gWzEEcBxEq0yw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT", + "dependencies": { + "deepmerge": "^2.2.1", + "object.omit": "^3.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-merge/node_modules/deepmerge": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", + "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", @@ -12332,6 +12502,12 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvas-hypertxt": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/canvas-hypertxt/-/canvas-hypertxt-1.0.3.tgz", + "integrity": "sha512-+VsMpRr64jYgKq2IeFUNel3vCZH/IzS+iXSHxmUV3IUH5dXlC9xHz4AwtPZisDxZ5MWcuK0V+TXgPKFPiZnxzg==", + "license": "MIT" + }, "node_modules/cardinal": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-2.1.1.tgz", @@ -12552,6 +12728,12 @@ "url": "https://polar.sh/cva" } }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -16218,6 +16400,12 @@ "integrity": "sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==", "license": "ISC" }, + "node_modules/html-element-attributes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/html-element-attributes/-/html-element-attributes-1.3.1.tgz", + "integrity": "sha512-UrRKgp5sQmRnDy4TEwAUsu14XBUlzKB8U3hjIYDjcZ3Hbp86Jtftzxfgrv6E/ii/h78tsaZwAnAE8HwnHr0dPA==", + "license": "MIT" + }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", @@ -21438,6 +21626,42 @@ "node": ">= 10" } }, + "node_modules/object.omit": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-3.0.0.tgz", + "integrity": "sha512-EO+BCv6LJfu+gBIF3ggLicFebFLN5zqzz/WWJlMFfkMyGth+oBkhxzDl0wx2W4GkLzuQs/FsSkXZb2IMWQqmBQ==", + "license": "MIT", + "dependencies": { + "is-extendable": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.omit/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.omit/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ohash": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", @@ -23244,6 +23468,18 @@ "loose-envify": "^1.1.0" } }, + "node_modules/react-easy-swipe": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/react-easy-swipe/-/react-easy-swipe-0.0.21.tgz", + "integrity": "sha512-OeR2jAxdoqUMHIn/nS9fgreI5hSpgGoL5ezdal4+oO7YSSgJR8ga+PkYGJrSrJ9MKlPcQjMQXnketrD7WNmNsg==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.5.8" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/react-error-boundary": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-6.1.1.tgz", @@ -23282,6 +23518,15 @@ "react": "^16.8.0 || ^17 || ^18 || ^19" } }, + "node_modules/react-html-attributes": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/react-html-attributes/-/react-html-attributes-1.4.6.tgz", + "integrity": "sha512-uS3MmThNKFH2EZUQQw4k5pIcU7XIr208UE5dktrj/GOH1CMagqxDl4DCLpt3o2l9x+IB5nVYBeN3Cr4IutBXAg==", + "license": "MIT", + "dependencies": { + "html-element-attributes": "^1.0.0" + } + }, "node_modules/react-input-mask": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/react-input-mask/-/react-input-mask-2.0.4.tgz", @@ -23353,6 +23598,16 @@ "react": ">=18" } }, + "node_modules/react-number-format": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.5.tgz", + "integrity": "sha512-y8O2yHHj3w0aE9XO8d2BCcUOOdQTRSVq+WIuMlLVucAm5XNjJAy+BoOJiuQMldVYVOKTMyvVNfnbl2Oqp+YxGw==", + "license": "MIT", + "peerDependencies": { + "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/react-reconciler": { "version": "0.29.2", "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.2.tgz", @@ -23435,6 +23690,17 @@ } } }, + "node_modules/react-responsive-carousel": { + "version": "3.2.23", + "resolved": "https://registry.npmjs.org/react-responsive-carousel/-/react-responsive-carousel-3.2.23.tgz", + "integrity": "sha512-pqJLsBaKHWJhw/ItODgbVoziR2z4lpcJg+YwmRlSk4rKH32VE633mAtZZ9kDXjy4wFO+pgUZmDKPsPe1fPmHCg==", + "license": "MIT", + "dependencies": { + "classnames": "^2.2.5", + "prop-types": "^15.5.8", + "react-easy-swipe": "^0.0.21" + } + }, "node_modules/react-router": { "version": "7.14.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.0.tgz", @@ -26213,6 +26479,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/ts-invariant": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.10.3.tgz", + "integrity": "sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ts-morph": { "version": "26.0.0", "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-26.0.0.tgz", diff --git a/package.json b/package.json index 778f4c1e..b4638253 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@fontsource-variable/geist": "^5.2.9", "@fontsource-variable/outfit": "^5.2.8", "@fontsource-variable/public-sans": "^5.2.7", + "@glideapps/glide-data-grid": "^6.0.3", "@hookform/resolvers": "^5.2.2", "@mapbox/mapbox-gl-draw": "^1.4.3", "@mapbox/mapbox-gl-geocoder": "^5.0.3", @@ -75,10 +76,11 @@ "echarts-for-react": "^3.0.2", "js-sha256": "^0.11.0", "jwt-decode": "^4.0.0", - "lodash": "^4.17.21", + "lodash": "^4.18.1", "lucide-react": "^1.17.0", "mapbox-gl": "^3.0.0", "mapbox-gl-style-switcher": "^1.0.11", + "marked": "^4.3.0", "pako": "^2.1.0", "posthog-js": "^1.363.1", "proj4": "^2.15.0", @@ -90,6 +92,7 @@ "react-input-mask": "^2.0.4", "react-map-gl": "^7.1.7", "react-markdown": "^10.1.0", + "react-responsive-carousel": "^3.2.23", "react-router": "^7.13.1", "shadcn": "^4.10.0", "tailwind-merge": "^3.6.0", diff --git a/src/App.tsx b/src/App.tsx index 6359b8ac..07496244 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,6 +6,7 @@ import { ThemedTitleV2 } from '@/components/layout/title' import { Callback } from '@/components/Auth' import { Home } from '@/pages/home' import { TypographyPage } from '@/pages/example/TypographyPage' +import { DataGridPage } from '@/pages/example/DataGridPage' import { ContentPage } from '@/pages/content' import { OcotilloRoutes, ST2Routes } from '@/routes' import { settings } from '@/settings' @@ -62,8 +63,9 @@ const App: React.FC = () => ( path="/ogcapi" element={} /> - {/* TEMPORARY: typography specimen */} + {/* TEMPORARY: example specimen pages */} } /> + } /> } /> } /> diff --git a/src/components/AppShell.tsx b/src/components/AppShell.tsx index fce1bee3..7ed30565 100644 --- a/src/components/AppShell.tsx +++ b/src/components/AppShell.tsx @@ -346,6 +346,14 @@ function ExampleNavItem() { Typography + + + Data Grid + + diff --git a/src/pages/example/DataGridPage.tsx b/src/pages/example/DataGridPage.tsx new file mode 100644 index 00000000..48622448 --- /dev/null +++ b/src/pages/example/DataGridPage.tsx @@ -0,0 +1,790 @@ +// TEMPORARY — Glide Data Grid specimen page. Delete when design work is settled. +import { useCallback, useContext, useEffect, useRef, useState } from 'react' +import '@glideapps/glide-data-grid/dist/index.css' +import { + DataEditor, + EditableGridCell, + GridCell, + GridCellKind, + GridColumn, + Item, +} from '@glideapps/glide-data-grid' +import { useList, useNavigation } from '@refinedev/core' +import { ColorModeContext } from '@/contexts' +import type { IWell } from '@/interfaces/ocotillo' +import { displayWellSiteName, formatAppDate } from '@/utils' +import { getContactDisplayName } from '@/utils/contactDisplayName' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Separator } from '@/components/ui/separator' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { Checkbox } from '@/components/ui/checkbox' +import { Textarea } from '@/components/ui/textarea' +import { + Collapsible, + CollapsibleContent, + CollapsibleTrigger, +} from '@/components/ui/collapsible' +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from '@/components/ui/tooltip' +import { + ChevronRight, + Download, + ExternalLink, + Filter, + Plus, + Rows3, + Upload, + X, +} from 'lucide-react' +import { cn } from '@/lib/utils' + +const COLUMNS: GridColumn[] = [ + { title: 'Well ID', id: 'name', width: 140 }, + { title: 'Site Name', id: 'site_name', width: 200 }, + { title: 'Monitoring', id: 'monitoring_status', width: 160 }, + { title: 'Well Status', id: 'well_status', width: 150 }, + { title: 'Type', id: 'thing_type', width: 130 }, + { title: 'Release Status', id: 'release_status', width: 130 }, + { title: 'Well Depth (ft)', id: 'well_depth', width: 130 }, + { title: 'First Visit', id: 'first_visit_date', width: 120 }, + { title: 'Aquifers', id: 'aquifers', width: 240 }, + { title: 'Contacts', id: 'contacts', width: 240 }, + { title: 'Created', id: 'created_at', width: 120 }, +] + +function getCellValue(well: IWell, colId: string): string { + switch (colId) { + case 'name': return well.name ?? '' + case 'site_name': return displayWellSiteName(well) + case 'monitoring_status': return well.monitoring_status ?? '' + case 'well_status': return well.well_status ?? '' + case 'thing_type': return well.thing_type ?? '' + case 'release_status': return well.release_status ?? '' + case 'well_depth': return well.well_depth != null ? String(well.well_depth) : '' + case 'first_visit_date': return formatAppDate(well.first_visit_date) + case 'aquifers': return well.aquifers?.map(a => a.aquifer_system).join(', ') ?? '' + case 'contacts': return well.contacts?.map(c => getContactDisplayName(c)).join(', ') ?? '' + case 'created_at': return formatAppDate(well.created_at) + default: return '' + } +} + +// A collapsible section used inside the Create Well panel +function FormSection({ + title, + defaultOpen = true, + children, +}: { + title: string + defaultOpen?: boolean + children: React.ReactNode +}) { + const [open, setOpen] = useState(defaultOpen) + return ( + + + + {title} + + +
+ {children} +
+
+
+ ) +} + +// A single labeled field cell inside a FormSection +function Field({ + label, + required, + span, + children, +}: { + label: string + required?: boolean + span?: 'full' + children: React.ReactNode +}) { + return ( +
+ + {children} +
+ ) +} + +function CreateWellPanel({ onClose }: { onClose: () => void }) { + return ( +
+ {/* Panel header */} +
+ Create Well + +
+ + {/* Scrollable form body */} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +