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) => { { if (errors.length == 0 && hasChanged) dispatch(EmailCategorySlice.DBAction({ verb: 'PATCH', record: category })); }} + onClick={() => { if (errors.length == 0 && hasChanged) emailCategoryController.DBAction('PATCH',category); }} data-tooltip='submit' onMouseEnter={() => setHover('submit')} onMouseLeave={() => setHover('none')}>Save Changes 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) => { { if (errors.length == 0 && hasChanged) dispatch(EmailTypeSlice.DBAction({ verb: 'PATCH', record: email })); }} + onClick={() => { if (errors.length == 0 && hasChanged) emailTypeController.DBAction('PATCH', email); }} data-tooltip='submit' onMouseEnter={() => setHover('submit')} onMouseLeave={() => setHover('none')}>Save Changes 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} /> - 0 && hasChanged ? '' : ' disabled')} type="submit" - onClick={() => { if (email.Template != null && email.Template.length > 0 && hasChanged) dispatch(EmailTypeSlice.DBAction({ verb: 'PATCH', record: email })); }} - data-tooltip='submit' onMouseEnter={() => setHover('submit')} onMouseLeave={() => setHover('none')}>Save Changes + 0 && hasChanged ? '' : ' disabled')} + type="submit" + onClick={() => { if (email.Template != null && email.Template.length > 0 && hasChanged) emailTypeController.DBAction('PATCH', email); }} + data-tooltip='submit' + onMouseEnter={() => setHover('submit')} + onMouseLeave={() => setHover('none')} + >Save Changes - { setEmail(props.Record); setHasChanged(false); }} - onMouseEnter={() => setHover('clear')} onMouseLeave={() => setHover('none')} >Clear Changes + 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 + - { setEmail(props.Record); setHasChanged(false); }} - onMouseEnter={() => setHover('clear')} onMouseLeave={() => setHover('none')} >Clear 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) => { { if (errors.length == 0 && hasChanged) { - dispatch(ScheduledEmailTypeSlice.DBAction({ verb: 'PATCH', record: email })); + scheduledEmailTypeController.DBAction('PATCH', email); } }} data-tooltip='submit' onMouseEnter={() => setHover('submit')} onMouseLeave={() => setHover('none')}>Save Changes 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 : ''} setShowDelete(true)}>Delete Report @@ -99,37 +118,37 @@ const EmailPage = (props: IProps) => { {tab == "settings" ? - + : <>>} {tab == "template" ? - + : <>>} {tab == "dataSources" ? - + : <>>} {tab == 'condition' ? - + : <>>} {tab == 'subscriptions' ? - + : <>>} - setShowTest(false)} record={email} /> - setShowTest(false)} record={scheduledEmail} /> + { if (conf) { - dispatch(ScheduledEmailTypeSlice.DBAction({ verb: 'DELETE', record: email })); + scheduledEmailTypeController.DBAction('DELETE', scheduledEmail); window.location.href = homePath + 'ReportEmails'; } setShowDelete(false); diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/Subscriptions.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/Subscriptions.tsx index c5422459a5..a8426ba6cf 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/Subscriptions.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/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 { ScheduledEmailType, SubscribeScheduledEmails } from '../global'; -import { ReportSubscriptionSlice } 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 { Table, Column, Paging } from '@gpa-gemstone/react-table'; +import { ScheduledEmailType, SubscribeScheduledEmails } from '../global'; declare var homePath; declare var version; @@ -39,23 +37,37 @@ interface IProps { Record: ScheduledEmailType } const Subscriptions = (props: IProps) => { - const dispatch = useAppDispatch(); - const subscriptions = useAppSelector(ReportSubscriptionSlice.SearchResults); - const status = useAppSelector(ReportSubscriptionSlice.SearchStatus); - const parentID = useAppSelector(ReportSubscriptionSlice.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 [page, setPage] = React.useState(0); - const totalPages = useAppSelector(ReportSubscriptionSlice.TotalPages); + const [totalPages, setTotalPages] = React.useState(0); const [hover, setHover] = React.useState('none'); + const [refreshTrigger, setRefreshTrigger] = React.useState(false); const [approvalStatus, setApprovalStatus] = React.useState('idle'); + const reportSubscriptionController = React.useMemo(() => new GenericController(`${homePath}api/ReportSubscription`, 'Email'),[]) + React.useEffect(() => { const filters: Search.IFilter[] = [{FieldName: "ScheduledEmailID", IsPivotColumn: false, SearchText: props.Record.ID.toString(), Operator: "=", Type: 'string'}]; - dispatch(ReportSubscriptionSlice.PagedSearch({filter: filters, sortField: sortField, ascending: ascending, page: page})) - }, [props.Record, parentID, ascending, sortField, page]) + const h = reportSubscriptionController.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, ascending, sortField, page, refreshTrigger]) function approve(record: SubscribeScheduledEmails) { setApprovalStatus('loading') @@ -67,7 +79,7 @@ const Subscriptions = (props: IProps) => { async: true }).then((d) => { setApprovalStatus('idle'); - dispatch(ReportSubscriptionSlice.Fetch(props.Record.ID)) + setRefreshTrigger(val => !val); }, () => { setApprovalStatus('error'); }); } @@ -83,7 +95,7 @@ const Subscriptions = (props: IProps) => { Promise.all(handles).then((d) => { setApprovalStatus('idle'); - dispatch(ReportSubscriptionSlice.Fetch(props.Record.ID)); + setRefreshTrigger(val => !val); }, () => { setApprovalStatus('error'); }); } diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/Template.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/Template.tsx index c3a53c0b20..8e6655c85b 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/Template.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/Template.tsx @@ -21,12 +21,11 @@ // //****************************************************************************************************** -import { useAppDispatch } from '../hooks'; import * as React from 'react'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; import { ScheduledEmailType } from '../global'; -import { ScheduledEmailTypeSlice } from '../Store'; import { TextArea, ToolTip } from '@gpa-gemstone/react-forms'; +import { GenericController } from '@gpa-gemstone/react-interactive'; declare var homePath; declare var version; @@ -36,12 +35,13 @@ interface IProps { Record: ScheduledEmailType } 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 scheduledEmailTypeController = React.useMemo(() => new GenericController(`${homePath}api/OpenXDA/ScheduledEmailType`, "Name", true), []) + React.useEffect(() => { setEmail(props.Record); }, [props.Record]) @@ -74,7 +74,7 @@ const Template = (props: IProps) => { 0 && hasChanged ? '' : ' disabled')} type="submit" - onClick={() => { if (email.Template != null && email.Template.length > 0 && hasChanged) dispatch(ScheduledEmailTypeSlice.DBAction({ verb: 'PATCH', record: email })); }} + onClick={() => { if (email.Template != null && email.Template.length > 0 && hasChanged) scheduledEmailTypeController.DBAction('PATCH', email); }} data-tooltip='submit' onMouseEnter={() => setHover('submit')} onMouseLeave={() => setHover('none')}>Save Changes diff --git a/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/TriggerWindow.tsx b/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/TriggerWindow.tsx index b8e30e903a..6c1b4804a3 100644 --- a/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/TriggerWindow.tsx +++ b/Source/Applications/SystemCenterNotification/Scripts/TSX/Report/TriggerWindow.tsx @@ -26,7 +26,7 @@ import * as React from 'react'; import { TextArea, ToolTip } from '@gpa-gemstone/react-forms'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; import { ScheduledEmailType } from '../global'; -import { ScheduledEmailTypeSlice } from '../Store'; +import { GenericController } from '@gpa-gemstone/react-interactive'; import moment from 'moment'; declare var homePath; @@ -49,6 +49,8 @@ const TriggerWindow = (props: IProps) => { const [triggers, setTriggers] = React.useState(false) const [selectedDateTime, setSelectedDateTime] = React.useState<{ dateTime: string }>({ dateTime: moment.utc().format(momentDateTimeFormat) }); + const scheduledEmailTypeController = React.useMemo(() => new GenericController
+ At least {props.MinSelection} items must be selected. +
No changes made.
A valid Template is required.
Changes to Template will be discarded.
Trigger SQL is invalid.
Suppression SQL is invalid.
Changes to Trigger SQL will be discarded.
Changes to Suppression SQL will be discarded.