Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Source/Applications/SystemCenter/SystemCenter.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -618,9 +618,15 @@
<Content Include="wwwroot\Scripts\TSX\SystemCenter\CommonComponents\Restrictions\EditionLockPage.tsx" />
<Content Include="wwwroot\Scripts\TSX\SystemCenter\CommonComponents\Restrictions\EditionLockModal.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\CommonComponents\AssetGroupWindow.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\CommonComponents\ControllerSelectPopup.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\CommonComponents\ExtDBTaskStatusModal.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\CommonComponents\FileGroupAnalysisJobPriority.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\CommonComponents\GenericForm.tsx" />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GenericForm.tsx is added to the csproj here, but the file isn't in the PR.

<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\CommonComponents\GenericInfo.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\CommonComponents\GenericRecord.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\CommonComponents\GenericRelation.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\CommonComponents\ProcessingStatus.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\CommonComponents\RecordContext.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\CommonComponents\Restrictions\EditionRestrictionTooltip.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\CommonComponents\Restrictions\RoleRestrictionTooltip.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\CommonComponents\ExternalDBUpdate.tsx" />
Expand Down
62 changes: 31 additions & 31 deletions Source/Applications/SystemCenter/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Source/Applications/SystemCenter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@
"webpack-cli": "4.7.2"
},
"dependencies": {
"@gpa-gemstone/react-graph": "1.0.115",
"@gpa-gemstone/react-graph": "1.0.116",
"@turf/turf": "7.4.0",
"@arcgis/core": "5.1.17",
"proj4leaflet": "^1.0.2",
"@gpa-gemstone/common-pages": "0.0.190",
"@gpa-gemstone/common-pages": "0.0.191",
"@reduxjs/toolkit": "1.8.3",
"assert": "2.0.0",
"buffer": "6.0.3",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ interface IProps<T extends U> {
MinSelection?: number,
children?: React.ReactNode,
DisableConfirm?: boolean
Filters?: Search.IFilter<T>[]
}


Expand All @@ -64,8 +65,9 @@ export default function ControllerSelectPopup<T extends U>(props: IProps<T>) {
}, [props.Selection])

React.useEffect(() => {
setFilters([]); // initialize filter list, which should add additional filters
}, [])
if (props.Filters != null) setFilters(props.Filters)
else setFilters([]); // initialize filter list, which should add additional filters

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is necessary to make sure the Filters passed into ControllerSelectPopup are reflected in the Searchbar. Without this change, the Filters passed into ControllerSelectPopup aren't reflected in the Searchbar, even after different filters are added or removed.

Comment on lines 67 to +69

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filters only seeds the popup's filters state, and the SearchBar writes that same state. As soon as the user searches, noSameFilter is replaced, so meters already on this instance show up again and can be added a second time.

On master, DefaultSelects.Meter appended AddlFilters to the search filters. Could ControllerSelectPopup treat these as always-applied base filters and combine them with the search filters when it queries?

Something like:

const [searchFilters, setSearchFilters] = React.useState<Search.IFilter<T>[]>([]);
const baseFilters = useStringMemonization(props.BaseFilters ?? []);
const filters = React.useMemo(() => [...baseFilters, ...searchFilters], [baseFilters, searchFilters]);

Then the [props.Filters] effect can go, and the search effect can skip while !props.Show. Renaming the prop to BaseFilters would also make it clear they're always applied.

}, [props.Filters])

function AddCurrentList() {
const updatedData = selectedData.concat(data);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//******************************************************************************************************
// GenericInfo.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:
// ----------------------------------------------------------------------------------------------------
// 09/10/2026 - Natalie Beatty
// Generated original version of source code.
//
//******************************************************************************************************

import * as React from 'react';
import { ToolTip } from '@gpa-gemstone/react-forms';
import { ReactIcons } from '@gpa-gemstone/gpa-symbols';
import { useRecordContext } from './RecordContext';

interface IProps<T> {
/**
* Forms to display within the tab.
*/
Forms: Array<(record: T, setter: React.Dispatch<React.SetStateAction<T>>, setErrors: React.Dispatch<React.SetStateAction<string[]>>, setChanged: React.Dispatch<React.SetStateAction<string[]>>) => React.ReactNode>
/**
* Determine whether the user has permissions to edit the record.
*/
HasPermissions: () => boolean
}

/**
* A generic tab for record of type T for viewing and editing fields of the record.
* @param props
* @returns
*/
function GenericInfo<T,>({
Forms,
HasPermissions
}: React.PropsWithChildren<IProps<T>>) {
const [hover, setHover] = React.useState<('None' | 'Clear' | 'Submit')>('None');
const context = useRecordContext<T>();

return (
<div className="card" style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
<div className="card-header">
<div className="row">
<div className="col">
<h4>{context.RecordType} Information:</h4>
</div>
</div>
</div>
<div className="card-body" style={{ flex: 1, overflowY: 'auto' }}>
{
Forms.map((form) => {
return form(context.SelectedRecord, context.SetSelectedRecord, context.SetErrors, context.SetWarnings)
})
}
</div>
<div className="card-footer">
<div className="btn-group mr-2">
<button className={"btn btn-primary" + (context.Warnings.length == 0 || context.Errors.length > 0 ? ' disabled' : '')} onClick={() => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HasPermissions only drives the tooltip here; the Save button's disabled state and onClick never check it. The Remote XDA form happens to disable its own inputs, but any other form would let a non-admin PATCH. Can Save also require HasPermissions()?

if (context.Warnings.length > 0 && context.Errors.length == 0) {
context.Patch();
}
}}
Comment on lines +70 to +74
onMouseEnter={() => setHover('Submit')} onMouseLeave={() => setHover('None')} data-tooltip={"Update"}
>Save Changes</button>
</div>
<ToolTip Show={hover == 'Submit' && context.Errors.length > 0} Position={'top'} Target={"Update"}>
{context.Errors.map((t, i) => <p key={`a_${i}`}><ReactIcons.CrossMark Color="var(--danger)" /> {t}</p>)}
</ToolTip>
<ToolTip Show={hover == 'Submit' && !HasPermissions()} Position={'top'} Target={"Update"}>
<p>Your role does not have permission. Please contact your Administrator if you believe this to be in error.</p>
</ToolTip>
<div className="btn-group mr-2">
<button className="btn btn-warning" onClick={() => {
if (context.Warnings.length > 0) {
context.ClearChanges();
}
}
} disabled={context.Warnings.length == 0}
onMouseEnter={() => setHover('Clear')} onMouseLeave={() => setHover('None')} data-tooltip={"Clr"}
>Clear Changes</button>
</div>
<ToolTip Show={hover == 'Clear' && context.Warnings.length > 0} Position={'top'} Target={"Clr"}>
{context.Warnings.map((t, i) => <p key={`a_${i}`}><ReactIcons.Warning Color="var(--warning)" /> {t}</p>)}
</ToolTip>
</div>
</div>
);
}

export default GenericInfo;
Loading
Loading