diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Asset/AssetChannel.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Asset/AssetChannel.tsx index 3c5c63d608..b591a81437 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Asset/AssetChannel.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Asset/AssetChannel.tsx @@ -24,9 +24,7 @@ import * as React from 'react'; import * as _ from 'lodash'; import { Application } from '@gpa-gemstone/application-typings'; -import { PhaseSlice, MeasurmentTypeSlice } from '../Store/Store' import { Table, Column, Paging } from '@gpa-gemstone/react-table'; -import { useAppSelector } from '../hooks'; import { LoadingIcon, ServerErrorIcon } from '@gpa-gemstone/react-interactive'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; @@ -64,16 +62,12 @@ interface ChannelDetail { //TODO: Move to Gemstone Trend: boolean } - const AssetChannelWindow = (props: IProps) => { const [assetChannels, setAssetChannels] = React.useState([]); const [page, setPage] = React.useState(0); const [totalPages, setTotalPages] = React.useState(0); const [totalRecords, setTotalRecords] = React.useState(0); const [recordsPerPage, setRecordsPerPage] = React.useState(0); - - const pStatus = useAppSelector(PhaseSlice.Status) as Application.Types.Status; - const mtStatus = useAppSelector(MeasurmentTypeSlice.Status) as Application.Types.Status; const [status, setStatus] = React.useState('idle'); const [sortField, setSortField] = React.useState('Name'); const [ascending, setAscending] = React.useState(true); @@ -109,7 +103,7 @@ const AssetChannelWindow = (props: IProps) => { } }, [props.ID, sortField, ascending, page]); - if (status == 'error' || pStatus == 'error' || mtStatus == 'error') + if (status == 'error') return
@@ -127,7 +121,7 @@ const AssetChannelWindow = (props: IProps) => {
- if (status == 'loading' || pStatus == 'loading' || mtStatus == 'loading') + if (status == 'loading') return
diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Asset/AssetSelect.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Asset/AssetSelect.tsx index 0031736e61..04eedba00d 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Asset/AssetSelect.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Asset/AssetSelect.tsx @@ -24,11 +24,11 @@ import * as React from 'react'; import * as _ from 'lodash'; -import { ByAssetSlice, AssetTypeSlice } from '../Store/Store'; +import { ByAssetSlice } from '../Store/Store'; import { OpenXDA, SystemCenter } from '@gpa-gemstone/application-typings'; -import { Search } from '@gpa-gemstone/react-interactive'; +import { Search, GenericController } from '@gpa-gemstone/react-interactive'; import { DefaultSelects } from '@gpa-gemstone/common-pages'; -import { useAppDispatch, useAppSelector } from '../hooks'; +import { useControllerFetch } from '../hooks'; import { Column } from '@gpa-gemstone/react-table'; declare var homePath: string; @@ -44,10 +44,11 @@ interface IProps { children?: React.ReactNode } +const AssetTypeController = new GenericController(`${homePath}api/OpenXDA/AssetType`, 'Name'); + export default function AssetSelect(props: IProps) { - const assetType = useAppSelector(AssetTypeSlice.Data); - const assetTypeStatus = useAppSelector(AssetTypeSlice.Status); - const dispatch = useAppDispatch(); + + const { FetchData: assetType } = useControllerFetch(AssetTypeController); const lineSegmentFilter: Search.IFilter = { @@ -58,11 +59,6 @@ export default function AssetSelect(props: IProps) { IsPivotColumn: false }; - React.useEffect(() => { - if (assetTypeStatus == 'changed' || assetTypeStatus == 'uninitiated') - dispatch(AssetTypeSlice.Fetch()); - }, [assetTypeStatus]); - function getEnum(setOptions, field) { if (field.key == 'AssetType' && field.type == 'enum') { setOptions(assetType.map((t) => ({ Value: t.Name, Label: t.Name }))) diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Asset/ByAsset.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Asset/ByAsset.tsx index 170be9427a..8da8cede45 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Asset/ByAsset.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Asset/ByAsset.tsx @@ -36,9 +36,8 @@ import ExternalDBUpdate from '../CommonComponents/ExternalDBUpdate'; import CapBankRelayAttributes from '../AssetAttribute/CapBankRelay'; import { Search, Modal, LoadingIcon, ServerErrorIcon, TabSelector, SearchBar, GenericController } from '@gpa-gemstone/react-interactive'; import { Paging, Table, Column } from '@gpa-gemstone/react-table'; -import { useAppDispatch, useAppSelector } from '../hooks'; +import { useAppDispatch, useAppSelector, useControllerFetch } from '../hooks'; import { SelectAssetStatus, FetchAsset, SelectAssets } from '../Store/AssetSlice'; -import { AssetTypeSlice } from '../Store/Store'; import DERAttributes from '../AssetAttribute/DER'; import GenerationAttributes from '../AssetAttribute/Generation'; import StationAuxAttributes from '../AssetAttribute/StationAux'; @@ -60,6 +59,7 @@ const extDBTabList = [ { Label: 'Capacitor Bank Relay', Id:'CapacitorBankRelay'} ]; const AssetController = new GenericController(`${homePath}api/OpenXDA/ByRestrictedDetailedAsset`, "AssetName", true); +const AssetTypeController = new GenericController(`${homePath}api/OpenXDA/AssetType`, 'Name'); declare var homePath: string; @@ -67,8 +67,7 @@ const ByAsset: Application.Types.iByComponent = (props) => { let navigate = useNavigate(); const dispatch = useAppDispatch(); - const assetType = useAppSelector(AssetTypeSlice.Data); - const assetTypeStatus = useAppSelector(AssetTypeSlice.Status); + const { FetchData: assetType } = useControllerFetch(AssetTypeController); const [data, setData] = React.useState([]); const [ascending, setAscending] = React.useState(true); @@ -93,11 +92,6 @@ const ByAsset: Application.Types.iByComponent = (props) => { const [assetErrors, setAssetErrors] = React.useState([]); - React.useEffect(() => { - if (assetTypeStatus == 'changed' || assetTypeStatus == 'uninitiated') - dispatch(AssetTypeSlice.Fetch()); - }, [assetTypeStatus]); - React.useEffect(() => { if (assetType.length == 0) return; diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetGroups/AssetAssetGroup.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetGroups/AssetAssetGroup.tsx index 651c90bcc4..154597a446 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetGroups/AssetAssetGroup.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetGroups/AssetAssetGroup.tsx @@ -26,12 +26,11 @@ import * as React from 'react'; import * as _ from 'lodash'; import { useNavigate } from 'react-router-dom'; import { Table, Column, Paging } from '@gpa-gemstone/react-table'; -import { AssetTypeSlice } from '../Store/Store'; import { Application, SystemCenter } from '@gpa-gemstone/application-typings'; import { Warning } from '@gpa-gemstone/react-interactive'; import { ToolTip } from '@gpa-gemstone/react-forms'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; -import { useAppDispatch, useAppSelector } from '../hooks'; +import { useAppSelector } from '../hooks'; import AssetSelect from '../Asset/AssetSelect'; import { SelectRoles } from '../Store/UserSettings'; @@ -50,9 +49,6 @@ function AssetAssetGroupWindow(props: { AssetGroupID: number }) { const [recordsPerPage, setRecordsPerPage] = React.useState(0); const [assetStatus, setAssetStatus] = React.useState('uninitiated'); const [refreshTrigger, setRefreshTrigger] = React.useState(false); - const assetType = useAppSelector(AssetTypeSlice.Data); - const assetTypeStatus = useAppSelector(AssetTypeSlice.Status); - const dispatch = useAppDispatch(); const [hover, setHover] = React.useState<('Update' | 'Reset' | 'None')>('None'); const roles = useAppSelector(SelectRoles); @@ -91,11 +87,6 @@ function AssetAssetGroupWindow(props: { AssetGroupID: number }) { } }, [props.AssetGroupID, refreshTrigger, ascending, page, sortKey]); - React.useEffect(() => { - if (assetTypeStatus == 'changed' || assetTypeStatus == 'uninitiated') - dispatch(AssetTypeSlice.Fetch()); - }, [assetTypeStatus]); - function saveItems(items: SystemCenter.Types.DetailedAsset[]) { let handle = $.ajax({ @@ -109,8 +100,6 @@ function AssetAssetGroupWindow(props: { AssetGroupID: number }) { }); handle.done(() => setRefreshTrigger(val => !val)) - - } function removeItem(id: number) { diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetGroups/ByAssetGroup.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetGroups/ByAssetGroup.tsx index 8b453ad23d..ac5f373a9e 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetGroups/ByAssetGroup.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetGroups/ByAssetGroup.tsx @@ -26,12 +26,12 @@ import { Table, Column } from '@gpa-gemstone/react-table'; import * as _ from 'lodash'; import { useNavigate } from "react-router-dom"; import { Application, OpenXDA, SystemCenter } from '@gpa-gemstone/application-typings' -import { Search, Modal } from '@gpa-gemstone/react-interactive'; +import { Search, Modal, GenericController } from '@gpa-gemstone/react-interactive'; import { CheckBox, Input } from '@gpa-gemstone/react-forms'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; -import { AssetGroupSlice, ByAssetSlice, ByMeterSlice, AssetTypeSlice } from '../Store/Store'; +import { AssetGroupSlice, ByMeterSlice } from '../Store/Store'; import { DefaultSearch, DefaultSelects } from '@gpa-gemstone/common-pages'; -import { useAppDispatch, useAppSelector } from '../hooks'; +import { useAppDispatch, useAppSelector, useControllerFetch } from '../hooks'; import AssetSelect from '../Asset/AssetSelect'; declare var homePath: string; @@ -41,6 +41,8 @@ interface extendedAssetGroup extends OpenXDA.Types.AssetGroup { MeterList: Array const emptyAssetGroup: extendedAssetGroup = { ID: -1, Name: '', DisplayDashboard: true, AssetGroups: 0, Meters: 0, Assets: 0, Users: 0, MeterList: [], AssetList: [], UserList: [], AssetGroupList: [], DisplayEmail: false }; +const AssetTypeController = new GenericController(`${homePath}api/OpenXDA/AssetType`, 'Name'); + declare var homePath: string; const ByAssetGroup: Application.Types.iByComponent = (props) => { @@ -55,8 +57,8 @@ const ByAssetGroup: Application.Types.iByComponent = (props) => { const status = useAppSelector(AssetGroupSlice.Status); const allAssetGroups = useAppSelector(AssetGroupSlice.Data); - const assetType = useAppSelector(AssetTypeSlice.Data); - const assetTypeStatus = useAppSelector(AssetTypeSlice.Status); + const { FetchData: assetType } = useControllerFetch(AssetTypeController); + const [showFilter, setFilter] = React.useState<('None' | 'Meter' | 'Asset' | 'Asset Group' | 'Station')>('None'); @@ -74,11 +76,6 @@ const ByAssetGroup: Application.Types.iByComponent = (props) => { dispatch(AssetGroupSlice.DBSearch({ filter: searchFields })); }, [searchStatus]); - React.useEffect(() => { - if (assetTypeStatus == 'changed' || assetTypeStatus == 'uninitiated') - dispatch(AssetTypeSlice.Fetch()); - }, [assetTypeStatus]); - React.useEffect(() => { let e = []; if (newAssetGroup.Name == null || newAssetGroup.Name.length == 0) diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ChannelGroup/ChannelGroupItemForm.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ChannelGroup/ChannelGroupItemForm.tsx index 29924d0ee0..651aea7085 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ChannelGroup/ChannelGroupItemForm.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ChannelGroup/ChannelGroupItemForm.tsx @@ -22,11 +22,11 @@ //****************************************************************************************************** import * as React from 'react'; -import { SystemCenter } from '@gpa-gemstone/application-typings'; +import { OpenXDA, SystemCenter } from '@gpa-gemstone/application-typings'; import { Input, Select } from '@gpa-gemstone/react-forms'; -import { MeasurmentTypeSlice, MeasurementCharacteristicSlice, ValueListSlice, ValueListGroupSlice } from '../Store/Store'; -import { useAppSelector, useAppDispatch } from '../hooks'; -import { LoadingIcon } from '@gpa-gemstone/react-interactive'; +import { ValueListSlice, ValueListGroupSlice } from '../Store/Store'; +import { useAppSelector, useAppDispatch, useControllerFetch } from '../hooks'; +import { LoadingIcon, GenericController } from '@gpa-gemstone/react-interactive'; interface IProps { Record: SystemCenter.Types.ChannelGroupDetails, @@ -34,13 +34,13 @@ interface IProps { SetErrors?: (e: string[]) => void } +const MeasurementTypeController = new GenericController(`${homePath}api/OpenXDA/MeasurementType`, 'Name'); +const MeasurementCharacteristicController = new GenericController(`${homePath}api/OpenXDA/MeasurementCharacteristic`, 'Name'); + export default function ChannelGroupItemForm(props: IProps) { const dispatch = useAppDispatch(); - - const measurementTypeData = useAppSelector(MeasurmentTypeSlice.Data); - const measurementTypeStatus = useAppSelector(MeasurmentTypeSlice.Status); - const measurementCharacteristicData = useAppSelector(MeasurementCharacteristicSlice.Data); - const measurementCharacteristicStatus = useAppSelector(MeasurementCharacteristicSlice.Status); + const { FetchData: measurementTypeData, FetchStatus: measurementTypeStatus } = useControllerFetch(MeasurementTypeController); + const { FetchData: measurementCharacteristicData, FetchStatus: measurementCharacteristicStatus } = useControllerFetch(MeasurementCharacteristicController); const valueListData = useAppSelector(ValueListSlice.Data); const valueListStatus = useAppSelector(ValueListSlice.Status); const valueListGroupData = useAppSelector(ValueListGroupSlice.Data); @@ -56,16 +56,6 @@ export default function ChannelGroupItemForm(props: IProps) { dispatch(ValueListSlice.Fetch()); }, [valueListStatus]); - React.useEffect(() => { - if (measurementTypeStatus == 'uninitiated' || measurementTypeStatus == 'changed') - dispatch(MeasurmentTypeSlice.Fetch()); - }, [measurementTypeStatus]); - - React.useEffect(() => { - if (measurementCharacteristicStatus == 'uninitiated' || measurementCharacteristicStatus == 'changed') - dispatch(MeasurementCharacteristicSlice.Fetch()); - }, [measurementCharacteristicStatus]); - function Valid(field: keyof (SystemCenter.Types.ChannelGroupDetails)): boolean { if (field == 'DisplayName') return props.Record.DisplayName != null && props.Record.DisplayName.length > 0 && props.Record.DisplayName.length <= 200; diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/CommonComponents/ControllerSelectPopup.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/CommonComponents/ControllerSelectPopup.tsx new file mode 100644 index 0000000000..a6143ffaa2 --- /dev/null +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/CommonComponents/ControllerSelectPopup.tsx @@ -0,0 +1,171 @@ +// ****************************************************************************************************** +// ControllerSelectPopup.tsx - Gbtc +// +// Copyright © 2026, 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: +// ---------------------------------------------------------------------------------------------------- +// 08/12/2026 - Natalie Beatty +// Generated original version of source code. +// ****************************************************************************************************** + +import { Table, Column, FilterableColumn, ConfigurableColumn } from "@gpa-gemstone/react-table"; +import * as React from 'react'; +import { Modal, GenericController, Search } from "@gpa-gemstone/react-interactive"; +import * as _ from "lodash"; +import { ReactIcons } from "@gpa-gemstone/gpa-symbols"; + +interface U { ID: number | string } + +interface IProps { + Controller: GenericController, + Selection: T[], + OnClose: (selected: T[], conf: boolean) => void + Show: boolean, + Searchbar: (children: React.ReactNode, setFilters: (filters: Search.IFilter[]) => void) => React.ReactNode, + Type?: 'single' | 'multiple', + Title: string, + MinSelection?: number, + children?: React.ReactNode +} + + +export default function ControllerSelectPopup(props: IProps) { + const [filters, setFilters] = React.useState[]>([]); + const [sortField, setSortField] = React.useState('ID') + const [ascending, setAscending] = React.useState(true); + const [data, setData] = React.useState([]); + + const [selectedData, setSelectedData] = React.useState(props.Selection); + + const [sortKeySelected, setSortKeySelected] = React.useState(''); + const [ascendingSelected, setAscendingSelected] = React.useState(false); + + React.useEffect(() => { + const handle = props.Controller.DBSearch(filters, sortField, ascending); + handle.done((d) => setData(d)); + return () => { if (handle != null && handle.abort != null) handle.abort() } + }, [props.Controller, ascending, sortField, filters]) + + React.useEffect(() => { + setSelectedData(props.Selection); + }, [props.Selection]) + + React.useEffect(() => { + setFilters([]); // initialize filter list, which should add additional filters + }, []) + + function AddCurrentList() { + const updatedData = selectedData.concat(data); + setSelectedData(_.uniqBy(updatedData, (d) => d.ID)); + } + + return (<> + props.OnClose(selectedData, conf)} + DisableConfirm={props.MinSelection !== undefined && selectedData.length < props.MinSelection} + ConfirmShowToolTip={props.MinSelection !== undefined && selectedData.length < props.MinSelection} + ConfirmToolTipContent={

At least {props.MinSelection} items must be selected.

} + > +
+
+ {props.Searchbar( + <> + {props.Type === 'multiple' ?
  • +
    + Quick Selects: +
    +
    +
    { event.preventDefault(); AddCurrentList(); }}>Add Current List to Selection
    +
    +
    +
    { event.preventDefault(); setSelectedData([]) }}>Remove All
    +
    +
    +
    +
  • : null} + {React.Children.map(props.children, (e) => { + if (React.isValidElement(e)) { + if (((e as React.ReactElement).type === FilterableColumn) || + ((e as React.ReactElement).type === Column) || + ((e as React.ReactElement).type === ConfigurableColumn) + ) return null; + return e; + } + return null; + })} + , setFilters)} +
    +
    +
    +
    + + TableClass="table table-hover" + Data={data} + SortKey={sortField as string} + Ascending={ascending} + OnSort={(d) => { + if (d.colKey === "Scroll") + return; + if (d.colKey === sortField) + setAscending(asc => !asc); + else { + setSortField(d.colKey as keyof T) + setAscending(true); + } + }} + OnClick={(d) => { + if (props.Type === undefined || props.Type === 'single') + setSelectedData([d.row]) + else + setSelectedData((s) => [...s.filter(item => item.ID !== d.row.ID), d.row]) + }} + Selected={(item) => selectedData.findIndex(d => d.ID === item.ID) > -1} + KeySelector={item => item.ID} + > + {props.children} + +
    + {props.Type === 'multiple' ?
    +
    +

    Current Selection

    +
    + + TableClass="table table-hover" + Data={selectedData} + SortKey={sortKeySelected} + Ascending={ascendingSelected} + OnSort={(d) => { + if (d.colKey === sortKeySelected) { + const ordered = _.orderBy(selectedData, [d.colKey], [(!ascendingSelected ? "asc" : "desc")]) as T[]; + setAscendingSelected(!ascendingSelected); + setSelectedData(ordered); + } + else { + const ordered = _.orderBy(selectedData, [d.colKey], ["asc"]) as T[]; + setAscendingSelected(!ascendingSelected); + setSelectedData(ordered); + setSortKeySelected(d.colKey); + } + }} + OnClick={(d) => setSelectedData([...selectedData.filter(item => item.ID !== d.row.ID)])} + Selected={() => false} + KeySelector={item => item.ID} + > + {props.children} + +
    : null} +
    +
    + ) +} \ No newline at end of file diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/CommonComponents/FilterSelect.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/CommonComponents/FilterSelect.tsx index 842dffe5df..f90fe0cffa 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/CommonComponents/FilterSelect.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/CommonComponents/FilterSelect.tsx @@ -24,11 +24,11 @@ import * as React from 'react'; import * as _ from 'lodash'; -import { ByMeterSlice, ByAssetSlice, AssetTypeSlice, ByLocationSlice, CustomerSlice } from '../Store/Store'; +import { ByMeterSlice, ByAssetSlice, ByLocationSlice, CustomerSlice } from '../Store/Store'; import { OpenXDA, SystemCenter } from '@gpa-gemstone/application-typings'; -import { Search } from '@gpa-gemstone/react-interactive'; +import { Search, GenericController } from '@gpa-gemstone/react-interactive'; import { DefaultSelects } from '@gpa-gemstone/common-pages'; -import { useAppDispatch, useAppSelector } from '../hooks'; +import { useAppDispatch, useAppSelector, useControllerFetch } from '../hooks'; import { Column } from '@gpa-gemstone/react-table'; declare var homePath: string; @@ -46,9 +46,12 @@ interface IProps { type Data = (SystemCenter.Types.DetailedMeter | SystemCenter.Types.DetailedLocation | OpenXDA.Types.Customer |SystemCenter.Types.DetailedAsset); +const AssetTypeController = new GenericController(`${homePath}api/OpenXDA/AssetType`, 'Name'); + export default function FilterSelect(props: IProps) { - const assetType = useAppSelector(AssetTypeSlice.Data); - const assetTypeStatus = useAppSelector(AssetTypeSlice.Status); + + const { FetchData: assetType } = useControllerFetch(AssetTypeController); + const dispatch = useAppDispatch(); const select = React.useMemo(() => { @@ -69,11 +72,6 @@ export default function FilterSelect(props: IProps) { setSelectedData(data.filter(i => props.Selected.has(i.ID))); }, [data, props.Selected]); - React.useEffect(() => { - if (assetTypeStatus == 'changed' || assetTypeStatus == 'uninitiated') - dispatch(AssetTypeSlice.Fetch()); - }, [assetTypeStatus]); - const closeCallback = React.useCallback((selected: Data[], confirmed: boolean) => { const newSelected = new Set(); selected.forEach(item => newSelected.add(item.ID)); diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Customer/MDMKeys.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Customer/MDMKeys.tsx index beda696f22..49060e375e 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Customer/MDMKeys.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Customer/MDMKeys.tsx @@ -21,27 +21,20 @@ // //****************************************************************************************************** -import { Application, OpenXDA, SystemCenter } from "@gpa-gemstone/application-typings"; +import { SystemCenter } from "@gpa-gemstone/application-typings"; import { ReactIcons } from "@gpa-gemstone/gpa-symbols"; import { Input, ToolTip } from "@gpa-gemstone/react-forms"; -import { Modal, Search } from "@gpa-gemstone/react-interactive"; +import { Modal, GenericController, LoadingScreen, ServerErrorIcon } from "@gpa-gemstone/react-interactive"; import { Table, Column } from "@gpa-gemstone/react-table"; import React from "react"; -import { useAppDispatch, useAppSelector } from "../hooks"; -import { LSCVSAccountSlice } from "../Store/Store"; +import { useAppSelector, useControllerFetch } from "../hooks"; import { SelectRoles } from "../Store/UserSettings"; interface IProps {CustomerID: number} -function MDMKeys(props: IProps) { - - const searchStatus: Application.Types.Status = useAppSelector(LSCVSAccountSlice.SearchStatus); - const search: Search.IFilter[] = useAppSelector(LSCVSAccountSlice.SearchFilters); - const status: Application.Types.Status = useAppSelector(LSCVSAccountSlice.Status); - const data: SystemCenter.Types.LSCVSAccount[] = useAppSelector(LSCVSAccountSlice.SearchResults); - const parentID = useAppSelector(LSCVSAccountSlice.ParentID); - const dispatch = useAppDispatch(); +const MDMKeyController = new GenericController(`${homePath}api/LSCVSAccount`, "AccountID", false); +function MDMKeys(props: IProps) { const emptyLSCVS = { ID: 0, AccountID: '', CustomerID: props.CustomerID }; const [ascending, setAscending] = React.useState(false); @@ -49,23 +42,11 @@ function MDMKeys(props: IProps) { const [showAdd, setShowAdd] = React.useState(false); const [sortField, setSortField] = React.useState('AccountID'); const [errors, setErrors] = React.useState([]); - + const [refreshCount, refreshData] = React.useState(0); const [hover, setHover] = React.useState<('Update' | 'Reset' | 'None')>('None'); const roles = useAppSelector(SelectRoles); - React.useEffect(() => { - if (status === 'uninitiated' || status === 'changed' || parentID != props.CustomerID) - dispatch(LSCVSAccountSlice.Fetch(props.CustomerID)); - }, [status, props.CustomerID, parentID]); - - React.useEffect(() => { - if (searchStatus === 'uninitiated' || status === 'changed') - dispatch(LSCVSAccountSlice.DBSearch({ filter: search, sortField, ascending })); - }, [searchStatus, status]); - - React.useEffect(() => { - dispatch(LSCVSAccountSlice.DBSearch({ filter: search, sortField, ascending })); - }, [ascending]); + const { FetchData, FetchStatus } = useControllerFetch(MDMKeyController, sortField, ascending, props.CustomerID, refreshCount); React.useEffect(() => { const e: string[] = []; @@ -87,6 +68,8 @@ function MDMKeys(props: IProps) { return ( <> + +
    @@ -99,7 +82,7 @@ function MDMKeys(props: IProps) {
    TableClass="table table-hover" - Data={data} + Data={FetchData} SortKey={sortField} Ascending={ascending} OnSort={(d) => { @@ -140,7 +123,7 @@ function MDMKeys(props: IProps) { ShowX={true} CallBack={(conf, isBtn) => { if (conf && errors.length == 0) - dispatch(LSCVSAccountSlice.DBAction({ verb: 'POST', record: newLSCVSAccount })) + MDMKeyController.DBAction('POST', newLSCVSAccount).then(() => refreshData(x => x + 1)); setShowAdd(false); }} DisableConfirm={errors.length > 0} diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/DB/DBCleanup.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/DB/DBCleanup.tsx index 5b3a35fde8..2d911d56ff 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/DB/DBCleanup.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/DB/DBCleanup.tsx @@ -27,9 +27,9 @@ import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; import { GenericController, Modal, Warning } from '@gpa-gemstone/react-interactive'; import { Application } from '@gpa-gemstone/application-typings'; import { SystemCenter } from '../global'; -import { DBCleanupSlice } from '../Store/Store'; import GenericByPage from '../CommonComponents/GenericByPage'; -import { useAppSelector } from '../hooks'; +import { useControllerFetch } from '../hooks'; + export interface DBCleanup { ID: number; Name: string; @@ -46,9 +46,9 @@ const controllerPath = `${homePath}api/OpenXDA/DBCleanup` const DBCleanupController = new GenericController(controllerPath, "ID", true); const DBCleanup: Application.Types.iByComponent = (props) => { - const allDBCleanup: DBCleanup[] = useAppSelector(DBCleanupSlice.Data); const emptyDBCleanup = { ID: 0, Name: '', SQLCommand: '', Schedule: '' }; + const [editNewDBCleanup, setEditNewDBCleanup] = React.useState(emptyDBCleanup); const [editNew, setEditNew] = React.useState('New'); const [showWarning, setShowWarning] = React.useState(false); @@ -57,6 +57,8 @@ const DBCleanup: Application.Types.iByComponent = (props) => { const [errors, setErrors] = React.useState([]); const [refreshCount, refreshData] = React.useState(0); + const { FetchData, FetchStatus } = useControllerFetch(DBCleanupController, undefined, undefined, undefined, refreshCount); + React.useEffect(() => { setHasChanged(false) }, [showModal]); React.useEffect(() => { @@ -65,7 +67,7 @@ const DBCleanup: Application.Types.iByComponent = (props) => { e.push("A Schedule is required."); if (editNewDBCleanup.Name == null || editNewDBCleanup.Name.length === 0) e.push("A Name is required."); - if (editNewDBCleanup.Name != null && editNewDBCleanup.Name.length > 0 && allDBCleanup.findIndex(s => s.Name.toLowerCase() === editNewDBCleanup.Name.toLowerCase() && s.ID !== editNewDBCleanup.ID) > -1) + if (editNewDBCleanup.Name != null && editNewDBCleanup.Name.length > 0 && FetchData.findIndex(s => s.Name.toLowerCase() === editNewDBCleanup.Name.toLowerCase() && s.ID !== editNewDBCleanup.ID) > -1) e.push('Name must be unique.') if (editNewDBCleanup.SQLCommand == null || editNewDBCleanup.SQLCommand.length === 0) e.push("A SQL Command is required."); diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/EventType/EventTypeForm.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/EventType/EventTypeForm.tsx index e170049fa1..d814d57a24 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/EventType/EventTypeForm.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/EventType/EventTypeForm.tsx @@ -22,10 +22,11 @@ //****************************************************************************************************** import * as React from 'react'; -import { useAppSelector, useAppDispatch } from '../hooks'; +import { useAppSelector, useAppDispatch, useControllerFetch } from '../hooks'; import { Input, CheckBox, TextArea } from '@gpa-gemstone/react-forms'; -import { AssetTypeSlice, EventTypeAssetTypeSlice, EventTypeSlice } from '../Store/Store'; +import { EventTypeAssetTypeSlice, EventTypeSlice } from '../Store/Store'; import { Application, OpenXDA } from '@gpa-gemstone/application-typings'; +import { GenericController } from '@gpa-gemstone/react-interactive'; interface IProps { Record: OpenXDA.Types.EventType, @@ -34,6 +35,8 @@ interface IProps { setAssetTypeETs: (records: OpenXDA.Types.EventTypeAssetType[]) => void } +const AssetTypeController = new GenericController(`${homePath}api/OpenXDA/AssetType`, 'Name'); + export default function EventTypeForm(props: IProps) { React.useEffect(() => { let e = []; @@ -80,8 +83,7 @@ function EventTypeAssetTypeForm(props: IRightProps) { const dispatch = useAppDispatch(); - const assetTypes = useAppSelector(AssetTypeSlice.Data) as OpenXDA.Types.AssetType[]; - const atStatus = useAppSelector(AssetTypeSlice.Status) as Application.Types.Status; + const { FetchData: assetTypes } = useControllerFetch(AssetTypeController); const eventTypeAssetTypeData = useAppSelector(EventTypeAssetTypeSlice.Data); const eventTypeAssettypeParentID = useAppSelector(EventTypeAssetTypeSlice.ParentID); @@ -94,11 +96,6 @@ function EventTypeAssetTypeForm(props: IRightProps) { React.useEffect(() => { props.SetAssetTypeETs(etAt); }, [etAt]); - React.useEffect(() => { - if (atStatus == 'uninitiated' || atStatus == 'changed') - dispatch(AssetTypeSlice.Fetch()); - }, [atStatus]); - React.useEffect(() => { if (atetStatus == 'uninitiated' || atetStatus == 'changed' || eventTypeAssettypeParentID != props?.ID) dispatch(EventTypeAssetTypeSlice.Fetch(props?.ID)); diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Location/LocationAsset.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Location/LocationAsset.tsx index 8202b18229..709d3b2ed7 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Location/LocationAsset.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Location/LocationAsset.tsx @@ -32,11 +32,10 @@ import BusAttributes from '../AssetAttribute/Bus'; import CapBankAttributes from '../AssetAttribute/CapBank'; import LineAttributes from '../AssetAttribute/Line'; import TransformerAttributes from '../AssetAttribute/Transformer'; -import { useAppSelector, useAppDispatch } from '../hooks'; +import { useAppSelector, useAppDispatch, useControllerFetch } from '../hooks'; import { FetchAsset, SelectAssets, SelectAssetStatus } from '../Store/AssetSlice'; -import { AssetTypeSlice } from '../Store/Store'; import { getAssetWithAdditionalFields, editExistingAsset } from '../../../TS/Services/Asset'; -import { LoadingIcon, Modal, ServerErrorIcon } from '@gpa-gemstone/react-interactive'; +import { LoadingIcon, Modal, ServerErrorIcon, GenericController } from '@gpa-gemstone/react-interactive'; import { ToolTip } from '@gpa-gemstone/react-forms'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; import GenerationAttributes from '../AssetAttribute/Generation'; @@ -47,6 +46,8 @@ import { SelectRoles } from '../Store/UserSettings'; declare var homePath: string; const PagingID = 'LocationAssetPage'; +const AssetTypeController = new GenericController(`${homePath}api/OpenXDA/AssetType`, 'Name'); + function LocationAssetWindow(props: { Location: OpenXDA.Types.Location }): JSX.Element{ let navigate = useNavigate(); let dispatch = useAppDispatch(); @@ -67,22 +68,16 @@ function LocationAssetWindow(props: { Location: OpenXDA.Types.Location }): JSX.E const [lStatus, setLStatus] = React.useState<'error' | 'loading' | 'idle'>('idle'); const aStatus = useAppSelector(SelectAssetStatus); - const atStatus = useAppSelector(AssetTypeSlice.Status); - - const assetTypes = useAppSelector(AssetTypeSlice.Data); const allAssets = useAppSelector(SelectAssets); + const { FetchData: assetTypes, FetchStatus: atStatus } = useControllerFetch(AssetTypeController); + const [hover, setHover] = React.useState<('Update' | 'Reset' | 'None')>('None'); const roles = useAppSelector(SelectRoles); const [page, setPage] = React.useState(0); const [pageInfo, setPageInfo] = React.useState<{ RecordsPerPage: number, NumberOfPages: number, TotalRecords: number }>({ RecordsPerPage: 0, NumberOfPages: 0, TotalRecords: 0 }); - React.useEffect(() => { - if (atStatus == 'uninitiated' || atStatus == 'changed') - dispatch(AssetTypeSlice.Fetch()); - }, []); - React.useEffect(() => { if (aStatus == 'uninitiated' || aStatus == 'changed') dispatch(FetchAsset()); diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/MATLABAnalytics/MATLABAnalyticForm.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/MATLABAnalytics/MATLABAnalyticForm.tsx index 85d77500f6..5f0a122cd1 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/MATLABAnalytics/MATLABAnalyticForm.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/MATLABAnalytics/MATLABAnalyticForm.tsx @@ -23,10 +23,13 @@ import * as React from 'react'; import * as _ from 'lodash'; -import { useAppSelector, useAppDispatch } from '../hooks'; -import { MATLABAnalyticEventTypeSlice, MATLABAnalyticAssetTypeSlice, EventTypeSlice, AssetTypeSlice } from '../Store/Store'; +import { useAppSelector, useAppDispatch, useControllerFetch } from '../hooks'; +import { MATLABAnalyticEventTypeSlice, MATLABAnalyticAssetTypeSlice, EventTypeSlice } from '../Store/Store'; import { OpenXDA } from '@gpa-gemstone/application-typings'; import { Input, MultiCheckBoxSelect } from '@gpa-gemstone/react-forms'; +import { GenericController } from '@gpa-gemstone/react-interactive'; + +const AssetTypeController = new GenericController(`${homePath}api/OpenXDA/AssetType`, 'Name'); export default function MATLABAnalyticForm(props: { Record: OpenXDA.Types.MATLABAnalytic, @@ -48,8 +51,8 @@ export default function MATLABAnalyticForm(props: { const allEventTypes = useAppSelector(EventTypeSlice.Data); const allEventTypesStatus = useAppSelector(EventTypeSlice.Status); - const allAssetTypes = useAppSelector(AssetTypeSlice.Data); - const allAssetTypesStatus = useAppSelector(AssetTypeSlice.Status); + + const { FetchData: allAssetTypes } = useControllerFetch(AssetTypeController); const eventTypeParentID = useAppSelector(MATLABAnalyticEventTypeSlice.ParentID); const assetTypeParentID = useAppSelector(MATLABAnalyticAssetTypeSlice.ParentID); @@ -59,11 +62,6 @@ export default function MATLABAnalyticForm(props: { dispatch(EventTypeSlice.Fetch()); }, [allEventTypesStatus]); - React.useEffect(() => { - if (allAssetTypesStatus == 'uninitiated' || allAssetTypesStatus == 'changed') - dispatch(AssetTypeSlice.Fetch()); - }, [allAssetTypesStatus]); - React.useEffect(() => { if (eventTypeStatus == 'uninitiated' || eventTypeStatus == 'changed' || eventTypeParentID != props.Record.ID) dispatch(MATLABAnalyticEventTypeSlice.Fetch(props.Record.ID)); diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/ChannelScaling/ChannelScalingForm.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/ChannelScaling/ChannelScalingForm.tsx index 8533e425f2..f2ea8f94a1 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/ChannelScaling/ChannelScalingForm.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/ChannelScaling/ChannelScalingForm.tsx @@ -26,9 +26,8 @@ import * as React from 'react'; import * as _ from 'lodash'; import { Application, OpenXDA } from '@gpa-gemstone/application-typings'; -import { useAppSelector, useAppDispatch } from '../../hooks'; -import { MeasurementCharacteristicSlice, MeasurmentTypeSlice, PhaseSlice } from '../../Store/Store'; -import { LoadingIcon, ServerErrorIcon } from '@gpa-gemstone/react-interactive'; +import { useAppSelector, useControllerFetch } from '../../hooks'; +import { LoadingIcon, ServerErrorIcon, GenericController } from '@gpa-gemstone/react-interactive'; import { Table, Column, Paging } from '@gpa-gemstone/react-table'; import { ChannelScalingWrapper, ChannelScalingType, IMultiplier } from './ChannelScalingWrapper'; import { Input, ToolTip } from '@gpa-gemstone/react-forms'; @@ -46,20 +45,18 @@ interface IProps { TotalPages?: number } +const PhaseController = new GenericController(`${homePath}api/OpenXDA/Phase`, 'Name'); +const MeasurementTypeController = new GenericController(`${homePath}api/OpenXDA/MeasurementType`, 'Name'); +const MeasurementCharacteristicController = new GenericController(`${homePath}api/OpenXDA/MeasurementCharacteristic`, 'Name'); const ChannelScalingForm = (props: IProps) => { - const dispatch = useAppDispatch(); - const [multiplier, setMultiplier] = React.useState({ Voltage: 1, Current: 1 }); const [Wrappers, setWrappers] = React.useState([]); - const phases = useAppSelector(PhaseSlice.Data) as OpenXDA.Types.Phase[]; - const measurementTypes = useAppSelector(MeasurmentTypeSlice.Data) as OpenXDA.Types.MeasurementType[]; - const measurementCharacteristics = useAppSelector(MeasurementCharacteristicSlice.Data) as OpenXDA.Types.MeasurementCharacteristic[]; + const { FetchData: phases, FetchStatus: pStatus } = useControllerFetch(PhaseController); + const { FetchData: measurementTypes, FetchStatus: mtStatus } = useControllerFetch(MeasurementTypeController); + const { FetchData: measurementCharacteristics, FetchStatus: mcStatus } = useControllerFetch(MeasurementCharacteristicController); - const pStatus = useAppSelector(PhaseSlice.Status) as Application.Types.Status; - const mtStatus = useAppSelector(MeasurmentTypeSlice.Status) as Application.Types.Status; - const mcStatus = useAppSelector(MeasurementCharacteristicSlice.Status) as Application.Types.Status; const [status, setStatus] = React.useState('idle'); const [hover, setHover] = React.useState<('Reset' | 'None' | 'Replace' | 'Adjust')>('None'); @@ -100,21 +97,6 @@ const ChannelScalingForm = (props: IProps) => { localStorage.setItem('SystemCenter.ChannelScaling.' + props.Key + '.I', multiplier.Current.toString()); }, [multiplier.Voltage, multiplier.Current]); - React.useEffect(() => { - if (pStatus == 'uninitiated' || pStatus == 'changed') - dispatch(PhaseSlice.Fetch()); - }, [pStatus]) - - React.useEffect(() => { - if (mtStatus == 'uninitiated' || pStatus == 'changed') - dispatch(MeasurmentTypeSlice.Fetch()); - }, [mtStatus]) - - React.useEffect(() => { - if (mcStatus == 'uninitiated' || pStatus == 'changed') - dispatch(MeasurementCharacteristicSlice.Fetch()); - }, [mcStatus]) - React.useEffect(() => { initializeWrappers(); }, [props.Channels, status, pStatus, mtStatus, mcStatus]); diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/MeterEventChannel.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/MeterEventChannel.tsx index 20408154fb..ceb02b796a 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/MeterEventChannel.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/MeterEventChannel.tsx @@ -24,8 +24,7 @@ import * as React from 'react'; import * as _ from 'lodash'; import { Application, OpenXDA as GemstoneOpenXDA } from '@gpa-gemstone/application-typings'; -import { PhaseSlice, MeasurmentTypeSlice } from '../Store/Store' -import { useAppSelector, useAppDispatch } from '../hooks'; +import { useAppSelector, useAppDispatch, useControllerFetch } from '../hooks'; import { LoadingIcon, ServerErrorIcon, Warning, GenericController } from '@gpa-gemstone/react-interactive'; import { Input, Select, ToolTip } from '@gpa-gemstone/react-forms'; import { AssetAttributes } from '../AssetAttribute/Asset'; @@ -42,6 +41,8 @@ interface IProps { Meter: GemstoneOpenXDA.Types.Meter, IsVisible: boolean } type RecordChange = Map>; const EventChannelController = new GenericController(`${homePath}api/OpenXDA/EventChannel`, "Name"); +const PhaseController = new GenericController(`${homePath}api/OpenXDA/Phase`, 'Name'); +const MeasurementTypeController = new GenericController(`${homePath}api/OpenXDA/MeasurementType`, 'Name'); const MeterEventChannelWindow = (props: IProps) => { const dispatch = useAppDispatch(); @@ -57,12 +58,12 @@ const MeterEventChannelWindow = (props: IProps) => { const [status, setStatus] = React.useState('uninitiated'); const [refreshTrigger, setRefreshTrigger] = React.useState(false); - const phases = useAppSelector(PhaseSlice.Data) as GemstoneOpenXDA.Types.Phase[]; - const measurementTypes = useAppSelector(MeasurmentTypeSlice.Data) as GemstoneOpenXDA.Types.MeasurementType[]; + + const { FetchData: phases, FetchStatus: pStatus } = useControllerFetch(PhaseController); + const { FetchData: measurementTypes, FetchStatus: mtStatus } = useControllerFetch(MeasurementTypeController); + const [assets, setAssets] = React.useState([]); - const pStatus = useAppSelector(PhaseSlice.Status) as Application.Types.Status; - const mtStatus = useAppSelector(MeasurmentTypeSlice.Status) as Application.Types.Status; const [assetStatus, setAssetStatus] = React.useState('idle') const [removeRecord, setRemoveRecord] = React.useState(null); @@ -71,16 +72,6 @@ const MeterEventChannelWindow = (props: IProps) => { const [hover, setHover] = React.useState<('Update' | 'Reset' | 'None' | 'Add')>('None'); const roles = useAppSelector(SelectRoles); - React.useEffect(() => { - if (pStatus == 'uninitiated' || pStatus == 'changed') - dispatch(PhaseSlice.Fetch()); - }, [pStatus]) - - React.useEffect(() => { - if (mtStatus == 'uninitiated' || mtStatus == 'changed') - dispatch(MeasurmentTypeSlice.Fetch()); - }, [mtStatus]) - React.useEffect(() => { setStatus('loading'); const handle = EventChannelController.PagedSearch([], sortKey, ascending, page, props.Meter.ID); diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/MeterTrendChannel.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/MeterTrendChannel.tsx index 56e63f0530..7462c50b66 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/MeterTrendChannel.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/MeterTrendChannel.tsx @@ -29,9 +29,8 @@ import { LoadingIcon, ServerErrorIcon, Warning, GenericController } from '@gpa-g import { Input, Select, ToolTip } from '@gpa-gemstone/react-forms'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; import { IsNumber } from '@gpa-gemstone/helper-functions'; -import { PhaseSlice, MeasurmentTypeSlice, MeasurementCharacteristicSlice } from '../Store/Store'; import { AssetAttributes } from '../AssetAttribute/Asset'; -import { useAppSelector, useAppDispatch } from '../hooks'; +import { useAppSelector, useControllerFetch } from '../hooks'; import { ConfigurableTable, ConfigurableColumn, Column, Paging } from '@gpa-gemstone/react-table'; import { SelectRoles } from '../Store/UserSettings'; @@ -40,9 +39,11 @@ declare var homePath: string; interface IProps { Meter: GemstoneOpenXDA.Types.Meter, IsVisible: boolean } const TrendChannelController = new GenericController(`${homePath}api/OpenXDA/TrendChannel`, 'Name'); +const PhaseController = new GenericController(`${homePath}api/OpenXDA/Phase`, 'Name'); +const MeasurementTypeController = new GenericController(`${homePath}api/OpenXDA/MeasurementType`, 'Name'); +const MeasurementCharacteristicController = new GenericController(`${homePath}api/OpenXDA/MeasurementCharacteristic`, 'Name'); const MeterTrendChannelWindow = (props: IProps) => { - const dispatch = useAppDispatch(); const [data, setData] = React.useState([]); const [sortKey, setSortKey] = React.useState('Name'); @@ -55,14 +56,12 @@ const MeterTrendChannelWindow = (props: IProps) => { const [recordChanges, setRecordChanges] = React.useState>>(new Map()); const [refreshTrigger, setRefreshTrigger] = React.useState(false); - const phases = useAppSelector(PhaseSlice.Data) as GemstoneOpenXDA.Types.Phase[]; - const measurementTypes = useAppSelector(MeasurmentTypeSlice.Data) as GemstoneOpenXDA.Types.MeasurementType[]; - const measurementCharacteristics = useAppSelector(MeasurementCharacteristicSlice.Data) as GemstoneOpenXDA.Types.MeasurementCharacteristic[]; + const { FetchData: phases, FetchStatus: phaseStatus } = useControllerFetch(PhaseController); + const { FetchData: measurementTypes, FetchStatus: mtStatus } = useControllerFetch(MeasurementTypeController); + const { FetchData: measurementCharacteristics, FetchStatus: mcStatus } = useControllerFetch(MeasurementCharacteristicController); + const [assets, setAssets] = React.useState([]); - const phaseStatus = useAppSelector(PhaseSlice.Status) as Application.Types.Status; - const mtStatus = useAppSelector(MeasurmentTypeSlice.Status) as Application.Types.Status; - const mcStatus = useAppSelector(MeasurementCharacteristicSlice.Status) as Application.Types.Status; const [assetStatus, setAssetStatus] = React.useState('idle') const [removeRecord, setRemoveRecord] = React.useState(null); @@ -71,21 +70,6 @@ const MeterTrendChannelWindow = (props: IProps) => { const [hover, setHover] = React.useState<('Update' | 'Reset' | 'None' | 'Add')>('None'); const roles = useAppSelector(SelectRoles); - React.useEffect(() => { - if (phaseStatus == 'uninitiated' || phaseStatus == 'changed') - dispatch(PhaseSlice.Fetch()); - }, [phaseStatus]); - - React.useEffect(() => { - if (mtStatus == 'uninitiated' || mtStatus == 'changed') - dispatch(MeasurmentTypeSlice.Fetch()); - }, [mtStatus]); - - React.useEffect(() => { - if (mcStatus == 'uninitiated' || mcStatus == 'changed') - dispatch(MeasurementCharacteristicSlice.Fetch()); - }, [mcStatus]); - React.useEffect(() => { setStatus('loading'); const handle = TrendChannelController.PagedSearch([], sortKey, ascending, page, props.Meter.ID); diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/NewMeterWizard/AssetPage.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/NewMeterWizard/AssetPage.tsx index 83304a7b11..b9bbdbdb22 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/NewMeterWizard/AssetPage.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/NewMeterWizard/AssetPage.tsx @@ -23,8 +23,7 @@ import * as React from 'react'; import * as _ from 'lodash'; -import { Application, OpenXDA, SystemCenter } from '@gpa-gemstone/application-typings'; -import { CreateGuid } from '@gpa-gemstone/helper-functions'; +import { OpenXDA, SystemCenter } from '@gpa-gemstone/application-typings'; import BreakerAttributes from '../AssetAttribute/Breaker'; import BusAttributes from '../AssetAttribute/Bus'; import CapBankAttributes from '../AssetAttribute/CapBank'; @@ -32,16 +31,15 @@ import LineAttributes from '../AssetAttribute/Line'; import TransformerAttributes from '../AssetAttribute/Transformer'; import { AssetAttributes } from '../AssetAttribute/Asset'; import CapBankRelayAttributes from '../AssetAttribute/CapBankRelay'; -import { useAppDispatch, useAppSelector } from '../hooks'; -import { ByAssetSlice, AssetTypeSlice } from '../Store/Store'; +import { useAppDispatch, useAppSelector, useControllerFetch } from '../hooks'; +import { ByAssetSlice } from '../Store/Store'; import { SelectAssetStatus, FetchAsset, SelectAssets } from '../Store/AssetSlice'; -import { Modal, Search, TabSelector } from '@gpa-gemstone/react-interactive'; +import { Modal, Search, TabSelector, GenericController } from '@gpa-gemstone/react-interactive'; import DERAttributes from '../AssetAttribute/DER'; import AssetSelect from '../Asset/AssetSelect'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; import { getAssetWithAdditionalFields } from '../../../TS/Services/Asset'; import LocationDrawings from '../Meter/PropertyUI/LocationDrawings'; -import { GetNodeSize } from '@gpa-gemstone/helper-functions'; import { Table, Column } from '@gpa-gemstone/react-table'; import GenerationAttributes from '../AssetAttribute/Generation'; import StationAuxAttributes from '../AssetAttribute/StationAux'; @@ -65,12 +63,14 @@ interface IProps { // temp key for new assets, this should never make it out of here const tempKey = "Something_is_wrong_with_NMW"; +const AssetTypeController = new GenericController(`${homePath}api/OpenXDA/AssetType`, 'Name'); + type AssetType = OpenXDA.Types.DetailedAsset export default function AssetPage(props: IProps) { const dispatch = useAppDispatch(); - const assetTypes = useAppSelector(AssetTypeSlice.Data); - const atStatus = useAppSelector(AssetTypeSlice.Status); + const { FetchData: assetTypes } = useControllerFetch(AssetTypeController); + const assets = useAppSelector(SelectAssets); const aStatus = useAppSelector(SelectAssetStatus); const byAssetStatus = useAppSelector(ByAssetSlice.Status); @@ -152,14 +152,6 @@ export default function AssetPage(props: IProps) { setChannelsWorking(props.Channels); }, [props.Channels]); - React.useEffect(() => { - if (atStatus === 'uninitiated' || atStatus === 'changed') { - dispatch(AssetTypeSlice.Fetch()); - return function () { - } - } - }, [atStatus]); - React.useEffect(() => { if (aStatus === 'uninitiated' || aStatus === 'changed') { dispatch(FetchAsset()); diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/NewMeterWizard/ChannelPage.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/NewMeterWizard/ChannelPage.tsx index 5eb1bd9663..68a33af109 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/NewMeterWizard/ChannelPage.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/NewMeterWizard/ChannelPage.tsx @@ -26,13 +26,12 @@ import * as _ from 'lodash'; import { OpenXDA, Application, SystemCenter } from '@gpa-gemstone/application-typings'; import CFGParser from '../../../TS/CFGParser'; import { Input, Select, TextArea } from '@gpa-gemstone/react-forms'; -import { Modal, Warning, ServerErrorIcon, BtnDropdown } from '@gpa-gemstone/react-interactive'; +import { Modal, Warning, ServerErrorIcon, BtnDropdown, GenericController } from '@gpa-gemstone/react-interactive'; import PARParser from '../../../TS/PARParser'; import { ConfigurableTable, ConfigurableColumn, Column } from '@gpa-gemstone/react-table'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; import ChannelScalingForm from '../Meter/ChannelScaling/ChannelScalingForm'; -import { MeasurementCharacteristicSlice, MeasurmentTypeSlice, PhaseSlice } from '../Store/Store'; -import { useAppDispatch, useAppSelector } from '../hooks'; +import { useControllerFetch } from '../hooks'; import TemplateWindow from './TemplateWindow'; import VirtualChannelModal from './VirtualChannelModal'; declare var homePath: string; @@ -47,9 +46,11 @@ interface IProps { TrendChannels: boolean, IsEngineer: boolean, } +const PhaseController = new GenericController(`${homePath}api/OpenXDA/Phase`, 'Name'); +const MeasurementTypeController = new GenericController(`${homePath}api/OpenXDA/MeasurementType`, 'Name'); +const MeasurementCharacteristicController = new GenericController(`${homePath}api/OpenXDA/MeasurementCharacteristic`, 'Name'); export default function ChannelPage(props: IProps) { - const dispatch = useAppDispatch(); const fileInput = React.useRef(null); const [showCFGError, setShowCFGError] = React.useState(false); @@ -68,13 +69,9 @@ export default function ChannelPage(props: IProps) { const [sortKey, setSortKey] = React.useState('Series'); const [asc, setAsc] = React.useState(true); - const phases = useAppSelector(PhaseSlice.Data); - const measurementCharateristics = useAppSelector(MeasurementCharacteristicSlice.Data); - const measurementTypes = useAppSelector(MeasurmentTypeSlice.Data); - - const pStatus = useAppSelector(PhaseSlice.Status); - const mCStatus = useAppSelector(MeasurementCharacteristicSlice.Status); - const mTStatus = useAppSelector(MeasurmentTypeSlice.Status); + const { FetchData: phases, FetchStatus: pStatus } = useControllerFetch(PhaseController); + const { FetchData: measurementTypes, FetchStatus: mTStatus } = useControllerFetch(MeasurementTypeController); + const { FetchData: measurementCharateristics, FetchStatus: mCStatus } = useControllerFetch(MeasurementCharacteristicController); const baseWarnings: string[] = ["Ensure all Scaling values are correct.", "Ensure all virtual Channels are configured."]; const serverParsedExtensions: string[] = ['pqd', 'sel', 'cev', 'eve', 'ctl', 'txt']; @@ -102,21 +99,6 @@ export default function ChannelPage(props: IProps) { } }, []); - React.useEffect(() => { - if (mTStatus == 'uninitiated' || mTStatus == 'changed') - dispatch(MeasurmentTypeSlice.Fetch()); - }, [mTStatus]); - - React.useEffect(() => { - if (mCStatus == 'uninitiated' || mCStatus == 'changed') - dispatch(MeasurementCharacteristicSlice.Fetch()); - }, [mCStatus]); - - React.useEffect(() => { - if (pStatus == 'uninitiated' || pStatus == 'changed') - dispatch(PhaseSlice.Fetch()); - }, [pStatus]); - React.useEffect(() => { props.SetWarning(baseWarnings) @@ -200,9 +182,6 @@ export default function ChannelPage(props: IProps) { const channels = sortChannels(data); handleParsedChannels(channels); // Need to fetch these after since the server parser will add new things to these if it spots them - dispatch(PhaseSlice.SetChanged()); - dispatch(MeasurementCharacteristicSlice.SetChanged()); - dispatch(MeasurmentTypeSlice.SetChanged()); }).fail(() => { setChannelStatus('error'); }); diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/NewMeterWizard/TemplateWindow.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/NewMeterWizard/TemplateWindow.tsx index a1c63d48f5..318c2dde98 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/NewMeterWizard/TemplateWindow.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/NewMeterWizard/TemplateWindow.tsx @@ -23,14 +23,10 @@ import * as React from 'react'; import * as _ from 'lodash'; -import { OpenXDA, Application } from '@gpa-gemstone/application-typings'; -import CFGParser from '../../../TS/CFGParser'; import { CheckBox, Input, ToolTip } from '@gpa-gemstone/react-forms'; import { Modal, LoadingIcon } from '@gpa-gemstone/react-interactive'; -import PARParser from '../../../TS/PARParser'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; -import ChannelScalingForm from '../Meter/ChannelScaling/ChannelScalingForm'; -import { MeasurementCharacteristicSlice, MeasurmentTypeSlice, PhaseSlice, ChannelTemplateSlice } from '../Store/Store'; +import { ChannelTemplateSlice } from '../Store/Store'; import { useAppDispatch, useAppSelector } from '../hooks'; import { Table, Column } from '@gpa-gemstone/react-table'; import { SystemCenter } from '../global'; diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Settings/DataOperations.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Settings/DataOperations.tsx index e9514aebcb..673d7c1596 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Settings/DataOperations.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Settings/DataOperations.tsx @@ -25,7 +25,6 @@ import { Application, OpenXDA } from '@gpa-gemstone/application-typings'; import { SystemCenter } from '../global'; import { GenericController, Modal, Warning } from '@gpa-gemstone/react-interactive'; import * as React from 'react'; -import { DataOperationSlice } from '../Store/Store'; import GenericByPage from '../CommonComponents/GenericByPage'; import { Input } from '@gpa-gemstone/react-forms'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; @@ -155,7 +154,6 @@ const DataOperations: Application.Types.iByComponent = (props) => { CallBack={(conf) => { if (conf) DataOperationController.DBAction("DELETE", editnewSetting).done(() => refreshData(x => x + 1)); - DataOperationSlice.DBAction({ verb: 'DELETE', record: editnewSetting }); setShowWarning(false); }} /> diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Store/Store.ts b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Store/Store.ts index 0a797447e8..3bc909fddc 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Store/Store.ts +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Store/Store.ts @@ -35,7 +35,7 @@ import { DBCleanup } from '../DB/DBCleanup'; import { ApplicationCategory } from '../ApplicationCategory/ByApplicationCategory'; import { OpenXDA as LocalXDA, SystemCenter as LocalSystemCenter } from '../global' import PQISlice from './PQISlice'; -import { IApplicationRole, ISecurityGroup, IUserAccount } from '../User/Types'; +import { IApplicationRole } from '../User/Types'; import UserSettingsReducer from './UserSettings'; import { EventWidget } from '../../../../../EventWidgets/TSX/global'; import { IAPIAccessKey } from '../APIAccessKeys/APIAccessKeys' @@ -54,41 +54,24 @@ export const ValueListSlice = new GenericSlice export const LocationDrawingSlice = new GenericSlice('LocationDrawing', `${homePath}api/LocationDrawing`, 'Name'); export const ChannelGroupSlice = new GenericSlice('ChannelGroup', `${homePath}api/ChannelGroup`, 'Name'); -export const ChannelGroupViewSlice = new GenericSlice('ChannelGroupView', `${homePath}api/ChannelGroup`, 'Name'); export const ChannelGroupDetailsSlice = new GenericSlice('ChannelGroupDetails', `${homePath}api/ChannelGroupDetails`, 'DisplayName'); export const SystemCenterSettingSlice = new GenericSlice('SystemCenterSetting', `${homePath}api/Setting`, 'Name'); -export const OpenXDASettingSlice = new GenericSlice('OpenXDASetting', `${homePath}api/OpenXDA/Setting`, 'Name'); -export const MiMDSettingSlice = new GenericSlice('MiMDSetting', `${homePath}api/MiMD/Setting`, 'Name'); -export const OpenSEESettingSlice = new GenericSlice('OpenSEESetting', `${homePath}api/OpenSEE/Setting`, 'Name'); -export const SEBrowserSettingSlice = new GenericSlice('SEBrowserSetting', `${homePath}api/SEBrowser/Setting`, 'Name'); -export const PQDigestSettingSlice = new GenericSlice('PQDigestSetting', `${homePath}api/PQDigest/Setting`, 'Name'); - export const AssetConnectionTypeSlice = new GenericSlice("AssetConnectionType", `${homePath}api/OpenXDA/AssetConnectionType`, 'Name'); -export const AssetTypeSlice = new GenericSlice("AssetType", `${homePath}api/OpenXDA/AssetType`, 'Name'); -export const PhaseSlice = new GenericSlice("Phase", `${homePath}api/OpenXDA/Phase`, 'Name'); -export const MeasurmentTypeSlice = new GenericSlice("MeasurementType", `${homePath}api/OpenXDA/MeasurementType`, 'Name'); export const MeasurementCharacteristicSlice = new GenericSlice("MeasurementCharacteristic", `${homePath}api/OpenXDA/MeasurementCharacteristic`, 'Name'); export const EventTypeAssetTypeSlice = new GenericSlice("EventTypeAssetType", `${homePath}api/OpenXDA/EventTypeAssetType`, "ID", false); export const CustomerSlice = new GenericSlice("Customer", `${homePath}api/SystemCenter/Customer`, "Name", true); -export const CustomerMeterSlice = new GenericSlice('CustomerMeter', `${homePath}api/SystemCenter/CustomerMeter`, 'MeterName', true); -export const CustomerAssetSlice = new GenericSlice('CustomerAsset', `${homePath}api/SystemCenter/CustomerAsset`, 'AssetName', true); - export const EventTypeSlice = new GenericSlice("EventType", `${homePath}api/OpenXDA/EventType`, "Name"); export const LocationSlice = new GenericSlice("Location", `${homePath}api/OpenXDA/Location`, "LocationKey", true); -export const DataOperationSlice = new GenericSlice("DataOperation", `${homePath}api/OpenXDA/DataOperation`, "LoadOrder"); -export const DataReaderSlice = new GenericSlice("DataReader", `${homePath}api/OpenXDA/DataReader`, "LoadOrder"); export const ExternalDatabasesSlice = new GenericSlice("ExternalDatabases", `${homePath}api/SystemCenter/ExternalDatabases`, "Name", false); export const ExternalDBTablesSlice = new GenericSlice("ExternalDataBaseTable", `${homePath}api/SystemCenter/extDBTables`, "TableName", true); export const AdditionalFieldsSlice = new GenericSlice("AdditionalFields", `${homePath}api/SystemCenter/AdditionalFieldView`, "FieldName", true); export const ExternalXDAFieldsSlice = new GenericSlice("ExternalOpenXDAField", `${homePath}api/SystemCenter/ExternalOpenXDAField`, "FieldName", true); export const ApplicationNodeSlice = new GenericSlice("ApplicationNode", `${homePath}api/OpenXDA/ApplicationNode`, "Name", true); -export const LSCVSAccountSlice = new GenericSlice("LSCVSAccount", `${homePath}api/LSCVSAccount`, "AccountID", false); -export const DBCleanupSlice = new GenericSlice("DBCleanup", `${homePath}api/OpenXDA/DBCleanup`, "ID", true); export const ApplicationCategorySlice = new GenericSlice("PQApplicationCategory", `${homePath}api/OpenXDA/ApplicationCategory`, "Name", true); export const PQApplicationsSlice = new GenericSlice("PQApplications", `${homePath}api/OpenXDA/PQApplications`, "SortOrder", true); @@ -100,18 +83,14 @@ export const ByMeterSlice = new GenericSlice(" export const RemoteXDAInstanceSlice = new GenericSlice("remoteXDAInstance", `${homePath}api/OpenXDA/remoteXDAInstance`, "Name", false); export const RemoteXDAMeterSlice = new GenericSlice("RemoteXDAMeter", `${homePath}api/OpenXDA/RemoteXDAMeter`, "LocalMeterName", false); export const RemoteXDAAssetSlice = new GenericSlice("RemoteXDAAsset", `${homePath}api/OpenXDA/RemoteXDAAsset`, "LocalAssetName", false); -export const UserAccountSliceRemote = new GenericSlice("UserAccountRemote", `${homePath}api/SystemCenter/RemoteUserAccount`, "Name", false); export const AssetNoteSlice = new NoteSlice('Asset'); export const MeterNoteSlice = new NoteSlice('Meter'); export const UserNoteSlice = new NoteSlice('User'); export const LocationNoteSlice = new NoteSlice('Location'); export const CustomerNoteSlice = new NoteSlice('Customer'); -export const UserAccountSlice = new GenericSlice('UserAccounts', `${homePath}api/SystemCenter/UserAccount`, "DisplayName", true); export const UserAdditionalFieldSlice = new AdditionalUserFieldSlice('AdditionalUserFields', `${homePath}api/SystemCenter`); -export const SourceImpedanceSlice = new GenericSlice("SourceImpedance", `${homePath}api/OpenXDA/SourceImpedance`, "AssetLocationID", false); -export const SecurityGroupSlice = new GenericSlice("SecurityGroup", `${homePath}api/SystemCenter/FullSecurityGroup`, "DisplayName", true) export const ApplicationRoleSlice = new GenericSlice("ApplicationRole", `${homePath}api/SystemCenter/ApplicationRole`, "Name", false) export const WidgetCategorySlice = new GenericSlice("WidgetCategory", `${homePath}api/SystemCenter/WidgetCategory`, "OrderBy", true) @@ -121,7 +100,6 @@ export const PQDigestHomeScreenSlice = new GenericSlice("SEBrowserWidgetView", `${homePath}api/SEbrowser/WidgetView`, "Name", true) -export const MagDurCurveSlice = new GenericSlice('MagDurCurve', `${homePath}api/SystemCenter/StandardMagDurCurve`, 'Name'); export const APIAccessKeySlice = new GenericSlice('APIAccessKey', `${homePath}api/OpenXDA/APIAccessKey`, 'RegistrationKey'); @@ -132,8 +110,6 @@ export const MATLABAnalyticSlice = new GenericSlice("MATLABAnalyticEventType", `${homePath}api/OpenXDA/MATLABAnalyticEventType`, 'ID'); export const MATLABAnalyticAssetTypeSlice = new GenericSlice("MATLABAnalyticAssetType", `${homePath}api/OpenXDA/MATLABAnalyticAssetType`, 'ID'); -export const TrendChannelSlice = new GenericSlice('TrendChannels', `${homePath}api/OpenXDA/TrendChannel`, 'Name'); - export const ChannelTemplateSlice = new GenericSlice('ChannelTemplate', `${homePath}api/SystemCenter/ChannelTemplateFile`, 'ID', true); export const ConfigSlice = new ConfigurationSlice("Config"); @@ -141,14 +117,11 @@ export const ConfigSlice = new ConfigurationSlice("Config"); const store = configureStore({ reducer: { UserSettings: UserSettingsReducer, - LSCVSAccount: LSCVSAccountSlice.Reducer, EventTypeAssetType: EventTypeAssetTypeSlice.Reducer, AssetGroup: AssetGroupSlice.Reducer, ByAsset: ByAssetSlice.Reducer, ByMeter: ByMeterSlice.Reducer, ByLocation: ByLocationSlice.Reducer, - MeasurementType: MeasurmentTypeSlice.Reducer, - Phase: PhaseSlice.Reducer, AssetConnectionType: AssetConnectionTypeSlice.Reducer, Meter: MeterReducer, Asset: AssetReducer, @@ -159,46 +132,30 @@ const store = configureStore({ ChannelGroupDetails: ChannelGroupDetailsSlice.Reducer, LocationDrawing: LocationDrawingSlice.Reducer, SystemCenterSetting: SystemCenterSettingSlice.Reducer, - OpenXDASetting: OpenXDASettingSlice.Reducer, - MiMDSetting: MiMDSettingSlice.Reducer, - OpenSEESetting: OpenSEESettingSlice.Reducer, - SEBrowserSetting: SEBrowserSettingSlice.Reducer, - PQDigestSetting: PQDigestSettingSlice.Reducer, - AssetType: AssetTypeSlice.Reducer, Customer: CustomerSlice.Reducer, AssetNote: AssetNoteSlice.Reducer, MeterNote: MeterNoteSlice.Reducer, UserNote: UserNoteSlice.Reducer, LocationNote: LocationNoteSlice.Reducer, CustomerNote: CustomerNoteSlice.Reducer, - UserAccounts: UserAccountSlice.Reducer, AdditionalUserFields: UserAdditionalFieldSlice.Reducer, EventType: EventTypeSlice.Reducer, MeasurementCharacteristic: MeasurementCharacteristicSlice.Reducer, - DataOperation: DataOperationSlice.Reducer, - DataReader: DataReaderSlice.Reducer, ExternalDataBaseTable: ExternalDBTablesSlice.Reducer, remoteXDAInstance: RemoteXDAInstanceSlice.Reducer, RemoteXDAAsset: RemoteXDAAssetSlice.Reducer, RemoteXDAMeter: RemoteXDAMeterSlice.Reducer, - UserAccountRemote: UserAccountSliceRemote.Reducer, ApplicationNode: ApplicationNodeSlice.Reducer, PQApplicationCategory: ApplicationCategorySlice.Reducer, PQApplications: PQApplicationsSlice.Reducer, - DBCleanup: DBCleanupSlice.Reducer, EventChannels: EventChannelSlice.reducer, - CustomerMeter: CustomerMeterSlice.Reducer, - CustomerAsset: CustomerAssetSlice.Reducer, PQI: PQISlice, - SourceImpedance: SourceImpedanceSlice.Reducer, - SecurityGroup: SecurityGroupSlice.Reducer, WidgetCategory: WidgetCategorySlice.Reducer, ApplicationRole: ApplicationRoleSlice.Reducer, SEBrowserWidget: SEBrowserWidgetSlice.Reducer, PQDigestWidget: PQDigestWidgetSlice.Reducer, PQDigestHomeScreenWidget: PQDigestHomeScreenSlice.Reducer, SEBrowserWidgetView: SEBrowserWidgetViewSlice.Reducer, - MagDurCurve: MagDurCurveSlice.Reducer, APIAccessKey: APIAccessKeySlice.Reducer, ExternalDatabases: ExternalDatabasesSlice.Reducer, AdditionalFields: AdditionalFieldsSlice.Reducer, @@ -207,9 +164,7 @@ const store = configureStore({ MATLABAnalytic: MATLABAnalyticSlice.Reducer, MATLABAnalyticEventType: MATLABAnalyticEventTypeSlice.Reducer, MATLABAnalyticAssetType: MATLABAnalyticAssetTypeSlice.Reducer, - TrendChannels: TrendChannelSlice.Reducer, ChannelTemplate: ChannelTemplateSlice.Reducer, - ChannelGroupView: ChannelGroupViewSlice.Reducer, ValueListGroupView: ValueListGroupViewSlice.Reducer, Config: ConfigSlice.Reducer } diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/User/ByUser.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/User/ByUser.tsx index 863dadbf53..0449682015 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/User/ByUser.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/User/ByUser.tsx @@ -23,13 +23,11 @@ import * as React from 'react'; import { Table, Column, Paging } from '@gpa-gemstone/react-table'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; -import { SearchBar, Search, Modal, ServerErrorIcon, LoadingScreen } from '@gpa-gemstone/react-interactive'; +import { SearchBar, Search, Modal, ServerErrorIcon, LoadingScreen, GenericController } from '@gpa-gemstone/react-interactive'; import { SystemCenter, Application } from '@gpa-gemstone/application-typings'; import * as _ from 'lodash'; import UserForm from './UserForm'; -import { useAppDispatch, useAppSelector, useBoundPaging } from '../../hooks'; import { useNavigate } from "react-router-dom"; -import { ValueListSlice, ValueListGroupSlice, UserAdditionalFieldSlice, UserAccountSlice } from '../../Store/Store'; import { IUserAccount } from '../Types'; import moment from 'moment'; @@ -61,74 +59,111 @@ const newAcct: IUserAccount = { DisplayName: '' } +const UserAccountController = new GenericController(`${homePath}api/SystemCenter/UserAccount`, "DisplayName", true); + const ByUser: Application.Types.iByComponent = (props) => { let navigate = useNavigate(); - const dispatch = useAppDispatch(); - const search = useAppSelector(UserAccountSlice.SearchFilters); + const [search, setSearch] = React.useState[]>([]) - const data = useAppSelector(UserAccountSlice.SearchResults); - const userStatus: Application.Types.Status = useAppSelector(UserAccountSlice.Status); - const searchStatus: Application.Types.Status = useAppSelector(UserAccountSlice.SearchStatus); + const [data, setData] = React.useState([]); + const [searchStatus, setSearchStatus] = React.useState('uninitiated'); - const sortField = useAppSelector(UserAccountSlice.SortField) - const ascending = useAppSelector(UserAccountSlice.Ascending) - const currentPage = useAppSelector(UserAccountSlice.CurrentPage); - const totalPages = useAppSelector(UserAccountSlice.TotalPages); - const totalRecords = useAppSelector(UserAccountSlice.TotalRecords); + const [sortField, setSortField] = React.useState('DisplayName'); + const [ascending, setAscending] = React.useState(true); + const [page, setPage] = React.useState(0); + const [totalPages, setTotalPages] = React.useState(0); + const [totalRecords, setTotalRecords] = React.useState(0); + const [recordsPerPage, setRecordsPerPage] = React.useState(0); + const [refreshTrigger, setRefreshTrigger] = React.useState(false); - const adlFields: Application.Types.iAdditionalUserField[] = useAppSelector(UserAdditionalFieldSlice.Fields) - const adlFieldStatus: Application.Types.Status = useAppSelector(UserAdditionalFieldSlice.FieldStatus) + const [adlFields, setAdlFields] = React.useState([]) + const [adlFieldStatus, setAdlFieldStatus] = React.useState('uninitiated'); const [filterableList, setFilterableList] = React.useState[]>(defaultSearchcols); const [showModal, setShowModal] = React.useState(false); const [userError, setUserError] = React.useState([]); - const valueListItems: SystemCenter.Types.ValueListItem[] = useAppSelector(ValueListSlice.Data); - const valueListItemStatus: Application.Types.Status = useAppSelector(ValueListSlice.Status); + const [valueListItems, setValueListItems] = React.useState([]); + const [valueListItemStatus, setValueListItemStatus] = React.useState('uninitiated'); - const valueListGroups: SystemCenter.Types.ValueListGroup[] = useAppSelector(ValueListGroupSlice.Data); - const valueListGroupStatus: Application.Types.Status = useAppSelector(ValueListGroupSlice.Status); + const [valueListGroups, setValueListGroups] = React.useState([]); + const [valueListGroupStatus, setValueListGroupStatus] = React.useState('uninitiated'); const [act, setAct] = React.useState(newAcct) const [pageStatus, setPageStatus] = React.useState('uninitiated'); React.useEffect(() => { - if (userStatus === 'error' || adlFieldStatus === 'error' || valueListItemStatus === 'error' || valueListGroupStatus === 'error') + if (adlFieldStatus === 'error' || valueListItemStatus === 'error' || valueListGroupStatus === 'error') setPageStatus('error') - else if (userStatus === 'loading' || adlFieldStatus === 'loading' || valueListItemStatus === 'loading' || valueListGroupStatus === 'loading') + else if (adlFieldStatus === 'loading' || valueListItemStatus === 'loading' || valueListGroupStatus === 'loading') setPageStatus('loading') else setPageStatus('idle'); - }, [userStatus, adlFieldStatus, valueListItemStatus, valueListGroupStatus]) + }, [adlFieldStatus, valueListItemStatus, valueListGroupStatus]) React.useEffect(() => { - if (searchStatus === 'uninitiated' || searchStatus === 'changed') - dispatch(UserAccountSlice.PagedSearch({ filter: search, sortField: sortField ?? "ID", ascending: ascending, page: currentPage})); - }, [searchStatus, search, sortField, ascending, currentPage]); + setAdlFieldStatus('loading') + const handle = new GenericController(`${homePath}api/SystemCenter/AdditionalUserField`, "FieldName").Fetch(); + handle.done((d) => { + setAdlFields(d) + setAdlFieldStatus('idle') + }).fail(() => { + setAdlFieldStatus('error') + }) + return () => { + if (handle != null && handle.abort != null) + handle.abort() + }; + }, []); React.useEffect(() => { - if (adlFieldStatus === 'uninitiated' || adlFieldStatus === 'changed') - dispatch(UserAdditionalFieldSlice.FetchField()); - }, [adlFieldStatus]); + setValueListItemStatus('loading') + const handle = new GenericController(`${homePath}api/ValueList`, 'SortOrder').Fetch(); + handle.done((d) => { + setValueListItems(d) + setValueListItemStatus('idle') + }).fail(() => { + setValueListItemStatus('error') + }) + + return () => { + if (handle != null && handle.abort != null) handle.abort(); + } + }, []); React.useEffect(() => { - if (valueListItemStatus === 'uninitiated' || valueListItemStatus === 'changed') - dispatch(ValueListSlice.Fetch()); - }, [valueListItemStatus]); + setValueListGroupStatus('loading') + const handle = new GenericController(`${homePath}api/ValueListGroup`, 'Name').Fetch(); + handle.done((d) => { + setValueListGroups(d) + setValueListGroupStatus('idle') + }).fail(() => { + setValueListGroupStatus('error') + }) + + return () => { + if (handle != null && handle.abort != null) handle.abort(); + } + }, []); React.useEffect(() => { - if (valueListGroupStatus === 'uninitiated' || valueListGroupStatus === 'changed') - dispatch(ValueListGroupSlice.Fetch()); - }, [valueListGroupStatus]); - - const setPage = React.useCallback((page) => { - dispatch(UserAccountSlice.PagedSearch({ filter: search, sortField: sortField ?? "ID", ascending: ascending, page: page - 1 })) - }, [sortField, ascending, search]) - - useBoundPaging(currentPage, totalPages, setPage) + setSearchStatus('loading') + const handle = UserAccountController.PagedSearch(search, sortField, ascending, page); + handle.done((d) => { + setData(JSON.parse(d.Data as unknown as string)); + setTotalPages(d.NumberOfPages); + setTotalRecords(d.TotalRecords); + setRecordsPerPage(d.RecordsPerPage); + if (page >= d.NumberOfPages) + setPage(Math.max(d.NumberOfPages - 1, 0)); + setSearchStatus('idle') + }) + handle.fail(() => setSearchStatus('error')) + return () => { if (handle != null && handle.abort != null) handle.abort() } + }, [page, search, sortField, ascending, refreshTrigger]) React.useEffect(() => { function ConvertType(type: string) { @@ -143,10 +178,6 @@ const ByUser: Application.Types.iByComponent = (props) => { setFilterableList(ordered) }, [adlFields]); - const setFilters = React.useCallback((filters: Search.IFilter[]) => { - dispatch(UserAccountSlice.PagedSearch({ sortField: sortField ?? "ID", ascending: ascending, filter: filters, page: currentPage })) - }, [sortField, ascending, currentPage]) - if (pageStatus === 'error') return
    @@ -155,9 +186,9 @@ const ByUser: Application.Types.iByComponent = (props) => { return (
    - CollumnList={filterableList} SetFilter={setFilters} + CollumnList={filterableList} SetFilter={setSearch} Direction={'left'} defaultCollumn={{ label: 'Username', key: 'DisplayName', type: 'string', isPivotField: false }} Width={'50%'} Label={'Search'} - ShowLoading={searchStatus === 'loading'} ResultNote={searchStatus === 'error' ? 'Could not complete Search' : 'Found ' + totalRecords + ' User Account(s)'} + ShowLoading={searchStatus === 'loading'} ResultNote={searchStatus === 'error' ? 'Could not complete Search' : `Displaying User(s) ${totalRecords > 0 ? (recordsPerPage * page + 1) : 0} - ${recordsPerPage * page + data.length} out of ${totalRecords}`} StorageID="UsersFilter" GetEnum={(setOptions, field) => { @@ -192,7 +223,12 @@ const ByUser: Application.Types.iByComponent = (props) => { SortKey={sortField} Ascending={ascending} OnSort={(d) => { - dispatch(UserAccountSlice.Sort({ SortField: d.colField, Ascending: d.ascending })); + if (d.colKey === sortField) + setAscending(a => !a); + else { + setAscending(true); + setSortField(d.colKey as keyof IUserAccount); + } }} OnClick={(d) => navigate(`${homePath}index.cshtml?name=User&UserAccountID=${d.row.ID}`)} TableStyle={{ @@ -259,8 +295,8 @@ const ByUser: Application.Types.iByComponent = (props) => {
    setPage(p - 1)} Total={totalPages} />
    @@ -268,7 +304,7 @@ const ByUser: Application.Types.iByComponent = (props) => { { if (confirm) - dispatch(UserAccountSlice.DBAction({ verb: 'POST', record: act })) + UserAccountController.DBAction('POST', act).then(() => setRefreshTrigger(val => !val)) setAct(newAcct); setShowModal(false); }} diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/User/Info.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/User/Info.tsx index a6feffdcd6..af49fbd7fa 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/User/Info.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/User/Info.tsx @@ -23,16 +23,20 @@ import * as React from 'react'; import * as _ from 'lodash'; import UserForm from './UserForm'; +import { Application } from '@gpa-gemstone/application-typings'; import { ToolTip } from '@gpa-gemstone/react-forms'; +import { GenericController } from '@gpa-gemstone/react-interactive' import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; -import { UserAccountSlice } from '../../Store/Store'; -import { useAppDispatch, useAppSelector } from '../../hooks'; import { IUserAccount } from '../Types'; const UserInfo = (props: { AccountId: string }) => { - const dispatch = useAppDispatch(); - const currentUser = useAppSelector((state) => UserAccountSlice.Datum(state,props.AccountId)); + // the user as represented in the database + const [currentUser, setCurrentUser] = React.useState(null) + const [status, setStatus] = React.useState('uninitiated'); + const [refreshTrigger, setRefreshTrigger] = React.useState(false); + + // the user as represented in the form const [user, setUser] = React.useState(currentUser); const [warnings, setWarning] = React.useState([]); const [hover, setHover] = React.useState<('None' | 'Clear')>('None'); @@ -69,8 +73,31 @@ const UserInfo = (props: { AccountId: string }) => { React.useEffect(() => { setUser(currentUser) }, [currentUser]) + React.useEffect(() => { + setStatus('loading'); + const handle = $.ajax({ + type: "GET", + url: `${homePath}api/SystemCenter/UserAccount/One/${props.AccountId}`, + contentType: "application/json; charset=utf-8", + dataType: 'json', + cache: true, + async: true + }) + handle.done((d) => { + setStatus('idle'); + setCurrentUser(d); + }) + handle.fail(() => { + setStatus('error') + }) + return () => { + if (handle != null && handle.abort != null) + handle.abort() + } + }, [props.AccountId, refreshTrigger]) + function updateUser() { - dispatch(UserAccountSlice.DBAction({ verb: 'PATCH', record: user })) + new GenericController(`${homePath}api/SystemCenter/UserAccount`, "DisplayName", true).DBAction( 'PATCH', user).then(() => setRefreshTrigger(val => !val)) } return ( diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/User/User.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/User/User.tsx index dbd48a9fec..511f42829e 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/User/User.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/User/User.tsx @@ -21,15 +21,14 @@ // ****************************************************************************************************** import * as React from 'react'; -import { LoadingScreen, ServerErrorIcon, TabSelector, Warning } from '@gpa-gemstone/react-interactive'; +import { LoadingScreen, ServerErrorIcon, TabSelector, Warning, GenericController } from '@gpa-gemstone/react-interactive'; import { Application } from '@gpa-gemstone/application-typings'; import * as _ from 'lodash'; import UserInfo from './Info'; import UserPermissions from './Permissions'; -import AdditionalField from '../AdditionalUserFieldsWindow' -import { useAppDispatch, useAppSelector } from '../../hooks'; +import AdditionalField from '../AdditionalUserFieldsWindow'; +import { IUserAccount } from '../Types'; import { CheckBox } from '@gpa-gemstone/react-forms'; -import { UserAccountSlice } from '../../Store/Store'; import { useNavigate } from "react-router-dom"; declare type Tab = 'userInfo' | 'permissions' | 'additionalFields' @@ -38,9 +37,8 @@ interface IProps { UserID: string, Tab: Tab } function User(props: IProps) { const navigate = useNavigate(); - const dispatch = useAppDispatch(); - const user = useAppSelector((state) => UserAccountSlice.Datum(state, props.UserID)); - const status: Application.Types.Status = useAppSelector(UserAccountSlice.Status); + const [user, setUser] = React.useState(null); + const [status, setStatus] = React.useState('uninitiated'); const [tab, setTab] = React.useState(getTab()); const [showWarning, setShowWarning] = React.useState(false); @@ -59,9 +57,27 @@ function User(props: IProps) { }, [tab]); React.useEffect(() => { - if (status === 'uninitiated' || status === 'changed') - dispatch(UserAccountSlice.Fetch()); - }, [status]); + setStatus('loading'); + const handle = $.ajax({ + type: "GET", + url: `${homePath}api/SystemCenter/UserAccount/One/${props.UserID}`, + contentType: "application/json; charset=utf-8", + dataType: 'json', + cache: true, + async: true + }) + handle.done((d) => { + setStatus('idle'); + setUser(d); + }) + handle.fail(() => { + setStatus('error') + }) + return () => { + if (handle != null && handle.abort != null) + handle.abort() + } + }, [props.UserID]) if (status === 'error') return
    @@ -107,12 +123,12 @@ function User(props: IProps) { (user == null || user.Type == 'Database' ? 'This will permanently remove the User. Are you sure you want to continue?' : 'This will remove the User from openXDA. The User may still have rights and the ability to log in to the system if they are in an Azure or Active Directory group. Contact your domain administrator to have the User removed from Azure or AD.') } Title={'Delete ' + (user?.AccountName ?? 'User')} Show={showWarning} CallBack={(c) => { - setShowWarning(false); - if (c) { - dispatch(UserAccountSlice.DBAction({ verb: 'DELETE', record: user })); - navigate(`${homePath}index.cshtml?name=Users`); + setShowWarning(false); + if (c) { + new GenericController(`${homePath}api/SystemCenter/UserAccount`, "DisplayName", true).DBAction('DELETE', user).then(() => navigate(`${homePath}index.cshtml?name=Users`)) + }; } - }} /> + } />
    ) } diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/User/UserForm.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/User/UserForm.tsx index 1b5c80999e..4415fccdd9 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/User/UserForm.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/User/UserForm.tsx @@ -21,13 +21,13 @@ // ****************************************************************************************************** import * as React from 'react'; -import { Input, DatePicker, CheckBox } from '@gpa-gemstone/react-forms' +import { Input, DatePicker, CheckBox } from '@gpa-gemstone/react-forms'; +import { GenericController } from '@gpa-gemstone/react-interactive'; import * as _ from 'lodash'; -import { UserAccountSlice } from '../../Store/Store'; -import { useAppDispatch, useAppSelector } from '../../hooks'; import { IUserAccount } from '../Types'; import * as CryptoJS from 'crypto-js'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; +import { Application } from '@gpa-gemstone/application-typings'; import moment from 'moment'; interface IProps { @@ -38,18 +38,28 @@ interface IProps { } function UserForm(props: IProps) { - const dispatch = useAppDispatch(); - const allUsers = useAppSelector(UserAccountSlice.Data); - const userStatus = useAppSelector(UserAccountSlice.Status); + const [allUsers, setAllUsers] = React.useState([]); + const [userStatus, setUserStatus] = React.useState('uninitiated'); const [errors, setErrors] = React.useState([]); const [valid, setValid] = React.useState<'valid' | 'resolving' | 'invalid' | 'unknown'>("valid") React.useEffect(() => { - if (userStatus === 'uninitiated' || userStatus === 'changed') - dispatch(UserAccountSlice.Fetch()); - }, [userStatus]); + setUserStatus('loading'); + const handle = new GenericController(`${homePath}api/SystemCenter/UserAccount`, "DisplayName", true).Fetch(); + handle.done((d) => { + setAllUsers(d); + setUserStatus('idle'); + }) + handle.fail(() => { + setUserStatus('error'); + }) + return () => { + if (handle != null && handle.abort != null) + handle.abort() + } + }, []); React.useEffect(() => { if (props.SetErrors !== undefined) diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/ByUserGroup.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/ByUserGroup.tsx index e87c5a9b62..f888503748 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/ByUserGroup.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/ByUserGroup.tsx @@ -23,14 +23,11 @@ import * as React from 'react'; import { Table, Column, Paging } from '@gpa-gemstone/react-table'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; -import { SearchBar, Search, Modal, ServerErrorIcon, LoadingScreen } from '@gpa-gemstone/react-interactive'; +import { SearchBar, Search, Modal, ServerErrorIcon, LoadingScreen, GenericController } from '@gpa-gemstone/react-interactive'; import { Application } from '@gpa-gemstone/application-typings'; import * as _ from 'lodash'; -import { useAppDispatch, useBoundPaging } from '../../hooks'; import { useNavigate } from "react-router-dom"; -import { SecurityGroupSlice } from '../../Store/Store'; import { ISecurityGroup } from '../Types'; -import { useSelector } from 'react-redux'; import GroupForm from './GroupForm'; const defaultSearchcols: Search.IField[] = [ @@ -41,23 +38,24 @@ const defaultSearchcols: Search.IField[] = [ const emptyGroup: ISecurityGroup = { Name: "", CreatedBy: "", CreatedOn: new Date(), Description: "", DisplayName: "", ID: "00000000-0000-0000-0000-000000000000", Type: "Database", UpdatedOn: new Date() }; +const SecurityGroupController = new GenericController(`${homePath}api/SystemCenter/FullSecurityGroup`, "DisplayName" as keyof ISecurityGroup); const ByUser: Application.Types.iByComponent = (props) => { let navigate = useNavigate(); - const dispatch = useAppDispatch(); - const search = useSelector(SecurityGroupSlice.SearchFilters); - const data = useSelector(SecurityGroupSlice.SearchResults); - const currentPage = useSelector(SecurityGroupSlice.CurrentPage); - const totalPages = useSelector(SecurityGroupSlice.TotalPages); - const totalRecords = useSelector(SecurityGroupSlice.TotalRecords); + const [search, setSearch] = React.useState[]>([]); + const [data, setData] = React.useState([]); - const [status, setStatus] = React.useState('uninitiated'); - const searchStatus = useSelector(SecurityGroupSlice.SearchStatus); + const [searchStatus, setSearchStatus] = React.useState('uninitiated'); - const sortField = useSelector(SecurityGroupSlice.SortField); - const ascending = useSelector(SecurityGroupSlice.Ascending); + const [sortField, setSortField] = React.useState('DisplayName'); + const [ascending, setAscending] = React.useState(true); + const [page, setPage] = React.useState(0); + const [totalPages, setTotalPages] = React.useState(0); + const [totalRecords, setTotalRecords] = React.useState(0); + const [recordsPerPage, setRecordsPerPage] = React.useState(0); + const [refreshTrigger, setRefreshTrigger] = React.useState(false); const [showModal, setShowModal] = React.useState(false); const [groupError, setGroupError] = React.useState([]); @@ -65,42 +63,35 @@ const ByUser: Application.Types.iByComponent = (props) => { const [pageStatus, setPageStatus] = React.useState('uninitiated'); + // fetch data paged and sorted for the table React.useEffect(() => { - if (status === 'error') - setPageStatus('error') - else if (status === 'loading' ) - setPageStatus('loading') - else - setPageStatus('idle'); - }, [status]) - - React.useEffect(() => { - if (searchStatus == 'uninitiated' || searchStatus == 'changed') - dispatch(SecurityGroupSlice.PagedSearch({ sortField, ascending, filter: search, page: currentPage})) - }, [searchStatus, sortField, ascending, search, currentPage]) - - const setFilters = React.useCallback((filters: Search.IFilter[]) => { - dispatch(SecurityGroupSlice.PagedSearch({ sortField, ascending, filter: filters, page: currentPage})) - }, [sortField, ascending, currentPage]) - - const setPage = React.useCallback((page) => { - dispatch(SecurityGroupSlice.PagedSearch({ filter: search, sortField: sortField ?? "ID", ascending: ascending, page: page - 1 })) - }, [sortField, ascending, search]) + setSearchStatus('loading'); + const handle = SecurityGroupController.PagedSearch(search, sortField, ascending, page); + handle.done((d) => { + setData(JSON.parse(d.Data as unknown as string)) + setTotalPages(d.NumberOfPages); + setTotalRecords(d.TotalRecords); + setRecordsPerPage(d.RecordsPerPage); + if (page >= d.NumberOfPages) + setPage(Math.max(d.NumberOfPages - 1, 0)); + setSearchStatus('idle') + }).fail(() => setSearchStatus('error')); + + return () => { if (handle != null && handle.abort != null) handle.abort() } + }, [sortField, ascending, search, page, refreshTrigger]) if (pageStatus === 'error') return
    ; - useBoundPaging(currentPage, totalPages, setPage) - return (
    - CollumnList={defaultSearchcols} SetFilter={setFilters} + CollumnList={defaultSearchcols} SetFilter={setSearch} Direction={'left'} defaultCollumn={{ label: 'Name', key: 'DisplayName', type: 'string', isPivotField: false }} Width={'50%'} Label={'Search'} - ShowLoading={searchStatus === 'loading'} ResultNote={searchStatus === 'error' ? 'Could not complete Search' : 'Found ' + totalRecords + ' User Group(s)'} + ShowLoading={searchStatus === 'loading'} ResultNote={searchStatus === 'error' ? 'Could not complete Search' : `Displaying User Group(s) ${totalRecords > 0 ? (recordsPerPage * page + 1) : 0} - ${recordsPerPage * page + data.length} out of ${totalRecords}`} StorageID="UsersGroupFilter" GetEnum={() => { return () => { } @@ -125,7 +116,12 @@ const ByUser: Application.Types.iByComponent = (props) => { SortKey={sortField} Ascending={ascending} OnSort={(d) => { - dispatch(SecurityGroupSlice.Sort({ SortField: d.colField, Ascending: d.ascending })); + if (d.colKey === sortField) + setAscending(a => !a); + else { + setAscending(true); + setSortField(d.colKey as keyof ISecurityGroup); + } }} OnClick={(d) => navigate(`${homePath}index.cshtml?name=Group&GroupID=${d.row.ID}`)} TableStyle={{ @@ -184,8 +180,8 @@ const ByUser: Application.Types.iByComponent = (props) => {
    setPage(p - 1)} Total={totalPages} />
    @@ -193,10 +189,10 @@ const ByUser: Application.Types.iByComponent = (props) => { { if (confirm) - dispatch(SecurityGroupSlice.DBAction({ - verb: 'POST', - record: { ...newGroup, Name: ((newGroup.Name?.length ?? 0) > 0? newGroup.Name : newGroup.DisplayName) } - })) + SecurityGroupController.DBAction( + 'POST', + { ...newGroup, Name: ((newGroup.Name?.length ?? 0) > 0 ? newGroup.Name : newGroup.DisplayName) } + ).then(() => setRefreshTrigger(val => !val)); setShowModal(false); }} ConfirmShowToolTip={groupError.length > 0} diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/GroupForm.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/GroupForm.tsx index 78f9c0ec17..a179fc4767 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/GroupForm.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/GroupForm.tsx @@ -23,10 +23,10 @@ import * as React from 'react'; import { Input, TextArea } from '@gpa-gemstone/react-forms' import * as _ from 'lodash'; -import { SecurityGroupSlice } from '../../Store/Store'; -import { useAppDispatch, useAppSelector } from '../../hooks'; import { ISecurityGroup } from '../Types'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; +import { GenericController } from '@gpa-gemstone/react-interactive'; +import { Application } from '@gpa-gemstone/application-typings'; interface IProps { Group: ISecurityGroup, @@ -36,18 +36,26 @@ interface IProps { } function GroupForm(props: IProps) { - const dispatch = useAppDispatch(); - const allGroups = useAppSelector(SecurityGroupSlice.Data); - const groupStatus = useAppSelector(SecurityGroupSlice.Status); + const [allGroups, setAllGroups] = React.useState([]); + const [groupStatus, setGroupStatus] = React.useState('uninitiated'); const [errors, setErrors] = React.useState([]); const [valid, setValid] = React.useState<'valid' | 'resolving' | 'invalid' | 'unknown'>("valid") React.useEffect(() => { - if (groupStatus === 'uninitiated' || groupStatus === 'changed') - dispatch(SecurityGroupSlice.Fetch()); - }, [groupStatus]); + setGroupStatus('loading') + const handle = new GenericController(`${homePath}api/SystemCenter/FullSecurityGroup`, "DisplayName").Fetch(); + handle.done((d) => { + setAllGroups(d); + setGroupStatus('idle') + }) + handle.fail(() => setGroupStatus('error')) + return () => { + if (handle != null && handle.abort != null) + handle.abort(); + } + }, []) React.useEffect(() => { if (props.SetErrors !== undefined) diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/GroupUsers.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/GroupUsers.tsx index 6441e92749..d892b1b67e 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/GroupUsers.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/GroupUsers.tsx @@ -23,16 +23,18 @@ import * as React from 'react'; import { Application } from '@gpa-gemstone/application-typings'; import * as _ from 'lodash'; -import { LoadingScreen } from '@gpa-gemstone/react-interactive'; -import { UserAccountSliceRemote } from '../../Store/Store'; +import { LoadingScreen, GenericController, SearchBar } from '@gpa-gemstone/react-interactive'; import { ISecurityGroup } from '../Types'; import { Table, Column, Paging } from '@gpa-gemstone/react-table'; -import { DefaultSelects } from '@gpa-gemstone/common-pages'; +import ControllerSelectPopup from '../../CommonComponents/ControllerSelectPopup' + +const RemoteUserAccountController = new GenericController(`${homePath}api/SystemCenter/RemoteUserAccount`, "Name", false); const GroupUser = (props: { Group: ISecurityGroup }) => { const [showSelect, setShowSelect] = React.useState(false); const [users, setUsers] = React.useState([]); + const [groupUsers, setGroupUsers] = React.useState([]); const [asc, setAsc] = React.useState(true); const [sortField, setSortField] = React.useState('AccountName'); @@ -44,7 +46,9 @@ const GroupUser = (props: { Group: ISecurityGroup }) => { const [refreshTrigger, setRefreshTrigger] = React.useState(false); const [status, setStatus] = React.useState('uninitiated'); + const [groupStatus, setGroupStatus] = React.useState('uninitiated'); + // fetch group's users, paged and sorted for the table React.useEffect(() => { if (props.Group.Type != 'Database') return; @@ -71,6 +75,27 @@ const GroupUser = (props: { Group: ISecurityGroup }) => { return () => { if (handle != null && handle.abort != null) handle.abort(); } }, [props.Group.ID, props.Group.Type, asc, sortField, page, refreshTrigger]) + // fetch all of group's users to use as the base 'selected' users in the selection popup. + React.useEffect(() => { + if (props.Group.Type != 'Database') + return; + + setGroupStatus('loading') + + const handle = $.ajax({ + type: "POST", + url: `${homePath}api/SystemCenter/FullSecurityGroup/Users/${props.Group.ID}`, + contentType: "application/json; charset=utf-8", + cache: false, + async: true + }).done((d) => { + setGroupUsers(JSON.parse(d.Data as unknown as string)); + setGroupStatus('idle'); + }).fail(() => setGroupStatus('error')); + + return () => { if (handle != null && handle.abort != null) handle.abort(); } + }, [props.Group.ID, props.Group.Type, refreshTrigger]) + function saveUser(u) { if (props.Group.Type != 'Database') return; @@ -204,10 +229,9 @@ const GroupUser = (props: { Group: ISecurityGroup }) => { disabled={props.Group.Type !== 'Database'}>Add Users
    - - + Controller={RemoteUserAccountController} + Selection={groupUsers} OnClose={(selected, conf) => { setShowSelect(false); if (!conf) return; @@ -216,8 +240,20 @@ const GroupUser = (props: { Group: ISecurityGroup }) => { Show={showSelect} Type={'multiple'} Title={"Add Users to " + props.Group.Name} - GetEnum={() => () => { }} - GetAddlFields={() => () => { }} + Searchbar={(children, setFilters) => + SetFilter={setFilters} + CollumnList={[ + { label: 'First Name', key: 'FirstName', type: 'string', isPivotField: false }, + { label: 'Last Name', key: 'LastName', type: 'string', isPivotField: false }, + { label: 'Email', key: 'Email', type: 'string', isPivotField: false } + ]} + Direction={'left'} + defaultCollumn={{ label: 'Username', key: 'Name', type: 'string', isPivotField: false }} + Width={'50%'} + Label={'Search'} + > + {children} + } > Username @@ -229,8 +265,7 @@ const GroupUser = (props: { Group: ISecurityGroup }) => { >Phone Email - - +
    ); diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/Info.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/Info.tsx index 85d0dec6f8..1ad9d2614a 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/Info.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/Info.tsx @@ -22,15 +22,13 @@ import * as React from 'react'; import * as _ from 'lodash'; +import { GenericController } from '@gpa-gemstone/react-interactive'; import { ToolTip } from '@gpa-gemstone/react-forms'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; -import { SecurityGroupSlice } from '../../Store/Store'; -import { useAppDispatch } from '../../hooks'; import { ISecurityGroup } from '../Types'; import GroupForm from './GroupForm'; -const GroupInfo = (props: {Group: ISecurityGroup}) => { - const dispatch = useAppDispatch(); +const GroupInfo = (props: { Group: ISecurityGroup }) => { const [group, setGroup] = React.useState(props.Group); const [warnings, setWarning] = React.useState([]); @@ -76,7 +74,7 @@ const GroupInfo = (props: {Group: ISecurityGroup}) => {
    diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/UserGroup.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/UserGroup.tsx index 461286c6fe..e544c488bb 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/UserGroup.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/UserGroup.tsx @@ -21,11 +21,10 @@ // ****************************************************************************************************** import * as React from 'react'; -import { LoadingScreen, ServerErrorIcon, TabSelector, Warning } from '@gpa-gemstone/react-interactive'; +import { LoadingScreen, ServerErrorIcon, TabSelector, Warning, GenericController } from '@gpa-gemstone/react-interactive'; import * as _ from 'lodash'; -import { useAppDispatch, useAppSelector } from '../../hooks'; +import { Application } from '@gpa-gemstone/application-typings'; import { useNavigate } from "react-router-dom"; -import { SecurityGroupSlice } from '../../Store/Store'; import { ISecurityGroup } from '../Types'; import GroupInfo from './Info'; import GroupUser from './GroupUsers'; @@ -37,9 +36,8 @@ interface IProps { GroupID: string, Tab: Tab } function UserGroup(props: IProps) { const navigate = useNavigate(); - const dispatch = useAppDispatch(); - const group: ISecurityGroup = useAppSelector((state) => SecurityGroupSlice.Datum(state, props.GroupID) as ISecurityGroup); - const status = useAppSelector(SecurityGroupSlice.Status); + const [group, setGroup] = React.useState(null) + const [status, setStatus] = React.useState('uninitiated'); const [tab, setTab] = React.useState(getTab()); const [showWarning, setShowWarning] = React.useState(false); @@ -51,17 +49,35 @@ function UserGroup(props: IProps) { return 'info'; } + React.useEffect(() => { + setStatus('loading'); + const handle = $.ajax({ + type: "GET", + url: `${homePath}api/SystemCenter/FullSecurityGroup/One/${props.GroupID}`, + contentType: "application/json; charset=utf-8", + dataType: 'json', + cache: true, + async: true + }) + handle.done((d) => { + setStatus('idle'); + setGroup(d); + }) + handle.fail(() => { + setStatus('error') + }) + return () => { + if (handle != null && handle.abort != null) + handle.abort() + } + }, [props.GroupID]) + React.useEffect(() => { const saved = getTab(); if (saved !== tab) sessionStorage.setItem('UserGroup.Tab', JSON.stringify(tab)); }, [tab]); - React.useEffect(() => { - if (status == 'uninitiated' || status == 'changed') - dispatch(SecurityGroupSlice.Fetch()); - }, [status]) - if (status === 'error') return
    @@ -94,8 +110,7 @@ function UserGroup(props: IProps) { { setShowWarning(false); if (c) { - dispatch(SecurityGroupSlice.DBAction({ verb: 'DELETE', record: group })); - navigate(`${homePath}index.cshtml?name=Groups`); + new GenericController(`${homePath}api/SystemCenter/FullSecurityGroup`, "DisplayName").DBAction('DELETE', group).then(() => navigate(`${homePath}index.cshtml?name=Groups`)); } }} />
    diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/hooks.ts b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/hooks.ts index c9570207a8..cd5de3f4f9 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/hooks.ts +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/hooks.ts @@ -21,9 +21,12 @@ // //****************************************************************************************************** +import * as React from 'react'; import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; +import { Application } from '@gpa-gemstone/application-typings'; +import { GenericController } from '@gpa-gemstone/react-interactive'; import { AppDispatch, RootState } from './Store/Store'; -import * as React from 'react'; + export const useAppDispatch: () => AppDispatch = useDispatch; export const useAppSelector: TypedUseSelectorHook = useSelector; @@ -38,4 +41,41 @@ export const useBoundPaging = (currentPage: number, totalPages: number, setPage: } }, [totalPages, currentPage]) -} \ No newline at end of file +} + +export function useControllerFetch(controller: GenericController, sortField?: keyof T, ascending?: boolean, parentID?: string|number, refreshCount?: number,) { + + const fetchHandle = React.useRef | null>(null); + const [fetchData, setFetchData] = React.useState([]); + const [fetchStatus, setFetchStatus] = React.useState('uninitiated'); + const sortKey = sortField ?? controller.DefaultSort; + const asc = ascending ?? controller.Ascending; + + const refetchData = React.useCallback(() => { + setFetchStatus('loading'); + + fetchHandle.current = controller.Fetch(parentID, sortKey, asc) + .done((data: T[]) => { + setFetchData(data); + setFetchStatus('idle'); + }) + .fail(() => setFetchStatus('error')) + const cleanup = () => { + if (fetchHandle.current?.abort != null) fetchHandle.current.abort(); + } + return { + fetchHandle: fetchHandle.current, + cleanup + } + }, [controller, parentID, sortKey, asc]) + + React.useEffect(() => { + const { cleanup } = refetchData(); + return cleanup; + }, [refetchData, refreshCount]) + + return { + FetchData: fetchData, + FetchStatus: fetchStatus + } +} \ No newline at end of file