diff --git a/SEBrowser/Controllers/CapBankReport/CapBankReportController.cs b/SEBrowser/Controllers/CapBankReport/CapBankReportController.cs index bd6e2bef1..00106e8b9 100644 --- a/SEBrowser/Controllers/CapBankReport/CapBankReportController.cs +++ b/SEBrowser/Controllers/CapBankReport/CapBankReportController.cs @@ -134,8 +134,6 @@ public DataTable GetSubstationData() { using (AdoDataConnection connection = new("systemSettings")) { - - DataTable table = new(); using (IDbCommand sc = connection.Connection.CreateCommand()) @@ -205,13 +203,12 @@ public DataTable GetEventTable() { Dictionary query = Request.QueryParameters(); int capBankId = int.Parse(query["capBankId"]); - DateTime dateTime = DateTime.ParseExact(query["date"] + " " + query["time"], "MM/dd/yyyy HH:mm:ss.fff", new CultureInfo("en-US")); - string timeWindowUnits = ((TimeWindowUnits)int.Parse(query["timeWindowUnits"])).GetDescription(); - int windowSize = int.Parse(query["windowSize"]); + DateTime start = DateTime.ParseExact(query["start"], "MM/dd/yyyy HH:mm:ss.fff", new CultureInfo("en-US")); + DateTime end = DateTime.ParseExact(query["end"], "MM/dd/yyyy HH:mm:ss.fff", new CultureInfo("en-US")); int selectedBank = int.Parse(query["bankNum"]); - string timeRestriction = $"(CBAnalyticResult.Time BETWEEN DATEADD({ timeWindowUnits}, { (-1 * windowSize)}, '{dateTime}') AND DATEADD({ timeWindowUnits}, { (windowSize)}, '{dateTime}'))"; + string timeRestriction = $"(CBAnalyticResult.Time BETWEEN '{start}' AND '{end}')"; string capBankRestriction = $"((SELECT AssetID FROM EVENT WHERE Event.ID = CBAnalyticResult.EventID) = {capBankId})"; string bankNumRestriction = $"(CBAnalyticResult.EnergizedBanks = {selectedBank} OR CBAnalyticResult.DeEnergizedBanks = {selectedBank})"; string otherFilter = ProcessFilter(query); @@ -293,13 +290,12 @@ public TrendingResponse GetTrendData() { Dictionary query = Request.QueryParameters(); int capBankId = int.Parse(query["capBankId"]); - DateTime dateTime = DateTime.ParseExact(query["date"] + " " + query["time"], "MM/dd/yyyy HH:mm:ss.fff", new CultureInfo("en-US")); - string timeWindowUnits = ((TimeWindowUnits)int.Parse(query["timeWindowUnits"])).GetDescription(); - int windowSize = int.Parse(query["windowSize"]); + DateTime start = DateTime.ParseExact(query["start"], "MM/dd/yyyy HH:mm:ss.fff", new CultureInfo("en-US")); + DateTime end = DateTime.ParseExact(query["end"], "MM/dd/yyyy HH:mm:ss.fff", new CultureInfo("en-US")); int selectedBank = int.Parse(query["bankNum"]); - string timeRestriction = $"(CBAnalyticResult.Time BETWEEN DATEADD({ timeWindowUnits}, { (-1 * windowSize)}, '{dateTime}') AND DATEADD({ timeWindowUnits}, { (windowSize)}, '{dateTime}'))"; + string timeRestriction = $"(CBAnalyticResult.Time BETWEEN '{start}' AND '{end}')"; string capBankRestriction = $"((SELECT AssetID FROM EVENT WHERE Event.ID = CBAnalyticResult.EventID) = {capBankId})"; string bankNumRestriction = $"(CBAnalyticResult.EnergizedBanks = {selectedBank} OR CBAnalyticResult.DeEnergizedBanks = {selectedBank})"; string bankNumAfterRestriction = $"(CBAnalyticResult.StepPost = {selectedBank})"; diff --git a/SEBrowser/Controllers/DERReport/DERReportController.cs b/SEBrowser/Controllers/DERReport/DERReportController.cs index cb09b7eaa..5ce4ff132 100644 --- a/SEBrowser/Controllers/DERReport/DERReportController.cs +++ b/SEBrowser/Controllers/DERReport/DERReportController.cs @@ -235,9 +235,8 @@ public IHttpActionResult GetRegulations() public class DERReportPostRequest { public List DERIDs { get; set; } - public DateTime Time { get; set; } - public int Window { get; set; } - public TimeWindowUnits TimeWindowUnit { get; set; } + public DateTime StartTime { get; set; } + public DateTime EndTime { get; set; } public List Regulations { get; set; } } @@ -264,7 +263,7 @@ DERAnalyticResult JOIN ChannelDetail ON DERAnalyticResult.ChannelID = ChannelDetail.ID WHERE Asset.ID IN ({(content.DERIDs.Any() ? string.Join(",", content.DERIDs) : "-1")}) AND - (DERAnalyticResult.Time BETWEEN DATEADD({ content.TimeWindowUnit}, { (-1 * content.Window)}, '{content.Time}') AND DATEADD({ content.TimeWindowUnit}, { (content.Window)}, '{content.Time}')) AND + (DERAnalyticResult.Time BETWEEN DATEADD('{content.StartTime}') AND DATEADD('{content.EndTime}')) AND DERAnalyticResult.Regulation IN ({(content.Regulations.Any() ? string.Join(",", content.Regulations.Select(s => "'" +s + "'")) : "-1")}) ORDER BY DERAnalyticResult.Time"; diff --git a/SEBrowser/Controllers/OpenXDAController.cs b/SEBrowser/Controllers/OpenXDAController.cs index 7abe57cf3..fa13658a6 100644 --- a/SEBrowser/Controllers/OpenXDAController.cs +++ b/SEBrowser/Controllers/OpenXDAController.cs @@ -31,6 +31,7 @@ using System; using System.Collections.Generic; using System.Data; +using System.Data.SqlClient; using System.Globalization; using System.IO; using System.Linq; @@ -116,10 +117,8 @@ static OpenXDAController() public class EventSearchPostData { - public string date { get; set; } - public string time { get; set; } - public double windowSize { get; set; } - public int timeWindowUnits { get; set; } + public string start { get; set; } + public string end { get; set; } public double durationMin { get; set; } public double durationMax { get; set; } public Phase phases { get; set; } @@ -176,8 +175,9 @@ public DataTable GetEventSearchData(EventSearchPostData postData) { using (AdoDataConnection connection = new(SettingsCategory)) { - DateTime dateTime = DateTime.ParseExact(postData.date + " " + postData.time, "MM/dd/yyyy HH:mm:ss.fff", new CultureInfo("en-US")); - + DateTime startTime = DateTime.ParseExact(postData.start, "MM/dd/yyyy HH:mm:ss.fff", new CultureInfo("en-US")); + DateTime endTime = DateTime.ParseExact(postData.end, "MM/dd/yyyy HH:mm:ss.fff", new CultureInfo("en-US")); + string eventType = (postData.typeIDs is null) ? null : getEventTypeFilter(postData); string phase = (postData.phases is null) ? null : getPhaseFilter(postData); string eventCharacteristic = getEventCharacteristicFilter(postData); @@ -195,52 +195,52 @@ public DataTable GetEventSearchData(EventSearchPostData postData) string query = $@"SELECT TOP {postData.numberResults ?? "100"} - {Collumns} - FROM - ( - SELECT - Event.ID EventID, - EventWorstDisturbance.WorstDisturbanceID DisturbanceID, - FaultSummary.FaultNumber FaultID - FROM - Event JOIN - EventType ON Event.EventTypeID = EventType.ID LEFT OUTER JOIN - EventWorstDisturbance ON - EventWorstDisturbance.EventID = Event.ID AND - EventType.Name IN ('Sag', 'Swell', 'Interruption', 'Transient') LEFT OUTER JOIN - FaultGroup ON - FaultGroup.EventID = Event.ID AND - COALESCE(FaultGroup.FaultDetectionLogicResult, 0) <> 0 LEFT OUTER JOIN - FaultSummary ON - FaultSummary.EventID = Event.ID AND - FaultSummary.IsSelectedAlgorithm <> 0 AND - ( - FaultGroup.ID IS NOT NULL OR - ( - FaultSummary.IsValid <> 0 AND - FaultSummary.IsSuppressed = 0 - ) - ) AND - EventType.Name IN ('Fault', 'RecloseIntoFault') - WHERE - ({getTimeFilter(postData)}) AND + {Collumns} + FROM + ( + SELECT + Event.ID EventID, + EventWorstDisturbance.WorstDisturbanceID DisturbanceID, + FaultSummary.FaultNumber FaultID + FROM + Event JOIN + EventType ON Event.EventTypeID = EventType.ID LEFT OUTER JOIN + EventWorstDisturbance ON + EventWorstDisturbance.EventID = Event.ID AND + EventType.Name IN ('Sag', 'Swell', 'Interruption', 'Transient') LEFT OUTER JOIN + FaultGroup ON + FaultGroup.EventID = Event.ID AND + COALESCE(FaultGroup.FaultDetectionLogicResult, 0) <> 0 LEFT OUTER JOIN + FaultSummary ON + FaultSummary.EventID = Event.ID AND + FaultSummary.IsSelectedAlgorithm <> 0 AND ( - EventWorstDisturbance.ID IS NOT NULL OR - FaultSummary.ID IS NOT NULL OR - EventType.Name IN ('BreakerOpen', 'Other') - ) - {filters} - ) Main LEFT JOIN - [SEBrowser.EventSearchEventView] ON Main.EventID = [SEBrowser.EventSearchEventView].EventID Inner JOIN - [SEBrowser.EventSearchDetailsView] ON - Main.EventID = [SEBrowser.EventSearchDetailsView].EventID AND + FaultGroup.ID IS NOT NULL OR + ( + FaultSummary.IsValid <> 0 AND + FaultSummary.IsSuppressed = 0 + ) + ) AND + EventType.Name IN ('Fault', 'RecloseIntoFault') + WHERE + ({getTimeFilter(postData)}) AND ( - (Main.DisturbanceID IS NOT NULL AND [SEBrowser.EventSearchDetailsView].DisturbanceID = Main.DisturbanceID) OR - (Main.FaultID IS NOT NULL AND [SEBrowser.EventSearchDetailsView].FaultID = Main.FaultID) OR - (COALESCE([SEBrowser.EventSearchDetailsView].DisturbanceID, Main.DisturbanceID) IS NULL AND COALESCE([SEBrowser.EventSearchDetailsView].FaultID, Main.FaultID) IS NULL) - ) {sortBy}"; + EventWorstDisturbance.ID IS NOT NULL OR + FaultSummary.ID IS NOT NULL OR + EventType.Name IN ('BreakerOpen', 'Other') + ) + {filters} + ) Main LEFT JOIN + [SEBrowser.EventSearchEventView] ON Main.EventID = [SEBrowser.EventSearchEventView].EventID Inner JOIN + [SEBrowser.EventSearchDetailsView] ON + Main.EventID = [SEBrowser.EventSearchDetailsView].EventID AND + ( + (Main.DisturbanceID IS NOT NULL AND [SEBrowser.EventSearchDetailsView].DisturbanceID = Main.DisturbanceID) OR + (Main.FaultID IS NOT NULL AND [SEBrowser.EventSearchDetailsView].FaultID = Main.FaultID) OR + (COALESCE([SEBrowser.EventSearchDetailsView].DisturbanceID, Main.DisturbanceID) IS NULL AND COALESCE([SEBrowser.EventSearchDetailsView].FaultID, Main.FaultID) IS NULL) + ) {sortBy}"; - DataTable table = connection.RetrieveData(query, dateTime); + DataTable table = connection.RetrieveData(query, startTime, endTime); return table; } @@ -249,9 +249,7 @@ [SEBrowser.EventSearchDetailsView] ON private string getTimeFilter(EventSearchPostData postData) { - string timeWindowUnits = ((TimeWindowUnits)postData.timeWindowUnits).GetDescription(); - - return $"Event.StartTime BETWEEN DATEADD({timeWindowUnits},{-1 * postData.windowSize}, {{0}}) AND DATEADD({timeWindowUnits},{postData.windowSize}, {{0}})"; + return $"Event.StartTime BETWEEN '{postData.start}' AND '{postData.end}'"; } private string getEventTypeFilter(EventSearchPostData postData) diff --git a/SEBrowser/Controllers/RelayReport/RelayReportController.cs b/SEBrowser/Controllers/RelayReport/RelayReportController.cs index 71e9ad292..bd5a474f7 100644 --- a/SEBrowser/Controllers/RelayReport/RelayReportController.cs +++ b/SEBrowser/Controllers/RelayReport/RelayReportController.cs @@ -208,11 +208,11 @@ FROM Channel } - private DataTable RelayHistoryTable( DateTime dateTime, int windowSize, string timeWindowUnits, int relayID, int channelID = -1) + private DataTable RelayHistoryTable( DateTime start, DateTime end, int relayID, int channelID = -1) { DataTable dataTable; - string timeRestriction = $"TripInitiate Between DATEADD({timeWindowUnits}, { (-1 * windowSize)}, '{dateTime}') AND DATEADD({ timeWindowUnits}, { (windowSize)}, '{dateTime}')"; + string timeRestriction = $"TripInitiate Between DATEADD('{start}') AND DATEADD('{end}')"; using (AdoDataConnection connection = new("systemSettings")) { @@ -222,7 +222,8 @@ private DataTable RelayHistoryTable( DateTime dateTime, int windowSize, string t } else { - dataTable = connection.RetrieveData($"SELECT * FROM BreakerHistory WHERE BreakerId = {{0}} AND {timeRestriction}", relayID); + dataTable = connection.RetrieveData($"SELECT * FROM BreakerHistory WHERE BreakerId = {{0}}" + + $" AND {timeRestriction}", relayID); } } return dataTable; @@ -235,21 +236,18 @@ public DataTable GetRelayPerformance() int lineID; int channelID; - DateTime dateTime = DateTime.ParseExact(query["date"] + " " + query["time"], "MM/dd/yyyy HH:mm:ss.fff", new CultureInfo("en-US")); - string timeWindowUnits = ((TimeWindowUnits)int.Parse(query["timeWindowUnits"])).GetDescription(); - int windowSize = int.Parse(query["windowSize"]); - + DateTime start = DateTime.ParseExact(query["start"], "MM/dd/yyyy HH:mm:ss.fff", new CultureInfo("en-US")); + DateTime end = DateTime.ParseExact(query["end"], "MM/dd/yyyy HH:mm:ss.fff", new CultureInfo("en-US")); try { channelID = int.Parse(query["channelID"]); } catch { channelID = -1; } try { lineID = int.Parse(query["lineID"]); } catch { lineID = -1; } - + if (lineID <= 0) return new DataTable(); - - return RelayHistoryTable(dateTime,windowSize,timeWindowUnits,lineID, channelID); - + + return RelayHistoryTable(start, end, lineID, channelID); // change this to start end too } #endregion diff --git a/SEBrowser/EventWidgets b/SEBrowser/EventWidgets index 8de302b0e..d9f1a899a 160000 --- a/SEBrowser/EventWidgets +++ b/SEBrowser/EventWidgets @@ -1 +1 @@ -Subproject commit 8de302b0e4abd997f242fc894c472e90ea6a70c4 +Subproject commit d9f1a899af5c90ee07285ea03972eb7e031855bb diff --git a/SEBrowser/Scripts/TSX/Components/BreakerReport/BreakerReport.tsx b/SEBrowser/Scripts/TSX/Components/BreakerReport/BreakerReport.tsx index cc0d317e2..1254c6780 100644 --- a/SEBrowser/Scripts/TSX/Components/BreakerReport/BreakerReport.tsx +++ b/SEBrowser/Scripts/TSX/Components/BreakerReport/BreakerReport.tsx @@ -23,9 +23,10 @@ import * as React from 'react'; import BreakerReportNavbar from './BreakerReportNavbar'; import * as queryString from 'querystring'; -const momentDateFormat = "MM/DD/YYYY"; import moment from 'moment'; import { useLocation, useNavigate } from 'react-router-dom'; +import { useSelector } from 'react-redux'; +import { SelectDateTimeFormat } from '../SettingsSlice'; declare let homePath: string; @@ -36,17 +37,18 @@ interface State { } const BreakerReport = () => { - const [fromDate, setFromDate] = React.useState(''); - const [toDate, setToDate] = React.useState(''); - const [breaker, setBreaker] = React.useState(''); const navigate = useNavigate(); const history = useLocation(); + const dateTimeFormat = useSelector(SelectDateTimeFormat); + const [toDate, setToDate] = React.useState(''); + const [breaker, setBreaker] = React.useState(''); + const [fromDate, setFromDate] = React.useState(''); React.useEffect(() => { const query = queryString.parse(history.search.replace("?", ""), "&", "="); - setFromDate(query['fromDate'] != undefined ? query['fromDate'].toString() : moment().subtract(30, 'days').format(momentDateFormat)); - setToDate(query['toDate'] != undefined ? query['toDate'].toString() : moment().format(momentDateFormat)); + setFromDate(query['fromDate'] != undefined ? query['fromDate'].toString() : moment().subtract(30, 'days').format(dateTimeFormat)); + setToDate(query['toDate'] != undefined ? query['toDate'].toString() : moment().format(dateTimeFormat)); setBreaker(query['breaker'] != undefined ? query['breaker'].toString() : '0'); }, []); @@ -58,7 +60,7 @@ const BreakerReport = () => { const q = queryString.stringify(state, "&", "="); const handle = setTimeout(() => navigate(history.pathname + '?' + q), 500); return (() => { clearTimeout(handle); }) - }, [fromDate,toDate,breaker]) + }, [fromDate, toDate, breaker]) function setState(a: State) { setFromDate(a.fromDate); @@ -69,7 +71,7 @@ const BreakerReport = () => { const link = `${homePath}api/BreakerReport/${(breaker == '0' ? `AllBreakersReport?` : `IndividualBreakerReport?breakerId=${breaker}&`)}startDate=${fromDate}&endDate=${toDate}`; return (
- +
diff --git a/SEBrowser/Scripts/TSX/Components/BreakerReport/BreakerReportNavbar.tsx b/SEBrowser/Scripts/TSX/Components/BreakerReport/BreakerReportNavbar.tsx index 5cec6fd4e..b7ce3f515 100644 --- a/SEBrowser/Scripts/TSX/Components/BreakerReport/BreakerReportNavbar.tsx +++ b/SEBrowser/Scripts/TSX/Components/BreakerReport/BreakerReportNavbar.tsx @@ -24,21 +24,18 @@ import React from 'react'; import BreakerReportService from './../../../TS/Services/BreakerReport' - export interface BreakerReportNavbarProps { fromDate: string, toDate: string, breaker: string, - stateSetter(state): void + stateSetter(state): void, + dateTimeFormat: string } interface State { breakers: Array } -const momentDateFormat = "MM/DD/YYYY"; - - export default class BreakerReportNavbar extends React.Component { breakerReportService: BreakerReportService; constructor(props, context) { @@ -51,13 +48,13 @@ export default class BreakerReportNavbar extends React.Component this.props.stateSetter({ toDate: (e.target as any).value })); - $('#fromDatePicker').datetimepicker({ format: momentDateFormat }); + $('#fromDatePicker').datetimepicker({ format: this.props.dateTimeFormat }); $('#fromDatePicker').on('dp.change', (e) => this.props.stateSetter({ fromDate: (e.target as any).value })); this.breakerReportService.getMaximoBreakers().done(data => { - this.setState({ breakers: data.map((d,i) => )}); + this.setState({ breakers: data.map((d, i) => ) }); }); } diff --git a/SEBrowser/Scripts/TSX/Components/CapBankReport/CapBankReport.tsx b/SEBrowser/Scripts/TSX/Components/CapBankReport/CapBankReport.tsx index 293df9568..46341bfb4 100644 --- a/SEBrowser/Scripts/TSX/Components/CapBankReport/CapBankReport.tsx +++ b/SEBrowser/Scripts/TSX/Components/CapBankReport/CapBankReport.tsx @@ -26,10 +26,9 @@ import CapBankReportPane from './CapBankReportPane'; import * as queryString from 'querystring'; import moment from 'moment'; import { useLocation, useNavigate } from 'react-router-dom'; -import { SEBrowser } from '../../global'; - -const momentDateFormat = "MM/DD/YYYY"; -const momentTimeFormat = "HH:mm:ss.SSS"; +import { SEBrowser } from '../../global'; +import { useSelector } from 'react-redux'; +import { SelectTimeZone, SelectDateTimeFormat, SelectDateTimeSetting } from '../SettingsSlice'; interface IState { searchBarProps: CapBankReportNavBarProps, @@ -39,12 +38,10 @@ const CapBankReport = () => { const navigate = useNavigate(); const history = useLocation(); const [CapBankID, setCapBankID] = React.useState(0); - const [time, setTime] = React.useState({ - date: '1/1/2000', - time: '12:00:00', - windowSize: 1, - timeWindowUnits: 1 - }); + const [time, setTime] = React.useState({ + start: '01/01/2000 12:00:00.000', + end: '01/02/2000 12:00:00.000', + }); const [selectedBank, setSelectedBank] = React.useState(0); const [StationId, setStationID] = React.useState(0); const [numBanks, setNumBanks] = React.useState(0); @@ -57,17 +54,19 @@ const CapBankReport = () => { const [HealthFilt, setHealthFilt] = React.useState([]); const [PhaseFilter, setPhaseFilter] = React.useState([]); + const timeZone = useSelector(SelectTimeZone); + const dateTimeFormat = useSelector(SelectDateTimeFormat); + const dateTimeMode = useSelector(SelectDateTimeSetting); + React.useEffect(() => { const query = queryString.parse(history.search.replace("?", ""), "&", "="); setCapBankID(query['capBankId'] != undefined ? parseInt(query['capBankId'] as string) : -1); - const time = { - date: query['date'] != undefined ? query['date'] as string : moment().format(momentDateFormat), - time: query['time'] != undefined ? query['time'] as string : moment().format(momentTimeFormat), - windowSize: query['windowSize'] != undefined ? parseInt(query['windowSize'].toString()) : 10, - timeWindowUnits: query['timeWindowUnits'] != undefined ? parseInt(query['timeWindowUnits'].toString()) : 2 - } - setTime(time); + const newTime = { + start: query['start']?.toString() ?? moment().utc().subtract(8, 'hours').format(dateTimeFormat), + end: query['end']?.toString() ?? moment().utc().format(dateTimeFormat) + } + setTime(newTime); setSelectedBank(query['selectedBank'] != undefined ? parseInt(query['selectedBank'].toString()) : -1); setStationID(query['StationId'] != undefined ? parseInt(query['StationId'] as string) : -1); setNumBanks(0); @@ -78,27 +77,28 @@ const CapBankReport = () => { setPISFilt([999]); setHealthFilt([999]); setPhaseFilter([999]); - + }, []); React.useEffect(() => { const state = { - CapBankID, - date: time.date, time: time.time, windowSize: time.windowSize, timeWindowUnits: time.timeWindowUnits, + CapBankID, + start: time.start, end: time.end, selectedBank, StationId, numBanks, ResFilt, StatFilt, - OpFilt, RestFilt, PISFilt, HealthFilt, PhaseFilter }; + OpFilt, RestFilt, PISFilt, HealthFilt, PhaseFilter + }; const q = queryString.stringify(state, "&", "="); const handle = setTimeout(() => navigate(history.pathname + '?' + q), 500); return (() => { clearTimeout(handle); }) - }, [CapBankID, time, + }, [CapBankID, time, selectedBank, StationId, numBanks, ResFilt, StatFilt, OpFilt, RestFilt, PISFilt, HealthFilt, PhaseFilter]) function setState(a: IState) { setCapBankID(a.searchBarProps.CapBankID); - setTime(a.searchBarProps.TimeFilter); + setTime(a.searchBarProps.TimeFilter); setSelectedBank(a.searchBarProps.selectedBank); setStationID(a.searchBarProps.StationId); setNumBanks(a.searchBarProps.numBanks); @@ -112,10 +112,10 @@ const CapBankReport = () => { } const searchBarProps: CapBankReportNavBarProps = { - CapBankID, TimeFilter: time, + CapBankID, TimeFilter: time, selectedBank, StationId, numBanks, ResFilt, StatFilt, OpFilt, RestFilt, PISFilt, HealthFilt, PhaseFilter, - stateSetter: setState + timeZone, dateTimeMode, stateSetter: setState } return ( diff --git a/SEBrowser/Scripts/TSX/Components/CapBankReport/CapBankReportNavBar.tsx b/SEBrowser/Scripts/TSX/Components/CapBankReport/CapBankReportNavBar.tsx index 267d710c5..db86e8aaf 100644 --- a/SEBrowser/Scripts/TSX/Components/CapBankReport/CapBankReportNavBar.tsx +++ b/SEBrowser/Scripts/TSX/Components/CapBankReport/CapBankReportNavBar.tsx @@ -1,387 +1,393 @@ -//****************************************************************************************************** -// CapBankReportNavBar.tsx - Gbtc -// -// Copyright © 2019, Grid Protection Alliance. All Rights Reserved. -// -// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See -// the NOTICE file distributed with this work for additional information regarding copyright ownership. -// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this -// file except in compliance with the License. You may obtain a copy of the License at: -// -// http://opensource.org/licenses/MIT -// -// Unless agreed to in writing, the subject software distributed under the License is distributed on an -// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the -// License for the specific language governing permissions and limitations. -// -// Code Modification History: -// ---------------------------------------------------------------------------------------------------- -// 09/21/2019 - Christoph Lackner -// Generated original version of source code. -// -//****************************************************************************************************** -import * as React from 'react'; -import _ from 'lodash'; - -import SEBrowserService from './../../../TS/Services/SEBrowser'; -import { Modal } from '@gpa-gemstone/react-interactive'; -import ReportTimeFilter from '../ReportTimeFilter'; +//****************************************************************************************************** +// CapBankReportNavBar.tsx - Gbtc +// +// Copyright © 2019, Grid Protection Alliance. All Rights Reserved. +// +// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See +// the NOTICE file distributed with this work for additional information regarding copyright ownership. +// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this +// file except in compliance with the License. You may obtain a copy of the License at: +// +// http://opensource.org/licenses/MIT +// +// Unless agreed to in writing, the subject software distributed under the License is distributed on an +// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the +// License for the specific language governing permissions and limitations. +// +// Code Modification History: +// ---------------------------------------------------------------------------------------------------- +// 09/21/2019 - Christoph Lackner +// Generated original version of source code. +// +//****************************************************************************************************** +import * as React from 'react'; +import _ from 'lodash'; +import SEBrowserService from './../../../TS/Services/SEBrowser'; +import { Modal } from '@gpa-gemstone/react-interactive'; +import { TimeFilter } from '@gpa-gemstone/common-pages' import { SEBrowser } from '../../global'; - - -const momentDateFormat = "MM/DD/YYYY"; -const momentTimeFormat = "HH:mm:ss.SSS"; - - -export interface Substation { - LocationID: number, LocationKey: string, AssetName: string -} - -export interface EventFilter { - ResFilt: Array, - StatFilt: Array, - OpFilt: Array, - RestFilt: Array, - PISFilt: Array, - HealthFilt: Array, - PhaseFilter: Array -} - -export interface CapBankReportNavBarProps extends EventFilter { - stateSetter(state): void, - CapBankID: number, - TimeFilter: SEBrowser.IReportTimeFilter, - selectedBank: number, - StationId: number, - numBanks: number, - -} - -interface CapBank { - Id: number, - AssetKey: string, - AssetName: string, - numBanks: number, - fused: boolean, - compensated: boolean -} - -interface Istate { - capBanks: Array, - subStations: Array, - showFilter: boolean, -} - -export default class CapBankReportNavBar extends React.Component{ - seBrowserService: SEBrowserService; - - constructor(props: CapBankReportNavBarProps, context) { - super(props, context); - this.seBrowserService = new SEBrowserService(); - this.state = { - capBanks: [], - subStations: [], - showFilter: false, - }; - } - - componentDidMount() { - this.getSubstationData(); - - if (this.props.StationId > -1) - this.getCapBankData(this.props.StationId); - } - - componentWillReceiveProps(nextProps: CapBankReportNavBarProps) { - - if (this.state.capBanks.length == 0) - this.getCapBankData(nextProps.StationId); - } - - getCapBankData(LocationID: number) { - - this.seBrowserService.GetCapBankData(LocationID).done(results => { - this.setState({ capBanks: results }) - if (results.length > 0) - this.setCapBank(results[0].Id) - this.setBankNumber(-1); - }); - - } - - setCapBank(capBankId: number) { - - const object = _.clone(this.props) as CapBankReportNavBarProps; - object.CapBankID = capBankId; - object.selectedBank = -1; - if (this.state.capBanks.find(cB => cB.Id == capBankId) != null) - object.numBanks = this.state.capBanks.find(cB => cB.Id == capBankId).numBanks; - - this.props.stateSetter({ searchBarProps: object }); - } - - setBankNumber(capBankNumber: number) { - const object = _.clone(this.props) as CapBankReportNavBarProps; - object.selectedBank = capBankNumber; - this.props.stateSetter({ searchBarProps: object }); - } - - setDate(filter: SEBrowser.IReportTimeFilter) { - - const object = _.clone(this.props) as CapBankReportNavBarProps; - object.TimeFilter = filter; - this.props.stateSetter({ searchBarProps: object }); - } - - getSubstationData() { - this.seBrowserService.GetCapBankSubstationData().done(results => { - if (results == null) - return - this.setState({ subStations: results }) - - if (this.props.StationId == -1 && results.length > 0) - this.setStation(results[0].LocationID) - - }); - } - - setStation(id: number) { - const object = _.clone(this.props) as CapBankReportNavBarProps; - object.StationId = id; - this.props.stateSetter({ searchBarProps: object }); - this.getCapBankData(id); - } - - render() { - - const bankOptions: Array = []; - let i = 1; - let n = 1; - if (this.state.capBanks.find(cB => cB.Id == this.props.CapBankID) != null) - n = this.state.capBanks.find(cB => cB.Id == this.props.CapBankID).numBanks; - - bankOptions.push() - - - for (i = 0; i < n; i++) { - bankOptions.push() - } - - bankOptions.push(); - - return ( - <> - - - this.setState({ showFilter: false })}> -
-
- { - const object = _.clone(this.props) as CapBankReportNavBarProps; - object.PhaseFilter = result; - this.props.stateSetter({ searchBarProps: object }); - }} - filters={[ - { Label: 'AN', Values: [1] }, - { Label: 'BN', Values: [2] }, - { Label: 'CN', Values: [3] }, - ]} /> -
-
- { - const object = _.clone(this.props) as CapBankReportNavBarProps; - object.StatFilt = result; - this.props.stateSetter({ searchBarProps: object }); - }} - filters={[ - { Label: 'Error', Values: [-1] }, - { Label: 'Normal', Values: [0] }, - { Label: '>2 cyc Between Poles', Values: [12] }, - { Label: 'Abnormal Health', Values: [2] }, - { Label: 'Failed Opening', Values: [3, 4] }, - { Label: 'Failed Closing', Values: [10, 5] }, - { Label: 'Restrike/Reignition', Values: [4, 5] }, - { Label: 'Abnormal PreInsertion Switching', Values: [8] }, - { Label: 'Missing Pole', Values: [11] }, - { Label: 'Shorted Units', Values: [20] }, - { Label: 'Blown Fuse', Values: [21] }, - { Label: 'Other', Values: [6, 22, 7] } - ]} /> -
-
- { - const object = _.clone(this.props) as CapBankReportNavBarProps; - object.OpFilt = result; - this.props.stateSetter({ searchBarProps: object }); - }} - filters={[ - { Label: 'Sag/Swell', Values: [-200] }, - { Label: 'No Switching', Values: [-103, -102, -101] }, - { Label: 'Not Determined', Values: [-1] }, - { Label: 'Opening', Values: [101, 102] }, - { Label: 'Closing', Values: [201, 202] } - ]} /> -
-
- { - const object = _.clone(this.props) as CapBankReportNavBarProps; - object.ResFilt = result; - this.props.stateSetter({ searchBarProps: object }); - }} - filters={[ - { Label: 'Resonance', Values: [1] }, - { Label: 'No Resonance', Values: [0] } - ]} /> -
-
- { - const object = _.clone(this.props) as CapBankReportNavBarProps; - object.HealthFilt = result; - this.props.stateSetter({ searchBarProps: object }); - }} - filters={[ - { Label: 'Normal', Values: [0] }, - { Label: 'Shorted Units', Values: [1] }, - { Label: 'Blown Fuses', Values: [2] }, - { Label: 'Tap Voltages Missing', Values: [3] }, - ]} /> -
-
- { - const object = _.clone(this.props) as CapBankReportNavBarProps; - object.RestFilt = result; - this.props.stateSetter({ searchBarProps: object }); - }} - filters={[ - { Label: 'No Restrike', Values: [0, 20] }, - { Label: 'Possible Restrike', Values: [10] }, - { Label: 'Restrike', Values: [32, 42] }, - { Label: 'Reignition', Values: [31, 41] }, - { Label: 'Reversed Polarity', Values: [41, 42] } - ]} /> -
-
- { - const object = _.clone(this.props) as CapBankReportNavBarProps; - object.PISFilt = result; - this.props.stateSetter({ searchBarProps: object }); - }} - filters={[ - { Label: 'Normal', Values: [0] }, - { Label: 'Transient', Values: [1] }, - { Label: 'Too Short', Values: [2] }, - { Label: 'Unknown', Values: [3] }, - ]} /> -
- -
-
- - ); - } -} - -interface IFilter { - Label: string, - Values: Array -} - -const CBEventFilter = (props: { filters: Array, Label: string, showAll: boolean, setter: (filter: Array) => void, activeFilter: Array }) => { - - const allSelected: boolean = props.activeFilter.includes(999); - const isSelected: Array = props.filters.map(item => mapState(item)); - - function FilterChanged(index: number) { - - let updatedStat = isSelected.map((item, i) => (i === index ? !item : item)); - - if (index !== -1 && allSelected) - updatedStat = isSelected.map((item, i) => (i === index ? false : true)); - - let result = []; - updatedStat.forEach((item, i) => { - if (item) - result = result.concat(props.filters[i].Values) - }) - - if (index === -1 && !allSelected) - result.push(999); - - props.setter(result) - } - - function mapState(filter: IFilter) { - let state = true; - - filter.Values.forEach(item => { - if (!props.activeFilter.includes(item)) - state = false; - }) - - return state; - } - - return ( -
-
- {props.Label}: -
-
    - {props.showAll ? -
  • - : null} - {props.filters.map((filt, index) => -
  • - )} -
-
-
-
- ); + +export interface Substation { + LocationID: number, LocationKey: string, AssetName: string +} + +export interface EventFilter { + ResFilt: Array, + StatFilt: Array, + OpFilt: Array, + RestFilt: Array, + PISFilt: Array, + HealthFilt: Array, + PhaseFilter: Array +} + +export interface CapBankReportNavBarProps extends EventFilter { + stateSetter(state): void, + CapBankID: number, + TimeFilter: SEBrowser.IReportTimeFilter, + selectedBank: number, + StationId: number, + numBanks: number, + timeZone: string, + dateTimeMode: SEBrowser.TimeWindowMode +} + +interface CapBank { + Id: number, + AssetKey: string, + AssetName: string, + numBanks: number, + fused: boolean, + compensated: boolean +} + +interface Istate { + capBanks: Array, + subStations: Array, + showFilter: boolean, +} + +export default class CapBankReportNavBar extends React.Component { + seBrowserService: SEBrowserService; + + constructor(props: CapBankReportNavBarProps, context) { + super(props, context); + this.seBrowserService = new SEBrowserService(); + this.state = { + capBanks: [], + subStations: [], + showFilter: false, + }; + } + + componentDidMount() { + this.getSubstationData(); + + if (this.props.StationId > -1) + this.getCapBankData(this.props.StationId); + } + + UNSAFE_componentWillReceiveProps(nextProps: CapBankReportNavBarProps) { + + if (this.state.capBanks.length == 0) + this.getCapBankData(nextProps.StationId); + } + + getCapBankData(LocationID: number) { + + this.seBrowserService.GetCapBankData(LocationID).done(results => { + this.setState({ capBanks: results }) + if (results.length > 0) + this.setCapBank(results[0].Id) + this.setBankNumber(-1); + }); + + } + + setCapBank(capBankId: number) { + + const object = _.clone(this.props) as CapBankReportNavBarProps; + object.CapBankID = capBankId; + object.selectedBank = -1; + if (this.state.capBanks.find(cB => cB.Id == capBankId) != null) + object.numBanks = this.state.capBanks.find(cB => cB.Id == capBankId).numBanks; + + this.props.stateSetter({ searchBarProps: object }); + } + + setBankNumber(capBankNumber: number) { + const object = _.clone(this.props) as CapBankReportNavBarProps; + object.selectedBank = capBankNumber; + this.props.stateSetter({ searchBarProps: object }); + } + + setDate(filter: SEBrowser.IReportTimeFilter) { + const object = _.clone(this.props) as CapBankReportNavBarProps; + object.TimeFilter = filter; + this.props.stateSetter({ searchBarProps: object }); + } + + getSubstationData() { + this.seBrowserService.GetCapBankSubstationData().done(results => { + if (results == null) + return + this.setState({ subStations: results }) + + if (this.props.StationId == -1 && results.length > 0) + this.setStation(results[0].LocationID) + + }); + } + + setStation(id: number) { + const object = _.clone(this.props) as CapBankReportNavBarProps; + object.StationId = id; + this.props.stateSetter({ searchBarProps: object }); + this.getCapBankData(id); + } + + render() { + + const bankOptions: Array = []; + let i = 1; + let n = 1; + if (this.state.capBanks.find(cB => cB.Id == this.props.CapBankID) != null) + n = this.state.capBanks.find(cB => cB.Id == this.props.CapBankID).numBanks; + + bankOptions.push() + + + for (i = 0; i < n; i++) { + bankOptions.push() + } + + bankOptions.push(); + + // Wrapper function to match the expected type for setFilter + const handleSetFilter = (start: string, end: string) => { + this.setDate({ + start: start, + end: end, + }); + }; + + return ( + <> + + + this.setState({ showFilter: false })}> +
+
+ { + const object = _.clone(this.props) as CapBankReportNavBarProps; + object.PhaseFilter = result; + this.props.stateSetter({ searchBarProps: object }); + }} + filters={[ + { Label: 'AN', Values: [1] }, + { Label: 'BN', Values: [2] }, + { Label: 'CN', Values: [3] }, + ]} /> +
+
+ { + const object = _.clone(this.props) as CapBankReportNavBarProps; + object.StatFilt = result; + this.props.stateSetter({ searchBarProps: object }); + }} + filters={[ + { Label: 'Error', Values: [-1] }, + { Label: 'Normal', Values: [0] }, + { Label: '>2 cyc Between Poles', Values: [12] }, + { Label: 'Abnormal Health', Values: [2] }, + { Label: 'Failed Opening', Values: [3, 4] }, + { Label: 'Failed Closing', Values: [10, 5] }, + { Label: 'Restrike/Reignition', Values: [4, 5] }, + { Label: 'Abnormal PreInsertion Switching', Values: [8] }, + { Label: 'Missing Pole', Values: [11] }, + { Label: 'Shorted Units', Values: [20] }, + { Label: 'Blown Fuse', Values: [21] }, + { Label: 'Other', Values: [6, 22, 7] } + ]} /> +
+
+ { + const object = _.clone(this.props) as CapBankReportNavBarProps; + object.OpFilt = result; + this.props.stateSetter({ searchBarProps: object }); + }} + filters={[ + { Label: 'Sag/Swell', Values: [-200] }, + { Label: 'No Switching', Values: [-103, -102, -101] }, + { Label: 'Not Determined', Values: [-1] }, + { Label: 'Opening', Values: [101, 102] }, + { Label: 'Closing', Values: [201, 202] } + ]} /> +
+
+ { + const object = _.clone(this.props) as CapBankReportNavBarProps; + object.ResFilt = result; + this.props.stateSetter({ searchBarProps: object }); + }} + filters={[ + { Label: 'Resonance', Values: [1] }, + { Label: 'No Resonance', Values: [0] } + ]} /> +
+
+ { + const object = _.clone(this.props) as CapBankReportNavBarProps; + object.HealthFilt = result; + this.props.stateSetter({ searchBarProps: object }); + }} + filters={[ + { Label: 'Normal', Values: [0] }, + { Label: 'Shorted Units', Values: [1] }, + { Label: 'Blown Fuses', Values: [2] }, + { Label: 'Tap Voltages Missing', Values: [3] }, + ]} /> +
+
+ { + const object = _.clone(this.props) as CapBankReportNavBarProps; + object.RestFilt = result; + this.props.stateSetter({ searchBarProps: object }); + }} + filters={[ + { Label: 'No Restrike', Values: [0, 20] }, + { Label: 'Possible Restrike', Values: [10] }, + { Label: 'Restrike', Values: [32, 42] }, + { Label: 'Reignition', Values: [31, 41] }, + { Label: 'Reversed Polarity', Values: [41, 42] } + ]} /> +
+
+ { + const object = _.clone(this.props) as CapBankReportNavBarProps; + object.PISFilt = result; + this.props.stateSetter({ searchBarProps: object }); + }} + filters={[ + { Label: 'Normal', Values: [0] }, + { Label: 'Transient', Values: [1] }, + { Label: 'Too Short', Values: [2] }, + { Label: 'Unknown', Values: [3] }, + ]} /> +
+ +
+
+ + ); + } +} + +interface IFilter { + Label: string, + Values: Array +} + +const CBEventFilter = (props: { filters: Array, Label: string, showAll: boolean, setter: (filter: Array) => void, activeFilter: Array }) => { + + const allSelected: boolean = props.activeFilter.includes(999); + const isSelected: Array = props.filters.map(item => mapState(item)); + + function FilterChanged(index: number) { + + let updatedStat = isSelected.map((item, i) => (i === index ? !item : item)); + + if (index !== -1 && allSelected) + updatedStat = isSelected.map((item, i) => (i === index ? false : true)); + + let result = []; + updatedStat.forEach((item, i) => { + if (item) + result = result.concat(props.filters[i].Values) + }) + + if (index === -1 && !allSelected) + result.push(999); + + props.setter(result) + } + + function mapState(filter: IFilter) { + let state = true; + + filter.Values.forEach(item => { + if (!props.activeFilter.includes(item)) + state = false; + }) + + return state; + } + + return ( +
+
+ {props.Label}: +
+
    + {props.showAll ? +
  • + : null} + {props.filters.map((filt, index) => +
  • + )} +
+
+
+
+ ); } \ No newline at end of file diff --git a/SEBrowser/Scripts/TSX/Components/CapBankReport/CapBankReportPane.tsx b/SEBrowser/Scripts/TSX/Components/CapBankReport/CapBankReportPane.tsx index 967339b7e..015dd3d0d 100644 --- a/SEBrowser/Scripts/TSX/Components/CapBankReport/CapBankReportPane.tsx +++ b/SEBrowser/Scripts/TSX/Components/CapBankReport/CapBankReportPane.tsx @@ -24,10 +24,9 @@ import * as React from 'react'; import moment from 'moment'; import { CapBankReportNavBarProps } from './CapBankReportNavBar'; -import _, { cloneDeep } from 'lodash'; +import _ from 'lodash'; import { Warning, Modal } from '@gpa-gemstone/react-interactive'; -import { Plot, Line } from '@gpa-gemstone/react-graph' -import { findAppropriateUnit, getMoment, getStartEndTime } from '../EventSearch/TimeWindowUtils'; +import { Plot, Line } from '@gpa-gemstone/react-graph'; interface ITrendSeries { @@ -102,7 +101,7 @@ interface ICBEvent { export default class CapBankReportPane extends React.Component { - + eventTableHandle: JQuery.jqXHR; trendHandle: JQuery.jqXHR; @@ -155,7 +154,7 @@ export default class CapBankReportPane extends React.Component 0) && (!this.props.PhaseFilter.includes(999))) filter = filter + `&phaseFilt=${this.props.PhaseFilter.join(',')}` - + return filter; } @@ -211,15 +210,10 @@ export default class CapBankReportPane extends React.Component { - + if (data == null) { - this.setState({EventData: []}) + this.setState({ EventData: [] }) return; } this.setState({ EventData: data }) }); - - this.getTrendData().then(data => { if (data == null) { @@ -249,8 +241,6 @@ export default class CapBankReportPane extends React.Component { this.setState({ ShowWarning: false }); if (confirmed) this.updateCapBank(); else this.setState({ ShowCapBankEdit: true });}} /> + CallBack={(confirmed) => { this.setState({ ShowWarning: false }); if (confirmed) this.updateCapBank(); else this.setState({ ShowCapBankEdit: true }); }} />
{(this.state.TrendData.Q.length > 0 ?
@@ -340,12 +330,12 @@ export default class CapBankReportPane extends React.Component
: null)} - {(this.state.TrendData.DeltaQ.length > 0? -
-
Change in Q
+ {(this.state.TrendData.DeltaQ.length > 0 ? +
+
Change in Q
this.createPointTable(this.state.TrendData.DeltaQ, 'Change in Q','(MVA)')}> + Tlabel={'Time'} Ylabel={'Delta Q (kVAR)'} showMouse={true} onDataInspect={() => this.createPointTable(this.state.TrendData.DeltaQ, 'Change in Q', '(MVA)')}> {this.state.TrendData.DeltaQ.map((s, i) => )}
@@ -357,7 +347,7 @@ export default class CapBankReportPane extends React.ComponentRMS Current
this.createPointTable(this.state.TrendData.Irms, 'RMS Current','(A)')}> + Tlabel={'Time'} Ylabel={'I RMS (A)'} showMouse={true} onDataInspect={() => this.createPointTable(this.state.TrendData.Irms, 'RMS Current', '(A)')}> {this.state.TrendData.Irms.map((s, i) => )}
@@ -367,7 +357,7 @@ export default class CapBankReportPane extends React.ComponentChange in RMS Current
this.createPointTable(this.state.TrendData.DeltaIrms, 'Change in RMS Current','(A)')}> + Tlabel={'Time'} Ylabel={'Delta I RMS (A)'} showMouse={true} onDataInspect={() => this.createPointTable(this.state.TrendData.DeltaIrms, 'Change in RMS Current', '(A)')}> {this.state.TrendData.DeltaIrms.map((s, i) => )}
@@ -377,7 +367,7 @@ export default class CapBankReportPane extends React.ComponentRMS Voltage
this.createPointTable(this.state.TrendData.Vrms, 'RMS Voltage','(%)')}> + Tlabel={'Time'} Ylabel={'V RMS (%)'} showMouse={true} useMetricFactors={false} onDataInspect={() => this.createPointTable(this.state.TrendData.Vrms, 'RMS Voltage', '(%)')}> {this.state.TrendData.Vrms.map((s, i) => )}
@@ -387,7 +377,7 @@ export default class CapBankReportPane extends React.ComponentChange in RMS Voltage
this.createPointTable(this.state.TrendData.DeltaVrms, 'Change in RMS Voltage','(%)')}> + Ylabel={'Delta V RMS (%)'} showMouse={true} useMetricFactors={false} onDataInspect={() => this.createPointTable(this.state.TrendData.DeltaVrms, 'Change in RMS Voltage', '(%)')}> {this.state.TrendData.DeltaVrms.map((s, i) => )}
@@ -397,7 +387,7 @@ export default class CapBankReportPane extends React.ComponentResonance Frequency
this.createPointTable(this.state.TrendData.Freq, 'Resonance Frequency','(Hz)')}> + Ylabel={'Res. Freq. (Hz)'} showMouse={true} onDataInspect={() => this.createPointTable(this.state.TrendData.Freq, 'Resonance Frequency', '(Hz)')}> {this.state.TrendData.Freq.map((s, i) => )}
@@ -408,7 +398,7 @@ export default class CapBankReportPane extends React.ComponentPeak Voltage
this.createPointTable(this.state.TrendData.PeakV, 'Peak Voltage','(%)')}> + Tlabel={'Time'} Ylabel={'Voltage peak (%)'} showMouse={true} useMetricFactors={false} onDataInspect={() => this.createPointTable(this.state.TrendData.PeakV, 'Peak Voltage', '(%)')}> {this.state.TrendData.PeakV.map((s, i) => )}
@@ -418,7 +408,7 @@ export default class CapBankReportPane extends React.ComponentVoltage THD
this.createPointTable(this.state.TrendData.THDV, 'Voltage THD','(%)')}> + Ylabel={'THD (%)'} showMouse={true} useMetricFactors={false} onDataInspect={() => this.createPointTable(this.state.TrendData.THDV, 'Voltage THD', '(%)')}> {this.state.TrendData.THDV.map((s, i) => )}
@@ -429,7 +419,7 @@ export default class CapBankReportPane extends React.ComponentChange in Voltage THD
this.createPointTable(this.state.TrendData.DeltaTHDV, 'Change in Voltage THD','(%)')}> + Tlabel={'Time'} Ylabel={'Delta THD (%)'} showMouse={true} useMetricFactors={false} onDataInspect={() => this.createPointTable(this.state.TrendData.DeltaTHDV, 'Change in Voltage THD', '(%)')}> {this.state.TrendData.DeltaTHDV.map((s, i) => )}
@@ -439,7 +429,7 @@ export default class CapBankReportPane extends React.ComponentCurrent THD
this.createPointTable(this.state.TrendData.THDI, 'Current THD','(%)')}> + Ylabel={'THD (%)'} showMouse={true} useMetricFactors={false} onDataInspect={() => this.createPointTable(this.state.TrendData.THDI, 'Current THD', '(%)')}> {this.state.TrendData.THDI.map((s, i) => )}
@@ -450,7 +440,7 @@ export default class CapBankReportPane extends React.ComponentChange in Current THD
this.createPointTable(this.state.TrendData.THDI, 'Change in Current THD','(%)')}> + Ylabel={'Delta THD (%)'} showMouse={true} useMetricFactors={false} onDataInspect={() => this.createPointTable(this.state.TrendData.THDI, 'Change in Current THD', '(%)')}> {this.state.TrendData.DeltaTHDI.map((s, i) => )}
@@ -460,7 +450,7 @@ export default class CapBankReportPane extends React.ComponentSwitching Frequency
this.createPointTable(this.state.TrendData.SwitchingFreq, 'Switching Frequency','(Hz)')}> + Ylabel={'Switching Freq. (Hz)'} showMouse={true} onDataInspect={() => this.createPointTable(this.state.TrendData.SwitchingFreq, 'Switching Frequency', '(Hz)')}> {this.state.TrendData.SwitchingFreq.map((s, i) => )}
@@ -470,7 +460,7 @@ export default class CapBankReportPane extends React.ComponentCapacitor Bank Impedance
this.createPointTable(this.state.TrendData.Xcap, 'Capacitor Bank Impedance','(Ohm)')}> + showMouse={true} onDataInspect={() => this.createPointTable(this.state.TrendData.Xcap, 'Capacitor Bank Impedance', '(Ohm)')}> {this.state.TrendData.Xcap.map((s, i) => )}
@@ -480,7 +470,7 @@ export default class CapBankReportPane extends React.ComponentCapacitor Bank Impedance Change
this.createPointTable(this.state.TrendData.DeltaXcap, 'Capacitor Bank Impedance Change','(Ohm)')}> + showMouse={true} onDataInspect={() => this.createPointTable(this.state.TrendData.DeltaXcap, 'Capacitor Bank Impedance Change', '(Ohm)')}> {this.state.TrendData.DeltaXcap.map((s, i) => )}
@@ -489,9 +479,9 @@ export default class CapBankReportPane extends React.Component 0 ?
Capacitor Bank Restrike Duration
-
+
this.createPointTable(this.state.TrendData.RestrikeDuration, 'Capacitor Bank Restrike Duration','(cycles)')} showMouse={true}> + onDataInspect={() => this.createPointTable(this.state.TrendData.RestrikeDuration, 'Capacitor Bank Restrike Duration', '(cycles)')} showMouse={true}> {this.state.TrendData.RestrikeDuration.map((s, i) => )}
@@ -501,7 +491,7 @@ export default class CapBankReportPane extends React.ComponentCapacitor Bank Restrike Current Peak
this.createPointTable(this.state.TrendData.RestrikeI, 'Capacitor Bank Restrike Current Peak','(kA)')}> + Ylabel={'Current Peak (kA)'} showMouse={true} onDataInspect={() => this.createPointTable(this.state.TrendData.RestrikeI, 'Capacitor Bank Restrike Current Peak', '(kA)')}> {this.state.TrendData.RestrikeI.map((s, i) => )}
@@ -511,10 +501,10 @@ export default class CapBankReportPane extends React.ComponentCapacitor Bank Restrike Voltage Peak
this.createPointTable(this.state.TrendData.RestrikeV, 'Capacitor Bank Restrike Voltage Peak','(kV)')}> + Ylabel={'Voltage Peak (kV)'} showMouse={true} onDataInspect={() => this.createPointTable(this.state.TrendData.RestrikeV, 'Capacitor Bank Restrike Voltage Peak', '(kV)')}> {this.state.TrendData.RestrikeV.map((s, i) => )} -
+ : null)} {(this.state.TrendData.PISDuration.length > 0 ? @@ -522,7 +512,7 @@ export default class CapBankReportPane extends React.ComponentCapacitor Bank Pre-Insertion Switching Duration
this.createPointTable(this.state.TrendData.PISDuration, 'Capacitor Bank Pre-Insertion Switching Duration','(cycles)')}> + Ylabel={'Duration (cycles)'} showMouse={true} onDataInspect={() => this.createPointTable(this.state.TrendData.PISDuration, 'Capacitor Bank Pre-Insertion Switching Duration', '(cycles)')}> {this.state.TrendData.PISDuration.map((s, i) => )}
@@ -532,7 +522,7 @@ export default class CapBankReportPane extends React.ComponentCapacitor Bank Pre-Insertion Switching Impedance
this.createPointTable(this.state.TrendData.PISZ, 'Capacitor Bank Pre-Insertion Switching Impedance','(Ohm)')}> + Ylabel={'Impedance (Ohm)'} showMouse={true} onDataInspect={() => this.createPointTable(this.state.TrendData.PISZ, 'Capacitor Bank Pre-Insertion Switching Impedance', '(Ohm)')}> {this.state.TrendData.PISZ.map((s, i) => )}
@@ -542,7 +532,7 @@ export default class CapBankReportPane extends React.ComponentCapacitor Bank Pre-Insertion Switching Current
this.createPointTable(this.state.TrendData.PISI, 'Capacitor Bank Pre-Insertion Switching Current','(kA)')}> + Ylabel={'Current (kA)'} showMouse={true} onDataInspect={() => this.createPointTable(this.state.TrendData.PISI, 'Capacitor Bank Pre-Insertion Switching Current', '(kA)')}> {this.state.TrendData.PISI.map((s, i) => )}
@@ -553,7 +543,7 @@ export default class CapBankReportPane extends React.ComponentCapacitor Bank K Factor
this.createPointTable(this.state.TrendData.KFactor, 'Capacitor Bank k Factor','')}> + Ylabel={'K Factor'} showMouse={true} useMetricFactors={false} onDataInspect={() => this.createPointTable(this.state.TrendData.KFactor, 'Capacitor Bank k Factor', '')}> {this.state.TrendData.KFactor.map((s, i) => )}
@@ -563,7 +553,7 @@ export default class CapBankReportPane extends React.ComponentCapacitor Bank Relay Differential Voltage
this.createPointTable(this.state.TrendData.RelaydV, 'Capacitor Bank Relay Differential Voltage','(V)')}> + Ylabel={'Diff. Voltage (V)'} showMouse={true} onDataInspect={() => this.createPointTable(this.state.TrendData.RelaydV, 'Capacitor Bank Relay Differential Voltage', '(V)')}> {this.state.TrendData.RelaydV.map((s, i) => )}
@@ -573,7 +563,7 @@ export default class CapBankReportPane extends React.ComponentCapacitor Bank Relay Voltage
this.createPointTable(this.state.TrendData.RelayV, 'Capacitor Bank Relay Voltage','(V)')}> + Ylabel={'Voltage (V)'} showMouse={true} onDataInspect={() => this.createPointTable(this.state.TrendData.RelayV, 'Capacitor Bank Relay Voltage', '(V)')}> {this.state.TrendData.RelayV.map((s, i) => )}
@@ -583,7 +573,7 @@ export default class CapBankReportPane extends React.ComponentCapacitor Bank Voltage-Impedance Ratio Missmatch
onDataInspect={() => this.createPointTable(this.state.TrendData.RelayXV, 'Capacitor Bank Voltage-Impedance Ratio Missmatch','(%)')} + Ylabel={'ratio missmatch (%)'} showMouse={true} useMetricFactors={false}> onDataInspect={() => this.createPointTable(this.state.TrendData.RelayXV, 'Capacitor Bank Voltage-Impedance Ratio Missmatch', '(%)')} {this.state.TrendData.RelayXV.map((s, i) => )}
@@ -593,7 +583,7 @@ export default class CapBankReportPane extends React.ComponentCapacitor Bank LV Cap Reactance or Midstack Reactances
this.createPointTable(this.state.TrendData.RelayXLV, 'Capacitor Bank LV Cap Reactance or Midstack Reactances','(Ohm)')}> + Tlabel={'Time'} Ylabel={'Reactance (Ohm)'} showMouse={true} onDataInspect={() => this.createPointTable(this.state.TrendData.RelayXLV, 'Capacitor Bank LV Cap Reactance or Midstack Reactances', '(Ohm)')}> {this.state.TrendData.RelayXLV.map((s, i) => )}
@@ -603,7 +593,7 @@ export default class CapBankReportPane extends React.ComponentCapacitor Bank Neutral Current
this.createPointTable(this.state.TrendData.Ineutral, 'Capacitor Bank Neutral Current','(A)')}> + showMouse={true} onDataInspect={() => this.createPointTable(this.state.TrendData.Ineutral, 'Capacitor Bank Neutral Current', '(A)')}> {this.state.TrendData.Ineutral.map((s, i) => )}
@@ -613,7 +603,7 @@ export default class CapBankReportPane extends React.ComponentCapacitor Bank Unbalance Factors
this.createPointTable(this.state.TrendData.Unbalance, 'Capacitor Bank Unbalance Factors','(%)')}> + Ylabel={'Unbalance (%)'} showMouse={true} useMetricFactors={false} onDataInspect={() => this.createPointTable(this.state.TrendData.Unbalance, 'Capacitor Bank Unbalance Factors', '(%)')}> {this.state.TrendData.Unbalance.map((s, i) => )}
@@ -623,7 +613,7 @@ export default class CapBankReportPane extends React.ComponentCapacitor Bank Zero Sequence Voltage
this.createPointTable(this.state.TrendData.BusV, 'Capacitor Bank Zero Sequence Voltage','(V)')} > + Ylabel={'Voltage (V)'} showMouse={true} onDataInspect={() => this.createPointTable(this.state.TrendData.BusV, 'Capacitor Bank Zero Sequence Voltage', '(V)')} > {this.state.TrendData.BusV.map((s, i) => )}
@@ -633,7 +623,7 @@ export default class CapBankReportPane extends React.ComponentCapacitor Bank Zero Sequence Impedance
this.createPointTable(this.state.TrendData.BusZ, 'Capacitor Bank Zero Sequence Impedance','(Ohm)')}> + Ylabel={'Impedance (Ohm)'} showMouse={true} onDataInspect={() => this.createPointTable(this.state.TrendData.BusZ, 'Capacitor Bank Zero Sequence Impedance', '(Ohm)')}> {this.state.TrendData.BusZ.map((s, i) => )}
@@ -656,28 +646,23 @@ export default class CapBankReportPane extends React.Component ) - } getTimeLimits() { - const [Tstart, Tend] = getStartEndTime(getMoment(this.props.TimeFilter.date, this.props.TimeFilter.time), - this.props.TimeFilter.windowSize, this.props.TimeFilter.timeWindowUnits); - this.setState({ Tstart: Tstart.valueOf(), Tend: Tend.valueOf()}) + const startMoment = moment(this.props.TimeFilter.start); + const endMoment = moment(this.props.TimeFilter.end); + const [Tstart, Tend] = [startMoment, endMoment]; + this.setState({ Tstart: Tstart.valueOf(), Tend: Tend.valueOf() }) } getTrendData() { if (this.trendHandle !== undefined) this.trendHandle.abort(); - const adjustedTimeFrame = findAppropriateUnit(getMoment(this.props.TimeFilter.date, this.props.TimeFilter.time), - getStartEndTime(getMoment(this.props.TimeFilter.date, this.props.TimeFilter.time), this.props.TimeFilter.windowSize, this.props.TimeFilter.timeWindowUnits)[0], - this.props.TimeFilter.timeWindowUnits); - this.trendHandle = $.ajax({ type: "GET", - url: `${homePath}api/PQDashboard/CapBankReport/GetTrend?capBankId=${this.props.CapBankID}&date=${this.props.TimeFilter.date}` + - `&time=${this.props.TimeFilter.time}&timeWindowunits=${adjustedTimeFrame[0]}&windowSize=${adjustedTimeFrame[1]}` + - `&bankNum=${this.props.selectedBank}` + this.getFilterString(), + url: `${homePath}api/PQDashboard/CapBankReport/GetTrend?capBankId=${this.props.CapBankID}&start=${this.props.TimeFilter.start}` + + `&end=${this.props.TimeFilter.end}&bankNum=${this.props.selectedBank}` + this.getFilterString(), contentType: "application/json; charset=utf-8", dataType: 'json', cache: false, @@ -686,14 +671,12 @@ export default class CapBankReportPane extends React.Component void) => { return ( - {showEdit ? stateSetter({ ShowCapBankEdit: true, SelectedEvent: row.ID, SelectedCapBank: 1})}> : null} + {showEdit ? stateSetter({ ShowCapBankEdit: true, SelectedEvent: row.ID, SelectedCapBank: 1 })}> : null} {moment.utc(row.Time).format('MM/DD/YY HH:mm:ss.SSS')} @@ -711,7 +694,7 @@ const EventRow = (row: ICBEvent, showEdit: boolean, stateSetter: (obj: any) => v const EventHeader = (props: { showEdit: boolean }) => { return ( - {props.showEdit? : null} + {props.showEdit ? : null} Time Phase Analysis Status diff --git a/SEBrowser/Scripts/TSX/Components/DERAnalysisReport/DERAnalysisReport.tsx b/SEBrowser/Scripts/TSX/Components/DERAnalysisReport/DERAnalysisReport.tsx index 3c5f1a2a6..bfd9c8c91 100644 --- a/SEBrowser/Scripts/TSX/Components/DERAnalysisReport/DERAnalysisReport.tsx +++ b/SEBrowser/Scripts/TSX/Components/DERAnalysisReport/DERAnalysisReport.tsx @@ -27,14 +27,12 @@ import Table from '@gpa-gemstone/react-table'; import { OpenXDA } from '@gpa-gemstone/application-typings'; import queryString from 'querystring'; import moment from 'moment'; -import ReportTimeFilter from '../ReportTimeFilter'; import { orderBy } from 'lodash'; import { Line, Plot } from '@gpa-gemstone/react-graph'; import { useNavigate, useLocation } from 'react-router-dom'; -import { findAppropriateUnit, getMoment, getStartEndTime } from '../EventSearch/TimeWindowUtils'; - -const momentDateFormat = "MM/DD/YYYY"; -const momentTimeFormat = "HH:mm:ss.SSS"; +import { TimeFilter } from '@gpa-gemstone/common-pages' +import { useSelector } from 'react-redux'; +import { SelectTimeZone, SelectDateTimeSetting, SelectDateTimeFormat } from '../SettingsSlice'; interface DERAnalyticResult { ID: number, @@ -54,10 +52,11 @@ function DERAnalysisReport() { const history = useLocation(); const navigate = useNavigate(); - const [date, setDate] = React.useState(moment().format(momentDateFormat)); - const [time, setTime] = React.useState(moment().format(momentTimeFormat)); - const [windowSize, setWindowSize] = React.useState(1); - const [timeWindowUnits, setTimeWindowUnits] = React.useState(4); + const timeZone = useSelector(SelectTimeZone); + const dateTimeSetting = useSelector(SelectDateTimeSetting); + const dateTimeFormat = useSelector(SelectDateTimeFormat); + const [start, setStart] = React.useState(moment().format(dateTimeFormat)); + const [end, setEnd] = React.useState(moment().format(dateTimeFormat)); const [regulations, setRegulations] = React.useState<{ Value: number, Text: string, Selected: boolean }[]>([]) const [stations, setStations] = React.useState<{ Value: number, Text: string, Selected: boolean }[]>([]); const [ders, setDERs] = React.useState<{ Value: number, Text: string, Selected: boolean }[]>([]); @@ -68,25 +67,17 @@ function DERAnalysisReport() { React.useEffect(() => { const query = queryString.parse(history.search.replace("?", ""), "&", "=", { decodeURIComponent: queryString.unescape }); - - setTime(query['time'] != undefined ? query['time'] as string : moment().format(momentTimeFormat)) - setDate(query['date'] != undefined ? query['date'] as string : moment().format(momentDateFormat)) - setWindowSize(query['windowSize'] != undefined ? parseInt(query['windowSize'] as string) : 1); - setTimeWindowUnits(query['timeWindowUnits'] != undefined ? parseInt(query['timeWindowUnits'] as string) : 4); + setStart(query['start'] != undefined ? query['start'] as string : moment().format(dateTimeFormat)) + setEnd(query['end'] != undefined ? query['end'] as string : moment().format(dateTimeFormat)) }, []); React.useEffect(() => { - const queryParam = { - time, - date, - windowSize, - timeWindowUnits - }; + const queryParam = { start, end }; const q = queryString.stringify(queryParam, "&", "=", { encodeURIComponent: queryString.escape }); const handle = setTimeout(() => navigate(history.pathname + '?' + q), 500); return (() => { clearTimeout(handle); }) - }, [time, date, windowSize, timeWindowUnits]) + }, [start, end]) React.useEffect(() => { const handle1 = $.ajax({ @@ -98,7 +89,7 @@ function DERAnalysisReport() { async: true }) as JQuery.jqXHR; - handle1.done(d => setRegulations(d.map((reg, i) => ({Value: i, Text: reg, Selected: true}))) ) + handle1.done(d => setRegulations(d.map((reg, i) => ({ Value: i, Text: reg, Selected: true })))) const handle2 = $.ajax({ type: "GET", @@ -107,14 +98,13 @@ function DERAnalysisReport() { dataType: 'json', cache: false, async: true - }) as JQuery.jqXHR<{LocationID: number, LocationKey: string, Name: string}[]>; + }) as JQuery.jqXHR<{ LocationID: number, LocationKey: string, Name: string }[]>; handle2.done(d => setStations(d.map((reg) => ({ Value: reg.LocationID, Text: reg.Name, Selected: true })))) return () => { if (handle1.abort != undefined) handle1.abort(); if (handle2.abort != undefined) handle2.abort(); - }; }, []); @@ -135,7 +125,7 @@ function DERAnalysisReport() { url: `${homePath}api/DERReport/DER`, contentType: "application/json; charset=utf-8", dataType: 'json', - data: JSON.stringify({ SubstationIDs: stations.filter(s=> s.Selected).map(s => s.Value)}), + data: JSON.stringify({ SubstationIDs: stations.filter(s => s.Selected).map(s => s.Value) }), cache: false, async: true }) as JQuery.jqXHR; @@ -150,22 +140,16 @@ function DERAnalysisReport() { React.useEffect(() => { - - const adjustedTime = findAppropriateUnit(getMoment(date, time), - getStartEndTime(getMoment(date, time), windowSize, timeWindowUnits)[1], - timeWindowUnits); - const handle1 = $.ajax({ type: "POST", url: `${homePath}api/DERReport`, contentType: "application/json; charset=utf-8", dataType: 'json', - data: JSON.stringify({ + data: JSON.stringify({ DERIDs: ders.filter(s => s.Selected).map(s => s.Value), - Time: date + ' ' + time, - Window: adjustedTime[1], - TimeWindowUnit: adjustedTime[0], - Regulations: regulations.filter(s => s.Selected).map(s => s.Text) + StartTime: start, + EndTime: end, + Regulations: regulations.filter(s => s.Selected).map(s => s.Text) }), cache: false, async: true @@ -177,7 +161,7 @@ function DERAnalysisReport() { if (handle1.abort != undefined) handle1.abort(); }; - }, [ders, date, time, windowSize, timeWindowUnits, regulations]); + }, [ders, start, end, regulations]); return (
@@ -225,12 +209,13 @@ function DERAnalysisReport() {
  • - { - setDate(f.date); - setTime(f.time); - setTimeWindowUnits(f.timeWindowUnits); - setWindowSize(f.windowSize); - }} showQuickSelect={false} /> + { + setStart(start); + setEnd(end); + }} + showQuickSelect={false} timeZone={timeZone} + dateTimeSetting={dateTimeSetting} isHorizontal={false} />
  • @@ -242,7 +227,7 @@ function DERAnalysisReport() {
    cols={[ - { key: 'Time', label: 'Time', field: 'Time', content: (item) => moment(item.Time).format(momentDateFormat + ' ' + momentTimeFormat) }, + { key: 'Time', label: 'Time', field: 'Time', content: (item) => moment(item.Time).format(dateTimeFormat) }, { key: 'Meter', label: 'Meter', field: 'Meter' }, { key: 'Asset', label: 'Asset', field: 'Asset' }, { key: 'Channel', label: 'Channel', field: 'Channel' }, @@ -257,7 +242,7 @@ function DERAnalysisReport() { ascending={ascending} sortKey={sortKey} onSort={(data) => { - if(data.colField == sortKey) + if (data.colField == sortKey) setAscending(!ascending); else setSortKey(data.colField); @@ -303,12 +288,12 @@ function DERAnalysisReport() {
    - +
    Software Title DER Operation Version #0
    Electric Power Research Institute (EPRI)
    3420 Hillview Ave.
    Palo Alto, CA 94304
    -
    +
    Copyright © 2021 Electric Power Research Institute, Inc. All rights reserved.

    As a user of this EPRI preproduction software, you accept and acknowledge that:
    @@ -335,7 +320,7 @@ function DERAnalysisReport() { } const Graph = (props: DERAnalyticResult) => { - const [data, setData] = React.useState<[number,number][]>([]); + const [data, setData] = React.useState<[number, number][]>([]); React.useEffect(() => { if (props.ID == undefined) return; diff --git a/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearch.tsx b/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearch.tsx index ddb2d3886..680da7ec3 100644 --- a/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearch.tsx +++ b/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearch.tsx @@ -32,8 +32,7 @@ import EventSearchMagDur from './EventSearchMagDur'; import { ProcessQuery, SelectEventList, SelectQueryParam } from './EventSearchSlice'; import { useLocation, useNavigate } from 'react-router-dom'; import { useAppDispatch, useAppSelector } from '../../hooks'; -import { SplitSection, VerticalSplit } from '@gpa-gemstone/react-interactive'; -import moment from 'moment' +import { SplitSection, VerticalSplit } from '@gpa-gemstone/react-interactive'; type tab = 'Waveform' | 'Fault' | 'Correlating' | 'Configuration' | 'All' | undefined; @@ -43,10 +42,6 @@ const EventSearch = () => { const dispatch = useAppDispatch(); const [eventId, setEventId] = React.useState(-1); - const [date, setDate] = React.useState(''); - const [time, setTime] = React.useState(''); - const [windowSize, setWindowSize] = React.useState(0); - const [timeWindowUnits, setTimeWindowUnits] = React.useState(0); const [initialTab, setInitialTab] = React.useState(undefined); const [showMagDur, setShowMagDur] = React.useState(false); const [showNav, setShowNav] = React.useState(getShowNav()); @@ -55,25 +50,16 @@ const EventSearch = () => { const queryParam = useAppSelector(SelectQueryParam); const evtList = useAppSelector(SelectEventList); - const momentDateFormat = "MM/DD/YYYY"; - const momentTimeFormat = "HH:mm:ss.SSS"; - React.useEffect(() => { - const query = queryString.parse(history.search.replace("?",""), "&", "=", { decodeURIComponent: queryString.unescape }); - + const query = queryString.parse(history.search.replace("?", ""), "&", "=", { decodeURIComponent: queryString.unescape }); dispatch(ProcessQuery(query)); - setInitialTab(query['tab'] != undefined ? query['tab'].toString() as any : undefined); setShowMagDur(query['magDur'] != undefined ? query['magDur'] == 'true' : false); setEventId(query['eventid'] != undefined ? parseInt(query['eventid'].toString()) : -1); - setDate(query['date'] != undefined ? query['date'] as string : moment().format(momentDateFormat)); - setTime(query['time'] != undefined ? query['time'] as string : moment().format(momentTimeFormat)); - setWindowSize(query['windowSize'] != undefined ? parseInt(query['windowSize'].toString()) : 10); - setTimeWindowUnits(query['timeWindowUnits'] != undefined ? parseInt(query['timeWindowUnits'].toString()) : 2); }, []); - + React.useEffect(() => { const q = queryString.stringify(queryParam, "&", "=", { encodeURIComponent: queryString.escape }); const handle = setTimeout(() => navigate(history.pathname + '?' + q), 500); @@ -90,10 +76,9 @@ const EventSearch = () => { function getShowNav(): boolean { - if (Object.prototype.hasOwnProperty.call(localStorage, 'SEbrowser.EventSearch.ShowNav')) - return JSON.parse(localStorage.getItem('SEbrowser.EventSearch.ShowNav')) - else - return true; + if (Object.prototype.hasOwnProperty.call(localStorage, 'SEbrowser.EventSearch.ShowNav')) + return JSON.parse(localStorage.getItem('SEbrowser.EventSearch.ShowNav')); + else return true; } return ( @@ -103,40 +88,34 @@ const EventSearch = () => { showNav={showNav} setHeight={setNavHeight} /> - - -
    -
    -
    -
    + + +
    +
    +
    +
    -
    - {showMagDur ? - : - - }
    -
    - + {showMagDur + ? + : + } +
    + +
    -
    +
    diff --git a/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchList.tsx b/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchList.tsx index 10728b389..968c3ee04 100644 --- a/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchList.tsx +++ b/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchList.tsx @@ -24,12 +24,12 @@ import * as React from 'react'; import ReactDOM from 'react-dom'; import { LoadingIcon, OverlayDrawer } from '@gpa-gemstone/react-interactive'; -import { ConfigTable } from '@gpa-gemstone/react-interactive'; +import { ConfigTable } from '@gpa-gemstone/react-interactive'; import { ReactTable } from '@gpa-gemstone/react-table' import { useAppDispatch, useAppSelector } from '../../hooks'; -import { Redux } from '../../global'; +import { Redux } from '../../global'; import { SelectEventSearchsAscending, SelectEventSearchsSortField, Sort, SelectEventSearchsStatus, FetchEventSearches, SelectEventSearchs } from './EventSearchSlice'; -import { SelectEventSearchSettings } from '../SettingsSlice'; +import { SelectEventSearchSettings } from '../SettingsSlice'; interface IProps { eventid: number, @@ -45,15 +45,18 @@ interface IColumn { export default function EventSearchList(props: IProps) { const ref = React.useRef(); - const closureHandler = React.useRef<((o: boolean) => void)>(() => {/*Do Nothing*/ }) + const closureHandler = React.useRef<((o: boolean) => void)>(() => {/*Do Nothing*/ }); const count = React.useRef(null); + const dispatch = useAppDispatch(); + const status = useAppSelector(SelectEventSearchsStatus); const sortField = useAppSelector(SelectEventSearchsSortField); const ascending = useAppSelector(SelectEventSearchsAscending); const data = useAppSelector((state: Redux.StoreState) => SelectEventSearchs(state)); + const numberResults = useAppSelector((state: Redux.StoreState) => SelectEventSearchSettings(state).NumberResults); + const [cols, setCols] = React.useState([]); - const numberResults = useAppSelector((state: Redux.StoreState) => SelectEventSearchSettings(state).NumberResults) const [hCounter, setHCounter] = React.useState(0); React.useEffect(() => { @@ -78,9 +81,7 @@ export default function EventSearchList(props: IProps) { flds = Object.keys(data[0]).filter(item => item != "Time" && item != "DisturbanceID" && item != "EventID" && item != "EventID1" && item != 'MagDurDuration' && item != 'MagDurMagnitude').sort(); if (flds.length != cols.length) - setCols(flds.map(item => ({ - field: item, key: item, label: item }))) - + setCols(flds.map(item => ({ field: item, key: item, label: item }))) }, [data]) React.useLayoutEffect(() => { @@ -88,7 +89,7 @@ export default function EventSearchList(props: IProps) { }); function closeSettings(open: boolean) { - closureHandler.current(open); + closureHandler.current(open); } function handleKeyPress(event) { @@ -105,7 +106,7 @@ export default function EventSearchList(props: IProps) { else if (index == data.length - 1) props.selectEvent(data[0].EventID); else - props.selectEvent( data[index + 1].EventID); + props.selectEvent(data[index + 1].EventID); } else if (event.keyCode == 38) // arrow up key @@ -124,7 +125,6 @@ export default function EventSearchList(props: IProps) { } function setScrollBar() { - const rowHeight = $(ReactDOM.findDOMNode(ref.current)).find('tbody').children()[0].clientHeight; const index = data.map(a => a.EventID.toString()).indexOf(props.eventid.toString()); const tableHeight = data.length * rowHeight; @@ -135,86 +135,92 @@ export default function EventSearchList(props: IProps) { const sectionIndex = Math.floor(index / rowsPerSection); const scrollTop = $(ReactDOM.findDOMNode(ref.current)).find('tbody').scrollTop(); - if(scrollTop <= sectionIndex * tableSectionHeight || scrollTop >= (sectionIndex + 1) * tableSectionHeight - tableSectionHeight/2) + if (scrollTop <= sectionIndex * tableSectionHeight || scrollTop >= (sectionIndex + 1) * tableSectionHeight - tableSectionHeight / 2) $(ReactDOM.findDOMNode(ref.current)).find('tbody').scrollTop(sectionIndex * tableSectionHeight); } function ProcessWhitespace(txt: string | number): React.ReactNode { if (txt == null) - return <>N/A + return <>N/A; const lines = txt.toString().split("
    "); - return lines.map((item, index) => { - if (index == 0) - return <> {item} - return <>
    {item} + return lines.map((item, i) => { + if (i == 0) + return
    {item}
    ; + return <>
    {item} ; }) } return ( <> -
    - {status == 'loading' ?
    - -
    : null} - {cols.length > 0 ? < ConfigTable.Table - LocalStorageKey="SEbrowser.EventSearch.TableCols" - TableClass="table table-hover" - Data={data} - SortKey={sortField} - Ascending={ascending} - TheadStyle={{ fontSize: 'smaller', display: 'table', tableLayout: 'fixed', width: '100%', height: 60 }} - TbodyStyle={{ display: 'block', overflowY: 'scroll', maxHeight: props.height - hCounter - 60 }} - RowStyle={{ display: 'table', tableLayout: 'fixed', width: 'calc(100%)' }} - TableStyle={{ marginBottom: 0 }} - Selected={(item) => { - if (item.EventID == props.eventid) return true; - else return false; - }} - KeySelector={(item) => (item.EventID.toString() + '-' + item.DisturbanceID)} - OnSort={(d) => { - if (d.colKey == sortField) dispatch(Sort({ Ascending: ascending, SortField: sortField })); - else dispatch(Sort({ Ascending: true, SortField: d.colKey })); - }} - OnClick={(item) => props.selectEvent(item.row.EventID)} - SettingsPortal={'TableSettings'} - OnSettingsChange={closeSettings} - > - - Key={'Time'} - AllowSort={true} - Content={({ item, field }) => ProcessWhitespace(item[field])} - Field={'Time'} - > - Time - - {...cols.map(c => ( - - - Key={c.key} - AllowSort={true} - Field={c.label} - Content={({ item, field }) => ProcessWhitespace(item[field])} - > - {c.label} - - - ))} - : null} - {status == 'loading' ? null : - data.length == numberResults ? +
    + {status == 'loading' ? +
    + +
    + : null} + {cols.length > 0 ? + + LocalStorageKey="SEbrowser.EventSearch.TableCols" + TableClass="table table-hover" + Data={data} + SortKey={sortField} + Ascending={ascending} + TheadStyle={{ fontSize: 'smaller', display: 'table', tableLayout: 'fixed', width: '100%', height: 60 }} + TbodyStyle={{ display: 'block', overflowY: 'scroll', maxHeight: props.height - hCounter - 60 }} + RowStyle={{ display: 'table', tableLayout: 'fixed', width: 'calc(100%)' }} + TableStyle={{ marginBottom: 0 }} + KeySelector={(item) => (item.EventID.toString() + '-' + item.DisturbanceID)} + OnClick={(item) => props.selectEvent(item.row.EventID)} + SettingsPortal={'TableSettings'} + OnSettingsChange={closeSettings} + Selected={(item) => { + if (item.EventID == props.eventid) return true; + else return false; + }} + OnSort={(d) => { + if (d.colKey == sortField) dispatch(Sort({ Ascending: ascending, SortField: sortField })); + else dispatch(Sort({ Ascending: true, SortField: d.colKey })); + }} + > + + Key={'Time'} + AllowSort={true} + Content={({ item, field }) => ProcessWhitespace(item[field])} + Field={'Time'} + > + Time + + {...cols.map((c, i) => ( + + + Key={c.key} + AllowSort={true} + Field={c.label} + Content={({ item, field }) => ProcessWhitespace(item[field])} + > + {c.label} + + + ))} + + : null} + {status == 'loading' ? + null + : data.length == numberResults ?
    - Only the first {data.length} results are shown (sorted {(ascending? 'ascending' : 'descending')} by {sortField}) - please narrow your search or increase the number of results in the application settings. -
    : -
    - {data.length} results -
    } + Only the first {data.length} results are shown (sorted {(ascending ? 'ascending' : 'descending')} by {sortField}) - please narrow your search or increase the number of results in the application settings. +
    + :
    {data.length} results
    + }
    { closureHandler.current = s; }} HideHandle={true}> -
    - -
    +
    ); diff --git a/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchMagDur.tsx b/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchMagDur.tsx index f410f187c..dfa8f8886 100644 --- a/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchMagDur.tsx +++ b/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchMagDur.tsx @@ -24,12 +24,12 @@ import * as React from 'react'; import { SelectEventSearchsStatus, FetchEventSearches, SelectEventSearchs, SelectCharacteristicFilter, SelectEventSearchsSortField, SelectEventSearchsAscending, Sort } from './EventSearchSlice'; import { useAppDispatch, useAppSelector } from '../../hooks'; -import { Redux } from '../../global'; +import { Redux } from '../../global'; import { MagDurCurveSlice } from '../../Store'; -import { Line, Plot, Circle, AggregatingCircles } from '@gpa-gemstone/react-graph'; -import { SelectEventSearchSettings, SelectGeneralSettings } from '../SettingsSlice'; -import { OverlayDrawer } from '@gpa-gemstone/react-interactive'; -import Table, { Column } from '@gpa-gemstone/react-table'; +import { Line, Plot, Circle, AggregatingCircles } from '@gpa-gemstone/react-graph'; +import { SelectEventSearchSettings, SelectGeneralSettings } from '../SettingsSlice'; +import { OverlayDrawer } from '@gpa-gemstone/react-interactive'; +import Table, { Column } from '@gpa-gemstone/react-table'; import { OpenXDA } from '@gpa-gemstone/application-typings' interface IProps { @@ -41,36 +41,39 @@ interface IProps { const MagDurChart = (props: IProps) => { const chart = React.useRef(null); const count = React.useRef(null); - const empty = React.useCallback(() => {/*Do Nothing*/ }, []); + + const empty = React.useCallback(() => {/*Do Nothing*/ }, []); + + const dispatch = useAppDispatch(); + + const status = useAppSelector(SelectEventSearchsStatus); + const points: any[] = useAppSelector(SelectEventSearchs); + const settings = useAppSelector(SelectEventSearchSettings); const magDurStatus = useAppSelector(MagDurCurveSlice.Status); const magDurCurves = useAppSelector(MagDurCurveSlice.Data) as OpenXDA.Types.MagDurCurve[]; - const [currentCurve, setCurrentCurve] = React.useState(null) - const numberResults = useAppSelector((state: Redux.StoreState) => SelectEventSearchSettings(state).NumberResults) + const numberResults = useAppSelector((state: Redux.StoreState) => SelectEventSearchSettings(state).NumberResults) + const selectedCurve = useAppSelector((state: Redux.StoreState) => SelectCharacteristicFilter(state).curveID); const generalSettings: Redux.IGeneralSettings = useAppSelector(SelectGeneralSettings); - const [width, setWidth] = React.useState(0); + const showSelectedCurve = useAppSelector((state: Redux.StoreState) => SelectCharacteristicFilter(state).curveInside != SelectCharacteristicFilter(state).curveOutside); + const [x, setX] = React.useState(false); - const [hCounter, setHCounter] = React.useState(0); - const dispatch = useAppDispatch(); - const status = useAppSelector(SelectEventSearchsStatus); - const points: any[] = useAppSelector(SelectEventSearchs); const [data, setData] = React.useState([]); + const [width, setWidth] = React.useState(0); + const [hCounter, setHCounter] = React.useState(0); const [selectedMag, setSelectedMag] = React.useState(0); const [selectedDur, setSelectedDur] = React.useState(0); - const settings = useAppSelector(SelectEventSearchSettings); - const selectedCurve = useAppSelector((state: Redux.StoreState) => SelectCharacteristicFilter(state).curveID); - const showSelectedCurve = useAppSelector((state: Redux.StoreState) => SelectCharacteristicFilter(state).curveInside != SelectCharacteristicFilter(state).curveOutside); + const [currentCurve, setCurrentCurve] = React.useState(null) + // This needs to be used instead of a Layout effect since a Layout Effect would not get triggered since nothing is redrawn when + // size of the parent div changes. + React.useEffect(() => { + setWidth(chart?.current?.offsetWidth ?? 0) + + const h = setTimeout(() => { + setX((a) => !a) + }, 500); + + return () => { if (h !== null) clearTimeout(h); }; - // This needs to be used instead of a Layout effect since a Layout Effect would not get triggered since nothing is redrawn when - // size of the parent div changes. - React.useEffect(() => { - setWidth(chart?.current?.offsetWidth ?? 0) - - const h = setTimeout(() => { - setX((a) => !a) - }, 500); - - return () => { if (h !== null) clearTimeout(h); }; - }, [x]) React.useLayoutEffect(() => { @@ -95,65 +98,65 @@ const MagDurChart = (props: IProps) => { }, [magDurCurves]); React.useEffect(() => { - setData(points.filter(p => p['EventID'] != props.EventID).map((p) => ({ - data: [p['MagDurDuration'], p['MagDurMagnitude']], - color: 'red', - radius: 5, - onClick: () => { - props.SelectEvent(p['EventID'] as number) - setSelectedDur(0); - setSelectedMag(0); - } + setData(points.filter(p => p['EventID'] != props.EventID).map((p) => ({ + data: [p['MagDurDuration'], p['MagDurMagnitude']], + color: 'red', + radius: 5, + onClick: () => { + props.SelectEvent(p['EventID'] as number) + setSelectedDur(0); + setSelectedMag(0); + } }))) }, [points]) function generateCurve(curve: OpenXDA.Types.MagDurCurve) { - const pt = curve.Area.split(','); - const cu = pt.map(point => { const s = point.trim().split(" "); return [parseFloat(s[0]), parseFloat(s[1])] as [number, number]; }) - return cu; + const pt = curve.Area.split(','); + const cu = pt.map(point => { const s = point.trim().split(" "); return [parseFloat(s[0]), parseFloat(s[1])] as [number, number]; }) + return cu; } function AggregateCurves(d, { XTransformation, YTransformation, YInverseTransformation, XInverseTransformation }) { - const xmax = Math.max(...d.map(c => XTransformation(c.data[0]))) + 5; - const ymax = Math.max(...d.map(c => YTransformation(c.data[1]))) + 5; - const xmin = Math.min(...d.map(c => XTransformation(c.data[0]))) - 5; - const ymin = Math.min(...d.map(c => YTransformation(c.data[1]))) - 5; - const xcenter = 0.5 * (xmax + xmin); - const ycenter = 0.5 * (ymax + ymin); - const r = Math.max(Math.abs(xmax - xcenter), Math.abs(xmin - xcenter), Math.abs(ymax - ycenter), Math.abs(ymin - ycenter)) - let handler = ({ setTDomain, setYDomain }) => { - setTDomain([XInverseTransformation(xmin), XInverseTransformation(xmax)]); - setYDomain([YInverseTransformation(ymax), YInverseTransformation(ymin)]); - setSelectedDur(0); - setSelectedMag(0); - }; - if (Math.abs(xmin - xmax) < 11 && Math.abs(ymin - ymax) < 11) - handler = () => { - setSelectedDur(d[0].data[0]); - setSelectedMag(d[0].data[1]); - } - return { - data: [XInverseTransformation(xcenter), YInverseTransformation(ycenter)] as [number, number], - color: 'rgb(108, 117, 125)', - borderColor: 'black', - borderThickness: 2, - text: d.length.toString(), - radius: r, - opacity: 0.5, - onClick: handler - }; + const xmax = Math.max(...d.map(c => XTransformation(c.data[0]))) + 5; + const ymax = Math.max(...d.map(c => YTransformation(c.data[1]))) + 5; + const xmin = Math.min(...d.map(c => XTransformation(c.data[0]))) - 5; + const ymin = Math.min(...d.map(c => YTransformation(c.data[1]))) - 5; + const xcenter = 0.5 * (xmax + xmin); + const ycenter = 0.5 * (ymax + ymin); + const r = Math.max(Math.abs(xmax - xcenter), Math.abs(xmin - xcenter), Math.abs(ymax - ycenter), Math.abs(ymin - ycenter)) + let handler = ({ setTDomain, setYDomain }) => { + setTDomain([XInverseTransformation(xmin), XInverseTransformation(xmax)]); + setYDomain([YInverseTransformation(ymax), YInverseTransformation(ymin)]); + setSelectedDur(0); + setSelectedMag(0); + }; + if (Math.abs(xmin - xmax) < 11 && Math.abs(ymin - ymax) < 11) + handler = () => { + setSelectedDur(d[0].data[0]); + setSelectedMag(d[0].data[1]); + } + return { + data: [XInverseTransformation(xcenter), YInverseTransformation(ycenter)] as [number, number], + color: 'rgb(108, 117, 125)', + borderColor: 'black', + borderThickness: 2, + text: d.length.toString(), + radius: r, + opacity: 0.5, + onClick: handler + }; } function CanAggregate(d1, d2, { XTransformation, YTransformation }) { - const dx = XTransformation(d1.data[0]) - XTransformation(d2.data[0]); - const dy = YTransformation(d1.data[1]) - YTransformation(d2.data[1]); - const r = d1.radius + d2.radius; - return (Math.pow(dx, 2) + Math.pow(dy, 2) < Math.pow(r, 2)); + const dx = XTransformation(d1.data[0]) - XTransformation(d2.data[0]); + const dy = YTransformation(d1.data[1]) - YTransformation(d2.data[1]); + const r = d1.radius + d2.radius; + return (Math.pow(dx, 2) + Math.pow(dy, 2) < Math.pow(r, 2)); } function IsSame(d1, d2) { - return Math.abs(d1.data[0] - d2.data[0]) < 0.0001 && Math.abs(d1.data[1] - d2.data[1]) < 1E-10; + return Math.abs(d1.data[0] - d2.data[0]) < 0.0001 && Math.abs(d1.data[1] - d2.data[1]) < 1E-10; } const plotContent = React.useMemo(() => { @@ -166,15 +169,15 @@ const MagDurChart = (props: IProps) => { legend={s.Name} key={i} width={showSelectedCurve && selectedCurve == s.ID ? 9 : 3} />), - , - ...points.filter(e => e['EventID'] == props.EventID).map((p) => e['EventID'] == props.EventID).map((p) => ) - ] + ] }, [magDurCurves, points, props.EventID, data, settings.AggregateMagDur, showSelectedCurve, selectedCurve]) return ( @@ -218,10 +221,10 @@ interface IEventListProps { Duration: number; Height: number; Width: number; -} +} const EventList = (props: IEventListProps) => { - const closureHandler = React.useRef<((o: boolean) => void)>(() => {/*Do Nothing*/ }); + const closureHandler = React.useRef<((o: boolean) => void)>(() => {/*Do Nothing*/ }); const dataFilter = React.useCallback((state: Redux.StoreState) => SelectEventSearchs(state).filter(p => Math.abs(p['MagDurDuration'] - props.Duration) < 1E-10 && Math.abs(p['MagDurMagnitude'] - props.Magnitude) < 0.0001), @@ -246,7 +249,7 @@ const EventList = (props: IEventListProps) => { function ProcessWhitespace(txt: string | number): React.ReactNode { if (txt == null) return <>N/A - const lines = txt.toString().split("
    "); + const lines = txt.toString().split("
    "); return lines.map((item, index) => { if (index == 0) return <> {item} @@ -255,16 +258,16 @@ const EventList = (props: IEventListProps) => { } function LoadColumns() { - let c = [{ field: "Time", key: "Time", label: "Time", content: (item, key, fld) => ProcessWhitespace(item[fld]) }]; + let c = [{ field: "Time", key: "Time", label: "Time", content: (item, key, fld) => ProcessWhitespace(item[fld]) }]; const flds = Object.keys(data[0]).filter(item => item != "Time" && item != "DisturbanceID" && item != "EventID" && item != "EventID1" && item != 'MagDurDuration' && item != 'MagDurMagnitude').sort(); let keys = []; - const currentState = localStorage.getItem('SEbrowser.EventSearch.TableCols'); - if (currentState !== null) + const currentState = localStorage.getItem('SEbrowser.EventSearch.TableCols'); + if (currentState !== null) keys = currentState.split(","); - c = c.concat(flds.filter(f => keys.includes(f)).map(f => ({ field: f, key: f, label: f, content: (item, key, fld) => ProcessWhitespace(item[fld]) }))); + c = c.concat(flds.filter(f => keys.includes(f)).map(f => ({ field: f, key: f, label: f, content: (item, key, fld) => ProcessWhitespace(item[fld]) }))); - setCollumns(c); + setCollumns(c); } return { closureHandler.current = s; }} HideHandle={true}> diff --git a/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchNavbar.tsx b/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchNavbar.tsx index de22139b9..009ae98dc 100644 --- a/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchNavbar.tsx +++ b/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchNavbar.tsx @@ -24,20 +24,16 @@ //****************************************************************************************************** import React from 'react'; import _ from 'lodash'; -import ReportTimeFilter from '../ReportTimeFilter'; import { useAppDispatch, useAppSelector } from '../../hooks'; import { SelectAssetGroupList, SelectAssetList, SelectCharacteristicFilter, SelectMeterList, SelectReset, SelectStationList, SelectTimeFilter, SelectTypeFilter, SetFilterLists } from './EventSearchSlice'; -import { ResetFilters, SetFilters } from './EventSearchSlice'; +import { ResetFilters, SetFilters } from './EventSearchSlice'; import { AssetGroupSlice, AssetSlice, LocationSlice, MeterSlice, MagDurCurveSlice, EventTypeSlice } from '../../Store'; -import { DefaultSelects } from '@gpa-gemstone/common-pages'; +import { DefaultSelects, TimeFilter, EventTypeFilter, EventCharacteristicFilter } from '@gpa-gemstone/common-pages'; import NavbarFilterButton from '../Common/NavbarFilterButton'; import { SystemCenter, OpenXDA } from '@gpa-gemstone/application-typings'; -import { Input, Select, MultiCheckBoxSelect } from '@gpa-gemstone/react-forms'; import { Search } from '@gpa-gemstone/react-interactive'; -import { SEBrowser } from '../../Global'; -import EventSearchTypeFilters from './EventSearchTypeFilter'; -import { SelectDateTimeSetting, SelectTimeZone } from '../SettingsSlice'; -import { getMoment, getStartEndTime, readableUnit } from './TimeWindowUtils'; +import { SelectDateTimeFormat, SelectDateTimeSetting, SelectTimeZone } from '../SettingsSlice'; +import moment from 'moment'; interface IProps { toggleVis: () => void, @@ -45,55 +41,36 @@ interface IProps { setHeight: (h: number) => void } -const momentDateTimeFormat = "MM/DD/YYYY HH:mm:ss.SSS"; +type TimeUnit = 'y' | 'M' | 'w' | 'd' | 'h' | 'm' | 's' | 'ms' +const units = ['ms', 's', 'm', 'h', 'd', 'w', 'M', 'y'] as TimeUnit[] const EventSearchNavbar = (props: IProps) => { const navRef = React.useRef(null); const dispatch = useAppDispatch(); - const eventCharacteristicFilter = useAppSelector(SelectCharacteristicFilter); - const timeFilter = useAppSelector(SelectTimeFilter); - const eventTypeFilter = useAppSelector(SelectTypeFilter); - const magDurStatus = useAppSelector(MagDurCurveSlice.Status); - const magDurCurves = useAppSelector(MagDurCurveSlice.Data); - const timeZone = useAppSelector(SelectTimeZone); - - const assetGroupList = useAppSelector(SelectAssetGroupList); + const reset = useAppSelector(SelectReset); + const timeZone = useAppSelector(SelectTimeZone); const meterList = useAppSelector(SelectMeterList); const assetList = useAppSelector(SelectAssetList); - const locationList = useAppSelector(SelectStationList); - + const timeFilter = useAppSelector(SelectTimeFilter); const eventTypes = useAppSelector(EventTypeSlice.Data); + const magDurStatus = useAppSelector(MagDurCurveSlice.Status); + const magDurCurves = useAppSelector(MagDurCurveSlice.Data); + const locationList = useAppSelector(SelectStationList); const evtTypeStatus = useAppSelector(EventTypeSlice.Status); - - const reset = useAppSelector(SelectReset); + const assetGroupList = useAppSelector(SelectAssetGroupList); + const eventTypeFilter = useAppSelector(SelectTypeFilter); + const dateTimeSetting = useAppSelector(SelectDateTimeSetting); + const dateTimeFormat = useAppSelector(SelectDateTimeFormat); + const eventCharacteristicFilter = useAppSelector(SelectCharacteristicFilter); const [height, setHeight] = React.useState(0); - const [showFilter, setFilter] = React.useState<('None' | 'Meter' | 'Asset' | 'AssetGroup' | 'Station')>('None'); - const [newEventCharacteristicFilter, setNewEventCharacteristicFilter] = React.useState(null); - const [newTypeFilter, setNewTypeFilter] = React.useState(null); - const lineNeutralOptions = [{ Value: 'LL', Label: 'LL' }, { Value: 'LN', Label: 'LN' }, { Value: 'both', Label: 'LL/LN' }]; - const dateTimeSetting = useAppSelector(SelectDateTimeSetting) - - React.useLayoutEffect(() => setHeight(navRef?.current?.offsetHeight ?? 0)) - React.useEffect(() => props.setHeight(height), [height]) - - React.useEffect(() => { setNewTypeFilter(eventTypeFilter) }, [eventTypeFilter]) - React.useEffect(() => { setNewEventCharacteristicFilter(eventCharacteristicFilter) }, [eventCharacteristicFilter]) - - const [newPhases, setNewPhases] = React.useState<{ Value: number, Text: string, Selected: boolean }[]>([]); - const [timeRange, setTimeRange] = React.useState(''); - React.useEffect(() => { - setNewEventCharacteristicFilter(eventCharacteristicFilter); - setNewTypeFilter(eventTypeFilter); - const setupPhases: { Value: number, Text: string, Selected: boolean }[] = []; - Object.keys(eventCharacteristicFilter.phases).forEach((key, index) => setupPhases.push({ Value: index, Text: key, Selected: eventCharacteristicFilter.phases[key] })); - setNewPhases(setupPhases); - }, []); + React.useLayoutEffect(() => setHeight(navRef?.current?.offsetHeight ?? 0)); + React.useEffect(() => props.setHeight(height), [height]); React.useEffect(() => { if (magDurStatus == 'changed' || magDurStatus == 'unintiated') @@ -106,41 +83,68 @@ const EventSearchNavbar = (props: IProps) => { }, [evtTypeStatus]); React.useEffect(() => { + let range = ""; + const startMoment = moment(timeFilter.start, dateTimeFormat); + const endMoment = moment(timeFilter.end, dateTimeFormat); + const unit = findAppropriateUnit(startMoment, endMoment); - const characteristics = validEventCharacteristicsFilter() ? newEventCharacteristicFilter : undefined; - - dispatch(SetFilters({ - characteristics, - types: newTypeFilter - })); - }, [newEventCharacteristicFilter, newTypeFilter]); + if (dateTimeSetting == 'startEnd') { + range = `${timeFilter.start} to ${timeFilter.end} (${timeZone})`; + } + else if (dateTimeSetting == 'startWindow') { + const startEndDifference = endMoment.diff(startMoment, unit); + range = `${timeFilter.start} (${timeZone}) ${startEndDifference} ${findDisplayUnit(unit)}`; + } + else if (dateTimeSetting == 'endWindow') { + const startEndDifference = startMoment.diff(endMoment, unit); + range = `${timeFilter.end} (${timeZone}) ${startEndDifference} ${findDisplayUnit(unit)}`; + } - React.useEffect(() => { - let r = ""; - const center = getMoment(timeFilter.date, timeFilter.time); - const [start, end] = getStartEndTime(center, timeFilter.windowSize, timeFilter.timeWindowUnits); - - if (dateTimeSetting == 'startEnd') - r = `${start.format(momentDateTimeFormat)}`; - if (dateTimeSetting == 'startWindow') - r = `${start.format(momentDateTimeFormat)} (${timeZone})`; - else if (dateTimeSetting == 'endWindow') - r = `${end.format(momentDateTimeFormat)} (${timeZone})`; - else if (dateTimeSetting == 'center') - r = `${center.format(momentDateTimeFormat)} (${timeZone})`; - - if (dateTimeSetting == 'startEnd') - r += ` to ${end.format(momentDateTimeFormat)} (${timeZone})`; - else if (dateTimeSetting == 'center') - r += ` +/- ${timeFilter.windowSize} ${readableUnit(timeFilter.timeWindowUnits)}`; - else if (dateTimeSetting == 'startWindow') - r += ` + ${2*timeFilter.windowSize} ${readableUnit(timeFilter.timeWindowUnits)}`; - else if (dateTimeSetting == 'endWindow') - r += ` - ${2*timeFilter.windowSize} ${readableUnit(timeFilter.timeWindowUnits)}`; - - setTimeRange(r); + setTimeRange(range); }, [timeFilter, dateTimeSetting, timeZone]) - + + function findAppropriateUnit(startTime: moment.Moment, endTime: moment.Moment): TimeUnit { + const unitIndex = 7; + let diff = endTime.diff(startTime, units[unitIndex], true); + + for (let i = unitIndex; i >= 1; i--) { + if (Number.isInteger(diff)) { + return units[i]; + } + const nextI = i - 1; + + diff = endTime.diff(startTime, units[nextI], true); + + if (diff > 65000) { + diff = endTime.diff(startTime, units[i], true); + return units[i]; + } + } + + return units[0]; + } + + function findDisplayUnit(unit): string { + switch (unit) { + case 'ms': + return 'miliseconds'; + case 's': + return 'seconds'; + case 'm': + return 'minutes'; + case 'h': + return 'hours'; + case 'd': + return 'days'; + case 'w': + return 'weeks'; + case 'M': + return 'months'; + case 'y': + return 'years'; + } + } + function getEnum(setOptions, field) { let handle = null; if (field.type != 'enum' || field.enum == undefined || field.enum.length != 1) @@ -219,78 +223,7 @@ const EventSearchNavbar = (props: IProps) => { }; } - function validEventCharacteristicsFilter() { - let valid = newEventCharacteristicFilter != null; - - if (!valid) - return valid; - - valid = valid && validMinMax('durationMin'); - valid = valid && validMinMax('durationMax'); - - valid = valid && validMinMax('sagMin'); - valid = valid && validMinMax('sagMax'); - - valid = valid && validMinMax('swellMin'); - valid = valid && validMinMax('swellMax'); - - valid = valid && validMinMax('transientMin'); - valid = valid && validMinMax('transientMax'); - - return valid; - } - - function NullOrNaN(val) { - return val == null || isNaN(val); - } - - function validMinMax(field: keyof SEBrowser.IEventCharacteristicFilters) { - if (field == 'durationMin') - return NullOrNaN(newEventCharacteristicFilter.durationMin) || ( - newEventCharacteristicFilter.durationMin >= 0 && newEventCharacteristicFilter.durationMin < 100 && - (NullOrNaN(newEventCharacteristicFilter.durationMax) || - newEventCharacteristicFilter.durationMax >= newEventCharacteristicFilter.durationMin)) - if (field == 'durationMax') - return NullOrNaN(newEventCharacteristicFilter.durationMax) || ( - newEventCharacteristicFilter.durationMax >= 0 && newEventCharacteristicFilter.durationMax < 100 && - (NullOrNaN(newEventCharacteristicFilter.durationMin) || - newEventCharacteristicFilter.durationMax >= newEventCharacteristicFilter.durationMin)) - if (field == 'sagMin') - return NullOrNaN(newEventCharacteristicFilter.sagMin) || ( - newEventCharacteristicFilter.sagMin >= 0 && newEventCharacteristicFilter.sagMin < 1 && - (NullOrNaN(newEventCharacteristicFilter.sagMax) || - newEventCharacteristicFilter.sagMax >= newEventCharacteristicFilter.sagMin)) - if (field == 'sagMax') - return NullOrNaN(newEventCharacteristicFilter.sagMax) || ( - newEventCharacteristicFilter.sagMax >= 0 && newEventCharacteristicFilter.sagMax < 1 && - (NullOrNaN(newEventCharacteristicFilter.sagMax) || - newEventCharacteristicFilter.sagMax >= newEventCharacteristicFilter.sagMax)) - if (field == 'swellMin') - return NullOrNaN(newEventCharacteristicFilter.swellMin) || ( - newEventCharacteristicFilter.swellMin >= 1 && newEventCharacteristicFilter.swellMin < 9999 && - (NullOrNaN(newEventCharacteristicFilter.swellMax) || - newEventCharacteristicFilter.swellMax >= newEventCharacteristicFilter.swellMin)) - if (field == 'swellMax') - return NullOrNaN(newEventCharacteristicFilter.swellMax) || ( - newEventCharacteristicFilter.swellMax >= 1 && newEventCharacteristicFilter.swellMax < 9999 && - (NullOrNaN(newEventCharacteristicFilter.swellMin) || - newEventCharacteristicFilter.swellMax >= newEventCharacteristicFilter.swellMin)) - if (field == 'transientMin') - return NullOrNaN(newEventCharacteristicFilter.transientMin) || ( - newEventCharacteristicFilter.transientMin >= 0 && newEventCharacteristicFilter.transientMin < 9999 && - (NullOrNaN(newEventCharacteristicFilter.transientMax) || - newEventCharacteristicFilter.transientMax >= newEventCharacteristicFilter.transientMin)) - if (field == 'transientMax') - return NullOrNaN(newEventCharacteristicFilter.transientMax) || ( - newEventCharacteristicFilter.transientMax >= 0 && newEventCharacteristicFilter.transientMax < 9999 && - (NullOrNaN(newEventCharacteristicFilter.transientMin) || - newEventCharacteristicFilter.transientMax >= newEventCharacteristicFilter.transientMin)) - - - return true; - } - - if (newEventCharacteristicFilter === null || timeFilter === null || newTypeFilter === null) return null; + if (timeFilter === null) { return null; } if (!props.showNav) return ( @@ -308,300 +241,36 @@ const EventSearchNavbar = (props: IProps) => { ); - const sagsSelected = newTypeFilter.find(i => i == eventTypes.find(item => item.Name == 'Sag')?.ID ?? -1) != null; - const swellsSelected = newTypeFilter.find(i => i == eventTypes.find(item => item.Name == 'Swell')?.ID ?? -1) != null; - const transientsSelected = newTypeFilter.find(i => i == eventTypes.find(item => item.Name == 'Transient')?.ID ?? -1) != null; + // Wrapper function to match the expected type for setFilter + const handleSetFilter = (start: string, end: string) => { + dispatch(SetFilters({ + time: { + start: start, + end: end + } + })) + }; return ( <>
    - { - setFilter('None'); - if (conf) - dispatch(SetFilterLists({ Assets: assetList, Groups: assetGroupList, Meters: selected, Stations: locationList })) - } - } - Show={showFilter == 'Meter'} - Type={'multiple'} - Columns={[ - { key: 'AssetKey', field: 'AssetKey', label: 'Key', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'Name', field: 'Name', label: 'Name', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'Location', field: 'Location', label: 'Substation', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'MappedAssets', field: 'MappedAssets', label: 'Assets', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'Make', field: 'Make', label: 'Make', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'Model', field: 'Model', label: 'Model', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'Scroll', label: '', headerStyle: { width: 17, padding: 0 }, rowStyle: { width: 0, padding: 0 } }, - ]} - Title={"Filter by Meter"} - GetEnum={getEnum} - GetAddlFields={getAdditionalMeterFields} /> - { - setFilter('None'); - if (conf) - dispatch(SetFilterLists({ Assets: selected, Groups: assetGroupList, Meters: meterList, Stations: locationList })) - }} - Show={showFilter == 'Asset'} - Type={'multiple'} - Columns={[ - { key: 'AssetKey', field: 'AssetKey', label: 'Key', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'AssetName', field: 'AssetName', label: 'Name', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'AssetType', field: 'AssetType', label: 'Asset Type', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'VoltageKV', field: 'VoltageKV', label: 'Voltage (kV)', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'Meters', field: 'Meters', label: 'Meters', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'Locations', field: 'Locations', label: 'Substations', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } } - ]} - Title={"Filter by Asset"} - GetEnum={getEnum} - GetAddlFields={getAdditionalAssetFields} /> + { + setFilter('None'); + if (conf) + dispatch(SetFilterLists({ Assets: assetList, Groups: assetGroupList, Meters: selected, Stations: locationList })) + } + } + Show={showFilter == 'Meter'} + Type={'multiple'} + Columns={[ + { key: 'AssetKey', field: 'AssetKey', label: 'Key', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, + { key: 'Name', field: 'Name', label: 'Name', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, + { key: 'Location', field: 'Location', label: 'Substation', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, + { key: 'MappedAssets', field: 'MappedAssets', label: 'Assets', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, + { key: 'Make', field: 'Make', label: 'Make', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, + { key: 'Model', field: 'Model', label: 'Model', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, + { key: 'Scroll', label: '', headerStyle: { width: 17, padding: 0 }, rowStyle: { width: 0, padding: 0 } }, + ]} + Title={"Filter by Meter"} + GetEnum={getEnum} + GetAddlFields={getAdditionalMeterFields} /> + { + setFilter('None'); + if (conf) + dispatch(SetFilterLists({ Assets: selected, Groups: assetGroupList, Meters: meterList, Stations: locationList })) + }} + Show={showFilter == 'Asset'} + Type={'multiple'} + Columns={[ + { key: 'AssetKey', field: 'AssetKey', label: 'Key', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, + { key: 'AssetName', field: 'AssetName', label: 'Name', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, + { key: 'AssetType', field: 'AssetType', label: 'Asset Type', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, + { key: 'VoltageKV', field: 'VoltageKV', label: 'Voltage (kV)', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, + { key: 'Meters', field: 'Meters', label: 'Meters', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, + { key: 'Locations', field: 'Locations', label: 'Substations', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } } + ]} + Title={"Filter by Asset"} + GetEnum={getEnum} + GetAddlFields={getAdditionalAssetFields} /> { - setFilter('None'); - if (conf) - dispatch(SetFilterLists({ Assets: assetList, Groups: assetGroupList, Meters: meterList, Stations: selected })) - }} - Show={showFilter == 'Station'} - Type={'multiple'} - Columns={[ - { key: 'Name', field: 'Name', label: 'Name', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'LocationKey', field: 'LocationKey', label: 'Key', headerStyle: { width: '30%' }, rowStyle: { width: '30%' } }, - //{ key: 'Type', field: 'Type', label: 'Type', headerStyle: { width: '10%' }, rowStyle: { width: '10%' } }, - { key: 'Meters', field: 'Meters', label: 'Meters', headerStyle: { width: '10%' }, rowStyle: { width: '10%' } }, - { key: 'Assets', field: 'Assets', label: 'Assets', headerStyle: { width: '10%' }, rowStyle: { width: '10%' } }, - { key: 'Scroll', label: '', headerStyle: { width: 17, padding: 0 }, rowStyle: { width: 0, padding: 0 } }, - ]} - Title={"Filter by Substation"} - GetEnum={getEnum} - GetAddlFields={() => { return () => {/*Do Nothing*/} }} /> - { - setFilter('None'); - if (conf) - dispatch(SetFilterLists({ Assets: assetList, Groups: selected, Meters: meterList, Stations: locationList })) - }} - Show={showFilter == 'AssetGroup'} - Type={'multiple'} - Columns={[ - { key: 'Name', field: 'Name', label: 'Name', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'Assets', field: 'Assets', label: 'Assets', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'Meters', field: 'Meters', label: 'Meters', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'Users', field: 'Users', label: 'Users', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'AssetGroups', field: 'AssetGroups', label: 'SubGroups', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'Scroll', label: '', headerStyle: { width: 17, padding: 0 }, rowStyle: { width: 0, padding: 0 } }, - ]} - Title={"Filter by Asset Group"} - GetEnum={getEnum} - GetAddlFields={() => { return () => {/*Do Nothing*/ } }} /> - + Selection={locationList} + OnClose={(selected, conf) => { + setFilter('None'); + if (conf) + dispatch(SetFilterLists({ Assets: assetList, Groups: assetGroupList, Meters: meterList, Stations: selected })) + }} + Show={showFilter == 'Station'} + Type={'multiple'} + Columns={[ + { key: 'Name', field: 'Name', label: 'Name', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, + { key: 'LocationKey', field: 'LocationKey', label: 'Key', headerStyle: { width: '30%' }, rowStyle: { width: '30%' } }, + //{ key: 'Type', field: 'Type', label: 'Type', headerStyle: { width: '10%' }, rowStyle: { width: '10%' } }, + { key: 'Meters', field: 'Meters', label: 'Meters', headerStyle: { width: '10%' }, rowStyle: { width: '10%' } }, + { key: 'Assets', field: 'Assets', label: 'Assets', headerStyle: { width: '10%' }, rowStyle: { width: '10%' } }, + { key: 'Scroll', label: '', headerStyle: { width: 17, padding: 0 }, rowStyle: { width: 0, padding: 0 } }, + ]} + Title={"Filter by Substation"} + GetEnum={getEnum} + GetAddlFields={() => { return () => {/*Do Nothing*/ } }} /> + { + setFilter('None'); + if (conf) + dispatch(SetFilterLists({ Assets: assetList, Groups: selected, Meters: meterList, Stations: locationList })) + }} + Show={showFilter == 'AssetGroup'} + Type={'multiple'} + Columns={[ + { key: 'Name', field: 'Name', label: 'Name', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, + { key: 'Assets', field: 'Assets', label: 'Assets', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, + { key: 'Meters', field: 'Meters', label: 'Meters', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, + { key: 'Users', field: 'Users', label: 'Users', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, + { key: 'AssetGroups', field: 'AssetGroups', label: 'SubGroups', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, + { key: 'Scroll', label: '', headerStyle: { width: 17, padding: 0 }, rowStyle: { width: 0, padding: 0 } }, + ]} + Title={"Filter by Asset Group"} + GetEnum={getEnum} + GetAddlFields={() => { return () => {/*Do Nothing*/ } }} /> + ); } diff --git a/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchPreview/EventSearchPreviewPane.tsx b/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchPreview/EventSearchPreviewPane.tsx index 4e3d75a10..53cf0d7ec 100644 --- a/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchPreview/EventSearchPreviewPane.tsx +++ b/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchPreview/EventSearchPreviewPane.tsx @@ -23,7 +23,7 @@ // //****************************************************************************************************** import React from 'react'; -import { Redux, SEBrowser } from '../../../global' +import { Redux } from '../../../global' import { useAppSelector } from '../../../hooks'; import { SelectEventSearchByID } from './../EventSearchSlice'; import { SelectWidgetCategories } from '../../SettingsSlice'; @@ -35,10 +35,6 @@ interface IProps { EventID: number, InitialTab?: string, Height: number, - Date?: string, - Time?: string, - TimeWindowUnits?: number, - WindowSize?: number } const widgetStore = { @@ -119,6 +115,7 @@ export default function EventPreviewPane(props: IProps) { /> })} - ) + + ) } diff --git a/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchSlice.ts b/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchSlice.ts index 3629bf21e..70e39c0ea 100644 --- a/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchSlice.ts +++ b/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchSlice.ts @@ -29,9 +29,8 @@ import moment from 'moment'; import queryString from 'querystring'; import { AssetGroupSlice, AssetSlice, EventTypeSlice, LocationSlice, MeterSlice } from '../../Store'; import { SystemCenter, OpenXDA } from '@gpa-gemstone/application-typings'; -import { findAppropriateUnit, getStartEndTime, getMoment } from './TimeWindowUtils'; -const momentDateFormat = "MM/DD/YYYY"; +const dateTimeFormat = 'MM/DD/yyyy HH:mm:ss.SSS'; let fetchHandle: JQuery.jqXHR | null = null; @@ -48,15 +47,11 @@ export const FetchEventSearches = createAsyncThunk('EventSearchs/FetchEventSearc const sortKey = (getState() as Redux.StoreState).EventSearch.SortField; const ascending = (getState() as Redux.StoreState).EventSearch.Ascending; - const adjustedTime = findAppropriateUnit(getMoment(time.date, time.time), - getStartEndTime(getMoment(time.date, time.time), time.windowSize, time.timeWindowUnits)[1], - time.timeWindowUnits); - - const filter = { - date: time.date, time: time.time, windowSize: adjustedTime[1], timeWindowUnits: adjustedTime[0], + start: time.start, end: time.end, typeIDs: types, - durationMin: characteristics.durationMin ?? 0, durationMax: characteristics.durationMax ?? 0, + durationMin: characteristics.durationMin ?? 0, + durationMax: characteristics.durationMax ?? 0, phases: { AN: characteristics.phases.AN, BN: characteristics.phases.BN, CN: characteristics.phases.CN, AB: characteristics.phases.AB, BC: characteristics.phases.BC, CA: characteristics.phases.CA, ABG: characteristics.phases.ABG, BCG: characteristics.phases.BCG, ABC: characteristics.phases.ABC, ABCG: characteristics.phases.ABCG, @@ -68,7 +63,7 @@ export const FetchEventSearches = createAsyncThunk('EventSearchs/FetchEventSearc meterIDs: meterList.map(item => item.ID), assetIDs: assetList.map(item => item.ID), groupIDs: groupList.map(item => item.ID), locationIDs: locationList.map(item => item.ID), numberResults: settings.NumberResults, - } + } const additionalArguments = { numberResults: settings.NumberResults, @@ -95,7 +90,7 @@ export const Sort = createAsyncThunk('EventSearchs/Sort', async (arg: { SortFiel return dispatch(FetchEventSearches()); }) -export const ProcessQuery = createAsyncThunk('EventSearchs/ProcessQuery', async (query: queryString.ParsedUrlQuery , { dispatch, getState }) => { +export const ProcessQuery = createAsyncThunk('EventSearchs/ProcessQuery', async (query: queryString.ParsedUrlQuery, { dispatch, getState }) => { let state = getState() as Redux.StoreState; if (state.Asset.Status == 'unintiated') await dispatch(AssetSlice.Fetch()); @@ -119,7 +114,7 @@ export const ProcessQuery = createAsyncThunk('EventSearchs/ProcessQuery', async export const ResetFilters = createAsyncThunk('EventSearchs/ResetFilterThunk', async (_: void, { dispatch, getState }) => { let state = getState() as Redux.StoreState; - + if (state.EventType.Status == 'unintiated') await dispatch(EventTypeSlice.Fetch()); state = getState() as Redux.StoreState; @@ -172,10 +167,8 @@ export const EventSearchsSlice = createSlice({ curveID: 1, curveInside: true, curveOutside: true }, TimeRange: { - date: moment.utc().subtract(84,'h').format(momentDateFormat), - time: '12:00:00.000', - windowSize: 84, - timeWindowUnits: 3 + start: moment.utc().subtract(8, 'hours').format(dateTimeFormat), + end: moment.utc().format(dateTimeFormat), }, EventType: [], isReset: true, @@ -194,12 +187,8 @@ export const EventSearchsSlice = createSlice({ groups: OpenXDA.Types.AssetGroup[], locations: SystemCenter.Types.DetailedLocation[], meters: SystemCenter.Types.DetailedMeter[], typeIDs: OpenXDA.Types.EventType[] }>) => { - - - state.TimeRange.date = action.payload.query['date']?.toString() ?? state.TimeRange.date; - state.TimeRange.time = action.payload.query['time']?.toString() ?? state.TimeRange.time; - state.TimeRange.windowSize = parseFloat(action.payload.query['windowSize']?.toString() ?? state.TimeRange.windowSize.toString()); - state.TimeRange.timeWindowUnits = parseInt(action.payload.query['timeWindowUnits']?.toString() ?? state.TimeRange.timeWindowUnits.toString()); + state.TimeRange.start = action.payload.query['start']?.toString() ?? state.TimeRange.start; + state.TimeRange.end = action.payload.query['end']?.toString() ?? state.TimeRange.end; state.EventType = parseList('types', action.payload.query)?.map(id => action.payload.typeIDs.find(item => item.ID == parseInt(id))).filter(item => item != null).map(t => t.ID) ?? action.payload.typeIDs.filter(item => item.ShowInFilter).map(t => t.ID); @@ -267,7 +256,7 @@ export const EventSearchsSlice = createSlice({ ResetFilters: (state, action: PayloadAction<{ types: OpenXDA.Types.EventType[] }>) => { state.EventCharacteristic = { durationMax: null, durationMin: null, - phases: { AN: true, BN: true, CN: true, AB: true, BC: true, CA: true, ABG: true, BCG: true, ABC: true, ABCG: true }, + phases: { AN: true, BN: true, CN: true, AB: true, BC: true, CA: true, ABG: true, BCG: true, ABC: true, ABCG: true }, transientMin: null, transientMax: null, sagMin: null, sagMax: null, swellMin: null, swellMax: null, sagType: 'both', swellType: 'both', transientType: 'both', curveID: 1, curveInside: true, curveOutside: true }; @@ -321,7 +310,7 @@ export const EventSearchsSlice = createSlice({ state.Ascending = !action.meta.arg.Ascending; else state.SortField = action.meta.arg.SortField; - + }); } @@ -387,44 +376,33 @@ function computeReset(state: Redux.EventSearchState, eventTypes: OpenXDA.Types.E return event && types && state.SelectedAssets.length == 0 && state.SelectedStations.length == 0 && state.SelectedMeters.length == 0 && state.SelectedGroups.length == 0; } -export function GenerateQueryParams(event: SEBrowser.IEventCharacteristicFilters, type: number[], +export function GenerateQueryParams(event: SEBrowser.IEventCharacteristicFilters, type: number[], // ! TODO: not sure this was updated fully time: SEBrowser.IReportTimeFilter, assets: SystemCenter.Types.DetailedAsset[], groups: OpenXDA.Types.AssetGroup[], meters: SystemCenter.Types.DetailedMeter[], stations: SystemCenter.Types.DetailedLocation[], eventID: number = null): any { const result: any = {}; if (assets.length > 0 && assets.length < 100) { - let i = 0; - assets.forEach(a => { + assets.forEach((a, i) => { result["assets" + i] = a.ID; - i = i + 1; }) } if (meters.length > 0 && meters.length < 100) { - let i = 0; - meters.forEach(m => { + meters.forEach((m, i) => { result["meters" + i] = m.ID; - i = i + 1; }) } if (stations.length > 0 && stations.length < 100) { - let i = 0; - stations.forEach(s => { + stations.forEach((s, i) => { result["stations" + i] = s.ID; - i = i + 1; }) } if (groups.length > 0 && groups.length < 100) { - let i = 0; - groups.forEach(ag => { + groups.forEach((ag, i) => { result["groups" + i] = ag.ID; - i = i + 1; }) } - if (type.length > 0 && type.length < 100) { - let i = 0; - type.forEach(ag => { + type.forEach((ag, i) => { result["types" + i] = ag; - i = i + 1; }) } @@ -486,12 +464,8 @@ export function GenerateQueryParams(event: SEBrowser.IEventCharacteristicFilters } if (time != null) { - result["date"] = time.date; - result["time"] = time.time; - console.log(time.date); - console.log(time.time); - result["windowSize"] = time.windowSize; - result["timeWindowUnits"] = time.timeWindowUnits; + result['start'] = time.start; + result['end'] = time.end; } if (eventID != null) { diff --git a/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchTypeFilter.tsx b/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchTypeFilter.tsx deleted file mode 100644 index 5bb80b3b3..000000000 --- a/SEBrowser/Scripts/TSX/Components/EventSearch/EventSearchTypeFilter.tsx +++ /dev/null @@ -1,183 +0,0 @@ -//****************************************************************************************************** -// EventSearchTypeFilter.tsx - Gbtc -// -// Copyright © 2023, Grid Protection Alliance. All Rights Reserved. -// -// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See -// the NOTICE file distributed with this work for additional information regarding copyright ownership. -// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this -// file except in compliance with the License. You may obtain a copy of the License at: -// -// http://opensource.org/licenses/MIT -// -// Unless agreed to in writing, the subject software distributed under the License is distributed on an -// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the -// License for the specific language governing permissions and limitations. -// -// Code Modification History: -// ---------------------------------------------------------------------------------------------------- -// 02/02/2023 - C. Lackner -// Generated original version of source code. -// -//****************************************************************************************************** -import React from 'react'; -import 'moment'; -import _ from 'lodash'; -import { useAppDispatch, useAppSelector } from '../../hooks'; -import { SelectTypeFilter } from './EventSearchSlice'; -import { SetFilters } from './EventSearchSlice'; -import { EventTypeSlice } from '../../Store'; -import { SEBrowser } from '../../Global'; -import { OpenXDA } from '@gpa-gemstone/application-typings'; -interface IProps { - Height: number -} -interface ICategory { label: string, height: number } - -const EventSearchTypeFilters = (props: IProps) => { - const dispatch = useAppDispatch(); - - const eventTypes = useAppSelector(EventTypeSlice.Data); - const evtTypeStatus = useAppSelector(EventTypeSlice.Status); - - const eventTypeFilter = useAppSelector(SelectTypeFilter); - - const [evtTypeCategories, setEvtTypeCategories] = React.useState([]); - const [nCol, setnCol] = React.useState(1); - - React.useEffect(() => setEvtTypeCategories(_.uniq(eventTypes.map(e => e.Category)).map(c => ({ label: c, height: 0 }))), [eventTypes]); - - - React.useEffect(() => { - let navHeight = props.Height; - const heights = evtTypeCategories.map(h => h.height); - if (heights.some(h => h > navHeight)) { - navHeight = Math.max(...heights); - } - - let nCollumn = 0; - heights.sort(); - - while (heights.length > 0) { - nCollumn = nCollumn + 1; - let hc = heights[0]; - heights.splice(0, 1); - - let index = heights.findIndex(h => h <= (navHeight - hc)); - while (index >= 0 && heights[index] != 0) { - hc = hc + heights[index]; - heights.splice(index, 1); - index = heights.findIndex(h => h <= (navHeight - hc)) - } - } - setnCol(nCollumn); - }, [evtTypeCategories, props.Height]); - - - React.useEffect(() => { - if (evtTypeStatus == 'changed' || evtTypeStatus == 'unintiated') - dispatch(EventTypeSlice.Fetch()); - }, [evtTypeStatus]); - - function generateCollumn(colIndex: number) { - - const flts: ICategory[] = []; - - let navHeight = props.Height; - if (evtTypeCategories.some(h => h.height > navHeight)) - navHeight = Math.max(...evtTypeCategories.map(h => h.height)); - - const categories = _.orderBy(evtTypeCategories, (e) => e.height); - let nCollumn = 0; - while (categories.length > 0 && nCollumn <= colIndex) { - nCollumn = nCollumn + 1; - if (nCollumn == colIndex + 1) - flts.push(categories[0]); - let hc = categories[0].height; - categories.splice(0, 1); - let index = categories.findIndex(h => h.height <= (navHeight - hc)) - while (index >= 0) { - hc = hc + categories[index].height; - if (nCollumn == colIndex + 1) - flts.push(categories[index]); - categories.splice(index, 1); - index = categories.findIndex(h => h.height <= (navHeight - hc)) - } - } - - return
  • c.height == 0) ? 5 : '100%', overflow: 'hidden' }}> - {flts.map(c => ( { - dispatch(SetFilters({ - types: (selected ? eventTypeFilter.filter(id => eventTypes.find(t => id == t.ID && t.Category == c.label) == null) : _.uniq([...eventTypeFilter, ...eventTypes.filter(t => t.Category == c.label).map(i => i.ID)])) - })); - }} - Data={eventTypes.filter(et => et.Category == c.label)} - OnChange={(record, selected) => { - dispatch(SetFilters({ - types: (selected ? [...eventTypeFilter, record.ID] : eventTypeFilter.filter(t => t != record.ID)) - })); - }} - SetHeight={(h) => setHeight(c.label, h)} />))} -
  • - } - - function setHeight(label: string, h: number) { - const index = evtTypeCategories.findIndex(c => c.label == label) - if (index > -1 && evtTypeCategories[index].height != h) - setEvtTypeCategories((d) => { - const u = _.cloneDeep(d); - u[index].height = h; - return u; - }) - } - return ( - <> - {Array.from({ length: nCol }, (_, i) => i).map(c => generateCollumn(c))} - ); - -} - -interface ICategoryProps { - Label: string, - SetHeight: (h: number) => void, - Data: OpenXDA.Types.EventType[], - SelectedID:number[], - OnChange: (record: OpenXDA.Types.EventType, selected: boolean) => void - SelectAll: (selected: boolean) => void -} - -const EventSearchTypeCategory = (props: ICategoryProps) => { - const formRef = React.useRef(null); - - React.useLayoutEffect(() => props.SetHeight(formRef?.current?.offsetHeight ?? 0) ) - - return
    - {(props.Label != null && props.Label.length > 0 ? props.Label : 'Other Types')}: - { - const isSelected = props.Data.filter(item => props.SelectedID.find(i => i == item.ID) == null).length == 0; - props.SelectAll(isSelected); - - }}> - ({props.Data.filter(item => props.SelectedID.find(i => i == item.ID) == null).length == 0 ? 'un' : ''}select all) - - -
    -
      - {props.Data.map((item) =>
    • - -
    • )} -
    -
    -
    -} - -export default EventSearchTypeFilters; \ No newline at end of file diff --git a/SEBrowser/Scripts/TSX/Components/EventSearch/TimeWindowUtils.tsx b/SEBrowser/Scripts/TSX/Components/EventSearch/TimeWindowUtils.tsx deleted file mode 100644 index 9e33f6b36..000000000 --- a/SEBrowser/Scripts/TSX/Components/EventSearch/TimeWindowUtils.tsx +++ /dev/null @@ -1,109 +0,0 @@ -//****************************************************************************************************** -// TimeWindowUtils.tsx - Gbtc -// -// Copyright © 2023, Grid Protection Alliance. All Rights Reserved. -// -// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See -// the NOTICE file distributed with this work for additional information regarding copyright ownership. -// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this -// file except in compliance with the License. You may obtain a copy of the License at: -// -// http://opensource.org/licenses/MIT -// -// Unless agreed to in writing, the subject software distributed under the License is distributed on an -// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the -// License for the specific language governing permissions and limitations. -// -// Code Modification History: -// ---------------------------------------------------------------------------------------------------- -// 07/11/2023 - C. Lackner -// Generated original version of source code. -//****************************************************************************************************** -import moment from 'moment'; - -export function momentUnit(unit: number) { - if (unit == 7) { - return 'y'; - } else if (unit == 6) { - return 'M'; - } else if (unit == 5) { - return 'w'; - } else if (unit == 4) { - return 'd'; - } else if (unit == 3) { - return 'h'; - } else if (unit == 2) { - return 'm'; - } else if (unit == 1) { - return 's'; - } - return 'ms'; -} - -export function findAppropriateUnit(startTime: moment.Moment, endTime: moment.Moment, unit?: number, useHalfWindow?: boolean) { - - if (unit === undefined) - unit = 7; - - let diff = endTime.diff(startTime, momentUnit(unit), true); - if (useHalfWindow !== undefined && useHalfWindow) - diff = diff / 2; - - for (let i = unit; i >= 1; i--) { - if (i == 6) // Remove month as appropriate due to innacuracy in definition (31/30/28/29 days) - continue; - if (Number.isInteger(diff)) { - return [i, diff]; - } - let nextI = i - 1; - if (nextI == 6) - nextI = 5; - - diff = endTime.diff(startTime, momentUnit(nextI), true); - if (useHalfWindow !== undefined && useHalfWindow) - diff = diff / 2; - - if (diff > 65000) { - diff = endTime.diff(startTime, momentUnit(i), true); - if (useHalfWindow !== undefined && useHalfWindow) - diff = diff / 2; - return [i, Math.round(diff)]; - } - - } - - return [0, Math.round(diff)]; -} - -export function getStartEndTime(center: moment.Moment, duration: number, unit: number): [moment.Moment, moment.Moment] { - const d = moment.duration(duration, momentUnit(unit)); - const start = center.clone().subtract(d.asHours(), 'h'); - const end = center.clone().add(d.asHours(), 'h'); - return [start, end] -} - -export function getMoment(date: string, time?: string) { - if (time === undefined) - return moment(date, 'MM/DD/YYYY HH:mm:ss.SSS'); - return moment(date + ' ' + time, 'MM/DD/YYYY HH:mm:ss.SSS'); -} - - -export function readableUnit(unit: number) { - if (unit == 7) { - return 'Year(s)'; - } else if (unit == 6) { - return 'Month(s)'; - } else if (unit == 5) { - return 'Week(s)'; - } else if (unit == 4) { - return 'Day(s)'; - } else if (unit == 3) { - return 'Hour(s)'; - } else if (unit == 2) { - return 'Minute(s)'; - } else if (unit == 1) { - return 'Second(s)'; - } - return 'Millisecond(s)'; -} \ No newline at end of file diff --git a/SEBrowser/Scripts/TSX/Components/MeterActivity.tsx b/SEBrowser/Scripts/TSX/Components/MeterActivity.tsx index 1ac57dd16..55e382eae 100644 --- a/SEBrowser/Scripts/TSX/Components/MeterActivity.tsx +++ b/SEBrowser/Scripts/TSX/Components/MeterActivity.tsx @@ -24,7 +24,9 @@ import React from 'react'; import Table from './Table'; import SEBrowserService from './../../TS/Services/SEBrowser'; +import { useSelector } from 'react-redux'; import moment from 'moment'; +import { SelectDateTimeFormat } from './SettingsSlice'; declare let xdaInstance: string; declare let homePath: string; @@ -34,20 +36,19 @@ declare let homePath: string; // //buildMeterActivityTables(); // }, updateInterval); -const momentFormat = "YYYY/MM/DD HH:mm:ss"; const MeterActivity: React.FunctionComponent = () => { - + const dateTimeFormat = useSelector(SelectDateTimeFormat); return (
    - +
    - +
    -
    +
    @@ -64,17 +65,21 @@ interface MostActiveMeterActivityRow { '30Days': number } -class MostActiveMeters extends React.Component, sortField: string, rowsPerPage: number }>{ +interface IProps { + meterTable: Array, + sortField: string, + rowsPerPage: number, + dateTimeFormat: string +} +class MostActiveMeters extends React.Component, sortField: string, rowsPerPage: number }> { seBrowserService: SEBrowserService; - constructor(props) { + constructor(props: IProps) { super(props); - this.seBrowserService = new SEBrowserService(); - this.state = { - meterTable: [], - sortField: '24Hours', - rowsPerPage: 7 + meterTable: props.meterTable, + sortField: props.sortField, + rowsPerPage: props.rowsPerPage } } @@ -124,7 +129,7 @@ class MostActiveMeters extends React.Component this.openWindowToMeterEventsByLine(item.FirstEventID, context, moment().format(momentFormat))} style={{ color: 'blue' }}>{item[key]} + return this.openWindowToMeterEventsByLine(item.FirstEventID, context, moment().format(this.props.dateTimeFormat))} style={{ color: 'blue' }}>{item[key]} } else { return {item[key]}; @@ -142,7 +147,7 @@ class MostActiveMeters extends React.ComponentMost Active Meters {/*Click on event count to view events*/}
    -
    +
    cols={[ { key: 'AssetKey', label: 'Name', headerStyle: { width: 'calc(40%)' } }, @@ -154,18 +159,18 @@ class MostActiveMeters extends React.Component false } + selected={() => false} onSort={(data) => { this.setState({ sortField: data.col }, () => this.createTableRows()) }} - onClick={() => {/*Do Nothing*/}} + onClick={() => {/*Do Nothing*/ }} theadStyle={{ fontSize: 'smaller' }} />
    ); } - + } - + interface LeastActiveMeterActivityRow { AssetKey: string, '180Days': number, @@ -173,18 +178,22 @@ interface LeastActiveMeterActivityRow { '30Days': number, FirstEventID: number } - -class LeastActiveMeters extends React.Component, sortField: keyof(LeastActiveMeterActivityRow), rowsPerPage: number }>{ +interface ILeastProps { + meterTable: Array, + sortField: keyof (LeastActiveMeterActivityRow), + rowsPerPage: number, + dateTimeFormat: string +} + +class LeastActiveMeters extends React.Component, sortField: keyof (LeastActiveMeterActivityRow), rowsPerPage: number }> { seBrowserService: SEBrowserService; - constructor(props) { + constructor(props: ILeastProps) { super(props); - this.seBrowserService = new SEBrowserService(); - this.state = { meterTable: [], - sortField: '30Days', - rowsPerPage: 7 + sortField: this.props.sortField, + rowsPerPage: this.props.rowsPerPage } } @@ -219,7 +228,7 @@ class LeastActiveMeters extends React.Component this.openWindowToMeterEventsByLine(item.FirstEventID, context, moment().format(momentFormat))} style={{ color: 'blue' }}>{item[key]} + return this.openWindowToMeterEventsByLine(item.FirstEventID, context, moment().format(this.props.dateTimeFormat))} style={{ color: 'blue' }}>{item[key]} } else { return {item[key]}; @@ -254,9 +263,9 @@ class LeastActiveMeters extends React.Component cols={[ { key: 'AssetKey', label: 'Name', headerStyle: { width: 'calc(40%)' } }, - { key: '30Days', label: 'Files(Events) 30D', headerStyle: { width: '20%' }, content: (item, key) => this.createContent(item, key) }, - { key: '90Days', label: 'Files(Events) 90D', headerStyle: { width: '20%' }, content: (item, key) => this.createContent(item, key) }, - { key: '180Days', label: 'Files(Events) 180D', headerStyle: { width: '20%' }, content: (item, key) => this.createContent(item, key) }, + { key: '30Days', label: 'Files(Events) 30D', headerStyle: { width: '20%' }, content: (item, key) => this.createContent(item, key) }, + { key: '90Days', label: 'Files(Events) 90D', headerStyle: { width: '20%' }, content: (item, key) => this.createContent(item, key) }, + { key: '180Days', label: 'Files(Events) 180D', headerStyle: { width: '20%' }, content: (item, key) => this.createContent(item, key) }, ]} tableClass="table" data={this.state.meterTable} @@ -274,7 +283,7 @@ class LeastActiveMeters extends React.Component, sortField: string}>{ +class FilesProcessed extends React.Component, sortField: string }> { seBrowserService: SEBrowserService; constructor(props) { super(props); @@ -295,7 +304,8 @@ class FilesProcessed extends React.Component { this.setState({ - meterTable: data.map((x) => ) }); + meterTable: data.map((x) => ) + }); }); } @@ -308,7 +318,7 @@ class FilesProcessed extends React.Component
      -
    • Time Processed
      File
    • +
    • Time Processed
      File
    • {this.state.meterTable}
    @@ -364,17 +374,17 @@ const ListItem = (props: { CreationTime: string, FilePath: string, FileGroupID: return (
  • -
    - -
    -
    - {moment(props.CreationTime).format('MM/DD/YYYY')}
    {moment(props.CreationTime).format('HH:mm:ss.SSSSSSS')}
    -
    -
    - {buildFileGroupContent(props)} +
    + +
    +
    + {moment(props.CreationTime).format('MM/DD/YYYY')}
    {moment(props.CreationTime).format('HH:mm:ss.SSSSSSS')}
    +
    +
    + {buildFileGroupContent(props)}
    -
    +
    diff --git a/SEBrowser/Scripts/TSX/Components/RelayReport/RelayReport.tsx b/SEBrowser/Scripts/TSX/Components/RelayReport/RelayReport.tsx index c98c4af7a..f75955e22 100644 --- a/SEBrowser/Scripts/TSX/Components/RelayReport/RelayReport.tsx +++ b/SEBrowser/Scripts/TSX/Components/RelayReport/RelayReport.tsx @@ -26,9 +26,9 @@ import RelayReportPane from './RelayReportPane'; import * as queryString from 'querystring'; import moment from 'moment'; import { useLocation, useNavigate } from 'react-router-dom'; - -const momentDateFormat = "MM/DD/YYYY"; -const momentTimeFormat = "HH:mm:ss.SSS"; +import { SelectDateTimeFormat } from '../SettingsSlice'; +import { useSelector } from 'react-redux'; +import { SEBrowser } from 'Scripts/TSX/global'; interface IState { searchBarProps: RelayReportNavBarProps, @@ -37,54 +37,44 @@ interface IState { const RelayReport = () => { const navigate = useNavigate(); const history = useLocation(); + const dateTimeFormat = useSelector(SelectDateTimeFormat); const [BreakerID, setBreakerID] = React.useState(0); const [ChannelID, setChannelID] = React.useState(0); const [StationId, setStationId] = React.useState(0); - const [date, setDate] = React.useState(''); - const [time, setTime] = React.useState(''); - const [windowSize, setWindowSize] = React.useState(0); - const [timeWindowUnits, setTimeWindowUnits] = React.useState(0); + const [time, setTime] = React.useState({ + start: '01/01/2000 12:00:00.000', + end: '01/02/2000 12:00:00.000', + }); React.useEffect(() => { const query = queryString.parse(history.search.replace("?", ""), "&", "="); setBreakerID(query['breakerid'] != undefined ? parseInt(query['breakerid'] as string) : -1); setChannelID(query['channelid'] != undefined ? parseInt(query['channelid'] as string) : -1); - setDate(query['date'] != undefined ? query['date'] as string : moment().format(momentDateFormat)); - setTime(query['time'] != undefined ? query['time'] as string : moment().format(momentTimeFormat)); - setWindowSize(query['windowSize'] != undefined ? parseInt(query['windowSize'].toString()) : 10); - setTimeWindowUnits(query['timeWindowUnits'] != undefined ? parseInt(query['timeWindowUnits'].toString()) : 2); setStationId(query['StationId'] != undefined ? parseInt(query['StationId'] as string) : -1); + const newTime = { + start: query['start']?.toString() ?? moment().utc().subtract(8, 'hours').format(dateTimeFormat), + end: query['end']?.toString() ?? moment().utc().format(dateTimeFormat) + } + setTime(newTime); }, []); React.useEffect(() => { - const state = { - BreakerID, ChannelID, StationId, date, time, - windowSize, timeWindowUnits - }; - + const state = { BreakerID, ChannelID, StationId, start: time.start, end: time.end }; const q = queryString.stringify(state, "&", "="); const handle = setTimeout(() => navigate(history.pathname + '?' + q), 500); return (() => { clearTimeout(handle); }) - - }, [BreakerID, ChannelID, StationId, date, time, - windowSize, timeWindowUnits]) + }, [BreakerID, ChannelID, StationId, time]) function setState(obj: IState) { setBreakerID(obj.searchBarProps.BreakerID); setChannelID(obj.searchBarProps.ChannelID); - setDate(obj.searchBarProps.date); - setTime(obj.searchBarProps.time); - setWindowSize(obj.searchBarProps.windowSize); - setTimeWindowUnits(obj.searchBarProps.timeWindowUnits); + setTime(obj.searchBarProps.TimeFilter); setStationId(obj.searchBarProps.StationId); } - const searchBarProps: RelayReportNavBarProps = { - BreakerID, ChannelID, StationId, date, time, - windowSize, timeWindowUnits, stateSetter: setState - }; + const searchBarProps: RelayReportNavBarProps = { BreakerID, ChannelID, StationId, TimeFilter: time, stateSetter: setState }; return (
    diff --git a/SEBrowser/Scripts/TSX/Components/RelayReport/RelayReportNavBar.tsx b/SEBrowser/Scripts/TSX/Components/RelayReport/RelayReportNavBar.tsx index b83c516f7..591f661c3 100644 --- a/SEBrowser/Scripts/TSX/Components/RelayReport/RelayReportNavBar.tsx +++ b/SEBrowser/Scripts/TSX/Components/RelayReport/RelayReportNavBar.tsx @@ -22,11 +22,10 @@ //****************************************************************************************************** import * as React from 'react'; import _ from 'lodash'; -import ReportTimeFilter from '../ReportTimeFilter'; - -const momentDateFormat = "MM/DD/YYYY"; -const momentTimeFormat = "HH:mm:ss.SSS"; - +import { TimeFilter } from '@gpa-gemstone/common-pages' +import { useSelector } from 'react-redux'; +import { SelectTimeZone, SelectDateTimeSetting } from '../SettingsSlice'; +import { SEBrowser } from 'Scripts/TSX/global'; interface Substation { LocationID: number, AssetKey: string, AssetName: string @@ -48,20 +47,19 @@ export interface RelayReportNavBarProps { BreakerID: number, ChannelID: number, StationId: number, - date: string, - time: string, - windowSize: number, - timeWindowUnits: number, - + TimeFilter: SEBrowser.IReportTimeFilter } const RelayReportNavBar = (props: RelayReportNavBarProps) => { + const timeZone = useSelector(SelectTimeZone); + const dateTimeSetting = useSelector(SelectDateTimeSetting); + const [breakers, setBreakers] = React.useState([]); const [substations, setSubstations] = React.useState([]); const [channels, setChannels] = React.useState([]); React.useEffect(() => { - const handle = getSubstationData(); + const handle = getSubstationData(); return () => { if (handle != null && handle.abort != null) handle.abort(); } }, []) @@ -76,7 +74,7 @@ const RelayReportNavBar = (props: RelayReportNavBarProps) => { if (substations.findIndex(s => s.LocationID == props.StationId) == -1) setStation(substations[0].LocationID); }, [substations, props.StationId]) - + React.useEffect(() => { if (breakers.length == 0) return; @@ -96,7 +94,7 @@ const RelayReportNavBar = (props: RelayReportNavBarProps) => { setChannel(channels[0].ID) }, [channels, props.ChannelID]) - function getBreakerData(): JQuery.jqXHR{ + function getBreakerData(): JQuery.jqXHR { const h = $.ajax({ type: "GET", url: `${homePath}api/PQDashboard/RelayReport/GetBreakerData?locationID=${props.StationId}`, @@ -112,11 +110,11 @@ const RelayReportNavBar = (props: RelayReportNavBarProps) => { }) return h; - + } function getSubstationData(): JQuery.jqXHR { - const h = $.ajax({ + const h = $.ajax({ type: "GET", url: `${homePath}api/PQDashboard/RelayReport/GetSubstationData`, contentType: "application/json; charset=utf-8", @@ -130,10 +128,9 @@ const RelayReportNavBar = (props: RelayReportNavBarProps) => { setSubstations(d); }) return h; - + } - function getCoilData(): JQuery.jqXHR { const h = $.ajax({ type: "GET", @@ -171,87 +168,64 @@ const RelayReportNavBar = (props: RelayReportNavBarProps) => { props.stateSetter({ searchBarProps: object }); } - function setDate(date: string) { - - const object = _.clone(props) as RelayReportNavBarProps; - object.date = date; - props.stateSetter({ searchBarProps: object }); - } - - function setTime(time: string) { - - const object = _.clone(props) as RelayReportNavBarProps; - object.time = time; - props.stateSetter({ searchBarProps: object }); - } - - function setTimeWindowUnits(timeWindowUnits: number) { - - const object = _.clone(props) as RelayReportNavBarProps; - object.timeWindowUnits = timeWindowUnits; - props.stateSetter({ searchBarProps: object }); - } - - function setWindowSize(windowSize: number) { - + function setTime(filter: SEBrowser.IReportTimeFilter) { const object = _.clone(this.props) as RelayReportNavBarProps; - object.windowSize = windowSize; - props.stateSetter({ searchBarProps: object }); + object.TimeFilter = filter; + this.props.stateSetter({ searchBarProps: object }); } + return ( + + ); - return ( - - ); - } export default RelayReportNavBar; \ No newline at end of file diff --git a/SEBrowser/Scripts/TSX/Components/RelayReport/RelayReportPane.tsx b/SEBrowser/Scripts/TSX/Components/RelayReport/RelayReportPane.tsx index 1558d3e50..79e11fb8e 100644 --- a/SEBrowser/Scripts/TSX/Components/RelayReport/RelayReportPane.tsx +++ b/SEBrowser/Scripts/TSX/Components/RelayReport/RelayReportPane.tsx @@ -25,7 +25,8 @@ import moment from 'moment'; import { RelayReportNavBarProps } from './RelayReportNavBar'; import { Line, LineWithThreshold, Plot } from '@gpa-gemstone/react-graph'; import { RandomColor } from '@gpa-gemstone/helper-functions'; -import { findAppropriateUnit, getMoment, getStartEndTime } from '../EventSearch/TimeWindowUtils'; +import { SelectDateTimeFormat } from '../SettingsSlice'; +import { useSelector } from 'react-redux'; interface IRelayPerformance { EventID: number, @@ -57,6 +58,7 @@ interface IRelayPerformance { } const RelayReportPane = (props: RelayReportNavBarProps) => { + const [realyPerformance, setRelayPerformance] = React.useState([]); const [Tstart, setTstart] = React.useState(0); const [Tend, setTend] = React.useState(0); @@ -65,33 +67,23 @@ const RelayReportPane = (props: RelayReportNavBarProps) => { const h = getRelayPerformance() return () => { if (h != null && h.abort != null) h.abort(); }; - }, [props]); React.useEffect(() => { getTimeLimits() - }, [props.windowSize, props.timeWindowUnits, props.time, props.date]) + }, [props.TimeFilter]) function getTimeLimits() { - const [Tstart, Tend] = getStartEndTime(getMoment(props.date, props.time), props.windowSize, props.timeWindowUnits) - setTend(Tend.valueOf()); - setTstart(Tstart.valueOf()) - - } - - - - function getRelayPerformance(): JQuery.jqXHR { - - const adjustedTime = findAppropriateUnit(getMoment(props.date, props.time), - getStartEndTime(getMoment(props.date, props.time), props.windowSize, props.timeWindowUnits)[1], - props.timeWindowUnits); + const [startMoment, endMoment] = [moment.utc(props.TimeFilter.start), moment.utc(props.TimeFilter.end)] + setTend(endMoment.valueOf()); + setTstart(startMoment.valueOf()) + } - const h = $.ajax({ + function getRelayPerformance(): JQuery.jqXHR { + const h = $.ajax({ type: "GET", - url: `${homePath}api/PQDashboard/RelayReport/getRelayPerformance?lineID=${props.BreakerID}&channelID=${props.ChannelID}}&date=${props.date}` + - `&time=${props.time}&timeWindowunits=${adjustedTime[0]}&windowSize=${adjustedTime[1]}`, + url: `${homePath}api/PQDashboard/RelayReport/getRelayPerformance?lineID=${props.BreakerID}&channelID=${props.ChannelID}}&start=${props.TimeFilter.end}&end=${props.TimeFilter.end}`, contentType: "application/json; charset=utf-8", dataType: 'json', cache: false, @@ -102,9 +94,7 @@ const RelayReportPane = (props: RelayReportNavBarProps) => { return h; } - - - return ( <> + return (<>
    Breaker Performance:
    @@ -167,13 +157,13 @@ const RelayReportPane = (props: RelayReportNavBarProps) => {
    - - - - + + + + - )} + )}
    LineStart TimeType
    {item.Idrop.toFixed(3)} {(item.TiDrop / 10).toFixed(0)} {(item.Tend / 10).toFixed(0)}{item.ExtinctionTimeA == undefined ? '-' :(item.ExtinctionTimeA / 10).toFixed(0)}{item.ExtinctionTimeB == undefined ? '-' :(item.ExtinctionTimeB / 10).toFixed(0)}{item.ExtinctionTimeC == undefined? '-' :(item.ExtinctionTimeC / 10).toFixed(0)}{item.I2CA == undefined? '-' : item.I2CA.toFixed(3)}{item.ExtinctionTimeA == undefined ? '-' : (item.ExtinctionTimeA / 10).toFixed(0)}{item.ExtinctionTimeB == undefined ? '-' : (item.ExtinctionTimeB / 10).toFixed(0)}{item.ExtinctionTimeC == undefined ? '-' : (item.ExtinctionTimeC / 10).toFixed(0)}{item.I2CA == undefined ? '-' : item.I2CA.toFixed(3)} {item.I2CB == undefined ? '-' : item.I2CB.toFixed(3)} {item.I2CC == undefined ? '-' : item.I2CC.toFixed(3)}
    @@ -185,13 +175,13 @@ const RelayReportPane = (props: RelayReportNavBarProps) => {
    - [moment.utc(ev.TripInitiate).valueOf(), ev.TripTime * 0.1] as [number,number]).reverse()} - threshHolds={realyPerformance.length > 0 && realyPerformance[0].TripTimeAlert != 0 && realyPerformance[0].TripTimeAlert != undefined ? [{ Value: realyPerformance[0].TripTimeAlert, Color: '#ff0000' }] : []} legend={'Trip Time'}/> + [moment.utc(ev.TripInitiate).valueOf(), ev.TripTime * 0.1] as [number, number]).reverse()} + threshHolds={realyPerformance.length > 0 && realyPerformance[0].TripTimeAlert != 0 && realyPerformance[0].TripTimeAlert != undefined ? [{ Value: realyPerformance[0].TripTimeAlert, Color: '#ff0000' }] : []} legend={'Trip Time'} /> [moment.utc(ev.TripInitiate).valueOf(), ev.PickupTime * 0.1] as [number, number]).reverse()} - threshHolds={realyPerformance.length > 0 && realyPerformance[0].PickupTimeAlert != 0 && realyPerformance[0].PickupTimeAlert != undefined ? [{ Value: realyPerformance[0].PickupTimeAlert, Color: '#ff0000' }] : []} legend={'Pickup Time'}/> + threshHolds={realyPerformance.length > 0 && realyPerformance[0].PickupTimeAlert != 0 && realyPerformance[0].PickupTimeAlert != undefined ? [{ Value: realyPerformance[0].PickupTimeAlert, Color: '#ff0000' }] : []} legend={'Pickup Time'} /> @@ -199,7 +189,7 @@ const RelayReportPane = (props: RelayReportNavBarProps) => { threshHolds={realyPerformance.length > 0 && realyPerformance[0].TripcoilConditionAlert != 0 && realyPerformance[0].TripcoilConditionAlert != undefined ? [{ Value: realyPerformance[0].TripcoilConditionAlert, Color: '#ff0000' }] : []} legend={'Trip Coil condition'} /> - + [moment.utc(ev.TripInitiate).valueOf(), ev.Imax1] as [number, number]).reverse()} legend={'Plunger starts to Move'} /> @@ -212,7 +202,7 @@ const RelayReportPane = (props: RelayReportNavBarProps) => { [moment.utc(ev.TripInitiate).valueOf(), ev.Tmax1 / 10.0] as [number, number]).reverse()} legend={'Plunger starts to Move'} /> - [moment.utc(ev.TripInitiate).valueOf(), ev.TplungerLatch/ 10.0] as [number, number]).reverse()} legend={'Plunger hits Latch'} /> + [moment.utc(ev.TripInitiate).valueOf(), ev.TplungerLatch / 10.0] as [number, number]).reverse()} legend={'Plunger hits Latch'} /> [moment.utc(ev.TripInitiate).valueOf(), ev.TiDrop / 10.0] as [number, number]).reverse()} legend={'A finger opens'} /> [moment.utc(ev.TripInitiate).valueOf(), ev.Tend / 10.0] as [number, number]).reverse()} legend={'TCE Extinction'} /> @@ -232,7 +222,7 @@ const RelayReportPane = (props: RelayReportNavBarProps) => {
    : null} - + ) diff --git a/SEBrowser/Scripts/TSX/Components/ReportTimeFilter.tsx b/SEBrowser/Scripts/TSX/Components/ReportTimeFilter.tsx deleted file mode 100644 index 0e569f973..000000000 --- a/SEBrowser/Scripts/TSX/Components/ReportTimeFilter.tsx +++ /dev/null @@ -1,652 +0,0 @@ -//****************************************************************************************************** -// ReportTimeFilter.tsx - Gbtc -// -// Copyright © 2020, Grid Protection Alliance. All Rights Reserved. -// -// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See -// the NOTICE file distributed with this work for additional information regarding copyright ownership. -// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this -// file except in compliance with the License. You may obtain a copy of the License at: -// -// http://opensource.org/licenses/MIT -// -// Unless agreed to in writing, the subject software distributed under the License is distributed on an -// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the -// License for the specific language governing permissions and limitations. -// -// Code Modification History: -// ---------------------------------------------------------------------------------------------------- -// 09/16/2021 - Christoph Lackner -// Generated original version of source code. -//****************************************************************************************************** - -import * as React from 'react'; -import { SEBrowser } from '../global'; -import moment from 'moment'; -import momentTZ from 'moment-timezone'; -import { DatePicker, Select, Input } from '@gpa-gemstone/react-forms' -import { useSelector } from 'react-redux'; -import { SelectTimeZone, SelectDateTimeSetting } from './SettingsSlice'; -import { findAppropriateUnit, getMoment, getStartEndTime, momentUnit } from './EventSearch/TimeWindowUtils'; - -interface IProps { - filter: SEBrowser.IReportTimeFilter; - setFilter: (filter: SEBrowser.IReportTimeFilter) => void, - showQuickSelect: boolean -} - -interface ITimeFilter { - centerTime: string, - startTime: string, - endTime: string, - timeWindowUnits: number, - windowSize: number, - halfWindowSize: number, -} - -interface IQuickSelect { label: string, createFilter: (timeZone: string) => SEBrowser.IReportTimeFilter } - -export const momentDateFormat = "MM/DD/YYYY"; -export const momentTimeFormat = "HH:mm:ss.SSS"; // Also is the gemstone format - - -const AvailableQuickSelects: IQuickSelect[] = [ - { - label: 'This Hour', createFilter: (tz) => { - const offset = momentTZ.tz(moment.utc().startOf('hour').format('YYYY-MM-DDTHH:mm:ss.SSSSS'), tz).utcOffset(); - const t = moment.utc().add(offset, 'minutes').startOf('hour'); - t.add(30, 'minutes'); - return { - date: t.format(momentDateFormat), - time: t.format(momentTimeFormat), - timeWindowUnits: 2, - windowSize: 30 - } - } - }, - { - label: 'Last Hour', createFilter: (tz) => { - const offset = momentTZ.tz(moment.utc().startOf('hour').subtract(1, 'hour').format('YYYY-MM-DDTHH:mm:ss.SSSSS'), tz).utcOffset(); - const t = moment.utc().add(offset, 'minutes').startOf('hour').subtract(1, 'hour'); - t.add(30, 'minutes') - return { - date: t.format(momentDateFormat), - time: t.format(momentTimeFormat), - timeWindowUnits: 2, - windowSize: 30 - } - } - }, - { - label: 'Last 60 Minutes', createFilter: (tz) => { - const offset = momentTZ.tz(moment.utc().startOf('minute').subtract(1, 'hour').format('YYYY-MM-DDTHH:mm:ss.SSSSS'), tz).utcOffset(); - const t = moment.utc().add(offset, 'minutes').startOf('minute').subtract(1, 'hour'); - t.add(30, 'minutes'); - return { - date: t.format(momentDateFormat), - time: t.format(momentTimeFormat), - timeWindowUnits: 2, - windowSize: 30 - } - } - }, - { - label: 'Today', createFilter: (tz) => { - const offset = momentTZ.tz(moment.utc().startOf('day').format('YYYY-MM-DDTHH:mm:ss.SSSSS'), tz).utcOffset(); - const t = moment.utc().add(offset, 'minutes').startOf('day'); - t.add(12, 'hours'); - return { - date: t.format(momentDateFormat), - time: t.format(momentTimeFormat), - timeWindowUnits: 3, - windowSize: 12 - } - } - }, - { - label: 'Yesterday', createFilter: (tz) => { - const offset = momentTZ.tz(moment.utc().startOf('day').subtract(1, 'days').format('YYYY-MM-DDTHH:mm:ss.SSSSS'), tz).utcOffset(); - const t = moment.utc().add(offset, 'minutes').startOf('day').subtract(1, 'days'); - t.add(12, 'hours'); - return { - date: t.format(momentDateFormat), - time: t.format(momentTimeFormat), - timeWindowUnits: 3, - windowSize: 12 - } - } - }, - { - label: 'Last 24 Hours', createFilter: (tz) => { - const offset = momentTZ.tz(moment.utc().startOf('hour').subtract(24, 'hours').format('YYYY-MM-DDTHH:mm:ss.SSSSS'), tz).utcOffset(); - const t = moment.utc().add(offset, 'minutes').subtract(24, 'hours'); - t.add(12, 'hours'); - return { - date: t.format(momentDateFormat), - time: t.format(momentTimeFormat), - timeWindowUnits: 3, - windowSize: 12 - } - } - }, - { - label: 'This Week', createFilter: (tz) => { - const offset = momentTZ.tz(moment.utc().startOf('week').format('YYYY-MM-DDTHH:mm:ss.SSSSS'), tz).utcOffset(); - const t = moment.utc().add(offset, 'minutes').startOf('week'); - t.add(3.5 * 24, 'hours'); - return { - date: t.format(momentDateFormat), - time: t.format(momentTimeFormat), - timeWindowUnits: 3, - windowSize: 3.5 * 24 - } - } - }, - { - label: 'Last Week', createFilter: (tz) => { - const offset = momentTZ.tz(moment.utc().startOf('week').format('YYYY-MM-DDTHH:mm:ss.SSSSS'), tz).utcOffset(); - const t = moment.utc().add(offset, 'minutes').startOf('week'); - t.subtract(3.5 * 24, 'hours'); - return { - date: t.format(momentDateFormat), - time: t.format(momentTimeFormat), - timeWindowUnits: 3, - windowSize: 3.5 * 24 - } - } - }, - { - label: 'Last 7 Days', createFilter: (tz) => { - const offset = momentTZ.tz(moment.utc().startOf('day').format('YYYY-MM-DDTHH:mm:ss.SSSSS'), tz).utcOffset(); - const t = moment.utc().add(offset, 'minutes').startOf('day'); - t.subtract(3.5 * 24, 'hours'); - return { - date: t.format(momentDateFormat), - time: t.format(momentTimeFormat), - timeWindowUnits: 3, - windowSize: 3.5 * 24 - } - } - }, - { - label: 'This Month', createFilter: (tz) => { - const offset = momentTZ.tz(moment.utc().startOf('month').format('YYYY-MM-DDTHH:mm:ss.SSSSS'), tz).utcOffset(); - const t = moment.utc().add(offset, 'minutes').startOf('month'); - t.add(12 * t.daysInMonth(), 'hours'); - return { - date: t.format(momentDateFormat), - time: t.format(momentTimeFormat), - timeWindowUnits: 3, - windowSize: (t.daysInMonth() * 24) / 2.0 - } - } - }, - { - label: 'Last Month', createFilter: (tz) => { - const offset = momentTZ.tz(moment.utc().startOf('month').subtract(1, 'month').format('YYYY-MM-DDTHH:mm:ss.SSSSS'), tz).utcOffset(); - const t = moment.utc().add(offset, 'minutes').startOf('month').subtract(1, 'month'); - t.add(12 * t.daysInMonth(), 'hours'); - return { - date: t.format(momentDateFormat), - time: t.format(momentTimeFormat), - timeWindowUnits: 3, - windowSize: (t.daysInMonth() * 24) / 2.0 - } - } - }, - { - label: 'Last 30 Days', createFilter: (tz) => { - const offset = momentTZ.tz(moment.utc().startOf('day').format('YYYY-MM-DDTHH:mm:ss.SSSSS'), tz).utcOffset(); - const t = moment.utc().add(offset, 'minutes').startOf('day'); - t.subtract(15, 'days'); - return { - date: t.format(momentDateFormat), - time: t.format(momentTimeFormat), - timeWindowUnits: 4, - windowSize: 15 - } - } - }, - { - label: 'This Quarter', createFilter: (tz) => { - const offset = momentTZ.tz(moment.utc().startOf('quarter').add(1, 'quarter').format('YYYY-MM-DDTHH:mm:ss.SSSSS'), tz).utcOffset(); - const offset_tend = momentTZ.tz(moment.utc().startOf('quarter').format('YYYY-MM-DDTHH:mm:ss.SSSSS'), tz).utcOffset(); - const t = moment.utc().add(offset, 'minutes').startOf('quarter'); - const tend = moment.utc().add(offset_tend, 'minutes').startOf('quarter'); - tend.add(1, 'quarter') - const h = moment.duration(tend.diff(t)).asDays(); - t.add(h * 0.5, 'day'); - return { - date: t.format(momentDateFormat), - time: t.format(momentTimeFormat), - timeWindowUnits: 4, - windowSize: h * 0.5 - } - } - }, - { - label: 'Last Quarter', createFilter: (tz) => { - const offset = momentTZ.tz(moment.utc().startOf('quarter').subtract(1, 'quarter').format('YYYY-MM-DDTHH:mm:ss.SSSSS'), tz).utcOffset(); - const offset_tend = momentTZ.tz(moment.utc().startOf('quarter').format('YYYY-MM-DDTHH:mm:ss.SSSSS'), tz).utcOffset(); - const t = moment.utc().add(offset, 'minutes').startOf('quarter'); - const tend = moment.utc().add(offset_tend, 'minutes').startOf('quarter'); - t.subtract(1, 'quarter'); - const h = moment.duration(tend.diff(t)).asDays(); - t.add(h * 0.5, 'day'); - return { - date: t.format(momentDateFormat), - time: t.format(momentTimeFormat), - timeWindowUnits: 4, - windowSize: h * 0.5 - } - } - }, - { - label: 'Last 90 Days', createFilter: (tz) => { - const offset = momentTZ.tz(moment.utc().startOf('day').subtract(45, 'days').format('YYYY-MM-DDTHH:mm:ss.SSSSS'), tz).utcOffset(); - const t = moment.utc().add(offset, 'minutes').startOf('day'); - t.subtract(45, 'days'); - return { - date: t.format(momentDateFormat), - time: t.format(momentTimeFormat), - timeWindowUnits: 4, - windowSize: 45 - } - } - }, - { - label: 'This Year', createFilter: (tz) => { - const offset = momentTZ.tz(moment.utc().startOf('year').add(6, 'month').format('YYYY-MM-DDTHH:mm:ss.SSSSS'), tz).utcOffset(); - const t = moment.utc().add(offset, 'minutes').startOf('year'); - t.add(6, 'month'); - return { - date: t.format(momentDateFormat), - time: t.format(momentTimeFormat), - timeWindowUnits: 6, - windowSize: 6 - } - } - }, - { - label: 'Last Year', createFilter: (tz) => { - const offset = momentTZ.tz(moment.utc().startOf('year').subtract(1, 'year').format('YYYY-MM-DDTHH:mm:ss.SSSSS'), tz).utcOffset(); - const t = moment.utc().add(offset, 'minute').startOf('year').subtract(1, 'year'); - t.add(6, 'month'); - - return { - date: t.format(momentDateFormat), - time: t.format(momentTimeFormat), - timeWindowUnits: 6, - windowSize: 6 - } - } - }, - { - label: 'Last 365 Days', createFilter: (tz) => { - const offset = momentTZ.tz(moment.utc().startOf('day').subtract(182.5, 'days').format('YYYY-MM-DDTHH:mm:ss.SSSSS'), tz).utcOffset(); - const t = moment.utc().add(offset, 'minute').startOf('day'); - t.subtract(182.5, 'days'); - return { - date: t.format(momentDateFormat), - time: t.format(momentTimeFormat), - timeWindowUnits: 4, - windowSize: 182 - } - } - } -]; - - - -const ReportTimeFilter = (props: IProps) => { - const timeZone = useSelector(SelectTimeZone); - const [activeQP, setActiveQP] = React.useState(-1); - const dateTimeSetting = useSelector(SelectDateTimeSetting); - const [filter, setFilter] = React.useState({ - centerTime: props.filter.date + ' ' + props.filter.time, - startTime: getMoment(props.filter.date, props.filter.time) - .subtract(props.filter.windowSize, momentUnit(props.filter.timeWindowUnits)) - .format(momentDateFormat + ' ' + momentTimeFormat), - endTime: getMoment(props.filter.date,props.filter.time) - .add(props.filter.windowSize, momentUnit(props.filter.timeWindowUnits)) - .format(momentDateFormat + ' ' + momentTimeFormat), - timeWindowUnits: props.filter.timeWindowUnits, - windowSize: props.filter.windowSize*2, - halfWindowSize: props.filter.windowSize, - }); - - - React.useEffect(() => { - if (isEqual(filter, props.filter)) - return; - props.setFilter({ - time: filter.centerTime.split(' ')[1], - date: filter.centerTime.split(' ')[0], - windowSize: filter.halfWindowSize, - timeWindowUnits: filter.timeWindowUnits - }); - }, [filter]) - - function isEqual(flt1: ITimeFilter, flt2: SEBrowser.IReportTimeFilter) { - const t = flt2.date + ' ' + flt2.time; - return flt1.centerTime == t && - flt1.timeWindowUnits == flt2.timeWindowUnits && - flt1.windowSize == flt2.windowSize * 2; - } - - React.useEffect(() => { - if (isEqual(filter, props.filter)) - return; - - const durationValue = props.filter.windowSize; - const dUnits = props.filter.timeWindowUnits; - - const centerTime = getMoment(props.filter.date, props.filter.time); - const [startTime, endTime] = getStartEndTime(centerTime, durationValue, dUnits); - - setFilter(prevState => ({ - ...prevState, - centerTime: centerTime.format('MM/DD/YYYY HH:mm:ss.SSS') , - startTime: startTime.format('MM/DD/YYYY HH:mm:ss.SSS'), - endTime: endTime.format('MM/DD/YYYY HH:mm:ss.SSS'), - timeWindowUnits: dUnits, - windowSize: durationValue * 2, - halfWindowSize: durationValue, - })); - - }, [props.filter]); - - - return ( -
    - Date/Time Filter: - {dateTimeSetting === 'center' ? -
    -
    - Record={filter} Field="centerTime" Help={`All times are in system time. System time is currently set to ${timeZone}. `} - Setter={(r) => { - const centerTime = getMoment(r.centerTime); - const [startTime, endTime] = getStartEndTime(centerTime, filter.halfWindowSize, filter.timeWindowUnits); - - setFilter(prevFilter => ({ - ...prevFilter, - centerTime: centerTime.format(momentDateFormat + ' ' + momentTimeFormat), - startTime: startTime.format(momentDateFormat + ' ' + momentTimeFormat), - endTime: endTime.format(momentDateFormat + ' ' + momentTimeFormat) - })); - setActiveQP(-1); - }} - Label='Time Window Center:' - Type='datetime-local' - Valid={(record) => { return true; }} Format={momentDateFormat + ' ' + momentTimeFormat} /> -
    -
    - : null - } - {dateTimeSetting === 'startWindow' || dateTimeSetting === 'startEnd' ? -
    -
    - Record={filter} Field="startTime" Help={`All times are in system time. System time is currently set to ${timeZone}. `} - Setter={(r) => { - const startTime = getMoment(r.startTime); - let window = filter.windowSize; - let unit = filter.timeWindowUnits; - if (dateTimeSetting === 'startEnd') { - [unit, window] = findAppropriateUnit(startTime, getMoment(filter.endTime),undefined, true); - } - - const d = moment.duration(window, momentUnit(unit)); - const centerTime = startTime.clone().add(d); - const endTime = centerTime.clone().add(d); - setFilter({ - centerTime: centerTime.format(momentDateFormat + ' ' + momentTimeFormat), - startTime: startTime.format(momentDateFormat + ' ' + momentTimeFormat), - endTime: endTime.format(momentDateFormat + ' ' + momentTimeFormat), - windowSize: window* 2, - halfWindowSize: window, - timeWindowUnits: unit - - }); - setActiveQP(-1); - }} - Label='Start of Time Window:' - Type='datetime-local' - Valid={() => true} Format={momentDateFormat + ' ' + momentTimeFormat} - /> -
    -
    - : null - } - {dateTimeSetting === 'endWindow' || dateTimeSetting === 'startEnd' ? -
    -
    - Record={filter} Field="endTime" Help={`All times are in system time. System time is currently set to ${timeZone}. `} - Setter={(r) => { - const endTime = getMoment(r.endTime); - let window = filter.windowSize; - let unit = filter.timeWindowUnits; - if (dateTimeSetting === 'startEnd') { - [unit, window] = findAppropriateUnit(getMoment(filter.startTime), endTime, undefined, true); - } - const d = moment.duration(window, momentUnit(unit)); - const centerTime = endTime.clone().subtract(d); - const startTime = centerTime.clone().subtract(d); - setFilter({ - centerTime: centerTime.format(momentDateFormat + ' ' + momentTimeFormat), - startTime: startTime.format(momentDateFormat + ' ' + momentTimeFormat), - endTime: endTime.format(momentDateFormat + ' ' + momentTimeFormat), - windowSize: window*2, - halfWindowSize: window, - timeWindowUnits: unit - - }); - setActiveQP(-1); - }} - Label='End of Time Window :' - Type='datetime-local' - Valid={() => true } Format={momentDateFormat + ' ' + momentTimeFormat} - /> -
    -
    - : null - } - {dateTimeSetting === 'center' ? - <> - -
    -
    - Record={filter} Field='halfWindowSize' Setter={(r) => { - const window = r.halfWindowSize; - const centerTime = getMoment(filter.centerTime); - const [startTime, endTime] = getStartEndTime(centerTime, window, filter.timeWindowUnits); - - setFilter(prevFilter => ({ - ...prevFilter, - windowSize: 2 * window, - halfWindowSize: window, - startTime: startTime.format(momentDateFormat + ' ' + momentTimeFormat), - endTime: endTime.format(momentDateFormat + ' ' + momentTimeFormat) - })); - setActiveQP(-1); - }} Label='' Valid={(record) => { return true; }} - Type='number' /> -
    -
    - Record={filter} Label='' - Field='timeWindowUnits' - Setter={(r) => { - const centerTime = getMoment(filter.centerTime); - const [startTime, endTime] = getStartEndTime(centerTime, filter.halfWindowSize, r.timeWindowUnits); - setFilter(prevFilter => ({ - ...prevFilter, - timeWindowUnits: r.timeWindowUnits, - startTime: startTime.format(momentDateFormat + ' ' + momentTimeFormat), - endTime: endTime.format(momentDateFormat + ' ' + momentTimeFormat) - })); - setActiveQP(-1); - }} - Options={[ - { Value: '7', Label: 'Year' }, - { Value: '6', Label: 'Month' }, - { Value: '5', Label: 'Week' }, - { Value: '4', Label: 'Day' }, - { Value: '3', Label: 'Hour' }, - { Value: '2', Label: 'Minute' }, - { Value: '1', Label: 'Second' }, - { Value: '0', Label: 'Millisecond' } - ]} /> -
    -
    - - : null - } - {dateTimeSetting === 'startWindow' ? - <> - -
    -
    - Record={filter} Field='windowSize' Setter={(r) => { - const startTime = getMoment(filter.startTime); - const d = moment.duration(r.windowSize / 2, momentUnit(filter.timeWindowUnits)); - const centerTime = startTime.clone().add(d); - const endTime = centerTime.clone().add(d); - setFilter(prevFilter => ({ - ...prevFilter, - windowSize: r.windowSize, - halfWindowSize: r.windowSize / 2, - centerTime: centerTime.format(momentDateFormat + ' ' + momentTimeFormat), - endTime: endTime.format(momentDateFormat + ' ' + momentTimeFormat) - })); - setActiveQP(-1); - }} Label='' Valid={(record) => { return true; }} - Type='number' /> -
    -
    - Record={filter} Label='' - Field='timeWindowUnits' - Setter={(r) => { - const startTime = getMoment(filter.startTime); - const d = moment.duration(filter.halfWindowSize, momentUnit(r.timeWindowUnits)); - const centerTime = startTime.clone().add(d); - const endTime = centerTime.clone().add(d); - setFilter(prevFilter => ({ - ...prevFilter, - timeWindowUnits: r.timeWindowUnits, - centerTime: centerTime.format(momentDateFormat + ' ' + momentTimeFormat), - endTime: endTime.format(momentDateFormat + ' ' + momentTimeFormat) - })); - setActiveQP(-1); - }} - Options={[ - { Value: '7', Label: 'Year' }, - { Value: '6', Label: 'Month' }, - { Value: '5', Label: 'Week' }, - { Value: '4', Label: 'Day' }, - { Value: '3', Label: 'Hour' }, - { Value: '2', Label: 'Minute' }, - { Value: '1', Label: 'Second' }, - { Value: '0', Label: 'Millisecond' } - ]} /> -
    -
    - - : null - } - {dateTimeSetting === 'endWindow' ? - <> - -
    -
    - Record={filter} Field='windowSize' Setter={(r) => { - const endTime = getMoment(filter.endTime); - const d = moment.duration(r.windowSize / 2, momentUnit(filter.timeWindowUnits)); - const centerTime = endTime.clone().subtract(d); - const startTime = centerTime.clone().subtract(d); - setFilter(prevFilter => ({ - ...prevFilter, - windowSize: r.windowSize, - halfWindowSize: r.windowSize/2, - centerTime: centerTime.format(momentDateFormat + ' ' + momentTimeFormat), - startTime: startTime.format(momentDateFormat + ' ' + momentTimeFormat) - })); - setActiveQP(-1); - }} Label='' Valid={(record) => { return true; }} - Type='number' /> -
    -
    - Record={filter} Label='' - Field='timeWindowUnits' - Setter={(r) => { - const endTime = getMoment(filter.endTime); - const d = moment.duration(filter.halfWindowSize, momentUnit(r.timeWindowUnits)); - const centerTime = endTime.clone().subtract(d); - const startTime = centerTime.clone().subtract(d); - setFilter(prevFilter => ({ - ...prevFilter, - timeWindowUnits: r.timeWindowUnits, - centerTime: centerTime.format(momentDateFormat + ' ' + momentTimeFormat), - startTime: startTime.format(momentDateFormat + ' ' + momentTimeFormat) - })); - setActiveQP(-1); - }} - Options={[ - { Value: '7', Label: 'Year' }, - { Value: '6', Label: 'Month' }, - { Value: '5', Label: 'Week' }, - { Value: '4', Label: 'Day' }, - { Value: '3', Label: 'Hour' }, - { Value: '2', Label: 'Minute' }, - { Value: '1', Label: 'Second' }, - { Value: '0', Label: 'Millisecond' } - ]} /> -
    -
    - - : null - } - - {props.showQuickSelect ? -
    - - {AvailableQuickSelects.map((qs, i) => { - if (i % 3 !== 0) - return null; - return ( -
    -
      -
    • { - props.setFilter(AvailableQuickSelects[i].createFilter(timeZone)); - setActiveQP(i); - }} - className={"item badge badge-" + (i == activeQP ? "primary" : "secondary")}>{AvailableQuickSelects[i].label} -
    • - {i + 1 < AvailableQuickSelects.length ? -
    • { - props.setFilter(AvailableQuickSelects[i + 1].createFilter(timeZone)); - setActiveQP(i + 1) - }}> - {AvailableQuickSelects[i + 1].label} -
    • : null} - {i + 2 < AvailableQuickSelects.length ? -
    • { - props.setFilter(AvailableQuickSelects[i + 2].createFilter(timeZone)); - setActiveQP(i + 2); - }}> - {AvailableQuickSelects[i + 2].label} -
    • : null} -
    -
    - ) - })} -
    - : null} -
    - ); -} -export default ReportTimeFilter; \ No newline at end of file diff --git a/SEBrowser/Scripts/TSX/Components/SettingsSlice.ts b/SEBrowser/Scripts/TSX/Components/SettingsSlice.ts index c7d5bf174..27d4e694c 100644 --- a/SEBrowser/Scripts/TSX/Components/SettingsSlice.ts +++ b/SEBrowser/Scripts/TSX/Components/SettingsSlice.ts @@ -1,157 +1,159 @@ -//****************************************************************************************************** -// SettingsSlice.tsx - Gbtc -// -// Copyright © 2023, Grid Protection Alliance. All Rights Reserved. -// -// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See -// the NOTICE file distributed with this work for additional information regarding copyright ownership. -// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this -// file except in compliance with the License. You may obtain a copy of the License at: -// -// http://opensource.org/licenses/MIT -// -// Unless agreed to in writing, the subject software distributed under the License is distributed on an -// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the -// License for the specific language governing permissions and limitations. -// -// Code Modification History: -// ---------------------------------------------------------------------------------------------------- -// 12/08/2022 - Harrison Stropkay -// Generated original version of source code. -// 01/17/2023 - C Lackner -// Cleaned up Settings code. -// -//****************************************************************************************************** -import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; -import { cloneDeep } from 'lodash'; -import { Redux } from '../global'; - -declare let homePath: string; - -export const LoadSettings = createAsyncThunk('Settings/LoadSettingsThunk', async () => { - return Promise.all([loadTimeZone(), loadWidgetCategories()]) -}); - -const defaultState = { - timeZone: 'UTC', - eventSearch: { - NumberResults: 100, - WidgetCategories: [], - AggregateMagDur: true, - }, - trendData: { - BorderPlots: false, - InsertAtStart: false, - MarkerSnapping: false, - StartWithOptionsClosed: false, - LegendDisplay: 'bottom' - }, - general: { - MoveOptionsLeft: false, - ShowDataPoints: true, - DateTime: 'center' - } -} as Redux.SettingsState; - -const settingsSlice = createSlice({ - name: 'Settings', - - initialState: cloneDeep(defaultState) as Redux.SettingsState, - - reducers: { - SetEventSearch: (state: Redux.SettingsState, action: { type: string, payload: Redux.IEventSearchSettings }) => { - state.eventSearch = action.payload; - saveSettings(state); - }, - SetTrendData: (state: Redux.SettingsState, action: { type: string, payload: Redux.ITrendDataSettings }) => { - state.trendData = action.payload; - saveSettings(state); - }, - SetGeneral: (state: Redux.SettingsState, action: { type: string, payload: Redux.IGeneralSettings }) => { - state.general = action.payload - saveSettings(state); - }, - }, - extraReducers: (builder) => { - - builder.addCase(LoadSettings.fulfilled, (state, action) => { - const preserved = readSettings(); - - Object.keys(preserved).forEach(key => { - if (preserved[key] !== undefined) state[key] = preserved[key]; - else state[key] = cloneDeep(defaultState[key]); - }); - - state.timeZone = cloneDeep(action.payload[0]); - state.eventSearch.WidgetCategories = cloneDeep(action.payload[1]); - return state; - }); - - builder.addCase(LoadSettings.rejected, (state) => { - const preserved = readSettings(); - - Object.keys(preserved).forEach(key => { - if (preserved[key] !== undefined) state[key] = preserved[key]; - else state[key] = cloneDeep(defaultState[key]); - }); - - state.timeZone = 'UTC'; - return state; - }); - } - -}) - -function readSettings() { - let preserved: Redux.SettingsState; - try { - const serializedState = localStorage.getItem('SEBrowser.Settings'); - if (serializedState === null) throw new Error("No setting state found") - preserved = JSON.parse(serializedState); - return preserved; - } catch (err) { - return cloneDeep(defaultState); - } -} - -function saveSettings(state: Redux.SettingsState) { - try { - const serializedState = JSON.stringify(state); - localStorage.setItem('SEBrowser.Settings', serializedState); - } catch { - // ignore write errors - } -} - -function loadTimeZone() { - return $.ajax({ - type: "GET", - url: `${homePath}api/SEBrowser/GetTimeZone`, - contentType: "application/json; charset=utf-8", - dataType: 'json', - cache: true, - async: true - }); - -} - -function loadWidgetCategories() { - return $.ajax({ - type: "GET", - url: `${homePath}api/openXDA/WidgetCategory`, - contentType: "application/json; charset=utf-8", - dataType: 'json', - cache: true, - async: true - }); -} - - -export const SettingsReducer = settingsSlice.reducer -export const { SetEventSearch, SetTrendData, SetGeneral } = settingsSlice.actions -export const SelectEventSearchSettings = (state: Redux.StoreState) => state.Settings.eventSearch -export const SelectTrendDataSettings = (state: Redux.StoreState) => state.Settings.trendData -export const SelectGeneralSettings = (state: Redux.StoreState) => state.Settings.general -export const SelectTimeZone = (state: Redux.StoreState) => state.Settings.timeZone -export const SelectWidgetCategories = (state: Redux.StoreState) => state.Settings.eventSearch.WidgetCategories -export const SelectDateTimeSetting = (state: Redux.StoreState) => state.Settings.general.DateTime \ No newline at end of file +//****************************************************************************************************** +// SettingsSlice.tsx - Gbtc +// +// Copyright © 2023, Grid Protection Alliance. All Rights Reserved. +// +// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See +// the NOTICE file distributed with this work for additional information regarding copyright ownership. +// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this +// file except in compliance with the License. You may obtain a copy of the License at: +// +// http://opensource.org/licenses/MIT +// +// Unless agreed to in writing, the subject software distributed under the License is distributed on an +// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the +// License for the specific language governing permissions and limitations. +// +// Code Modification History: +// ---------------------------------------------------------------------------------------------------- +// 12/08/2022 - Harrison Stropkay +// Generated original version of source code. +// 01/17/2023 - C Lackner +// Cleaned up Settings code. +// +//****************************************************************************************************** +import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; +import { cloneDeep } from 'lodash'; +import { Redux } from '../global'; + +declare let homePath: string; + +export const LoadSettings = createAsyncThunk('Settings/LoadSettingsThunk', async () => { + return Promise.all([loadTimeZone(), loadWidgetCategories()]) +}); + +const defaultState = { + timeZone: 'UTC', + eventSearch: { + NumberResults: 100, + WidgetCategories: [], + AggregateMagDur: true, + }, + trendData: { + BorderPlots: false, + InsertAtStart: false, + MarkerSnapping: false, + StartWithOptionsClosed: false, + LegendDisplay: 'bottom' + }, + general: { + MoveOptionsLeft: false, + ShowDataPoints: true, + DateTimeMode: 'startEnd', + DateTimeFormat: 'MM/DD/YYYY HH:mm:ss.SSS' + } +} as Redux.SettingsState; + +const settingsSlice = createSlice({ + name: 'Settings', + + initialState: cloneDeep(defaultState) as Redux.SettingsState, + + reducers: { + SetEventSearch: (state: Redux.SettingsState, action: { type: string, payload: Redux.IEventSearchSettings }) => { + state.eventSearch = action.payload; + saveSettings(state); + }, + SetTrendData: (state: Redux.SettingsState, action: { type: string, payload: Redux.ITrendDataSettings }) => { + state.trendData = action.payload; + saveSettings(state); + }, + SetGeneral: (state: Redux.SettingsState, action: { type: string, payload: Redux.IGeneralSettings }) => { + state.general = action.payload + saveSettings(state); + }, + }, + extraReducers: (builder) => { + + builder.addCase(LoadSettings.fulfilled, (state, action) => { + const preserved = readSettings(); + + Object.keys(preserved).forEach(key => { + if (preserved[key] !== undefined) state[key] = preserved[key]; + else state[key] = cloneDeep(defaultState[key]); + }); + + state.timeZone = cloneDeep(action.payload[0]); + state.eventSearch.WidgetCategories = cloneDeep(action.payload[1]); + return state; + }); + + builder.addCase(LoadSettings.rejected, (state) => { + const preserved = readSettings(); + + Object.keys(preserved).forEach(key => { + if (preserved[key] !== undefined) state[key] = preserved[key]; + else state[key] = cloneDeep(defaultState[key]); + }); + + state.timeZone = 'UTC'; + return state; + }); + } + +}) + +function readSettings() { + let preserved: Redux.SettingsState; + try { + const serializedState = localStorage.getItem('SEBrowser.Settings'); + if (serializedState === null) throw new Error("No setting state found"); + preserved = JSON.parse(serializedState); + return preserved; + } catch (err) { + return cloneDeep(defaultState); + } +} + +function saveSettings(state: Redux.SettingsState) { + try { + const serializedState = JSON.stringify(state); + localStorage.setItem('SEBrowser.Settings', serializedState); + } catch { + // ignore write errors + } +} + +function loadTimeZone() { + return $.ajax({ + type: "GET", + url: `${homePath}api/SEBrowser/GetTimeZone`, + contentType: "application/json; charset=utf-8", + dataType: 'json', + cache: true, + async: true + }); + +} + +function loadWidgetCategories() { + return $.ajax({ + type: "GET", + url: `${homePath}api/openXDA/WidgetCategory`, + contentType: "application/json; charset=utf-8", + dataType: 'json', + cache: true, + async: true + }); +} + + +export const SettingsReducer = settingsSlice.reducer +export const { SetEventSearch, SetTrendData, SetGeneral } = settingsSlice.actions +export const SelectEventSearchSettings = (state: Redux.StoreState) => state.Settings.eventSearch +export const SelectTrendDataSettings = (state: Redux.StoreState) => state.Settings.trendData +export const SelectGeneralSettings = (state: Redux.StoreState) => state.Settings.general +export const SelectTimeZone = (state: Redux.StoreState) => state.Settings.timeZone +export const SelectWidgetCategories = (state: Redux.StoreState) => state.Settings.eventSearch.WidgetCategories +export const SelectDateTimeSetting = (state: Redux.StoreState) => state.Settings.general.DateTimeMode +export const SelectDateTimeFormat = (state: Redux.StoreState) => state.Settings.general.DateTimeFormat \ No newline at end of file diff --git a/SEBrowser/Scripts/TSX/Components/TrendData/Components/TrendChannelTable.tsx b/SEBrowser/Scripts/TSX/Components/TrendData/Components/TrendChannelTable.tsx index 7850f993b..7e03b4663 100644 --- a/SEBrowser/Scripts/TSX/Components/TrendData/Components/TrendChannelTable.tsx +++ b/SEBrowser/Scripts/TSX/Components/TrendData/Components/TrendChannelTable.tsx @@ -22,7 +22,7 @@ //****************************************************************************************************** import React from 'react'; import _ from 'lodash'; -import { TrendSearch } from '../../../Global'; +import { TrendSearch } from '../../../global'; import { ConfigTable } from '@gpa-gemstone/react-interactive'; import { TrashCan } from '@gpa-gemstone/gpa-symbols'; import { ReactTable } from '@gpa-gemstone/react-table'; @@ -137,7 +137,7 @@ const TrendChannelTable = (props: IProps) => { }} Selected={(item) => props.Type === 'multi' ? props.SelectedSet.has(item.ID) : props.Selected === item.ID} TheadStyle={{ fontSize: 'smaller', display: 'table', tableLayout: 'fixed', width: '100%' }} - TbodyStyle={{ display: 'block', overflowY: 'scroll', height: props.Height - 45, userSelect: 'none'}} + TbodyStyle={{ display: 'block', overflowY: 'scroll', height: props.Height - 45, userSelect: 'none' }} RowStyle={{ display: 'table', tableLayout: 'fixed', width: 'calc(100%)' }} TableClass="table table-hover" TableStyle={{ marginBottom: 0 }}> @@ -168,7 +168,7 @@ const TrendChannelTable = (props: IProps) => { RowStyle={{ width: "50px" }} HeaderStyle={{ width: "50px" }} Content={removeButton} - > : null} + > : null} ); } diff --git a/SEBrowser/Scripts/TSX/Components/TrendData/Components/TrendMarkerTable.tsx b/SEBrowser/Scripts/TSX/Components/TrendData/Components/TrendMarkerTable.tsx index 3b182d6a2..1e298525b 100644 --- a/SEBrowser/Scripts/TSX/Components/TrendData/Components/TrendMarkerTable.tsx +++ b/SEBrowser/Scripts/TSX/Components/TrendData/Components/TrendMarkerTable.tsx @@ -26,6 +26,8 @@ import { TrashCan } from '@gpa-gemstone/gpa-symbols'; import Table from '@gpa-gemstone/react-table'; import { TrendSearch } from '../../../global'; import moment from 'moment'; +import { useSelector } from 'react-redux'; +import { SelectDateTimeFormat } from '../../SettingsSlice'; interface IProps { Markers: TrendSearch.IMarker[], @@ -37,10 +39,11 @@ interface IProps { } const TrendMarkerTable = (props: IProps) => { + const dateTimeFormat = useSelector(SelectDateTimeFormat); + const [trendMarkers, setTrendMarkers] = React.useState([]); const [sortField, setSortField] = React.useState('MeterName'); const [ascending, setAscending] = React.useState(true); - const momentFormat = "DD HH:mm:ss.SSS"; React.useEffect(() => { setTrendMarkers(_.orderBy(props.Markers, sortField, (ascending ? 'asc' : 'desc'))); @@ -94,9 +97,9 @@ const TrendMarkerTable = (props: IProps) => { } else { switch (item.type) { case "VeHo": - return (item["isHori"] ?? true) ? item["value"].toFixed(2) : moment.utc(item["value"]).format(momentFormat); + return (item["isHori"] ?? true) ? item["value"].toFixed(2) : moment.utc(item["value"]).format(dateTimeFormat); case "Symb": - return `${moment.utc(item["xPos"]).format(momentFormat)} | ${item["yPos"].toFixed(2)}`; + return `${moment.utc(item["xPos"]).format(dateTimeFormat)} | ${item["yPos"].toFixed(2)}`; default: return "All Events"; } diff --git a/SEBrowser/Scripts/TSX/Components/TrendData/Settings/AllSettingsModal.tsx b/SEBrowser/Scripts/TSX/Components/TrendData/Settings/AllSettingsModal.tsx index 01eccc7a3..80d7cb185 100644 --- a/SEBrowser/Scripts/TSX/Components/TrendData/Settings/AllSettingsModal.tsx +++ b/SEBrowser/Scripts/TSX/Components/TrendData/Settings/AllSettingsModal.tsx @@ -28,7 +28,7 @@ import { PlotSettingsTab } from './OverlayTabs/PlotSettingsTab'; import { MarkerTab } from './OverlayTabs/MarkerTab'; import { LineStylesTab } from './OverlayTabs/LineStylesTab'; import { ColorTab } from './OverlayTabs/ColorTab'; -import { TrendSearch } from '../../../Global'; +import { TrendSearch } from '../../../global'; interface IProps { Show: boolean, @@ -67,7 +67,7 @@ const AllSettingsModal = React.memo((props: IProps) => { if (!_.isEqual(allPlot[field], props.Defaults[field])) props.ApplyFieldToAll(allPlot, field as keyof (TrendSearch.ITrendPlot)); }); - } + } // Settings defaults if (confirmed || futureOnly) { // Handling Markers diff --git a/SEBrowser/Scripts/TSX/Components/TrendData/Settings/OverlayTabs/ColorTab.tsx b/SEBrowser/Scripts/TSX/Components/TrendData/Settings/OverlayTabs/ColorTab.tsx index 1ca2910dd..ea5bdf849 100644 --- a/SEBrowser/Scripts/TSX/Components/TrendData/Settings/OverlayTabs/ColorTab.tsx +++ b/SEBrowser/Scripts/TSX/Components/TrendData/Settings/OverlayTabs/ColorTab.tsx @@ -22,10 +22,8 @@ //****************************************************************************************************** import { Input, Select } from '@gpa-gemstone/react-forms'; import { ReactTable } from '@gpa-gemstone/react-table'; -import { Column } from '@gpa-gemstone/react-table'; import React from 'react'; -import { TrendSearch } from '../../../../Global'; -import { LineStyles } from '../TabProperties/LineStyles'; +import { TrendSearch } from '../../../../global'; import { TrashCan, UpArrow, DownArrow } from '@gpa-gemstone/gpa-symbols'; import { BlockPicker } from 'react-color'; import _ from 'lodash'; @@ -69,7 +67,7 @@ const ColorTab = React.memo((props: IColorTabProps) => { return (
    -
    +

    These settings will only apply to line plots.
    diff --git a/SEBrowser/Scripts/TSX/Components/TrendData/Settings/OverlayTabs/LineStylesTab.tsx b/SEBrowser/Scripts/TSX/Components/TrendData/Settings/OverlayTabs/LineStylesTab.tsx index 42ae411fc..0caece9cd 100644 --- a/SEBrowser/Scripts/TSX/Components/TrendData/Settings/OverlayTabs/LineStylesTab.tsx +++ b/SEBrowser/Scripts/TSX/Components/TrendData/Settings/OverlayTabs/LineStylesTab.tsx @@ -21,7 +21,7 @@ // //****************************************************************************************************** import React from 'react'; -import { TrendSearch } from '../../../../Global'; +import { TrendSearch } from '../../../../global'; import { LineStyles } from '../TabProperties/LineStyles' interface IChannelTabProps { @@ -36,7 +36,7 @@ interface IChannelTabProps { const LineStylesTab = React.memo((props: IChannelTabProps) => { return (
    -
    +
    These settings will only apply to line plots.
    diff --git a/SEBrowser/Scripts/TSX/Components/TrendData/Settings/OverlayTabs/PlotSettingsTab.tsx b/SEBrowser/Scripts/TSX/Components/TrendData/Settings/OverlayTabs/PlotSettingsTab.tsx index 8f835cfbd..1399f6431 100644 --- a/SEBrowser/Scripts/TSX/Components/TrendData/Settings/OverlayTabs/PlotSettingsTab.tsx +++ b/SEBrowser/Scripts/TSX/Components/TrendData/Settings/OverlayTabs/PlotSettingsTab.tsx @@ -21,10 +21,11 @@ // //****************************************************************************************************** import React from 'react'; -import _ from 'lodash'; -import { IMultiCheckboxOption, TrendSearch } from '../../../../global'; -import ReportTimeFilter from '../../../ReportTimeFilter'; +import { IMultiCheckboxOption, SEBrowser, TrendSearch } from '../../../../global'; +import { TimeFilter } from '@gpa-gemstone/common-pages' import { CheckBox, Input, MultiCheckBoxSelect, Select } from '@gpa-gemstone/react-forms'; +import { useSelector } from 'react-redux'; +import { SelectTimeZone, SelectDateTimeSetting } from '../../../SettingsSlice'; interface IProps { Plot: TrendSearch.ITrendPlot, @@ -44,6 +45,8 @@ const axisOptions: { Value: string, Label: string }[] = [ const PlotSettingsTab = React.memo((props: IProps) => { const [limits, setLimits] = React.useState({ LeftUpper: 1, LeftLower: 0, RightUpper: 1, RightLower: 0 }); + const timeZone = useSelector(SelectTimeZone); + const dateTimeSetting = useSelector(SelectDateTimeSetting); const setPlotLimits = React.useCallback((limits: AxisLimits) => { const newPlot = { ...props.Plot }; @@ -79,17 +82,26 @@ const PlotSettingsTab = React.memo((props: IProps) => { } return true; } - + function isValid(): boolean { return validateTrendPlot('Height') && validateTrendPlot('Width') && validateLimit("LeftUpper") && validateLimit("RightUpper"); } + // Wrapper function to match the expected type for setFilter + const handleSetFilter = (start: string, end: string) => { + const newFilter = { + start: start, + end: end, + } + props.SetPlot({ ...props.Plot, TimeFilter: newFilter }) + }; + return (
    Plot Settings:
    -
    +
    Record={props.Plot} Label={'Plot Title'} Field={'Title'} Setter={props.SetPlot} Valid={() => true} />
    @@ -134,8 +146,9 @@ const PlotSettingsTab = React.memo((props: IProps) => { props.SetPlot({ ...props.Plot, PlotFilter: options }); }} /> - props.SetPlot({ ...props.Plot, TimeFilter: newFilter })} /> +
    Axis Limits:
    @@ -152,17 +165,17 @@ const PlotSettingsTab = React.memo((props: IProps) => {
    Record={limits} Setter={setPlotLimits} Valid={validateLimit} Feedback={limitFeedback} - Label='Left Axis Upper' Field='LeftUpper' Type='integer' Disabled={props.Plot.AxisZoom !== 'Manual'}/> + Label='Left Axis Upper' Field='LeftUpper' Type='integer' Disabled={props.Plot.AxisZoom !== 'Manual'} />
    Record={limits} Setter={setPlotLimits} Valid={validateLimit} Feedback={limitFeedback} - Label='Right Axis Lower' Field='RightLower' Type='integer' Disabled={props.Plot.AxisZoom !== 'Manual'}/> + Label='Right Axis Lower' Field='RightLower' Type='integer' Disabled={props.Plot.AxisZoom !== 'Manual'} />
    Record={limits} Setter={setPlotLimits} Valid={validateLimit} Feedback={limitFeedback} - Label='Right Axis Upper' Field='RightUpper' Type='integer' Disabled={props.Plot.AxisZoom !== 'Manual'}/> + Label='Right Axis Upper' Field='RightUpper' Type='integer' Disabled={props.Plot.AxisZoom !== 'Manual'} />
    diff --git a/SEBrowser/Scripts/TSX/Components/TrendData/Settings/TabProperties/LineSettings.tsx b/SEBrowser/Scripts/TSX/Components/TrendData/Settings/TabProperties/LineSettings.tsx index c2a942fc4..580b4563a 100644 --- a/SEBrowser/Scripts/TSX/Components/TrendData/Settings/TabProperties/LineSettings.tsx +++ b/SEBrowser/Scripts/TSX/Components/TrendData/Settings/TabProperties/LineSettings.tsx @@ -24,7 +24,7 @@ import React from 'react'; import { BlockPicker } from 'react-color'; import { Input, Select } from '@gpa-gemstone/react-forms'; import { LineTypeOptions, AxisOptions } from '../SettingsModal'; -import { TrendSearch } from '../../../../Global'; +import { TrendSearch } from '../../../../global'; interface ILineProps { // Assumption that this doesnt change outside of this overlay diff --git a/SEBrowser/Scripts/TSX/Components/TrendData/Settings/TabProperties/LineStyles.tsx b/SEBrowser/Scripts/TSX/Components/TrendData/Settings/TabProperties/LineStyles.tsx index 20bcb52d9..c91f9d166 100644 --- a/SEBrowser/Scripts/TSX/Components/TrendData/Settings/TabProperties/LineStyles.tsx +++ b/SEBrowser/Scripts/TSX/Components/TrendData/Settings/TabProperties/LineStyles.tsx @@ -23,7 +23,7 @@ import React from 'react'; import { Input, Select } from '@gpa-gemstone/react-forms'; import { LineTypeOptions } from '../SettingsModal'; -import { TrendSearch } from '../../../../Global'; +import { TrendSearch } from '../../../../global'; interface IChannelTabProps { DefaultStyle: TrendSearch.ILineStyleSettings, @@ -42,7 +42,7 @@ const LineStyles = React.memo((props: IChannelTabProps) => { }} /> Record={props.DefaultStyle} Label={'Line Style'} Field={'Type'} Setter={props.SetDefaultStyle} Options={LineTypeOptions} /> -
    +
    ); }); diff --git a/SEBrowser/Scripts/TSX/Components/TrendData/TrendData.tsx b/SEBrowser/Scripts/TSX/Components/TrendData/TrendData.tsx index 0902bf5c1..293e3a25a 100644 --- a/SEBrowser/Scripts/TSX/Components/TrendData/TrendData.tsx +++ b/SEBrowser/Scripts/TSX/Components/TrendData/TrendData.tsx @@ -26,22 +26,24 @@ import moment from 'moment'; import _ from 'lodash'; import TrendSearchNavbar from './TrendDataNavbar'; import TrendPlot from './TrendPlot/TrendPlot'; -import { TrendSearch } from '../../Global'; +import { TrendSearch } from '../../global'; import AllSettingsModal from './Settings/AllSettingsModal'; -import { SelectTrendDataSettings } from './../SettingsSlice'; +import { SelectDateTimeFormat, SelectTrendDataSettings } from './../SettingsSlice'; import { useAppSelector } from './../../hooks'; import { SVGIcons } from '@gpa-gemstone/gpa-symbols'; +import { useSelector } from 'react-redux'; -const momentDateFormat = "MM/DD/YYYY"; const trendSearchId = "TrendDataChartAll"; const defaultsIgnored = new Set(["ID", "TimeFilter", "Type", "Channels", "PlotFilter"]); const TrendData = () => { + const dateTimeFormat = useSelector(SelectDateTimeFormat); + const closureHandler = React.useRef<((o: boolean) => void)>(() => { return; }); const [showNav, setShowNav] = React.useState(getShowNav()); const [plotList, setPlotList] = React.useState([]); const [defaultPlotSettings, setDefaultPlotSettings] = React.useState({ - TimeFilter: { date: moment.utc().format(momentDateFormat), time: '12:00:00.000', windowSize: 12, timeWindowUnits: 3 }, + TimeFilter: { start: moment.utc().format(dateTimeFormat), end: moment.utc().add(12, 'hours').format(dateTimeFormat) }, Type: 'Line', Channels: [], PlotFilter: [{ Text: "Minimum", Value: "min", Selected: true }, { Text: "Maximum", Value: "max", Selected: true }, { Text: "Average/Values", Value: "avg", Selected: true }], diff --git a/SEBrowser/Scripts/TSX/Components/TrendData/TrendDataNavbar.tsx b/SEBrowser/Scripts/TSX/Components/TrendData/TrendDataNavbar.tsx index a43aff4d1..1980d7023 100644 --- a/SEBrowser/Scripts/TSX/Components/TrendData/TrendDataNavbar.tsx +++ b/SEBrowser/Scripts/TSX/Components/TrendData/TrendDataNavbar.tsx @@ -26,19 +26,21 @@ import _ from 'lodash'; import { useAppDispatch, useAppSelector } from '../../hooks'; import { useLocation, useNavigate } from 'react-router-dom'; import { AssetSlice, MeterSlice, PhaseSlice, ChannelGroupSlice } from '../../Store'; -import { SEBrowser, TrendSearch, IMultiCheckboxOption } from '../../Global'; +import { SEBrowser, TrendSearch, IMultiCheckboxOption } from '../../global'; import { SystemCenter } from '@gpa-gemstone/application-typings'; import { MultiCheckBoxSelect } from '@gpa-gemstone/react-forms'; -import { DefaultSelects } from '@gpa-gemstone/common-pages'; +import { DefaultSelects, TimeFilter } from '@gpa-gemstone/common-pages'; import { Search, ToolTip } from '@gpa-gemstone/react-interactive'; import { CrossMark, SVGIcons } from '@gpa-gemstone/gpa-symbols'; import { CreateGuid } from '@gpa-gemstone/helper-functions'; -import ReportTimeFilter from '../ReportTimeFilter'; import NavbarFilterButton from '../Common/NavbarFilterButton'; import TrendChannelTable from './Components/TrendChannelTable'; import html2canvas from 'html2canvas'; import jspdf from 'jspdf'; import queryString from 'querystring'; +import { useSelector } from 'react-redux'; +import { SelectTimeZone, SelectDateTimeSetting } from '../SettingsSlice'; +import moment from 'moment'; interface IProps { ToggleVis: () => void, @@ -65,6 +67,9 @@ interface ITrendDataFilter { AssetList: SystemCenter.Types.DetailedAsset[] } +type TimeUnit = 'y' | 'M' | 'w' | 'd' | 'h' | 'm' | 's' | 'ms' +const units = ['ms', 's', 'm', 'h', 'd', 'w', 'M', 'y'] as TimeUnit[] + const TrendSearchNavbar = React.memo((props: IProps) => { const navRef = React.useRef(null); const timeRef = React.useRef(null); @@ -76,6 +81,9 @@ const TrendSearchNavbar = React.memo((props: IProps) => { const phaseStatus = useAppSelector(PhaseSlice.SearchStatus); const allPhases = useAppSelector(PhaseSlice.SearchResults); + const timeZone = useSelector(SelectTimeZone); + const dateTimeSetting = useSelector(SelectDateTimeSetting); + const channelGroupStatus = useAppSelector(ChannelGroupSlice.SearchStatus); const allChannelGroups = useAppSelector(ChannelGroupSlice.SearchResults); @@ -85,9 +93,12 @@ const TrendSearchNavbar = React.memo((props: IProps) => { const assetStatus = useAppSelector(AssetSlice.Status); const allAssets = useAppSelector(AssetSlice.Data); + const [dateTimeFormat, setDateTimeFormat] = React.useState(dateTimeSetting.DateTimeFormat); + const [dateTimeMode, setDateTimeMode] = React.useState(dateTimeSetting.Mode); const [showFilter, setShowFilter] = React.useState<('None' | 'Meter' | 'Asset')>('None'); const [timeFilter, setTimeFilter] = React.useState(props.TimeFilter); + const [timeRange, setTimeRange] = React.useState(''); const [trendFilter, setTrendFilter] = React.useState(null); const [phaseOptions, setPhaseOptions] = React.useState([]); @@ -102,7 +113,7 @@ const TrendSearchNavbar = React.memo((props: IProps) => { const [queryReady, setQueryReady] = React.useState(false); // Button Consts - const [hover, setHover] = React.useState < 'None' | 'Show' | 'Hide' | 'Cog' | 'Single-Line' | 'Multi-Line' | 'Group-Line' | 'Cyclic' | 'Move' | 'Trash' | 'Select' | 'Capture' >('None'); + const [hover, setHover] = React.useState<'None' | 'Show' | 'Hide' | 'Cog' | 'Single-Line' | 'Multi-Line' | 'Group-Line' | 'Cyclic' | 'Move' | 'Trash' | 'Select' | 'Capture'>('None'); // Page effects React.useLayoutEffect(() => { @@ -153,7 +164,7 @@ const TrendSearchNavbar = React.memo((props: IProps) => { }, [props.LinePlot]); React.useEffect(() => { - if (queryReady) setTimeFilter(props.TimeFilter); + setTimeFilter(props.TimeFilter); }, [props.TimeFilter]); // Slice dispatches @@ -236,6 +247,43 @@ const TrendSearchNavbar = React.memo((props: IProps) => { AssetList: queryRef.current.assetIds == null ? [] : allAssets?.filter(asset => queryRef.current.assetIds.has(asset.ID)) ?? [] }); }, [channelGroupStatus, phaseStatus, meterStatus, assetStatus, queryReady]); + React.useEffect(() => { + let range = ""; + const startMoment = moment(timeFilter.start); // These default to the date+time format + const endMoment = moment(timeFilter.end); + const unit = findAppropriateUnit(startMoment, endMoment); + const startEndDifference = startMoment.diff(endMoment, unit); + + if (dateTimeSetting == 'startEnd') + range = `${timeFilter.start} to ${timeFilter.end} (${timeZone})`; + if (dateTimeSetting == 'startWindow') + range = `${timeFilter.start} (${timeZone}) +${startEndDifference}`; + else if (dateTimeSetting == 'endWindow') + range = `${timeFilter.end} (${timeZone}) -${startEndDifference}`; + + setTimeRange(range); + }, [timeFilter, dateTimeSetting, timeZone]) + + function findAppropriateUnit(startTime: moment.Moment, endTime: moment.Moment): TimeUnit { + const unitIndex = 7; + let diff = endTime.diff(startTime, units[unitIndex], true); + + for (let i = unitIndex; i >= 1; i--) { + if (Number.isInteger(diff)) { + return units[i]; + } + const nextI = i - 1; + + diff = endTime.diff(startTime, units[nextI], true); + + if (diff > 65000) { + diff = endTime.diff(startTime, units[i], true); + return units[i]; + } + } + + return units[0]; + } function makeMultiCheckboxOptions(keyValues: IKeyValuePair[], setOptions: (options: IMultiCheckboxOption[]) => void, allKeys: { ID: number, Name: string, Description: string }[]) { if (allKeys == null || keyValues == null) return; @@ -271,8 +319,6 @@ const TrendSearchNavbar = React.memo((props: IProps) => { }); } - // TODO: These can be in a shared place with eventSearchBar - function formatWindowUnit(i: number) { if (i == 7) return "Years"; @@ -340,6 +386,13 @@ const TrendSearchNavbar = React.memo((props: IProps) => { }; } + const handleSetFilter = (start: string, end: string) => { + setTimeFilter({ + start: start, + end: end, + }) + }; + function getAdditionalAssetFields(setFields) { const handle = $.ajax({ type: "GET", @@ -376,7 +429,7 @@ const TrendSearchNavbar = React.memo((props: IProps) => { <>
    - {timeFilter.date} {timeFilter.time} +/- {timeFilter.windowSize} {formatWindowUnit(timeFilter.timeWindowUnits)} + {timeRange}
    @@ -394,7 +447,9 @@ const TrendSearchNavbar = React.memo((props: IProps) => { <>
    • - +
    • @@ -456,7 +511,7 @@ const TrendSearchNavbar = React.memo((props: IProps) => { Type='multi' SelectedSet={selectedSet} SetSelectedSet={setSelectedSet} EnableDragDrop={!props.Movable} />
    -
    +
    diff --git a/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/CyclicHistogram.tsx b/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/CyclicHistogram.tsx index 906755e74..826daf930 100644 --- a/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/CyclicHistogram.tsx +++ b/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/CyclicHistogram.tsx @@ -23,8 +23,8 @@ import React from 'react'; import _ from 'lodash'; import moment from 'moment'; -import { IMultiCheckboxOption, SEBrowser, TrendSearch } from '../../../Global'; -import { SelectTrendDataSettings, SelectGeneralSettings } from './../../SettingsSlice'; +import { IMultiCheckboxOption, SEBrowser, TrendSearch } from '../../../global'; +import { SelectTrendDataSettings, SelectGeneralSettings, SelectDateTimeFormat } from './../../SettingsSlice'; import { useAppSelector } from './../../../hooks'; import GraphError from './GraphError'; import { Application } from '@gpa-gemstone/application-typings'; @@ -32,6 +32,7 @@ import { LoadingIcon, ToolTip } from '@gpa-gemstone/react-interactive'; import { HeatMapChart, Plot } from '@gpa-gemstone/react-graph'; import { HexToHsv } from '@gpa-gemstone/helper-functions'; import { Warning } from '@gpa-gemstone/gpa-symbols'; +import { useSelector } from 'react-redux'; interface IProps { ID: string, @@ -54,7 +55,7 @@ interface IProps { children?: React.ReactNode } -interface ICyclicSeries{ +interface ICyclicSeries { Channel: TrendSearch.ITrendChannel, Color: string } @@ -65,30 +66,8 @@ interface IChartData { Series: [number, number, number][] } -// TODO: These can be in a shared place with eventSearchBar -function formatWindowUnit(i: number) { - if (i == 7) - return "years"; - if (i == 6) - return "months"; - if (i == 5) - return "weeks"; - if (i == 4) - return "days"; - if (i == 3) - return "hours"; - if (i == 2) - return "minutes"; - if (i == 1) - return "seconds"; - return "milliseconds"; -} - -// Formats that will be used for dateBoxes -const timeFilterFormat = "MM/DD/YYYYHH:mm:ss.SSS"; -const serverFormat = "YYYY-MM-DD[T]HH:mm:ss.SSSZ"; - const CyclicHistogram = React.memo((props: IProps) => { + const dateTimeFormat = useSelector(SelectDateTimeFormat); // Graph Consts const [timeLimits, setTimeLimits] = React.useState<[number, number]>([0, 1]); const [chartData, setChartData] = React.useState(null); @@ -111,10 +90,10 @@ const CyclicHistogram = React.memo((props: IProps) => { if (props.ChannelInfo == null || props.TimeFilter == null) return; if (_.isEqual(props.TimeFilter, oldValues.current.TimeFilter) && props.ChannelInfo.Channel.ID === oldValues.current.ChannelInfo.Channel.ID) return; - const centerTime: moment.Moment = moment(props.TimeFilter.date + props.TimeFilter.time, timeFilterFormat); - const startTime: string = centerTime.add(-props.TimeFilter.windowSize, formatWindowUnit(props.TimeFilter.timeWindowUnits)).format(serverFormat); - // Need to move back in the other direction, so entire window - const endTime: string = centerTime.add(2 * props.TimeFilter.windowSize, formatWindowUnit(props.TimeFilter.timeWindowUnits)).format(serverFormat); + const startMoment: moment.Moment = moment(props.TimeFilter.start, dateTimeFormat); + const endMoment: moment.Moment = moment(props.TimeFilter.end, dateTimeFormat); + const startTime: string = startMoment.format(dateTimeFormat); + const endTime: string = endMoment.format(dateTimeFormat); const handle = GetMetaData(props.ChannelInfo.Channel.ID, startTime, endTime); return () => { @@ -149,17 +128,17 @@ const CyclicHistogram = React.memo((props: IProps) => { }, [metaData]); React.useEffect(() => { - const centerTime: moment.Moment = moment.utc(props.TimeFilter.date + props.TimeFilter.time, timeFilterFormat); - const startTime: number = centerTime.add(-props.TimeFilter.windowSize, formatWindowUnit(props.TimeFilter.timeWindowUnits)).valueOf(); - // Need to move back in the other direction, so entire window - const endTime: number = centerTime.add(2 * props.TimeFilter.windowSize, formatWindowUnit(props.TimeFilter.timeWindowUnits)).valueOf(); + const startMoment: moment.Moment = moment.utc(props.TimeFilter.start, dateTimeFormat); + const endMoment: moment.Moment = moment.utc(props.TimeFilter.end, dateTimeFormat); + const startTime: number = startMoment.valueOf(); + const endTime: number = endMoment.valueOf(); setTimeLimits([startTime, endTime]); }, [props.TimeFilter]); React.useEffect(() => { if (props.ChannelInfo?.Color == null) return; const color = HexToHsv(props.ChannelInfo.Color); - setBarColor({ Hue: color.h, Saturation: color.s}) + setBarColor({ Hue: color.h, Saturation: color.s }) }, [props.ChannelInfo?.Color]); React.useEffect(() => { @@ -212,8 +191,8 @@ const CyclicHistogram = React.memo((props: IProps) => { cache: false, async: true }).done((data: TrendSearch.ICyclicData[]) => { - const startTicks = moment.utc(metaData.StartTime, serverFormat).valueOf(); - const ticksPerIndex = (moment.utc(metaData.EndTime, serverFormat).valueOf() - startTicks) / + const startTicks = moment.utc(metaData.StartTime, dateTimeFormat).valueOf(); + const ticksPerIndex = (moment.utc(metaData.EndTime, dateTimeFormat).valueOf() - startTicks) / ((metaData.SamplingRate / metaData.FundamentalFrequency) + 1); const binSize = (metaData.CyclesMax - metaData.CyclesMin) / metaData.CyclicHistogramBins; const newChartData: IChartData = { @@ -242,17 +221,21 @@ const CyclicHistogram = React.memo((props: IProps) => {

    {props?.Title ?? ''} - {(chartData?.Series?.length == null || chartData.Series.length === 0) ? - setHover(true)} onMouseLeave={() => setHover(false)}>{Warning} - : null - } + {(chartData?.Series?.length == null || chartData.Series.length === 0) ? + setHover(true)} onMouseLeave={() => setHover(false)}>{Warning} + : null + }

    - captureCallback(0)} cursorOverride={props.Cursor} snapMouse={trendDatasettings.MarkerSnapping} - legend={trendDatasettings.LegendDisplay} useMetricFactors={props.Metric ?? false} holdMenuOpen={!trendDatasettings.StartWithOptionsClosed} showDateOnTimeAxis={false} limitZoom={true} - Tlabel={props.XAxisLabel} Ylabel={[props.YAxisLabel]} showMouse={props.MouseHighlight} yDomain={props.AxisZoom} defaultYdomain={props.DefaultZoom}> + captureCallback(0)} + cursorOverride={props.Cursor} snapMouse={trendDatasettings.MarkerSnapping} legend={trendDatasettings.LegendDisplay} useMetricFactors={props.Metric ?? false} + holdMenuOpen={!trendDatasettings.StartWithOptionsClosed} showDateOnTimeAxis={false} limitZoom={true} + Tlabel={props.XAxisLabel} Ylabel={[props.YAxisLabel]} showMouse={props.MouseHighlight} + yDomain={props.AxisZoom} defaultYdomain={props.DefaultZoom}> {(chartData?.Series?.length == null || chartData.Series.length === 0 || barColor === null) ? null : - + } {props.children} {props.AlwaysRender} diff --git a/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/LineGraph.tsx b/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/LineGraph.tsx index a31d5a61d..79bc4f02a 100644 --- a/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/LineGraph.tsx +++ b/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/LineGraph.tsx @@ -23,14 +23,15 @@ import React from 'react'; import _ from 'lodash'; import moment from 'moment'; -import { IMultiCheckboxOption, SEBrowser, TrendSearch } from '../../../Global'; -import { SelectTrendDataSettings, SelectGeneralSettings } from './../../SettingsSlice'; +import { IMultiCheckboxOption, SEBrowser, TrendSearch } from '../../../global'; +import { SelectTrendDataSettings, SelectGeneralSettings, SelectDateTimeFormat } from './../../SettingsSlice'; import { useAppSelector } from './../../../hooks'; import GraphError from './GraphError'; import { Application } from '@gpa-gemstone/application-typings'; import { LoadingIcon, ToolTip } from '@gpa-gemstone/react-interactive'; import { Line, Plot } from '@gpa-gemstone/react-graph'; import { Warning } from '@gpa-gemstone/gpa-symbols'; +import { useSelector } from 'react-redux'; interface IProps { ID: string, @@ -61,37 +62,15 @@ interface IChartData { AvgSeries: [number, number][] } -// TODO: These can be in a shared place with eventSearchBar -function formatWindowUnit(i: number) { - if (i == 7) - return "years"; - if (i == 6) - return "months"; - if (i == 5) - return "weeks"; - if (i == 4) - return "days"; - if (i == 3) - return "hours"; - if (i == 2) - return "minutes"; - if (i == 1) - return "seconds"; - return "milliseconds"; -} - -// Formats that will be used for dateBoxes -const timeFilterFormat = "MM/DD/YYYYHH:mm:ss.SSS"; -const serverFormat = "YYYY-MM-DD[T]HH:mm:ss.SSSZ"; - const LineGraph = React.memo((props: IProps) => { + const dateTimeFormat = useSelector(SelectDateTimeFormat); // Graph Consts const [timeLimits, setTimeLimits] = React.useState<[number, number]>([0, 1]); const [displayMin, setDisplayMin] = React.useState(true); const [displayMax, setDisplayMax] = React.useState(true); const [displayAvg, setDisplayAvg] = React.useState(true); const [hover, setHover] = React.useState(false); - const [allChartData, setAllChartData] = React.useState>(new Map()); + const [allChartData, setAllChartData] = React.useState>(new Map()); const [graphStatus, setGraphStatus] = React.useState('unintiated'); // Height mangement const [titleHeight, setTitleHeight] = React.useState(0); @@ -99,27 +78,26 @@ const LineGraph = React.memo((props: IProps) => { const [extraLegendHeight, setExtraLegendHeight] = React.useState(0); const titleRef = React.useRef(null); const oldValues = React.useRef<{ ChannelInfo: TrendSearch.ILineSeries[], TimeFilter: SEBrowser.IReportTimeFilter }>({ ChannelInfo: [], TimeFilter: null }); - const trendDatasettings = useAppSelector(SelectTrendDataSettings); + const trendDataSettings = useAppSelector(SelectTrendDataSettings); const generalSettings = useAppSelector(SelectGeneralSettings); React.useLayoutEffect(() => setTitleHeight(titleRef?.current?.offsetHeight ?? 0)); - React.useEffect(() => { if (props.ChannelInfo == null || props.TimeFilter == null) return; - const centerTime: moment.Moment = moment(props.TimeFilter.date + props.TimeFilter.time, timeFilterFormat); - const startTime: string = centerTime.add(-props.TimeFilter.windowSize, formatWindowUnit(props.TimeFilter.timeWindowUnits)).format(serverFormat); - // Need to move back in the other direction, so entire window - const endTime: string = centerTime.add(2 * props.TimeFilter.windowSize, formatWindowUnit(props.TimeFilter.timeWindowUnits)).format(serverFormat); + const startMoment: moment.Moment = moment(props.TimeFilter.start, dateTimeFormat); + const endMoment: moment.Moment = moment(props.TimeFilter.end, dateTimeFormat); + const startTime: string = startMoment.format(dateTimeFormat); + const endTime: string = endMoment.format(dateTimeFormat); let newChannels: number[] = props.ChannelInfo.map(chan => chan.Channel.ID); - let keptOldData: Map = new Map(); + let keptOldData: Map = new Map(); // If the time filter is the same, we only need to ask for information on channels we have not yet seen if (_.isEqual(props.TimeFilter, oldValues.current.TimeFilter)) { newChannels = newChannels.filter(channel => oldValues.current.ChannelInfo.findIndex(oldChannel => oldChannel.Channel.ID === channel) === -1); // This represents data we already have and still need (only makes sense if we aren't changing our time window) keptOldData = allChartData; keptOldData.forEach((data, channelID) => { - if (props.ChannelInfo.findIndex(channel => channel.Channel.ID === Number("0x"+channelID)) < 0) + if (props.ChannelInfo.findIndex(channel => channel.Channel.ID === Number("0x" + channelID)) < 0) keptOldData.delete(channelID); } ); @@ -143,10 +121,11 @@ const LineGraph = React.memo((props: IProps) => { }, [props.PlotFilter]); React.useEffect(() => { - const centerTime: moment.Moment = moment.utc(props.TimeFilter.date + props.TimeFilter.time, timeFilterFormat); - const startTime: number = centerTime.add(-props.TimeFilter.windowSize, formatWindowUnit(props.TimeFilter.timeWindowUnits)).valueOf(); - // Need to move back in the other direction, so entire window - const endTime: number = centerTime.add(2 * props.TimeFilter.windowSize, formatWindowUnit(props.TimeFilter.timeWindowUnits)).valueOf(); + const startMoment: moment.Moment = moment.utc(props.TimeFilter.start, dateTimeFormat); + const endMoment: moment.Moment = moment.utc(props.TimeFilter.end, dateTimeFormat); + + const startTime: number = startMoment.valueOf(); + const endTime: number = endMoment.valueOf(); setTimeLimits([startTime, endTime]); }, [props.TimeFilter]); @@ -185,7 +164,7 @@ const LineGraph = React.memo((props: IProps) => { console.error("Failed to parse point: " + jsonPoint); } if (point !== undefined) { - const timeStamp = moment.utc(point.Timestamp, serverFormat).valueOf(); + const timeStamp = moment.utc(point.Timestamp, dateTimeFormat).valueOf(); if (cachedData.has(point.Tag)) { const chartData = cachedData.get(point.Tag); chartData.MinSeries.push([timeStamp, point.Minimum]); @@ -235,12 +214,12 @@ const LineGraph = React.memo((props: IProps) => { {props.AlwaysRender} - ); + ); else return (
    -

    +

    {props?.Title ?? ''} {props?.ChannelInfo == null || props.ChannelInfo.findIndex(info => !(info.Min.HasData || info.Max.HasData || info.Avg.HasData)) != -1 ? setHover(true)} onMouseLeave={() => setHover(false)}>{Warning} @@ -248,8 +227,8 @@ const LineGraph = React.memo((props: IProps) => { }

    captureCallback(0)} cursorOverride={props.Cursor} snapMouse={trendDatasettings.MarkerSnapping} - legend={trendDatasettings.LegendDisplay} useMetricFactors={props.Metric ?? false} holdMenuOpen={!trendDatasettings.StartWithOptionsClosed} showDateOnTimeAxis={false} limitZoom={true} + defaultTdomain={timeLimits} onSelect={props.OnSelect} onCapture={captureCallback} onCaptureComplete={() => captureCallback(0)} cursorOverride={props.Cursor} snapMouse={trendDataSettings.MarkerSnapping} + legend={trendDataSettings.LegendDisplay} useMetricFactors={props.Metric ?? false} holdMenuOpen={!trendDataSettings.StartWithOptionsClosed} showDateOnTimeAxis={false} limitZoom={true} Tlabel={props.XAxisLabel} Ylabel={[props.YLeftLabel, props.YRightLabel]} showMouse={props.MouseHighlight} yDomain={props.AxisZoom} defaultYdomain={props.DefaultZoom}> {props?.ChannelInfo == null ? null : props.ChannelInfo.map((series, index) => { const lineArray: JSX.Element[] = []; diff --git a/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/TrendPlot.tsx b/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/TrendPlot.tsx index 12df1df4a..6ac52a870 100644 --- a/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/TrendPlot.tsx +++ b/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/TrendPlot.tsx @@ -23,6 +23,7 @@ import React from 'react'; import _ from 'lodash'; import queryString from 'querystring'; +import { useSelector } from 'react-redux'; import moment from 'moment'; import { CreateGuid, SpacedColor } from '@gpa-gemstone/helper-functions'; import { TrashCan, Pencil, Plus } from '@gpa-gemstone/gpa-symbols'; @@ -30,15 +31,13 @@ import { Button, SymbolicMarker, Infobox, VerticalMarker, HorizontalMarker, Axis import { SystemCenter } from '@gpa-gemstone/application-typings'; import { LineGraph } from './LineGraph'; import { SEBrowser, TrendSearch } from '../../../global'; -import { SelectTrendDataSettings, SelectGeneralSettings } from './../../SettingsSlice'; +import { SelectTrendDataSettings, SelectGeneralSettings, SelectDateTimeFormat } from './../../SettingsSlice'; import { useAppSelector } from './../../../hooks'; import { GenerateQueryParams } from '../../EventSearch/EventSearchSlice'; -import { momentDateFormat, momentTimeFormat } from '../../ReportTimeFilter'; import { SettingsModal, SeriesSettings } from '../Settings/SettingsModal'; import { CyclicHistogram, ICyclicSeries } from './CyclicHistogram'; type customSelects = "drag" | "symbol" | "horizontal" | "vertical"; -const eventFormat = "MM/DD/YYYY[
    ]hh:mm:ss.SSSSSSS"; const defaultSelect = "drag"; const defaultHighlight = "vertical"; const defaultCursor = undefined; @@ -80,9 +79,10 @@ const TrendPlot: React.FunctionComponent = (props: IContainerPr // Plot Saved Settings const generalSettings = useAppSelector(SelectGeneralSettings); + const dateTimeFormat = useSelector(SelectDateTimeFormat); const trendDatasettings = useAppSelector(SelectTrendDataSettings); const [plotAllSeriesSettings, setPlotAllSeriesSettings] = React.useState(null); - const colorIndex = React.useRef<{ ind: number, assetMap: Map }>({ind: -1, assetMap: new Map()}); + const colorIndex = React.useRef<{ ind: number, assetMap: Map }>({ ind: -1, assetMap: new Map() }); const changedProperties = React.useRef>(new Set()); // Plot Markers @@ -364,8 +364,7 @@ const TrendPlot: React.FunctionComponent = (props: IContainerPr url: `${homePath}api/OpenXDA/GetEventSearchData`, contentType: "application/json; charset=utf-8", data: JSON.stringify({ - date: timeFilter.date, time: timeFilter.time, - windowSize: timeFilter.windowSize, timeWindowUnits: timeFilter.timeWindowUnits, + start: timeFilter.start, end: timeFilter.end, meterIDs: meters, assetIDs: assets, locationIDs: [], groupIDs: [], curveOutside: true, curveInside: true, numberResults: 15, @@ -376,7 +375,7 @@ const TrendPlot: React.FunctionComponent = (props: IContainerPr }).done((data: any[]) => { setEventMarkers(data.map(datum => { const meterID = props.Plot.Channels.find(channel => channel.MeterKey === datum["Meter Key"]).MeterID; - return { value: moment.utc(datum.Time, eventFormat).valueOf(), meterID: meterID, eventID: datum["EventID"]} + return { value: moment.utc(datum.Time, dateTimeFormat).valueOf(), meterID: meterID, eventID: datum["EventID"] } })); }); } @@ -393,10 +392,8 @@ const TrendPlot: React.FunctionComponent = (props: IContainerPr } const time = moment.utc(marker.value, "x"); const timeFilter: SEBrowser.IReportTimeFilter = { - date: time.format(momentDateFormat), - time: time.format(momentTimeFormat), - windowSize: 1, - timeWindowUnits: 3, + start: time.format(dateTimeFormat), + end: time.format(dateTimeFormat), } const queryParams = GenerateQueryParams(null, [], timeFilter, [], [], [meter], [], marker.eventID); const queryUrl = queryString.stringify(queryParams, "&", "=", { encodeURIComponent: queryString.escape }); @@ -484,7 +481,7 @@ const TrendPlot: React.FunctionComponent = (props: IContainerPr } case "horizontal": isHori = true; - // falls through + // falls through case "vertical": { const currentMarkers = [...horiVertMarkers]; const newMarker = _.cloneDeep(props.MarkerDefaults.VeHo.Default); @@ -513,9 +510,9 @@ const TrendPlot: React.FunctionComponent = (props: IContainerPr newList[index]["value"] = value; setHoriVertMarkers(newList); }, [horiVertMarkers, setHoriVertMarkers]); - + const setHoverPosition = React.useCallback((xArg: number, yArg: number) => { - setMousePosition({x: xArg, y: yArg}) + setMousePosition({ x: xArg, y: yArg }) }, [setMousePosition]); let plotBody = null; @@ -531,10 +528,21 @@ const TrendPlot: React.FunctionComponent = (props: IContainerPr switch (props.Plot.Type) { case 'Line': plotBody = ( - + {props.Plot.ShowEvents ? eventMarkers.map((marker, i) => { if (eventSettings.type === "Event-Vert") return ( @@ -544,7 +552,7 @@ const TrendPlot: React.FunctionComponent = (props: IContainerPr else return ( + usePixelPositioning={{ x: false, y: true }} xPos={marker.value} yPos={eventSettings.alignTop ? 11 : -11} radius={12}> {eventSettings.symbol} ); }) : null} @@ -595,10 +603,20 @@ const TrendPlot: React.FunctionComponent = (props: IContainerPr break; case ('Cyclic'): plotBody = ( - 0 ? plotAllSeriesSettings[0] as ICyclicSeries : null} TimeFilter={props.Plot.TimeFilter} PlotFilter={props.Plot.PlotFilter} - Title={props.Plot.Title} XAxisLabel={props.Plot.XAxisLabel} YAxisLabel={props.Plot.YLeftLabel} MouseHighlight={lineHighlight} SetExtraSpace={setExtraHeight} - Height={chartHeight} Width={chartWidth} Metric={props.Plot.Metric} Cursor={customCursor} AxisZoom={props.Plot.AxisZoom} DefaultZoom={props.Plot.DefaultZoom} - OnSelect={createMarker} AlwaysRender={[overlayButton, closeButton]}/> + 0 ? plotAllSeriesSettings[0] as ICyclicSeries : null} + TimeFilter={props.Plot.TimeFilter} PlotFilter={props.Plot.PlotFilter} + Title={props.Plot.Title} + XAxisLabel={props.Plot.XAxisLabel} YAxisLabel={props.Plot.YLeftLabel} + MouseHighlight={lineHighlight} + SetExtraSpace={setExtraHeight} + Height={chartHeight} Width={chartWidth} + Metric={props.Plot.Metric} + Cursor={customCursor} + AxisZoom={props.Plot.AxisZoom} DefaultZoom={props.Plot.DefaultZoom} + OnSelect={createMarker} + AlwaysRender={[overlayButton, closeButton]} /> ); break; } @@ -612,7 +630,7 @@ const TrendPlot: React.FunctionComponent = (props: IContainerPr ref={chartRef} onDragOver={handleDragOverChannel} onDrop={handleDropChannel}> {plotBody}
    @@ -657,7 +675,8 @@ const DragHalf: React.FunctionComponent = (props) => { onDragEnter={handleDragEnter} onDragLeave={handleDragLeave} onDragOver={handleDragOver} onDragStart={handleDragStart} onDrop={handleDrop} draggable={true}>
    + width: 'calc(100% - 40px)', height: '100%', float: (props.isLeft ? 'right' : 'left'), userSelect: 'none' + }} />
    diff --git a/SEBrowser/Scripts/TSX/SEBrowser.tsx b/SEBrowser/Scripts/TSX/SEBrowser.tsx index 0528b0e16..de6dcccf2 100644 --- a/SEBrowser/Scripts/TSX/SEBrowser.tsx +++ b/SEBrowser/Scripts/TSX/SEBrowser.tsx @@ -59,7 +59,11 @@ const SEBrowserMainPage = () => { handle.done(data => setLinks(data)); - return () => { if(handle.abort != undefined) handle.abort()} + handle.fail((jqXHR, textStatus, errorThrown) => { + console.error('Failed to load data:', textStatus, errorThrown); + setLinks([]); // Set default empty array to prevent rendering issues + }); + return () => { if (handle.abort != undefined) handle.abort() } }, []); React.useEffect(() => { @@ -92,22 +96,22 @@ const SEBrowserMainPage = () => { }} onClick={() => setShowSettings(true)}> {SVGIcons.Settings} - -
  • } - OnSignOut={() => { window.location.href = `${homePath}/Logout`;}} + + } + OnSignOut={() => { window.location.href = `${homePath}/Logout`; }} > - - - - - - - - - -
    - {links.map((item, i) => {createWidget(item.AltValue ?? item.Value)})} -
    + + + + + + + + + +
    + {links.map((item, i) => {createWidget(item.AltValue ?? item.Value)})} +
    setShowSettings(false)} /> diff --git a/SEBrowser/Scripts/TSX/Settings.tsx b/SEBrowser/Scripts/TSX/Settings.tsx index 685e70005..6a555f49b 100644 --- a/SEBrowser/Scripts/TSX/Settings.tsx +++ b/SEBrowser/Scripts/TSX/Settings.tsx @@ -29,10 +29,6 @@ import { Redux } from './global'; import { FetchEventSearches } from './Components/EventSearch/EventSearchSlice'; const searchSettingsOptions = [ - { - Value: 'center', - Label: 'Center Date/Time and Window', - }, { Value: 'startWindow', Label: 'Start Date/Time and Window', @@ -108,15 +104,15 @@ const Settings = (props: { Show: boolean, Close: () => void }) => { >
    -
    - Event Search Settings: -
    -
    +
    + Event Search Settings: +
    +
    Record={evtSearch} Field='NumberResults' Setter={setEvtSearch} Valid={() => true} Label='Number of Results' Type='integer' /> -
    +
    @@ -149,7 +145,7 @@ const Settings = (props: { Show: boolean, Close: () => void }) => { Record={trendData} Field='BorderPlots' Setter={setTrendData} - Label='Enable Plot Borders'/> + Label='Enable Plot Borders' />
    @@ -158,7 +154,7 @@ const Settings = (props: { Show: boolean, Close: () => void }) => { Record={trendData} Field='InsertAtStart' Setter={setTrendData} - Label='Add New Plots to Top'/> + Label='Add New Plots to Top' />
    @@ -167,7 +163,7 @@ const Settings = (props: { Show: boolean, Close: () => void }) => { Record={trendData} Field='StartWithOptionsClosed' Setter={setTrendData} - Label='Toolbar Closed by Default'/> + Label='Toolbar Closed by Default' />
    @@ -176,7 +172,7 @@ const Settings = (props: { Show: boolean, Close: () => void }) => { Record={trendData} Field='MarkerSnapping' Setter={setTrendData} - Label='Snap Markers to Nearest Data Point'/> + Label='Snap Markers to Nearest Data Point' />
    @@ -191,7 +187,7 @@ const Settings = (props: { Show: boolean, Close: () => void }) => { Options={searchSettingsOptions} Record={general} - Field='DateTime' + Field='DateTimeMode' Setter={(g) => setGeneral(g)} Label='Date/Time Filter Mode' /> @@ -218,7 +214,7 @@ const Settings = (props: { Show: boolean, Close: () => void }) => { - + ); } diff --git a/SEBrowser/Scripts/TSX/global.d.ts b/SEBrowser/Scripts/TSX/global.d.ts index 92db06b67..72b7e730d 100644 --- a/SEBrowser/Scripts/TSX/global.d.ts +++ b/SEBrowser/Scripts/TSX/global.d.ts @@ -96,7 +96,8 @@ export namespace Redux { interface IGeneralSettings { MoveOptionsLeft: boolean, ShowDataPoints: boolean, - DateTime: SEBrowser.TimeWindowMode + DateTimeMode: SEBrowser.TimeWindowMode, + DateTimeFormat: string } interface IEventSearchSettings { @@ -115,21 +116,21 @@ export namespace Redux { } export namespace SEBrowser { type Status = 'loading' | 'idle' | 'error' | 'changed' | 'unitiated'; - type TimeWindowMode = 'center' | 'startWindow' | 'endWindow' | 'startEnd'; - + type TimeWindowMode = 'startWindow' | 'endWindow' | 'startEnd'; + interface State { tab?: string, startTime?: string, endTime?: string, context?: string, meterGroup?: number } interface EventPreviewPaneSetting { ID: number, Name: string, Show: boolean, OrderBy: number } - interface IReportTimeFilter { date: string, time: string, windowSize: number, timeWindowUnits: number } + interface IReportTimeFilter { start: string, end: string } interface IPhaseFilters { AN: boolean, BN: boolean, CN: boolean, AB: boolean, BC: boolean, CA: boolean, ABG: boolean, BCG: boolean, ABC: boolean, ABCG: boolean } interface IEventCharacteristicFilters { - durationMin: number, durationMax: number, + durationMin?: number, durationMax?: number, phases: IPhaseFilters, - transientMin?: number, transientMax?: number, transientType: ('LL'|'LN'|'both'), + transientMin?: number, transientMax?: number, transientType: ('LL' | 'LN' | 'both'), sagMin?: number, sagMax?: number, sagType: ('LL' | 'LN' | 'both'), swellMin?: number, swellMax?: number, swellType: ('LL' | 'LN' | 'both'), curveID: number, curveInside: boolean, curveOutside: boolean } - + interface EventNote extends XDA.Types.Note { EventIDs: number[], IDs: number[], @@ -223,7 +224,7 @@ export namespace TrendSearch { Value: number } - type IPlotTypes = 'Line'|'Cyclic'; + type IPlotTypes = 'Line' | 'Cyclic'; interface ITrendPlot { // Represents Data Needed by Outer diff --git a/SEBrowser/Web.config b/SEBrowser/Web.config index 75819cc67..749c5aa82 100644 --- a/SEBrowser/Web.config +++ b/SEBrowser/Web.config @@ -1,4 +1,4 @@ - +
    @@ -302,4 +302,4 @@ - + \ No newline at end of file diff --git a/SEBrowser/package-lock.json b/SEBrowser/package-lock.json index cedf63462..4f4f07e4c 100644 --- a/SEBrowser/package-lock.json +++ b/SEBrowser/package-lock.json @@ -8,13 +8,13 @@ "name": "sebrowser", "version": "1.0.0", "dependencies": { - "@gpa-gemstone/application-typings": "0.0.72", - "@gpa-gemstone/common-pages": "0.0.106", - "@gpa-gemstone/gpa-symbols": "0.0.36", - "@gpa-gemstone/react-forms": "1.1.62", - "@gpa-gemstone/react-graph": "1.0.53", - "@gpa-gemstone/react-interactive": "1.0.121", - "@gpa-gemstone/react-table": "1.2.46", + "@gpa-gemstone/application-typings": "0.0.75", + "@gpa-gemstone/common-pages": "0.0.113", + "@gpa-gemstone/gpa-symbols": "0.0.39", + "@gpa-gemstone/react-forms": "1.1.68", + "@gpa-gemstone/react-graph": "1.0.57", + "@gpa-gemstone/react-interactive": "1.0.127", + "@gpa-gemstone/react-table": "1.2.50", "@reduxjs/toolkit": "1.8.3", "@types/d3": "7.1.0", "@types/eonasdan-bootstrap-datetimepicker": "4.17.29", @@ -88,28 +88,28 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", - "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", - "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helpers": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/template": "^7.24.7", - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -125,11 +125,11 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", - "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", "dependencies": { - "@babel/types": "^7.24.7", + "@babel/types": "^7.25.6", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -150,13 +150,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", - "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "dependencies": { - "@babel/compat-data": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -165,18 +165,16 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.7.tgz", - "integrity": "sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz", + "integrity": "sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ==", "dependencies": { "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.8", "@babel/helper-optimise-call-expression": "^7.24.7", - "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-replace-supers": "^7.25.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/traverse": "^7.25.4", "semver": "^6.3.1" }, "engines": { @@ -186,47 +184,13 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", - "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", - "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", - "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", - "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.7.tgz", - "integrity": "sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", + "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", "dependencies": { - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -245,15 +209,14 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", - "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", "@babel/helper-module-imports": "^7.24.7", "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7" + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" }, "engines": { "node": ">=6.9.0" @@ -274,21 +237,21 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", - "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz", - "integrity": "sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", + "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-member-expression-to-functions": "^7.24.7", - "@babel/helper-optimise-call-expression": "^7.24.7" + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/traverse": "^7.25.0" }, "engines": { "node": ">=6.9.0" @@ -321,21 +284,10 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", - "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", - "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "engines": { "node": ">=6.9.0" } @@ -349,20 +301,20 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", - "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", - "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6" }, "engines": { "node": ">=6.9.0" @@ -383,9 +335,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", - "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "dependencies": { + "@babel/types": "^7.25.6" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -426,6 +381,34 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.6.tgz", + "integrity": "sha512-sXaDXaJN9SNLymBdlWFA+bjzBhFD617ZaFiY13dGt7TVslVvVgA6fkZOP7Ki3IGElC45lwHdOTrCtKZGVAWeLQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", @@ -528,6 +511,20 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-top-level-await": { "version": "7.14.5", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", @@ -543,11 +540,11 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", - "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz", + "integrity": "sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.7" + "@babel/helper-plugin-utils": "^7.24.8" }, "engines": { "node": ">=6.9.0" @@ -557,12 +554,12 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.7.tgz", - "integrity": "sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", + "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", "dependencies": { - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", "@babel/helper-simple-access": "^7.24.7" }, "engines": { @@ -573,13 +570,14 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.24.7.tgz", - "integrity": "sha512-iLD3UNkgx2n/HrjBesVbYX6j0yqn/sJktvbtKKgcaLIQ4bTTQ8obAypc1VpyHPD2y4Phh9zHOaAt8e/L14wCpw==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.2.tgz", + "integrity": "sha512-lBwRvjSmqiMYe/pS0+1gggjJleUJi7NzjvQ1Fkqtt69hBa/0t1YuW/MLQMAPixfwaQOHUXsd6jeU3Z+vdGv3+A==", "dependencies": { "@babel/helper-annotate-as-pure": "^7.24.7", - "@babel/helper-create-class-features-plugin": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", "@babel/plugin-syntax-typescript": "^7.24.7" }, "engines": { @@ -619,31 +617,28 @@ } }, "node_modules/@babel/template": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", - "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", - "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-hoist-variables": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -652,11 +647,11 @@ } }, "node_modules/@babel/types": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", - "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", "dependencies": { - "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-string-parser": "^7.24.8", "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, @@ -802,24 +797,26 @@ } }, "node_modules/@gpa-gemstone/application-typings": { - "version": "0.0.72", - "resolved": "https://registry.npmjs.org/@gpa-gemstone/application-typings/-/application-typings-0.0.72.tgz", - "integrity": "sha512-DtPXhkrMShYxpdF4L6YePa9RCfaU7M0sS3kefVRoFgZx6LoSh/XWYkjGbjqXiA/5kdsfAJcExLrD7xQTz/SwiA==", + "version": "0.0.75", + "resolved": "https://registry.npmjs.org/@gpa-gemstone/application-typings/-/application-typings-0.0.75.tgz", + "integrity": "sha512-fYQlsnHlcVB+Ac5DFMu2N3H8j4FsZiMdLgCINBrl6GxlOqINNsxNPveVjhgFOGOWF4qSVon06PlPDxqbriiing==", "dependencies": { - "@types/webpack": "^5.28.0" + "@types/react": "^17.0.14", + "@types/webpack": "^5.28.0", + "react": "^18.2.0" } }, "node_modules/@gpa-gemstone/common-pages": { - "version": "0.0.106", - "resolved": "https://registry.npmjs.org/@gpa-gemstone/common-pages/-/common-pages-0.0.106.tgz", - "integrity": "sha512-iDwMZBb5WF9F/7GXTj8Xn9/61XgvdI5v4tpZIaZlsU371dUxSff7hpgrg7NVde8Eje+dDTIYFFbgVL42fxGnIQ==", - "dependencies": { - "@gpa-gemstone/application-typings": "0.0.72", - "@gpa-gemstone/gpa-symbols": "0.0.36", - "@gpa-gemstone/helper-functions": "0.0.32", - "@gpa-gemstone/react-forms": "1.1.62", - "@gpa-gemstone/react-interactive": "1.0.121", - "@gpa-gemstone/react-table": "1.2.46", + "version": "0.0.113", + "resolved": "https://registry.npmjs.org/@gpa-gemstone/common-pages/-/common-pages-0.0.113.tgz", + "integrity": "sha512-TZC4ItkhqupwARUzNPPLB3v0cD06GZVmKgMSWCeBeFQa5B8n0D8UuHE6CUA8r7os7cxiODsFdTe4MeJHO8TGGA==", + "dependencies": { + "@gpa-gemstone/application-typings": "0.0.75", + "@gpa-gemstone/gpa-symbols": "0.0.40", + "@gpa-gemstone/helper-functions": "0.0.33", + "@gpa-gemstone/react-forms": "1.1.68", + "@gpa-gemstone/react-interactive": "1.0.127", + "@gpa-gemstone/react-table": "1.2.50", "@reduxjs/toolkit": "1.8.3", "crypto-js": "4.0.0", "moment": "^2.29.4", @@ -829,10 +826,25 @@ "styled-components": "5.3.3" } }, + "node_modules/@gpa-gemstone/common-pages/node_modules/@gpa-gemstone/gpa-symbols": { + "version": "0.0.40", + "resolved": "https://registry.npmjs.org/@gpa-gemstone/gpa-symbols/-/gpa-symbols-0.0.40.tgz", + "integrity": "sha512-kMAyPoaewnVIDlWpuuN8tcE9Ov0e2g5vmjA0XnfVuxyc55tkGfWds8cmZhm4yXvxSocj+xgZQlGhrNouY9LJsg==", + "dependencies": { + "@babel/preset-typescript": "^7.14.5", + "babel-jest": "^29.0.0", + "babel-loader": "^8.2.2", + "jest-cli": "^29.0.0", + "react": "18.2.0", + "webpack": "^5.47.0", + "webpack-cli": "^4.7.2", + "yarn": "^1.22.11" + } + }, "node_modules/@gpa-gemstone/gpa-symbols": { - "version": "0.0.36", - "resolved": "https://registry.npmjs.org/@gpa-gemstone/gpa-symbols/-/gpa-symbols-0.0.36.tgz", - "integrity": "sha512-TvKfewPk1s+xAXoV27XYPzNsaeIEQ8QB+YLu06nuM4nLdPXwgCbgTOmK1Slje3fhQ0p3TBEtXIMQPIcw84vaUQ==", + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@gpa-gemstone/gpa-symbols/-/gpa-symbols-0.0.39.tgz", + "integrity": "sha512-a4l8RqO4wSqR8S872gnwehMXfUgjOco4njDGhxcAoL6RGm0zQTyg+jKnZ4Czj+ZtaMo2+R5z4DGuOt2XoDIc6A==", "dependencies": { "@babel/preset-typescript": "^7.14.5", "babel-jest": "^29.0.0", @@ -845,20 +857,20 @@ } }, "node_modules/@gpa-gemstone/helper-functions": { - "version": "0.0.32", - "resolved": "https://registry.npmjs.org/@gpa-gemstone/helper-functions/-/helper-functions-0.0.32.tgz", - "integrity": "sha512-NkiDU0kDUT31V99Bd71bfo1vz9BDtJKu22ncmNCjKNmPcIYawhUD2idDmySb3XnTp4b+ChLbV4WoYYNugoG3xw==", + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@gpa-gemstone/helper-functions/-/helper-functions-0.0.33.tgz", + "integrity": "sha512-ni+z2421MRFkaKQmJ2lubDlNgp/54Ugc8xKo7VSmXnGIdBaPyASJgR4jY0KK7ZW9flEc6U9uY9aG/yIfSDREaQ==", "dependencies": { "react": "^18.2.0" } }, "node_modules/@gpa-gemstone/react-forms": { - "version": "1.1.62", - "resolved": "https://registry.npmjs.org/@gpa-gemstone/react-forms/-/react-forms-1.1.62.tgz", - "integrity": "sha512-3h7Ca2qdU9L5YZU8bk/6k5OZr7TqS7zwOUR1ozMat609gvFKhiihwv2jdHi2C5TTNZmwUvPVspNBS7KwRmF2+A==", + "version": "1.1.68", + "resolved": "https://registry.npmjs.org/@gpa-gemstone/react-forms/-/react-forms-1.1.68.tgz", + "integrity": "sha512-1mmVWACx7qfLzXxbTy4H62Z7+Lf8mE1C0QM2VHOWgfWhu/nrFhIBxsK73+SMpPxUiGhF7+bF08Q1faXOrt3Cww==", "dependencies": { - "@gpa-gemstone/application-typings": "0.0.72", - "@gpa-gemstone/helper-functions": "0.0.32", + "@gpa-gemstone/application-typings": "0.0.75", + "@gpa-gemstone/helper-functions": "0.0.33", "@types/react": "^17.0.14", "@types/styled-components": "^5.1.11", "lodash": "^4.17.21", @@ -870,28 +882,43 @@ } }, "node_modules/@gpa-gemstone/react-graph": { - "version": "1.0.53", - "resolved": "https://registry.npmjs.org/@gpa-gemstone/react-graph/-/react-graph-1.0.53.tgz", - "integrity": "sha512-cWNnss/9iKEPNKDPCWuU/08j55NGAONw8uADxu82F8XTlprU9q23rdwmKEO1lnPRZR+ArHk60FzBRspkA5lNTw==", + "version": "1.0.57", + "resolved": "https://registry.npmjs.org/@gpa-gemstone/react-graph/-/react-graph-1.0.57.tgz", + "integrity": "sha512-P09LPA7z4DDjIiIuC56LS7q1/43K5BeyC+sX3xs3yJBLI5Oct7nmI5D7ZIKkZZoW6deMcUi1BajJZ2cv0SvIkA==", "dependencies": { - "@gpa-gemstone/gpa-symbols": "0.0.36", - "@gpa-gemstone/helper-functions": "0.0.32", + "@gpa-gemstone/gpa-symbols": "0.0.40", + "@gpa-gemstone/helper-functions": "0.0.33", "html2canvas": "^1.4.1", "lodash": "^4.17.21", "moment": "^2.29.4", "react": "^18.2.0" } }, + "node_modules/@gpa-gemstone/react-graph/node_modules/@gpa-gemstone/gpa-symbols": { + "version": "0.0.40", + "resolved": "https://registry.npmjs.org/@gpa-gemstone/gpa-symbols/-/gpa-symbols-0.0.40.tgz", + "integrity": "sha512-kMAyPoaewnVIDlWpuuN8tcE9Ov0e2g5vmjA0XnfVuxyc55tkGfWds8cmZhm4yXvxSocj+xgZQlGhrNouY9LJsg==", + "dependencies": { + "@babel/preset-typescript": "^7.14.5", + "babel-jest": "^29.0.0", + "babel-loader": "^8.2.2", + "jest-cli": "^29.0.0", + "react": "18.2.0", + "webpack": "^5.47.0", + "webpack-cli": "^4.7.2", + "yarn": "^1.22.11" + } + }, "node_modules/@gpa-gemstone/react-interactive": { - "version": "1.0.121", - "resolved": "https://registry.npmjs.org/@gpa-gemstone/react-interactive/-/react-interactive-1.0.121.tgz", - "integrity": "sha512-c9YMOrLUbewewEztLFSp0lTQRfV6bETaK+EBiTLMnSkyig1tsDpVKNh1cNyZiIYPwia1rT0pSWvvoWhuPppc4A==", - "dependencies": { - "@gpa-gemstone/application-typings": "0.0.72", - "@gpa-gemstone/gpa-symbols": "0.0.36", - "@gpa-gemstone/helper-functions": "0.0.32", - "@gpa-gemstone/react-forms": "1.1.62", - "@gpa-gemstone/react-table": "1.2.46", + "version": "1.0.127", + "resolved": "https://registry.npmjs.org/@gpa-gemstone/react-interactive/-/react-interactive-1.0.127.tgz", + "integrity": "sha512-qWP/3sghM8sZCEdxnEk0GFV4W7Ln6sRuDEPxn+2D4UeFczujVaZPIIaeOyg5E/z8YXLCcnvRi7P8D8gN8DwqZg==", + "dependencies": { + "@gpa-gemstone/application-typings": "0.0.75", + "@gpa-gemstone/gpa-symbols": "0.0.40", + "@gpa-gemstone/helper-functions": "0.0.33", + "@gpa-gemstone/react-forms": "1.1.68", + "@gpa-gemstone/react-table": "1.2.50", "@reduxjs/toolkit": "1.8.3", "jquery": "^3.6.0", "lodash": "^4.17.21", @@ -902,19 +929,49 @@ "styled-components": "5.3.3" } }, + "node_modules/@gpa-gemstone/react-interactive/node_modules/@gpa-gemstone/gpa-symbols": { + "version": "0.0.40", + "resolved": "https://registry.npmjs.org/@gpa-gemstone/gpa-symbols/-/gpa-symbols-0.0.40.tgz", + "integrity": "sha512-kMAyPoaewnVIDlWpuuN8tcE9Ov0e2g5vmjA0XnfVuxyc55tkGfWds8cmZhm4yXvxSocj+xgZQlGhrNouY9LJsg==", + "dependencies": { + "@babel/preset-typescript": "^7.14.5", + "babel-jest": "^29.0.0", + "babel-loader": "^8.2.2", + "jest-cli": "^29.0.0", + "react": "18.2.0", + "webpack": "^5.47.0", + "webpack-cli": "^4.7.2", + "yarn": "^1.22.11" + } + }, "node_modules/@gpa-gemstone/react-table": { - "version": "1.2.46", - "resolved": "https://registry.npmjs.org/@gpa-gemstone/react-table/-/react-table-1.2.46.tgz", - "integrity": "sha512-ZhjR4AoZOYVsv2zwHB1hlqSKAiCBNXneaRi0WS1lxKkXyjJIhXKGv9OsQUU633NK+gXPv715Zf2dH9IHS5ik3w==", + "version": "1.2.50", + "resolved": "https://registry.npmjs.org/@gpa-gemstone/react-table/-/react-table-1.2.50.tgz", + "integrity": "sha512-6fShGTT12XdWNDmV9U46GnxyZXnfwCQdBSqr3Lft5qhyKY26NFGNY0T58drH6d1C20BZ46uh3Zx5UF8LNlcrog==", "dependencies": { - "@gpa-gemstone/gpa-symbols": "0.0.36", - "@gpa-gemstone/helper-functions": "0.0.32", + "@gpa-gemstone/gpa-symbols": "0.0.40", + "@gpa-gemstone/helper-functions": "0.0.33", "@types/lodash": "^4.14.171", "@types/react": "^17.0.14", "lodash": "^4.17.21", "react": "^18.2.0" } }, + "node_modules/@gpa-gemstone/react-table/node_modules/@gpa-gemstone/gpa-symbols": { + "version": "0.0.40", + "resolved": "https://registry.npmjs.org/@gpa-gemstone/gpa-symbols/-/gpa-symbols-0.0.40.tgz", + "integrity": "sha512-kMAyPoaewnVIDlWpuuN8tcE9Ov0e2g5vmjA0XnfVuxyc55tkGfWds8cmZhm4yXvxSocj+xgZQlGhrNouY9LJsg==", + "dependencies": { + "@babel/preset-typescript": "^7.14.5", + "babel-jest": "^29.0.0", + "babel-loader": "^8.2.2", + "jest-cli": "^29.0.0", + "react": "18.2.0", + "webpack": "^5.47.0", + "webpack-cli": "^4.7.2", + "yarn": "^1.22.11" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -1375,9 +1432,9 @@ } }, "node_modules/@jest/reporters/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "bin": { "semver": "bin/semver.js" }, @@ -2263,9 +2320,9 @@ } }, "node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dependencies": { "@types/yargs-parser": "*" } @@ -2995,22 +3052,25 @@ } }, "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0" @@ -3230,9 +3290,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "funding": [ { "type": "opencollective", @@ -3248,10 +3308,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -3364,9 +3424,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001629", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001629.tgz", - "integrity": "sha512-c3dl911slnQhmxUIT4HhYzT7wnBK/XYpGnYLOj4nJBaRiw52Ibe7YxlDaAeRECvA786zCuExhxIUJ2K7nHMrBw==", + "version": "1.0.30001658", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001658.tgz", + "integrity": "sha512-N2YVqWbJELVdrnsW5p+apoQyYt51aBMSsBZki1XZEfeBCexcM/sf4xiAHcXQBkuOwJBXtWF7aW1sYX6tKebPHw==", "funding": [ { "type": "opencollective", @@ -3497,9 +3557,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz", - "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.0.tgz", + "integrity": "sha512-N1NGmowPlGBLsOZLPvm48StN04V4YvQRL0i6b7ctrVY3epjP/ct7hFLOItz6pDIvRjwpfPxi52a2UWV2ziir8g==" }, "node_modules/cliui": { "version": "8.0.1", @@ -4400,9 +4460,9 @@ "optional": true }, "node_modules/electron-to-chromium": { - "version": "1.4.795", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.795.tgz", - "integrity": "sha512-hHo4lK/8wb4NUa+NJYSFyJ0xedNHiR6ylilDtb8NUW9d4dmBFmGiecYEKCEbti1wTNzbKXLfl4hPWEkAFbHYlw==" + "version": "1.5.17", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.17.tgz", + "integrity": "sha512-Q6Q+04tjC2KJ8qsSOSgovvhWcv5t+SmpH6/YfAWmhpE5/r+zw6KQy1/yWVFFNyEBvy68twTTXr2d5eLfCq7QIw==" }, "node_modules/elliptic": { "version": "6.5.5", @@ -4507,9 +4567,9 @@ "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==" }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "engines": { "node": ">=6" } @@ -6061,9 +6121,9 @@ } }, "node_modules/istanbul-lib-report/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "bin": { "semver": "bin/semver.js" }, @@ -7329,9 +7389,9 @@ } }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "bin": { "semver": "bin/semver.js" }, @@ -8119,9 +8179,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -9847,9 +9907,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", - "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "funding": [ { "type": "opencollective", diff --git a/SEBrowser/package.json b/SEBrowser/package.json index 2e1b5c2be..27d5e2d24 100644 --- a/SEBrowser/package.json +++ b/SEBrowser/package.json @@ -19,13 +19,13 @@ "webpack-cli": "4.9.2" }, "dependencies": { - "@gpa-gemstone/application-typings": "0.0.72", - "@gpa-gemstone/common-pages": "0.0.106", - "@gpa-gemstone/gpa-symbols": "0.0.36", - "@gpa-gemstone/react-forms": "1.1.62", - "@gpa-gemstone/react-graph": "1.0.53", - "@gpa-gemstone/react-interactive": "1.0.121", - "@gpa-gemstone/react-table": "1.2.46", + "@gpa-gemstone/application-typings": "0.0.75", + "@gpa-gemstone/common-pages": "0.0.113", + "@gpa-gemstone/gpa-symbols": "0.0.39", + "@gpa-gemstone/react-forms": "1.1.68", + "@gpa-gemstone/react-graph": "1.0.57", + "@gpa-gemstone/react-interactive": "1.0.127", + "@gpa-gemstone/react-table": "1.2.50", "@reduxjs/toolkit": "1.8.3", "@types/d3": "7.1.0", "@types/eonasdan-bootstrap-datetimepicker": "4.17.29", @@ -63,4 +63,4 @@ "update": "npx npm-check-updates", "lint": "eslint ./Scripts --ext .ts,.tsx" } -} +} \ No newline at end of file