diff --git a/package-lock.json b/package-lock.json index 9a143029..a68b0eeb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "hhd-ui", - "version": "0.13.2", + "version": "0.13.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "hhd-ui", - "version": "0.13.2", + "version": "0.13.3", "dependencies": { "@ag-grid-community/client-side-row-model": "^30.0.3", "@ag-grid-community/core": "^30.0.3", diff --git a/src/app-bundles/chart-editor-bundle.js b/src/app-bundles/chart-editor-bundle.js index 946bd459..29266108 100644 --- a/src/app-bundles/chart-editor-bundle.js +++ b/src/app-bundles/chart-editor-bundle.js @@ -350,12 +350,14 @@ const chartEditorBundle = { } const domainName = getDomainName(domains, parameter_id); + const unitName = domains['unit'].find(el => el.id === unit_id)?.value; if (!chartData.find(x => x.name === parameter_id)) { chartData.push({ id: series.id, name: parameter_id, domainName, + unitName, unit: unit_id, data: plotData, }); @@ -364,6 +366,7 @@ const chartEditorBundle = { id: series.id, name: parameter_id, domainName, + unitName, unit: unit_id, data: plotData, }); @@ -377,6 +380,7 @@ const chartEditorBundle = { id: series.id, name: parameter_id, domainName, + unitName, unit: unit_id, data: [...item.data, ...plotData], }); diff --git a/src/app-bundles/data-logger-equivalency-bundle.js b/src/app-bundles/data-logger-equivalency-bundle.js index 93669f81..8d6c7677 100644 --- a/src/app-bundles/data-logger-equivalency-bundle.js +++ b/src/app-bundles/data-logger-equivalency-bundle.js @@ -1,3 +1,5 @@ +import { tUpdateError, tUpdateSuccess } from "../common/helpers/toast-helpers"; + export default { name: 'dataLoggerEquivalency', getReducer: () => { @@ -65,7 +67,7 @@ export default { }); }, - doUpdateDataLoggerEquivalency: (data) => ({ store, apiPut }) => { + doUpdateSingleDataLoggerEquivalency: (data) => ({ store, apiPut }) => { const { dataLoggerId, id, fieldName, displayName, instrumentId, timeseriesId } = data; const uri = `/datalogger/${dataLoggerId}/equivalency_table`; // const toastId = toast.loading('Updating Field Mapping...'); @@ -95,9 +97,26 @@ export default { }); }, - // doDeleteDataLoggerEquivalency: ({ dataLoggerId }) => ({ dispatch, store, apiDelete }) => { - // const uri = `/datalogger/${dataLoggerId}/equivalency_table`; - // }, + // For use in auto-assigning only. + doUpdateMultipleDataLoggerEquivalency: (dataLoggerId, rows, toastId) => ({ store, apiPut }) => { + const uri = `/datalogger/${dataLoggerId}/equivalency_table`; + + const payload = { + datalogger_id: dataLoggerId, + rows, + }; + + apiPut(uri, payload, (err, _body) => { + if (err) { + // eslint-disable-next-line no-console + console.log('test err: ', JSON.stringify(err)); + tUpdateError(toastId, 'Failed to assign timeseries to field names. Please try again later.'); + } else { + tUpdateSuccess(toastId, 'Successfully assigned timeseries to field names!'); + store.doFetchDataLoggerEquivalency({ dataLoggerId }); + } + }); + }, doDeleteDataLoggerEquivalencyRow: ({ dataLoggerId, id, refreshData = true }) => ({ store, apiDelete }) => { const uri = `/datalogger/${dataLoggerId}/equivalency_table/row?id=${id}`; diff --git a/src/app-bundles/index.js b/src/app-bundles/index.js index 420d5579..a953d183 100644 --- a/src/app-bundles/index.js +++ b/src/app-bundles/index.js @@ -40,6 +40,7 @@ import instrumentGroupInstrumentsBundle from './instrument-group-instruments-bun import instrumentGroupMapBundle from './instrument-group-map-bundle'; import instrumentMapBundle from './instrument-map-bundle'; import instrumentNotesBundle from './instrument-notes-bundle'; +import instrumentSensorsBundle from './instrument-sensors-bundle'; import instrumentStatusBundle from './instrument-status-bundle'; import mapsBundle from './maps-bundle'; import modalBundle from './modal-bundle'; @@ -138,6 +139,7 @@ export default composeBundles( instrumentGroupInstrumentsBundle, instrumentMapBundle, instrumentNotesBundle, + instrumentSensorsBundle, instrumentStatusBundle, mapsBundle, modalBundle, diff --git a/src/app-bundles/instrument-sensors-bundle.js b/src/app-bundles/instrument-sensors-bundle.js new file mode 100644 index 00000000..a321bf33 --- /dev/null +++ b/src/app-bundles/instrument-sensors-bundle.js @@ -0,0 +1,91 @@ +export default { + name: 'instrumentSensors', + getReducer: () => { + const initialState = { + sensors: [], + measurements: [], + _lastFetched: null, + }; + + return (state = initialState, { type, payload }) => { + switch (type) { + case 'INSTRUMENT_SENSORS_UPDATED': + return { + ...state, + sensors: payload, + _lastFetched: new Date(), + }; + case 'SENSOR_MEASUREMENTS_UPDATED': + return { + ...state, + measurements: payload, + }; + default: + return state; + } + }; + }, + + selectInstrumentSensorsRaw: (state) => state.instrumentSensors, + selectInstrumentSensors: (state) => state.instrumentSensors.sensors, + selectInstrumentSensorsMeasurements: (state) => state.instrumentSensors.measurements, + selectInstrumentSensorsLastFetched: (state) => state.instrumentSensors._lastFetched, + + doFetchInstrumentSensorsById: () => ({ dispatch, store, apiGet }) => { + dispatch({ type: 'INSTRUMENT_SENSORS_BY_ID_FETCH_START' }); + const { instrumentId } = store.selectInstrumentsIdByRoute(); + + const url = `/instruments/saa/${instrumentId}/segments`; + + apiGet(url, (err, body) => { + if (err) { + // eslint-disable-next-line no-console + console.log('error: ', err); + } else { + dispatch({ + type: 'INSTRUMENT_SENSORS_UPDATED', + payload: body, + }); + } + + dispatch({ type: 'INSTRUMENT_SENSORS_BY_ID_FETCH_FINISHED' }); + }); + }, + + doUpdateInstrumentSensor: (formData) => ({ dispatch, store, apiPut }) => { + dispatch({ type: 'INSTRUMENT_SENSOR_UPDATE_START' }); + + const { instrumentId } = store.selectInstrumentsIdByRoute(); + const url = `/instruments/saa/${instrumentId}/segments`; + + apiPut(url, formData, (err, _body) => { + if (err) { + // eslint-disable-next-line no-console + console.log('todo', err); + } else { + store.doFetchInstrumentSensorsById(); + } + }); + }, + + doFetchInstrumentSensorMeasurements: (before, after) => ({ dispatch, store, apiGet }) => { + dispatch({ type: 'SENSOR_MEASUREMENTS_FETCH_START' }); + const { instrumentId } = store.selectInstrumentsIdByRoute(); + const url = `/instruments/saa/${instrumentId}/measurements?before=${before}&after=${after}`; + + apiGet(url, (err, body) => { + if (err) { + // eslint-disable-next-line no-console + console.log('todo', err); + } else { + dispatch({ + type: 'SENSOR_MEASUREMENTS_UPDATED', + payload: body, + }); + } + }); + + dispatch({ type: 'SENSOR_MEASUREMENTS_FETCH_FINISHED' }); + + }, +}; diff --git a/src/app-bundles/time-series-bundle.js b/src/app-bundles/time-series-bundle.js index 9b7c5ccc..6b8105a3 100644 --- a/src/app-bundles/time-series-bundle.js +++ b/src/app-bundles/time-series-bundle.js @@ -1,6 +1,8 @@ -import createRestBundle from './create-rest-bundle'; import { createSelector } from 'redux-bundler'; +import createRestBundle from './create-rest-bundle'; +import { tLoading, tUpdateError, tUpdateManual } from '../common/helpers/toast-helpers'; + export default createRestBundle({ name: 'instrumentTimeseries', uid: 'id', @@ -17,6 +19,7 @@ export default createRestBundle({ 'INSTRUMENTGROUPS_FETCH_FINISHED', 'INSTRUMENTCONSTANTS_SAVE_FINISHED', 'INSTRUMENTTIMESERIES_SAVE_FINISHED', + 'CREATED_NEW_TIMESERIES_FOR_FIELD_NAMES', ], urlParamSelectors: ['selectProjectsIdByRoute'], reduceFurther: (state, { type, payload }) => { @@ -27,6 +30,54 @@ export default createRestBundle({ } }, addons: { + doSaveFieldNamesToTimeseries: (newObject, instrumentId, dataLoggerId) => ({ dispatch, store, apiPost }) => { + dispatch({ type: 'ASSIGN_FIELD_NAMES_TO_TIMESERIES_START' }); + const toastId = tLoading('Creating new timeseries...'); + const { projectId } = store.selectProjectsIdByRoute(); + const { newTs, existingTs } = newObject; + + const uri = '/timeseries'; + const formData = []; + + newTs.forEach(item => formData.push({ + project_id: projectId, + instrument_id: instrumentId, + name: item?.field_name, + })); + + apiPost(uri, formData, (err, body) => { + if (err) { + tUpdateError(toastId, 'Failed to create new timeseries. Please try again later.'); + // eslint-disable-next-line no-console + console.error(err); + } else { + dispatch({ type: 'CREATED_NEW_TIMESERIES_FOR_FIELD_NAMES' }); + tUpdateManual(toastId, 'Assigning timeseries to field names...', { isLoading: true }); + const rows = []; + + rows.push(...newTs.map(el => ({ + ...el, + instrument_id: instrumentId, + timeseries_id: body.find(item => item.name === el.field_name)?.id, + }))); + + rows.push(...existingTs.map(el => { + const { field, timeseries } = el; + + return { + ...field, + instrument_id: instrumentId, + timeseries_id: timeseries?.id, + }; + })); + + store.doUpdateMultipleDataLoggerEquivalency(dataLoggerId, rows, toastId); + } + }); + + dispatch({ type: 'ASSIGN_FIELD_NAMES_TO_TIMESERIES_FINISHED' }); + }, + doInstrumentTimeseriesSetActiveId: (id) => ({ dispatch }) => { dispatch({ type: 'INSTRUMENTTIMESERIES_SET_ACTIVE_ID', diff --git a/src/app-bundles/time-series-measurements-bundle.js b/src/app-bundles/time-series-measurements-bundle.js index d3b16424..cd1fb158 100644 --- a/src/app-bundles/time-series-measurements-bundle.js +++ b/src/app-bundles/time-series-measurements-bundle.js @@ -1,7 +1,5 @@ -import { toast } from 'react-toastify'; - import createRestBundle from './create-rest-bundle'; -import { tUpdateError, tUpdateSuccess } from '../common/helpers/toast-helpers'; +import { tLoading, tUpdateError, tUpdateSuccess } from '../common/helpers/toast-helpers'; const afterDate = '1900-01-01T00:00:00.00Z'; const beforeDate = '2025-12-31T00:00:00.00Z'; @@ -83,7 +81,7 @@ export default createRestBundle({ const url = `/projects/${projectId}/timeseries_measurements`; - const toastId = toast.loading('Uploading measurements...'); + const toastId = tLoading('Uploading measurements. This may take a while for large data files...'); apiPost(url, measurements, (err, _body) => { if (err) { diff --git a/src/app-components/chart/viz-timeseries.jsx b/src/app-components/chart/viz-timeseries.jsx index eabcc818..5eee6c85 100644 --- a/src/app-components/chart/viz-timeseries.jsx +++ b/src/app-components/chart/viz-timeseries.jsx @@ -58,7 +58,7 @@ export default connect( data.length ? ( {data.map(x => ( - +
{x.data[0].isInclinometer ? diff --git a/src/app-components/domain-select.jsx b/src/app-components/domain-select.jsx index 9283fdcf..0f6b9b42 100644 --- a/src/app-components/domain-select.jsx +++ b/src/app-components/domain-select.jsx @@ -1,33 +1,38 @@ -import React from 'react'; +import React, { useEffect, useState } from 'react'; import { connect } from 'redux-bundler-react'; -import Select from 'react-select'; +import { Autocomplete, TextField } from '@mui/material'; export default connect( 'selectDomainsItemsByGroup', - ({ value, onChange, domain, domainsItemsByGroup }) => { + ({ + domainsItemsByGroup, + defaultValue, + onChange, + domain, + }) => { + const [selectValue, setSelectValue] = useState(); const options = domainsItemsByGroup[domain]?.map(item => ( { value: item.id, label: item.value } - )); + )) || []; - const initValue = domainsItemsByGroup[domain]?.find(el => el.id === value); + useEffect(() => { + const item = domainsItemsByGroup[domain]?.find(el => el.value === selectValue); + onChange(item); + }, [selectValue]); return ( <> {!options || !options.length ? ( No Options... ) : ( - dispatch({ type: 'update', key: 'x_timeseries_id', data: item.value })} + options={tsOpts} + /> +
+ +
+
+ +
+
+ dispatch({ type: 'update', key: 'z_timeseries_id', data: item.value })} + options={tsOpts} + /> +
+
+
+
+ +
+
+ setName(e.target.value)} - className='form-control' - type='text' - placeholder='Text input' + el.value === name)} + renderInput={(params) => ( + setName(e.target.value)} /> + )} + onChange={e => setName(e.target.innerText)} + // @TODO: check value from this. />
setParameterId(val)} + defaultValue={parameter_id} + onChange={val => setParameterId(val?.id)} domain='parameter' />
setUnitId(val)} + defaultValue={unit_id} + onChange={val => setUnitId(val?.id)} domain='unit' />
diff --git a/src/app-pages/instrument/timeseries/timeseries-list-item.jsx b/src/app-pages/instrument/timeseries/timeseries-list-item.jsx index ff7b6334..2a98023f 100644 --- a/src/app-pages/instrument/timeseries/timeseries-list-item.jsx +++ b/src/app-pages/instrument/timeseries/timeseries-list-item.jsx @@ -28,7 +28,7 @@ export default connect(
+ + ({ marker: { color }, }); -const generateDataTraces = (data = []) => { +const generateDataTraces = (data = [], currentProjectId) => { if (!data.length) return []; return data.reduce((accum, current, index) => { @@ -28,9 +28,11 @@ const generateDataTraces = (data = []) => { month, red_submittals, yellow_submittals, - // project_id, + project_id, } = current; + if (currentProjectId && currentProjectId !== project_id) return { ...accum }; + const monthDT = DateTime.fromISO(month, { setZone: 'UTC' }); const displayString = (index) => { @@ -81,18 +83,21 @@ const generateDataTraces = (data = []) => { const DistrictRollupModal = connect( 'doFetchDistrictRollup', 'selectDistrictRollupRaw', + 'selectProjectsIdByRoute', ({ doFetchDistrictRollup, - districtRollupRaw + districtRollupRaw, + projectsIdByRoute, }) => { const { evaluation, measurement } = districtRollupRaw; + const { projectId } = projectsIdByRoute; const [showCurrentProjectOnly, setShowCurrentProjectOnly] = useState(false); const [dateRange, setDateRange] = useState([subDays(new Date(), 365), new Date()]); const [fromTime, endTime] = dateRange; - const evaluationTraces = generateDataTraces(evaluation); - const measurementTraces = generateDataTraces(measurement); + const evaluationTraces = generateDataTraces(evaluation, showCurrentProjectOnly ? projectId : false); + const measurementTraces = generateDataTraces(measurement, showCurrentProjectOnly? projectId : false); useEffect(() => { doFetchDistrictRollup('evaluation'); diff --git a/src/app-pages/project/qa-qc/components/modals/newAlertConfigModal.jsx b/src/app-pages/project/qa-qc/components/modals/newAlertConfigModal.jsx index dcf86a81..d2c89f57 100644 --- a/src/app-pages/project/qa-qc/components/modals/newAlertConfigModal.jsx +++ b/src/app-pages/project/qa-qc/components/modals/newAlertConfigModal.jsx @@ -180,8 +180,8 @@ const NewAlertConfigModal = connect(
dispatch({ type: 'update', key: 'alert_type_id', data: val })} + defaultValue={formState?.alert_type_id?.val} + onChange={val => dispatch({ type: 'update', key: 'alert_type_id', data: val?.id })} domain='alert_type' />
diff --git a/src/app-pages/project/qa-qc/components/modals/newEvaluationModal.jsx b/src/app-pages/project/qa-qc/components/modals/newEvaluationModal.jsx index 3d0de53a..87d3f6d1 100644 --- a/src/app-pages/project/qa-qc/components/modals/newEvaluationModal.jsx +++ b/src/app-pages/project/qa-qc/components/modals/newEvaluationModal.jsx @@ -39,7 +39,7 @@ const defaultFormState = { instruments: [], start_date: '', end_date: '', - alert_config_id: '', + alert_config_id: null, submittal_id: '', }; diff --git a/src/common/forms/instrument-form.jsx b/src/common/forms/instrument-form.jsx index 2971cce6..a056e642 100644 --- a/src/common/forms/instrument-form.jsx +++ b/src/common/forms/instrument-form.jsx @@ -27,6 +27,7 @@ export default connect( 'selectProjDisplayProjection', 'selectProjOptions', 'selectProjectsByRoute', + 'selectDomainsItemsByGroup', ({ doModalClose, doInstrumentsSave, @@ -45,18 +46,20 @@ export default connect( projDisplayProjection, projOptions, projectsByRoute: project, + domainsItemsByGroup, item = {}, isEdit = true, }) => { const [name, setName] = useState(item?.name || ''); - const [type_id, setTypeId] = useState(item.type_id || ''); - const [station, setStation] = useState(item.station || ''); - const [offset, setOffset] = useState(item.offset || ''); + const [type_id, setTypeId] = useState(item?.type_id || ''); + const [station, setStation] = useState(item?.station || ''); + const [offset, setOffset] = useState(item?.offset || ''); // @TODO - utilize this for setting offset // const [offsetDescriptor, setOffsetDescriptor] = useState(item?.offsetDescriptor || ''); const [project_id] = useState(item?.project_id || project.id); const [status_id, setStatusId] = useState(item?.status_id || ''); const [status_time, setStatusTime] = useState(new Date()); + const [opts, setOpts] = useState(item?.opts || {}); const projected = instrumentDrawLon && instrumentDrawLat @@ -104,6 +107,8 @@ export default connect( // look to see if status should be updated const statusHasChanged = status_id !== item.status_id; + const findDomainItem = (group, name) => domainsItemsByGroup[group]?.find(item => item.value === name); + const handleSave = (e) => { e.preventDefault(); if (x && y) { @@ -129,6 +134,7 @@ export default connect( type_id, status_id, status_time, + opts, station: station === null || station === '' ? null @@ -210,11 +216,45 @@ export default connect(
- setTypeId(val)} domain='instrument_type' /> + setTypeId(val?.id)} domain='instrument_type' />
+ {findDomainItem('instrument_type', 'SAA')?.id === type_id && ( +
+
+ + setOpts(prev => ({ ...prev, bottom_elevation: Number(e.target.value) }))} + className='form-control' + type='number' + placeholder='Bottom Elevation' + /> +
+
+ + setOpts(prev => ({ ...prev, num_segments: Number(e.target.value) }))} + className='form-control' + type='number' + placeholder='Number of Segments' + /> +
+ {/*
+ + setOpts(prev => ({ ...prev, segment_length: e.target.value }))} + className='form-control' + type='number' + placeholder='Segment Length' + /> +
*/} +
+ )}
- setStatusId(val)} domain='status' /> + setStatusId(val?.id)} domain='status' />
{statusHasChanged ? (
@@ -255,7 +295,7 @@ export default connect(
- setStatusId(val)} domain='offset_descriptor' /> + setStatusId(val?.id)} domain='offset_descriptor' />
diff --git a/src/common/helpers/form-helpers.js b/src/common/helpers/form-helpers.js index 2f85044f..51f63251 100644 --- a/src/common/helpers/form-helpers.js +++ b/src/common/helpers/form-helpers.js @@ -38,6 +38,9 @@ export const reduceState = (state, action) => { isDirty: true, } }; + case 'init': return { + ...data, + }; default: { throw new Error(); } diff --git a/src/common/helpers/toast-helpers.js b/src/common/helpers/toast-helpers.js index a3bf9a35..3be22e39 100644 --- a/src/common/helpers/toast-helpers.js +++ b/src/common/helpers/toast-helpers.js @@ -2,7 +2,17 @@ import { toast } from 'react-toastify'; export const tLoading = (message, opts = {}) => toast.loading(message, { closeOnClick: true, ...opts }); -export const tUpdateSuccess = (id, message) => { +export const tUpdateManual = (id, message, opts) => { + toast.update(id, { + render: message, + autoClose: 2500, + closeOnClick: true, + draggable: true, + ...opts, + }); +} + +export const tUpdateSuccess = (id, message, opts) => { toast.update(id, { render: message, type: 'success', @@ -10,10 +20,11 @@ export const tUpdateSuccess = (id, message) => { autoClose: 2500, closeOnClick: true, draggable: true, + ...opts, }); }; -export const tUpdateError = (id, message) => { +export const tUpdateError = (id, message, opts) => { toast.update(id, { render: message, type: 'error', @@ -21,10 +32,11 @@ export const tUpdateError = (id, message) => { autoClose: 7500, closeOnClick: true, draggable: true, + ...opts, }); }; -export const tUpdateWarning = (id, message) => { +export const tUpdateWarning = (id, message, opts) => { toast.update(id, { render: message, type: 'warning', @@ -32,5 +44,6 @@ export const tUpdateWarning = (id, message) => { autoClose: 4000, closeOnClick: true, draggable: true, + ...opts, }); };