From 4e22e1cc86e9489bc37a954838f9ac07ebdf1ee5 Mon Sep 17 00:00:00 2001 From: Dennis Smith Date: Wed, 29 Nov 2023 08:48:45 -0500 Subject: [PATCH 01/10] configure pathname prefix depending on build env vars (#194) * update vite base path by env var; fix .env formatting * update links to use relative bath including base * alert content use redux actions * remove uneeded Link wrapper --- .env.production | 13 +-- .env.test | 13 +-- .github/workflows/develop.yml | 1 + .github/workflows/stable.yml | 1 + src/app-bundles/collection-group-bundle.js | 4 +- src/app-bundles/create-auth-bundle.js | 2 +- .../create-url-base-path-bundle.js | 13 +++ src/app-bundles/home-data-bundle.js | 6 +- src/app-bundles/inclinometer-measurements.js | 4 +- src/app-bundles/index.js | 2 + src/app-bundles/instrument-bundle.js | 4 +- src/app-bundles/instrument-group-bundle.js | 4 +- src/app-bundles/profile-bundle.js | 8 +- src/app-bundles/routes-bundle.js | 24 +++--- .../time-series-measurements-bundle.js | 4 +- src/app-components/navigation/navBar.jsx | 6 +- src/app-components/navigation/navItem.jsx | 4 +- src/app-components/pageContent.jsx | 4 +- src/app-pages/home/project-list.jsx | 6 +- src/app-pages/profile/userProfile.jsx | 11 +-- .../dashboard/cards/dataLoggerCard.jsx | 10 +-- src/app-pages/signup/signup.jsx | 8 +- src/index.jsx | 2 +- vite.config.js | 85 ++++++++++--------- 24 files changed, 131 insertions(+), 108 deletions(-) create mode 100644 src/app-bundles/create-url-base-path-bundle.js diff --git a/.env.production b/.env.production index a6be6968..5950b945 100644 --- a/.env.production +++ b/.env.production @@ -1,6 +1,7 @@ -VITE_ALERT_EDITOR: false -VITE_FORMULA_EDITOR: true -VITE_INSTRUMENT_CHART: true -VITE_CROSS_SECTION: true -VITE_DEVELOPMENT_BANNER: false -VITE_API_URL: https://midas.sec.usace.army.mil/api +VITE_ALERT_EDITOR=false +VITE_FORMULA_EDITOR=true +VITE_INSTRUMENT_CHART=true +VITE_CROSS_SECTION=true +VITE_DEVELOPMENT_BANNER=false +VITE_API_URL=https://midas.sec.usace.army.mil/api +VITE_URL_BASE_PATH=/midas diff --git a/.env.test b/.env.test index bb89df14..0783a221 100644 --- a/.env.test +++ b/.env.test @@ -1,6 +1,7 @@ -VITE_ALERT_EDITOR: false -VITE_FORMULA_EDITOR: true -VITE_INSTRUMENT_CHART: true -VITE_CROSS_SECTION: true -VITE_DEVELOPMENT_BANNER: false -VITE_API_URL: https://midas-test.cwbi.us/api +VITE_ALERT_EDITOR=false +VITE_FORMULA_EDITOR=true +VITE_INSTRUMENT_CHART=true +VITE_CROSS_SECTION=true +VITE_DEVELOPMENT_BANNER=false +VITE_API_URL=https://midas-test.cwbi.us/api +VITE_URL_BASE_PATH=/midas diff --git a/.github/workflows/develop.yml b/.github/workflows/develop.yml index d4ac9a19..595b3ceb 100644 --- a/.github/workflows/develop.yml +++ b/.github/workflows/develop.yml @@ -18,6 +18,7 @@ jobs: VITE_CROSS_SECTION: true VITE_DEVELOPMENT_BANNER: true VITE_API_URL: https://develop-midas-api.rsgis.dev + VITE_URL_BASE_PATH: '' runs-on: ubuntu-latest strategy: matrix: diff --git a/.github/workflows/stable.yml b/.github/workflows/stable.yml index 87044126..9010805a 100644 --- a/.github/workflows/stable.yml +++ b/.github/workflows/stable.yml @@ -14,6 +14,7 @@ jobs: env: VITE_FORMULA_EDITOR: true VITE_API_URL: https://midas-api.rsgis.dev + VITE_URL_BASE_PATH: '' runs-on: ubuntu-latest strategy: matrix: diff --git a/src/app-bundles/collection-group-bundle.js b/src/app-bundles/collection-group-bundle.js index 60ff7d33..52f93b0c 100644 --- a/src/app-bundles/collection-group-bundle.js +++ b/src/app-bundles/collection-group-bundle.js @@ -20,11 +20,11 @@ export default createRestBundle({ urlParamSelectors: ['selectProjectsIdByRoute'], prefetch: (store) => { const hash = store.selectHash(); - const url = store.selectUrlObject(); + const pathname = store.selectRelativePathname(); const whiteList = ['dashboard']; return ( - whiteList.includes(hash) || url.pathname.includes('/collection-groups/') + whiteList.includes(hash) || pathname.includes('/collection-groups/') ); }, addons: { diff --git a/src/app-bundles/create-auth-bundle.js b/src/app-bundles/create-auth-bundle.js index 1d5ef0ae..870bc73a 100644 --- a/src/app-bundles/create-auth-bundle.js +++ b/src/app-bundles/create-auth-bundle.js @@ -89,7 +89,7 @@ const createAuthBundle = (opts) => { }); store.doRemoveProfile(); const redirect = store.selectAuthRedirectOnLogout(); - if (redirect) store.doUpdateUrl(redirect); + if (redirect) store.doUpdateRelativeUrl(redirect); } }, diff --git a/src/app-bundles/create-url-base-path-bundle.js b/src/app-bundles/create-url-base-path-bundle.js new file mode 100644 index 00000000..393d948a --- /dev/null +++ b/src/app-bundles/create-url-base-path-bundle.js @@ -0,0 +1,13 @@ +import { createSelector } from "redux-bundler"; + +export default ({ base }) => { + return { + name: 'urlBasePath', + + selectRelativePathname: createSelector('selectPathname', pathname => pathname.replace(base, '')), + + doUpdateRelativeUrl: (url, opts) => ({ store }) => { + store.doUpdateUrl(`${base}${url}`, opts) + }, + } +}; diff --git a/src/app-bundles/home-data-bundle.js b/src/app-bundles/home-data-bundle.js index b7a6ec8f..c90f0317 100644 --- a/src/app-bundles/home-data-bundle.js +++ b/src/app-bundles/home-data-bundle.js @@ -11,11 +11,7 @@ export default createRestBundle({ getTemplate: '/home', fetchActions: ['URL_UPDATED', 'AUTH_LOGGED_IN'], forceFetchActions: [], - prefetch: store => { - const url = store.selectUrlObject(); - - return url.pathname === '/'; - }, + prefetch: store => store.selectRelativePathname() === '/', addons: { selectHomeData: createSelector('selectHomeItems', (items) => { const data = items && items.length ? items[0] : null; diff --git a/src/app-bundles/inclinometer-measurements.js b/src/app-bundles/inclinometer-measurements.js index dce469fd..d41b1167 100644 --- a/src/app-bundles/inclinometer-measurements.js +++ b/src/app-bundles/inclinometer-measurements.js @@ -25,12 +25,12 @@ export default createRestBundle({ mergeItems: true, prefetch: (store) => { const hash = store.selectHash(); - const url = store.selectUrlObject(); + const pathname = store.selectRelativePathname(); const whitelist = []; const pathnameWhitelist = ['/instruments/', '/groups/', '/collection-groups/']; - return whitelist.includes(hash) || pathnameWhitelist.some(elem => url.pathname.includes(elem)); + return whitelist.includes(hash) || pathnameWhitelist.some(elem => pathname.includes(elem)); }, addons: { doFetchInclinometerMeasurementsByTimeseriesId: (timeseriesId) => ({ dispatch, apiGet }) => { diff --git a/src/app-bundles/index.js b/src/app-bundles/index.js index a953d183..a8fd5bf0 100644 --- a/src/app-bundles/index.js +++ b/src/app-bundles/index.js @@ -7,6 +7,7 @@ import { import createAuthBundle from './create-auth-bundle'; // Required change from @corpsmap/create-jwt-api-bundle; import createJwtApiBundle from './create-jwt-api-bundle'; +import createUrlBasePathBundle from './create-url-base-path-bundle'; import cache from '../common/helpers/cache'; import alertReadBundle from './alert-read-bundle'; @@ -108,6 +109,7 @@ export default composeBundles( cacheFn: cache.set, }), createUrlBundle(), + createUrlBasePathBundle({ base: import.meta.env.VITE_URL_BASE_PATH ?? '' }), alertReadBundle, alertSubscribeBundle, alertUnreadBundle, diff --git a/src/app-bundles/instrument-bundle.js b/src/app-bundles/instrument-bundle.js index 3bf8e324..8bc65700 100644 --- a/src/app-bundles/instrument-bundle.js +++ b/src/app-bundles/instrument-bundle.js @@ -31,7 +31,7 @@ export default createRestBundle({ urlParamSelectors: ['selectProjectsIdByRoute'], prefetch: (store) => { const hash = store.selectHash(); - const url = store.selectUrlObject(); + const pathname = store.selectRelativePathname(); const whiteList = [ 'dashboard', 'uploader', @@ -49,7 +49,7 @@ export default createRestBundle({ return ( whiteList.includes(hash) || - pathnameWhitelist.some((elem) => url.pathname.includes(elem)) + pathnameWhitelist.some((elem) => pathname.includes(elem)) ); }, addons: { diff --git a/src/app-bundles/instrument-group-bundle.js b/src/app-bundles/instrument-group-bundle.js index f94013e9..15b7d132 100644 --- a/src/app-bundles/instrument-group-bundle.js +++ b/src/app-bundles/instrument-group-bundle.js @@ -20,10 +20,10 @@ export default createRestBundle({ urlParamSelectors: ['selectProjectsIdByRoute'], prefetch: (store) => { const hash = store.selectHash(); - const url = store.selectUrlObject(); + const pathname = store.selectRelativePathname(); const whiteList = ['dashboard', 'explorer']; - return whiteList.includes(hash) || url.pathname.includes('/groups/'); + return whiteList.includes(hash) || pathname.includes('/groups/'); }, addons: { selectInstrumentGroupsIdByRoute: createSelector( diff --git a/src/app-bundles/profile-bundle.js b/src/app-bundles/profile-bundle.js index d080af75..06f12ca7 100644 --- a/src/app-bundles/profile-bundle.js +++ b/src/app-bundles/profile-bundle.js @@ -73,7 +73,7 @@ export default createRestBundle({ }), reactProfileExists: createSelector( 'selectAuthIsLoggedIn', - 'selectPathname', + 'selectRelativePathname', 'selectProfileIsLoading', 'selectProfileActive', (isLoggedIn, path, profileIsLoading, profile) => { @@ -81,7 +81,7 @@ export default createRestBundle({ if (!profile) { if (path !== '/signup') return { - actionCreator: 'doUpdateUrl', + actionCreator: 'doUpdateRelativeUrl', args: ['/signup'], }; } @@ -91,11 +91,11 @@ export default createRestBundle({ reactProfileCreatedRedirect: createSelector( 'selectProfileActive', 'selectAuthIsLoggedIn', - 'selectPathname', + 'selectRelativePathname', (profile, isLoggedIn, path) => { if (path === '/signup' && (profile || !isLoggedIn)) return { - actionCreator: 'doUpdateUrl', + actionCreator: 'doUpdateRelativeUrl', args: ['/'], }; } diff --git a/src/app-bundles/routes-bundle.js b/src/app-bundles/routes-bundle.js index 64dd96f5..d9650dc9 100644 --- a/src/app-bundles/routes-bundle.js +++ b/src/app-bundles/routes-bundle.js @@ -11,19 +11,21 @@ import Profile from '../app-pages/profile/userProfile'; import Project from '../app-pages/project'; import SignUp from '../app-pages/signup/signup'; +const base = import.meta.env.VITE_URL_BASE_PATH ?? '' + export default createRouteBundle( { - '': Home, - '/': Home, - '/help': Help, - '/logout': Logout, - '/signup': SignUp, - '/profile': Profile, - '/not-found': NotFound, - '/:projectSlug': Project, - '/:projectSlug/groups/:groupSlug': InstrumentGroup, - '/:projectSlug/instruments/:instrumentSlug': Instrument, - '/:projectSlug/collection-groups/:collectionGroupSlug': CollectionGroup, + [`${base}`]: Home, + [`${base}/`]: Home, + [`${base}/help`]: Help, + [`${base}/logout`]: Logout, + [`${base}/signup`]: SignUp, + [`${base}/profile`]: Profile, + [`${base}/not-found`]: NotFound, + [`${base}/:projectSlug`]: Project, + [`${base}/:projectSlug/groups/:groupSlug`]: InstrumentGroup, + [`${base}/:projectSlug/instruments/:instrumentSlug`]: Instrument, + [`${base}/:projectSlug/collection-groups/:collectionGroupSlug`]: CollectionGroup, '*': NotFound, } ); diff --git a/src/app-bundles/time-series-measurements-bundle.js b/src/app-bundles/time-series-measurements-bundle.js index cd1fb158..c4e60123 100644 --- a/src/app-bundles/time-series-measurements-bundle.js +++ b/src/app-bundles/time-series-measurements-bundle.js @@ -28,12 +28,12 @@ export default createRestBundle({ mergeItems: true, prefetch: (store) => { const hash = store.selectHash(); - const url = store.selectUrlObject(); + const pathname = store.selectRelativePathname(); const whitelist = []; const pathnameWhitelist = ['/instruments/', '/groups/', '/collection-groups/']; - return whitelist.includes(hash) || pathnameWhitelist.some(elem => url.pathname.includes(elem)); + return whitelist.includes(hash) || pathnameWhitelist.some(elem => pathname.includes(elem)); }, addons: { doTimeseriesMeasurementsFetchById: ({ diff --git a/src/app-components/navigation/navBar.jsx b/src/app-components/navigation/navBar.jsx index 88f5eb63..6cd0e0b7 100644 --- a/src/app-components/navigation/navBar.jsx +++ b/src/app-components/navigation/navBar.jsx @@ -27,12 +27,12 @@ const NavBar = connect( 'doAuthLogin', 'selectAuthIsLoggedIn', 'selectProjectsByRoute', - 'selectPathname', + 'selectRelativePathname', ({ doAuthLogin, authIsLoggedIn, projectsByRoute: project, - pathname, + relativePathname: pathname, }) => { const [hideBrand, setHideBrand] = useState(false); const [brand, setBrand] = useState(null); @@ -92,7 +92,7 @@ const NavBar = connect(
@@ -235,7 +235,7 @@ const DepthBasedPlots = connect( variant='info' size='small' text='Set Initial Time' - handleClick={() => doModalOpen(SetInitialTimeModal, {}, 'lg')} + handleClick={() => doModalOpen(SetInitialTimeModal, { type: 'saa' }, 'lg')} />
@@ -263,4 +263,4 @@ const DepthBasedPlots = connect( }, ); -export default DepthBasedPlots; +export default SaaDepthBasedPlots; diff --git a/src/app-pages/instrument/sensors/automapSensorModal.jsx b/src/app-pages/instrument/sensors/automapSensorModal.jsx index 111325cf..be549328 100644 --- a/src/app-pages/instrument/sensors/automapSensorModal.jsx +++ b/src/app-pages/instrument/sensors/automapSensorModal.jsx @@ -16,6 +16,7 @@ const AutomapSensorModal = connect( doUpdateInstrumentSensor, instrumentSensors, instrumentTimeseriesItems: timeseries, + type, }) => { const [overwriteExisting, setOverwriteExisting] = useState(false); const xyzTimeseries = []; @@ -79,7 +80,7 @@ const AutomapSensorModal = connect( formData.push(current); }); - doUpdateInstrumentSensor(formData); + doUpdateInstrumentSensor(type, formData); }; return ( diff --git a/src/app-pages/instrument/sensors/sensorDetails.jsx b/src/app-pages/instrument/sensors/sensorDetails.jsx index 154aed8e..cbe17336 100644 --- a/src/app-pages/instrument/sensors/sensorDetails.jsx +++ b/src/app-pages/instrument/sensors/sensorDetails.jsx @@ -20,6 +20,7 @@ const SensorDetails = connect( doUpdateInstrumentSensor, instrumentTimeseriesItemsByRoute: timeseries, activeSensor, + type, }) => { // eslint-disable-next-line no-unused-vars const { instrument_id, ...rest } = activeSensor || {}; @@ -27,7 +28,7 @@ const SensorDetails = connect( const tsOpts = generateOptions(timeseries); const getOption = key => tsOpts.find(opt => opt.value === options[key]?.val); - const handleSave = () => doUpdateInstrumentSensor([extractState(options)]); + const handleSave = () => doUpdateInstrumentSensor(type, [extractState(options)]); useDeepCompareEffect(() => { // eslint-disable-next-line no-unused-vars @@ -41,58 +42,122 @@ const SensorDetails = connect( Select a Sensor to assign or view mapped timeseries. ) : ( <> -
-
- -
-
- dispatch({ type: 'update', key: 'y_timeseries_id', data: item.value})} - options={tsOpts} - /> -
-
-
-
- -
-
- dispatch({ type: 'update', key: 'temp_timeseries_id', data: item.value })} - options={tsOpts} - /> -
-
+ {type === 'saa' && ( + <> +
+
+ +
+
+ dispatch({ type: 'update', key: 'y_timeseries_id', data: item.value})} + options={tsOpts} + /> +
+
+
+
+ +
+
+ dispatch({ type: 'update', key: 'temp_timeseries_id', data: item.value })} + options={tsOpts} + /> +
+
+ + )} + {type === 'ipi' && ( + <> +
+
+ +
+
+ dispatch({ type: 'update', key: 'length_timeseries_id', data: item?.value })} + options={tsOpts} + /> +
+
+
+
+ +
+
+ dispatch({ type: 'update', key: 'temp_timeseries_id', data: item?.value })} + options={tsOpts} + /> +
+
+ + )} + + + {!!selectedProject && ( + <> +
+ p.id === selectedProject)} setSelectedProject={setSelectedProject} /> + + )} + + + + + ); + } +); + +export default AdminPage; diff --git a/src/app-pages/admin/index.js b/src/app-pages/admin/index.js new file mode 100644 index 00000000..04965d7b --- /dev/null +++ b/src/app-pages/admin/index.js @@ -0,0 +1 @@ +export { default as default } from './admin'; diff --git a/src/app-pages/admin/projectDetails.jsx b/src/app-pages/admin/projectDetails.jsx new file mode 100644 index 00000000..b68987bb --- /dev/null +++ b/src/app-pages/admin/projectDetails.jsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { Button } from '@mui/material'; +import { connect } from 'redux-bundler-react'; +import { DateTime } from 'luxon'; + +import DeleteConfirm from '../../app-components/delete-confirm'; +import AddEditProjectModal from './addEditProjectModal'; + +const ProjectDetails = connect( + 'doModalOpen', + 'doProjectsDelete', + 'selectDistricts', + ({ + doModalOpen, + doProjectsDelete, + districts, + project, + setSelectedProject, + }) => { + const { _raw, title, img, href, instrumentCount } = project; + const { + create_date, + creator_username, + update_date, + updater_username, + federal_id, + district_id, + } = _raw; + + const currentDistrict = districts.find(el => el.id === district_id); + + return ( +
+
+ +
+ +
+
+
Project Details
+ Name: {title} + Federal ID: {federal_id ?? None} + District ID: {district_id ? `${currentDistrict.name} / ${district_id}` : None} + # of Assigned Instruments: {instrumentCount} +
+ Created On: {DateTime.fromISO(create_date).toLocaleString(DateTime.DATETIME_SHORT)} + Created By: {creator_username} + Updated On: {update_date ? DateTime.fromISO(update_date).toLocaleString(DateTime.DATETIME_SHORT) : Not Updated} + Updated By: {updater_username ?? Not Updated} +
+ + { + setSelectedProject(''); + doProjectsDelete(_raw); + }} + /> +
+
+ ); + } +); + +export default ProjectDetails; diff --git a/src/app-pages/home/homeFilters.js b/src/app-pages/home/homeFilters.js deleted file mode 100644 index 597e7d3f..00000000 --- a/src/app-pages/home/homeFilters.js +++ /dev/null @@ -1,271 +0,0 @@ -export const filters = [ - { - abbr: 'All', - text: 'All', - children: [], - }, - { - abbr: 'LRD', - text: 'Great Lakes and Ohio River Division', - children: [ - { - abbr: 'LRB', - text: 'Buffalo District', - children: [], - }, - { - abbr: 'LRC', - text: 'Chicago District', - children: [], - }, - { - abbr: 'LRE', - text: 'Detroit District', - children: [], - }, - { - abbr: 'LRH', - text: 'Huntington District', - children: [], - }, - { - abbr: 'LRL', - text: 'Louisville District', - children: [], - }, - { - abbr: 'LRN', - text: 'Nashville District', - children: [], - }, - { - abbr: 'LRP', - text: 'Pittsburgh District', - children: [], - }, - ], - }, - { - abbr: 'MVD', - text: 'Mississippi Valley Division', - children: [ - { - abbr: 'MVM', - text: 'Memphis District', - children: [], - }, - { - abbr: 'MVN', - text: 'New Orleans District', - children: [], - }, - { - abbr: 'MVR', - text: 'Rock Island District', - children: [], - }, - { - abbr: 'MVS', - text: 'St. Louis District', - children: [], - }, - { - abbr: 'MVP', - text: 'St. Paul District', - children: [], - }, - { - abbr: 'MVK', - text: 'Vicksburg District', - children: [], - }, - ], - }, - { - abbr: 'NAD', - text: 'North Atlantic Division', - children: [ - { - abbr: 'NAB', - text: 'Baltimore District', - children: [], - }, - { - abbr: 'NAU', - text: 'Europe District', - children: [], - }, - { - abbr: 'NAE', - text: 'New England District', - children: [], - }, - { - abbr: 'NAN', - text: 'New York District', - children: [], - }, - { - abbr: 'NAO', - text: 'Norfolk District', - children: [], - }, - { - abbr: 'NAP', - text: 'Philadelphia District', - children: [], - }, - ], - }, - { - abbr: 'NWD', - text: 'Northwestern Division', - children: [ - { - abbr: 'NWK', - text: 'Kansas City District', - children: [], - }, - { - abbr: 'NWO', - text: 'Omaha District', - children: [], - }, - { - abbr: 'NWP', - text: 'Portland District', - children: [], - }, - { - abbr: 'NWS', - text: 'Seattle District', - children: [], - }, - { - abbr: 'NWW', - text: 'Walla Walla District', - children: [], - }, - ], - }, - { - abbr: 'POD', - text: 'Pacific Ocean Division', - children: [ - { - abbr: 'POA', - text: 'Alaska District', - children: [], - }, - { - abbr: 'POF', - text: 'Far East District', - children: [], - }, - { - abbr: 'POH', - text: 'Honolulu District', - children: [], - }, - { - abbr: 'POJ', - text: 'Japan Engineer District', - children: [], - }, - ], - }, - { - abbr: 'SAD', - text: 'South Atlantic Division', - children: [ - { - abbr: 'SAC', - text: 'Charleston District', - children: [], - }, - { - abbr: 'SAJ', - text: 'Jacksonville District', - children: [], - }, - { - abbr: 'SAM', - text: 'Mobile District', - children: [], - }, - { - abbr: 'SAS', - text: 'Savannah District', - children: [], - }, - { - abbr: 'SAW', - text: 'Wilmington District', - children: [], - }, - ], - }, - { - abbr: 'SPD', - text: 'South Pacific Division', - children: [ - { - abbr: 'SPA', - text: 'Albuquerque District', - children: [], - }, - { - abbr: 'SPL', - text: 'Los Angeles District', - children: [], - }, - { - abbr: 'SPK', - text: 'Sacramento District', - children: [], - }, - { - abbr: 'SPN', - text: 'San Francisco District', - children: [], - }, - ], - }, - { - abbr: 'SWD', - text: 'Southwestern Division', - children: [ - { - abbr: 'SWF', - text: 'Fort Worth District', - children: [], - }, - { - abbr: 'SWG', - text: 'Galveston District', - children: [], - }, - { - abbr: 'SWL', - text: 'Little Rock District', - children: [], - }, - { - abbr: 'SWT', - text: 'Tulsa District', - children: [], - }, - ], - }, - { - abbr: 'TAD', - text: 'Transatlantic Division', - children: [ - { - abbr: 'TAM', - text: 'Middle East District', - children: [], - }, - ], - }, -]; \ No newline at end of file diff --git a/src/app-pages/home/project-list.jsx b/src/app-pages/home/project-list.jsx index 470aa130..87c02b85 100644 --- a/src/app-pages/home/project-list.jsx +++ b/src/app-pages/home/project-list.jsx @@ -1,93 +1,129 @@ -import React, { useState, useRef } from 'react'; -import { connect } from 'redux-bundler-react'; +import React, { useState, useMemo, useEffect } from 'react'; import Select from 'react-select'; +import { connect } from 'redux-bundler-react'; +import { createColumnHelper } from '@tanstack/react-table'; +import { IconButton } from '@mui/material'; import { Filter, KeyboardArrowUp, KeyboardArrowDown, - StarOutline, Toc, } from '@mui/icons-material'; import Button from '../../app-components/button'; import ProjectCard from './project-card'; -import Table from '../../app-components/table'; -import { filters } from './homeFilters'; - -const FilterItemList = ({ items, filter, setFilter, active }) => ( -
    - {items.map((item, i) => ( - - ))} -
-); +import Table from '../../app-components/table/table'; -const FilterItem = ({ item, filter, setFilter, active }) => { - const el = useRef(null); - const [expanded, setExpanded] = useState(false); - const isActive = active || item.abbr === filter; - - return ( -
  • { - if (e.currentTarget === el.current) { - e.stopPropagation(); - e.preventDefault(); - setFilter(item.abbr); - } - }} - onDoubleClick={(e) => { - if (e.currentTarget === el.current) { - e.stopPropagation(); - e.preventDefault(); - setExpanded(!expanded); - } - }} - className={`list-group-item list-group-item-action${isActive ? ' active' : ''} pointer`} - > -
    - {item.children && !!item.children.length && ( - setExpanded(!expanded)} className='pr-2'> - {expanded ? : } - - )}{' '} - {item.abbr} - {item.abbr !== item.text && ( - {item.text} - )} -
    - {item.children && expanded && ( - - )} -
  • - ); +const sortByDistrictName = (rowA, rowB, districts) => { + const { original: origA } = rowA; + const { original: origB } = rowB; + const { districtId: districtIdA } = origA; + const { districtId: districtIdB } = origB; + + const foundA = districts.find(el => el.district_id === districtIdA)?.name; + const foundB = districts.find(el => el.district_id === districtIdB)?.name; + + if (!foundA) return -1; + else if (!foundB) return 1; + else return foundB.localeCompare(foundA); +}; + +const mapDistrictName = (districtId, length, districts = []) => { + if (!districts?.length || !length) return ''; + + const found = districts.find(el => el.id === districtId); + + const str = (found && found?.name) ? found.name : 'No Associated District'; + + return `${str}${length ? ` (${length})` : '' }`; +}; + +const groupProjects = (projects = []) => { + if (!projects?.length) return []; + + const grouped = projects.reduce((accum, current) => { + const { districtId } = current; + + const index = accum.findIndex(el => el.districtId === districtId); + + if (index >= 0) { + accum[index] = { + ...accum[index], + projects: [...accum[index].projects, current], + } + } else { + accum.push({ + districtId, + projects: [current], + }) + } + + return accum; + }, []); + + return grouped; }; export default connect( 'doUpdateRelativeUrl', + 'doFetchDistricts', + 'selectDistricts', 'selectProjectsItemsWithLinks', - ({ doUpdateRelativeUrl, projectsItemsWithLinks: projects }) => { - const [filter, setFilter] = useState('All'); - const [filteredProjects, setFilteredProjects] = useState(projects); + ({ + doUpdateRelativeUrl, + doFetchDistricts, + districts, + projectsItemsWithLinks: projects, + }) => { + const [groupedProjects, setGroupedProjects] = useState(groupProjects(projects)); const [isTableMode, setIsTableMode] = useState(true); const [inputString, setInputString] = useState(''); + const columnHelper = createColumnHelper(); - const onInputChange = input => { - const filtered = projects.filter(p => (p.title).toLowerCase().includes(input.toLowerCase())); - setFilteredProjects(filtered); - }; + const columns = useMemo( + () => [ + columnHelper.accessor('districtId', { + header: 'District', + id: 'districtId', + enableColumnFilter: false, + sortingFn: (a, b) => sortByDistrictName(a, b, districts), + cell: (info) => ( + <> + {info.row.getCanExpand() && ( + + {info.row.getIsExpanded() ? ( + + ) : ( + + )} + + )} + {mapDistrictName(info.getValue(), info.row.subRows.length, districts)} + + ), + }), + columnHelper.accessor('title', { + header: 'Project Name', + id: 'title', + enableColumnFilter: false, + cell: (info) => ( + {info.getValue()} + ), + }), + columnHelper.accessor('instrumentCount', { + header: 'Instrument Count', + id: 'instrumentCount', + enableColumnFilter: false, + enableSorting: false, + }), + columnHelper.accessor('instrumentGroupCount', { + header: 'Instrument Group Count', + id: 'instrumentGroupCount', + enableColumnFilter: false, + enableSorting: false, + }), + ], + [districts]); const onChange = selected => { const { value } = selected; @@ -98,25 +134,22 @@ export default connect( }; const filterList = projects - ? projects.filter(p => p.instrumentCount).map(p => ({ value: p.title, label: p.title })) + ? projects.filter(p => p.href).map(p => ({ value: p.title, label: p.title })) : {}; - const isProdReady = import.meta.env.VITE_DISTRICT_SELECTOR === 'true'; + useEffect(() => { + setGroupedProjects(groupProjects(projects, districts)); + }, [projects, districts, setGroupedProjects, groupProjects]); + + useEffect(() => { + doFetchDistricts(); + }, [doFetchDistricts]); return (
    - {isProdReady && ( -
    - -
    - )} -
    - { projects.length ? ( +
    + {projects.length ? ( <>
    @@ -144,7 +177,6 @@ export default connect( onInputChange={(value, action) => { if (action.action === 'input-change') { setInputString(value); - onInputChange(value); } }} onChange={onChange} @@ -154,47 +186,24 @@ export default connect( <> {isTableMode ? ( ( - <> - - {data?.title} - -  - {data?.subtitle} - - ), - }, { - key: 'instrumentCount', - header: 'Instrument Count', - isSortable: true, - }, { - key: 'instrumentGroupCount', - header: 'Instrument Group Count', - isSortable: true, - }, { - key: 'tools', - header: 'Tools', - render: (_data) => ( -