Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tdap 20 #94

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions SEBrowser/Controllers/OpenXDA/TrendChannelController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ public DataTable GetTrendSearchData([FromBody] JObject postData)
{
using (AdoDataConnection connection = new(SettingsCategory))
{
string phaseFilter = GetKeyValueFilter(postData["Phases"], "Phase.Name");
string channelGroupFilter = GetKeyValueFilter(postData["ChannelGroups"], "ChannelGroup.Name");
string phaseFilter = GetKeyValueFilter((JArray) postData["Phases"], "Phase.ID");
string channelGroupFilter = GetKeyValueFilter((JArray) postData["ChannelGroups"], "ChannelGroup.ID");
string assetFilter = GetIDFilter(postData["AssetList"], "Asset.ID");
string meterFilter = GetIDFilter(postData["MeterList"], "Meter.ID");
// Meters must be selected
Expand Down Expand Up @@ -137,12 +137,12 @@ public Task<HttpResponseMessage> GetCyclicChartData([FromBody] JObject postData,
#endregion

#region [ Private Methods ]
private string GetKeyValueFilter(JToken keyValuePairs, string fieldName)
private string GetKeyValueFilter(JArray keyValuePairs, string fieldName)
{
//Note: we're only gonna filter out ones that have been explicitly exluded
IEnumerable<JToken> validPairs = keyValuePairs.Where(pair => !pair["Value"].ToObject<bool>());
if (validPairs.Count() == 0) return null;
return $"{fieldName} NOT IN ({string.Join(", ", validPairs.Select(pair => "\'" + pair["Key"].ToObject<string>() + "\'"))})";
IEnumerable<string> validIds = keyValuePairs.SelectMany(pair => ((JObject) pair).Properties()).Where(pair => !pair.Value.ToObject<bool>()).Select(pair => pair.Name);
if (validIds.Count() == 0) return null;
return $"{fieldName} NOT IN ({string.Join(", ", validIds)})";
}
private string GetIDFilter(JToken idObjectList, string fieldName)
{
Expand Down
2 changes: 1 addition & 1 deletion SEBrowser/EventWidgets
118 changes: 101 additions & 17 deletions SEBrowser/Scripts/TSX/Components/TrendData/TrendDataNavbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import React from 'react';
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 { SystemCenter } from '@gpa-gemstone/application-typings';
Expand All @@ -37,6 +38,7 @@ import NavbarFilterButton from '../Common/NavbarFilterButton';
import TrendChannelTable from './Components/TrendChannelTable';
import html2canvas from 'html2canvas';
import jspdf from 'jspdf';
import queryString from 'querystring';

interface IProps {
ToggleVis: () => void,
Expand All @@ -53,8 +55,7 @@ interface IProps {
}

interface IKeyValuePair {
Key: string,
Value: boolean
[key: string]: boolean
}

interface ITrendDataFilter {
Expand All @@ -69,13 +70,21 @@ const TrendSearchNavbar = React.memo((props: IProps) => {
const timeRef = React.useRef(null);
const filtRef = React.useRef(null);
const dispatch = useAppDispatch();
const location = useLocation();
const navigate = useNavigate();

const phaseStatus = useAppSelector(PhaseSlice.SearchStatus);
const allPhases = useAppSelector(PhaseSlice.SearchResults);

const channelGroupStatus = useAppSelector(ChannelGroupSlice.SearchStatus);
const allChannelGroups = useAppSelector(ChannelGroupSlice.SearchResults);

const meterStatus = useAppSelector(MeterSlice.Status);
const allMeters = useAppSelector(MeterSlice.Data);

const assetStatus = useAppSelector(AssetSlice.Status);
const allAssets = useAppSelector(AssetSlice.Data);

const [showFilter, setShowFilter] = React.useState<('None' | 'Meter' | 'Asset')>('None');

const [timeFilter, setTimeFilter] = React.useState<SEBrowser.IReportTimeFilter>(props.TimeFilter);
Expand All @@ -89,6 +98,9 @@ const TrendSearchNavbar = React.memo((props: IProps) => {
const [selectedSet, setSelectedSet] = React.useState<Set<number>>(new Set<number>());
const [tableHeight, setTableHeight] = React.useState<number>(100);

const queryRef = React.useRef<{ phaseIds: Set<number>, groupIds: Set<number>, assetIds: Set<number>, meterIds: Set<number> }>({ phaseIds: undefined, groupIds: undefined, assetIds: undefined, meterIds: undefined})
const [queryReady, setQueryReady] = React.useState<boolean>(false);

// Button Consts
const [hover, setHover] = React.useState < 'None' | 'Show' | 'Hide' | 'Cog' | 'Single-Line' | 'Multi-Line' | 'Group-Line' | 'Cyclic' | 'Move' | 'Trash' | 'Select' | 'Capture' >('None');

Expand All @@ -99,6 +111,33 @@ const TrendSearchNavbar = React.memo((props: IProps) => {
setTableHeight(timeHeight > filtRef ? timeHeight : filtHeight);
});

// Parsing URL Params
React.useEffect(() => {
function parseArrayIntoSet(array: string): Set<number> {
const returnSet = new Set<number>();
array.substring(1, array.length - 1).split(',').forEach(strId => {
const id = parseInt(strId);
if (id !== NaN) returnSet.add(id);
});
return returnSet;
}

const query = queryString.parse(location.search.replace("?", ""), "&", "=", { decodeURIComponent: queryString.unescape });
setTimeFilter({
windowSize: query['windowSize'] !== undefined ? parseInt(query['windowSize'].toString()) : props.TimeFilter.windowSize,
timeWindowUnits: query['timeWindowUnits'] !== undefined ? parseInt(query['timeWindowUnits'].toString()) : props.TimeFilter.timeWindowUnits,
time: query['time'] !== undefined ? query['time'].toString() : props.TimeFilter.time,
date: query['date'] !== undefined ? query['date'].toString() : props.TimeFilter.date
});
if (query['phases'] !== undefined) queryRef.current.phaseIds = parseArrayIntoSet(query['phases'].toString());
if (query['groups'] !== undefined) queryRef.current.groupIds = parseArrayIntoSet(query['groups'].toString());
if (query['meters'] !== undefined) queryRef.current.meterIds = parseArrayIntoSet(query['meters'].toString());
if (query['assets'] !== undefined) queryRef.current.assetIds = parseArrayIntoSet(query['assets'].toString());

setQueryReady(true);
console.log(query);
}, []);

// Multicheckbox Options Updates
React.useEffect(() => {
makeMultiCheckboxOptions(trendFilter?.Phases, setPhaseOptions, allPhases);
Expand All @@ -114,7 +153,7 @@ const TrendSearchNavbar = React.memo((props: IProps) => {
}, [props.LinePlot]);

React.useEffect(() => {
setTimeFilter(props.TimeFilter);
if (queryReady) setTimeFilter(props.TimeFilter);
}, [props.TimeFilter]);

// Slice dispatches
Expand All @@ -128,6 +167,16 @@ const TrendSearchNavbar = React.memo((props: IProps) => {
dispatch(ChannelGroupSlice.DBSearch({ filter: [], sortField: "Name", ascending: true }));
}, [channelGroupStatus]);

React.useEffect(() => {
if (meterStatus == 'changed' || meterStatus == 'unintiated')
dispatch(MeterSlice.Fetch());
}, [meterStatus]);

React.useEffect(() => {
if (assetStatus == 'changed' || assetStatus == 'unintiated')
dispatch(AssetSlice.Fetch());
}, [assetStatus]);

React.useEffect(() => {
if (trendFilter === null) return;
// Get the data from the filter
Expand All @@ -137,26 +186,61 @@ const TrendSearchNavbar = React.memo((props: IProps) => {
};
}, [trendFilter]);

React.useEffect(() => {
const queryParams = {};
queryParams['time'] = timeFilter.time;
queryParams['date'] = timeFilter.date;
queryParams['windowSize'] = timeFilter.windowSize;
queryParams['timeWindowUnits'] = timeFilter.timeWindowUnits;

function SetParamArrayKeyVal(field: string, options?: IKeyValuePair[]): string {
if (options == null) return;
const optionsSelected = options.filter(opt => opt[Object.keys(opt)[0]]);
if (optionsSelected.length === 0) return;
queryParams[field] = `[${optionsSelected.map(opt => Object.keys(opt)[0]).join(',')}]`;
}
function SetParamArrayFilter(field: string, selected?: { ID: number }[]): string {
if (selected == null || selected.length === 0) return;
queryParams[field] = `[${selected.map(select => select.ID).join(',')}]`;
}

SetParamArrayFilter('assets', trendFilter?.AssetList);
SetParamArrayFilter('meters', trendFilter?.MeterList);
SetParamArrayKeyVal('phases', trendFilter?.Phases);
SetParamArrayKeyVal('groups', trendFilter?.ChannelGroups);

console.log(timeFilter);

const q = queryString.stringify(queryParams, "&", "=", { encodeURIComponent: queryString.escape });
const handle = setTimeout(() => navigate(location.pathname + '?' + q), 500);
return (() => { clearTimeout(handle); })
}, [trendFilter, timeFilter]);

React.useEffect(() => {
// Todo: get filters from memory
if (trendFilter !== null || channelGroupStatus !== 'idle' || phaseStatus !== 'idle') return;
if (trendFilter !== null ||
channelGroupStatus !== 'idle' || phaseStatus !== 'idle' || meterStatus !== 'idle' || assetStatus !== 'idle' ||
!queryReady) return;

// Note: the different arguements of startingArray and fallBack need different types since we don't know Id's at compile time and don't know names at query parse time
function makeKeyValuePairs(allKeys: { ID: number, Name: string, Description: string }[], startingTrueSet: Set<number> | undefined, fallBackTrueSet: Set<string>): IKeyValuePair[] {
if (allKeys == null) return [];
if (startingTrueSet == null) return allKeys.map(key => ({ [key.ID]: fallBackTrueSet.has(key.Name) }));
return allKeys.map(key => ({ [key.ID]: startingTrueSet.has(key.ID) }));
}

setTrendFilter({
Phases: makeKeyValuePairs(allPhases, new Set(["AB", "BC", "CA"])),
ChannelGroups: makeKeyValuePairs(allChannelGroups, new Set(["Voltage"])),
MeterList: [],
AssetList: []
Phases: makeKeyValuePairs(allPhases, queryRef.current.phaseIds, new Set(["AB", "BC", "CA"])),
ChannelGroups: makeKeyValuePairs(allChannelGroups, queryRef.current.groupIds, new Set(["Voltage"])),
MeterList: queryRef.current.meterIds == null ? [] : allMeters.filter(meter => queryRef.current.meterIds.has(meter.ID)),
AssetList: queryRef.current.assetIds == null ? [] : allAssets?.filter(asset => queryRef.current.assetIds.has(asset.ID)) ?? []
});
}, [channelGroupStatus, phaseStatus]);

function makeKeyValuePairs(allKeys: { ID: number, Name: string, Description: string }[], defaultTrueSet?: Set<string>): IKeyValuePair[] {
if (allKeys == null) return [];
return allKeys.map(key => ({ Key: key.Name, Value: defaultTrueSet?.has(key.Name) ?? false }));
}
}, [channelGroupStatus, phaseStatus, meterStatus, assetStatus, queryReady]);

function makeMultiCheckboxOptions(keyValues: IKeyValuePair[], setOptions: (options: IMultiCheckboxOption[]) => void, allKeys: { ID: number, Name: string, Description: string }[]) {
if (allKeys == null || keyValues == null) return;
const newOptions: IMultiCheckboxOption[] = [];
allKeys.forEach((key, index) => newOptions.push({ Value: index, Text: key.Name, Selected: keyValues.find(e => e.Key === key.Name)?.Value ?? false }));
allKeys.forEach((key) => newOptions.push({ Value: key.ID, Text: key.Name, Selected: keyValues.find(e => e[key.ID] !== undefined)[key.ID] ?? false }));
setOptions(newOptions);
}

Expand All @@ -166,8 +250,8 @@ const TrendSearchNavbar = React.memo((props: IProps) => {
oldOptions.forEach(item => {
const selected: boolean = item.Selected != (newOptions.findIndex(option => item.Value === option.Value) > -1);
options.push({ ...item, Selected: selected });
pairs.push({ Key: item.Text, Value: selected });
})
pairs.push({ [item.Value]: selected });
});
setOptions(options);
setTrendFilter({ ...trendFilter, [filterField]: pairs });
}
Expand Down