diff --git a/Source/Applications/SystemCenterNotification/NotificationPages.csproj b/Source/Applications/SystemCenterNotification/NotificationPages.csproj index 446e48d5dd..db7a15f3f1 100644 --- a/Source/Applications/SystemCenterNotification/NotificationPages.csproj +++ b/Source/Applications/SystemCenterNotification/NotificationPages.csproj @@ -366,6 +366,7 @@ + diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/CellCarrier/ByCellCarrier.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/CellCarrier/ByCellCarrier.tsx index 039d8c115c..27c05ec887 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/CellCarrier/ByCellCarrier.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/CellCarrier/ByCellCarrier.tsx @@ -22,16 +22,12 @@ //****************************************************************************************************** import * as React from 'react'; -import { LoadingScreen, Modal, Search, SearchBar } from '@gpa-gemstone/react-interactive' +import { LoadingScreen, Modal, Search, SearchBar, GenericController } from '@gpa-gemstone/react-interactive' import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; import { Application } from '@gpa-gemstone/application-typings'; import { Table, Column, Paging } from '@gpa-gemstone/react-table'; -import moment from 'moment'; import { ICellCarrier } from '../global'; -import { CellCarrierSlice } from '../Store'; -import { useAppDispatch, useAppSelector } from '../hooks'; import { Input } from '@gpa-gemstone/react-forms'; -import { castArray } from 'lodash'; declare var homePath; @@ -46,30 +42,54 @@ const searchFields: Search.IField[] = [ interface IProps {} const ByCellCarrier = (props: IProps) => { - const dispatch = useAppDispatch(); - - const searchStatus: Application.Types.Status = useAppSelector(CellCarrierSlice.SearchStatus); - const status: Application.Types.Status = useAppSelector(CellCarrierSlice.Status); - const data: ICellCarrier[] = useAppSelector(CellCarrierSlice.SearchResults); - const allData: ICellCarrier[] = useAppSelector(CellCarrierSlice.Data); + const [searchStatus, setSearchStatus] = React.useState('uninitiated'); + const [fetchStatus, setFetchStatus] = React.useState('uninitiated'); + const [status, setStatus] = React.useState('uninitiated'); + const [data, setData] = React.useState([]); + const [allData, setAllData] = React.useState([]); const [sortField, setSortField] = React.useState('Name'); const [ascending, setAscending] = React.useState(true); const [filters, setFilters] = React.useState[]>([]); const [showModal, setShowModal] = React.useState<'New' | 'Edit' | 'Hide'>('Hide'); const [carrier, setCarrier] = React.useState(emptyCarrier); const [page, setPage] = React.useState(0); - const totalPages = useAppSelector(CellCarrierSlice.TotalPages) - const recordsPerPage = useAppSelector(CellCarrierSlice.RecordsPerPage); - const totalRecords = useAppSelector(CellCarrierSlice.TotalRecords); + const [totalPages, setTotalPages] = React.useState(0); + const [recordsPerPage, setRecordsPerPage] = React.useState(0); + const [totalRecords, setTotalRecords] = React.useState(0); const [refreshTrigger, setRefreshTrigger] = React.useState(false); + const cellCarrierController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/CellCarrier`, "Name", true),[]) + React.useEffect(() => { - dispatch(CellCarrierSlice.PagedSearch({ filter: filters, sortField, ascending: ascending, page: page })); - }, [page, filters, sortField, ascending, refreshTrigger]) + setStatus('uninitiated') + const h = cellCarrierController.PagedSearch(filters, sortField, ascending, page); + h.done((d) => { + setData(JSON.parse(d.Data as unknown as string)); + setTotalPages(d.NumberOfPages); + setTotalRecords(d.TotalRecords); + setRecordsPerPage(d.RecordsPerPage); + setStatus('idle'); + }) + h.fail(() => setStatus('error')) + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + }, [page, filters, sortField, ascending, refreshTrigger, cellCarrierController.PagedSearch]) React.useEffect(() => { - dispatch(CellCarrierSlice.Fetch()); - }, [refreshTrigger]) + setFetchStatus('uninitiated') + const h = cellCarrierController.Fetch(); + h.done((d) => { + setAllData(d) + setFetchStatus('idle') + }) + h.fail(() => setStatus('error')) + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + }, [refreshTrigger, cellCarrierController.Fetch]) return (
@@ -161,11 +181,11 @@ const ByCellCarrier = (props: IProps) => { } ConfirmBtnClass={'btn-primary'} CallBack={(c, b) => { if (showModal == 'New' && c) - dispatch(CellCarrierSlice.DBAction({ verb: 'POST', record: carrier })).then(() => setRefreshTrigger((val) => !val)); + cellCarrierController.DBAction('POST', carrier).then(() => setRefreshTrigger((val) => !val)); if (showModal == 'Edit' && c) - dispatch(CellCarrierSlice.DBAction({ verb: 'PATCH', record: carrier })).then(() => setRefreshTrigger((val) => !val)); + cellCarrierController.DBAction('PATCH', carrier).then(() => setRefreshTrigger((val) => !val)); if (showModal == 'Edit' && b && !c) - dispatch(CellCarrierSlice.DBAction({ verb: 'DELETE', record: carrier })).then(() => setRefreshTrigger((val) => !val)); + cellCarrierController.DBAction('DELETE', carrier).then(() => setRefreshTrigger((val) => !val)); setShowModal('Hide'); }} diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/CommonComponents/ControllerSelects.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/CommonComponents/ControllerSelects.tsx new file mode 100644 index 0000000000..c23d4a4aed --- /dev/null +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/CommonComponents/ControllerSelects.tsx @@ -0,0 +1,208 @@ +//****************************************************************************************************** +// ControllerSelects.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: +// ---------------------------------------------------------------------------------------------------- +// 07/15/2026 - N. Beatty +// Generated original version of source code. +// +//****************************************************************************************************** + +import _ from "lodash"; +import * as React from 'react'; +import { Application } from '@gpa-gemstone/application-typings'; +import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; +import { GenericController, Modal, Search } from '@gpa-gemstone/react-interactive'; +import { Column, ConfigurableColumn, FilterableColumn, Paging, Table } from '@gpa-gemstone/react-table'; + +interface IProps { + Controller: GenericController, + Show: boolean, + Title: string, + Selection: T[], + PrimaryKey: keyof T, + OnClose: (selected: T[], conf: boolean) => void, + Searchbar: (children: React.ReactNode, SetFilter: (filters: Search.IFilter[]) => void, SearchStatus: Application.Types.Status, ResultCount: number) => React.ReactNode, + MinSelection: number, + Type?: 'single' | 'multiple', + DefaultSortField: keyof T, + children?: React.ReactNode +} + +export const ControllerSelects = (props: IProps) => { + const [data, setData] = React.useState([]); + const [filters, setFilters] = React.useState[]>([]); + const [sortField, setSortField] = React.useState(props.DefaultSortField); + const [ascending, setAscending] = React.useState(true); + const [activePage, setActivePage] = React.useState(0); + const [totalPages, setTotalPages] = React.useState(0); + const [totalRecords, setTotalRecords] = React.useState(0); + const [searchStatus, setSearchStatus] = React.useState('uninitiated'); + + const [selectedData, setSelectedData] = React.useState(props.Selection); + const [sortKeySelected, setSortKeySelected] = React.useState(''); + const [ascendingSelected, setAscendingSelected] = React.useState(false); + + //keep the active page valid when the filtered result set shrinks + React.useEffect(() => { + if (totalPages > 0 && activePage >= totalPages) + setActivePage(totalPages - 1); + }, [totalPages, activePage]); + + React.useEffect(() => { + setSelectedData(props.Selection); + }, [props.Selection]) + + // when controller is changed, use default sort field. + React.useEffect(() => { + setSortField(props.DefaultSortField); + },[props.Controller, props.DefaultSortField]) + + React.useEffect(() => { + setSearchStatus('loading') + const h = props.Controller.PagedSearch(filters, sortField, ascending, activePage); + h.done((d) => { + setData(JSON.parse(d.Data)); + setTotalPages(d.NumberOfPages); + setTotalRecords(d.TotalRecords); + setSearchStatus('idle') + }) + h.fail(() => setSearchStatus('error')) + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + }, [props.Controller, filters, sortField, ascending, activePage]) + + return ( + props.OnClose(selectedData, conf)} + ShowX={true} + Size={'xlg'} + BodyStyle={{ display: 'flex', flexDirection: 'column', height: 'calc(100vh - 210px)', overflow: 'hidden' }} + 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( + <> + {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, searchStatus, totalRecords)} +
+ {props.Type === 'multiple' ?
+

Current Selection

+
: null} +
+
+
+ + TableClass="table table-hover h-100" + Data={data} + SortKey={sortField as string} + Ascending={ascending} + OnSort={(d) => { + if (d.colKey === "Scroll") + return; + if (d.colKey === sortField) + setAscending(!ascending); + else { + setSortField(d.colField as keyof T); + setAscending(true); + } + }} + OnClick={(d, e) => { + //the table fires OnClick for both the clicked cell and the bubbled row, so stop + //propagation to avoid a double toggle that would immediately deselect the row + e.stopPropagation(); + setSelectedData((s) => { + const isSelected = s.findIndex(item => item[props.PrimaryKey] === d.row[props.PrimaryKey]) > -1; + + if (isSelected) + return s.filter(item => item[props.PrimaryKey] !== d.row[props.PrimaryKey]); + + if (props.Type === undefined || props.Type === 'single') + return [d.row] + + return [...s, d.row] + }) + }} + Selected={(item) => selectedData.findIndex(d => d[props.PrimaryKey] === item[props.PrimaryKey]) > -1} + KeySelector={item => item[props.PrimaryKey] as string | number} + > + {props.children} + +
+ {props.Type === 'multiple' ?
+ + TableClass="table table-hover h-100" + 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[props.PrimaryKey] !== d.row[props.PrimaryKey])])} + Selected={() => false} + KeySelector={item => item[props.PrimaryKey] as string | number} + > + {props.children} + +
: null} +
+
+
+ setActivePage(p - 1)} + /> +
+ {props.Type === 'multiple' ? +
+ : null + } +
+ + ) +} \ No newline at end of file diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailCategory/ByEmailCategory.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailCategory/ByEmailCategory.tsx index 8aa58fdc08..7b37f3b4f3 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailCategory/ByEmailCategory.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailCategory/ByEmailCategory.tsx @@ -21,13 +21,11 @@ // //****************************************************************************************************** -import { useAppDispatch, useAppSelector } from '../hooks'; import * as React from 'react'; -import { LoadingScreen, Modal, Search, SearchBar } from '@gpa-gemstone/react-interactive' +import { LoadingScreen, Modal, Search, SearchBar, GenericController } from '@gpa-gemstone/react-interactive' import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; import { Application } from '@gpa-gemstone/application-typings'; import { EmailCategory } from '../global'; -import { EmailCategorySlice } from '../Store'; import { Table, Column, Paging } from '@gpa-gemstone/react-table'; import EmailCategoryForm from './EmailCategoryForm'; import { useNavigate } from 'react-router-dom'; @@ -37,31 +35,55 @@ interface IProps { } const ByEmailCategory = (props: IProps) => { const navigate = useNavigate(); - const dispatch = useAppDispatch(); const [search, setSearch] = React.useState[]>([]); - const status: Application.Types.Status = useAppSelector(EmailCategorySlice.Status); - const searchStatus: Application.Types.Status = useAppSelector(EmailCategorySlice.SearchStatus); - const data: EmailCategory[] = useAppSelector(EmailCategorySlice.SearchResults); - const allData: EmailCategory[] = useAppSelector(EmailCategorySlice.Data); + const [status, setStatus] = React.useState('uninitiated'); + const [searchStatus, setSearchStatus] = React.useState('uninitiated'); + const [data, setData] = React.useState([]); + const [allData, setAllData] = React.useState([]); const [showModal, setShowModal] = React.useState(false); const [errors, setErrors] = React.useState([]); const [newEmailCategory, setNewEmailCategory] = React.useState({ ID: -1, Name: '', SelfSubscribe: true }); const [sortField, setSortField] = React.useState('Name'); const [asc, setAsc] = React.useState(true); const [page, setPage] = React.useState(0); - const totalPages: number = useAppSelector(EmailCategorySlice.TotalPages); - const totalRecords: number = useAppSelector(EmailCategorySlice.TotalRecords); - const recordsPerPage: number = useAppSelector(EmailCategorySlice.RecordsPerPage); + const [totalPages, setTotalPages] = React.useState(0); + const [recordsPerPage, setRecordsPerPage] = React.useState(0); + const [totalRecords, setTotalRecords] = React.useState(0); const [refreshTrigger, setRefreshTrigger] = React.useState(false) + const emailCategoryController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/EmailCategory`, "Name", true), []); + React.useEffect(() => { - dispatch(EmailCategorySlice.Fetch()); - }, [refreshTrigger]); + setStatus('uninitiated') + const h = emailCategoryController.Fetch(); + h.done((d) => { + setAllData(d) + setStatus('idle') + }) + h.fail(() => setStatus('error')) + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + }, [refreshTrigger, emailCategoryController.Fetch]); React.useEffect(() => { - dispatch(EmailCategorySlice.PagedSearch({ filter: search, sortField, ascending: asc, page: page })); - }, [search, sortField, asc, page, refreshTrigger]) + setStatus('uninitiated') + const h = emailCategoryController.PagedSearch(search, sortField, asc, page); + h.done((d) => { + setData(JSON.parse(d.Data as unknown as string)); + setTotalPages(d.NumberOfPages); + setTotalRecords(d.TotalRecords); + setRecordsPerPage(d.RecordsPerPage); + setStatus('idle'); + }) + h.fail(() => setStatus('error')) + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + }, [search, sortField, asc, page, refreshTrigger, emailCategoryController.PagedSearch]) React.useEffect(() => { let e = []; @@ -159,7 +181,7 @@ const ByEmailCategory = (props: IProps) => { Show={showModal} ShowX={true} Size={'lg'} ShowCancel={false} ConfirmText={'Add'} CallBack={(conf) => { if (conf) - dispatch(EmailCategorySlice.DBAction({ verb: "POST", record: newEmailCategory })).then(()=>setRefreshTrigger((val) => !val)); + emailCategoryController.DBAction("POST",newEmailCategory).then(()=>setRefreshTrigger((val) => !val)); setShowModal(false); }} DisableConfirm={errors.length > 0} diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailCategory/EmailCategoryForm.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailCategory/EmailCategoryForm.tsx index 32524c7936..08169808be 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailCategory/EmailCategoryForm.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailCategory/EmailCategoryForm.tsx @@ -21,24 +21,36 @@ // //****************************************************************************************************** -import { useAppDispatch, useAppSelector } from '../hooks'; import * as React from 'react'; import { EmailCategory } from '../global'; -import { EmailCategorySlice } from '../Store'; import { CheckBox, Input } from '@gpa-gemstone/react-forms'; +import { GenericController } from '@gpa-gemstone/react-interactive'; +import { Application } from '@gpa-gemstone/application-typings'; interface IProps { record: EmailCategory, setRecord: (d: EmailCategory) => void } const EmailCategoryForm = (props: IProps) => { - const dispatch = useAppDispatch(); - const emailCategories = useAppSelector(EmailCategorySlice.Data); - const status = useAppSelector(EmailCategorySlice.Status); + const [emailCategories, setEmailCategories] = React.useState([]); + const [status, setStatus] = React.useState('uninitiated'); + + const emailCategoryController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/EmailCategory`, "Name", true), []) React.useEffect(() => { - if (status == 'uninitiated' || status == 'changed') - dispatch(EmailCategorySlice.Fetch()); - }, [status]); + setStatus('loading') + const h = emailCategoryController.Fetch(); + h.done((d) => { + setEmailCategories(d) + setStatus('idle') + }); + h.fail(() => setStatus('error')); + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + + }, [emailCategoryController.Fetch]); return (
diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailCategory/EmailCategoryPage.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailCategory/EmailCategoryPage.tsx index ae2f3674e7..ec48a71ff8 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailCategory/EmailCategoryPage.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailCategory/EmailCategoryPage.tsx @@ -21,12 +21,12 @@ // //****************************************************************************************************** -import { useAppDispatch, useAppSelector } from '../hooks'; +import * as $ from 'jquery' import * as React from 'react'; -import { LoadingScreen, TabSelector, Warning } from '@gpa-gemstone/react-interactive'; +import { LoadingScreen, TabSelector, Warning, GenericController } from '@gpa-gemstone/react-interactive'; import { Application } from '@gpa-gemstone/application-typings'; -import { EmailCategorySlice } from '../Store'; import EmailCategoryWindow from './EmailCategoryWindow'; +import { EmailCategory } from '../global'; import EmailList from './EmailList'; interface IProps { useParams: { id: string } } @@ -39,12 +39,11 @@ const Tabs = [ const EmailCategoryPage = (props: IProps) => { - const dispatch = useAppDispatch(); - + const emailCategoryController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/EmailCategory`, "Name", true), []); const [showDelete, setShowDelete] = React.useState(false); - const category = useAppSelector((state) => EmailCategorySlice.Datum(state, parseInt(props.useParams.id))); - const status: Application.Types.Status = useAppSelector(EmailCategorySlice.Status); + const [category, setCategory] = React.useState(null); + const [status, setStatus] = React.useState('uninitiated'); const getTab = React.useCallback(() => { if (sessionStorage.hasOwnProperty('EmailCategory.Tab')) @@ -54,17 +53,35 @@ const EmailCategoryPage = (props: IProps) => { const [tab, setTab] = React.useState(getTab()); - React.useEffect(() => { - if (status == 'uninitiated' || status == 'changed') - dispatch(EmailCategorySlice.Fetch()); - }, [status]); - React.useEffect(() => { const saved = getTab(); if (saved !== tab) sessionStorage.setItem('EmailCategory.Tab', JSON.stringify(tab)); }, [tab]); + React.useEffect(() => { + setStatus('loading') + const h = $.ajax({ + type: "GET", + url: `${homePath}api/OpenXDA/EmailCategory/One/${props.useParams.id}`, + contentType: "application/json; charset=utf-8", + dataType: 'json', + cache: false, + async: true + }) + h.done((d) => { + setCategory(d); + setStatus('idle') + }) + h.fail(() => { + setStatus('error') + }) + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + }, [props.useParams.id]) + return (
@@ -104,7 +121,7 @@ const EmailCategoryPage = (props: IProps) => { Title={`Delete ${category?.Name ?? 'Email Category'}`} CallBack={(conf) => { if (conf) { - dispatch(EmailCategorySlice.DBAction({ verb: 'DELETE', record: category })); + emailCategoryController.DBAction('DELETE',category); window.location.href = `${homePath}/Categories`; } setShowDelete(false); diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailCategory/EmailCategoryWindow.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailCategory/EmailCategoryWindow.tsx index 37d69c081c..7aefc7ae61 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailCategory/EmailCategoryWindow.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailCategory/EmailCategoryWindow.tsx @@ -21,29 +21,47 @@ // //****************************************************************************************************** -import { useAppDispatch, useAppSelector } from '../hooks'; import * as React from 'react'; -import { Warning } from '@gpa-gemstone/react-interactive'; +import { Warning, GenericController } from '@gpa-gemstone/react-interactive'; import { ToolTip } from '@gpa-gemstone/react-forms'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; import { EmailCategory } from '../global'; -import { EmailCategorySlice } from '../Store'; import EmailCategoryForm from './EmailCategoryForm'; +import { Application } from '@gpa-gemstone/application-typings'; interface IProps { Category: EmailCategory } const EmailCategoryWindow = (props: IProps) => { - const dispatch = useAppDispatch(); const [category, setCategory] = React.useState(props.Category); const [hasChanged, setHasChanged] = React.useState(false); const [hover, setHover] = React.useState<('submit' | 'clear' | 'none')>('none'); - const categories = useAppSelector(EmailCategorySlice.Data); + const [categories, setCategories] = React.useState([]) + const [status, setStatus] = React.useState('uninitiated'); const [errors, setErrors] = React.useState([]); + + const emailCategoryController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/EmailCategory`, "Name", true), []) + + React.useEffect(() => { + setStatus('loading') + const h = emailCategoryController.Fetch(); + h.done((d) => { + setCategories(d) + setStatus('idle') + }); + h.fail(() => setStatus('error')); + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + + }, [emailCategoryController.Fetch]); + React.useEffect(() => { let e = []; if (category.Name == undefined || category.Name.length < 1) @@ -78,7 +96,7 @@ const EmailCategoryWindow = (props: IProps) => {
diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailCategory/EmailList.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailCategory/EmailList.tsx index e1273962bc..099642c99a 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailCategory/EmailList.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailCategory/EmailList.tsx @@ -21,34 +21,47 @@ // //****************************************************************************************************** -import { useAppDispatch, useAppSelector } from '../hooks'; import * as React from 'react'; +import { Application } from '@gpa-gemstone/application-typings' +import { GenericController, Search } from '@gpa-gemstone/react-interactive' +import { Column, Paging, Table } from '@gpa-gemstone/react-table'; import { EmailType } from '../global'; -import { EmailTypeSlice } from '../Store'; -import { Table, Column, Paging } from '@gpa-gemstone/react-table'; -import { Search } from '@gpa-gemstone/react-interactive' interface IProps { CategoryID: number} const EmailList = (props: IProps) => { - const dispatch = useAppDispatch(); - const emails = useAppSelector(EmailTypeSlice.SearchResults); - const status = useAppSelector(EmailTypeSlice.SearchStatus); - - const parentID = useAppSelector(EmailTypeSlice.ParentID); + const [emails, setEmails] = React.useState([]); + const [status, setStatus] = React.useState('uninitiated'); const [sortField, setSortField] = React.useState('Name'); const [ascending, setAscending] = React.useState(false); const [page, setPage] = React.useState(0); - const totalPages = useAppSelector(EmailTypeSlice.TotalPages); + const [totalPages, setTotalPages] = React.useState(0); + + const emailTypeController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/EmailType`, "Name", true), []) React.useEffect(() => { - const filters: Search.IFilter[] = [{ FieldName: "EmailCategoryID", Type: 'number', Operator: '=', SearchText: props.CategoryID.toString(), IsPivotColumn: false}] - dispatch(EmailTypeSlice.PagedSearch({ filter: filters, sortField: sortField, ascending: ascending, page: page })); - }, [parentID, props.CategoryID, sortField, ascending, page]) + setStatus('loading') + + const filters: Search.IFilter[] = [{ FieldName: "EmailCategoryID", Type: 'number', Operator: '=', SearchText: props.CategoryID.toString(), IsPivotColumn: false }]; + const h = emailTypeController.PagedSearch(filters, sortField, ascending, page); + + h.done((d) => { + setEmails(JSON.parse(d.Data)); + setTotalPages(d.NumberOfPages); + setStatus('idle') + }) + h.fail(() => setStatus('error')) + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + + }, [props.CategoryID, sortField, ascending, page, emailTypeController.PagedSearch]) return (
diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/ByEmailType.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/ByEmailType.tsx index 438f015615..e7c3884026 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/ByEmailType.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/ByEmailType.tsx @@ -21,18 +21,16 @@ // //****************************************************************************************************** -import { useAppDispatch, useAppSelector } from '../hooks'; import * as React from 'react'; -import { LoadingScreen, Modal, Search, SearchBar } from '@gpa-gemstone/react-interactive' -import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; +import { useNavigate } from 'react-router-dom'; import { Application } from '@gpa-gemstone/application-typings'; -import { EmailType } from '../global'; -import { EmailCategorySlice, EmailTypeSlice } from '../Store'; -import { Table, Column, Paging } from '@gpa-gemstone/react-table'; -import EmailForm from './EmailForm'; import { IsNumber } from '@gpa-gemstone/helper-functions'; -import { useNavigate } from 'react-router-dom'; +import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; +import { LoadingScreen, Modal, Search, SearchBar, GenericController } from '@gpa-gemstone/react-interactive' +import { Table, Column, Paging } from '@gpa-gemstone/react-table'; import TestEmailButton from '../CommonComponents/TestEmailButton'; +import { EmailType, EmailCategory } from '../global'; +import EmailForm from './EmailForm'; declare var homePath; declare var version; @@ -55,15 +53,14 @@ const emptyEmail = { const ByEmailType = (props: IProps) => { const navigate = useNavigate(); - const dispatch = useAppDispatch(); const [search, setSearch] = React.useState[]>([]); - const status: Application.Types.Status = useAppSelector(EmailTypeSlice.Status); - const searchStatus: Application.Types.Status = useAppSelector(EmailTypeSlice.SearchStatus); - const data: EmailType[] = useAppSelector(EmailTypeSlice.SearchResults); - const allData: EmailType[] = useAppSelector(EmailTypeSlice.Data); - const categories = useAppSelector(EmailCategorySlice.Data); - const parentID = useAppSelector(EmailTypeSlice.ParentID); + const [status, setStatus] = React.useState('uninitiated'); + const [searchStatus, setSearchStatus] = React.useState('uninitiated'); + const [data, setData] = React.useState([]); + const [allData, setAllData] = React.useState([]); + const [categoryStatus, setCategoryStatus] = React.useState('uninitiated'); + const [categories, setCategories] = React.useState([]); const [showModal, setShowModal] = React.useState(false); const [errors, setErrors] = React.useState([]); @@ -74,22 +71,66 @@ const ByEmailType = (props: IProps) => { const [asc, setAsc] = React.useState(true); const [page, setPage] = React.useState(0); - const totalPages = useAppSelector(EmailTypeSlice.TotalPages); + const [totalPages, setTotalPages] = React.useState(0); + const [recordsPerPage, setRecordsPerPage] = React.useState(0); + const [totalRecords, setTotalRecords] = React.useState(0); const [refreshTrigger, setRefreshTrigger] = React.useState(false) + const emailCategoryController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/EmailCategory`, "Name", true), []) + const emailTypeController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/EmailType`, "Name", true), []) React.useEffect(() => { - dispatch(EmailCategorySlice.Fetch()); - }, [refreshTrigger]); + setCategoryStatus('loading') + const h = emailCategoryController.Fetch(); + h.done((d) => { + setCategories(d) + setCategoryStatus('idle') + }); + h.fail(() => setCategoryStatus('error')); + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + + }, [emailCategoryController.Fetch]); React.useEffect(() => { - dispatch(EmailTypeSlice.Fetch()); - }, [refreshTrigger, parentID]); + setStatus('loading') + const h = emailTypeController.Fetch(); + h.done((d) => { + setAllData(d) + setStatus('idle') + }); + h.fail(() => setStatus('error')); + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + + }, [emailTypeController.Fetch, refreshTrigger]); React.useEffect(() => { - dispatch(EmailTypeSlice.PagedSearch({ filter: search, sortField: sortField, ascending: asc, page: page})); - }, [search, sortField, asc, page, refreshTrigger]) + setSearchStatus('loading') + const h = emailTypeController.PagedSearch(search, sortField, asc, page); + + h.done((d) => { + setData(JSON.parse(d.Data)); + setTotalPages(d.NumberOfPages); + setTotalRecords(d.TotalRecords); + setRecordsPerPage(d.RecordsPerPage); + setSearchStatus('idle') + }) + h.fail(() => setSearchStatus('error')) + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + + }, [search, sortField, asc, page, emailTypeController.PagedSearch, refreshTrigger]) React.useEffect(() => { let e = []; @@ -137,7 +178,7 @@ const ByEmailType = (props: IProps) => { CollumnList={searchFields} SetFilter={setSearch} Direction={'left'} defaultCollumn={{ key: 'Name', label: 'Name', type: 'string', isPivotField: false }} Width={'50%'} Label={'Search'} - ShowLoading={searchStatus === 'loading'} ResultNote={searchStatus === 'error' ? 'Could not complete Search' : 'Found ' + data.length + ' Event Notification(s)'} + ShowLoading={searchStatus === 'loading'} ResultNote={searchStatus === 'error' ? 'Could not complete Search' : `Displaying Event Notification(s) ${totalRecords > 0 ? recordsPerPage * page + 1 : 0}-${recordsPerPage * page + data.length} out of ${totalRecords}`} >
  • @@ -259,7 +300,7 @@ const ByEmailType = (props: IProps) => { Show={showModal} ShowX={true} Size={'lg'} ShowCancel={false} ConfirmText={'Add'} CallBack={(conf, isBtn) => { if (conf) - dispatch(EmailTypeSlice.DBAction({ verb: "POST", record: newEmail })).then(() => setRefreshTrigger((val) => !val)) + emailTypeController.DBAction("POST", newEmail).then(() => setRefreshTrigger((val) => !val)) setShowModal(false); }} DisableConfirm={errors.length > 0} diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/DatasourceUI/DataSourceModal.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/DatasourceUI/DataSourceModal.tsx index 5305f0b32c..8639133740 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/DatasourceUI/DataSourceModal.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/DatasourceUI/DataSourceModal.tsx @@ -21,17 +21,16 @@ // //****************************************************************************************************** -import { useAppDispatch, useAppSelector } from '../../hooks'; +import { cloneDeep } from 'lodash'; import * as React from 'react'; -import { Modal } from '@gpa-gemstone/react-interactive' +import { Application } from '@gpa-gemstone/application-typings'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; -import { IDataSourceTriggeredEmailType, IEvent, ITriggeredEmailDataSourceSetting } from '../../global'; -import { TriggeredDataSourceSettingSlice, TriggeredDataSourceSlice, TriggeredEmailDataSourceSlice } from '../../Store'; import { Select } from '@gpa-gemstone/react-forms'; -import SQLDataSource from './SQLDataSource'; -import { cloneDeep } from 'lodash' -import PQIDataSource from './PQIDataSource'; +import { GenericController, Modal } from '@gpa-gemstone/react-interactive' +import { IDataSourceTriggeredEmailType, ITriggeredDataSource, ITriggeredEmailDataSourceSetting } from '../../global'; import FTTDataSource from './FTTDataSource'; +import PQIDataSource from './PQIDataSource'; +import SQLDataSource from './SQLDataSource'; declare var homePath; declare var version; @@ -45,20 +44,21 @@ interface IProps { const AllDataSources: DataSourceSettingUI[] = [SQLDataSource, PQIDataSource, FTTDataSource]; const DataSourceModal = (props: IProps) => { - const dispatch = useAppDispatch(); - const typeStatus = useAppSelector(TriggeredDataSourceSlice.Status); - const types = useAppSelector(TriggeredDataSourceSlice.Data); + const [typeStatus, setTypeStatus] = React.useState('uninitiated'); + const [types, setTypes] = React.useState([]); const [record, setRecord] = React.useState(); const dataSourceID = React.useMemo(() => record != null ? record.TriggeredEmailDataSourceID : null, [record]); - const originalsettings = useAppSelector(TriggeredDataSourceSettingSlice.Data); - const settingStatus = useAppSelector(TriggeredDataSourceSettingSlice.Status); - const settingsParentID = useAppSelector(TriggeredDataSourceSettingSlice.ParentID); + const [originalsettings, setOriginalSettings] = React.useState([]); + const [settingStatus, setSettingStatus] = React.useState('uninitiated'); const [currentSettings, setCurrentSettings] = React.useState([]); const [errors, setErrors] = React.useState([]); const [changes, setChanges] = React.useState([]); + const triggeredEmailDataSourceController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/TriggeredEmailDataSourceEmailType`, "TriggeredEmailDataSourceName", false), []) + const triggeredDataSourceSettingController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/TriggeredEmailDataSourceSetting`, "Name", false), []) + const dataSourceUI = React.useMemo(() => { const type = types.find(item => item.ID == dataSourceID); const dataSourceName = type != null ? type.ConfigUI : null; @@ -75,16 +75,41 @@ const DataSourceModal = (props: IProps) => { }, [originalsettings, dataSourceUI]); React.useEffect(() => { - if (typeStatus == 'uninitiated' || typeStatus == 'changed') - dispatch(TriggeredDataSourceSlice.Fetch()); - }, [typeStatus]); + const triggeredDataSourceController = new GenericController(`${homePath}api/OpenXDA/TriggeredEmailDataSource`, "Name", false); + setTypeStatus('uninitiated'); + + const h = triggeredDataSourceController.Fetch(); + h.done((d: ITriggeredDataSource[]) => { + setTypes(d); + setTypeStatus('idle'); + }) + h.fail(() => setTypeStatus('error')) + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + }, []); React.useEffect(() => { if (props.Record == null) return - if (settingStatus == 'uninitiated' || settingStatus == 'changed' || settingsParentID != props.Record.ID) - dispatch(TriggeredDataSourceSettingSlice.Fetch(props.Record.ID)); - }, [settingStatus, props.Record]); + + setSettingStatus('loading'); + const h = triggeredDataSourceSettingController.Fetch(props.Record.ID); + + h.done((d) => { + setOriginalSettings(d); + setSettingStatus('idle'); + }) + + h.fail(() => setSettingStatus('error')); + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + }, [props.Record, triggeredDataSourceSettingController.Fetch]); React.useEffect(() => { if (props.Record == null) @@ -143,11 +168,13 @@ const DataSourceModal = (props: IProps) => { { if (c && record.ID < 0) { - dispatch(TriggeredEmailDataSourceSlice.DBAction({ verb: 'POST', record: { ...record, Settings: currentSettings.filter(s => s.Value != null) } })) + triggeredEmailDataSourceController.DBAction('POST', { ...record, Settings: currentSettings.filter(s => s.Value != null) }) } if (c && record.ID >= 0) { - dispatch(TriggeredEmailDataSourceSlice.DBAction({ verb: 'PATCH', record })) - currentSettings.forEach((s) => { if (s.Value != null) dispatch(TriggeredDataSourceSettingSlice.DBAction({ verb: s.ID == 0 ? 'POST' : 'PATCH', record: s }));}); + triggeredEmailDataSourceController.DBAction('PATCH', record ) + currentSettings.forEach((s) => { + if (s.Value != null) triggeredDataSourceSettingController.DBAction(s.ID == 0 ? 'POST' : 'PATCH', s); + }); } if (!c) resetCurrentSettings(); diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/DatasourceUI/DataSourceWindow.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/DatasourceUI/DataSourceWindow.tsx index 96536e4e88..dfac90d30b 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/DatasourceUI/DataSourceWindow.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/DatasourceUI/DataSourceWindow.tsx @@ -23,13 +23,13 @@ import { useAppDispatch, useAppSelector } from '../../hooks'; import * as React from 'react'; +import { Application } from '@gpa-gemstone/application-typings'; import { EmailType, IDataSourceTriggeredEmailType } from '../../global'; -import { TriggeredEmailDataSourceSlice } from '../../Store'; import DataSourceModal from './DataSourceModal'; import { Table, Column, Paging } from '@gpa-gemstone/react-table'; import DataSourceTesting from './DataSourceTesting'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; -import { Warning, Search } from '@gpa-gemstone/react-interactive'; +import { GenericController, Search, Warning } from '@gpa-gemstone/react-interactive'; declare var homePath; @@ -40,21 +40,35 @@ interface IProps { Record: EmailType} const DataSourceWindow = (props: IProps) => { const dispatch = useAppDispatch(); - const status = useAppSelector(TriggeredEmailDataSourceSlice.Status); - const data = useAppSelector(TriggeredEmailDataSourceSlice.SearchResults); - const emailID = useAppSelector(TriggeredEmailDataSourceSlice.ParentID); + const [status, setStatus] = React.useState('uninitiated'); + const [data, setData] = React.useState([]); const [dataSource, setDataSource] = React.useState(null); const [showTest, setShowTest] = React.useState(false); const [showRemoveWarning, setShowRemoveWarning] = React.useState(false); const [showDataSourceModal, setShowDataSourceModal] = React.useState(false); const [page, setPage] = React.useState(0); - const totalPages = useAppSelector(TriggeredEmailDataSourceSlice.TotalPages); + const [totalPages, setTotalPages] = React.useState(0); const [refreshTrigger, setRefreshTrigger] = React.useState(false); + const triggeredEmailDataSourceController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/TriggeredEmailDataSourceEmailType`, "TriggeredEmailDataSourceName", false),[]) + React.useEffect(() => { const filters: Search.IFilter[] = [{ FieldName: "EmailTypeID", SearchText: props.Record.ID.toString(), Operator: "=", IsPivotColumn: false, Type: 'string' }] - dispatch(TriggeredEmailDataSourceSlice.PagedSearch({ filter: filters, sortField: 'TriggeredEmailDataSourceName', ascending: true, page: page})); - }, [status, props.Record.ID, emailID, dataSource, page, refreshTrigger]); + const h = triggeredEmailDataSourceController.PagedSearch(filters, 'TriggeredEmailDataSourceName', true, page); + setStatus('loading'); + + h.done((d) => { + setStatus('idle'); + setData(JSON.parse(d.Data)); + setTotalPages(d.NumberOfPages); + }) + h.fail(() => setStatus('error')) + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + }, [props.Record.ID, dataSource, page, refreshTrigger]); return (
    @@ -162,9 +176,9 @@ const DataSourceWindow = (props: IProps) => { { if (c) - dispatch(TriggeredEmailDataSourceSlice.DBAction({ - verb: 'DELETE', record: dataSource - })).then(() => setRefreshTrigger((val) => !val)); + triggeredEmailDataSourceController.DBAction( + 'DELETE', dataSource + ).then(() => setRefreshTrigger((val) => !val)); setShowRemoveWarning(false); }} diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/EmailForm.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/EmailForm.tsx index 2bfbb6d664..7af077babc 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/EmailForm.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/EmailForm.tsx @@ -21,34 +21,55 @@ // //****************************************************************************************************** -import { useAppDispatch, useAppSelector } from '../hooks'; import * as React from 'react'; -import { EmailCategory, EmailType } from '../global'; -import { EmailCategorySlice, EmailTypeSlice } from '../Store'; +import { Application } from '@gpa-gemstone/application-typings'; +import { IsNumber } from '@gpa-gemstone/helper-functions'; import { CheckBox, Input, Select } from '@gpa-gemstone/react-forms'; -import { IsNumber } from '@gpa-gemstone/helper-functions' +import { GenericController } from '@gpa-gemstone/react-interactive'; +import { EmailCategory, EmailType } from '../global'; interface IProps { record: EmailType, setRecord: (d: EmailType) => void } const EmailForm = (props: IProps) => { - const dispatch = useAppDispatch(); - const emails = useAppSelector(EmailTypeSlice.Data); - const status = useAppSelector(EmailTypeSlice.Status); + const [emails, setEmails] = React.useState([]); + const [status, setStatus] = React.useState('uninitiated'); - const categoryStatus = useAppSelector(EmailCategorySlice.Status); - const categories = useAppSelector(EmailCategorySlice.Data); + const [categoryStatus, setCategoryStatus] = React.useState('uninitiated'); + const [categories, setCategories] = React.useState([]); + const emailCategoryController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/EmailCategory`, "Name", true), []); + const emailTypeController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/EmailType`, "Name", true), []); React.useEffect(() => { - if (categoryStatus == 'uninitiated' || categoryStatus == 'changed') - dispatch(EmailCategorySlice.Fetch()); - }, [categoryStatus]); + setCategoryStatus('loading') + const h = emailCategoryController.Fetch(); + h.done((d) => { + setCategories(d) + setCategoryStatus('idle') + }); + h.fail(() => setCategoryStatus('error')); + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + + }, [emailCategoryController.Fetch]); React.useEffect(() => { - if (status == 'uninitiated' || status == 'changed') - dispatch(EmailTypeSlice.Fetch()); - }, [status]); + setStatus('loading') + const h = emailTypeController.Fetch(); + h.done((d) => { + setEmails(d) + setStatus('idle') + }); + h.fail(() => setStatus('error')); + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + }, [emailTypeController.Fetch]); function Valid(field: keyof EmailType) { if (field == 'Name') diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/EmailPage.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/EmailPage.tsx index 2f6bc707cf..348035be46 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/EmailPage.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/EmailPage.tsx @@ -21,18 +21,18 @@ // //****************************************************************************************************** -import { useAppDispatch, useAppSelector } from '../hooks'; +import * as $ from 'jquery'; import * as React from 'react'; -import { TabSelector, Warning, LoadingScreen, ServerErrorIcon, Modal } from '@gpa-gemstone/react-interactive' import { Application } from '@gpa-gemstone/application-typings'; -import { EmailTypeSlice } from '../Store'; +import { GenericController, LoadingScreen, ServerErrorIcon, TabSelector, Warning } from '@gpa-gemstone/react-interactive' +import { EmailType } from '../global' +import DataSourceWindow from './DatasourceUI/DataSourceWindow'; import GeneralInfo from './GeneralInfo'; +import History from './History' import Subscriptions from './Subscriptions'; -import TriggerWindow from './TriggerUI/TriggerWindow'; -import DataSourceWindow from './DatasourceUI/DataSourceWindow'; import Template from './Template'; import TestEmail from './TestEmail'; -import History from './History' +import TriggerWindow from './TriggerUI/TriggerWindow'; declare var homePath; declare var version; @@ -41,22 +41,40 @@ interface IProps { useParams: {id: string}} type tab = 'settings' | 'template' | 'dataSources' | 'subscriptions' | 'trigger' | 'history' - const EmailPage = (props: IProps) => { - const dispatch = useAppDispatch(); const [showDelete, setShowDelete] = React.useState(false); const [showTest, setShowTest] = React.useState(false); - const email = useAppSelector((state) => EmailTypeSlice.Datum(state, parseInt(props.useParams.id))); - const status: Application.Types.Status = useAppSelector(EmailTypeSlice.Status); + const [email, setEmail] = React.useState(null); //= useAppSelector((state) => EmailTypeSlice.Datum(state, parseInt(props.useParams.id))); + const [status, setStatus] = React.useState('uninitiated'); const [tab, setTab] = React.useState('settings'); + const emailTypeController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/EmailType`, "Name", true), []); + React.useEffect(() => { - if (status == 'uninitiated' || status == 'changed') - dispatch(EmailTypeSlice.Fetch()); - }, [status]); + setStatus('loading') + const h = $.ajax({ + type: "GET", + url: `${homePath}api/OpenXDA/EmailType/One/${props.useParams.id}`, + contentType: "application/json; charset=utf-8", + dataType: 'json', + cache: false, + async: true + }) + h.done((d) => { + setEmail(d) + setStatus('idle') + }); + h.fail(() => setStatus('error')); + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + + }, [props.useParams.id]) return (
    @@ -125,7 +143,7 @@ const EmailPage = (props: IProps) => { { if (conf) { - dispatch(EmailTypeSlice.DBAction({ verb: 'DELETE', record: email })); + emailTypeController.DBAction('DELETE', email); window.location.href = `${homePath}EventEmails`; } setShowDelete(false); diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/GeneralInfo.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/GeneralInfo.tsx index 1cf11a1296..2f4f8e2d18 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/GeneralInfo.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/GeneralInfo.tsx @@ -21,13 +21,13 @@ // //****************************************************************************************************** -import { useAppDispatch, useAppSelector } from '../hooks'; import * as React from 'react'; -import { ToolTip } from '@gpa-gemstone/react-forms'; +import { Application } from '@gpa-gemstone/application-typings'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; -import { EmailType } from '../global'; -import { EmailTypeSlice } from '../Store'; import { IsNumber } from '@gpa-gemstone/helper-functions'; +import { GenericController } from '@gpa-gemstone/react-interactive'; +import { ToolTip } from '@gpa-gemstone/react-forms'; +import { EmailType } from '../global'; import EmailForm from './EmailForm'; declare var homePath; @@ -35,19 +35,35 @@ declare var version; interface IProps { Record: EmailType} - - const GeneralInfo = (props: IProps) => { - const dispatch = useAppDispatch(); const [email, setEmail] = React.useState(props.Record); const [hasChanged, setHasChanged] = React.useState(false); const [hover, setHover] = React.useState<('submit' | 'clear' | 'none')>('none'); - const allEmails = useAppSelector(EmailTypeSlice.Data); + const [allEmails, setAllEmails] = React.useState([]); + const [status, setStatus] = React.useState('uninitiated'); const [errors, setErrors] = React.useState([]); + const emailTypeController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/EmailType`, "Name", true), []); + + + React.useEffect(() => { + setStatus('loading') + const h = emailTypeController.Fetch(); + h.done((d) => { + setAllEmails(d) + setStatus('idle') + }); + h.fail(() => setStatus('error')); + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + }, [emailTypeController.Fetch]); + React.useEffect(() => { let e = []; if (email.Name == undefined || email.Name.length < 1) @@ -109,7 +125,7 @@ const GeneralInfo = (props: IProps) => {
    diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/Subscriptions.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/Subscriptions.tsx index 3275b8a167..6262457a59 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/Subscriptions.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/Subscriptions.tsx @@ -21,16 +21,14 @@ // //****************************************************************************************************** -import { useAppDispatch, useAppSelector } from '../hooks'; -import * as React from 'react'; -import { LoadingScreen, Search } from '@gpa-gemstone/react-interactive' -import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; -import { EmailType, SubscribeEmails } from '../global'; -import { EventSubscriptionSlice } from '../Store'; -import { Table, Column, Paging } from '@gpa-gemstone/react-table'; import * as $ from 'jquery'; +import * as React from 'react'; import { Application } from '@gpa-gemstone/application-typings'; +import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; import { ToolTip } from '@gpa-gemstone/react-forms'; +import { GenericController, LoadingScreen, Search } from '@gpa-gemstone/react-interactive'; +import { Column, Table, Paging } from '@gpa-gemstone/react-table'; +import { EmailType, SubscribeEmails } from '../global'; declare var homePath; declare var version; @@ -40,11 +38,9 @@ interface IProps { Record: EmailType} const Subscriptions = (props: IProps) => { - const dispatch = useAppDispatch(); - const subscriptions = useAppSelector(EventSubscriptionSlice.SearchResults); - const status = useAppSelector(EventSubscriptionSlice.Status); - const parentID = useAppSelector(EventSubscriptionSlice.ParentID); + const [subscriptions, setSubscriptions] = React.useState([]); + const [status, setStatus] = React.useState('uninitiated'); const [ascending, setAscending] = React.useState(false); const [sortField, setSortField] = React.useState('FirstName'); const [hover, setHover] = React.useState('none'); @@ -52,13 +48,29 @@ const Subscriptions = (props: IProps) => { const [approvalStatus, setApprovalStatus] = React.useState('idle'); const [page, setPage] = React.useState(0); - const totalPages = useAppSelector(EventSubscriptionSlice.TotalPages); + const [totalPages, setTotalPages] = React.useState(0); const [refreshTrigger, setRefreshTrigger] = React.useState(false); + const eventSubscriptionController = React.useMemo(() => new GenericController(`${homePath}api/EventSubscription`, 'Email'),[]) + React.useEffect(() => { - const filters: Search.IFilter[] = [{FieldName: "EmailID", SearchText: props.Record.ID.toString(), Operator: '=', Type: 'string', IsPivotColumn: false}] - dispatch(EventSubscriptionSlice.PagedSearch({filter: filters, sortField: sortField, ascending: ascending, page: page})) - }, [props.Record.ID, sortField, ascending, page, refreshTrigger, parentID]) + setStatus('loading'); + const filters: Search.IFilter[] = [{ FieldName: "EmailID", SearchText: props.Record.ID.toString(), Operator: '=', Type: 'string', IsPivotColumn: false }]; + const h = eventSubscriptionController.PagedSearch(filters, sortField, ascending, page); + + h.done((d) => { + setSubscriptions(JSON.parse(d.Data)); + setTotalPages(d.NumberOfPages); + setStatus('idle'); + }) + h.fail(() => setStatus('error')) + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + + }, [props.Record.ID, sortField, ascending, page, refreshTrigger, eventSubscriptionController.PagedSearch]) function approve(record: SubscribeEmails) { setApprovalStatus('loading') diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/Template.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/Template.tsx index b935c81f10..dfba0aff43 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/Template.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/Template.tsx @@ -21,28 +21,26 @@ // //****************************************************************************************************** -import { useAppDispatch } from '../hooks'; import * as React from 'react'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; -import { EmailType } from '../global'; -import { EmailTypeSlice } from '../Store'; import { TextArea, ToolTip } from '@gpa-gemstone/react-forms'; +import { GenericController } from '@gpa-gemstone/react-interactive'; +import { EmailType } from '../global'; declare var homePath; declare var version; -interface IProps { Record: EmailType} +interface IProps { Record: EmailType } const Template = (props: IProps) => { - const dispatch = useAppDispatch(); const [email, setEmail] = React.useState(props.Record); const [hasChanged, setHasChanged] = React.useState(false); const [hover, setHover] = React.useState<('submit' | 'clear' | 'none')>('none'); - + const emailTypeController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/EmailType`, "Name", true), []); React.useEffect(() => { setEmail(props.Record); @@ -54,7 +52,6 @@ const Template = (props: IProps) => { setHasChanged(!h); }, [props.Record, email]) - return (
    @@ -69,33 +66,53 @@ const Template = (props: IProps) => {
    - Record={email} Setter={setEmail} Field={'Template'} Help={'XML-formatted template to specify the layout of the notification.'} Rows={12} Label='' Valid={(r) => email.Template != null && email.Template.length > 0} /> + + Record={email} + Setter={setEmail} + Field={'Template'} + Help={'XML-formatted template to specify the layout of the notification.'} + Rows={12} + Label='' + Valid={(r) => email.Template != null && email.Template.length > 0} />
    - +
    - + onMouseEnter={() => setHover('clear')} + onMouseLeave={() => setHover('none')} + >Clear Changes
    - + {!hasChanged ?

    No changes made.

    : null} {email.Template == null || email.Template.length == 0 ?

    A valid Template is required.

    : null}
    - +

    Changes to Template will be discarded.

    - ) + ) } export default Template; \ No newline at end of file diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/TriggerUI/EventDetails.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/TriggerUI/EventDetails.tsx index 74f8a4b998..09c04dc7e0 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/TriggerUI/EventDetails.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/TriggerUI/EventDetails.tsx @@ -90,7 +90,7 @@ const EventDetails = (props: IProps) => { tableLayout: 'fixed', overflow: 'hidden', display: 'flex', flexDirection: 'column' }} TheadStyle={{ fontSize: 'smaller', display: 'table', tableLayout: 'fixed', width: '100%' }} - TbodyStyle={{ display: 'block', overflowY: 'scroll', flex: 1 }} + TbodyStyle={{ display: 'block', overflowY: 'auto', flex: 1 }} RowStyle={{ display: 'table', tableLayout: 'fixed', width: '100%' }} Selected={(item) => false} KeySelector={(item, index) => index} diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/TriggerUI/EventSelect.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/TriggerUI/EventSelect.tsx index 80c612e532..e43542cbef 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/TriggerUI/EventSelect.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/TriggerUI/EventSelect.tsx @@ -54,7 +54,6 @@ const EventSelect = (props: IProps) => { React.useEffect(() => { props.SetStatus(suceeded, loading); }, [loading, suceeded]) React.useEffect(() => { - // Shortest possible is SELECT 1 which is 8 Characters if (props.TriggerSQL == null || props.TriggerSQL.length < 8) { setLoading(false); @@ -120,7 +119,7 @@ const EventSelect = (props: IProps) => { tableLayout: 'fixed', overflow: 'hidden', display: 'flex', flexDirection: 'column' }} TheadStyle={{ fontSize: 'smaller', display: 'table', tableLayout: 'fixed', width: '100%' }} - TbodyStyle={{ display: 'block', overflowY: 'scroll', flex: 1 }} + TbodyStyle={{ display: 'block', overflowY: 'auto', flex: 1 }} RowStyle={{ display: 'table', tableLayout: 'fixed', width: '100%' }} Selected={(item) => item.EventID == props.SelectedEventID } KeySelector={(item, index) => index } diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/TriggerUI/TriggerWindow.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/TriggerUI/TriggerWindow.tsx index 73f7545ed5..f01579e0d5 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/TriggerUI/TriggerWindow.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/EmailTypes/TriggerUI/TriggerWindow.tsx @@ -21,14 +21,13 @@ // //****************************************************************************************************** -import { useAppDispatch } from '../../hooks'; import * as React from 'react'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; +import { TextArea, ToolTip } from '@gpa-gemstone/react-forms'; +import { GenericController } from '@gpa-gemstone/react-interactive'; import { EmailType } from '../../global'; -import { EmailTypeSlice } from '../../Store'; -import EventSelect from './EventSelect'; import EventDetails from './EventDetails'; -import { TextArea, ToolTip } from '@gpa-gemstone/react-forms'; +import EventSelect from './EventSelect'; declare var homePath; declare var version; @@ -37,7 +36,6 @@ interface IProps { Record: EmailType} const TriggerWindow = (props: IProps) => { - const dispatch = useAppDispatch(); const [email, setEmail] = React.useState(props.Record); const [hasChanged, setHasChanged] = React.useState(false); @@ -51,6 +49,8 @@ const TriggerWindow = (props: IProps) => { const [selectedEvent, setSelectedEvent] = React.useState(-1); + const emailTypeController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/EmailType`, "Name", true), []); + React.useEffect(() => { setEmail(props.Record); }, [props.Record]) @@ -85,14 +85,20 @@ const TriggerWindow = (props: IProps) => {
    - { + { if (l) setTriggerStatus('loading'); else if (v) setTriggerStatus('valid'); else setTriggerStatus('invalid'); @@ -104,7 +110,9 @@ const TriggerWindow = (props: IProps) => {
    - { + { if (l) setAggregateStatus('loading'); else if (v) setAggregateStatus('valid'); else setAggregateStatus('invalid'); @@ -125,23 +133,37 @@ const TriggerWindow = (props: IProps) => { if ((triggerStatus == 'valid' || triggerStatus == 'idle') && (aggregateStatus == 'valid' || aggregateStatus == 'idle') && hasChanged) - dispatch(EmailTypeSlice.DBAction({ verb: 'PATCH', record: email })); + emailTypeController.DBAction('PATCH', email ); }} - data-tooltip='submit' onMouseEnter={() => setHover('submit')} onMouseLeave={() => setHover('none')}>Save Changes + data-tooltip='submit' + onMouseEnter={() => setHover('submit')} + onMouseLeave={() => setHover('none')} + >Save Changes +
    - + onMouseEnter={() => setHover('clear')} + onMouseLeave={() => setHover('none')} + >Clear Changes +
    - + {triggerStatus == 'invalid' ?

    Trigger SQL is invalid.

    : null} {aggregateStatus == 'invalid' ?

    Suppression SQL is invalid.

    : null}
    - + {props.Record.TriggerEmailSQL != email.TriggerEmailSQL ?

    Changes to Trigger SQL will be discarded.

    : null} {props.Record.CombineEventsSQL != email.CombineEventsSQL ?

    Changes to Suppression SQL will be discarded.

    : null}
    diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/EventFilterComponents/EventFilter.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/EventFilterComponents/EventFilter.tsx index d0581fc9b4..dd0deb19f0 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/EventFilterComponents/EventFilter.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/EventFilterComponents/EventFilter.tsx @@ -21,15 +21,14 @@ // //****************************************************************************************************** -import { useAppDispatch, useAppSelector } from '../hooks'; import * as React from 'react'; import { createPortal } from "react-dom"; -import {Modal } from '@gpa-gemstone/react-interactive' -import { IEventFilter } from '../global'; -import { EventTypeSlice } from '../Store'; +import { Application, OpenXDA } from '@gpa-gemstone/application-typings'; import { TimeFilter, EventTypeFilter } from '@gpa-gemstone/common-pages'; +import { GenericController, Modal } from '@gpa-gemstone/react-interactive'; import EventFilterButton from './EventFilterButton'; import FilterSelect from './FilterSelect'; +import { IEventFilter } from '../global'; declare var homePath; @@ -37,28 +36,40 @@ declare var version; interface IProps { Show: boolean, Close: () => void, Filter: IEventFilter, SetFilter: (f: IEventFilter) => void, RenderPortalId?: string } type TimeUnit = 'y' | 'M' | 'w' | 'd' | 'h' | 'm' | 's' | 'ms' - +type filterTypes = 'Meter' | 'Asset' | 'AssetGroup' | 'Location' | 'None' const EventFilter = (props: IProps) => { - const dispatch = useAppDispatch(); const [filter, setFilter] = React.useState(props.Filter); - const [showFilter, setShowFilter] = React.useState<('Meter' | 'Asset' | 'AssetGroup' | 'Location' | 'None')>('None') + const [showFilter, setShowFilter] = React.useState('None') - const eventTypes = useAppSelector(EventTypeSlice.Data); - const eventTypeStatus = useAppSelector(EventTypeSlice.Status); + const [eventTypes, setEventTypes] = React.useState([]); + const [eventTypeStatus, setEventTypeStatus] = React.useState('uninitiated'); // Portal rendering const const [domReady, setDomReady] = React.useState(false); const portalContainer = (props.RenderPortalId === undefined || props.RenderPortalId === null) ? document.getElementById('baseEventFilterPortal') : document.getElementById(props.RenderPortalId); + const eventTypeController = React.useMemo(() => new GenericController(`${homePath}api/EventType`, 'Name'), []) + React.useEffect(() => { - if (eventTypeStatus == 'uninitiated' || eventTypeStatus == 'changed') - dispatch(EventTypeSlice.Fetch()); - }, [eventTypeStatus]); + setEventTypeStatus('loading'); + + const h = eventTypeController.Fetch(); + h.done((d) => { + setEventTypes(d); + setEventTypeStatus('idle'); + }) + h.fail(() => setEventTypeStatus('error')) + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + }, [eventTypeController.Fetch]); React.useEffect(() => { if (props.Show) - setFilter((f) => ({...props.Filter, EventTypes: f.EventTypes})); + setFilter((f) => ({ ...props.Filter, EventTypes: f.EventTypes })); }, [props.Show]) React.useEffect(() => { @@ -70,6 +81,41 @@ const EventFilter = (props: IProps) => { setDomReady(true); }) + + function setIDs(type: filterTypes, ids: number[]) { + switch (type) { + case 'Asset': + setFilter((f) => ({ ...f, AssetIDs: ids })) + break + case 'AssetGroup': + setFilter((f) => ({ ...f, GroupIDs: ids })) + break + case 'Meter': + setFilter((f) => ({ ...f, MeterIDs: ids })) + break + case 'Location': + setFilter((f) => ({ ...f, SubstationIDs: ids })) + break + case 'None': + break + } + } + + function getIDs(type: filterTypes): number[] { + switch (type) { + case 'Asset': + return filter.AssetIDs + case 'AssetGroup': + return filter.GroupIDs + case 'Meter': + return filter.MeterIDs + case 'Location': + return filter.SubstationIDs + case 'None': + return [] + } + } + return (
    {domReady ? createPortal(<> @@ -123,14 +169,14 @@ const EventFilter = (props: IProps) => {
    - setShowFilter('None')} - Type={showFilter == 'None' ? 'Location' : showFilter} - IDs={[]} - OnConfirm={() => { }} + setShowFilter('None')} + Type={showFilter == 'None' ? 'Location' : showFilter} + IDs={getIDs(showFilter)} + OnConfirm={(ids) => { setIDs(showFilter, ids) }} /> , portalContainer) : null}
    - ) + ) } export default EventFilter; \ No newline at end of file diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/EventFilterComponents/EventFilterButton.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/EventFilterComponents/EventFilterButton.tsx index 694b93a743..83ea9bff26 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/EventFilterComponents/EventFilterButton.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/EventFilterComponents/EventFilterButton.tsx @@ -20,11 +20,12 @@ // Generated original version of source code. // //****************************************************************************************************** -import React from 'react'; -import 'moment'; + import _ from 'lodash'; -import { useAppDispatch, useAppSelector } from '../hooks'; -import { EventAssetGroupSlice, EventAssetSlice, EventLocationSlice, EventMeterSlice } from '../Store'; +import 'moment'; +import React from 'react'; +import { Application, OpenXDA, SystemCenter } from '@gpa-gemstone/application-typings'; +import { GenericController } from '@gpa-gemstone/react-interactive'; interface IProps { IDs: number[], @@ -33,43 +34,87 @@ interface IProps { } function EventFilterButton(props: IProps) { - const dispatch = useAppDispatch(); const [hover, setHover] = React.useState(false); const [rows, setRows] = React.useState([]); const [header, setHeader] = React.useState(null); - const meter = useAppSelector(EventMeterSlice.Data); - const meterStatus = useAppSelector(EventMeterSlice.Status); + const [meters, setMeters] = React.useState([]); + const [meterStatus, setMeterStatus] = React.useState('uninitiated'); + + const [assets, setAssets] = React.useState([]); + const [assetStatus, setAssetStatus] = React.useState('uninitiated'); - const asset = useAppSelector(EventAssetSlice.Data); - const assetStatus = useAppSelector(EventAssetSlice.Status); + const [locations, setLocations] = React.useState([]); + const [locationStatus, setLocationStatus] = React.useState('uninitiated'); - const location = useAppSelector(EventLocationSlice.Data); - const locationStatus = useAppSelector(EventLocationSlice.Status); + const [groups, setGroups] = React.useState([]); + const [groupStatus, setGroupStatus] = React.useState('uninitiated'); - const group = useAppSelector(EventAssetGroupSlice.Data); - const groupStatus = useAppSelector(EventAssetGroupSlice.Status); React.useEffect(() => { - if (meterStatus == 'uninitiated' || meterStatus == 'changed') - dispatch(EventMeterSlice.Fetch()); - }, [meterStatus]); + if (props.Type != 'Meter') return; + setMeterStatus('loading'); + const h = new GenericController(`${homePath}api/OpenXDA/Event/Meter`, "Name", true).Fetch(); + h.done((d: SystemCenter.Types.DetailedMeter[]) => { + setMeters(d); + setMeterStatus('idle'); + }) + h.fail(() => setMeterStatus('error')) + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + }, [props.Type]); React.useEffect(() => { - if (assetStatus == 'uninitiated' || assetStatus == 'changed') - dispatch(EventAssetSlice.Fetch()); - }, [assetStatus]); + if (props.Type != 'Asset') return; + setAssetStatus('loading'); + const h = new GenericController(`${homePath}api/OpenXDA/Event/Asset`, "AssetName", true).Fetch(); + h.done((d: SystemCenter.Types.DetailedAsset[]) => { + setAssets(d); + setAssetStatus('idle'); + }) + h.fail(() => setAssetStatus('error')) + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + }, [props.Type]); React.useEffect(() => { - if (locationStatus == 'uninitiated' || locationStatus == 'changed') - dispatch(EventLocationSlice.Fetch()); - }, [locationStatus]); + if (props.Type != 'Location') return; + setLocationStatus('loading'); + const h = new GenericController(`${homePath}api/OpenXDA/Event/Location`, "LocationKey", true).Fetch(); + h.done((d: SystemCenter.Types.DetailedLocation[]) => { + setLocations(d); + setLocationStatus('idle'); + }) + h.fail(() => setLocationStatus('error')) + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + }, [props.Type]); React.useEffect(() => { - if (groupStatus == 'uninitiated' || groupStatus == 'changed') - dispatch(EventAssetGroupSlice.Fetch()); - }, [groupStatus]); + if (props.Type != 'AssetGroup') return; + setGroupStatus('loading'); + const h = new GenericController(`${homePath}api/openXDA/Event/AssetGroup`, 'Name').Fetch(); + h.done((d: OpenXDA.Types.AssetGroup[]) => { + setGroups(d); + setGroupStatus('idle'); + }) + h.fail(() => setGroupStatus('error')) + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + }, [props.Type]); React.useEffect(() => { switch (props.Type) { @@ -90,7 +135,8 @@ function EventFilterButton(props: IProps) { React.useEffect(() => { switch (props.Type) { case ('Meter'): - setRows(props.IDs.filter((v, i) => i < 10).map((d) => meter.find(m => m.ID == d)).map((d) => + if (meterStatus !== 'idle') return + setRows(props.IDs.filter((v, i) => i < 10).map((d) => meters.find(m => m.ID == d)).map((d) => {d['Name']} {d['AssetKey']} {d['Location']} @@ -99,7 +145,8 @@ function EventFilterButton(props: IProps) { )); break; case ('Asset'): - setRows(props.IDs.filter((v, i) => i < 10).map((d) => asset.find(m => m.ID == d)).map((d) => + if (assetStatus !== 'idle') return + setRows(props.IDs.filter((v, i) => i < 10).map((d) => assets.find(m => m.ID == d)).map((d) => {d['AssetKey']} {d['AssetName']} {d['AssetType']} @@ -107,14 +154,16 @@ function EventFilterButton(props: IProps) { )); break; case ('AssetGroup'): - setRows(props.IDs.filter((v, i) => i < 10).map((d) => group.find(m => m.ID == d)).map((d) => + if (groupStatus !== 'idle') return + setRows(props.IDs.filter((v, i) => i < 10).map((d) => groups.find(m => m.ID == d)).map((d) => {d['Name']} {d['Assets']} {d['Meters']} )); break; default: - setRows(props.IDs.filter((v, i) => i < 10).map((d) => location.find(m => m.ID == d)).map((d) => + if (locationStatus !== 'idle') return + setRows(props.IDs.filter((v, i) => i < 10).map((d) => locations.find(m => m.ID == d)).map((d) => {d['Name']} {d['LocationKey']} {d['Meters']} @@ -122,7 +171,7 @@ function EventFilterButton(props: IProps) { )); } - }, [props.IDs, props.Type]) + }, [props.IDs, props.Type, meters, assets, groups, locations, meterStatus, groupStatus, locationStatus, assetStatus]) return ( <> diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/EventFilterComponents/FilterSelect.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/EventFilterComponents/FilterSelect.tsx index 352799f79b..5ab2a09a08 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/EventFilterComponents/FilterSelect.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/EventFilterComponents/FilterSelect.tsx @@ -23,13 +23,11 @@ import React from 'react'; import 'moment'; import _ from 'lodash'; -import { OpenXDA, SystemCenter } from '@gpa-gemstone/application-typings'; -import { DefaultSelects } from '@gpa-gemstone/common-pages'; -import { useAppDispatch, useAppSelector } from '../hooks'; -import { EventMeterSlice, EventLocationSlice, EventAssetSlice, EventAssetGroupSlice } from '../Store'; +import { Application, OpenXDA, SystemCenter } from '@gpa-gemstone/application-typings'; import * as $ from 'jquery'; -import { Search } from '@gpa-gemstone/react-interactive'; +import { GenericController, Search, SearchBar } from '@gpa-gemstone/react-interactive'; import { Column } from '@gpa-gemstone/react-table'; +import { ControllerSelects } from '../CommonComponents/ControllerSelects' interface IProps { IDs: number[], @@ -42,28 +40,53 @@ interface IProps { type Data = (SystemCenter.Types.DetailedMeter | SystemCenter.Types.DetailedAsset | OpenXDA.Types.AssetGroup | SystemCenter.Types.DetailedLocation); function FilterSelect(props: IProps) { - const dispatch = useAppDispatch(); - const select = React.useMemo(() => { - if (props.Type == 'Meter') return EventMeterSlice.Data; - if (props.Type == 'Location') return EventLocationSlice.Data; - if (props.Type == 'Asset') return EventAssetSlice.Data; - if (props.Type == 'AssetGroup') return EventAssetGroupSlice.Data; - }, [props.Type]) - const slice = React.useMemo(() => { - if (props.Type == 'Meter') return EventMeterSlice - if (props.Type == 'Location') return EventLocationSlice - if (props.Type == 'Asset') return EventAssetSlice - if (props.Type == 'AssetGroup') return EventAssetGroupSlice - }, [props.Type]) + const [data, setData] = React.useState([]); + const [selectedData, setSelectedData] = React.useState([]); + const [status, setStatus] = React.useState('uninitiated'); + const [addlFields, setAddlFields] = React.useState[]>([]) + + const meterController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/Event/Meter`, "Name", true), []) + const assetController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/Event/Asset`, "AssetName", true), []) + const groupController = React.useMemo(() => new GenericController(`${homePath}api/openXDA/Event/AssetGroup`, 'Name'), []) + const substationController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/Event/Location`, "LocationKey", true), []) + // get data according to whatever + React.useEffect(() => { + let controller; + switch (props.Type) { + case 'Meter': + controller = meterController; + break; + case 'Asset': + controller = assetController; + break; + case 'AssetGroup': + controller = groupController; + break; + case 'Location': + controller = substationController; + break; + } + setStatus('loading'); + const h = controller.Fetch(); + h.done((d) => { + setStatus('idle'); + setData(d); + }) + h.fail(() => { + setStatus('error'); + }) - const data: Data[] = useAppSelector(select as (state: any) => Data[]); - const [selectedData, setSelectedData] = React.useState(); + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + }, [props.Type]) React.useEffect(() => { setSelectedData(data.filter(i => props.IDs.findIndex((j) => j == i.ID) > -1)); - }, [data]) + }, [props.Type, data]) function getEnum(setOptions, field) { let handle = null; @@ -86,7 +109,7 @@ function FilterSelect(props: IProps) { } - function getAdditionalFields(setFields) { + React.useEffect(() => { let handle = $.ajax({ type: "GET", url: `${homePath}api/openXDA/AdditionalFieldView/ParentTable/${props.Type}/FieldName/0`, @@ -108,29 +131,47 @@ function FilterSelect(props: IProps) { let ordered = _.orderBy(d.filter(item => item.Searchable).map(item => ( { label: `[AF${item.ExternalDB != undefined ? " " + item.ExternalDB : ''}] ${item.FieldName}`, key: item.FieldName, ...ConvertType(item.Type), isPivotField: true } as Search.IField )), ['label'], ["asc"]); - setFields(ordered); + setAddlFields(ordered); }); return () => { - if (handle != null && handle.abort == null) handle.abort(); + if (handle != null && handle.abort != null) handle.abort(); }; - } + }, [props.Type]) if (props.Type == 'Meter') - return + Controller={meterController} + Selection={selectedData as any} OnClose={(selected, conf) => { props.OnClose(); - if (conf) + if (conf) props.OnConfirm(selected.map(m => m.ID)); - }} + }} Show={props.Show} Type={'multiple'} - Title={"Filter Meters"} - GetEnum={getEnum} - GetAddlFields={getAdditionalFields} + Title={`Filter by Meter`} + MinSelection={0} + PrimaryKey={'ID'} + Searchbar={(children, setFilter, searchStatus, resultCount) => + + SetFilter={setFilter} + CollumnList={[ + { label: 'Key', key: 'AssetKey', type: 'string', isPivotField: false }, + { label: 'Name', key: 'Name', type: 'string', isPivotField: false }, + { label: 'Substation Name', key: 'Location', type: 'string', isPivotField: false }, + { label: 'Make', key: 'Make', type: 'string', isPivotField: false }, + { label: 'Model', key: 'Model', type: 'string', isPivotField: false }, + { label: 'Number of Assets', key: 'MappedAssets', type: 'number', isPivotField: false }, + { label: 'Description', key: 'Description', type: 'string', isPivotField: false }, + ...addlFields + ]} + defaultCollumn={{ label: 'Name', key: 'Name', type: 'string', isPivotField: false }} + GetEnum={getEnum} + /> + } + DefaultSortField={'Name'} > Key @@ -144,69 +185,106 @@ function FilterSelect(props: IProps) { >Make Model - + if (props.Type == 'Asset') - return { - props.OnClose(); - if (conf) - props.OnConfirm(selected.map(m => m.ID)); - }} - Show={props.Show} - Type={'multiple'} - Title={"Filter Transmission Assets"} - GetEnum={getEnum} - GetAddlFields={getAdditionalFields} - > - Key - Name - Asset Type - Voltage (kV) - Meters - Substations - - + return + Controller={assetController} + Selection={selectedData as any} + OnClose={(selected, conf) => { + props.OnClose(); + if (conf) + props.OnConfirm(selected.map(m => m.ID)); + }} + Show={props.Show} + Type={'multiple'} + Title={"Filter by Asset"} + MinSelection={0} + PrimaryKey={'ID'} + Searchbar={(children, setFilter, searchStatus, resultCount) => + + SetFilter={setFilter} + CollumnList={[ + { label: 'Key', key: 'AssetKey', type: 'string', isPivotField: false }, + { label: 'Name', key: 'AssetName', type: 'string', isPivotField: false }, + { label: 'Nominal Voltage (L-L kV)', key: 'VoltageKV', type: 'number', isPivotField: false }, + { label: 'Type', key: 'AssetType', type: 'enum', isPivotField: false }, + { label: 'Meter Key', key: 'Meter', type: 'string', isPivotField: false }, + { label: 'Substation Key', key: 'Location', type: 'string', isPivotField: false }, + { label: 'Number of Meters', key: 'Meters', type: 'integer', isPivotField: false }, + { label: 'Number of Substations', key: 'Locations', type: 'integer', isPivotField: false }, + { label: 'Description', key: 'Description', type: 'string', isPivotField: false }, + ...addlFields + ]} + defaultCollumn={{label: 'Name', key: 'AssetName', type: 'string', isPivotField: false}} + GetEnum={getEnum} + /> + } + DefaultSortField={'AssetName'} + > + Key + Name + Asset Type + Voltage (kV) + Meters + Substations + + if (props.Type == 'AssetGroup') - return { - props.OnClose(); - if (conf) - props.OnConfirm(selected.map(m => m.ID)); - }} - Show={props.Show} - Type={'multiple'} - Title={"Filter Asset Groups"} - GetEnum={getEnum} - GetAddlFields={() => () => { }} - > - Name - Assets - Meters - Users - Sub Groups - + return + Controller={groupController} + Selection={selectedData as any} + OnClose={(selected, conf) => { + props.OnClose(); + if (conf) + props.OnConfirm(selected.map(m => m.ID)); + }} + Show={props.Show} + Type={'multiple'} + Title={"Filter by AssetGroup"} + MinSelection={0} + PrimaryKey={'ID'} + Searchbar={(children, setFilter, searchStatus, resultCount) => + + SetFilter={setFilter} + CollumnList={[ + { label: 'Name', key: 'Name', type: 'string', isPivotField: false }, + { label: 'Number of Meters', key: 'Meters', type: 'integer', isPivotField: false }, + { label: 'Number of Transmission Assets', key: 'Assets', type: 'integer', isPivotField: false }, + { label: 'Number of Asset Groups', key: 'AssetGroups', type: 'integer', isPivotField: false }, + { label: 'Show in PQ Dashboard', key: 'DisplayDashboard', type: 'boolean', isPivotField: false }, + { label: 'Show in Email Subscription', key: 'DisplayEmail', type: 'boolean', isPivotField: false }, + ...addlFields + ]} + GetEnum={getEnum} + defaultCollumn={{ label: 'Name', key: 'Name', type: 'string', isPivotField: false }} + /> + } + DefaultSortField={'Name'} + > + Name + Assets + Meters + Users + Sub Groups + if (props.Type == 'Location') - return + Controller={substationController} + Selection={selectedData as any} OnClose={(selected, conf) => { props.OnClose(); if (conf) @@ -215,8 +293,27 @@ function FilterSelect(props: IProps) { Show={props.Show} Type={'multiple'} Title={"Filter by Substation"} - GetEnum={getEnum} - GetAddlFields={getAdditionalFields} + MinSelection={0} + PrimaryKey={'ID'} + Searchbar={(children, setFilter, searchStatus, resultCount) => + + SetFilter={setFilter} + CollumnList={[ + { label: 'Name', key: 'Name', type: 'string', isPivotField: false }, + { label: 'Key', key: 'LocationKey', type: 'string', isPivotField: false }, + { label: 'Asset Key', key: 'Asset', type: 'string', isPivotField: false }, + { label: 'Meter Key', key: 'Meter', type: 'string', isPivotField: false }, + { label: 'Number of Assets', key: 'Assets', type: 'integer', isPivotField: false }, + { label: 'Number of Meters', key: 'Meters', type: 'integer', isPivotField: false }, + { label: 'Description', key: 'Description', type: 'string', isPivotField: false }, + ...addlFields + ]} + GetEnum={getEnum} + defaultCollumn={{ label: 'Name', key: 'Name', type: 'string', isPivotField: false } } + /> + } + DefaultSortField={'Name'} + > Name @@ -226,8 +323,7 @@ function FilterSelect(props: IProps) { >Meters Assets - - + return null } diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/ByReport.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/ByReport.tsx index 26a6f4f190..dc96bb44de 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/ByReport.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/ByReport.tsx @@ -21,13 +21,11 @@ // //****************************************************************************************************** -import { useAppDispatch, useAppSelector } from '../hooks'; import * as React from 'react'; -import { LoadingScreen, Modal, Search, SearchBar } from '@gpa-gemstone/react-interactive' +import { LoadingScreen, Modal, Search, SearchBar, GenericController } from '@gpa-gemstone/react-interactive' import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; import { Application } from '@gpa-gemstone/application-typings'; -import { ScheduledEmailType } from '../global'; -import { EmailCategorySlice, ScheduledEmailTypeSlice } from '../Store'; +import { ScheduledEmailType, EmailCategory } from '../global'; import { Table, Column, Paging } from '@gpa-gemstone/react-table'; import { IsCron } from '@gpa-gemstone/helper-functions'; import ReportForm from './ReportForm'; @@ -50,17 +48,14 @@ const emptyEmail: ScheduledEmailType = { interface IProps {} const ByReport = (props: IProps) => { - const dispatch = useAppDispatch(); const navigate = useNavigate(); - - const search: Search.IFilter[] = useAppSelector(ScheduledEmailTypeSlice.SearchFilters); - const status: Application.Types.Status = useAppSelector(ScheduledEmailTypeSlice.Status); - const searchStatus: Application.Types.Status = useAppSelector(ScheduledEmailTypeSlice.SearchStatus); - const data: ScheduledEmailType[] = useAppSelector(ScheduledEmailTypeSlice.SearchResults); - const allData: ScheduledEmailType[] = useAppSelector(ScheduledEmailTypeSlice.Data); - const categoryStatus = useAppSelector(EmailCategorySlice.Status); - const categories = useAppSelector(EmailCategorySlice.Data); - const parentID = useAppSelector(ScheduledEmailTypeSlice.ParentID); + const [filters, setFilters] = React.useState[]>([]) + const [status, setStatus] = React.useState('uninitiated'); + const [searchStatus, setSearchStatus] = React.useState('uninitiated'); + const [data, setData] = React.useState([]); + const [allData, setAllData] = React.useState([]); + const [categoryStatus, setCategoryStatus] = React.useState('uninitiated'); + const [categories, setCategories] = React.useState([]); const [showModal, setShowModal] = React.useState(false); const [errors, setErrors] = React.useState([]); @@ -71,24 +66,60 @@ const ByReport = (props: IProps) => { const [ascending, setAscending] = React.useState(false); const [page, setPage] = React.useState(0); - const totalPages = useAppSelector(ScheduledEmailTypeSlice.TotalPages); - const totalRecords = useAppSelector(ScheduledEmailTypeSlice.TotalRecords); - const recordsPerPage = useAppSelector(ScheduledEmailTypeSlice.RecordsPerPage); - + 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 scheduledEmailTypeController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/ScheduledEmailType`, "Name", true), []) + const emailCategoryController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/EmailCategory`, "Name", true), []) + React.useEffect(() => { - if (categoryStatus == 'uninitiated' || categoryStatus == 'changed') - dispatch(EmailCategorySlice.Fetch()); - }, []); + setCategoryStatus('loading') + const h = emailCategoryController.Fetch(); + h.done((d) => { + setCategories(d) + setCategoryStatus('idle') + }); + h.fail(() => setCategoryStatus('error')); + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + + }, [emailCategoryController.Fetch]); React.useEffect(() => { - dispatch(ScheduledEmailTypeSlice.Fetch()); - }, [parentID, refreshTrigger]); + setStatus('uninitiated') + const h = scheduledEmailTypeController.Fetch(); + h.done((d) => { + setAllData(d) + setStatus('idle') + }); + h.fail(() => setStatus('error')) + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + }, [scheduledEmailTypeController.Fetch, refreshTrigger]); React.useEffect(() => { - dispatch(ScheduledEmailTypeSlice.PagedSearch({ filter: undefined, sortField, ascending: ascending, page: page })); - }, [sortField, ascending, page, refreshTrigger]) + setSearchStatus('loading') + const h = scheduledEmailTypeController.PagedSearch(filters, sortField, ascending, page); + h.done((d) => { + setData(JSON.parse(d.Data as unknown as string)) + setTotalPages(d.NumberOfPages); + setRecordsPerPage(d.RecordsPerPage); + setTotalRecords(d.TotalRecords); + setSearchStatus('idle'); + }); + h.fail(() => setSearchStatus('error')) + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + }, [sortField, filters, ascending, page, refreshTrigger, scheduledEmailTypeController.PagedSearch]) React.useEffect(() => { let e = []; @@ -122,7 +153,7 @@ const ByReport = (props: IProps) => {
    CollumnList={searchFields} - SetFilter={(flds) => dispatch(ScheduledEmailTypeSlice.PagedSearch({ filter: flds }))} + SetFilter={setFilters} Direction={'left'} defaultCollumn={{ key: 'Name', label: 'Name', type: 'string', isPivotField: false }} Width={'50%'} Label={'Search'} ShowLoading={searchStatus === 'loading'} ResultNote={searchStatus === 'error' ? 'Could not complete Search' : `Displaying Report(s) ${totalRecords > 0 ? recordsPerPage * page + 1 : 0}-${recordsPerPage * page + data.length} out of ${totalRecords}`} GetEnum={() => { @@ -228,7 +259,7 @@ const ByReport = (props: IProps) => { Show={showModal} ShowX={true} Size={'lg'} ShowCancel={false} ConfirmText={'Add'} CallBack={(conf, isBtn) => { if (conf) - dispatch(ScheduledEmailTypeSlice.DBAction({ verb: "POST", record: newEmail })).then(() => setRefreshTrigger((val) => !val)) + scheduledEmailTypeController.DBAction("POST", newEmail).then(() => setRefreshTrigger((val) => !val)) setShowModal(false); }} DisableConfirm={errors.length > 0} diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/DatasourceUI/DataSourceModal.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/DatasourceUI/DataSourceModal.tsx index 15d0b3c03c..d25968725e 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/DatasourceUI/DataSourceModal.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/DatasourceUI/DataSourceModal.tsx @@ -21,13 +21,12 @@ // //****************************************************************************************************** -import { useAppDispatch, useAppSelector } from '../../hooks'; import * as React from 'react'; -import { Modal } from '@gpa-gemstone/react-interactive'; +import { Modal, GenericController } from '@gpa-gemstone/react-interactive'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; import { IDataSourceScheduledEmailType, IScheduledDataSource, IScheduledEmailDataSourceSetting } from '../../global'; -import { ScheduledDataSourceSettingSlice, ScheduledDataSourceSlice, ScheduledEmailDataSourceSlice } from '../../Store'; import { Select } from '@gpa-gemstone/react-forms'; +import { Application } from '@gpa-gemstone/application-typings'; import SQLDataSource from './SQLDataSource'; import AzureDataSource from './AzureDataSource'; import { cloneDeep } from 'lodash'; @@ -43,30 +42,55 @@ interface IProps { const DataSourceModal = (props: IProps) => { - const dispatch = useAppDispatch(); - const typeStatus = useAppSelector(ScheduledDataSourceSlice.Status); - const types: IScheduledDataSource[] = useAppSelector(ScheduledDataSourceSlice.Data); + const [typeStatus, setTypeStatus] = React.useState('uninitiated'); + const [types, setTypes] = React.useState([]); const [record, setRecord] = React.useState(); - const originalsettings: IScheduledEmailDataSourceSetting[] = useAppSelector(ScheduledDataSourceSettingSlice.Data); - const settingStatus = useAppSelector(ScheduledDataSourceSettingSlice.Status); - const settingsParentID = useAppSelector(ScheduledDataSourceSettingSlice.ParentID); + const [originalsettings, setOriginalSettings] = React.useState([]); + const [settingStatus, setSettingStatus] = React.useState('uninitiated'); const [currentSettings, setCurrentSettings] = React.useState([]); const [errors, setErrors] = React.useState([]); const [changes, setChanges] = React.useState([]); - React.useEffect(() => { - if (typeStatus == 'uninitiated' || typeStatus == 'changed') - dispatch(ScheduledDataSourceSlice.Fetch()); - }, [typeStatus]); + const scheduledDataSourceSettingController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/ScheduledEmailDataSourceSetting`, "Name", false), []) + const dataSourceScheduledEmailTypeController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/ScheduledEmailDataSourceEmailType`, "ScheduledEmailDataSourceName", false), []); + const scheduledDataSourceController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/ScheduledEmailDataSource`, "Name", false), []); + + React.useEffect(() => { + const h = scheduledDataSourceController.Fetch(); + setTypeStatus('loading'); + h.done((d) => { + setTypeStatus('idle'); + setTypes(d); + }) + h.fail(() => setTypeStatus('error')) + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + }, [scheduledDataSourceController.Fetch]); React.useEffect(() => { if (props.Record == null) return - if (settingStatus == 'uninitiated' || settingStatus == 'changed' || settingsParentID != props.Record.ID) - dispatch(ScheduledDataSourceSettingSlice.Fetch(props.Record.ID)); - }, [settingStatus, props.Record]); + + setSettingStatus('loading') + const h = scheduledDataSourceSettingController.Fetch(props.Record.ID); + + h.done((d) => { + setOriginalSettings(d) + setSettingStatus('idle') + }); + h.fail(() => setSettingStatus('error')); + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + + }, [props.Record, scheduledDataSourceSettingController.Fetch]); React.useEffect(() => { if (props.Record == null) @@ -172,11 +196,11 @@ const DataSourceModal = (props: IProps) => { { if (c && record.ID < 0) { - dispatch(ScheduledEmailDataSourceSlice.DBAction({ verb: 'POST', record: { ...record, Settings: currentSettings.filter(s => s.Value != null) } })) + dataSourceScheduledEmailTypeController.DBAction('POST',{ ...record, Settings: currentSettings.filter(s => s.Value != null) } ) } if (c && record.ID >= 0) { - dispatch(ScheduledEmailDataSourceSlice.DBAction({ verb: 'PATCH', record })) - currentSettings.forEach((s) => { if (s.Value != null) dispatch(ScheduledDataSourceSettingSlice.DBAction({ verb: s.ID == 0 ? 'POST' : 'PATCH', record: s }));}); + dataSourceScheduledEmailTypeController.DBAction('PATCH', record ) + currentSettings.forEach((s) => { if (s.Value != null) scheduledDataSourceSettingController.DBAction( s.ID == 0 ? 'POST' : 'PATCH', s );}); } if (!c) setCurrentSettings(originalsettings); diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/DatasourceUI/DataSourceWindow.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/DatasourceUI/DataSourceWindow.tsx index b1245f4118..0c17d69b18 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/DatasourceUI/DataSourceWindow.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/DatasourceUI/DataSourceWindow.tsx @@ -21,15 +21,14 @@ // //****************************************************************************************************** -import { useAppDispatch, useAppSelector } from '../../hooks'; import * as React from 'react'; +import { Application } from '@gpa-gemstone/application-typings'; +import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; +import { GenericController, Search, Warning } from '@gpa-gemstone/react-interactive'; +import { Column, Paging, Table } from '@gpa-gemstone/react-table'; import { ScheduledEmailType, IDataSourceScheduledEmailType } from '../../global'; -import { ScheduledEmailDataSourceSlice } from '../../Store'; import DataSourceModal from './DataSourceModal'; -import { Table, Column, Paging } from '@gpa-gemstone/react-table'; import DataSourceTesting from './DataSourceTesting'; -import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; -import { Warning, Search } from '@gpa-gemstone/react-interactive'; declare var homePath; @@ -39,22 +38,37 @@ interface IProps { Record: ScheduledEmailType } const DataSourceWindow = (props: IProps) => { - const dispatch = useAppDispatch(); - const status = useAppSelector(ScheduledEmailDataSourceSlice.Status); - const data = useAppSelector(ScheduledEmailDataSourceSlice.SearchResults); - const emailID = useAppSelector(ScheduledEmailDataSourceSlice.ParentID); + const [status, setStatus] = React.useState('uninitiated'); + const [data, setData] = React.useState([]); const [dataSource, setDataSource] = React.useState(null); const [showTest, setShowTest] = React.useState(false); const [showRemoveWarning, setShowRemoveWarning] = React.useState(false); const [showDataSourceModal, setShowDataSourceModal] = React.useState(false); const [page, setPage] = React.useState(0); - const totalPages = useAppSelector(ScheduledEmailDataSourceSlice.TotalPages); + const [totalPages, setTotalPages] = React.useState(0); const [refreshTrigger, setRefreshTrigger] = React.useState(false); + const dataSourceScheduledEmailTypeController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/ScheduledEmailDataSourceEmailType`, "ScheduledEmailDataSourceName", false), []); + React.useEffect(() => { - const filters: Search.IFilter[] = [{ FieldName: "ScheduledEmailTypeID", SearchText: props.Record.ID.toString(), Operator: "=", IsPivotColumn: false, Type: 'string' }] - dispatch(ScheduledEmailDataSourceSlice.PagedSearch({ filter: filters, sortField: 'ScheduledEmailDataSourceName', ascending: true, page: page })); - }, [status, props.Record.ID, emailID, dataSource, page, refreshTrigger]); + const filters: Search.IFilter[] = [{ FieldName: "ScheduledEmailTypeID", SearchText: props.Record.ID.toString(), Operator: "=", IsPivotColumn: false, Type: 'string' }]; + setStatus('loading'); + + const h = dataSourceScheduledEmailTypeController.PagedSearch(filters, 'ID', true, page) + + h.done((d) => { + setStatus('idle'); + setData(JSON.parse(d.Data)); + setTotalPages(d.NumberOfPages); + }) + + h.fail(() => setStatus('error')) + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + }, [props.Record.ID, dataSource, page, refreshTrigger]); return (
    @@ -154,9 +168,9 @@ const DataSourceWindow = (props: IProps) => { { if (c) - dispatch(ScheduledEmailDataSourceSlice.DBAction({ - verb: 'DELETE', record: dataSource - })).then(() => setRefreshTrigger((val) => !val)); + dataSourceScheduledEmailTypeController.DBAction( + 'DELETE', dataSource + ).then(() => setRefreshTrigger((val) => !val)); setShowRemoveWarning(false); }} diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/GeneralInfo.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/GeneralInfo.tsx index 529d6f7b30..dcc86ba3af 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/GeneralInfo.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/GeneralInfo.tsx @@ -21,13 +21,13 @@ // //****************************************************************************************************** -import { useAppDispatch, useAppSelector } from '../hooks'; import * as React from 'react'; -import { ToolTip } from '@gpa-gemstone/react-forms'; +import { Application } from '@gpa-gemstone/application-typings'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; -import { ScheduledEmailType } from '../global'; -import { ScheduledEmailTypeSlice } from '../Store'; import { IsCron } from '@gpa-gemstone/helper-functions'; +import { ToolTip } from '@gpa-gemstone/react-forms'; +import { GenericController } from '@gpa-gemstone/react-interactive'; +import { ScheduledEmailType } from '../global'; import ReportForm from './ReportForm'; declare var homePath; @@ -37,21 +37,39 @@ interface IProps { Record: ScheduledEmailType } const GeneralInfo = (props: IProps) => { - const dispatch = useAppDispatch(); const [email, setEmail] = React.useState(props.Record); const [hasChanged, setHasChanged] = React.useState(false); const [hover, setHover] = React.useState<('submit' | 'clear' | 'none')>('none'); - const allEmails = useAppSelector(ScheduledEmailTypeSlice.Data); + const [scheduledEmails, setScheduledEmails] = React.useState([]); + const [scheduledEmailStatus, setScheduledEmailStatus] = React.useState('uninitiated'); const [errors, setErrors] = React.useState([]); + const scheduledEmailTypeController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/ScheduledEmailType`, "Name", true), []) + + React.useEffect(() => { + setScheduledEmailStatus('loading') + const h = scheduledEmailTypeController.Fetch(); + h.done((d) => { + setScheduledEmails(d) + setScheduledEmailStatus('idle') + }); + h.fail(() => setScheduledEmailStatus('error')); + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + + }, [scheduledEmailTypeController.Fetch]); + React.useEffect(() => { let e = []; if (email.Name == undefined || email.Name.length < 1) e.push('A Name is required'); - if (allEmails.findIndex(s => s.Name === email.Name && s.ID !== email.ID) >= 0) + if (scheduledEmails.findIndex(s => s.Name === email.Name && s.ID !== email.ID) >= 0) e.push('An Email with this Name already exists'); if (email.EmailCategoryID < 0) e.push('A Category must be selected.'); @@ -98,7 +116,7 @@ const GeneralInfo = (props: IProps) => { diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/ReportForm.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/ReportForm.tsx index acdcec636a..2e858bf3a4 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/ReportForm.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/ReportForm.tsx @@ -23,36 +23,59 @@ import { useAppDispatch, useAppSelector } from '../hooks'; import * as React from 'react'; -import { ScheduledEmailType } from '../global'; -import { EmailCategorySlice, ScheduledEmailTypeSlice } from '../Store'; +import { ScheduledEmailType, EmailCategory } from '../global'; import { CheckBox, Input, Select } from '@gpa-gemstone/react-forms'; -import { IsCron } from '@gpa-gemstone/helper-functions' +import { IsCron } from '@gpa-gemstone/helper-functions'; +import { Application } from '@gpa-gemstone/application-typings'; +import { GenericController } from '@gpa-gemstone/react-interactive'; interface IProps { record: ScheduledEmailType, setRecord: (d: ScheduledEmailType) => void } const ReportForm = (props: IProps) => { - const dispatch = useAppDispatch(); - const emails = useAppSelector(ScheduledEmailTypeSlice.Data); - const status = useAppSelector(ScheduledEmailTypeSlice.Status); + const [scheduledEmails, setScheduledEmails] = React.useState([]); + const [scheduledEmailStatus, setScheduledEmailStatus] = React.useState('uninitiated'); - const categoryStatus = useAppSelector(EmailCategorySlice.Status); - const categories = useAppSelector(EmailCategorySlice.Data); + const [categoryStatus, setCategoryStatus] = React.useState('uninitiated'); + const [categories, setCategories] = React.useState([]); + const scheduledEmailTypeController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/ScheduledEmailType`, "Name", true), []) + const emailCategoryController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/EmailCategory`, "Name", true), []) React.useEffect(() => { - if (categoryStatus == 'uninitiated' || categoryStatus == 'changed') - dispatch(EmailCategorySlice.Fetch()); - }, [categoryStatus]); + setCategoryStatus('loading') + const h = emailCategoryController.Fetch(); + h.done((d) => { + setCategories(d) + setCategoryStatus('idle') + }); + h.fail(() => setCategoryStatus('error')); + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + + }, [emailCategoryController.Fetch]); React.useEffect(() => { - if (status == 'uninitiated' || status == 'changed') - dispatch(ScheduledEmailTypeSlice.Fetch()); - }, [status]); + setScheduledEmailStatus('loading') + const h = scheduledEmailTypeController.Fetch(); + h.done((d) => { + setScheduledEmails(d) + setScheduledEmailStatus('idle') + }); + h.fail(() => setScheduledEmailStatus('error')); + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + + }, [scheduledEmailTypeController.Fetch]); function Valid(field: keyof ScheduledEmailType) { if (field == 'Name') - return props.record.Name != null && props.record.Name.length != 0 && emails.findIndex(e => e.Name == props.record.Name && props.record.ID != e.ID) == -1; + return props.record.Name != null && props.record.Name.length != 0 && scheduledEmails.findIndex(e => e.Name == props.record.Name && props.record.ID != e.ID) == -1; if (field == 'Schedule') return props.record.Schedule != null && IsCron(props.record.Schedule); diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/ReportPage.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/ReportPage.tsx index 1c0b563b90..c27db129b5 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/ReportPage.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/ReportPage.tsx @@ -21,17 +21,17 @@ // //****************************************************************************************************** -import { useAppDispatch, useAppSelector } from '../hooks'; +import * as $ from 'jquery' import * as React from 'react'; -import { TabSelector, Warning, LoadingScreen, ServerErrorIcon, Modal } from '@gpa-gemstone/react-interactive' import { Application } from '@gpa-gemstone/application-typings'; -import { ScheduledEmailTypeSlice } from '../Store'; +import { GenericController, LoadingScreen, ServerErrorIcon, TabSelector, Warning } from '@gpa-gemstone/react-interactive'; +import { ScheduledEmailType } from '../global'; +import DataSourceWindow from './DatasourceUI/DataSourceWindow'; import GeneralInfo from './GeneralInfo'; import Subscriptions from './Subscriptions'; -import TriggerWindow from './TriggerWindow'; -import DataSourceWindow from './DatasourceUI/DataSourceWindow'; import Template from './Template'; import TestEmail from './TestEmail'; +import TriggerWindow from './TriggerWindow'; declare var homePath; declare var version; @@ -49,13 +49,14 @@ const Tabs = [ const EmailPage = (props: IProps) => { - const dispatch = useAppDispatch(); const [showDelete, setShowDelete] = React.useState(false); const [showTest, setShowTest] = React.useState(false); - const email = useAppSelector((state) => ScheduledEmailTypeSlice.Datum(state, parseInt(props.useParams.id))); - const status: Application.Types.Status = useAppSelector(ScheduledEmailTypeSlice.Status); + const [scheduledEmail, setScheduledEmail] = React.useState(null); + const [scheduledEmailStatus, setScheduledEmailStatus] = React.useState('uninitiated'); + + const scheduledEmailTypeController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/ScheduledEmailType`, "Name", true), []) const getTab = React.useCallback(() => { if (sessionStorage.hasOwnProperty('ReportPage.Tab')) @@ -66,9 +67,27 @@ const EmailPage = (props: IProps) => { const [tab, setTab] = React.useState(getTab()); React.useEffect(() => { - if (status == 'uninitiated' || status == 'changed') - dispatch(ScheduledEmailTypeSlice.Fetch()); - }, [status]); + setScheduledEmailStatus('loading') + const h = $.ajax({ + type: "GET", + url: `${homePath}api/OpenXDA/ScheduledEmailType/One/${props.useParams.id}`, + contentType: "application/json; charset=utf-8", + dataType: 'json', + cache: false, + async: true + }) + h.done((d) => { + setScheduledEmail(d) + setScheduledEmailStatus('idle') + }); + h.fail(() => setScheduledEmailStatus('error')); + + return function cleanup() { + if (h != null && h.abort != null) + h.abort(); + } + + }, [props.useParams.id]) React.useEffect(() => { const saved = getTab(); @@ -79,12 +98,12 @@ const EmailPage = (props: IProps) => { return (
    - - - {!email ? <> : <> + + + {!scheduledEmail ? <> : <>
    -

    {email != null ? email.Name : ''}

    +

    {scheduledEmail != null ? scheduledEmail.Name : ''}

    @@ -99,37 +118,37 @@ const EmailPage = (props: IProps) => {
    {tab == "settings" ?
    - +
    : <>} {tab == "template" ?
    -