diff --git a/Source/Applications/SystemCenter/Controllers/OpenXDA/AssetGroups/OpenXDAAssetGroupsController.cs b/Source/Applications/SystemCenter/Controllers/OpenXDA/AssetGroups/OpenXDAAssetGroupsController.cs index 97c92e7147..c5eba2475b 100644 --- a/Source/Applications/SystemCenter/Controllers/OpenXDA/AssetGroups/OpenXDAAssetGroupsController.cs +++ b/Source/Applications/SystemCenter/Controllers/OpenXDA/AssetGroups/OpenXDAAssetGroupsController.cs @@ -39,6 +39,7 @@ using GSF.Data; using GSF.Data.Model; using GSF.Web.Model; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using openXDA.Model; using SystemCenter.Model; @@ -57,8 +58,8 @@ private class extendedAssetGroupView: AssetGroupView } - [HttpGet, Route("{assetGroupID:int}/Assets")] - public IHttpActionResult GetAssets(int assetGroupID) + [HttpPost, Route("{assetGroupID:int}/Assets/{page:int}")] + public IHttpActionResult GetAssets([FromBody] PostData postData, [FromUri] int assetGroupID, [FromUri] int page) { if (GetRoles == string.Empty || User.IsInRole(GetRoles)) { @@ -66,7 +67,9 @@ public IHttpActionResult GetAssets(int assetGroupID) { try { - string sql = @"SELECT + int recordsPerPage = PageSize ?? 50; + + string sql = @$"SELECT DISTINCT Asset.ID, AssetAssetGroup.AssetGroupID, @@ -91,9 +94,30 @@ GROUP BY Asset.VoltageKV, AssetType.Name, AssetAssetGroup.AssetGroupID - HAVING AssetAssetGroup.AssetGroupID = {0}"; + HAVING AssetAssetGroup.AssetGroupID = {{0}} + ORDER BY {postData.OrderBy} {(postData.Ascending ? "ASC" : "DESC")} + OFFSET {recordsPerPage * page} ROWS + FETCH NEXT {recordsPerPage} ROWS ONLY + "; + + string countSql = @"SELECT + COUNT(DISTINCT AssetID) + FROM + AssetAssetGroup + WHERE + AssetGroupID = {0}"; + + int count = connection.ExecuteScalar(countSql, assetGroupID); - return Ok(connection.RetrieveData(sql,assetGroupID)); + DataTable results = connection.RetrieveData(sql, assetGroupID); + + return Ok(new PagedResults() + { + Data = JsonConvert.SerializeObject(results), + NumberOfPages = (count + recordsPerPage - 1) / recordsPerPage, + TotalRecords = count, + RecordsPerPage = recordsPerPage + }); } catch (Exception ex) { @@ -159,8 +183,8 @@ public IHttpActionResult RemoveAsset(int assetGroupID, int assetID) } } - [HttpGet, Route("{assetGroupID:int}/Meters")] - public IHttpActionResult GetMeters(int assetGroupID) + [HttpPost, Route("{assetGroupID:int}/Meters/{page:int}")] + public IHttpActionResult GetMeters([FromBody] PostData postData, [FromUri] int assetGroupID, [FromUri] int page) { if (GetRoles == string.Empty || User.IsInRole(GetRoles)) { @@ -168,7 +192,9 @@ public IHttpActionResult GetMeters(int assetGroupID) { try { - string sql = @"SELECT DISTINCT + int recordsPerPage = PageSize ?? 50; + + string sql = @$"SELECT DISTINCT Meter.ID, MeterAssetGroup.AssetGroupID, Meter.AssetKey, @@ -177,6 +203,28 @@ public IHttpActionResult GetMeters(int assetGroupID) Meter.Model, Location.Name as Location, COUNT(DISTINCT MeterAsset.AssetID) as MappedAssets + FROM + Meter LEFT JOIN + Location ON Meter.LocationID = Location.ID LEFT JOIN + MeterAsset ON Meter.ID = MeterAsset.MeterID LEFT JOIN + Asset ON MeterAsset.AssetID = Asset.ID LEFT JOIN + MeterAssetGroup ON Meter.ID = MeterAssetGroup.MeterID + GROUP BY + Meter.ID, + Meter.AssetKey, + Meter.Name, + Meter.Make, + Meter.Model, + Location.Name, + MeterAssetGroup.AssetGroupID + HAVING MeterAssetGroup.AssetGroupID = {{0}} + ORDER BY {postData.OrderBy} {(postData.Ascending ? "ASC" : "DESC")} + OFFSET {recordsPerPage * page} ROWS + FETCH NEXT {recordsPerPage} ROWS ONLY" + ; + + string countSql = @"SELECT + COUNT(DISTINCT Meter.ID) FROM Meter LEFT JOIN Location ON Meter.LocationID = Location.ID LEFT JOIN @@ -193,7 +241,17 @@ GROUP BY MeterAssetGroup.AssetGroupID HAVING MeterAssetGroup.AssetGroupID = {0}"; - return Ok(connection.RetrieveData(sql,assetGroupID)); + int count = connection.ExecuteScalar(countSql, assetGroupID); + + DataTable results = connection.RetrieveData(sql, assetGroupID); + + return Ok(new PagedResults() + { + Data = JsonConvert.SerializeObject(results), + TotalRecords = count, + RecordsPerPage = recordsPerPage, + NumberOfPages = (count + recordsPerPage - 1) / recordsPerPage + }); } catch (Exception ex) { @@ -283,8 +341,8 @@ public IHttpActionResult GetUserAccounts(int assetGroupID) return Unauthorized(); } - [HttpGet, Route("{assetGroupID:int}/AssetGroups")] - public IHttpActionResult GetSubGroups(int assetGroupID) + [HttpPost, Route("{assetGroupID:int}/AssetGroups/{page:int}")] + public IHttpActionResult GetSubGroupsPaged([FromBody] PostData postData, [FromUri] int assetGroupID, [FromUri] int page) { if (GetRoles == string.Empty || User.IsInRole(GetRoles)) { @@ -292,9 +350,23 @@ public IHttpActionResult GetSubGroups(int assetGroupID) { try { - IEnumerable records = new TableOperations(connection).QueryRecordsWhere("ID in (SELECT ChildAssetGroupID FROM AssetGroupAssetGroupView WHERE ParentAssetGroupID = {0})", assetGroupID); + int recordsPerPage = PageSize ?? 50; - return Ok(records); + TableOperations table = new TableOperations(connection); + + RecordRestriction recordRestriction = new RecordRestriction("ID in (SELECT ChildAssetGroupID FROM AssetGroupAssetGroupView WHERE ParentAssetGroupID = {0})", assetGroupID); + + int count = table.QueryRecordCount(recordRestriction); + + IEnumerable records = new TableOperations(connection).QueryRecords(postData.OrderBy, postData.Ascending, page + 1, recordsPerPage, recordRestriction); + + return Ok(new PagedResults() + { + Data = JsonConvert.SerializeObject(records), + TotalRecords = count, + RecordsPerPage = recordsPerPage, + NumberOfPages = (count + recordsPerPage - 1) / recordsPerPage + }); } catch (Exception ex) { diff --git a/Source/Applications/SystemCenter/Controllers/OpenXDA/Assets/OpenXDAAssetController.cs b/Source/Applications/SystemCenter/Controllers/OpenXDA/Assets/OpenXDAAssetController.cs index 3779cb0198..ce4497918f 100644 --- a/Source/Applications/SystemCenter/Controllers/OpenXDA/Assets/OpenXDAAssetController.cs +++ b/Source/Applications/SystemCenter/Controllers/OpenXDA/Assets/OpenXDAAssetController.cs @@ -71,7 +71,7 @@ public IHttpActionResult GetAssetLocationsPaged([FromBody] PostData postData, [F if (!GetAuthCheck()) return Unauthorized(); - int recordsPerPage = Take ?? 50; + int recordsPerPage = PageSize ?? 50; PagedResults results = new PagedResults(); @@ -164,7 +164,7 @@ public IHttpActionResult GetAssetMetersPaged([FromBody] PostData postData, [From { try { - int recordsPerPage = Take ?? 50; + int recordsPerPage = PageSize ?? 50; string[] sortFields = { "AssetKey", "Name", "Make", "Model" }; @@ -204,7 +204,7 @@ public IHttpActionResult GetAssetAssetConnections([FromBody] PostData postData, { try { - int recordsPerPage = Take ?? 50; + int recordsPerPage = PageSize ?? 50; string[] sortFields = { "AssetName", "AssetKey", "Name" }; @@ -686,8 +686,8 @@ public IHttpActionResult PostExistingMeterForAsset(int assetID, int meterID) } } - [HttpGet, Route("{assetID:int}/ConnectedChannels")] - public IHttpActionResult GetAssetChannels(int assetID) + [HttpPost, Route("{assetID:int}/ConnectedChannels/{page:int}")] + public IHttpActionResult GetAssetChannels([FromBody] PostData postData, [FromUri] int assetID, [FromUri] int page) { if (GetRoles == string.Empty || User.IsInRole(GetRoles)) { @@ -695,6 +695,8 @@ public IHttpActionResult GetAssetChannels(int assetID) { using (AdoDataConnection connection = new AdoDataConnection(Connection)) { + int recordsPerPage = PageSize ?? 50; + Asset asset = new TableOperations(connection).QueryRecordWhere("ID={0}", assetID); if (asset is null) throw (new Exception($"Asset ID={assetID} not found in OpenXDA database")); @@ -706,17 +708,43 @@ public IHttpActionResult GetAssetChannels(int assetID) if (connectedChannels.Count > 0) { - TableOperations tableOp = new TableOperations(connection); - // Channels get triplicated from Series Type ID in ChannelDetail View - IEnumerable uniqueChannels = new TableOperations(connection) - .QueryRecordsWhere($"ID in ({string.Join(", ", connectedChannels.Select(channels => channels.ID))})") - .DistinctBy(c => c.ID); - return Ok(uniqueChannels); + string countSql = $@" + SELECT COUNT(*) + FROM ChannelDetail + WHERE ID in ({string.Join(", ", connectedChannels.Select(channels => channels.ID))}) + "; + + string sql = @$" + SELECT * + FROM ChannelDetail + WHERE ID in ({string.Join(", ", connectedChannels.Select(channels => channels.ID))}) + ORDER BY {postData.OrderBy} {(postData.Ascending ? "ASC" : "DESC")} + OFFSET {page * recordsPerPage} ROWS + FETCH NEXT {recordsPerPage} ROWS ONLY + "; + + int count = connection.ExecuteScalar(countSql); + + DataTable results = connection.RetrieveData(sql); + + return Ok(new PagedResults() + { + Data = JsonConvert.SerializeObject(results), + RecordsPerPage = recordsPerPage, + TotalRecords = count, + NumberOfPages = (count + recordsPerPage - 1) / recordsPerPage + }); } else { - return Ok(new List()); + return Ok(new PagedResults() + { + Data = JsonConvert.SerializeObject(new List()), // just return an empty list + RecordsPerPage = recordsPerPage, + TotalRecords = 0, + NumberOfPages = 0 + }); } } } catch (Exception ex) diff --git a/Source/Applications/SystemCenter/Controllers/OpenXDA/Assets/OpenXDALineController.cs b/Source/Applications/SystemCenter/Controllers/OpenXDA/Assets/OpenXDALineController.cs index e5afaa32cb..48628cab45 100644 --- a/Source/Applications/SystemCenter/Controllers/OpenXDA/Assets/OpenXDALineController.cs +++ b/Source/Applications/SystemCenter/Controllers/OpenXDA/Assets/OpenXDALineController.cs @@ -31,6 +31,7 @@ using GSF.Data; using GSF.Data.Model; using GSF.Web.Model; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using openXDA.Model; using SystemCenter.Model; @@ -94,6 +95,38 @@ record = record.Concat(new TableOperations(connection).QueryRecords } + [HttpPost, Route("{lineID:int}/LineSegments/{page:int}")] + public IHttpActionResult GetLineSegmentsForLinePaged([FromBody] PostData postData, [FromUri] int lineID, [FromUri] int page) + { + if (GetRoles == string.Empty || User.IsInRole(GetRoles)) + { + int recordsPerPage = PageSize ?? 50; + + using (AdoDataConnection connection = new AdoDataConnection(Connection)) + { + RecordRestriction restriction = new RecordRestriction(@"ID in (select ChildID from AssetRelationship where AssetRelationshipTypeID = (SELECT ID FROM AssetRelationshipType WHERE Name = 'Line-LineSegment') AND ParentID = {0}) + OR ID in (select ParentID from AssetRelationship where AssetRelationshipTypeID = (SELECT ID FROM AssetRelationshipType WHERE Name = 'Line-LineSegment') AND ChildID = {0})", lineID); + + TableOperations tbl = new TableOperations(connection); + + int count = tbl.QueryRecordCount(restriction); + + IEnumerable records = tbl.QueryRecords(postData.OrderBy, postData.Ascending, page + 1, recordsPerPage, restriction); + + return Ok(new PagedResults() + { + Data = JsonConvert.SerializeObject(records), + RecordsPerPage = recordsPerPage, + TotalRecords = count, + NumberOfPages = (count + recordsPerPage - 1) / recordsPerPage + }); + } + } + else + return Unauthorized(); + + } + public override IHttpActionResult Post([FromBody] JObject record) { if (PostRoles == string.Empty || User.IsInRole(PostRoles)) diff --git a/Source/Applications/SystemCenter/Controllers/OpenXDA/Meters/OpenXDAMeterConfigurationController.cs b/Source/Applications/SystemCenter/Controllers/OpenXDA/Meters/OpenXDAMeterConfigurationController.cs index 2e07048530..bbfe79f3fd 100644 --- a/Source/Applications/SystemCenter/Controllers/OpenXDA/Meters/OpenXDAMeterConfigurationController.cs +++ b/Source/Applications/SystemCenter/Controllers/OpenXDA/Meters/OpenXDAMeterConfigurationController.cs @@ -45,7 +45,7 @@ public class OpenXDAMeterConfigurationController : ModelController(countQuery, meterID); + DataTable channelTable = connection.RetrieveData(ChannelQuery, meterID); string channelJSON = JsonConvert.SerializeObject(channelTable); JArray channelArray = JArray.Parse(channelJSON); @@ -395,7 +402,13 @@ IEnumerable FilterChannels() } } - return Ok(FilterChannels()); + return Ok(new PagedResults() + { + Data = JsonConvert.SerializeObject(FilterChannels()), + TotalRecords = channelCount, + NumberOfPages = (channelCount + recordsPerPage - 1) / recordsPerPage, + RecordsPerPage = recordsPerPage + }); } diff --git a/Source/Applications/SystemCenter/Controllers/OpenXDA/OpenXDAControllers.cs b/Source/Applications/SystemCenter/Controllers/OpenXDA/OpenXDAControllers.cs index 3bf4f15680..d33619a1c8 100644 --- a/Source/Applications/SystemCenter/Controllers/OpenXDA/OpenXDAControllers.cs +++ b/Source/Applications/SystemCenter/Controllers/OpenXDA/OpenXDAControllers.cs @@ -111,7 +111,7 @@ public IHttpActionResult RecentFailures([FromBody] PostData postData, [FromUri] if (!GetAuthCheck()) return Unauthorized(); - int recordsPerPage = Take ?? 50; + int recordsPerPage = PageSize ?? 50; List param = new(); diff --git a/Source/Applications/SystemCenter/Controllers/OpenXDA/OpenXDALocationController.cs b/Source/Applications/SystemCenter/Controllers/OpenXDA/OpenXDALocationController.cs index 9038b85caf..bc943e78cc 100644 --- a/Source/Applications/SystemCenter/Controllers/OpenXDA/OpenXDALocationController.cs +++ b/Source/Applications/SystemCenter/Controllers/OpenXDA/OpenXDALocationController.cs @@ -217,7 +217,7 @@ public IHttpActionResult GetMetersForLocation(int locationID, int page, int asc, if (!string.IsNullOrEmpty(GetRoles) && User.IsInRole(GetRoles)) return Unauthorized(); - int recordsPerPage = 50; + int recordsPerPage = PageSize ?? 50; using (AdoDataConnection connection = new AdoDataConnection(Connection)) { int totalRecords = connection.ExecuteScalar($@" @@ -256,7 +256,7 @@ FROM Meter [HttpGet, Route("{locationID:int}/Assets/{page:int}/{asc:int}/{orderBy}")] public IHttpActionResult GetAssetsForLocation(int locationID, int page, int asc, string orderBy) { - int recordsPerPage = 50; + int recordsPerPage = PageSize ?? 50; if (!string.IsNullOrEmpty(GetRoles) && User.IsInRole(GetRoles)) return Unauthorized(); @@ -323,7 +323,7 @@ public IHttpActionResult GetImagesForLocation(int locationID, int page) if (Directory.Exists(Path.Combine(path, key))) { IEnumerable imagePaths = Directory.GetFiles(Path.Combine(path, key)).Select(fp => new FileInfo(fp).Name); - return Ok(PageImagePaths(imagePaths, page, Take ?? 50)); + return Ok(PageImagePaths(imagePaths, page, PageSize ?? 50)); } else return Ok(new PagedResults() @@ -331,7 +331,7 @@ public IHttpActionResult GetImagesForLocation(int locationID, int page) Data = JsonConvert.SerializeObject(new string[0]), TotalRecords = 0, NumberOfPages = 0, - RecordsPerPage = Take ?? 50 + RecordsPerPage = PageSize ?? 50 }); } else diff --git a/Source/Applications/SystemCenter/Controllers/SystemCenter/SystemCenterController.cs b/Source/Applications/SystemCenter/Controllers/SystemCenter/SystemCenterController.cs index 563d97416a..3b5ff0f9be 100644 --- a/Source/Applications/SystemCenter/Controllers/SystemCenter/SystemCenterController.cs +++ b/Source/Applications/SystemCenter/Controllers/SystemCenter/SystemCenterController.cs @@ -633,7 +633,7 @@ public IHttpActionResult GetAdditionalFieldsForTable(string openXDAParentTable, { string orderByExpression = DefaultSort; - int recordsPerPage = Take ?? 50; + int recordsPerPage = PageSize ?? 50; if (sort != null && sort != string.Empty) orderByExpression = $"{sort} {(ascending == 1 ? "ASC" : "DESC")}"; diff --git a/Source/Applications/SystemCenter/Model/DataFile.cs b/Source/Applications/SystemCenter/Model/DataFile.cs index cc3dd6d1c6..5aa8a88573 100644 --- a/Source/Applications/SystemCenter/Model/DataFile.cs +++ b/Source/Applications/SystemCenter/Model/DataFile.cs @@ -321,7 +321,7 @@ public override IHttpActionResult GetPagedList([FromBody] PostData postData, int }).ToArray(); int recordCount = CountSearchResults(postData); - int recordPerPage = Take ?? 50; + int recordPerPage = PageSize ?? 50; return Ok(new PagedResults() { Data = JsonConvert.SerializeObject(results), @@ -360,7 +360,7 @@ public override IHttpActionResult GetPagedList([FromBody] PostData postData, int }).ToArray(); int recordCount = CountSearchResults(postData); - int recordPerPage = Take ?? 50; + int recordPerPage = PageSize ?? 50; return Ok(new PagedResults() { Data = JsonConvert.SerializeObject(results), @@ -398,7 +398,7 @@ public override IHttpActionResult GetPagedList([FromBody] PostData postData, int }).ToArray(); int recordCount = CountSearchResults(postData); - int recordPerPage = Take ?? 50; + int recordPerPage = PageSize ?? 50; return Ok(new PagedResults() { Data = JsonConvert.SerializeObject(results), diff --git a/Source/Applications/SystemCenter/Model/DeviceHealthReport.cs b/Source/Applications/SystemCenter/Model/DeviceHealthReport.cs index b74dc2becf..a5a7bebc89 100644 --- a/Source/Applications/SystemCenter/Model/DeviceHealthReport.cs +++ b/Source/Applications/SystemCenter/Model/DeviceHealthReport.cs @@ -111,7 +111,6 @@ public class DeviceHealthReport [RoutePrefix("api/DeviceHealthReport")] public class DeviceHealthReportController : ModelController { - public int PagingAmount { get; set; } = 50; public class DailyStatisticsRecord { [PrimaryKey(true)] @@ -144,7 +143,7 @@ public override IHttpActionResult GetPagedList([FromBody] PostData postData, int { PagedResults pagedReports = new() { - RecordsPerPage = 50 + RecordsPerPage = PageSize ?? 50 }; PostData openMicRequestBody = new() diff --git a/Source/Applications/SystemCenter/Model/Node.cs b/Source/Applications/SystemCenter/Model/Node.cs index 2ecac66e82..4b94d86db6 100644 --- a/Source/Applications/SystemCenter/Model/Node.cs +++ b/Source/Applications/SystemCenter/Model/Node.cs @@ -30,7 +30,7 @@ namespace SystemCenter.Model { - [TableName("Node"), ReturnLimit(50), + [TableName("Node"), CustomView(@" SELECT Node.ID, diff --git a/Source/Applications/SystemCenter/Model/Security/SecurityGroup.cs b/Source/Applications/SystemCenter/Model/Security/SecurityGroup.cs index 2906643a64..96f286fd15 100644 --- a/Source/Applications/SystemCenter/Model/Security/SecurityGroup.cs +++ b/Source/Applications/SystemCenter/Model/Security/SecurityGroup.cs @@ -100,6 +100,40 @@ public IHttpActionResult GetUsers(string groupID) "(SELECT COUNT(ID) FROM SecurityGroupUserAccount WHERE SecurityGroupID = {0} AND UserAccountID = UserAccount.ID) > 0", groupID))); } + [HttpPost] + [Route("Users/PagedList/{groupID}/{page:int}")] + public IHttpActionResult GetPagedUsers([FromBody] PostData postData, [FromUri] String groupID, [FromUri] int page) + { + if (!GetAuthCheck()) + return Unauthorized(); + + String[] sortFields = { "Phone", "Email", "FirstName", "LastName", "AccountName" }; + if (!sortFields.Any(f => f.Equals(postData.OrderBy, StringComparison.OrdinalIgnoreCase))) + return BadRequest("Invalid 'OrderBy' field."); + + int recordsPerPage = PageSize ?? 50; + + using (AdoDataConnection connection = new AdoDataConnection(Connection)) + { + string sql = $@"SELECT UserAccount.*, UserAccount.Name as AccountName + FROM SecurityGroupUserAccount JOIN UserAccount ON UserAccountID = UserAccount.ID WHERE SecurityGroupID = {{0}} + ORDER BY {postData.OrderBy} {(postData.Ascending ? "ASC" : "DESC")} + OFFSET {page * recordsPerPage} ROWS FETCH NEXT {recordsPerPage} ROWS ONLY"; + string countSql = "SELECT COUNT(*) FROM SecurityGroupUserAccount JOIN UserAccount ON UserAccountID = UserAccount.ID WHERE SecurityGroupID = {0}"; + + DataTable results = connection.RetrieveData(sql, groupID.ToString()); + int count = connection.ExecuteScalar(countSql, groupID); + + return Ok(new PagedResults() + { + Data= JsonConvert.SerializeObject(results), + TotalRecords = count, + NumberOfPages = (count + recordsPerPage - 1) / recordsPerPage, + RecordsPerPage = recordsPerPage + }); + } + } + [HttpPost] [Route("{groupID}/PostRoles")] public IHttpActionResult PostGroupRoles([FromBody] IEnumerable record, string groupID) @@ -274,7 +308,7 @@ protected override DataTable GetSearchResults(PostData postData, int? page) if (page is int p) // page manually, because filtering post-search requires it. { - int recordsPerPage = Take ?? 50; + int recordsPerPage = PageSize ?? 50; DataRow[] rows = dataTable.AsEnumerable() .Skip((p) * recordsPerPage) .Take(recordsPerPage) diff --git a/Source/Applications/SystemCenter/Model/Security/UserAccount.cs b/Source/Applications/SystemCenter/Model/Security/UserAccount.cs index 438efbf736..d5ee735dc1 100644 --- a/Source/Applications/SystemCenter/Model/Security/UserAccount.cs +++ b/Source/Applications/SystemCenter/Model/Security/UserAccount.cs @@ -242,7 +242,7 @@ protected override DataTable GetSearchResults(PostData postData, int? page) if (page is int p)// page manually, because filtering post-search requires it. { - int recordsPerPage = Take ?? 50; + int recordsPerPage = PageSize ?? 50; DataRow[] rows = dataTable.AsEnumerable() .Skip((p) * recordsPerPage) .Take(recordsPerPage) diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Asset/AssetChannel.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Asset/AssetChannel.tsx index 61ae1afcfb..d9b6142b33 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Asset/AssetChannel.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Asset/AssetChannel.tsx @@ -23,9 +23,9 @@ import * as React from 'react'; import * as _ from 'lodash'; -import { Application, OpenXDA } from '@gpa-gemstone/application-typings'; +import { Application } from '@gpa-gemstone/application-typings'; import { PhaseSlice, MeasurmentTypeSlice } from '../Store/Store' -import { Table, Column } from '@gpa-gemstone/react-table'; +import { Table, Column, Paging } from '@gpa-gemstone/react-table'; import { useAppSelector } from '../hooks'; import { LoadingIcon, ServerErrorIcon } from '@gpa-gemstone/react-interactive'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; @@ -67,6 +67,10 @@ interface ChannelDetail { //TODO: Move to Gemstone const AssetChannelWindow = (props: IProps) => { const [assetChannels, setAssetChannels] = React.useState([]); + const [page, setPage] = React.useState(0); + const [totalPages, setTotalPages] = React.useState(0); + const [totalRecords, setTotalRecords] = React.useState(0); + const [recordsPerPage, setRecordsPerPage] = React.useState(0); const pStatus = useAppSelector(PhaseSlice.Status) as Application.Types.Status; const mtStatus = useAppSelector(MeasurmentTypeSlice.Status) as Application.Types.Status; @@ -75,35 +79,35 @@ const AssetChannelWindow = (props: IProps) => { const [ascending, setAscending] = React.useState(true); React.useEffect(() => { - let channelHandle = getChannels(); - - Promise.all([channelHandle]); - - return () => { - if (channelHandle != null && channelHandle.abort != null) - channelHandle.abort(); - } - }, [props.ID]); - - function getChannels(): JQuery.jqXHR { setStatus('loading'); - return $.ajax( + + const handle = $.ajax( { - type: "GET", - url: `${homePath}api/OpenXDA/Asset/${props.ID}/ConnectedChannels`, + type: "POST", + url: `${homePath}api/OpenXDA/Asset/${props.ID}/ConnectedChannels/${page}`, contentType: "application/json; charset=utf-A", dataType: 'json', cache: true, - async: true + async: true, + data: JSON.stringify({ OrderBy: sortField, Ascending: ascending }) } ).done( - (d: Array) => { - const sortedChannels = sortData(sortField, ascending, d); - setAssetChannels(sortedChannels) + (d) => { + setAssetChannels(JSON.parse(d.Data)) + setTotalPages(d.NumberOfPages); + setTotalRecords(d.TotalRecords); + setRecordsPerPage(d.RecordsPerPage); + if (page >= d.NumberOfPages) + setPage(Math.max(d.NumberOfPages - 1, 0)); setStatus('idle'); } ).fail(() => setStatus('error')); + + return () => { + if (handle != null && handle.abort != null) + handle.abort(); } + }, [props.ID, sortField, ascending, page]); function sortData(key: keyof ChannelDetail, ascending: boolean, data: ChannelDetail[]) { return _.orderBy(data, [key], [(ascending ? "asc" : "desc")]); @@ -153,8 +157,17 @@ const AssetChannelWindow = (props: IProps) => {

Channels:

+
+
+

+ {`Displaying Asset Channel(s) ${totalRecords > 0 ? (recordsPerPage * page + 1) : 0} - ${recordsPerPage * page + assetChannels.length} out of ${totalRecords}`} +

+
+
+
+
TableClass="table table-hover" Data={assetChannels} @@ -163,14 +176,10 @@ const AssetChannelWindow = (props: IProps) => { OnSort={(d) => { if (d.colKey == sortField) { setAscending(!ascending); - const ordered = _.orderBy(assetChannels, [d.colKey], [(!ascending ? "asc" : "desc")]); - setAssetChannels(ordered); } else { setAscending(true); setSortField(d.colField); - const ordered = _.orderBy(assetChannels, [d.colKey], ["asc"]); - setAssetChannels(ordered); } }} TableStyle={{ padding: 0, width: '100%', tableLayout: 'fixed', display: 'flex', flexDirection: 'column', overflow: 'hidden' }} @@ -244,6 +253,17 @@ const AssetChannelWindow = (props: IProps) => {
+
+
+
+ setPage(p - 1)} + /> +
+
+
); } diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetAttribute/LineSegmentWindow.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetAttribute/LineSegmentWindow.tsx index 021b807adf..03568e4528 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetAttribute/LineSegmentWindow.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetAttribute/LineSegmentWindow.tsx @@ -24,10 +24,9 @@ import * as React from 'react'; import * as _ from 'lodash'; import { Application, OpenXDA } from '@gpa-gemstone/application-typings'; -import { Table, Column } from '@gpa-gemstone/react-table'; +import { Table, Column, Paging } from '@gpa-gemstone/react-table'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; import LineSegmentWizard from './FawgLineSegmentWizard/LineSegmentWizard'; -import moment from 'moment'; import { useAppSelector } from '../hooks'; import { SelectRoles } from '../Store/UserSettings'; import { ToolTip } from '@gpa-gemstone/react-forms'; @@ -37,33 +36,38 @@ function LineSegmentWindow(props: IProps): JSX.Element { const [segments, setSegments] = React.useState>([]); const [sortKey, setSortKey] = React.useState('AssetName'); const [ascending, setAscending] = React.useState(true); + const [page, setPage] = React.useState(0); + const [totalPages, setTotalPages] = React.useState(0); + const [totalRecords, setTotalRecords] = React.useState(0); + const [recordsPerPage, setRecordsPerPage] = React.useState(0); + const [refreshTrigger, setRefreshTrigger] = React.useState(false); + const [segmentStatus, setSegmentStatus] = React.useState('uninitiated'); const [showFawg, setShowFawg] = React.useState(false); const [hover, setHover] = React.useState<('Update' | 'Reset' | 'None')>('None'); const roles = useAppSelector(SelectRoles); React.useEffect(() => { - const h = getSegments(); - return () => { if (h != null && h.abort != null) h.abort(); } - }, [props.ID]); - - function getSegments() { - return $.ajax({ - type: "GET", - url: `${homePath}api/OpenXDA/Line/${props.ID}/LineSegments?_=${moment()}`, + setSegmentStatus('loading'); + const h = $.ajax({ + type: "POST", + url: `${homePath}api/OpenXDA/Line/${props.ID}/LineSegments/${page}`, contentType: "application/json; charset=utf-8", dataType: 'json', cache: false, - async: true - }).done((data: Array) => { - const sortedSegments = sortData(sortKey, ascending, data); - setSegments(sortedSegments) + async: true, + data: JSON.stringify({ orderBy: sortKey, ascending: ascending, searches: [] }) + }).done((d) => { + setTotalPages(d.NumberOfPages); + setTotalRecords(d.TotalRecords); + setRecordsPerPage(d.RecordsPerPage); + if (page >= d.NumberOfPages) + setPage(Math.max(d.NumberOfPages - 1, 0)); + setSegments(JSON.parse(d.Data)) + setSegmentStatus('idle'); props.OnChange(); - }); - } - - function sortData(key: string, ascending: boolean, data: OpenXDA.Types.LineSegment[]) { - return _.orderBy(data, [key], [(ascending ? "asc" : "desc")]); - } + }).fail(() => setSegmentStatus('error')); + return () => { if (h != null && h.abort != null) h.abort(); } + }, [props.ID, page, ascending, sortKey, refreshTrigger]); function hasPermissions(): boolean { if (roles.indexOf('Administrator') < 0 && roles.indexOf('Engineer') < 0) @@ -71,7 +75,22 @@ function LineSegmentWindow(props: IProps): JSX.Element { return true; } - let header = (

{"Line Segments: "}

); + let header = ( <> +
+
+

{"Line Segments: "}

+
+
+
+
+

+ {segmentStatus === 'error' ? 'Could not complete Search' : + segmentStatus === 'loading' ? 'Loading...' : + `Displaying Line Segment(s) ${totalRecords > 0 ? (recordsPerPage * page + 1) : 0} - ${recordsPerPage * page + segments.length} out of ${totalRecords}`} +

+
+
+ ) const tableContent = ( <> @@ -82,14 +101,10 @@ function LineSegmentWindow(props: IProps): JSX.Element { OnSort={(d) => { if (d.colKey == sortKey) { setAscending(!ascending); - const ordered = _.orderBy(segments, [d.colKey], [(!ascending ? "asc" : "desc")]); - setSegments(ordered); } else { setAscending(true); setSortKey(d.colField); - const ordered = _.orderBy(segments, [d.colKey], ["asc"]); - setSegments(ordered); } }} TableStyle={{ padding: 0, width: '100%', tableLayout: 'fixed', display: 'flex', flexDirection: 'column', overflow: 'hidden' }} @@ -181,7 +196,7 @@ function LineSegmentWindow(props: IProps): JSX.Element { > End? - {showFawg ? { setShowFawg(false); getSegments(); }} /> : null} + {showFawg ? { setShowFawg(false); setRefreshTrigger(val => !val)}} /> : null} ); const wizardButton = (); @@ -199,8 +214,21 @@ function LineSegmentWindow(props: IProps): JSX.Element {
{header}
-
+
+
+
{tableContent} +
+
+
+
+ setPage(p -1)} + /> +
+
diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetAttribute/SourceImpedanceWindow.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetAttribute/SourceImpedanceWindow.tsx index fd76a590b8..21f7477609 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetAttribute/SourceImpedanceWindow.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetAttribute/SourceImpedanceWindow.tsx @@ -24,20 +24,19 @@ import * as React from 'react'; import * as _ from 'lodash'; import { Application, OpenXDA } from '@gpa-gemstone/application-typings'; -import { AssetAttributes } from './Asset'; -import LineSegmentAttributes from './LineSegment'; -import { LoadingScreen, Modal, Warning, Search, ServerErrorIcon } from '@gpa-gemstone/react-interactive'; -import { Table, Column } from '@gpa-gemstone/react-table'; +import { LoadingScreen, Modal, Warning, Search, ServerErrorIcon, GenericController } from '@gpa-gemstone/react-interactive'; +import { Table, Column, Paging } from '@gpa-gemstone/react-table'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; -import moment from 'moment'; import { useAppDispatch, useAppSelector } from '../hooks'; -import { LocationSlice, SourceImpedanceSlice } from '../Store/Store'; -import { IsInteger, IsNumber } from '@gpa-gemstone/helper-functions'; +import { LocationSlice } from '../Store/Store'; +import { IsNumber } from '@gpa-gemstone/helper-functions'; import { Input, Select, ToolTip } from '@gpa-gemstone/react-forms'; import { SelectRoles } from '../Store/UserSettings'; const newImpedance: OpenXDA.Types.SourceImpedance = { RSrc: 0, XSrc: 0, AssetLocationID: null, ID: 0 } +const sourceImpedanceController = new GenericController(`${homePath}api/OpenXDA/SourceImpedance`, "AssetLocationID", false); + function SourceImpedanceWindow(props: { ID: number }): JSX.Element { const dispatch = useAppDispatch(); const locations = useAppSelector(LocationSlice.Data); @@ -46,13 +45,18 @@ function SourceImpedanceWindow(props: { ID: number }): JSX.Element { const [assetLocations, setAssetLocations] = React.useState([]); const [aLStatus, setALStatus] = React.useState('uninitiated'); - const sourceImpedances = useAppSelector(SourceImpedanceSlice.SearchResults); - const sourceImpedanceStatus = useAppSelector(SourceImpedanceSlice.SearchStatus); + const [sourceImpedances, setSourceImpedances] = React.useState([]); + const [sourceImpedanceStatus, setSourceImpedanceStatus] = React.useState('uninitiated'); const [ascending, setAscending] = React.useState(true); const [sortKey, setSortKey] = React.useState('AssetLocationID'); - const [data, setData] = React.useState([]); + const [page, setPage] = React.useState(0); + const [totalPages, setTotalPages] = React.useState(0); + const [totalRecords, setTotalRecords] = React.useState(0); + const [recordsPerPage, setRecordsPerPage] = React.useState(0); + + const [refreshTrigger, setRefreshTrigger] = React.useState(false); const [showAdd, setShowAdd] = React.useState(false); const [showWarning, setshowWarning] = React.useState(false); @@ -65,16 +69,29 @@ function SourceImpedanceWindow(props: { ID: number }): JSX.Element { //#ToDo Swap Type to query React.useEffect(() => { + setSourceImpedanceStatus('loading'); const filter = [ { FieldName: "AssetLocationID", Operator: "IN", Type: "query", IsPivotColumn: false, SearchText: `(SELECT ID FROM AssetLocation WHERE AssetID=${props.ID})` } ] as Search.IFilter[] - dispatch(SourceImpedanceSlice.DBSearch({ filter })) - }, [props.ID]); - React.useEffect(() => { - const sortedData = sortData(sortKey, ascending, sourceImpedances); - setData(sortedData); - }, [sourceImpedances]); + const handle = sourceImpedanceController.PagedSearch(filter, sortKey, ascending, page); + + handle.done((d) => { + setSourceImpedances(JSON.parse(d.Data as unknown as string)); + setTotalPages(d.NumberOfPages); + setTotalRecords(d.TotalRecords); + setRecordsPerPage(d.RecordsPerPage); + if (page >= d.NumberOfPages) + setPage(Math.max(d.NumberOfPages - 1, 0)); + setSourceImpedanceStatus('idle'); + }) + + handle.fail(() => setSourceImpedanceStatus('error')) + + return () => { + if (handle != null && handle.abort != null) handle.abort(); + } + }, [props.ID, sortKey, ascending, sourceImpedanceController, page, refreshTrigger]); React.useEffect(() => { const h = getAssetLocations(props.ID); @@ -86,15 +103,6 @@ function SourceImpedanceWindow(props: { ID: number }): JSX.Element { dispatch(LocationSlice.Fetch()); }, [locationStatus]) - React.useEffect(() => { - if (sourceImpedanceStatus == 'changed' || sourceImpedanceStatus == 'uninitiated') { - const filter = [ - { FieldName: "AssetLocationID", Operator: "IN", Type: "query", IsPivotColumn: false, SearchText: `(SELECT ID FROM AssetLocation WHERE AssetID=${props.ID})` } - ] as Search.IFilter[] - dispatch(SourceImpedanceSlice.DBSearch({ filter })) - } - }, [sourceImpedanceStatus]) - function getAssetLocations(assetID: number) { setALStatus('loading'); return $.ajax({ @@ -111,10 +119,6 @@ function SourceImpedanceWindow(props: { ID: number }): JSX.Element { }); } - function sortData(key: keyof OpenXDA.Types.SourceImpedance, ascending: boolean, data: OpenXDA.Types.SourceImpedance[]) { - return _.orderBy(data, [key], [(ascending ? "asc" : "desc")]); - } - function getLocationName(si: OpenXDA.Types.SourceImpedance) { const al = assetLocations.find(al => al.ID == si.AssetLocationID); if (al == null) @@ -169,25 +173,32 @@ function SourceImpedanceWindow(props: { ID: number }): JSX.Element { <>
-

Line Source Impedances:

-
-
+
+

Line Source Impedances:

+
+
+
+

+ {sourceImpedanceStatus === 'loading' ? 'Loading...' : + `Displaying Source Impedance(s) ${totalRecords > 0 ? (recordsPerPage * page + 1) : 0} - ${recordsPerPage * page + sourceImpedances.length} out of ${totalRecords}`} +

+
+
+
+
+
TableClass="table table-hover" - Data={data} + Data={sourceImpedances} SortKey={sortKey} Ascending={ascending} OnSort={(d) => { if (d.colKey == sortKey) { setAscending(!ascending); - const ordered = _.orderBy(data, [d.colKey], [(!ascending ? "asc" : "desc")]); - setData(ordered); } else { setAscending(true); setSortKey(d.colKey as keyof OpenXDA.Types.SourceImpedance); - const ordered = _.orderBy(data, [d.colKey], ["asc"]); - setData(ordered); } }} TableStyle={{ padding: 0, width: '100%', tableLayout: 'fixed', display: 'flex', flexDirection: 'column', overflow: 'hidden', height: '100%'}} @@ -247,6 +258,16 @@ function SourceImpedanceWindow(props: { ID: number }): JSX.Element { >

+
+
+
+ setPage(p - 1)} + Current={page + 1 } + /> +
+
@@ -260,13 +281,13 @@ function SourceImpedanceWindow(props: { ID: number }): JSX.Element {
{ if (confirm) dispatch(SourceImpedanceSlice.DBAction({ verb: 'DELETE', record: newEditImpedance })); setshowWarning(false); }} /> + CallBack={(confirm) => { if (confirm) sourceImpedanceController.DBAction('DELETE', newEditImpedance).then(() => setRefreshTrigger(val => !val)); setshowWarning(false); }} /> { if (confirm && newEdit == 'Edit') - dispatch(SourceImpedanceSlice.DBAction({ verb: 'PATCH', record: newEditImpedance })); + sourceImpedanceController.DBAction('PATCH', newEditImpedance).then(() => setRefreshTrigger(val => !val)); if (confirm && newEdit == 'New') - dispatch(SourceImpedanceSlice.DBAction({ verb: 'POST', record: newEditImpedance })); + sourceImpedanceController.DBAction('POST', newEditImpedance).then(() => setRefreshTrigger(val => !val)); setShowAdd(false); }} CancelText={'Close'} diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetGroups/AssetAssetGroup.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetGroups/AssetAssetGroup.tsx index 2ca4d51c7a..21b91112e5 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetGroups/AssetAssetGroup.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetGroups/AssetAssetGroup.tsx @@ -25,9 +25,9 @@ import * as React from 'react'; import * as _ from 'lodash'; import { useNavigate } from 'react-router-dom'; -import { Table, Column } from '@gpa-gemstone/react-table'; -import { AssetGroupSlice, AssetTypeSlice } from '../Store/Store'; -import { SystemCenter } from '@gpa-gemstone/application-typings'; +import { Table, Column, Paging } from '@gpa-gemstone/react-table'; +import { AssetTypeSlice } from '../Store/Store'; +import { Application, SystemCenter } from '@gpa-gemstone/application-typings'; import { Warning } from '@gpa-gemstone/react-interactive'; import { ToolTip } from '@gpa-gemstone/react-forms'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; @@ -43,9 +43,13 @@ function AssetAssetGroupWindow(props: { AssetGroupID: number}) { const [sortKey, setSortKey] = React.useState('AssetName'); const [ascending, setAscending] = React.useState(true); const [showAdd, setShowAdd] = React.useState(false); - const [counter, setCounter] = React.useState(0); const [removeAsset, setRemoveAsset] = React.useState(-1); - + const [page, setPage] = React.useState(0); + const [totalPages, setTotalPages] = React.useState(0); + const [totalRecords, setTotalRecords] = React.useState(0); + const [recordsPerPage, setRecordsPerPage] = React.useState(0); + const [assetStatus, setAssetStatus] = React.useState('uninitiated'); + const [refreshTrigger, setRefreshTrigger] = React.useState(false); const assetType = useAppSelector(AssetTypeSlice.Data); const assetTypeStatus = useAppSelector(AssetTypeSlice.Status); const dispatch = useAppDispatch(); @@ -54,42 +58,43 @@ function AssetAssetGroupWindow(props: { AssetGroupID: number}) { const roles = useAppSelector(SelectRoles); React.useEffect(() => { - dispatch(AssetGroupSlice.SetChanged()); - return getData(); - }, [props.AssetGroupID, counter]); - - React.useEffect(() => { - if (assetTypeStatus == 'changed' || assetTypeStatus == 'uninitiated') - dispatch(AssetTypeSlice.Fetch()); - }, [assetTypeStatus]); - - function getData() { if (props.AssetGroupID == null) return () => { }; + setAssetStatus('loading'); + let handle = $.ajax({ - type: "GET", - url: `${homePath}api/OpenXDA/AssetGroup/${props.AssetGroupID}/Assets`, + type: "POST", + url: `${homePath}api/OpenXDA/AssetGroup/${props.AssetGroupID}/Assets/${page}`, contentType: "application/json; charset=utf-8", dataType: 'json', cache: false, - async: true + async: true, + data: JSON.stringify({OrderBy: sortKey, Ascending: ascending}) }) - handle.done((data: Array) => { - const sortedData = sortData(sortKey, ascending, data); - setAssetList(sortedData); + handle.done((d) => { + setAssetList(JSON.parse(d.Data as unknown as string)); + setTotalPages(d.NumberOfPages); + setTotalRecords(d.TotalRecords); + setRecordsPerPage(d.RecordsPerPage); + if (page >= d.NumberOfPages) + setPage(Math.max(d.NumberOfPages - 1, 0)); + setAssetStatus('idle'); }); + handle.fail(() => setAssetStatus('error')) + return function cleanup() { if (handle.abort != null) handle.abort(); } - } + }, [props.AssetGroupID, refreshTrigger, ascending, page, sortKey]); - function sortData(key: string, ascending: boolean, data: SystemCenter.Types.DetailedAsset[]) { - return _.orderBy(data, [key], [(ascending ? "asc" : "desc")]); - } + React.useEffect(() => { + if (assetTypeStatus == 'changed' || assetTypeStatus == 'uninitiated') + dispatch(AssetTypeSlice.Fetch()); + }, [assetTypeStatus]); function saveItems(items: SystemCenter.Types.DetailedAsset[]) { @@ -103,7 +108,7 @@ function AssetAssetGroupWindow(props: { AssetGroupID: number}) { data: JSON.stringify(items.map(e => e.ID)) }); - handle.done(d => setCounter(x => x + 1)) + handle.done(() => setRefreshTrigger(val => !val)) } @@ -118,7 +123,7 @@ function AssetAssetGroupWindow(props: { AssetGroupID: number}) { async: true }); - handle.done(d => setCounter(x => x + 1)) + handle.done(() => setRefreshTrigger(val => !val)) } function hasPermissions(): boolean { @@ -141,9 +146,18 @@ function AssetAssetGroupWindow(props: { AssetGroupID: number}) {
-
-
-
+
+
+

+ {assetStatus === 'error' ? 'Could not complete Search' : + assetStatus === 'loading' ? 'Loading...' : + `Displaying Asset(s) ${totalRecords > 0 ? (recordsPerPage * page + 1) : 0} - ${recordsPerPage * page + assetList.length} out of ${totalRecords}`} +

+
+
+
+
+
TableClass="table table-hover" Data={assetList} @@ -155,14 +169,10 @@ function AssetAssetGroupWindow(props: { AssetGroupID: number}) { if (d.colKey === sortKey) { setAscending(!ascending); - const ordered = _.orderBy(assetList, [d.colKey], [(!ascending ? "asc" : "desc")]); - setAssetList(ordered); } else { setAscending(true); setSortKey(d.colKey); - const ordered = _.orderBy(assetList, [d.colKey], ["asc"]); - setAssetList(ordered); } }} OnClick={handleSelect} @@ -216,6 +226,15 @@ function AssetAssetGroupWindow(props: { AssetGroupID: number}) {
+
+
+ setPage(p - 1) } + /> +
+
diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetGroups/AssetGroupAssetGroup.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetGroups/AssetGroupAssetGroup.tsx index 9faff924ad..d0e2e643c0 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetGroups/AssetGroupAssetGroup.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/AssetGroups/AssetGroupAssetGroup.tsx @@ -24,15 +24,15 @@ import * as React from 'react'; import * as _ from 'lodash'; -import { OpenXDA } from '@gpa-gemstone/application-typings'; +import { Application, OpenXDA } from '@gpa-gemstone/application-typings'; import { useNavigate } from 'react-router-dom'; -import { Table, Column } from '@gpa-gemstone/react-table'; +import { Table, Column, Paging } from '@gpa-gemstone/react-table'; import { AssetGroupSlice } from '../Store/Store'; import { DefaultSelects } from '@gpa-gemstone/common-pages'; import { Search, Warning } from '@gpa-gemstone/react-interactive'; import { ToolTip } from '@gpa-gemstone/react-forms'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; -import { useAppDispatch, useAppSelector } from '../hooks'; +import { useAppSelector } from '../hooks'; import { SelectRoles } from '../Store/UserSettings'; declare var homePath: string; @@ -40,13 +40,17 @@ declare var homePath: string; function AssetGroupAssetGroupWindow(props: { AssetGroupID: number}) { let navigate = useNavigate(); - const dispatch = useAppDispatch(); const [groupList, setGroupList] = React.useState>([]); + const [groupStatus, setGroupStatus] = React.useState('uninitiated'); const [sortField, setSortField] = React.useState('Name'); const [ascending, setAscending] = React.useState(true); const [showAdd, setShowAdd] = React.useState(false); - const [counter, setCounter] = React.useState(0); const [removeGroup, setRemoveGroup] = React.useState(-1); + const [page, setPage] = React.useState(0); + const [totalPages, setTotalPages] = React.useState(0); + const [totalRecords, setTotalRecords] = React.useState(0); + const [recordsPerPage, setRecordsPerPage] = React.useState(0); + const [refreshTrigger, setRefreshTrigger] = React.useState(false); const [hover, setHover] = React.useState<('Update' | 'Reset' | 'None')>('None'); const roles = useAppSelector(SelectRoles); @@ -61,37 +65,36 @@ function AssetGroupAssetGroupWindow(props: { AssetGroupID: number}) { }; React.useEffect(() => { - dispatch(AssetGroupSlice.SetChanged()); - return getData(); - }, [props.AssetGroupID, counter]); - function getData() { - if (props.AssetGroupID == null) - return () => { }; + setGroupStatus('loading'); let handle = $.ajax({ - type: "GET", - url: `${homePath}api/OpenXDA/AssetGroup/${props.AssetGroupID}/AssetGroups`, + type: "POST", + url: `${homePath}api/OpenXDA/AssetGroup/${props.AssetGroupID}/AssetGroups/${page}`, contentType: "application/json; charset=utf-8", dataType: 'json', cache: false, - async: true + async: true, + data: JSON.stringify({ OrderBy: sortField, Ascending: ascending }) }); - handle.done((data: Array) => { - const sortedData = sortData(sortField, ascending, data); - setGroupList(sortedData); + handle.done((d) => { + setGroupList(JSON.parse(d.Data)); + setTotalPages(d.NumberOfPages); + setTotalRecords(d.TotalRecords); + setRecordsPerPage(d.RecordsPerPage); + if (page >= d.NumberOfPages) + setPage(Math.max(d.NumberOfPages - 1, 0)); + setGroupStatus('idle'); }); + handle.fail(() => setGroupStatus('error')) + return function cleanup() { if (handle.abort != null) handle.abort(); } - } - - function sortData(key: string, ascending: boolean, data: OpenXDA.Types.AssetGroup[]) { - return _.orderBy(data, [key], [(ascending ? "asc" : "desc")]); - } + }, [props.AssetGroupID, refreshTrigger, page, sortField, ascending]); function getEnum(setOptions, field) { let handle = null; @@ -123,7 +126,7 @@ function AssetGroupAssetGroupWindow(props: { AssetGroupID: number}) { async: true }); - handle.done(d => setCounter(x => x + 1)) + handle.done(() => setRefreshTrigger(val => !val)) } function saveItems(items: OpenXDA.Types.AssetGroup[]) { @@ -138,7 +141,7 @@ function AssetGroupAssetGroupWindow(props: { AssetGroupID: number}) { data: JSON.stringify(items.map(e => e.ID)) }); - handle.done(d => setCounter(x => x + 1)) + handle.done(() => setRefreshTrigger(val => !val)) } @@ -158,9 +161,18 @@ function AssetGroupAssetGroupWindow(props: { AssetGroupID: number}) {

Asset Groups in Asset Group:

-
-
-
+
+
+

+ {groupStatus === 'error' ? 'Could not complete Search' : + groupStatus === 'loading' ? 'Loading...' : + `Displaying Subgroups(s) ${totalRecords > 0 ? (recordsPerPage * page + 1) : 0} - ${recordsPerPage * page + groupList.length} out of ${totalRecords}`} +

+
+
+
+
+
TableClass="table table-hover" Data={groupList} @@ -169,14 +181,10 @@ function AssetGroupAssetGroupWindow(props: { AssetGroupID: number}) { OnSort={(d) => { if (d.colKey == sortField) { setAscending(!ascending); - const ordered = _.orderBy(groupList, [d.colKey], [(!ascending ? "asc" : "desc")]); - setGroupList(ordered); } else { setAscending(true); setSortField(d.colField); - const ordered = _.orderBy(groupList, [d.colKey], ["asc"]); - setGroupList(ordered); } }} OnClick={(data) => { navigate(`${homePath}index.cshtml?name=AssetGroup&AssetGroupID=${data.row.ID}`); }} @@ -234,7 +242,15 @@ function AssetGroupAssetGroupWindow(props: { AssetGroupID: number}) {
- +
+
+ setPage(p - 1) } + /> +
+
+
+
+

+ {meterStatus === 'error' ? 'Could not complete Search' : + meterStatus === 'loading' ? 'Loading...' : + `Displaying Meter(s) ${totalRecords > 0 ? (recordsPerPage * page + 1) : 0} - ${recordsPerPage * page + meterList.length} out of ${totalRecords}`} +

-
-
+
+
+
+
TableClass="table table-hover" Data={meterList} @@ -193,14 +204,10 @@ function MeterAssetGroupWindow(props: { AssetGroupID: number}) { if (d.colKey == 'Remove') return; if (d.colKey == sortField) { setAscending(!ascending); - const ordered = _.orderBy(meterList, [d.colKey], [(!ascending ? "asc" : "desc")]); - setMeterList(ordered); } else { setAscending(true); setSortField(d.colField); - const ordered = _.orderBy(meterList, [d.colKey], ["asc"]); - setMeterList(ordered); } }} OnClick={handleSelect} @@ -246,7 +253,15 @@ function MeterAssetGroupWindow(props: { AssetGroupID: number}) {
- +
+
+ setPage(p - 1) } + /> +
+
diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/ChannelScaling/ChannelScalingForm.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/ChannelScaling/ChannelScalingForm.tsx index 239de2c326..e2ca6f1f8b 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/ChannelScaling/ChannelScalingForm.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/ChannelScaling/ChannelScalingForm.tsx @@ -29,7 +29,7 @@ import { Application, OpenXDA } from '@gpa-gemstone/application-typings'; import { useAppSelector, useAppDispatch } from '../../hooks'; import { MeasurementCharacteristicSlice, MeasurmentTypeSlice, PhaseSlice } from '../../Store/Store'; import { LoadingIcon, ServerErrorIcon } from '@gpa-gemstone/react-interactive'; -import { Table, Column } from '@gpa-gemstone/react-table'; +import { Table, Column, Paging } from '@gpa-gemstone/react-table'; import { ChannelScalingWrapper, ChannelScalingType, IMultiplier } from './ChannelScalingWrapper'; import { Input, ToolTip } from '@gpa-gemstone/react-forms'; import { SelectRoles } from '../../Store/UserSettings'; @@ -40,7 +40,10 @@ interface IProps { Channels: OpenXDA.Types.Channel[], UpdateChannels: (channels: OpenXDA.Types.Channel[]) => void, ChannelStatus?: Application.Types.Status, - Key?: string + Key?: string, + Page?: number, + SetPage?: React.Dispatch>, + TotalPages?: number } @@ -270,6 +273,15 @@ const ChannelScalingForm = (props: IProps) => { > If Adjusted + {props.Page != null && props.SetPage != null && props.TotalPages != null ? +
+
+ props.SetPage(p - 1)} /> +
+
: null}
diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/ChannelScaling/ChannelScalingWindow.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/ChannelScaling/ChannelScalingWindow.tsx index 63fc4a8a38..e19d9ef285 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/ChannelScaling/ChannelScalingWindow.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/ChannelScaling/ChannelScalingWindow.tsx @@ -27,9 +27,6 @@ import * as React from 'react'; import * as _ from 'lodash'; import { Application, OpenXDA } from '@gpa-gemstone/application-typings'; import ChannelScalingForm from './ChannelScalingForm'; -import { TrendChannelSlice } from '../../Store/Store'; -import { useAppDispatch } from '../../hooks'; -import { SetChanged } from '../../Store/EventChannelSlice'; declare let homePath: string; @@ -41,18 +38,19 @@ interface IProps { const ChannelScalingWindow = (props: IProps) => { const [status, setStatus] = React.useState('uninitiated'); const [channels, setChannels] = React.useState([]); - const dispatch = useAppDispatch(); + const [page, setPage] = React.useState(0); + const [pageInfo, setPageInfo] = React.useState<{ TotalPages: number, TotalRecords: number, RecordsPerPage: number }>({ TotalPages: 0, TotalRecords: 0, RecordsPerPage: 0 }) React.useEffect(() => { if (props.IsVisible) - return loadChannels(); - }, [props.IsVisible]); + return loadChannels(page); + }, [props.IsVisible, page]); - function loadChannels() { + function loadChannels(page: number) { setStatus('loading'); const handle = $.ajax({ type: "GET", - url: `${homePath}api/OpenXDA/Meter/${props.Meter.ID}/Channels`, + url: `${homePath}api/OpenXDA/Meter/${props.Meter.ID}/Channels/${page}`, contentType: "application/json; charset=utf-8", dataType: "json", cache: false, @@ -60,8 +58,11 @@ const ChannelScalingWindow = (props: IProps) => { }); handle.done((d) => { - setChannels(d); + setChannels(JSON.parse(d.Data)); setStatus('idle'); + setPageInfo({ TotalPages: d.NumberOfPages, TotalRecords: d.TotalRecords, RecordsPerPage: d.RecordsPerPage }) + if (page >= d.NumberOfPages) + setPage(Math.max(d.NumberOfPages - 1, 0)); }); handle.fail(() => { setStatus('error'); }); @@ -84,9 +85,6 @@ const ChannelScalingWindow = (props: IProps) => { h.done(() => { setStatus('idle'); setChannels(channels); - dispatch(TrendChannelSlice.SetChanged()); - // This one is for event channels - dispatch(SetChanged()); }); h.fail(() => setStatus('error')); @@ -100,8 +98,17 @@ const ChannelScalingWindow = (props: IProps) => {

Channel Scaling:

+
+
+

+ {status === 'error' ? 'Could not complete Search' : + status === 'loading' ? 'Loading...' : + `Displaying Channel(s) ${pageInfo.TotalRecords > 0 ? (pageInfo.RecordsPerPage * page + 1) : 0} - ${pageInfo.RecordsPerPage * page + channels.length} out of ${pageInfo.TotalRecords}`} +

+
+
- +
); } diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/MeterEventChannel.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/MeterEventChannel.tsx index 211787dd67..2d1f4e1f4b 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/MeterEventChannel.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/MeterEventChannel.tsx @@ -26,16 +26,14 @@ import * as _ from 'lodash'; import { Application, OpenXDA as GemstoneOpenXDA} from '@gpa-gemstone/application-typings'; import { PhaseSlice, MeasurmentTypeSlice } from '../Store/Store' import { useAppSelector, useAppDispatch } from '../hooks'; -import { LoadingIcon, ServerErrorIcon, Warning } from '@gpa-gemstone/react-interactive'; +import { LoadingIcon, ServerErrorIcon, Warning, GenericController } from '@gpa-gemstone/react-interactive'; import { Input, Select, ToolTip } from '@gpa-gemstone/react-forms'; import { AssetAttributes } from '../AssetAttribute/Asset'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; import { OpenXDA } from '../global'; -import { SelectAscending, SelectSortKey, SelectEventChannels, SelectEventChannelStatus, SelectMeterID, dBAction } from '../Store/EventChannelSlice'; -import { FetchChannels } from '../Store/EventChannelSlice'; import { IsNumber } from '@gpa-gemstone/helper-functions'; import { cloneDeep } from 'lodash'; -import { ConfigurableTable, ConfigurableColumn, Column } from '@gpa-gemstone/react-table'; +import { ConfigurableTable, ConfigurableColumn, Column, Paging } from '@gpa-gemstone/react-table'; import { SelectRoles } from '../Store/UserSettings'; declare var homePath: string; @@ -43,16 +41,21 @@ declare var homePath: string; interface IProps { Meter: GemstoneOpenXDA.Types.Meter, IsVisible: boolean } type RecordChange = Map>; +const eventChannelController = new GenericController(`${homePath}api/OpenXDA/EventChannel`, "Name"); + const MeterEventChannelWindow = (props: IProps) => { const dispatch = useAppDispatch(); - const data = useAppSelector(SelectEventChannels); - const sortKey = useAppSelector(SelectSortKey) - const ascending = useAppSelector(SelectAscending) - const status = useAppSelector(SelectEventChannelStatus); - const meterID = useAppSelector(SelectMeterID); - + const [data, setData] = React.useState([]); + const [sortKey, setSortKey] = React.useState('Name'); + const [ascending, setAscending] = React.useState(false); + const [page, setPage] = React.useState(0); + const [totalPages, setTotalPages] = React.useState(0); + const [totalRecords, setTotalRecords] = React.useState(0); + const [recordsPerPage, setRecordsPerPage] = React.useState(0); const [recordChanges, setRecordChanges] = React.useState(new Map>()); + const [status, setStatus] = React.useState('uninitiated'); + const [refreshTrigger, setRefreshTrigger] = React.useState(false); const phases = useAppSelector(PhaseSlice.Data) as GemstoneOpenXDA.Types.Phase[]; const measurementTypes = useAppSelector(MeasurmentTypeSlice.Data) as GemstoneOpenXDA.Types.MeasurementType[]; @@ -68,8 +71,6 @@ const MeterEventChannelWindow = (props: IProps) => { const [hover, setHover] = React.useState<('Update' | 'Reset' | 'None' | 'Add')>('None'); const roles = useAppSelector(SelectRoles); - - React.useEffect(() => { if (pStatus == 'uninitiated' || pStatus == 'changed') dispatch(PhaseSlice.Fetch()); @@ -81,9 +82,19 @@ const MeterEventChannelWindow = (props: IProps) => { }, [mtStatus]) React.useEffect(() => { - if (status == 'uninitiated' || meterID !== props.Meter.ID || status == 'changed') - dispatch(FetchChannels({ meterId: props.Meter.ID })); - }, [props.Meter,status]) + setStatus('loading'); + const handle = eventChannelController.PagedSearch([], sortKey, ascending, page, props.Meter.ID); + handle.done((d) => { + setData(JSON.parse(d.Data as unknown as string)); + setTotalPages(d.NumberOfPages); + setTotalRecords(d.TotalRecords); + setRecordsPerPage(d.RecordsPerPage); + if (page >= d.NumberOfPages) + setPage(Math.max(d.NumberOfPages - 1, 0)); + setStatus('idle'); + }).fail(() => setStatus('error')) + return () => { if (handle != null && handle.abort != null) handle.abort() } + }, [props.Meter, sortKey, ascending, page, eventChannelController, refreshTrigger]) React.useEffect(() => { if (!props.IsVisible) @@ -151,7 +162,7 @@ const MeterEventChannelWindow = (props: IProps) => { for (let k of recordChanges.get(id).keys()) { original[k] = (recordChanges.get(id).get(k as keyof OpenXDA.EventChannel)) as any } - dispatch(dBAction({ record: original, verb: 'PATCH' })); + eventChannelController.DBAction("PATCH", original).then(() => setRefreshTrigger(val => !val)); } setRecordChanges(new Map>()); @@ -206,6 +217,13 @@ const MeterEventChannelWindow = (props: IProps) => {

Event Channels:

+
+
+

+ {'Could not complete Search'} +

+
+
@@ -224,6 +242,13 @@ const MeterEventChannelWindow = (props: IProps) => {

Event Channels:

+
+
+

+ {'Loading...'} +

+
+
@@ -242,9 +267,16 @@ const MeterEventChannelWindow = (props: IProps) => {

Event Channels:

+
+
+

+ {`Displaying Event Channel(s) ${totalRecords > 0 ? (recordsPerPage * page + 1) : 0} - ${recordsPerPage * page + data.length} out of ${totalRecords}`} +

+
+
-
-
+
+
LocalStorageKey="MeterEventChannelConfigTable" TableClass="table table-hover" @@ -258,10 +290,8 @@ const MeterEventChannelWindow = (props: IProps) => { SortKey={sortKey} Ascending={ascending} OnSort={(d) => { - if (d.colKey === sortKey) - dispatch(FetchChannels({ sortField: d.colField, ascending: !ascending, meterId: props.Meter.ID })); - else - dispatch(FetchChannels({ sortField: d.colField, ascending: true, meterId: props.Meter.ID })); + if (d.colKey === sortKey) setAscending(a => !a); + else setSortKey(d.colField); }} > @@ -402,7 +432,15 @@ const MeterEventChannelWindow = (props: IProps) => {

- +
+
+
+ setPage(p - 1)} + Total={totalPages} + /> +
@@ -438,7 +476,7 @@ const MeterEventChannelWindow = (props: IProps) => { Trend: false } - dispatch(dBAction({ verb: 'POST', record: newChannel })); + eventChannelController.DBAction('POST', newChannel).then(() => setRefreshTrigger(val => !val)); } }}>Add Channel
@@ -467,7 +505,11 @@ const MeterEventChannelWindow = (props: IProps) => {
- { if (c) dispatch(dBAction({ record: removeRecord, verb: 'DELETE' })); setRemoveRecord(null); }} /> + { if (c) eventChannelController.DBAction("DELETE", removeRecord).then(() => { setRemoveRecord(null); setRefreshTrigger(val => !val) }); }} /> } diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/MeterTrendChannel.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/MeterTrendChannel.tsx index cff4732234..4707922519 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/MeterTrendChannel.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/Meter/MeterTrendChannel.tsx @@ -25,29 +25,33 @@ import * as React from 'react'; import * as _ from 'lodash'; import { OpenXDA } from '../global'; import { Application, OpenXDA as GemstoneOpenXDA } from '@gpa-gemstone/application-typings'; -import { LoadingIcon, ServerErrorIcon, Warning } from '@gpa-gemstone/react-interactive'; +import { LoadingIcon, ServerErrorIcon, Warning, GenericController } from '@gpa-gemstone/react-interactive'; import { Input, Select, ToolTip } from '@gpa-gemstone/react-forms'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; import { IsNumber } from '@gpa-gemstone/helper-functions'; import { TrendChannelSlice, PhaseSlice, MeasurmentTypeSlice, MeasurementCharacteristicSlice } from '../Store/Store'; import { AssetAttributes } from '../AssetAttribute/Asset'; import { useAppSelector, useAppDispatch } from '../hooks'; -import { ConfigurableTable, ConfigurableColumn, Column } from '@gpa-gemstone/react-table'; +import { ConfigurableTable, ConfigurableColumn, Column, Paging } from '@gpa-gemstone/react-table'; import { SelectRoles } from '../Store/UserSettings'; declare var homePath: string; interface IProps { Meter: GemstoneOpenXDA.Types.Meter, IsVisible: boolean } +const trendChannelController = new GenericController(`${homePath}api/OpenXDA/TrendChannel`, 'Name'); + const MeterTrendChannelWindow = (props: IProps) => { const dispatch = useAppDispatch(); - const data = useAppSelector(TrendChannelSlice.Data); - const sortKey = useAppSelector(TrendChannelSlice.SortField) - const ascending = useAppSelector(TrendChannelSlice.Ascending) - const status = useAppSelector(TrendChannelSlice.Status); - const meterID = useAppSelector(TrendChannelSlice.ParentID); - + const [data, setData] = React.useState([]); + const [sortKey, setSortKey] = React.useState('Name'); + const [ascending, setAscending] = React.useState(false); + const [status, setStatus] = React.useState('uninitiated'); + const [page, setPage] = React.useState(0); + const [totalPages, setTotalPages] = React.useState(0); + const [totalRecords, setTotalRecords] = React.useState(0); + const [recordsPerPage, setRecordsPerPage] = React.useState(0); const [recordChanges, setRecordChanges] = React.useState>>(new Map()); const phases = useAppSelector(PhaseSlice.Data) as GemstoneOpenXDA.Types.Phase[]; @@ -66,7 +70,6 @@ const MeterTrendChannelWindow = (props: IProps) => { const [hover, setHover] = React.useState<('Update' | 'Reset' | 'None' | 'Add')>('None'); const roles = useAppSelector(SelectRoles); - React.useEffect(() => { if (phaseStatus == 'uninitiated' || phaseStatus == 'changed') dispatch(PhaseSlice.Fetch()); @@ -83,9 +86,19 @@ const MeterTrendChannelWindow = (props: IProps) => { }, [mcStatus]); React.useEffect(() => { - if (status == 'uninitiated' || status == 'changed' || meterID !== props.Meter.ID) - dispatch(TrendChannelSlice.Fetch(props.Meter.ID)); - }, [props.Meter, status]); + setStatus('loading'); + const handle = trendChannelController.PagedSearch([], sortKey, ascending, page, props.Meter.ID); + handle.done((d) => { + setData(JSON.parse(d.Data as unknown as string)) + setTotalPages(d.NumberOfPages); + setTotalRecords(d.TotalRecords); + setRecordsPerPage(d.RecordsPerPage); + if (page >= d.NumberOfPages) + setPage(Math.max(d.NumberOfPages - 1, 0)); + setStatus('idle'); + }).fail(() => setStatus('error')) + return () => { if (handle != null && handle.abort != null) handle.abort() } + }, [props.Meter, trendChannelController, sortKey, ascending, page]); React.useEffect(() => { if (!props.IsVisible) return; @@ -213,6 +226,13 @@ const MeterTrendChannelWindow = (props: IProps) => {

Trend Channels:

+
+
+

+ {'Could not complete Search'} +

+
+
@@ -231,6 +251,13 @@ const MeterTrendChannelWindow = (props: IProps) => {

Trend Channels:

+
+
+

+ {'Loading...'} +

+
+
@@ -249,6 +276,13 @@ const MeterTrendChannelWindow = (props: IProps) => {

Trend Channels:

+
+
+

+ {`Displaying Trend Channel(s) ${totalRecords > 0 ? (recordsPerPage * page + 1) : 0} - ${recordsPerPage * page + data.length} out of ${totalRecords}`} +

+
+
@@ -265,11 +299,8 @@ const MeterTrendChannelWindow = (props: IProps) => { SortKey={sortKey} Ascending={ascending} OnSort={(d) => { - - if (d.colKey === sortKey) - dispatch(TrendChannelSlice.Sort({ SortField: sortKey, Ascending: ascending })); - else - dispatch(TrendChannelSlice.Sort({ SortField: d.colField as keyof OpenXDA.TrendChannel, Ascending: true })); + if (d.colKey === sortKey) setAscending(a => !a); + else setSortKey(d.colField); }} > @@ -510,6 +541,11 @@ const MeterTrendChannelWindow = (props: IProps) => { >  + setPage(p - 1) } + />
diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/GroupUsers.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/GroupUsers.tsx index 6149a523b5..1edf55d29e 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/GroupUsers.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/User/UserGroup/GroupUsers.tsx @@ -26,7 +26,7 @@ import * as _ from 'lodash'; import { LoadingScreen } from '@gpa-gemstone/react-interactive'; import { UserAccountSliceRemote } from '../../Store/Store'; import { ISecurityGroup } from '../Types'; -import { Table, Column } from '@gpa-gemstone/react-table'; +import { Table, Column, Paging } from '@gpa-gemstone/react-table'; import { DefaultSelects } from '@gpa-gemstone/common-pages'; const GroupUser = (props: {Group: ISecurityGroup}) => { @@ -35,33 +35,41 @@ const GroupUser = (props: {Group: ISecurityGroup}) => { const [users, setUsers] = React.useState([]); const [asc, setAsc] = React.useState(true); const [sortField, setSortField] = React.useState('AccountName'); - const [status, setStatus] = React.useState('uninitiated'); - React.useEffect(() => { - const handle = getUsers(); - return () => { if (handle != null && handle.abort != null) handle.abort(); } - }, [props.Group.ID, props.Group.Type]) + const [page, setPage] = React.useState(0); + const [totalPages, setTotalPages] = React.useState(0); + const [totalRecords, setTotalRecords] = React.useState(0); + const [recordsPerPage, setRecordsPerPage] = React.useState(0); - React.useEffect(() => { - setUsers((u) => _.orderBy(u, [sortField], asc ? 'asc' : 'desc')); - }, [asc, sortField]) + const [refreshTrigger, setRefreshTrigger] = React.useState(false); + + const [status, setStatus] = React.useState('uninitiated'); - function getUsers() { + React.useEffect(() => { if (props.Group.Type != 'Database') return; setStatus('loading') - return $.ajax({ - type: "GET", - url: `${homePath}api/SystemCenter/FullSecurityGroup/Users/${props.Group.ID}`, + + const handle = $.ajax({ + type: "POST", + url: `${homePath}api/SystemCenter/FullSecurityGroup/Users/PagedList/${props.Group.ID}/${page}`, contentType: "application/json; charset=utf-8", cache: false, - async: true + async: true, + data: JSON.stringify({ OrderBy: sortField, Ascending: asc }) }).done((d) => { - setUsers(_.orderBy(d, [sortField], asc ? 'asc' : 'desc')); + setUsers(JSON.parse(d.Data as unknown as string)); + setTotalPages(d.NumberOfPages); + setTotalRecords(d.TotalRecords); + setRecordsPerPage(d.RecordsPerPage); + if (page >= d.NumberOfPages) + setPage(Math.max(d.NumberOfPages - 1, 0)); setStatus('idle'); - }, () => setStatus('error')); - } + }).fail(() => setStatus('error')); + + return () => { if (handle != null && handle.abort != null) handle.abort(); } + }, [props.Group.ID, props.Group.Type, asc, sortField, page, refreshTrigger]) function saveUser(u) { if (props.Group.Type != 'Database') @@ -77,7 +85,7 @@ const GroupUser = (props: {Group: ISecurityGroup}) => { cache: false, async: true }).done((d) => { - setUsers(_.orderBy(d, [sortField], asc ? 'asc' : 'desc')); + setRefreshTrigger(val => !val); setStatus('idle'); }, () => setStatus('error')); } @@ -92,6 +100,15 @@ const GroupUser = (props: {Group: ISecurityGroup}) => {

Users:

+
+
+

+ {status === 'error' ? 'Could not complete Search' : + status === 'loading' ? 'Loading...' : + `Displaying User(s) ${totalRecords > 0 ? (recordsPerPage * page + 1) : 0} - ${recordsPerPage * page + users.length} out of ${totalRecords}`} +

+
+
@@ -102,6 +119,9 @@ const GroupUser = (props: {Group: ISecurityGroup}) => { Users in an Active Directory Group cannot be edited in System Center. To add or remove Users, please contact your AD Administrator.
: null} {props.Group.Type == 'Database' ? + <> +
+
TableClass="table table-hover" Data={users} @@ -163,7 +183,18 @@ const GroupUser = (props: {Group: ISecurityGroup}) => { > Email - +
+
+
+
+ setPage(p -1)} + Total={totalPages} + /> +
+
+ : null}
diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ValueListGroup/ValueListGroupItem.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ValueListGroup/ValueListGroupItem.tsx index dbecd776e2..9cc58adfbb 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ValueListGroup/ValueListGroupItem.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ValueListGroup/ValueListGroupItem.tsx @@ -23,13 +23,11 @@ import * as React from 'react'; import * as _ from 'lodash'; -import { SystemCenter } from '@gpa-gemstone/application-typings'; -import { useAppSelector, useAppDispatch } from '../hooks'; -import { ValueListSlice } from '../Store/Store'; +import { Application, SystemCenter } from '@gpa-gemstone/application-typings'; import ValueListForm from './ValueListForm'; -import { Table, Column } from '@gpa-gemstone/react-table'; +import { Table, Column, Paging } from '@gpa-gemstone/react-table'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; -import { Modal } from '@gpa-gemstone/react-interactive'; +import { Modal, Search, GenericController } from '@gpa-gemstone/react-interactive'; import { ValueListItemDelete, RequiredValueLists } from './ValueListGroupDelete'; import { ToolTip } from '@gpa-gemstone/react-forms'; @@ -37,16 +35,23 @@ interface IProps { Record: SystemCenter.Types.ValueListGroup } +const controller = new GenericController(`${homePath}api/ValueList`, 'SortOrder'); + export default function ValueListGroupItems(props: IProps) { - const dispatch = useAppDispatch(); - const data = useAppSelector(ValueListSlice.Data); - const sortKey = useAppSelector(ValueListSlice.SortField); - const asc = useAppSelector(ValueListSlice.Ascending); - const status = useAppSelector(ValueListSlice.Status); - const parentID= useAppSelector(ValueListSlice.ParentID); + const [data, setData] = React.useState([]); + const [sortKey, setSortKey] = React.useState('Value'); + const [asc, setAsc] = React.useState(false); + const [status, setStatus] = React.useState('uninitiated'); + + const [page, setPage] = React.useState(0); + const [totalPages, setTotalPages] = React.useState(0); + const [totalRecords, setTotalRecords] = React.useState(0); + const [recordsPerPage, setRecordsPerPage] = React.useState(0); + + const [refreshTrigger, setRefreshTrigger] = React.useState(false); - const emptyRecord: SystemCenter.Types.ValueListItem = { ID: 0, GroupID: parentID as number, Value: '', AltValue: null, SortOrder: 0 }; + const emptyRecord: SystemCenter.Types.ValueListItem = { ID: 0, GroupID: props.Record.ID as number, Value: '', AltValue: null, SortOrder: 0 }; const [record, setRecord] = React.useState(emptyRecord); const [showWarning, setShowWarning] = React.useState(false); const [showModal, setShowModal] = React.useState(false); @@ -67,9 +72,24 @@ export default function ValueListGroupItems(props: IProps) { }, [props.Record?.Name, data.length, countDictionary]); React.useEffect(() => { - if (status == 'uninitiated' || status == 'changed' || parentID != props.Record.ID) - dispatch(ValueListSlice.Fetch(props.Record.ID)); - }, [status, parentID, props.Record.ID]); + setStatus('loading') + const filters = [{ FieldName: "GroupID", SearchText: props.Record.ID.toString(), Operator: "=" as Search.OperatorType, IsPivotColumn: false, Type: "number" as Search.FieldType }] + const h = controller.PagedSearch(filters, sortKey, asc, page) + h.done((d) => { + setData(JSON.parse(d.Data as unknown as string)) + setTotalPages(d.NumberOfPages) + setTotalRecords(d.TotalRecords); + setRecordsPerPage(d.RecordsPerPage); + if (page >= d.NumberOfPages) + setPage(Math.max(d.NumberOfPages - 1, 0)); + setStatus('idle') + }).fail((d) => { + setStatus('error') + }) + return () => { + if (h.abort != undefined) h.abort(); + } + }, [controller, sortKey, asc, page, props.Record, refreshTrigger]); React.useEffect(() => { if (props.Record?.Name == null) return; @@ -95,18 +115,31 @@ export default function ValueListGroupItems(props: IProps) {

List Items:

+
+
+

+ {status === 'error' ? 'Could not complete Search' : + status === 'loading' ? 'Loading...' : + `Displaying Item(s) ${totalRecords > 0 ? (recordsPerPage * page + 1) : 0} - ${recordsPerPage * page + data.length} out of ${totalRecords}`} +

+
+
-
-
+
+
+
TableClass="table table-hover" Data={data} SortKey={sortKey} Ascending={asc} OnSort={(d) => { - if (d.colKey == 'btns') - return; - dispatch(ValueListSlice.Sort({ SortField: d.colField, Ascending: d.ascending })); + if (d.colField === sortKey) + setAsc(!asc); + else { + setAsc(true); + setSortKey(d.colField); + } }} TableStyle={{ padding: 0, width: '100%', tableLayout: 'fixed', display: 'flex', flexDirection: 'column', overflow: 'hidden' }} TheadStyle={{ fontSize: 'smaller', display: 'table', tableLayout: 'fixed', width: '100%' }} @@ -180,6 +213,16 @@ export default function ValueListGroupItems(props: IProps) {
+
+
+
+ setPage(p - 1) } + /> +
+
@@ -192,7 +235,7 @@ export default function ValueListGroupItems(props: IProps) { Show={showWarning} CallBack={(conf) => { if (conf) - dispatch(ValueListSlice.DBAction({ verb: 'DELETE', record: { ...record } })); + controller.DBAction('DELETE', { ...record }).then(() => setRefreshTrigger(val => !val)); setShowWarning(false); }} Record={record} @@ -210,9 +253,9 @@ export default function ValueListGroupItems(props: IProps) { ShowX={true} CallBack={(conf) => { setShowModal(false); if (conf && record.ID > 0) - dispatch(ValueListSlice.DBAction({ verb: 'PATCH', record })); + controller.DBAction('PATCH', record).then(() => setRefreshTrigger(val => !val)); else if (conf && record.ID == 0) - dispatch(ValueListSlice.DBAction({ verb: 'POST', record })); + controller.DBAction('POST', record).then(() => setRefreshTrigger(val => !val)); }} > diff --git a/Source/Applications/SystemCenterNotification/Controllers/EmailTypeController.cs b/Source/Applications/SystemCenterNotification/Controllers/EmailTypeController.cs index 152d413c37..69a729bcd3 100644 --- a/Source/Applications/SystemCenterNotification/Controllers/EmailTypeController.cs +++ b/Source/Applications/SystemCenterNotification/Controllers/EmailTypeController.cs @@ -440,7 +440,7 @@ public IHttpActionResult SentEmailTimeline([FromBody] PostData postData, int Sen public IHttpActionResult SentEmailTimelinePaged([FromBody] PostData postData, [FromUri] int sentEmailID, [FromUri] int page) { PagedResults pagedResults = new PagedResults(); - int recordsPerPage = Take ?? 50; + int recordsPerPage = PageSize ?? 50; List timeline = GetEmailTimeline(postData, sentEmailID); int totalRecords = timeline.Count;