diff --git a/common-pages/src/Note.tsx b/common-pages/src/Note.tsx index 52ea89f8..b6141b2d 100644 --- a/common-pages/src/Note.tsx +++ b/common-pages/src/Note.tsx @@ -22,8 +22,8 @@ import * as React from 'react'; import { Select, TextArea } from '@gpa-gemstone/react-forms'; -import Table, { Column } from '@gpa-gemstone/react-table'; -import { CrossMark, Pencil, TrashCan } from '@gpa-gemstone/gpa-symbols'; +import { ReactTable } from '@gpa-gemstone/react-table'; +import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; import { Modal, ToolTip, ServerErrorIcon, LoadingScreen } from '@gpa-gemstone/react-interactive'; import { Application, OpenXDA } from '@gpa-gemstone/application-typings'; import moment = require('moment'); @@ -31,6 +31,7 @@ import { IGenericSlice } from './SliceInterfaces'; import { useDispatch, useSelector } from 'react-redux'; import { Dispatch } from '@reduxjs/toolkit'; +// Add additional columns as children interface IProps { NoteTypes: OpenXDA.Types.NoteType[], NoteTags: OpenXDA.Types.NoteTag[], @@ -44,27 +45,13 @@ interface IProps { AllowAdd?: boolean, ShowCard?: boolean, DefaultApplication?: OpenXDA.Types.NoteApplication, - Filter?: (note: OpenXDA.Types.Note) => boolean, - AdditionalCollumns?: Column[] + Filter?: (note: OpenXDA.Types.Note) => boolean } -function Note(props: IProps) { +function Note(props: React.PropsWithChildren) { const dispatch = useDispatch>(); - const standardCollumns: Column[] = [ - { - key: 'Note', field: 'Note', label: 'Note', - headerStyle: { width: '50%' }, rowStyle: { width: '50%' } }, - { - key: 'Timestamp', field: 'Timestamp', label: 'Time', - headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' }, - content: (item: OpenXDA.Types.Note) => moment.utc(item.Timestamp).format("MM/DD/YYYY HH:mm") }, - { - key: 'UserAccount', field: 'UserAccount', label: 'User', - headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } - } - ] const allowEdit = props.AllowEdit === undefined? true : props.AllowEdit; const allowRemove = props.AllowRemove === undefined? true : props.AllowRemove; @@ -73,15 +60,14 @@ function Note(props: IProps) { const defaultApplication = props.DefaultApplication !== undefined ? props.DefaultApplication : props.NoteApplications[0]; const showCard = props.ShowCard === undefined || props.ShowCard; - const [showEdit, setEdit] = React.useState(false); - const [hover, setHover] = React.useState<'add'|'clear'|'none'>('none') - const [collumns, setCollumns] = React.useState[]>(standardCollumns) + const [showEdit, setEdit] = React.useState(false); + const [hover, setHover] = React.useState<'add'|'clear'|'none'>('none'); - const data: OpenXDA.Types.Note[] = useSelector(props.NoteSlice.Data) + const data: OpenXDA.Types.Note[] = useSelector(props.NoteSlice.Data) const dataStatus: Application.Types.Status = useSelector(props.NoteSlice.Status) const parentID: number|string|undefined = useSelector((props.NoteSlice.ParentID === undefined? (state: any) => props.ReferenceTableID : props.NoteSlice.ParentID)) const sortField: keyof OpenXDA.Types.Note = useSelector(props.NoteSlice.SortField) - const ascending: boolean = useSelector(props.NoteSlice.Ascending) + const ascending: boolean = useSelector(props.NoteSlice.Ascending) const [note, setNote] = React.useState(CreateNewNote()); const [notes, setNotes] = React.useState([]); @@ -114,40 +100,10 @@ function Note(props: IProps) { return setNote((n) => ({...n, ReferenceTableID: props.ReferenceTableID !== undefined ? props.ReferenceTableID : -1})); },[props.ReferenceTableID]); - - React.useEffect(() => { - const c = standardCollumns; - - if (props.AdditionalCollumns !== undefined) - c.push(...props.AdditionalCollumns); - - if (props.NoteTags.length > 1) - c.push({ key: 'NoteTagID', field: 'NoteTagID', label: 'Type', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' }, - content: (n) => props.NoteTags.find(t => t.ID === n.NoteTagID)?.Name } - ); - if (props.NoteApplications.length > 1) - c.push({ key: 'NoteApplicationID', field: 'NoteApplicationID', label: 'Application', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' }, - content: (n) => props.NoteApplications.find(t => t.ID === n.NoteApplicationID)?.Name } - ); - - c.push({ - key: 'buttons', - label: '', - headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' }, - content: (item: OpenXDA.Types.Note) => <> - {allowEdit? : null } - {allowRemove? : null } - - }); - - setCollumns(c); - }, [props.NoteTags,props.NoteApplications, props.AdditionalCollumns]) React.useEffect(() => { setNotes(data.filter(n => (props.Filter === undefined? true : props.Filter(n)))); - }, [props.Filter, data]) - + }, [props.Filter, data]); function CreateNewNote() { const newNote: OpenXDA.Types.Note = {ID: -1, ReferenceTableID: -1, NoteTagID: -1, NoteTypeID: -1, NoteApplicationID: -1, Timestamp: '', UserAccount: '', Note: '' } @@ -201,107 +157,160 @@ function Note(props: IProps) { return ( -
- -
+
+ +

{props.Title !== undefined? props.Title : 'Notes:'}

-
- {allowAdd && !showCard? - <> - + {allowAdd && !showCard? + <> + setNote(n)} NoteTags={props.NoteTags} NoteTypes={props.NoteTypes} NoteApplications={props.NoteApplications} ShowApplications={!useFixedApp} - /> - -
- + /> +
+ -

{CrossMark} A note needs to be entered.

+

A note needs to be entered.

- + -

{CrossMark} The note field is already empty.

+

The note field is already empty.

- : null } + + : null }
- - cols={collumns} - tableClass="table table-hover" - data={notes} - sortKey={sortField} - ascending={ascending} - onSort={(d) => { - if (d.colField === undefined) - return; - if (d.colField === sortField) - dispatch(props.NoteSlice.Sort({SortField: sortField, Ascending: ascending})) - else - dispatch(props.NoteSlice.Sort({SortField: d.colField, Ascending: true})) - - }} - onClick={() => { return;}} - theadStyle={{ fontSize: 'smaller', display: 'table', tableLayout: 'fixed', width: '100%' }} - tbodyStyle={{ display: 'block', overflowY: 'scroll', maxHeight: props.MaxHeight - 300, width: '100%' }} - rowStyle={{ fontSize: 'smaller', display: 'table', tableLayout: 'fixed', width: '100%' }} - selected={() => false} - /> + + TableClass="table table-hover" + Data={notes} + SortKey={sortField} + Ascending={ascending} + OnSort={(d) => { + if (d.colField === undefined) + return; + if (d.colField === sortField) + dispatch(props.NoteSlice.Sort({SortField: sortField, Ascending: ascending})); + else + dispatch(props.NoteSlice.Sort({SortField: d.colField, Ascending: true})); + }} + OnClick={() => { return; }} + TbodyStyle={{ maxHeight: props.MaxHeight - 300 }} + Selected={() => false} + KeySelector={(d) => d.ID} + > + + Key="Note" Field="Note" HeaderStyle={{width:'50%'}} RowStyle={{width:'50%'}} + >Note + + Key="Timestamp" Field="Timestamp" HeaderStyle={{width:'auto'}} RowStyle={{width:'auto'}} + Content={(row) => moment.utc(row.item.Timestamp).format("MM/DD/YYYY HH:mm")} + >Time + + Key="UserAccount" Field="UserAccount" HeaderStyle={{width:'auto'}} RowStyle={{width:'auto'}} + >User + {props.children} + {props.NoteTags.length > 1 ? + + Key="NoteTagID" Field="NoteTagID" HeaderStyle={{width:'auto'}} RowStyle={{width:'auto'}} + Content={(row) => props.NoteTags.find(t => t.ID === row.item.NoteTagID)?.Name} + >Type + : <>} + {props.NoteApplications.length > 1 ? + + Key="NoteApplicationID" Field="NoteApplicationID" HeaderStyle={{width:'auto'}} RowStyle={{width:'auto'}} + Content={(row) => props.NoteApplications.find(t => t.ID === row.item.NoteApplicationID)?.Name} + >Application + : <>} + + Key="buttons" HeaderStyle={{width:'auto'}} RowStyle={{width:'auto'}} + Content={(row) => + <> + { allowEdit ? + + : null } + { allowRemove ? + + : null } + + } + >  +
- {allowAdd && showCard? - setNote(n)} - NoteTags={props.NoteTags} NoteTypes={props.NoteTypes} - NoteApplications={props.NoteApplications} - ShowApplications={!useFixedApp} - /> - : null } - {CrossMark} An empty Note can not be saved.

- }> - setNote(n)} - NoteTags={props.NoteTags} - NoteTypes={props.NoteTypes} - NoteApplications={props.NoteApplications} - /> -
-
- {allowAdd && showCard? -
-
- - -

{CrossMark} A note needs to be entered.

-
-
-
- - -

{CrossMark} The note field is already empty.

-
+ {allowAdd && showCard? + setNote(n)} + NoteTags={props.NoteTags} NoteTypes={props.NoteTypes} + NoteApplications={props.NoteApplications} + ShowApplications={!useFixedApp} + /> + : null } + An empty Note can not be saved.

+ } + > + setNote(n)} + NoteTags={props.NoteTags} + NoteTypes={props.NoteTypes} + NoteApplications={props.NoteApplications} + /> +
+
+ {allowAdd && showCard? +
+
+ + +

A note needs to be entered.

+
+
+
+ + +

The note field is already empty.

+
+
-
- : null} - {!allowAdd && showCard?
: null} -
- ) + : null} + {!allowAdd && showCard? +
+ : null} +
+ ); } interface OptionProps { diff --git a/common-pages/src/SelectionPopup.tsx b/common-pages/src/SelectionPopup.tsx index b62d8d61..f61230df 100644 --- a/common-pages/src/SelectionPopup.tsx +++ b/common-pages/src/SelectionPopup.tsx @@ -25,17 +25,17 @@ import { GenericSlice, Search} from "@gpa-gemstone/react-interactive"; import { Application, OpenXDA, SystemCenter } from "@gpa-gemstone/application-typings"; import SelectPopup from "./StandardSelectPopup"; import {DefaultSearch} from './SearchBar'; -import { Column } from "@gpa-gemstone/react-table"; interface U { ID: number|string } +// Pass columns in via children + interface IProps { Slice: GenericSlice, Selection: T[], OnClose: (selected: T[], conf: boolean ) => void Show: boolean, Type?: 'single'|'multiple', - Columns: Column[], Title: string, GetEnum: (setOptions: (options: IOptions[]) => void, field: Search.IField) => () => void, GetAddlFields: (setAddlFields: (cols: Search.IField[]) => void) => () => void, diff --git a/common-pages/src/Setting.tsx b/common-pages/src/Setting.tsx deleted file mode 100644 index 2d9e8df5..00000000 --- a/common-pages/src/Setting.tsx +++ /dev/null @@ -1,189 +0,0 @@ -// ****************************************************************************************************** -// Setting.tsx - Gbtc -// -// Copyright © 2020, 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: -// ---------------------------------------------------------------------------------------------------- -// 04/28/2021 - C. Lackner -// Generated original version of source code. -// ****************************************************************************************************** - -import * as React from 'react'; -import { Input } from '@gpa-gemstone/react-forms'; -import Table from '@gpa-gemstone/react-table'; -import { CrossMark } from '@gpa-gemstone/gpa-symbols'; -import { SearchBar, Search, Modal, Warning, LoadingScreen, ServerErrorIcon } from '@gpa-gemstone/react-interactive'; -import { ISearchableSlice } from './SliceInterfaces'; -import { Application, SystemCenter } from '@gpa-gemstone/application-typings'; -import { useDispatch, useSelector } from 'react-redux'; -import { Dispatch } from '@reduxjs/toolkit'; - -interface IProps { - SettingsSlice: ISearchableSlice -} - - - -function Setting(props: IProps) { - const dispatch = useDispatch>(); - - const search: Search.IFilter[] = useSelector(props.SettingsSlice.SearchFilters); - const searchStatus: Application.Types.Status = useSelector(props.SettingsSlice.SearchStatus); - - const data: SystemCenter.Types.Setting[] = useSelector(props.SettingsSlice.SearchResults); - const allSettings: SystemCenter.Types.Setting[] = useSelector(props.SettingsSlice.Data); - const status: Application.Types.Status = useSelector(props.SettingsSlice.Status); - - const [sortField, setSortField] = React.useState('Name'); - const [ascending, setAscending] = React.useState(true); - - const emptySetting = {ID: 0, Name: '', Value: '', DefaultValue: ''} - const [editnewSetting, setEditNewSetting] = React.useState(emptySetting); - const [editNew, setEditNew] = React.useState('New'); - - const [showModal, setShowModal] = React.useState(false); - const [showWarning, setShowWarning] = React.useState(false); - const [hasChanged, setHasChanged] = React.useState(false); - - const [errors, setErrors] = React.useState([]); - - React.useEffect(() => { - if (status === 'unintiated' || status === 'changed') - dispatch(props.SettingsSlice.Fetch()); - }, [dispatch,status]); - - React.useEffect(() => { - if (searchStatus === 'unintiated' || status === 'changed') - dispatch(props.SettingsSlice.DBSearch({filter: search, sortField, ascending})); - }, [dispatch,searchStatus, ascending, sortField, search]); - - React.useEffect(() => { setHasChanged(false) }, [showModal]); - - React.useEffect(() => { - const e: string[] = []; - if (editnewSetting.Name == null || editnewSetting.Name.length === 0) - e.push('A Name is required') - if (editnewSetting.Name != null && editnewSetting.Name.length > 0 && allSettings.findIndex(s => s.Name.toLowerCase() === editnewSetting.Name.toLowerCase() && s.ID !== editnewSetting.ID) > -1) - e.push('A Settign with this Name already exists.') - if (editnewSetting.Value == null || editnewSetting.Value.length === 0) - e.push('A Value is required') - setErrors(e) - }, [editnewSetting]) - - const searchFields: Search.IField[] = [ - { key: 'Name', label: 'Name', type: 'string', isPivotField: false }, - { key: 'DefaultValue', label: 'Default Value', type: 'string', isPivotField: false }, - { key: 'Value', label: 'Value', type: 'string', isPivotField: false } - ] - - if (status === 'error') - return
- -
; - - return ( - <> - -
- CollumnList={searchFields} SetFilter={(flds) => dispatch(props.SettingsSlice.DBSearch({filter: flds, sortField, ascending}))} - 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 + ' Settings'} - GetEnum={() => { - return () => {} - }} - > -
  • -
    - Actions: -
    - -
    -
    -
  • - - -
    - - cols={[ - { key: 'Name', field: 'Name', label: 'Setting Name', headerStyle: { width: '10%' }, rowStyle: { width: '10%' } }, - { key: 'Value', field: 'Value', label: 'Current Value', headerStyle: { width: '10%' }, rowStyle: { width: '10%' } }, - { key: 'DefaultValue', field: 'DefaultValue', label: 'Default Value', headerStyle: { width: '20%' }, rowStyle: { width: '20%' } }, - { key: 'scroll', label: '', headerStyle: { width: 17, padding: 0 }, rowStyle: { width: 0, padding: 0 } }, - ]} - tableClass="table table-hover" - data={data} - sortKey={sortField} - ascending={ascending} - onSort={(d) => { - if (d.colKey === 'scroll' || d.colField === undefined) - return; - if (d.colField === sortField) - setAscending(!ascending); - else { - setAscending(true); - setSortField(d.colField); - } - if (d.colField === sortField) - dispatch(props.SettingsSlice.DBSearch({filter: search, sortField, ascending: true})); - else - dispatch(props.SettingsSlice.DBSearch({filter: search, sortField: d.colField, ascending})); - }} - onClick={(item) => { setEditNewSetting(item.row); setShowModal(true); setEditNew('Edit');}} - theadStyle={{ fontSize: 'smaller', display: 'table', tableLayout: 'fixed', width: '100%' }} - tbodyStyle={{ display: 'block', overflowY: 'scroll', maxHeight: window.innerHeight - 300, width: '100%' }} - rowStyle={{ fontSize: 'smaller', display: 'table', tableLayout: 'fixed', width: '100%' }} - selected={() => false} - /> -
    -
    - { - if (conf && editNew === 'New') - dispatch(props.SettingsSlice.DBAction({verb: 'POST', record: editnewSetting})) - if (conf && editNew === 'Edit') - dispatch(props.SettingsSlice.DBAction({verb: 'PATCH', record: editnewSetting})) - if (!conf && isBtn) - setShowWarning(true); - setShowModal(false); - }} - DisableConfirm={(editNew === 'Edit' && !hasChanged) || errors.length > 0} - ConfirmShowToolTip={errors.length > 0} - ConfirmToolTipContent={ - errors.map((t,i) =>

    {CrossMark} {t}

    ) - } - > -
    -
    - Record={editnewSetting} Field={'Name'} Label='Setting Name' Feedback={'A unique Name is required.'} - Valid={field => editnewSetting.Name != null && editnewSetting.Name.length > 0 && allSettings.findIndex(s => s.Name === editnewSetting.Name && s.ID !== editnewSetting.ID) < 0} - Setter={(record) => { setEditNewSetting(record); setHasChanged(true); }} - /> - Record={editnewSetting} Field={'Value'} Label='Value' Feedback={'Value is required.'} - Valid={field => editnewSetting.Value != null && editnewSetting.Value.length > 0} - Setter={(record) => { setEditNewSetting(record); setHasChanged(true); }} - /> - Record={editnewSetting} Field={'DefaultValue'} Label='Default Value' Valid={field => true} - Setter={(record) => { setEditNewSetting(record); setHasChanged(true); }} - /> -
    -
    -
    - { if (conf) dispatch(props.SettingsSlice.DBAction({verb: 'DELETE', record: editnewSetting})); setShowWarning(false); }} /> - ) -} - -export default Setting; diff --git a/common-pages/src/StandardSelectPopup.tsx b/common-pages/src/StandardSelectPopup.tsx index 11459d9f..3c379e37 100644 --- a/common-pages/src/StandardSelectPopup.tsx +++ b/common-pages/src/StandardSelectPopup.tsx @@ -20,12 +20,12 @@ // Generated original version of source code. // ****************************************************************************************************** -import Table, { Column } from "@gpa-gemstone/react-table"; +import { ReactTable } from "@gpa-gemstone/react-table"; import * as React from 'react'; import { useDispatch, useSelector } from "react-redux"; import { GenericSlice, Modal } from "@gpa-gemstone/react-interactive"; import _ = require("lodash"); -import { CrossMark } from "@gpa-gemstone/gpa-symbols"; +import { ReactIcons } from "@gpa-gemstone/gpa-symbols"; import { Dispatch } from "@reduxjs/toolkit"; interface U { ID: number|string } @@ -37,7 +37,6 @@ interface IProps { Show: boolean, Searchbar: (children: React.ReactNode) => React.ReactNode, Type?: 'single'|'multiple', - Columns: Column[], Title: string, MinSelection?: number, children?: React.ReactNode @@ -60,16 +59,15 @@ export default function SelectPopup(props: IProps) { }, [props.Selection]) function AddCurrentList() { - let updatedData: any[]; - updatedData = (selectedData as any[]).concat(data); - setSelectedData(_.uniqBy((updatedData as T[]), (d) => d.ID)); + const updatedData = selectedData.concat(data); + setSelectedData(_.uniqBy(updatedData, (d) => d.ID)); } return (<> props.OnClose(selectedData, conf)} DisableConfirm={props.MinSelection !== undefined && selectedData.length < props.MinSelection} ConfirmShowToolTip={props.MinSelection !== undefined && selectedData.length < props.MinSelection} - ConfirmToolTipContent={

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

    } + ConfirmToolTipContent={

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

    } >
    @@ -89,7 +87,11 @@ export default function SelectPopup(props: IProps) { : null} {React.Children.map(props.children, (e) => { - if (React.isValidElement(e)) return e; + if (React.isValidElement(e)) { + if (((e as React.ReactElement).type === ReactTable.AdjustableColumn) || + ((e as React.ReactElement).type === ReactTable.Column)) return null; + return e; + } return null; })} )} @@ -97,45 +99,42 @@ export default function SelectPopup(props: IProps) {
    - - cols={props.Columns} - tableClass="table table-hover" - data={data} - sortKey={sortField as string} - ascending={ascending} - onSort={(d) => { + + TableClass="table table-hover" + Data={data} + SortKey={sortField as string} + Ascending={ascending} + OnSort={(d) => { if (d.colKey === "Scroll") return; - if (d.colKey === sortField) dispatch(props.Slice.Sort({SortField: sortField, Ascending: ascending})); else { dispatch(props.Slice.Sort({SortField: d.colField as keyof T, Ascending: true})); } }} - onClick={(d) => { + OnClick={(d) => { if (props.Type === undefined || props.Type === 'single') setSelectedData([d.row]) else setSelectedData((s) => [...s.filter(item => item.ID !== d.row.ID), d.row]) }} - theadStyle={{ fontSize: 'smaller', display: 'table', tableLayout: 'fixed', width: '100%' }} - tbodyStyle={{ display: 'block', overflowY: 'scroll', maxHeight: '400px', width: '100%' }} - rowStyle={{ fontSize: 'smaller', display: 'table', tableLayout: 'fixed', width: '100%' }} - selected={(item) => selectedData.findIndex(d => d.ID === item.ID) > -1 } - /> + Selected={(item) => selectedData.findIndex(d => d.ID === item.ID) > -1 } + KeySelector={item => item.ID} + > + {props.children} +
    {props.Type === 'multiple' ?

    Current Selection

    - { + + TableClass="table table-hover" + Data={selectedData} + SortKey={sortKeySelected} + Ascending={ascendingSelected} + OnSort={(d) => { if (d.colKey === sortKeySelected) { const ordered = _.orderBy(selectedData, [d.colKey], [(!ascendingSelected ? "asc" : "desc")]) as any; setAscendingSelected(!ascendingSelected); @@ -148,12 +147,12 @@ export default function SelectPopup(props: IProps) { setSortKeySelected(d.colKey); } }} - onClick={(d) => setSelectedData([...selectedData.filter(item => item.ID !== d.row.ID)])} - theadStyle={{ fontSize: 'smaller', display: 'table', tableLayout: 'fixed', width: '100%' }} - tbodyStyle={{ display: 'block', overflowY: 'scroll', maxHeight: '400px', width: '100%' }} - rowStyle={{ fontSize: 'smaller', display: 'table', tableLayout: 'fixed', width: '100%' }} - selected={(item) => false} - /> + OnClick={(d) => setSelectedData([...selectedData.filter(item => item.ID !== d.row.ID)])} + Selected={() => false} + KeySelector={item => item.ID} + > + {props.children} + : null} diff --git a/common-pages/src/ValueList/ByValueList.tsx b/common-pages/src/ValueList/ByValueList.tsx deleted file mode 100644 index 68e72bfc..00000000 --- a/common-pages/src/ValueList/ByValueList.tsx +++ /dev/null @@ -1,156 +0,0 @@ -// ****************************************************************************************************** -// ValueList.tsx - Gbtc -// -// Copyright © 2020, 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/10/2021 - C. Lackner -// Generated original version of source code. -// ****************************************************************************************************** - -import * as React from 'react'; -import Table from '@gpa-gemstone/react-table'; -import { CrossMark } from '@gpa-gemstone/gpa-symbols'; -import { SearchBar, Search, Modal } from '@gpa-gemstone/react-interactive'; -import { SystemCenter, Application } from '@gpa-gemstone/application-typings'; -import GroupForm from './GroupForm'; -import { useDispatch, useSelector } from 'react-redux'; -import { IGenericSlice, ISearchableSlice } from '../SliceInterfaces'; -import { Dispatch } from '@reduxjs/toolkit'; - -interface IProps { - OnValueListSelect: (id: number) => void, - ValueListSlice: ISearchableSlice; - ValueListItemSlice: IGenericSlice; -} - -function ByValueListGroup(props: IProps) { - const dispatch = useDispatch>(); - - const data: SystemCenter.Types.ValueListGroup[] = useSelector(props.ValueListSlice.SearchResults); - const dataStatus: Application.Types.Status = useSelector(props.ValueListSlice.SearchStatus); - - const groups: SystemCenter.Types.ValueListGroup[] = useSelector(props.ValueListSlice.Data); - const groupStatus: Application.Types.Status = useSelector(props.ValueListSlice.Status); - - const [sortKey,setSortKey] = React.useState('Name'); - const [asc,setASC] = React.useState(false); - - const emptyRecord: SystemCenter.Types.ValueListGroup = {ID: 0, Name: '', Description: ''}; - - const [showNew, setShowNew] = React.useState(false); - const [record, setRecord] = React.useState(emptyRecord); - - const items: SystemCenter.Types.ValueListItem[] = useSelector(props.ValueListItemSlice.Data); - const itemStatus: Application.Types.Status = useSelector(props.ValueListItemSlice.Status); - - const [search, setSearch] = React.useState[]>([]); - const [newErrors, setNewErrors] = React.useState([]); - const [validName, setValidName] = React.useState(true); - - React.useEffect(() => { - if (dataStatus === 'unintiated' || dataStatus === 'changed') - dispatch(props.ValueListSlice.DBSearch({filter: search, sortField: sortKey, ascending: asc})); - -}, [dispatch]); - - React.useEffect(() => { - dispatch(props.ValueListSlice.DBSearch({filter: search, sortField: sortKey, ascending: asc})); - },[search,asc,sortKey] - ); - - React.useEffect(() => { - if (itemStatus === 'unintiated' || itemStatus === 'changed') - dispatch(props.ValueListItemSlice.Fetch()); - -}, [dispatch]); - - React.useEffect(() => { - if (groupStatus === 'unintiated' || groupStatus === 'changed') - dispatch(props.ValueListSlice.Fetch()); - }, [dispatch]); - - React.useEffect(() => { - if (record.Name == null) - setValidName(true) - else - setValidName(groups.findIndex(g => g.Name.toLowerCase() === record.Name.toLowerCase()) < 0) - }, [record]) - - return ( -
    - CollumnList={[{ label: 'Name', key: 'Name', type: 'string', isPivotField: false }]} SetFilter={(flds) => setSearch(flds)} Direction={'left'} defaultCollumn={{ label: 'Name', key: 'Name', type: 'string', isPivotField: false }} Width={'50%'} Label={'Search'} - ShowLoading={dataStatus === 'loading' || itemStatus === 'loading'} ResultNote={dataStatus === 'error' || itemStatus === 'error' ? 'Could not complete Search' : 'Found ' + data.length + ' Groups'} - GetEnum={() => { return () => { }; }} - > -
  • -
    - Actions: -
    - - -
    -
  • - - -
    - - cols={[ - { key: 'Name', field: 'Name', label: 'Name', headerStyle: { width: '15%' }, rowStyle: { width: '15%' } }, - { key: 'Description', field: 'Description', label: 'Description/Comments', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'Items', field: 'Items', label: 'Items', headerStyle: { width: '10%' }, rowStyle: { width: '10%' }, content: (item) => items.filter(i => i.GroupID === item.ID).length }, - { key: 'Scroll', label: '', headerStyle: { width: 17, padding: 0 }, rowStyle: { width: 0, padding: 0 } }, - ]} - tableClass="table table-hover" - data={data} - sortKey={sortKey} - ascending={asc} - onSort={(d) => { - if (d.colKey === 'remove' || d.colKey === 'scroll' || d.colField === undefined) - return; - setSortKey(d.colField); - if (d.colField === sortKey) - setASC((b) => !b) - else - setASC(true) - }} - onClick={(d) => props.OnValueListSelect(d.row.ID)} - theadStyle={{ fontSize: 'smaller', display: 'table', tableLayout: 'fixed', width: '100%' }} - tbodyStyle={{ display: 'block', overflowY: 'scroll', maxHeight: window.innerHeight - 300, width: '100%' }} - rowStyle={{ fontSize: 'smaller', display: 'table', tableLayout: 'fixed', width: '100%' }} - selected={(item) => false} - /> -
    - - 0 || !validName} ConfirmShowToolTip={newErrors.length > 0 || !validName} - ConfirmToolTipContent={ - <> - {newErrors.map((t,i) =>

    {CrossMark} {t}

    )} - {!validName?

    {CrossMark} The Name has to be unique.

    : null} - - } - CallBack={(c)=>{ - setShowNew(false); - if (c) - dispatch(props.ValueListSlice.DBAction({verb: 'POST', record})) - - }}> - -
    -
    - ) -} - -export default ByValueListGroup; diff --git a/common-pages/src/ValueList/Group.tsx b/common-pages/src/ValueList/Group.tsx deleted file mode 100644 index f234f4c1..00000000 --- a/common-pages/src/ValueList/Group.tsx +++ /dev/null @@ -1,101 +0,0 @@ -// ****************************************************************************************************** -// Group.tsx - Gbtc -// -// Copyright © 2020, 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/04/2021 - C. Lackner -// Generated original version of source code. -// ****************************************************************************************************** - -import * as React from 'react'; -import { SystemCenter, Application } from '@gpa-gemstone/application-typings'; -import { ServerErrorIcon, TabSelector, Warning } from '@gpa-gemstone/react-interactive' -import InfoWindow from './GroupInfo'; -import GroupItemsWindow from './GroupItem'; -import { useDispatch, useSelector } from 'react-redux'; -import { IGenericSlice } from '../SliceInterfaces'; -import { Dispatch } from '@reduxjs/toolkit'; - -interface IProps { - Id: number; - ValueListSlice: IGenericSlice; - ValueListItemSlice: IGenericSlice; - OnDelete: () => {}; -} - - export default function ValueListGroup (props: IProps) { - const dispatch = useDispatch>(); - - const record: SystemCenter.Types.ValueListGroup|undefined = useSelector((state) => props.ValueListSlice.Data(state).find(i => i.ID === props.Id)) - const recordStatus: Application.Types.Status = useSelector(props.ValueListSlice.Status) - - const [tab, setTab] = React.useState('items'); - const [showWarning, setShowWarning] = React.useState(false); - - React.useEffect(() => { - - if (recordStatus === 'unintiated' || recordStatus === 'changed') - dispatch(props.ValueListSlice.Fetch()); - }, [dispatch, recordStatus]); - - const Tabs = [ - { Id: "info", Label: "Value List Group Info" }, - { Id: "items", Label: "List Items" } - ]; - - - if (recordStatus === 'error' ) - return
    - -
    ; - - if (record == null) - return null; - - return ( -
    -
    -
    -

    {record.Name}

    -
    -
    - -
    -
    -
    - setTab(t)} Tabs={Tabs} /> -
    -
    - { - dispatch(props.ValueListSlice.DBAction({verb: 'PATCH', record: r})) - }}/> -
    -
    - -
    -
    - { - setShowWarning(false); - if (c) - { - dispatch(props.ValueListSlice.DBAction({verb: 'DELETE', record})) - props.OnDelete(); - } - - }}/> -
    - ) - } diff --git a/common-pages/src/ValueList/GroupForm.tsx b/common-pages/src/ValueList/GroupForm.tsx deleted file mode 100644 index 5d90d46a..00000000 --- a/common-pages/src/ValueList/GroupForm.tsx +++ /dev/null @@ -1,67 +0,0 @@ -// ****************************************************************************************************** -// GroupForm.tsx - Gbtc -// -// Copyright © 2020, 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/04/2021 - C. Lackner -// Generated original version of source code. -// ****************************************************************************************************** - -import * as React from 'react'; -import { SystemCenter } from '@gpa-gemstone/application-typings'; -import { Input, TextArea } from '@gpa-gemstone/react-forms'; - -interface IProps { - Record: SystemCenter.Types.ValueListGroup, - Setter: (record: SystemCenter.Types.ValueListGroup) => void, - ErrorSetter?: (errors: string[]) => void - } - -export default function GroupForm(props: IProps) { - const [errors, setErrors] = React.useState([]); - - React.useEffect(() => { - const e = []; - - if (props.Record.Name == null || props.Record.Name.length === 0) - e.push('A Name is required.') - if (props.Record.Name != null && props.Record.Name.length > 200) - e.push('Name has to be less than 200 characters.') - setErrors(e) - - }, [props.Record]) - - React.useEffect(() => { - if (props.ErrorSetter !== undefined) - props.ErrorSetter(errors); - }, [errors,props.ErrorSetter]); - - function Valid(field: keyof (SystemCenter.Types.ValueListGroup)): boolean { - if (field === 'Name') - return props.Record.Name != null && props.Record.Name.length > 0 && props.Record.Name.length <= 200; - else if (field === 'Description') - return true; - return false; - } - - return ( -
    - Record={props.Record} Field={'Name'} Feedback={'Name must be less than 200 characters.'} Valid={Valid} Setter={props.Setter} /> - Rows={3} Record={props.Record} Field={'Description'} Valid={Valid} Setter={props.Setter} /> - - - ); -} diff --git a/common-pages/src/ValueList/GroupInfo.tsx b/common-pages/src/ValueList/GroupInfo.tsx deleted file mode 100644 index e4c7767c..00000000 --- a/common-pages/src/ValueList/GroupInfo.tsx +++ /dev/null @@ -1,91 +0,0 @@ -// ****************************************************************************************************** -// GroupInfo.tsx - Gbtc -// -// Copyright © 2021, 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/04/2021 - C. Lackner -// Generated original version of source code. -// ****************************************************************************************************** - -import * as React from 'react'; -import { SystemCenter } from '@gpa-gemstone/application-typings'; -import GroupForm from './GroupForm'; -import { ToolTip } from '@gpa-gemstone/react-interactive'; -import { Warning, CrossMark } from '@gpa-gemstone/gpa-symbols'; - -interface IProps { - Record: SystemCenter.Types.ValueListGroup - Setter: (record: SystemCenter.Types.ValueListGroup) => void, - } - - const InfoWindow = (props: IProps) => { - const [record, setRecord] = React.useState(props.Record); - const [errors, setErrors] = React.useState([]); - const [warnings, setWarnings] = React.useState([]); - const [hover, setHover] = React.useState<('None' | 'Clear' | 'Update')>('None'); - - React.useEffect(() => { - const w = []; - - if (record == null) - return; - if (record.Name !== props.Record.Name) - w.push('Changes to Name will be lost.'); - if (record.Description !== props.Record.Description) - w.push('Changes to Description will be lost.'); - setWarnings(w) - }, [props.Record, record]); - - if (record == null) return null; - - return ( -
    -
    -
    -
    -

    Value List Group Information:

    -
    -
    -
    -
    - setRecord(r) } ErrorSetter={setErrors}/> -
    -
    -
    - -
    - 0)} Position={'top'} Theme={'dark'} Target={"Update"}> - {errors.map((t, i) =>

    {CrossMark} {t}

    )} -
    -
    - -
    - 0)} Position={'top'} Theme={'dark'} Target={"Clr"}> - {warnings.map((t, i) =>

    {Warning} {t}

    )} -
    -
    - - -
    - ); - } - - export default InfoWindow; diff --git a/common-pages/src/ValueList/GroupItem.tsx b/common-pages/src/ValueList/GroupItem.tsx deleted file mode 100644 index 45476641..00000000 --- a/common-pages/src/ValueList/GroupItem.tsx +++ /dev/null @@ -1,144 +0,0 @@ -// ****************************************************************************************************** -// GroupItem.tsx - Gbtc -// -// Copyright © 2021, 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/04/2021 - C. Lackner -// Generated original version of source code. -// ****************************************************************************************************** - -import * as React from 'react'; -import { SystemCenter, Application } from '@gpa-gemstone/application-typings'; -import ItemForm from './ItemForm'; -import { Modal } from '@gpa-gemstone/react-interactive'; -import { SearchableTable } from '@gpa-gemstone/react-table'; -import { CrossMark } from '@gpa-gemstone/gpa-symbols'; -import { useDispatch, useSelector } from 'react-redux'; -import { IGenericSlice } from '../SliceInterfaces'; -import { Dispatch } from '@reduxjs/toolkit'; - -interface IProps { - Record: SystemCenter.Types.ValueListGroup - ValueListItemSlice: IGenericSlice; - } - - -export default function GroupItemsWindow(props: IProps) { - const dispatch = useDispatch>(); - - const recordStatus: Application.Types.Status = useSelector(props.ValueListItemSlice.Status); - const data: SystemCenter.Types.ValueListItem[] = useSelector(props.ValueListItemSlice.Data); - const parentID: number|string = useSelector((props.ValueListItemSlice.ParentID === undefined? (state: any) => -1 : props.ValueListItemSlice.ParentID)); - const [sortField, setSortField] = React.useState('Value'); - const [ascending,setAscending] = React.useState(false); - - const emptyRecord: SystemCenter.Types.ValueListItem = { ID: 0, GroupID: props.Record.ID, Value: '', AltValue: '', SortOrder: 0 }; - const [record, setRecord] = React.useState(emptyRecord); - - const [showNew, setShowNew] = React.useState(false); - const [newErrors, setNewErrors] = React.useState([]); - - const [validValue, setValidValue] = React.useState(true); - - React.useEffect(() => { - if (recordStatus === 'unintiated' || recordStatus === 'changed' || parentID !== props.Record.ID) - dispatch(props.ValueListItemSlice.Fetch(props.Record.ID)) - }, [recordStatus,dispatch]); - - React.useEffect(() => { - dispatch(props.ValueListItemSlice.Sort({Ascending: ascending, SortField: sortField})); - }, [sortField, ascending]) - - React.useEffect(() => { - if (record.Value === null) - setValidValue(true) - if (data.findIndex((d) => d.Value.toLowerCase() === record.Value.toLowerCase()) > -1 ) - setValidValue(false) - else - setValidValue(true) - }, [record, data]) - - return ( -
    -
    -
    -
    -

    List Items:

    -
    -
    -
    -
    -
    -
    - - cols={[ - { key: 'Value', field: 'Value', label: 'Value', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'AltValue', field: 'AltValue', label: 'Alternate Value', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'SortOrder', field: 'SortOrder', label: 'Sort Order', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'remove', label: '', headerStyle: { width: '10%' }, rowStyle: { width: '10%' }, content: (item) => - }, - { key: 'scroll', label: '', headerStyle: { width: 17, padding: 0 }, rowStyle: { width: 0, padding: 0 } }, - ]} - tableClass="table table-hover" - data={data} - sortKey={sortField} - onSort={(d) => { - if (d.colKey === 'remove' || d.colKey === 'scroll' || d.colField === undefined) - return; - setSortField(d.colField); - if (d.colField === sortField) - setAscending((asc) => !asc) - else - setAscending(true) - }} - ascending - onClick={() => {}} - theadStyle={{ fontSize: 'smaller', display: 'table', tableLayout: 'fixed', width: '100%' }} - tbodyStyle={{ display: 'block', overflowY: 'scroll', maxHeight: window.innerHeight - 300, width: '100%' }} - rowStyle={{ fontSize: 'smaller', display: 'table', tableLayout: 'fixed', width: '100%' }} - selected={(item) => false} - matchSearch={(item,search) => item.Value.toLowerCase().indexOf(search.toLowerCase()) > -1} - /> -
    -
    -
    -
    -
    - -
    -
    - 0 || !validValue} ConfirmShowToolTip={newErrors.length > 0 || !validValue} - ConfirmToolTipContent={ <> - {newErrors.map((t,i) =>

    {CrossMark} {t}

    )} - {!validValue?

    {CrossMark} A Value has to be unique in a given Item List.

    : null} - } - CallBack={(c) => { - setShowNew(false); - if (c) - dispatch(props.ValueListItemSlice.DBAction({verb: 'POST', record})) - }} - > - -
    - -
    - - ); - -} diff --git a/common-pages/src/ValueList/ItemForm.tsx b/common-pages/src/ValueList/ItemForm.tsx deleted file mode 100644 index 6c76b423..00000000 --- a/common-pages/src/ValueList/ItemForm.tsx +++ /dev/null @@ -1,75 +0,0 @@ -// ****************************************************************************************************** -// ItemForm.tsx - Gbtc -// -// Copyright © 2020, 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/04/2021 - C. Lackner -// Generated original version of source code. -// ****************************************************************************************************** - -import * as React from 'react'; -import { SystemCenter } from '@gpa-gemstone/application-typings'; -import { Input } from '@gpa-gemstone/react-forms'; -import { IsInteger } from '@gpa-gemstone/helper-functions'; - -interface IProps { - Record: SystemCenter.Types.ValueListItem, - Setter: (record: SystemCenter.Types.ValueListItem) => void, - ErrorSetter?: (errors: string[]) => void - } - -export default function ItemForm(props: IProps) { - const [errors, setErrors] = React.useState([]); - - React.useEffect(() => { - const e = []; - - if (props.Record.Value == null || props.Record.Value.length === 0) - e.push('A Value is required.') - if (props.Record.Value != null && props.Record.Value.length > 200) - e.push('Value has to be less than 200 characters.') - if (props.Record.AltValue != null && props.Record.AltValue.length > 200) - e.push('Alt Value has to be less than 200 characters.') - if (props.Record.SortOrder != null && !IsInteger(props.Record.SortOrder)) - e.push('Sort Order has to be an integer or be left blank.') - setErrors(e) - - }, [props.Record]) - - React.useEffect(() => { - if (props.ErrorSetter !== undefined) - props.ErrorSetter(errors); - }, [errors,props.ErrorSetter]); - - function Valid(field: keyof (SystemCenter.Types.ValueListItem)): boolean { - if (field === 'Value') - return props.Record.Value != null && props.Record.Value.length > 0 && props.Record.Value.length <= 200; - else if (field === 'AltValue') - return props.Record.AltValue == null || props.Record.AltValue.length <= 200; - else if (field === 'SortOrder') - return props.Record.SortOrder == null || IsInteger(props.Record.SortOrder) - return true; - } - - return ( -
    - Record={props.Record} Field={'Value'} Feedback={'Value must be set and be less than 200 characters.'} Valid={Valid} Setter={props.Setter} /> - Record={props.Record} Field={'AltValue'} Label={'Alt. Value'} Feedback={'Alt. Value must be less than 200 characters.'} Valid={Valid} Setter={props.Setter} /> - Record={props.Record} Field={'SortOrder'} Label={'Sort Order'} Type='number' Valid={Valid} Setter={props.Setter} /> - - - ); -} diff --git a/common-pages/src/index.tsx b/common-pages/src/index.tsx index 3971b7a8..39b8c400 100644 --- a/common-pages/src/index.tsx +++ b/common-pages/src/index.tsx @@ -21,12 +21,7 @@ // // ****************************************************************************************************** -import Setting from './Setting'; import Note from './Note'; -import ByValueList from './ValueList/ByValueList'; -import ValueList from './ValueList/Group'; -import ByUser from './user/ByUser'; -import User from './user/User'; import { DefaultSearch } from './SearchBar'; import SelectPopup from './StandardSelectPopup'; import { DefaultSelects } from './SelectionPopup'; @@ -43,12 +38,7 @@ export { EventTypeFilter, EventCharacteristicFilter, NavBarFilterButton, - Setting, Note, - ValueList, - ByValueList, - User, - ByUser, DefaultSearch, SelectPopup, DefaultSelects, diff --git a/common-pages/src/user/AdditionalField.tsx b/common-pages/src/user/AdditionalField.tsx deleted file mode 100644 index cb3cc698..00000000 --- a/common-pages/src/user/AdditionalField.tsx +++ /dev/null @@ -1,337 +0,0 @@ -// ****************************************************************************************************** -// AdditionalField.tsx - Gbtc -// -// Copyright © 2020, 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/14/2021 - C. Lackner -// Generated original version of source code. -// ****************************************************************************************************** - -import * as React from 'react'; -import Table from '@gpa-gemstone/react-table'; -import { CrossMark, HeavyCheckMark, Warning } from '@gpa-gemstone/gpa-symbols'; -import { Modal, ToolTip, ServerErrorIcon, Warning as WarningModal } from '@gpa-gemstone/react-interactive'; -import { SystemCenter, Application } from '@gpa-gemstone/application-typings'; -import { CheckBox, Input, Select } from '@gpa-gemstone/react-forms'; -import {IAdditionalFieldSlice, IGenericSlice} from '../SliceInterfaces'; -import { useDispatch, useSelector } from 'react-redux'; -import { IsInteger, IsNumber } from '@gpa-gemstone/helper-functions'; -import { Dispatch } from '@reduxjs/toolkit'; - -interface IField { - FieldName: string, - Type: string, -} - -interface IValue { - Value: string| number, - ID: number -} - -interface IProps { - Id: string|number, - AdditionalFieldSlice: IAdditionalFieldSlice, - ValueListItemSlice: IGenericSlice - ValueListGroupSlice: IGenericSlice, - EmptyField: Field - GetFieldValueIndex: (field: Field, values: Value[]) => number, - GetFieldIndex: (value: Value, fields: Field[]) => number, - FieldKeySelector: (field: Field) => string, - ValidateField: (field: Field) => string[], - CreateValue: (field: Field) => Value, - FieldUI: (field: Field, setField: (field: Field) => void) => JSX.Element -} - -function AdditionalField(props: IProps) { - const dispatch = useDispatch>(); - - const valueListItems: SystemCenter.Types.ValueListItem[] = useSelector(props.ValueListItemSlice.Data); - const valueListItemStatus: Application.Types.Status = useSelector(props.ValueListItemSlice.Status); - - const valueListGroups: SystemCenter.Types.ValueListGroup[] = useSelector(props.ValueListGroupSlice.Data); - const valueListGroupStatus: Application.Types.Status = useSelector(props.ValueListGroupSlice.Status); - - const fields: Field[] = useSelector(props.AdditionalFieldSlice.Fields); - const values: Value[] = useSelector(props.AdditionalFieldSlice.Values); - const fieldStatus: Application.Types.Status = useSelector(props.AdditionalFieldSlice.FieldStatus); - const valueStatus: Application.Types.Status = useSelector(props.AdditionalFieldSlice.ValueStatus); - const valueParentID: number|string = useSelector(props.AdditionalFieldSlice.ValueParentId); - - const [pageStatus,setPageStatus] = React.useState('unintiated'); - - const [editValues, setEditValues] = React.useState([]); - - const sortField: keyof Field = useSelector(props.AdditionalFieldSlice.SortField); - const ascending: boolean = useSelector(props.AdditionalFieldSlice.Ascending); - - const [newField, setNewField] = React.useState(props.EmptyField); - const [showWarning, setShowWarning] = React.useState(false); - const [showEdit, setShowEdit] = React.useState(false); - - const [hover, setHover] = React.useState<('None' | 'Save' | 'New' | 'View' | 'Clear')>('None'); - - const [mode, setMode] = React.useState<'Edit'|'View'>('View') - - const [changedFields, setChangedFields] = React.useState([]); - const [errorFields, setErrorFields] = React.useState([]); - const [fieldErrors, setFieldErrors] = React.useState([]); - - React.useEffect(() => { - if (fieldStatus === 'error' || valueStatus === 'error' || valueListGroupStatus === 'error' || valueListItemStatus=== 'error') - setPageStatus('error') - else if (fieldStatus === 'loading' || valueStatus === 'loading' || valueListGroupStatus === 'loading' || valueListItemStatus=== 'loading') - setPageStatus('loading') - else - setPageStatus('idle'); - }, [fieldStatus, valueStatus, valueListGroupStatus, valueListItemStatus ]) - - React.useEffect(() => { - if (fieldStatus === 'unintiated' || fieldStatus === 'changed') - dispatch(props.AdditionalFieldSlice.FetchField()); - }, [dispatch,fieldStatus]); - - React.useEffect(() => { - if (valueStatus === 'unintiated' || valueStatus === 'changed' || props.Id !== valueParentID) - dispatch(props.AdditionalFieldSlice.FetchValues(props.Id)); - }, [dispatch,valueStatus,props.Id, valueParentID]); - - React.useEffect(() => { - if (valueListItemStatus === 'unintiated' || valueListItemStatus === 'changed') - dispatch(props.ValueListItemSlice.Fetch()); - }, [dispatch,valueListItemStatus]); - - React.useEffect(() => { - if (valueListGroupStatus === 'unintiated' || valueListGroupStatus === 'changed') - dispatch(props.ValueListGroupSlice.Fetch()); - }, [dispatch,valueListGroupStatus]); - - React.useEffect(() => { setEditValues(values) }, [values]) - - const typeOptions = [{ Value: 'string', Label: 'string' }, { Value: 'integer', Label: 'integer' }, { Value: 'number', Label: 'number' }].concat(valueListGroups.map(x => { return { Value: x.Name, Label: x.Name } })); - - React.useEffect(() => { - const e = props.ValidateField(newField); - if (newField.FieldName == null || newField.FieldName.length === 0) - e.push('A FieldName is required') - else if (fields.findIndex(f => f.FieldName.toLowerCase() === newField.FieldName.toLowerCase() && props.FieldKeySelector(f) !== props.FieldKeySelector(newField)) > -1) - e.push('A Field with this FieldName already exists') - setFieldErrors(e); - }, [newField]) - - React.useEffect(() => { - const c: string[] = []; - const e: string[] = []; - - editValues.forEach(v => { - const eIndex = values.findIndex(val => val.ID === v.ID); - const fldIndex = props.GetFieldIndex(v,fields); - if (eIndex === -1 && fldIndex > -1) - c.push(fields[fldIndex].FieldName); - else if (fldIndex > -1 && v.Value !== values[eIndex].Value) - c.push(fields[fldIndex].FieldName); - - if (fldIndex > -1 && fields[fldIndex].Type === 'integer' && !IsInteger(v.Value)) - e.push("'" + fields[fldIndex].FieldName + "' has to be a valid integer") - if (fldIndex > -1 && fields[fldIndex].Type === 'number' && !IsNumber(v.Value)) - e.push("'" + fields[fldIndex].FieldName + "' has to be a valid number") - }) - - setErrorFields(e); - setChangedFields(c) - }, [values,editValues]) - - if (pageStatus === 'error') - return
    -
    -
    -
    -

    Additional Fields:

    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    - - return ( -
    -
    -
    -
    -

    Additional Fields:

    -
    -
    - {(mode === 'Edit') ? - : - } - 0} Position={'left'} Theme={'dark'} Target={"View"}> - {changedFields.map((fld,i) =>

    {Warning} Changes to '{fld}' will be lost.

    )} -
    -
    -
    - -
    -
    - - cols={[ - { key: 'FieldName', field: 'FieldName', label: 'Field', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'Type', field: 'Type', label: 'Type', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { - key: 'Value', label: 'Value', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' }, content: (item) => { - let valueListgrpId = valueListGroups.findIndex(g => g.Name === item.Type); - valueListgrpId = (valueListgrpId > -1? valueListGroups[valueListgrpId].ID : -1); - const vList = valueListItems.filter(i => i.GroupID === valueListgrpId); - const valIdx = props.GetFieldValueIndex(item,editValues); - if (valIdx > -1) - return setEditValues((d) => {const u = [...d]; u[valIdx] = val; return u;})} /> - return setEditValues((d) => {const u = [...d]; u.push(val); return u;})} /> - } - }, - { key: 'EditButton', label: '', headerStyle: { width: 40, paddingRight: 0, paddingLeft: 10 }, rowStyle: { width: 40, paddingRight: 0, paddingLeft: 10, paddingTop: 36 }, content: (item) => (mode === 'Edit' ? : '') }, - { key: 'DeleteButton', label: '', headerStyle: { width: 40, paddingLeft: 0, paddingRight: 10 }, rowStyle: { width: 40, paddingLeft: 0, paddingTop: 36, paddingRight: 10 }, content: (item) => (mode === 'Edit' ? : '') }, - - ]} - tableClass="table table-hover" - data={fields} - sortKey={sortField as string} - ascending={ascending} - onSort={(d) => { - if (d.colField === undefined) - return; - if (d.colKey === sortField) - dispatch(props.AdditionalFieldSlice.Sort({SortField: d.colField, Ascending: !ascending})) - else - dispatch(props.AdditionalFieldSlice.Sort({SortField: d.colField, Ascending: true})) - }} - onClick={() => { }} - theadStyle={{ fontSize: 'smaller', display: 'table', tableLayout: 'fixed', width: '100%' }} - tbodyStyle={{ display: 'block', overflowY: 'scroll', maxHeight: window.innerHeight - 455,}} - rowStyle={{display: 'table', tableLayout: 'fixed', width: '100%' }} - selected={() => false} - keySelector={props.FieldKeySelector} - /> -
    -
    -
    - -
    - -

    To add a new Field switch to Edit mode by clicking on the Edit Button on the upper right corner.

    -
    -
    - -
    - 0)} Position={'top'} Theme={'dark'} Target={"SaveValues"}> - {mode==='View' ?

    To change any Fields switch to Edit mode by clicking on the Edit Button on the upper right corner.

    : null} - {changedFields.length > 0 && errorFields.length === 0 ? changedFields.map((fld,i) =>

    {HeavyCheckMark } Changes to '{fld}' are valid.

    ) : null} - {changedFields.length > 0 && errorFields.length > 0 ? errorFields.map((t,i) =>

    {CrossMark} {t}.

    ) : null} -
    -
    - -
    - 0)} Position={'top'} Theme={'dark'} Target={'Reset'}> - {mode === 'View'?

    To change any Fields switch to Edit mode by clicking on the Edit Button on the upper right corner.

    : null} - {changedFields.length > 0? changedFields.map((fld,i) =>

    {Warning} Changes to '{fld}' will be lost.

    ) : null } -
    -
    - { if (confirm) dispatch(props.AdditionalFieldSlice.FieldAction({Verb: 'DELETE', Record: newField})); setShowWarning(false) }} /> - - 0 ? ' disabled' : '')} - Show={showEdit} Size={'lg'} - CallBack={(confirmation) => { - if (confirmation) { - if (props.FieldKeySelector(newField) === "new") - dispatch(props.AdditionalFieldSlice.FieldAction({Verb: "POST", Record: newField})) - else - dispatch(props.AdditionalFieldSlice.FieldAction({Verb: "PATCH", Record: newField})) - } - - setShowEdit(false); - }} - ConfirmShowToolTip={fieldErrors.length > 0} - ConfirmToolTipContent={fieldErrors.map((t,i) =>

    {CrossMark} {t}

    )} - > - Record={newField} Field='FieldName' Valid={(field) => - newField.FieldName != null && newField.FieldName.length > 0 - && fields.findIndex(f => f.FieldName.toLowerCase() === newField.FieldName.toLowerCase() && props.FieldKeySelector(f) !== props.FieldKeySelector(newField)) < 0} - Label="Field Name" Setter={setNewField} Feedback={'The additional field needs to have a unique Field Name'} /> - Record={newField} Field='Type' Options={typeOptions} Label="Field Type" Setter={setNewField} /> - {props.FieldUI !== undefined? props.FieldUI(newField,setNewField) : null} -
    -
    - - ); - -} - -export default AdditionalField; - -interface IValueDisplayProps { - Type: string, - ValueListItems: SystemCenter.Types.ValueListItem[], - Value: V, - Setter: (val: V) => void, - Mode: 'Edit'|'View' -} - -function ValueDisplay (props: IValueDisplayProps) { - - React.useEffect(() => { - if (props.Type === 'integer' || props.Type === 'number' || props.Type === 'string') - return; - else if (props.Type !== 'boolean' && - props.ValueListItems.findIndex(i => i.Value.toLowerCase() === props.Value.Value.toString().toLowerCase()) < 0 - && props.ValueListItems.length > 0) - props.Setter({...props.Value, Value: props.ValueListItems[0].Value}) - }, [props.Type, props.Value, props.ValueListItems]) - - if (props.Mode === 'View') { - if (props.Type === 'boolean') - return {props.Value.Value.toString().toLowerCase() === "true" ? "true" : "false"} - else - return {props.Value.Value}; - } - - if (props.Type === 'number') - return Record={props.Value} Field={'Value'} Valid={() => IsInteger(props.Value.Value)} Label={''} Type={'number'} Setter={props.Setter} Feedback={'Thi Field is a numeric field.'} /> - if (props.Type === 'integer') - return Record={props.Value} Field={'Value'} Valid={() => IsNumber(props.Value.Value)} Label={''} Type={'number'} Setter={props.Setter} Feedback={'Thi Field is an integer field.'} /> - else if (props.Type === 'string') - return Record={props.Value} Field={'Value'} Valid={() => true} Label={''} Type={'text'} Setter={props.Setter} /> - else if (props.Type === 'boolean') - return Record={props.Value} Field={'Value'} Label={''} Setter={props.Setter} /> - else - return EmptyOption={true} Record={props.Value} Field={'Value'} Label={''} Setter={props.Setter} - Options={props.ValueListItems.map(x => ({ Value: x.ID.toString(), Label: x.Value }))}/> - - } diff --git a/common-pages/src/user/ByUser.tsx b/common-pages/src/user/ByUser.tsx deleted file mode 100644 index ea219b78..00000000 --- a/common-pages/src/user/ByUser.tsx +++ /dev/null @@ -1,216 +0,0 @@ -// ****************************************************************************************************** -// ByUser.tsx - Gbtc -// -// Copyright © 2020, 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/14/2021 - C. Lackner -// Generated original version of source code. -// ****************************************************************************************************** - -import * as React from 'react'; -import Table from '@gpa-gemstone/react-table'; -import { CrossMark } from '@gpa-gemstone/gpa-symbols'; -import { SearchBar, Search, Modal, ServerErrorIcon, LoadingScreen } from '@gpa-gemstone/react-interactive'; -import { SystemCenter, Application } from '@gpa-gemstone/application-typings'; -import * as CryptoJS from 'crypto-js'; -import * as _ from 'lodash'; -import UserForm from './UserForm'; -import { IAdditionalFieldSlice, IGenericSlice, IUserAccountSlice } from '../SliceInterfaces'; -import { useDispatch, useSelector } from 'react-redux'; -import { Dispatch } from '@reduxjs/toolkit'; - -interface IProps { - UserSlice: IUserAccountSlice, - AdditionalFieldSlice: IAdditionalFieldSlice, - ValueListItemSlice: IGenericSlice, - ValueListGroupSlice: IGenericSlice, - OnUserSelect: (userID: string) => void, -} - - - -const defaultSearchcols: Search.IField[] = [ - { label: 'First Name', key: 'FirstName', type: 'string', isPivotField: false }, - { label: 'Last Name', key: 'LastName', type: 'string', isPivotField: false }, - { label: 'Location', key: 'Location', type: 'string', isPivotField: false }, - { label: 'Phone', key: 'Phone', type: 'string', isPivotField: false }, - { label: 'Email', key: 'Email', type: 'string', isPivotField: false }, -]; - - -function ByUser(props: IProps) { - const dispatch = useDispatch>(); - - const search: Search.IFilter[] = useSelector(props.UserSlice.SearchFilters); - - const data: Application.Types.iUserAccount[] = useSelector(props.UserSlice.SearchResults); - const userStatus: Application.Types.Status = useSelector(props.UserSlice.Status); - const searchStatus: Application.Types.Status = useSelector(props.UserSlice.SearchStatus); - - const sortField: keyof Application.Types.iUserAccount = useSelector(props.UserSlice.SortField); - const ascending: boolean = useSelector(props.UserSlice.Ascending); - - const currentUserAccount: Application.Types.iUserAccount = useSelector(props.UserSlice.CurrentUser); - - const adlFields: Application.Types.iAdditionalUserField[] = useSelector(props.AdditionalFieldSlice.Fields) - const adlFieldStatus: Application.Types.Status = useSelector(props.AdditionalFieldSlice.FieldStatus) - - const [filterableList, setFilterableList] = React.useState[]>(defaultSearchcols); - - const [showModal, setShowModal] = React.useState(false); - const [userError, setUserError] = React.useState([]); - - const valueListItems: SystemCenter.Types.ValueListItem[] = useSelector(props.ValueListItemSlice.Data); - const valueListItemStatus: Application.Types.Status = useSelector(props.ValueListItemSlice.Status); - - const valueListGroups: SystemCenter.Types.ValueListGroup[] = useSelector(props.ValueListGroupSlice.Data); - const valueListGroupStatus: Application.Types.Status = useSelector(props.ValueListGroupSlice.Status); - - - const [pageStatus, setPageStatus] = React.useState('unintiated'); - - React.useEffect(() => { - if (userStatus === 'error' || adlFieldStatus === 'error' || valueListItemStatus === 'error' || valueListGroupStatus === 'error') - setPageStatus('error') - else if (userStatus === 'loading' || adlFieldStatus === 'loading' || valueListItemStatus === 'loading' || valueListGroupStatus === 'loading') - setPageStatus('loading') - else - setPageStatus('idle'); - }, [userStatus, adlFieldStatus, valueListItemStatus, valueListGroupStatus ]) - - React.useEffect(() => { - if (adlFieldStatus === 'unintiated' || adlFieldStatus === 'changed') - dispatch(props.AdditionalFieldSlice.FetchField()); - }, [dispatch,adlFieldStatus]); - - React.useEffect(() => { - dispatch(props.UserSlice.DBSearch({sortField, ascending, filter: search})); - dispatch(props.UserSlice.SetNewUser()); - }, [dispatch]); - - React.useEffect(() => { - if (valueListItemStatus === 'unintiated' || valueListItemStatus === 'changed') - dispatch(props.ValueListItemSlice.Fetch()); - }, [dispatch,valueListItemStatus]); - - React.useEffect(() => { - if (valueListGroupStatus === 'unintiated' || valueListGroupStatus === 'changed') - dispatch(props.ValueListGroupSlice.Fetch()); - }, [dispatch,valueListGroupStatus]); - - - React.useEffect(() => { - function ConvertType(type: string) { - if (type === 'string' || type === 'integer' || type === 'number' || type === 'datetime' || type === 'boolean') - return { type } - return { type: 'enum', enum: [{ Label: type, Value: type }] } - } - const ordered = _.orderBy(defaultSearchcols.concat(adlFields.map(item => ( - { label: `[AF] ${item.FieldName}`, key: item.FieldName, ...ConvertType(item.Type) } as Search.IField - ))), ['label'], ["asc"]); - setFilterableList(ordered) - }, [adlFields]); - - if (pageStatus === 'error') - return
    - -
    ; - - return ( -
    - - CollumnList={filterableList} SetFilter={(flds) => dispatch(props.UserSlice.DBSearch({sortField, ascending, filter: flds}))} - Direction={'left'} defaultCollumn={{ label: 'Last Name', key: 'LastName', type: 'string', isPivotField: false }} Width={'50%'} Label={'Search'} - ShowLoading={searchStatus === 'loading'} ResultNote={searchStatus === 'error' ? 'Could not complete Search' : 'Found ' + data.length + ' UserAccounts'} - GetEnum={(setOptions, field) => { - - if (field.type !== 'enum' || field.enum === undefined || field.enum.length !== 1) - return () => { }; - - const grpName = (field.enum !== undefined? field.enum[0].Value.toLowerCase() : '') - const grpIndex = valueListGroups.findIndex(g => g.Name.toLowerCase() === grpName) - if (grpIndex < 0) - return () => {} - - setOptions(valueListItems.filter(v => v.GroupID === valueListGroups[grpIndex].ID).map(item => ({ Value: item.ID.toString(), Label: item.Value }))); - return () => {} - }} - - > -
  • -
    - Actions: -
    - - -
    -
  • - - -
    - - cols={[ - { key: 'Name', field: 'Name', label: 'User Name', headerStyle: { width: '10%' }, rowStyle: { width: '10%' } }, - { key: 'FirstName', field: 'FirstName', label: 'First Name', headerStyle: { width: '10%' }, rowStyle: { width: '10%' } }, - { key: 'LastName', field: 'LastName', label: 'Last Name', headerStyle: { width: '10%' }, rowStyle: { width: '10%' } }, - { key: 'Phone', field: 'Phone', label: 'Phone', headerStyle: { width: '10%' }, rowStyle: { width: '10%' } }, - { key: 'Email', field: 'Email', label: 'Email', headerStyle: { width: 'auto' }, rowStyle: { width: 'auto' } }, - { key: 'scroll', label: '', headerStyle: { width: 17, padding: 0 }, rowStyle: { width: 0, padding: 0 } }, - ]} - tableClass="table table-hover" - data={data} - sortKey={sortField} - ascending={ascending} - onSort={(d) => { - if (d.colField === undefined) - return; - if (d.colField !== sortField) - dispatch(props.UserSlice.DBSearch({sortField, ascending: !ascending, filter: search})) - else - dispatch(props.UserSlice.DBSearch({sortField: d.colField, ascending: true, filter: search})) - - - }} - onClick={(d) => props.OnUserSelect(d.row.ID)} - theadStyle={{ fontSize: 'smaller', display: 'table', tableLayout: 'fixed', width: '100%' }} - tbodyStyle={{ display: 'block', overflowY: 'scroll', maxHeight: window.innerHeight - 300, width: '100%' }} - rowStyle={{ fontSize: 'smaller', display: 'table', tableLayout: 'fixed', width: '100%' }} - selected={(item) => false} - /> -
    - { - if (confirm) - dispatch(props.UserSlice.DBAction({verb: 'POST', record: {...currentUserAccount, Password: CryptoJS.SHA256(currentUserAccount.Password + "0").toString(CryptoJS.enc.Base64) }})) - dispatch(props.UserSlice.SetNewUser()); - setShowModal(false); - }} - ConfirmShowToolTip={userError.length > 0} - ConfirmToolTipContent={userError.map((t, i) =>

    {CrossMark} {t}

    )} - DisableConfirm={userError.length > 0} - > - {currentUserAccount !== undefined? dispatch(props.UserSlice.SetCurrentUser(u))} - Edit={false} SetErrors={setUserError} UserSlice={props.UserSlice} - - /> : null } -
    -
    - ) - -} - -export default ByUser; diff --git a/common-pages/src/user/User.tsx b/common-pages/src/user/User.tsx deleted file mode 100644 index e151751a..00000000 --- a/common-pages/src/user/User.tsx +++ /dev/null @@ -1,119 +0,0 @@ -// ****************************************************************************************************** -// User.tsx - Gbtc -// -// Copyright © 2020, 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/14/2021 - C. Lackner -// Generated original version of source code. -// ****************************************************************************************************** - -import * as React from 'react'; -import { LoadingScreen, ServerErrorIcon, TabSelector, Warning } from '@gpa-gemstone/react-interactive'; -import { Application, SystemCenter } from '@gpa-gemstone/application-typings'; -import UserInfo from './UserInfo'; -import UserPermissions from './UserPermissions'; -import AdditionalField from './AdditionalField' -import { IAdditionalFieldSlice, IGenericSlice, ISecurityRoleSlice, IUserAccountSlice } from '../SliceInterfaces'; -import { useDispatch, useSelector } from 'react-redux'; -import { CheckBox } from '@gpa-gemstone/react-forms'; -import { Dispatch } from '@reduxjs/toolkit'; - -interface IProps { - UserID: string, - OnDelete: () => void, - SecurityRoleSlice: ISecurityRoleSlice, - UserSlice: IUserAccountSlice, - AdditionalFieldSlice: IAdditionalFieldSlice, - ValueListItemSlice: IGenericSlice, - ValueListGroupSlice: IGenericSlice -} - -function User(props: IProps) { - const dispatch = useDispatch>(); - - const user: Application.Types.iUserAccount = useSelector(props.UserSlice.CurrentUser); - const status: Application.Types.Status = useSelector(props.UserSlice.Status); - - const [tab, setTab] = React.useState('userInfo') - - const [showWarning, setShowWarning] = React.useState(false); - - React.useEffect(() => { - dispatch(props.UserSlice.LoadExistingUser(props.UserID)) - }, [dispatch, props.UserID]) - - if (status === 'error') - return
    - -
    ; - - const Tabs = [ - { Id: "userInfo", Label: "User Info" }, - { Id: "permissions", Label: "Permissions" }, - { Id: "additionalFields", Label: "Additional Fields" } - ]; - - return ( -
    -
    -
    -

    {user != null ? `${user.FirstName} ${user.LastName}` : ''}

    -
    -
    - -
    -
    - -
    - setTab(t)} Tabs={Tabs} /> -
    -
    - -
    -
    - {user == null? null : } -
    -
    - - Id={props.UserID} - AdditionalFieldSlice={props.AdditionalFieldSlice} - ValueListItemSlice={props.ValueListItemSlice} - ValueListGroupSlice={props.ValueListGroupSlice} - EmptyField={{ID: -1, IsSecure: false, FieldName: '', Type: 'string'}} - GetFieldValueIndex={(field, values) => values.findIndex(v => v.AdditionalUserFieldID === field.ID)} - GetFieldIndex={(value, fields) => fields.findIndex(f => f.ID === value.AdditionalUserFieldID)} - FieldKeySelector={(field) => (field.ID === -1? 'new' : field.ID.toString())} - ValidateField={() => []} - FieldUI={(fld, setter) => Record={fld} Field='IsSecure' Label="Secure Data" Setter={setter} />} - CreateValue={(fld) => ({Value: '', ID: -1, UserAccountID: props.UserID, AdditionalUserFieldID: fld.ID})} - /> -
    - -
    - { - setShowWarning(false); - if (c) { - dispatch(props.UserSlice.DBAction({verb: 'DELETE', record: user})); - props.OnDelete(); - } - }}/> -
    - ) - - -} - -export default User; diff --git a/common-pages/src/user/UserForm.tsx b/common-pages/src/user/UserForm.tsx deleted file mode 100644 index 759a51d5..00000000 --- a/common-pages/src/user/UserForm.tsx +++ /dev/null @@ -1,178 +0,0 @@ -// ****************************************************************************************************** -// UserForm.tsx - Gbtc -// -// Copyright © 2020, 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/14/2021 - C. Lackner -// Generated original version of source code. -// ****************************************************************************************************** - -import * as React from 'react'; -import { Application } from '@gpa-gemstone/application-typings'; -import {Input, DatePicker, CheckBox} from '@gpa-gemstone/react-forms' -import * as _ from 'lodash'; -import { IUserAccountSlice, UserValidation } from '../SliceInterfaces'; -import { useDispatch, useSelector } from 'react-redux'; -import { Dispatch } from '@reduxjs/toolkit'; - -interface IProps { - UserAccount: Application.Types.iUserAccount, - Setter: (record: Application.Types.iUserAccount) => void, - Edit: boolean, - SetErrors?: (e: string[]) => void - UserSlice: IUserAccountSlice, - } - -function UserForm(props: IProps) { - const dispatch = useDispatch>(); - - const [updatedAD, setUpdatedAD] = React.useState(false); - const userValidation: UserValidation = useSelector(props.UserSlice.ADValidation); - - const [userError, setUserError] = React.useState([]); - - React.useEffect(() => { - if (userValidation === 'Valid' && !props.Edit && updatedAD === false) - dispatch(props.UserSlice.ADUpdate()); - }, [userValidation, updatedAD]) - - React.useEffect(() => { - if (props.SetErrors !== undefined) - props.SetErrors(userError); - }, [userError, props.SetErrors]); - - React.useEffect(() => { - if (props.UserAccount == null) - return - const e = []; - if (props.UserAccount.Name == null || props.UserAccount.Name.length === 0) - e.push('An AccountName is required.') - if (props.UserAccount.UseADAuthentication && userValidation !== 'Valid') - e.push('The user could not be validated by the AD.') - - setUserError(e); - }, [props.UserAccount, userValidation]) - - function validUserAccountField(user: Application.Types.iUserAccount,field: keyof (Application.Types.iUserAccount)): boolean { - if (field === 'Name') - return user.Name != null && user.Name.length > 0 && user.Name.length <= 200; - else if (field === 'Password') - return user.Password == null || user.Password.length <= 200; - else if (field === 'FirstName') - return user.FirstName == null || user.FirstName.length <= 200; - else if (field === 'LastName') - return user.LastName == null || user.LastName.length <= 200; - else if (field === 'Phone') - return user.Phone == null || user.Phone.length <= 200; - else if (field === 'Email') - return user.Email == null || user.Email.length <= 200; - else if (field === "ChangePasswordOn") - return user.ChangePasswordOn == null; - return false; - -} - - - if (props.UserAccount == null) return null; - - return ( - <> -
    -
    -
    - Record={props.UserAccount} Disabled={props.Edit} Field={'Name'} Feedback={'A Name of less than 200 characters is required.'} Valid={field => validUserAccountField(props.UserAccount, field)} Setter={(record) => { - setUpdatedAD(false); - props.Setter(record); - }} /> - - - - - -
    -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - - -
    -
    - Record={props.UserAccount} Label='Locked Out' Field='LockedOut' Setter={props.Setter} /> - Record={props.UserAccount} Field='Approved' Setter={props.Setter} /> -
    -
    - Record={props.UserAccount} Label='Phone Confirmed' Field='PhoneConfirmed' Setter={props.Setter} /> - Record={props.UserAccount} Label='Email Confirmed' Field='EmailConfirmed' Setter={props.Setter} /> -
    -
    -
    - -
    -
    - - - ); -} - -export default UserForm; diff --git a/common-pages/src/user/UserInfo.tsx b/common-pages/src/user/UserInfo.tsx deleted file mode 100644 index 2f7e8d22..00000000 --- a/common-pages/src/user/UserInfo.tsx +++ /dev/null @@ -1,116 +0,0 @@ -// ****************************************************************************************************** -// UserInfo.tsx - Gbtc -// -// Copyright © 2020, 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/14/2021 - C. Lackner -// Generated original version of source code. -// ****************************************************************************************************** - -import * as React from 'react'; -import { Application } from '@gpa-gemstone/application-typings'; -import * as CryptoJS from 'crypto-js' -import UserForm from './UserForm'; -import { ToolTip } from '@gpa-gemstone/react-interactive'; -import { Warning } from '@gpa-gemstone/gpa-symbols'; -import { IUserAccountSlice } from '../SliceInterfaces'; -import { useDispatch, useSelector } from 'react-redux'; -import { Dispatch } from '@reduxjs/toolkit'; - -interface IProps { - UserSlice: IUserAccountSlice -} - -function UserInfo(props: IProps) { - const dispatch = useDispatch>(); - - const currentUser = useSelector(props.UserSlice.CurrentUser); - const [user, setUser] = React.useState(currentUser); - const [warnings, setWarning] = React.useState([]); - const [hover, setHover] = React.useState<('None' | 'Clear')>('None'); - - - React.useEffect(() => { - if (currentUser == null || user == null) - return; - - const encryptedPwd = (user.Password !== currentUser.Password ? CryptoJS.SHA256(user.Password + "0").toString(CryptoJS.enc.Base64) : user.Password) - - const w = []; - if (currentUser.FirstName !== user.FirstName) - w.push('Changes to First Name will be lost.') - if (currentUser.LastName !== user.LastName) - w.push('Changes to Last Name will be lost.') - if (currentUser.Phone !== user.Phone) - w.push('Changes to Phone will be lost.') - if (currentUser.Email !== user.Email) - w.push('Changes to Email will be lost.') - if (currentUser.ChangePasswordOn !== user.ChangePasswordOn) - w.push('Changes to Change Password Date will be lost.') - if (currentUser.LockedOut !== user.LockedOut) - w.push('Changes to Account Locked Status will be lost.') - if (currentUser.Approved !== user.Approved) - w.push('Changes Account Approved Status will be lost.') - if (currentUser.PhoneConfirmed !== user.PhoneConfirmed) - w.push('Changes to Phone Confirmed Status will be lost.') - if (currentUser.EmailConfirmed !== user.EmailConfirmed) - w.push('Changes to Email confirmed Status will be lost.') - if (!currentUser.UseADAuthentication && currentUser.Password !== encryptedPwd) - w.push('Changes to Password will be lost.') - - setWarning(w); - }, [currentUser, user]) - - React.useEffect(() => { setUser(currentUser)}, [currentUser]) - - function updateUser() { - const encryptedPwd = (user.Password !== currentUser.Password ? CryptoJS.SHA256(user.Password + "0").toString(CryptoJS.enc.Base64) : user.Password) - dispatch(props.UserSlice.SetCurrentUser({ ...user, Name: currentUser.Name, Password: encryptedPwd })); - dispatch(props.UserSlice.DBAction({verb: 'PATCH', record: { ...user, Name: currentUser.Name, Password: encryptedPwd }})) - } - - return ( -
    -
    -
    -
    -

    User Information:

    -
    -
    -
    -
    - setUser(u)} Edit={true} UserSlice={props.UserSlice}/> -
    -
    -
    - -
    -
    - -
    - 0)} Position={'top'} Theme={'dark'} Target={"Clr"}> - {warnings.map((t, i) =>

    {Warning} {t}

    )} -
    -
    - - -
    - ); - -} - -export default UserInfo; diff --git a/common-pages/src/user/UserPermissions.tsx b/common-pages/src/user/UserPermissions.tsx deleted file mode 100644 index 3061e470..00000000 --- a/common-pages/src/user/UserPermissions.tsx +++ /dev/null @@ -1,118 +0,0 @@ -// ****************************************************************************************************** -// UserPermission.tsx - Gbtc -// -// Copyright © 2020, 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/14/2021 - C. Lackner -// Generated original version of source code. -// ****************************************************************************************************** - -import * as React from 'react'; -import { Application } from '@gpa-gemstone/application-typings'; -import * as _ from 'lodash'; -import { CheckBox } from '@gpa-gemstone/react-forms'; -import { ISecurityRoleSlice } from '../SliceInterfaces'; -import { useDispatch, useSelector } from 'react-redux'; -import { Dispatch } from '@reduxjs/toolkit'; - -interface IProps { - UserID: string, - RoleSlice: ISecurityRoleSlice -} - -function UserPermission(props: IProps) { - const dispatch = useDispatch>(); - - const currentRoles: Application.Types.iApplicationRoleUserAccount[] = useSelector(props.RoleSlice.Roles); - const allRoleStatus: Application.Types.Status = useSelector(props.RoleSlice.Status) - const availableRoles: Application.Types.iApplicationRole[] = useSelector(props.RoleSlice.AvailableRoles) - const currentRoleStatus: Application.Types.Status = useSelector(props.RoleSlice.CurrentRoleStatus) - - const [workingRoles, setWorkingRoles] = React.useState[]>([]); - const [changed, setChanged] = React.useState(false) - - React.useEffect(() => { - if (allRoleStatus === 'unintiated' || allRoleStatus === 'changed') - dispatch(props.RoleSlice.FetchRoles()) - }, [dispatch, allRoleStatus]) - - React.useEffect(() => { - if (currentRoleStatus === 'unintiated' || currentRoleStatus === 'changed') - dispatch(props.RoleSlice.FetchUserRoles(props.UserID)) - }, [dispatch, currentRoleStatus, props.UserID]) - - React.useEffect(() => { - resetCurrentRoles(availableRoles, currentRoles) - },[currentRoles, availableRoles]); - -function resetCurrentRoles(avRoles: Application.Types.iApplicationRole[], currRoles: Application.Types.iApplicationRoleUserAccount[] ) { - setChanged(false); - setWorkingRoles(avRoles.map(src => { - const upd = _.cloneDeep(src); - upd.Assigned = currRoles.find(usrc => usrc.ApplicationRoleID === upd.ID) !== undefined; - return upd; - })) -} - - return ( -
    -
    -
    -
    -

    User Permissions:

    -
    -
    -
    -
    -
    -
    -
    - System Center: -
    - { - workingRoles.map((scr, i, array) => > key={scr.ID} Record={scr} Field='Assigned' Label={scr.Name} Setter={(record) => { - scr.Assigned = record.Assigned; - const newArray = _.clone(array); - setWorkingRoles(newArray); - setChanged(true); - }} />) - } - -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    - -
    -
    -
    - ); - - -} - -export default UserPermission; diff --git a/react-interactive/src/ConfigurableTable/ConfigurableTable.tsx b/react-interactive/src/ConfigurableTable/ConfigurableTable.tsx index ec6a1ad7..ef44134a 100644 --- a/react-interactive/src/ConfigurableTable/ConfigurableTable.tsx +++ b/react-interactive/src/ConfigurableTable/ConfigurableTable.tsx @@ -22,7 +22,7 @@ import * as React from 'react'; import Modal from '../Modal'; -import { ReactTable } from '@gpa-gemstone/react-table'; +import { ReactTable, ReactTableProps } from '@gpa-gemstone/react-table'; import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; import { Portal } from 'react-portal'; import ToolTip from '../ToolTip'; @@ -32,98 +32,11 @@ import * as _ from 'lodash'; import ConfigurableColumn from './ConfigurableColumn'; import Alert from '../Alert'; -interface TableProps { - /** - * List of T objects used to generate rows - */ - Data: T[]; - /** - * Callback when the user clicks on a data entry - * @param data contains the data including the columnKey - * @param event the onClick Event to allow propagation as needed - * @returns - */ - OnClick?: ( - data: { colKey?: string; colField?: keyof T; row: T; data: T[keyof T] | null; index: number }, - event: React.MouseEvent, - ) => void; - /** - * Key of the column to sort by - */ - SortKey: string; - /** - * Boolean to indicate whether the sort is ascending or descending - */ - Ascending: boolean; - /** - * Callback when the data should be sorted - * @param data the information of the collumn including the Key of the column - * @param event The onCLick event to allow Propagation as needed - */ - OnSort(data: { colKey: string; colField?: keyof T; ascending: boolean }, event: React.MouseEvent): void; - /** - * Class of the table component - */ - TableClass?: string; - /** - * style of the table component - */ - TableStyle?: React.CSSProperties; - /** - * style of the thead component - */ - TheadStyle?: React.CSSProperties; - /** - * Class of the thead component - */ - TheadClass?: string; - /** - * style of the tbody component - */ - TbodyStyle?: React.CSSProperties; - /** - * Class of the tbody component - */ - TbodyClass?: string; - /** - * determines if a row should be styled as selected - * @param data the item to be checked - * @returns true if the row should be styled as selected - */ - Selected?: (data: T) => boolean; - /** - * - * @param data he information of the row including the item of the row - * @param e the event triggering this - * @returns - */ - OnDragStart?: ( - data: { colKey?: string; colField?: keyof T; row: T; data: T[keyof T] | null; index: number }, - e: React.DragEvent, - ) => void; - /** - * The default style for the tr element - */ - RowStyle?: React.CSSProperties; - /** - * a Function that retrieves a unique key used for React key properties - * @param data the item to be turned into a key - * @returns a unique Key - */ - KeySelector: (data: T) => string | number; - - /** - * Optional Element to display in the last row of the Table - * use this for displaying warnings when the Table content gets cut off - */ - LastRow?: string | React.ReactNode; +interface ITableProps extends ReactTableProps.ITable { /** * Optional ZIndex for the configurable column modal */ ModalZIndex?: number -} - -interface IProps extends TableProps { /** * ID of the Portal used for tunneling Collumn settings */ @@ -147,7 +60,7 @@ interface IColDesc { /** * Table with modal to show and hide columns */ -export default function ConfigurableTable(props: React.PropsWithChildren>) { +export default function ConfigurableTable(props: React.PropsWithChildren>) { const getKeyMappings: () => Map = () => { const u = new Map(); React.Children.forEach(props.children, (element) => { diff --git a/react-interactive/src/FilterableTable/FilterableColumn.tsx b/react-interactive/src/FilterableTable/FilterableColumn.tsx new file mode 100644 index 00000000..2234d2e8 --- /dev/null +++ b/react-interactive/src/FilterableTable/FilterableColumn.tsx @@ -0,0 +1,44 @@ +// ****************************************************************************************************** +// FilterableColumn.tsx - Gbtc +// +// Copyright © 2023, 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: +// ---------------------------------------------------------------------------------------------------- +// 12/05/2024 - G. Santos +// Generated original version of source code. +// ****************************************************************************************************** + +import { ReactTableProps } from '@gpa-gemstone/react-table'; +import * as React from 'react'; +import { Search } from '../SearchBar'; +import { IUnit } from './NumberFilter'; + +interface IOptions { Value: string | number, Label: string } + +export const IsFilterableColumnProps = (props: any) => (props?.['Key'] != null); + +interface IFilterableCollumn extends ReactTableProps.IColumn { + Type?: Search.FieldType, + Enum?: IOptions[], + ExpandedLabel?: string, + Unit?: IUnit[] +} + +/** + * Wrapper to make any column configurable + */ +export default function FilterableColumn(props: React.PropsWithChildren>) { + return <>{props.children} +} \ No newline at end of file diff --git a/react-interactive/src/FilterableTable/FilterableTable.tsx b/react-interactive/src/FilterableTable/FilterableTable.tsx index ea51a852..e939106a 100644 --- a/react-interactive/src/FilterableTable/FilterableTable.tsx +++ b/react-interactive/src/FilterableTable/FilterableTable.tsx @@ -21,36 +21,30 @@ // ****************************************************************************************************** import * as React from 'react'; -import Table, { TableProps, Column } from '@gpa-gemstone/react-table'; -import {Search} from '../SearchBar'; -import { SVGIcons } from '@gpa-gemstone/gpa-symbols'; +import { ReactTable, ReactTableProps } from '@gpa-gemstone/react-table'; +import { Search } from '../SearchBar'; +import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; import { BooleanFilter } from './BooleanFilter'; import { TextFilter } from './TextFilter'; import { EnumFilter } from './EnumFilter'; import { NumberFilter, IUnit } from './NumberFilter'; import { DateFilter, DateTimeFilter, TimeFilter } from './DateTimeFilters'; import { CreateGuid} from '@gpa-gemstone/helper-functions'; +import { IsFilterableColumnProps } from './FilterableColumn'; interface IOptions { Value: string | number, Label: string } - -interface IFilterableCollumn extends Column { - Type?: Search.FieldType, - Enum?: IOptions[], - ExpandedLabel?: string, - Unit?: IUnit[] -} - -interface IProps extends TableProps { +interface IProps extends ReactTableProps.ITable { SetFilter: (filters: Search.IFilter[]) => void, - cols: IFilterableCollumn[], DefaultFilter?: Search.IFilter[] } +// ToDo: This whole structure is kinda gross, this should live in react-table so we don't have to map FilterableColumn -> Column -> HeaderWrapper + /** * Table with Filters in the column headers */ -export default function FilterableTable(props: IProps) { +export default function FilterableTable(props: React.PropsWithChildren>) { const [filters, setFilters] = React.useState[]>((props.DefaultFilter === undefined ? [] : props.DefaultFilter)); const [guid] = React.useState(CreateGuid()); @@ -64,43 +58,31 @@ export default function FilterableTable(props: IProps) { React.useEffect(() => { props.SetFilter(filters); }, [filters]); return ( - <> -
    ({ - ...c, label:
    f.FieldName === c.field?.toString())} - SetFilter={(f) => updateFilters(f, c.field)} - Field={c.field} - Type={c.Type} - Options={c.Enum} - ExpandedLabel={c.ExpandedLabel} - Guid={guid} - Unit={c.Unit} - /> - }))} - data={props.data} - onClick={props.onClick} - sortKey={props.sortKey} - ascending={props.ascending} - onSort={(d,evt) => { - // make sure we do not sort when clicking on the filter - const $div = evt.target.closest(`div[data-tableid="${guid}"]`); - if ($div === null) - props.onSort(d,evt); - }} - tableClass={props.tableClass} - tableStyle={props.tableStyle} - theadStyle={props.theadStyle} - theadClass={props.theadClass} - tbodyStyle={props.tbodyStyle} - tbodyClass={props.tbodyClass} - selected={props.selected} - rowStyle={props.rowStyle} - keySelector={props.keySelector} - /> - - + + {...props} + > + {React.Children.map(props.children, (element) => { + if (!React.isValidElement(element)) return null; + if (!IsFilterableColumnProps(element.props)) return null; + return ( + + {...element.props} + > +
    f.FieldName === element.props?.Field?.toString())} + SetFilter={(f) => updateFilters(f, element.props?.Field)} + Field={element.props?.Field} + Type={element.props?.Type} + Options={element.props?.Enum} + ExpandedLabel={element.props?.ExpandedLabel} + Guid={guid} + Unit={element.props?.Unit} + /> + + ); + })} + ); } @@ -128,7 +110,7 @@ function Header(props: IHeaderProps) { {props.Type !== undefined ? <>
    - {props.Filter.length > 0? SVGIcons.Filter : null} + {props.Filter.length > 0? : null}
    (props: IHeaderProps) { minWidth: 'calc(100% - 50px)', marginLeft: -25 }} data-tableid={props.Guid} + onClick={(evt) => { evt.preventDefault(); evt.stopPropagation(); }} > - {/*onClick={(evt) => { evt.preventDefault(); evt.stopPropagation(); }}*/}
    {((props.ExpandedLabel !== null) && (props.ExpandedLabel !== "") && (props.ExpandedLabel !== undefined)) ? diff --git a/react-interactive/src/index.tsx b/react-interactive/src/index.tsx index 45526cae..8de4dade 100644 --- a/react-interactive/src/index.tsx +++ b/react-interactive/src/index.tsx @@ -37,6 +37,7 @@ import Application from './Menue/Application'; import Page from './Menue/Page'; import Section from './Menue/Section'; import FilterableTable from './FilterableTable/FilterableTable'; +import FilterableColumn from './FilterableTable/FilterableColumn' import SplitSection from './Layout/SplitSection'; import VerticalSplit from './Layout/VerticalSplit'; import SplitDrawer from './Layout/SplitDrawer'; @@ -71,7 +72,7 @@ export { Application, Page, Section, - FilterableTable, + FilterableTable, FilterableColumn, SplitSection, VerticalSplit, SplitDrawer, diff --git a/react-table/src/AdjustableTable/AdjustableColumn.tsx b/react-table/src/AdjustableTable/AdjustableColumn.tsx deleted file mode 100644 index db29796f..00000000 --- a/react-table/src/AdjustableTable/AdjustableColumn.tsx +++ /dev/null @@ -1,250 +0,0 @@ -// ****************************************************************************************************** -// AdjustableColumn.tsx - Gbtc -// -// Copyright © 2023, 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: -// ---------------------------------------------------------------------------------------------------- -// 11/19/2023 - C. Lackner -// Generated original version of source code. -// 05/31/2024 - C. Lackner -// Refactored to fix sizing issues. -// -// ****************************************************************************************************** -import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; -import * as React from 'react'; -import { IColumnProps, IDataWrapperProps, IHeaderWrapperProps } from './Column'; - - -export default function AdjustableColumn(props: React.PropsWithChildren>) { - return <>{props.children} -} - - -interface IAdjustableHeaderWrapperProps extends IHeaderWrapperProps { - adjustment: number, - startAdjustment: React.MouseEventHandler, - setMinWidth: (w: number) => void, - minWidth?: number, - setMaxWidth: (w: number) => void, - maxWidth?: number, - extraWidth: number -} - -export function AdjustableColumnHeaderWrapper(props: React.PropsWithChildren) { - const thref = React.useRef(null); - const [mode, setMode] = React.useState<'width' | 'minWidth' | 'maxWidth'>('minWidth'); - //const [width, setWidth] = React.useState(); - const [minWidth, setMinWidth] = React.useState(); - const [maxWidth, setMaxWidth] = React.useState(); - - const [showBorder, setShowBorder] = React.useState(false); - - const onHover = React.useCallback(() => { setShowBorder(true); }, []) - const onLeave = React.useCallback(() => { setShowBorder(false); }, []) - - const style = (props.style !== undefined) ? { ...props.style } : {}; - - style.overflowX = style.overflowX ?? 'hidden'; - style.display = style.display ?? 'inline-block' - style.position = style.position ?? 'relative'; - style.borderTop = style.borderTop ?? 'none'; - style.minWidth = style.minWidth ?? 100; - - const isAuto = style.width == 'auto'; - const isUndefined = style.width === undefined; - - if ((style.width == undefined || style.width == 'auto') && props.width !== undefined && mode === 'width') { - style.width = (props.width) + props.extraWidth; - } - - if (mode === 'minWidth') { - style.width = style.minWidth; - } - - if (mode === 'maxWidth') { - style.width = style.maxWidth; - } - - if (mode === 'width' && props.adjustment !== undefined && props.adjustment !== 0 && style.width !== undefined) { - style.width = `calc(${formatwidth(style.width).toString()} ${props.adjustment < 0 ? '-' : '+'} ${Math.abs(props.adjustment).toString()}px)` - } - - if (style.cursor === undefined && (props.allowSort ?? true)) { - style.cursor = 'pointer'; - } - - React.useLayoutEffect(() => { - if (thref.current == null) return; - - if (mode == 'minWidth') { - const w = thref.current?.getBoundingClientRect().width; - if (w === undefined) return; - - if (w !== minWidth) { - setMinWidth(w); - } - - if (maxWidth === undefined && style.maxWidth !== undefined) { - setMode('maxWidth'); - - } else if (props.width === undefined) { - setMode('width'); - } - } - - if (mode == 'maxWidth') { - const w = thref.current?.getBoundingClientRect().width; - if (w === undefined) return; - - if (w !== maxWidth) { - setMaxWidth(w); - } - - if (props.width === undefined) { - setMode('width'); - } else if (props.minWidth === undefined && style.minWidth !== undefined) { - setMode('minWidth'); - } - } - - if (mode == 'width') { - const w = thref.current?.getBoundingClientRect().width; - if (w === undefined) return; - - if (props.width === undefined || (w !== (props.width + props.extraWidth))) { - props.setWidth(w, isAuto, isUndefined); - } - - if (maxWidth === undefined && style.maxWidth !== undefined) { - setMode('maxWidth'); - } else if (minWidth === undefined && style.minWidth !== undefined) { - setMode('minWidth'); - } - } - }); - - React.useEffect(() => { - if (minWidth !== props.minWidth) props.setMinWidth(minWidth as number); - }, [minWidth]); - React.useEffect(() => { - if (maxWidth !== props.maxWidth) props.setMaxWidth(maxWidth as number); - }, [maxWidth]); - - const onClick = React.useCallback((e) => { - if (props.allowSort ?? true) props.onSort(e); - }, [props.onSort, props.allowSort]); - - const onClickBorder = React.useCallback((e) => { - props.startAdjustment(e); - }, [props.startAdjustment]) - - - if (props.width != undefined && !props.enabled) - return null; - - return -} - -interface IAdjustableDataWrapperProps extends IDataWrapperProps { - adjustment?: number -} - - -export function AdjustableColumnDataWrapper(props: React.PropsWithChildren) { - const tdref = React.useRef(null); - const style = (props.style !== undefined) ? { ...props.style } : {}; - - style.overflowX = style.overflowX ?? 'hidden'; - style.display = style.display ?? 'inline-block' - - const isAuto = style.width == 'auto'; - const isUndefined = style.width === undefined; - - if ((style.width == undefined || style.width == 'auto') && props.width !== undefined) { - style.width = (props.width) + props.extraWidth; - } - - if (props.dragStart !== undefined) style.cursor = "grab"; - - React.useLayoutEffect(() => { - if (tdref.current == null) - return; - - const w = tdref.current?.getBoundingClientRect().width; - - if (style.width === undefined && props.width === undefined) { - props.setWidth(w, isAuto, isUndefined); - return; - } - - if (props.adjustment !== undefined && props.adjustment !== 0) - return; - - if (w === undefined || (props.width !== undefined && w == (props.width + props.extraWidth))) return; - props.setWidth(w, isAuto, isUndefined); - }) - - if (props.adjustment !== undefined && props.adjustment !== 0 && style.width !== undefined) - style.width = `calc(${formatwidth(style.width)} ${props.adjustment < 0 ? '-' : '+'} ${Math.abs(props.adjustment).toString()}px)` - - if (props.width != undefined && !props.enabled) - return null; - - return ( - - ); -} - - -function formatwidth(style: string | number) { - if (style.toString().endsWith('%') || style.toString().endsWith('px')) - return style; - return style.toString() + "px" -} \ No newline at end of file diff --git a/react-table/src/AdjustableTable/Column.tsx b/react-table/src/AdjustableTable/Column.tsx index 267b794b..34315fb2 100644 --- a/react-table/src/AdjustableTable/Column.tsx +++ b/react-table/src/AdjustableTable/Column.tsx @@ -20,103 +20,74 @@ // Generated original version of source code. // 05/31/2024 - C. Lackner // Refactored to fix sizing issues. +// 12/04/2024 - G. Santos +// Refactored to fix performance issues. // // ****************************************************************************************************** import { ReactIcons } from '@gpa-gemstone/gpa-symbols'; import * as React from 'react'; +import * as ReactTableProps from './Types'; - -export interface IColumnProps { - /** - * a unique Key for this Collumn - */ - Key: string; - /** - * Flag indicating whether sorting by this Collumn is allowed - */ - AllowSort?: boolean; - /** - * Optional - the Field to be used - */ - Field?: keyof T; - /** - * The Default style for the th element - */ - HeaderStyle?: React.CSSProperties; - /** - * The Default style for the td element - */ - RowStyle?: React.CSSProperties; - /** - * Determines the Content to be displayed - * @param d the data to be turned into content - * @returns the content displayed - */ - Content?: (d: { item: T, key: string, field: keyof T | undefined, index: number, style?: React.CSSProperties }) => React.ReactNode; +export function AdjustableColumn(props: React.PropsWithChildren>) { + return <>{props.children} } - -export default function Column(props: React.PropsWithChildren>) { +export function Column(props: React.PropsWithChildren>) { return <>{props.children} } export interface IHeaderWrapperProps { - setWidth: (w: number, a: boolean, u: boolean) => void, onSort: React.MouseEventHandler, + startAdjustment?: React.MouseEventHandler, sorted: boolean, asc: boolean, style: React.CSSProperties, allowSort?: boolean, - colKey: string, - width?: number, - enabled: boolean, - extraWidth: number + colKey: string } export function ColumnHeaderWrapper(props: React.PropsWithChildren) { - const thref = React.useRef(null); - - const style = (props.style !== undefined) ? { ...props.style } : {}; - - style.overflowX = style.overflowX ?? 'hidden'; - style.display = style.display ?? 'inline-block' - style.position = style.position ?? 'relative'; - style.borderTop = style.borderTop ?? 'none'; + const [showBorder, setShowBorder] = React.useState(false); - const isAuto = style.width == 'auto'; - const isUndefined = style.width === undefined; + const onHover = React.useCallback(() => { setShowBorder(true); }, []); + const onLeave = React.useCallback(() => { setShowBorder(false); }, []); - if ((style.width == undefined || style.width == 'auto') && props.width !== undefined) { - style.width = (props.width) + props.extraWidth; - } - - if (style.cursor === undefined && (props.allowSort ?? true)) { - style.cursor = 'pointer'; - } - - React.useLayoutEffect(() => { - if (thref.current == null) - return; - const w = thref.current?.getBoundingClientRect().width; - if (props.width !== undefined && (w === undefined || w == (props.width + props.extraWidth))) return; - props.setWidth(w, isAuto, isUndefined); - }) + const onClickBorder = React.useCallback((e) => { + if (props.startAdjustment != null) props.startAdjustment(e); + }, [props.startAdjustment]); const onClick = React.useCallback((e) => { if (props.allowSort ?? true) props.onSort(e); - }, [props.onSort, props.allowSort]) - - - if (props.width != undefined && !props.enabled) - return null; + }, [props.onSort, props.allowSort]); + + const preventPropagation = React.useCallback((e: React.MouseEvent) => { + e.stopPropagation(); + }, []); return @@ -495,124 +351,69 @@ function Rows(props: React.PropsWithChildren>) { const key = props.KeySelector(d, i); return ( onClick(e, d, i)}> - {React.Children.map(props.children, (element) => { - if (!React.isValidElement(element)) return null; - if ((element as React.ReactElement).type === Column) - if (props.AutoWidth.current.get(element.props.Key)?.enabled ?? true) - return ( - - props.OnClick!( - { - colKey: element.props.Key, - colField: element.props.Field, - row: d, - data: d[element.props.Field as keyof T], + {React.Children.map(props.children, (element) => { + if (!React.isValidElement(element)) return null; + if (!IsColumnProps(element.props)) return null; + const colWidth = props.ColWidths.current.get(element.props.Key); + if (colWidth == null || colWidth.width === 0) return null; + let cursor = undefined; + if (element.props?.RowStyle?.cursor != null) cursor = element.props.RowStyle.cursor + else if (props?.OnClick != null) cursor = 'pointer'; + else if (props?.DragStart != null) cursor = 'grab'; + const style = { + ...defaultDataCellStyle, + ...(element.props?.RowStyle), + width: colWidth.width, + cursor: cursor + }; + return ( + + props.OnClick!( + { + colKey: element.props.Key, + colField: element.props?.Field, + row: d, + data: d[element.props?.Field as keyof T], + index: i, + }, + e, + ) : undefined + } + dragStart={ + (props.DragStart != null) ? (e) => + props.DragStart!( + { + colKey: element.props.Key, + colField: element.props?.Field, + row: d, + data: d[element.props?.Field as keyof T], + index: i, + }, + e, + ) : undefined + } + style={style} + > + {element.props?.Content != null + ? element.props.Content({ + item: d, + key: element.props.Key, + field: element.props?.Field, + style: style, index: i, - }, - e, - ) : undefined - } - dragStart={ - (props.DragStart !== undefined && props.DragStart !== null) ? (e) => - props.DragStart!( - { - colKey: element.props.Key, - colField: element.props.Field, - row: d, - data: d[element.props.Field as keyof T], - index: i, - }, - e, - ) : undefined - } - setWidth={(w: number, a: boolean, u: boolean) => props.SetWidth(element.props.Key, key, w, a, u)} - style={element.props.RowStyle} - width={ - props.AutoWidth.current.get(element.props.Key)?.width.has(key) ?? false - ? props.AutoWidth.current.get(element.props.Key)?.maxColWidth // if not auto <- - : undefined // otherwise get the extrawidth - } - enabled={props.AutoWidth.current.get(element.props.Key)?.enabled ?? true} - > - {element.props.Content !== undefined - ? element.props.Content({ - item: d, - key: element.props.Key, - field: element.props.Field, - style: style, - index: i, - }) - : element.props.Field !== undefined - ? d[element.props.Field as keyof T] - : null} - + }) + : element.props?.Field != null + ? d[element.props.Field as keyof T] + : null} + ); - if ((element as React.ReactElement).type === AdjustableColumn) - if (props.AutoWidth.current.get(element.props.Key)?.enabled ?? true) - return ( - - props.OnClick!( - { - colKey: element.props.Key, - colField: element.props.Field, - row: d, - data: d[element.props.Field as keyof T], - index: i, - }, - e, - ) : undefined - } - dragStart={ - (props.DragStart !== undefined && props.DragStart !== null) ? (e) => - props.DragStart!( - { - colKey: element.props.Key, - colField: element.props.Field, - row: d, - data: d[element.props.Field as keyof T], - index: i, - }, - e, - ) : undefined - } - setWidth={(w: number, a: boolean, u: boolean) => props.SetWidth(element.props.Key, key, w, a, u)} - style={element.props.RowStyle} - width={ - props.AutoWidth.current.get(element.props.Key)?.width.has(key) ?? false - ? props.AutoWidth.current.get(element.props.Key)?.maxColWidth - : undefined - } - enabled={props.AutoWidth.current.get(element.props.Key)?.enabled ?? true} - extraWidth={props.ExtraWidth} - > - {' '} - {element.props.Content !== undefined - ? element.props.Content({ - item: d, - key: element.props.Key, - field: element.props.Field, - style: style, - index: i, - }) - : element.props.Field !== undefined - ? d[element.props.Field as keyof T] - : null} - - ); - return null; - })} - - ); - }) - } + })} + + ); + })} ); } @@ -626,196 +427,183 @@ interface IHeaderProps { data: { colKey: string; colField?: keyof T; ascending: boolean }, event: React.MouseEvent, ) => void; + ColWidths: React.MutableRefObject>; + TriggerRerender: ()=>void; + Trigger: number; LastColumn?: string | React.ReactNode; - AutoWidthVersion: number; - AutoWidth: React.MutableRefObject>; - SetWidth: (key: string, width: number, isAuto: boolean, isUndefined: boolean) => void; - SetMinWidth: (key: string, width: number) => void; - SetMaxWidth: (key: string, width: number) => void; - SetAdjustment: (key: string, width: number) => void; - ExtraWidth: number } function Header(props: React.PropsWithChildren>) { - const trRef = React.useRef(null); + const headStyle = React.useMemo(() => ({ ...defaultHeadStyle, ...props.Style }), [props.Style]); + // Consts for adjustable columns const [mouseDown, setMouseDown] = React.useState(0); const [currentKeys, setCurrentKeys] = React.useState<[string, string] | undefined>(undefined); const [deltaW, setDeltaW] = React.useState(0); const [tentativeLimits, setTentativeLimits] = React.useState<{min: number, max: number}>({min: -Infinity, max: Infinity}); - const calculateDeltaLimits = React.useCallback((mapKeys: [string, string] | undefined, autoWidthRef: React.MutableRefObject>) => { + const getLeftKey = React.useCallback((key: string, colWidthsRef: React.MutableRefObject>) => { + // Filtering down to shown adjustables only + const keys = React.Children.map(props.children ?? [], (element) => { + if (!React.isValidElement(element)) { + return null; + } + const keyWidth = colWidthsRef.current.get(key)?.width; + if (keyWidth == null || keyWidth <= 0) { + return null; + } + if ((element as React.ReactElement).type === AdjustableColumn) { + return (element.props as ReactTableProps.IColumn).Key; + } + return null; + }).filter((item) => item !== null); + + const index = keys.indexOf(key); + if (index <= 0) return undefined; + + return keys[index - 1]; + }, [props.children]); + + const calculateDeltaLimits = React.useCallback((mapKeys: [string, string] | undefined, colWidthsRef: React.MutableRefObject>) => { if (mapKeys === undefined) return ({min: -Infinity, max: Infinity}); - const maxLeftAdjustment = (autoWidthRef.current.get(mapKeys[0])?.maxColWidth ?? 0) + - (autoWidthRef.current.get(mapKeys[0])?.adjustement ?? 0); - const minWidthLeftAdjusted = (autoWidthRef.current.get(mapKeys[0])?.minWidth ?? 100) - maxLeftAdjustment; - const maxWidthLeftAdjusted = (autoWidthRef.current.get(mapKeys[0])?.maxWidth ?? 9e10) - maxLeftAdjustment; + const widthObjLeft = colWidthsRef.current.get(mapKeys[0]); + const widthObjRight = colWidthsRef.current.get(mapKeys[1]); - const maxRightAdjustment = (autoWidthRef.current.get(mapKeys[1])?.maxColWidth ?? 0) + - (autoWidthRef.current.get(mapKeys[1])?.adjustement ?? 0); - const minWidthRightAdjusted = (autoWidthRef.current.get(mapKeys[1])?.minWidth ?? 100) - maxRightAdjustment; - const maxWidthRightAdjusted = (autoWidthRef.current.get(mapKeys[1])?.maxWidth ?? 9e10) - maxRightAdjustment; + if (widthObjLeft == null || widthObjRight == null) return ({min: -Infinity, max: Infinity}); - // Recall that a right movement is the result of a negative value on deltaW - const minDeltaW = Math.abs(minWidthLeftAdjusted) < Math.abs(maxWidthRightAdjusted) ? minWidthLeftAdjusted : -maxWidthRightAdjusted; - const maxDeltaW = Math.abs(minWidthRightAdjusted) < Math.abs(maxWidthLeftAdjusted) ? -minWidthRightAdjusted : maxWidthLeftAdjusted; + const limitByShrinkLeft = widthObjLeft.width - widthObjLeft.minWidth; + const limitByGrowthLeft = widthObjLeft.maxWidth - widthObjLeft.width; + const limitByShrinkRight = widthObjRight.width - widthObjRight.minWidth; + const limitByGrowthRight = widthObjRight.maxWidth - widthObjRight.width; + + // Recall that a left movement is a negative deltaW + const minDeltaW = -(limitByShrinkLeft < limitByGrowthRight ? limitByShrinkLeft : limitByGrowthRight); + const maxDeltaW = limitByShrinkRight < limitByGrowthLeft ? limitByShrinkRight : limitByGrowthLeft; return ({min: minDeltaW, max: maxDeltaW}); }, []); - const calculateAdjustment = (key: string) => { - let adj = 0; - let delta: number; - - if (deltaW > tentativeLimits.max) delta = tentativeLimits.max; - else if (deltaW < tentativeLimits.min) delta = tentativeLimits.min; - else delta = deltaW; - - if (currentKeys !== undefined && currentKeys[0] == key) adj = delta; - else if (currentKeys !== undefined && currentKeys[1] == key) adj = -delta; - return (props.AutoWidth.current.get(key)?.adjustement ?? 0) + adj; - }; + const getDeltaSign = React.useCallback((index: number) => { + // Recall that a left movement is a negative deltaW + if (index === 0) return 1; + else if (index === 1) return -1; + else return 0; + }, []); - const finishAdjustment = () => { - if (currentKeys === undefined) return; + const finishAdjustment = React.useCallback((adjustment: number, adjustKeys: [string,string], colWidthsRef: React.MutableRefObject>) => { + const deltaLimits = calculateDeltaLimits(adjustKeys, colWidthsRef); - if (Math.abs(deltaW) > 5) { - const deltaLimits = calculateDeltaLimits(currentKeys, props.AutoWidth); - let delta: number; - if (deltaW > deltaLimits.max) delta = deltaLimits.max; - else if (deltaW < deltaLimits.min) delta = deltaLimits.min; - else delta = deltaW; - props.SetAdjustment(currentKeys[0], delta); - props.SetAdjustment(currentKeys[1], -delta); + let delta: number; + if (adjustment > deltaLimits.max) delta = deltaLimits.max; + else if (adjustment < deltaLimits.min) delta = deltaLimits.min; + else delta = adjustment; + + if (Math.abs(delta) > 5) { + const leftWidthObj = colWidthsRef.current.get(adjustKeys[0]); + const rightWidthObj = colWidthsRef.current.get(adjustKeys[1]); + if (leftWidthObj == null || rightWidthObj == null) { + console.error(`Unable to finalize adjustment on keys ${adjustKeys[0]}, ${adjustKeys[1]}`); + } + else { + leftWidthObj.width += (getDeltaSign(0) * delta); + rightWidthObj.width += (getDeltaSign(1) * delta); + } } setMouseDown(0); setTentativeLimits({min: -Infinity, max: Infinity}); setCurrentKeys(undefined); setDeltaW(0); - }; - - const getLeftKey = React.useCallback((key: string) => { - const keys = React.Children.map(props.children ?? [], (element) => { - if (!React.isValidElement(element)) { - return null; - } - if (!(props.AutoWidth.current.get((element.props as IColumnProps).Key)!.enabled) ?? true) { - return null; - } - if ((element as React.ReactElement).type === AdjustableColumn) { - return (element.props as IColumnProps).Key; - } - return null; - }).filter((item) => item !== null); - - const index = keys.indexOf(key); - if (index < 1) return undefined; - - return keys[index - 1]; - }, [props.children]); + }, [calculateDeltaLimits, getDeltaSign]); const onMove = React.useCallback((e: MouseEvent) => { if (currentKeys === undefined) return; const w = e.screenX - mouseDown; setDeltaW(w); }, [mouseDown, currentKeys]); - + return ( { onMove(e.nativeEvent); e.stopPropagation(); }} onMouseUp={(e) => { - finishAdjustment(); e.stopPropagation(); + if (currentKeys == null) return; + finishAdjustment(deltaW, currentKeys, props.ColWidths); + props.TriggerRerender(); }} onMouseLeave={(e) => { - finishAdjustment(); e.stopPropagation(); + if (currentKeys == null) return; + finishAdjustment(deltaW, currentKeys, props.ColWidths); + props.TriggerRerender(); }} > - + {React.Children.map(props.children, (element) => { if (!React.isValidElement(element)) return null; - if ((element as React.ReactElement).type === Column) - if (props.AutoWidth.current.get(element.props.Key)?.enabled ?? true) - return ( - props.SetWidth(element.props.Key, w, a, u)} - onSort={(e) => - props.OnSort( - { colKey: element.props.Key, colField: element.props.Field, ascending: props.Ascending }, - e, - ) - } - sorted={props.SortKey === element.props.Key && (element.props.AllowSort ?? true)} - asc={props.Ascending} - colKey={element.props.Key} - key={element.props.Key} - allowSort={element.props.AllowSort} - width={ - props.AutoWidth.current.get(element.props.Key)?.width.has(-1) ?? false - ? props.AutoWidth.current.get(element.props.Key)?.maxColWidth - : undefined - } - style={element.props.HeaderStyle ?? element.props.RowStyle} - extraWidth={props.ExtraWidth} - > - {' '} - {element.props.children ?? element.props.Key}{' '} - - ); + if (!IsColumnProps(element.props)) return null; + const colWidth = props.ColWidths.current.get(element.props.Key); + if (colWidth == null || colWidth.width === 0) return null; + // Handling temporary width changes due to being in mid-adjustments + let currentWidth = colWidth.width; + const keyIndex = currentKeys?.indexOf(element.props.Key) ?? -1; + if (keyIndex > -1) { + let delta: number; + if (deltaW > tentativeLimits.max) delta = tentativeLimits.max; + else if (deltaW < tentativeLimits.min) delta = tentativeLimits.min; + else delta = deltaW; + currentWidth += (getDeltaSign(keyIndex) * delta); + } + let cursor = undefined; + if (element.props?.HeaderStyle?.cursor != null) cursor = element.props.HeaderStyle.cursor + else if ((element.props?.AllowSort ?? true) as boolean) cursor = 'pointer'; + const style = { + ...defaultDataHeadStyle, + ...element.props?.HeaderStyle, + width: currentWidth, + cursor: cursor + }; + let startAdjustment: React.MouseEventHandler | undefined; if ((element as React.ReactElement).type === AdjustableColumn) - if (props.AutoWidth.current.get(element.props.Key)?.enabled ?? true) - return ( - props.SetWidth(element.props.Key, w, a, u)} - setMinWidth={(w) => props.SetMinWidth(element.props.Key, Math.round(w))} - minWidth={props.AutoWidth.current.get(element.props.Key)?.minWidth} - setMaxWidth={(w) => props.SetMaxWidth(element.props.Key, Math.round(w))} - maxWidth={props.AutoWidth.current.get(element.props.Key)?.maxWidth} - extraWidth={props.ExtraWidth} - onSort={(e) => - props.OnSort( - { colKey: element.props.Key, colField: element.props.Field, ascending: props.Ascending }, - e - ) - } - sorted={props.SortKey === element.props.Key && (element.props.AllowSort ?? true)} - key={element.props.Key} - colKey={element.props.Key} - asc={props.Ascending} - allowSort={element.props.AllowSort} - width={ - props.AutoWidth.current.get(element.props.Key)?.width.has(-1) ?? false - ? props.AutoWidth.current.get(element.props.Key)?.maxColWidth - : undefined - } - startAdjustment={(e) => { - let newCurrentKeys: [string, string] | undefined; - if (getLeftKey(element.props.Key) !== undefined) newCurrentKeys = [getLeftKey(element.props.Key) as string, element.props.Key as string]; + startAdjustment = (e) => { + const leftKey = getLeftKey(element.props.Key, props.ColWidths); + if (leftKey != null) { + const newCurrentKeys: [string, string] = [leftKey, element.props.Key as string]; setCurrentKeys(newCurrentKeys); setMouseDown(e.screenX); - setTentativeLimits(calculateDeltaLimits(newCurrentKeys, props.AutoWidth)); + setTentativeLimits(calculateDeltaLimits(newCurrentKeys, props.ColWidths)); setDeltaW(0); - }} - adjustment={calculateAdjustment(element.props.Key)} - style={element.props.HeaderStyle ?? element.props.RowStyle} - > - {' '} - {element.props.children ?? element.props.Key} - + } + + } + return ( + + props.OnSort( + { colKey: element.props.Key, colField: element.props?.Field, ascending: props.Ascending }, + e, + ) + } + sorted={props.SortKey === element.props.Key && (element.props?.AllowSort ?? true)} + asc={props.Ascending} + colKey={element.props.Key} + key={element.props.Key} + allowSort={element.props?.AllowSort} + startAdjustment={startAdjustment} + style={style} + > + {' '} + {element.props.children ?? element.props.Key}{' '} + ); - return null; })} - + ); diff --git a/react-table/src/AdjustableTable/Types.ts b/react-table/src/AdjustableTable/Types.ts new file mode 100644 index 00000000..03206b86 --- /dev/null +++ b/react-table/src/AdjustableTable/Types.ts @@ -0,0 +1,159 @@ +// ****************************************************************************************************** +// Types.ts - Gbtc +// +// Copyright © 2021, 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: +// ---------------------------------------------------------------------------------------------------- +// 12/06/2024 - G. Santos +// Migrated props to namespace. +// +// ****************************************************************************************************** + +export interface ITable { + /** + * List of T objects used to generate rows + */ + Data: T[]; + /** + * Callback when the user clicks on a data entry + * @param data contains the data including the columnKey + * @param event the onClick Event to allow propagation as needed + * @returns + */ + OnClick?: ( + data: { colKey?: string; colField?: keyof T; row: T; data: T[keyof T] | null; index: number }, + event: React.MouseEvent, + ) => void; + /** + * Key of the collumn to sort by + */ + SortKey: string; + /** + * Boolen to indicate whether the sort is ascending or descending + */ + Ascending: boolean; + /** + * Callback when the data should be sorted + * @param data the information of the collumn including the Key of the collumn + * @param event The onCLick event to allow Propagation as needed + */ + OnSort(data: { colKey: string; colField?: keyof T; ascending: boolean }, event: React.MouseEvent): void; + /** + * Class of the table component + */ + TableClass?: string; + /** + * style of the table component + */ + TableStyle?: React.CSSProperties; + /** + * style of the thead component + */ + TheadStyle?: React.CSSProperties; + /** + * Class of the thead component + */ + TheadClass?: string; + /** + * style of the tbody component + * Note: Display style overwritten to "block" + */ + TbodyStyle?: React.CSSProperties; + /** + * Class of the tbody component + */ + TbodyClass?: string; + /** + * style of the tfoot component + */ + TfootStyle?: React.CSSProperties; + /** + * Class of the tfoot component + */ + TfootClass?: string; + + /** + * determines if a row should be styled as selected + * @param data the item to be checked + * @returns true if the row should be styled as selected + */ + Selected?: (data: T, index: number) => boolean; + /** + * + * @param data the information of the row including the item of the row + * @param e the event triggering this + * @returns + */ + OnDragStart?: ( + data: { colKey?: string; colField?: keyof T; row: T; data: T[keyof T] | null; index: number }, + e: React.DragEvent, + ) => void; + /** + * The default style for the tr element + */ + RowStyle?: React.CSSProperties; + /** + * a Function that retrieves a unique key used for React key properties + * @param data the item to be turned into a key + * @returns a unique Key + */ + KeySelector: (data: T, index?: number) => string | number; + + /** + * Optional Element to display in the last row of the Table + * use this for displaying warnings when the Table content gets cut off. + * Data appears in the tfoot element + */ + LastRow?: string | React.ReactNode; + /** + * Optional Element to display on upper Right corner + */ + LastColumn?: string | React.ReactNode; + + /** + * Optional Callback that gets called when there is not enough space to display columns + * @param disabled takes in string of disabled keys + */ + ReduceWidthCallback?: (disabled: string[]) => void; +} + +export interface IColumn { + /** + * a unique Key for this Collumn + */ + Key: string; + /** + * Flag indicating whether sorting by this Collumn is allowed + */ + AllowSort?: boolean; + /** + * Optional - the Field to be used + */ + Field?: keyof T; + /** + * The Default style for the th element + */ + HeaderStyle?: React.CSSProperties; + /** + * The Default style for the td element + */ + RowStyle?: React.CSSProperties; + /** + * Determines the Content to be displayed + * @param d the data to be turned into content + * @returns the content displayed + */ + Content?: (d: { item: T, key: string, field: keyof T | undefined, index: number, style?: React.CSSProperties }) => React.ReactNode; +} \ No newline at end of file diff --git a/react-table/src/DynamicTable.tsx b/react-table/src/DynamicTable.tsx deleted file mode 100644 index 1ec968a2..00000000 --- a/react-table/src/DynamicTable.tsx +++ /dev/null @@ -1,84 +0,0 @@ -// ****************************************************************************************************** -// DynamicTable.tsx - Gbtc -// -// Copyright © 2021, 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/26/2021 - Billy Ernest -// Generated original version of source code. -// -// ****************************************************************************************************** - - -import * as React from 'react'; -import { Column, Header, Rows } from './Table'; -import { noop } from 'lodash'; - -const empty = new Map(); - -export interface DynamicTableProps { - /** - * List of T objects used to generate rows - */ - data: T[]; - onClick: (data: { colKey: string; colField?: keyof T; row: T; data: T[keyof T]|null, index: number }, event: React.MouseEvent) => void; - /** - * Key of the collumn to sort by - */ - sortKey: string; - /** - * Boolen to indicate whether the sort is ascending or descending - */ - ascending: boolean; - onSort(data: { colKey: string; colField?: keyof T; ascending: boolean }): void; - tableClass?: string; - tableStyle?: React.CSSProperties; - theadStyle?: React.CSSProperties; - theadClass?: string; - tbodyStyle?: React.CSSProperties; - tbodyClass?: string; - selected?(data: T): boolean; - rowStyle?: React.CSSProperties; - keySelector?: (data: T) => string; -} - -export function DynamicTable(props: DynamicTableProps) { - if (props.data.length <= 0) return null; - - const cols: Column[] = []; - const keys = Object.keys(props.data[0]); - for (const key of keys) { - cols.push({ key, label: key, field: key as keyof T}) - } - - return ( -
    { e.stopPropagation() }} - > -
    - {props.sorted ?
    - {props.asc ? : } -
    : null} -
    {props.children ?? props.colKey}
    -
    - {props.children} - { e.stopPropagation() }} + id={props.colKey} > + {props.startAdjustment == null ? <> : +
    + } {props.sorted?
    {props.asc ? : } @@ -128,47 +99,15 @@ export function ColumnHeaderWrapper(props: React.PropsWithChildren void, - width?: number, - enabled: boolean dragStart?: (e: React.DragEvent) => void, onClick?: (e: React.MouseEvent) => void, - style: React.CSSProperties, - extraWidth: number + style: React.CSSProperties } - export function ColumnDataWrapper (props: React.PropsWithChildren) { - const tdref = React.useRef(null); - const style = (props.style !== undefined) ? { ...props.style } : {}; - - style.overflowX = style.overflowX ?? 'hidden'; - style.display = style.display ?? 'inline-block' - - const isAuto = style.width == 'auto'; - const isUndefined = style.width === undefined; - - if ((style.width == undefined || style.width == 'auto') && props.width !== undefined) { - style.width = (props.width) + props.extraWidth; - } - - if (props.dragStart !== undefined) style.cursor = "grab"; - - React.useLayoutEffect(() => { - if (tdref.current == null) - return; - const w = tdref.current?.getBoundingClientRect().width; - if (props.width !== undefined && (w === undefined || w == (props.width + props.extraWidth))) return; - props.setWidth(w, isAuto, isUndefined); - }) - - if (props.width != undefined && !props.enabled) - return null; - return (
    { - /** - * List of T objects used to generate rows - */ - Data: T[]; - /** - * Callback when the user clicks on a data entry - * @param data contains the data including the columnKey - * @param event the onClick Event to allow propagation as needed - * @returns - */ - OnClick?: ( - data: { colKey?: string; colField?: keyof T; row: T; data: T[keyof T] | null; index: number }, - event: React.MouseEvent, - ) => void; - /** - * Key of the collumn to sort by - */ - SortKey: string; - /** - * Boolen to indicate whether the sort is ascending or descending - */ - Ascending: boolean; - /** - * Callback when the data should be sorted - * @param data the information of the collumn including the Key of the collumn - * @param event The onCLick event to allow Propagation as needed - */ - OnSort(data: { colKey: string; colField?: keyof T; ascending: boolean }, event: React.MouseEvent): void; - /** - * Class of the table component - */ - TableClass?: string; - /** - * style of the table component - */ - TableStyle?: React.CSSProperties; - /** - * style of the thead component - */ - TheadStyle?: React.CSSProperties; - /** - * Class of the thead component - */ - TheadClass?: string; - /** - * style of the tbody component - * Note: Display style overwritten to "block" - */ - TbodyStyle?: React.CSSProperties; - /** - * Class of the tbody component - */ - TbodyClass?: string; - /** - * style of the tfoot component - */ - TfootStyle?: React.CSSProperties; - /** - * Class of the tfoot component - */ - TfootClass?: string; - - /** - * determines if a row should be styled as selected - * @param data the item to be checked - * @returns true if the row should be styled as selected - */ - Selected?: (data: T, index: number) => boolean; - /** - * - * @param data the information of the row including the item of the row - * @param e the event triggering this - * @returns - */ - OnDragStart?: ( - data: { colKey?: string; colField?: keyof T; row: T; data: T[keyof T] | null; index: number }, - e: React.DragEvent, - ) => void; - /** - * The default style for the tr element - */ - RowStyle?: React.CSSProperties; - /** - * a Function that retrieves a unique key used for React key properties - * @param data the item to be turned into a key - * @returns a unique Key - */ - KeySelector: (data: T, index?: number) => string | number; - - /** - * Optional Element to display in the last row of the Table - * use this for displaying warnings when the Table content gets cut off. - * Data appears in the tfoot element - */ - LastRow?: string | React.ReactNode; - /** - * Optional Element to display on upper Right corner - */ - LastColumn?: string | React.ReactNode; - - /** - * Optional Callback that gets called when there is not enough space to display columns - * @param disabled takes in string of disabled keys - */ - ReduceWidthCallback?: (disabled: string[]) => void; -} - -interface IAutoWidth { - maxColWidth: number; - width: Map; - minWidth?: number; - maxWidth?: number; - enabled: boolean; - adjustement: number | undefined; - isAuto: boolean; - isUndefined: boolean; +type width = { + width: number, + minWidth: number, + maxWidth: number } const defaultTableStyle: React.CSSProperties = { padding: 0, - width: '100%', - height: '100%', + flex: 1, tableLayout: 'fixed', overflow: 'hidden', display: 'flex', flexDirection: 'column', marginBottom: 0, + width: '100%' +}; + +const defaultHeadStyle: React.CSSProperties = { + fontSize: 'auto', + tableLayout: 'fixed', + display: 'table', + width: '100%' +}; + +const defaultBodyStyle: React.CSSProperties = { + flex: 1, + display: 'block', + overflow: 'auto' }; -export default function AdjustableTable(props: React.PropsWithChildren>) { +const defaultRowStyle: React.CSSProperties = { + display: 'table', + tableLayout: 'fixed', + width: '100%' +}; + +const defaultDataHeadStyle: React.CSSProperties = { + display: 'inline-block', + position: 'relative', + borderTop: 'none', + width: 'auto' +}; + +const defaultDataCellStyle: React.CSSProperties = { + overflowX: 'hidden', + display: 'inline-block', + width: 'auto' +}; + +const IsColumnProps = (props: any) => (props?.['Key'] != null); + +export function AdjustableTable(props: React.PropsWithChildren>) { const bodyRef = React.useRef(null); - const throtleRef = React.useRef(null); - const colCountRef = React.useRef(null); + const colWidthsRef = React.useRef>(new Map()); + const oldWidthRef = React.useRef(0); - const autoWidth = React.useRef>(new Map()); - const [autoWidthVersion, setAutoWidthVersion] = React.useState(0); const [currentTableWidth, setCurrentTableWidth] = React.useState(0); const [scrolled, setScrolled] = React.useState(false); - const [extraWidthPerRow, setExtraWidthPerRow] = React.useState(0); + const [trigger, setTrigger] = React.useState(0); + + // Style consts + const tableStyle = React.useMemo(() => ({...defaultTableStyle, ...props.TableStyle}),[props.TableStyle]); + const headStyle = React.useMemo(() => ({...defaultHeadStyle, ...props.TheadStyle}),[props.TheadStyle]); + const bodyStyle = React.useMemo(() => ({...defaultBodyStyle, ...props.TbodyStyle}),[props.TbodyStyle]); + const rowStyle = React.useMemo(() => ({...defaultRowStyle, ...props.RowStyle}),[props.RowStyle]); + + // Send warning if styles are overridden + React.useEffect(() => { + if (props.TableStyle !== undefined) + console.warn('TableStyle properties may be overridden if needed. consider using the defaults'); + if (props.TheadStyle !== undefined) + console.warn('TheadStyle properties may be overridden if needed. consider using the defaults'); + if (props.TbodyStyle !== undefined) + console.warn('TBodyStyle properties may be overridden if needed. consider using the defaults'); + if (props.RowStyle !== undefined) + console.warn('RowStyle properties may be overridden if needed. consider using the defaults'); + }, []); + + + // Measure widths and hide columns + React.useLayoutEffect(() => { + if (currentTableWidth <= 0) return; + + // Helper functions for the calculations + const getWidthfromProps = (p: ReactTableProps.IColumn, type: 'width'|'maxWidth'|'minWidth') => { + // This priotizes rowstyling for width over header, since it was decided that they need to be the same + if (p?.RowStyle?.[type] !== undefined) return p.RowStyle[type]; + if (p?.HeaderStyle?.[type] !== undefined) return p.HeaderStyle[type]; + return undefined; + } + + // Construct base map + const newMap = new Map(); + React.Children.forEach(props.children, (element) => { + if (React.isValidElement(element) && IsColumnProps(element.props)) { + if (newMap.get(element.props.Key) != null) console.error("Multiple of the same key detected in table, this will cause issues."); + newMap.set(element.props.Key, {minWidth: 100, maxWidth: 1000, width: 100}) + } + }); + + // If width is the same and keys are identical, we can skip the operation + if (currentTableWidth === oldWidthRef.current && + ( + newMap.size === colWidthsRef.current.size && + ![...newMap.keys()].some(key => !colWidthsRef.current.has(key)) + ) + ) return; + + // Find and set widths for map + ['minWidth', 'width', 'maxWidth'].forEach(type => { + const widthsContainer = document.createElement("div"); + widthsContainer.style.height = '0px'; + widthsContainer.style.width = `${currentTableWidth}px`; + + // Append columns as divs for measurement + const autoKeys: string[] = []; + const measureKeys: string[] = []; + React.Children.forEach(props.children, (element) => { + if (React.isValidElement(element) && IsColumnProps(element.props)) { + let widthValue = getWidthfromProps(element.props, type as 'minWidth'|'width'|'maxWidth'); + if (type === 'width' && widthValue == null) widthValue = 'auto'; + if (widthValue != null) { + if (widthValue === 'auto') autoKeys.push(element.props.Key); + else { + const widthElement = document.createElement("div"); + widthElement.id = element.props.Key + "_measurement"; + widthElement.style.height = '0px'; + if ((widthValue as string)?.length != null) + widthElement.style.width = widthValue as string; + else + widthElement.style.width = `${widthValue}px`; + widthsContainer.appendChild(widthElement); + measureKeys.push(element.props.Key); + } + + } + } + }); + document.body.appendChild(widthsContainer); + + // Handle Measurements + let autoSpace = currentTableWidth; + measureKeys.forEach(key => { + const element = document.getElementById(key+"_measurement"); + if (element != null) { + const widthObj = newMap.get(key); + if (widthObj != null) { + autoSpace -= element.clientWidth; + widthObj[type as 'minWidth'|'width'|'maxWidth'] = element.clientWidth; + } + else console.error("Could not find width object for Key: " + key); + } + else console.error("Could not find measurement div with Key: " + key); + }); + document.body.removeChild(widthsContainer); + + // Handle Autos (width type only) + if (type === 'width' && autoKeys.length > 0) { + const spacePerElement = Math.floor(autoSpace / autoKeys.length); + autoKeys.forEach(key => { + const widthObj = newMap.get(key); + if (widthObj != null) widthObj[type] = spacePerElement; + else console.error("Could not find width object for Key: " + key); + }); + } + + let remainingSpace = currentTableWidth; + [...newMap.keys()].forEach(key => { + const widthObj = newMap.get(key); + if (widthObj != null) { + if (widthObj.minWidth <= remainingSpace) { + // This follows behavior consistent with MDN documentation on how these width types should behave + if (widthObj.minWidth > widthObj.width) widthObj.width = widthObj.minWidth; + if (widthObj.minWidth > widthObj.maxWidth) widthObj.maxWidth = widthObj.minWidth; + if (widthObj.width > widthObj.maxWidth) widthObj.width = widthObj.maxWidth; + // Constrain Width to remainingSpace + if (widthObj.width > remainingSpace) widthObj.width = remainingSpace; + remainingSpace -= widthObj.width; + } + else { + widthObj.minWidth = 0; + widthObj.width = 0; + widthObj.maxWidth = 0; + } + } + else console.error("Could not find width object for Key: " + key); + }); + }); + + colWidthsRef.current = newMap; + oldWidthRef.current = currentTableWidth; + setTrigger(c => c+1); + }, [props.children, currentTableWidth]); + const setTableWidth = React.useCallback(_.debounce(() => { if (bodyRef.current == null) return; @@ -196,248 +252,50 @@ export default function AdjustableTable(props: React.PropsWithChildren { - autoWidth.current = new Map(); - setAutoWidthVersion(0); - }, [currentTableWidth]); - - React.useEffect(() => { - let totalMaxWidth = 0; - autoWidth.current.forEach((v) => { - totalMaxWidth += v.maxColWidth; - }); - if (totalMaxWidth > currentTableWidth && currentTableWidth > 0) { - const hideKeys: string[] = []; - const showKeys: string[] = []; - let t = 0; - let colW = 0; - - autoWidth.current.forEach((v, k) => { - t = t + v.maxColWidth; - if (t < currentTableWidth) { - showKeys.push(k); - colW += v.maxColWidth; - } - else hideKeys.push(k); - }); - - const numEnabledColumns = showKeys.length; - - if (Array.from(autoWidth.current.values()).filter(autoWidth => !autoWidth.enabled).length == hideKeys.length) { - return; - } - if (colCountRef.current !== null) clearTimeout(colCountRef.current); - - colCountRef.current = setTimeout(() => { - hideKeys.forEach((k) => (autoWidth.current.get(k)!.enabled = false)); - showKeys.forEach((k) => (autoWidth.current.get(k)!.enabled = true)); - - const numOfColsWithAutoCSS = Array.from(autoWidth.current.values()).filter(autoWidth => autoWidth.isAuto && autoWidth.enabled).length; - const numOfColsWithUndefinedCSS = Array.from(autoWidth.current.values()).filter(autoWidth => autoWidth.isUndefined && autoWidth.enabled).length; - - let colsToDivideBy = numEnabledColumns; // Split the extra across all cols - - if (numOfColsWithAutoCSS > 0) { // Split only on the css auto-cols and no css undefined cols - colsToDivideBy = numOfColsWithAutoCSS; - } - if (numOfColsWithUndefinedCSS > 0 && numOfColsWithAutoCSS == 0) { // Split only on the css undefined if there are no auto-cols - colsToDivideBy = numOfColsWithUndefinedCSS; - } - const extraSpace = (currentTableWidth - colW) / colsToDivideBy; - setExtraWidthPerRow(Math.floor(extraSpace)); - props.ReduceWidthCallback?.(hideKeys); - setAutoWidthVersion((v) => v + 1); - }, 500); - } else if (currentTableWidth > 0) { - const numEnabledColumns = Array.from(autoWidth.current.values()).filter(autoWidth => autoWidth.enabled).length; - const numOfColsWithAutoCSS = Array.from(autoWidth.current.values()).filter(autoWidth => autoWidth.isAuto && autoWidth.enabled).length; - const colsToDivideBy = (numOfColsWithAutoCSS > 0) ? numOfColsWithAutoCSS : numEnabledColumns; - - const extraSpace = (currentTableWidth - totalMaxWidth - numEnabledColumns) / colsToDivideBy; - setExtraWidthPerRow(extraSpace); - - props.ReduceWidthCallback?.([]); - } - }, [autoWidthVersion]); - - React.useEffect(() => { - // if there are keys in the map not present in children, map is old - const children = React.Children.toArray(props.children); - const childKeys: string[] = []; - const mapKeys = autoWidth.current.keys(); - - children.forEach(child => { - if (!React.isValidElement(child)) return; - if ((child as React.ReactElement).type === Column || - (child as React.ReactElement).type === AdjustableColumn) - childKeys.push(child.props.Key); - }); - for (const key of mapKeys) { - if (childKeys.includes(key)) { - continue; - } else { - autoWidth.current.clear(); - setAutoWidthVersion(0); - break; - } - } - }, [props.children]); - const handleSort = React.useCallback((data: { colKey: string; colField?: keyof T; ascending: boolean }, event: React.MouseEvent) => { if (data.colKey !== null) props.OnSort(data, event); }, [props.OnSort]); - - const setWidth = React.useCallback((colKey: string, key: string | number, width: number, isAuto: boolean, isUndefined: boolean) => { - const flooredWidth = Math.floor(width); - if (!autoWidth.current.has(colKey)) { // does the column exist - autoWidth.current.set(colKey, { // if not, add it. - maxColWidth: flooredWidth, - width: new Map([[key, flooredWidth]]), - enabled: true, - adjustement: 0, - isAuto: isAuto, - isUndefined: isUndefined - }) - } else if (!(autoWidth.current.get(colKey)?.width.has(key) ?? false)) { // it exists but the key does not - autoWidth.current.get(colKey)?.width.set(key, flooredWidth); // add the width for this key - autoWidth.current.get(colKey)!.isAuto = isAuto; - if (flooredWidth > (autoWidth.current.get(colKey)?.maxColWidth ?? 9e10)) // if width is > to max col - autoWidth.current.get(colKey)!.maxColWidth = flooredWidth; // set max to width - - } else if (autoWidth.current.get(colKey)!.width.get(key) == flooredWidth) { // width == newW - autoWidth.current.get(colKey)!.isAuto = isAuto; - return; - - } else { - autoWidth.current.get(colKey)!.width.set(key, flooredWidth); // otherwise, it exists, just set the width - autoWidth.current.get(colKey)!.isAuto = isAuto; - if (flooredWidth == autoWidth.current.get(colKey)?.maxColWidth) // check against max - autoWidth.current.get(colKey)!.maxColWidth = Math.max(...autoWidth.current.get(colKey)!.width.values()); - } - - //cancel ref - //Add a Timer - runs within 10 ms from when the Timer started to avoid React thinking this is an indinfinte loop.... - if (throtleRef.current !== null) clearTimeout(throtleRef.current); - throtleRef.current = setTimeout(() => { - setAutoWidthVersion((v) => v + 1); - }, 10); - }, []); - const setAdjustment = React.useCallback((colKey: string, w: number) => { - if (!autoWidth.current.has(colKey) || autoWidth.current.get(colKey) == undefined) { // doesnt exist/is undefined - autoWidth.current.set(colKey, { // create it - maxColWidth: 0, - width: new Map(), - enabled: true, - adjustement: w, - isAuto: false, - isUndefined: false - }) - } else { - const x = autoWidth.current.get(colKey); - if (x?.adjustement != undefined) - x.adjustement += w; - } - //cancel ref - //Add a Timer - runs within 10 ms from when the Timer started to avoid React thinking this is an indinfinte loop.... - if (throtleRef.current !== null) clearTimeout(throtleRef.current); - - throtleRef.current = setTimeout(() => { - setAutoWidthVersion((v) => v + 1); - }, 10); - }, []); - - const setMinWidth = React.useCallback((colKey: string, w: number) => { - if (!autoWidth.current.has(colKey)) - autoWidth.current.set(colKey, { - maxColWidth: 0, - width: new Map(), - enabled: true, - adjustement: 0, - minWidth: w, - isAuto: false, - isUndefined: false - }); - else if (autoWidth.current.get(colKey)?.minWidth == w) return; - autoWidth.current.get(colKey)!.minWidth = w; - //cancel ref - //Add a Timer - runs within 10 ms from when the Timer started to avoid React thinking this is an indinfinte loop.... - if (throtleRef.current !== null) clearTimeout(throtleRef.current); - throtleRef.current = setTimeout(() => { - setAutoWidthVersion((v) => v + 1); - }, 10); - }, []); - - const setMaxWidth = React.useCallback((colKey: string, w: number) => { - if (!autoWidth.current.has(colKey)) - autoWidth.current.set(colKey, { - maxColWidth: 0, - width: new Map(), - enabled: true, - adjustement: 0, - maxWidth: w, - isAuto: false, - isUndefined: false - }); - else if (autoWidth.current.get(colKey)?.maxWidth == w) return; - - autoWidth.current.get(colKey)!.maxWidth = w; - - //cancel ref - //Add a Timer - runs within 10 ms from when the Timer started to avoid React thinking this is an indinfinte loop.... - if (throtleRef.current !== null) clearTimeout(throtleRef.current); - throtleRef.current = setTimeout(() => { - setAutoWidthVersion((v) => v + 1); - }, 10); - }, []); - return ( - - Class={props.TheadClass} - Style={props.TheadStyle} - SortKey={props.SortKey} - Ascending={props.Ascending} - OnSort={handleSort} - SetWidth={(k, w, a, u) => setWidth(k, -1, w, a, u)} - SetMaxWidth={setMaxWidth} - SetMinWidth={setMinWidth} - AutoWidth={autoWidth} - AutoWidthVersion={autoWidthVersion} - LastColumn={props.LastColumn} - SetAdjustment={setAdjustment} - ExtraWidth={extraWidthPerRow} - > - {props.children} - - - DragStart={props.OnDragStart} - Data={props.Data} - RowStyle={props.RowStyle} - BodyStyle={props.TbodyStyle} - BodyClass={props.TbodyClass} - OnClick={props.OnClick} - Selected={props.Selected} - KeySelector={props.KeySelector} - AutoWidth={autoWidth} - AutoWidthVersion={autoWidthVersion} - SetWidth={setWidth} - ExtraWidth={extraWidthPerRow} - BodyRef={bodyRef} - BodyScrolled={scrolled} - > - {props.children} - - {props.LastRow !== undefined ? ( - - - {props.LastRow} - - - ) : null} + className={props.TableClass !== undefined ? props.TableClass : 'table table-hover'} + style={tableStyle}> + + Class={props.TheadClass} + Style={headStyle} + SortKey={props.SortKey} + Ascending={props.Ascending} + LastColumn={props.LastColumn} + OnSort={handleSort} + ColWidths={colWidthsRef} + Trigger={trigger} + TriggerRerender={() => setTrigger(c => c+1)}> + {props.children} + + + DragStart={props.OnDragStart} + Data={props.Data} + RowStyle={rowStyle} + BodyStyle={bodyStyle} + BodyClass={props.TbodyClass} + OnClick={props.OnClick} + Selected={props.Selected} + KeySelector={props.KeySelector} + BodyRef={bodyRef} + BodyScrolled={scrolled} + ColWidths={colWidthsRef} + Trigger={trigger}> + {props.children} + + { + props.LastRow !== undefined ? ( + + + {props.LastRow} + + + ) : null + }
    ); } @@ -457,15 +315,15 @@ interface IRowProps { ) => void; Selected?: (data: T, index: number) => boolean; KeySelector: (data: T, index?: number) => string | number; - AutoWidthVersion: number; - AutoWidth: React.MutableRefObject>; - SetWidth: (key: string, itemKey: string | number, width: number, isAuto: boolean, isUndefined: boolean) => void; - ExtraWidth: number, BodyRef?: React.MutableRefObject; - BodyScrolled: boolean + BodyScrolled: boolean; + ColWidths: React.MutableRefObject>; + Trigger: number; } function Rows(props: React.PropsWithChildren>) { + const bodyStyle = React.useMemo(() => ({ ...props.BodyStyle, paddingRight: (props.BodyScrolled ? 0 : 17), display: "block" }), [props.BodyStyle, props.BodyScrolled]); + const onClick = React.useCallback((e: React.MouseEvent, item: T, index: number) => { if (props.OnClick !== undefined) props.OnClick( @@ -479,8 +337,6 @@ function Rows(props: React.PropsWithChildren>) { e, ); }, [props.OnClick]); - - const bodyStyle = React.useMemo(() => ({ ...props.BodyStyle, paddingRight: (props.BodyScrolled ? 0 : 17), display: "block" }), [props.BodyStyle, props.BodyScrolled]); return (
    {props.LastColumn}{props.LastColumn}
    - Class={props.theadClass} Style={props.theadStyle} - Cols={cols} SortKey={props.sortKey} Ascending={props.ascending} - Click={(d) => handleSort(d)} - /> - Data={props.data} Cols={cols} RowStyle={props.rowStyle} - BodyStyle={props.tbodyStyle} BodyClass={props.tbodyClass} - Click={(data, e) => props.onClick(data, e)} Selected={props.selected} KeySelector={props.keySelector} /> -
    - ); - - function handleSort( - data: { colKey: string; colField?: keyof T; ascending: boolean } - ) { - if (data.colKey !== null) - props.onSort(data); - } -} diff --git a/react-table/src/SearchableTable.tsx b/react-table/src/SearchableTable.tsx deleted file mode 100644 index 83c957fd..00000000 --- a/react-table/src/SearchableTable.tsx +++ /dev/null @@ -1,52 +0,0 @@ -// ****************************************************************************************************** -// SearchableTable.tsx - Gbtc -// -// Copyright © 2021, 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/12/2021 - C. Lackner -// Generated original version of source code. -// -// ****************************************************************************************************** - -import * as React from 'react'; -import Table, {TableProps} from './Table'; - -interface ISearchableTableProps extends TableProps { - /** - * Function used to filter data - * @param item: The T to be matched - * @param search: The string to search by - * @returns: true if the item should be shown in the Table - */ - matchSearch: (item: T, search: string) => boolean; -} - -/** - * A Table with an input Field to search on top - */ -export function SearchableTable(props: ISearchableTableProps) { - const [data, setData] = React.useState(props.data); - const [searchTextAS, setSearchTextAS] = React.useState(''); - - React.useEffect(() => { - setData(props.data.filter(s => props.matchSearch(s,searchTextAS))) - }, [props.data, searchTextAS]); - - return <> - setSearchTextAS(e.target.value)} /> - - -} diff --git a/react-table/src/SelectTable.tsx b/react-table/src/SelectTable.tsx deleted file mode 100644 index 03cefb49..00000000 --- a/react-table/src/SelectTable.tsx +++ /dev/null @@ -1,130 +0,0 @@ -// ****************************************************************************************************** -// SelectTable.tsx - Gbtc -// -// Copyright © 2021, 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: -// ---------------------------------------------------------------------------------------------------- -// 01/22/2021 - C. Lackner -// Generated original version of source code. -// -// ****************************************************************************************************** - -import * as React from 'react'; -import * as _ from 'lodash'; -import Table, {Column, TableProps} from './Table'; - -export interface ISelectTableProps { - cols: Column[]; - data: T[]; - sortKey: string; - ascending: boolean; - tableClass?: string; - tableStyle?: React.CSSProperties; - theadStyle?: React.CSSProperties; - theadClass?: string; - tbodyStyle?: React.CSSProperties; - tbodyClass?: string; - rowStyle?: React.CSSProperties; - onSelection: (selected: T[]) => void; - KeyField: keyof T; - selectAllCounter?: number; -} - -export function SelectTable(props: ISelectTableProps) { - const didMountRef = React.useRef(false); - - const [data, setData] = React.useState(props.data); - const [selected, setSelected] = React.useState<(T[keyof T])[]>([]); - - const [sortKey, setSortKey] = React.useState(props.sortKey); - const [ascending, setAscending] = React.useState(props.ascending); - - React.useEffect(() => { - if (didMountRef.current) - selectAll(); - else - didMountRef.current = true; - }, [props.selectAllCounter]); - - React.useEffect(() => { - if (props.data.length !== data.length) - setData(props.data); - }, [props.data]); - - React.useEffect(() => { - setSelected((d) => d.filter(keyItem => data.findIndex(item => item[props.KeyField] === keyItem) > -1)) - }, [data]); - - React.useEffect(() => { - const sortColumn = props.cols.filter(col => col.key === sortKey)[0]; - - if (sortColumn === undefined || sortColumn.field === undefined) - return; - - const sortField: keyof T = sortColumn.field; - setData((lst) => (_.orderBy(lst, [sortField], [(ascending ? "asc" : "desc")]))) - }, [ascending, sortKey]); - - React.useEffect(() => { - props.onSelection(data.filter(item => selected.findIndex(key => key === item[props.KeyField]) > -1)); - }, [selected]) - - function handleClick(d: { colKey: string; row: T; data: T[keyof T]|null }) { - const sIndex = selected.findIndex(item => item === d.row[props.KeyField]); - if (sIndex === -1) - setSelected((od) => [...od, d.row[props.KeyField]]) - else - setSelected((od) => od.filter(item => item !== d.row[props.KeyField])); - } - - function selectAll() { - setSelected((d) => {if (d.length === data.length) return []; else return data.map(row => row[props.KeyField]); }); - } - - function handleSort( - d: { colKey: string; ascending: boolean }, - ) { - if (d.colKey === sortKey) - setAscending(!ascending); - else - setSortKey(d.colKey); - } - - const tableProps: TableProps = { - cols: [ - { key: 'gemstone-checkbox', field: props.KeyField, label: '', headerStyle: { width: '4em' }, rowStyle: { width: '4em' }, content: (item: T) => { - const index = selected.findIndex(i => i === item[props.KeyField]); - const iconClass = index > -1 ? 'fa-check-square-o' : 'fa-square-o'; - return
    ; - }}, - ...props.cols - ], - data, - onClick: handleClick, - sortKey, - ascending, - onSort: handleSort, - tableClass: props.tableClass, - tableStyle: props.tableStyle, - theadStyle: props.theadStyle, - theadClass: props.theadClass, - tbodyStyle: props.tbodyStyle, - tbodyClass: props.tbodyClass, - selected: (d: T) => false, - rowStyle: props.rowStyle - }; - - return
    ; -} diff --git a/react-table/src/Table.tsx b/react-table/src/Table.tsx deleted file mode 100644 index 3ebe3ebd..00000000 --- a/react-table/src/Table.tsx +++ /dev/null @@ -1,238 +0,0 @@ -// ****************************************************************************************************** -// Table.tsx - Gbtc -// -// Copyright © 2018, Grid Protection Alliance. All Rights Reserved. -// -// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See -// the NOTICE file distributed with this work for additional information regarding copyright ownership. -// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this -// file except in compliance with the License. You may obtain a copy of the License at: -// -// http://opensource.org/licenses/MIT -// -// Unless agreed to in writing, the subject software distributed under the License is distributed on an -// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the -// License for the specific language governing permissions and limitations. -// -// Code Modification History: -// ---------------------------------------------------------------------------------------------------- -// 08/02/2018 - Billy Ernest -// Generated original version of source code. -// -// ****************************************************************************************************** - -import { SVGIcons } from '@gpa-gemstone/gpa-symbols'; -import * as React from 'react'; - -export interface Column { - key: string; - label: string|React.ReactNode; - field?: keyof T; - headerStyle?: React.CSSProperties; - rowStyle?: React.CSSProperties; - content?(item: T, key: string, field: keyof T | undefined, style: React.CSSProperties, index: number): React.ReactNode; -} - - -export interface TableProps { - /** - * List of Collumns in this Table - */ - cols: Column[]; - /** - * List of T objects used to generate rows - */ - data: T[]; - onClick?: (data: { colKey: string; colField?: keyof T; row: T; data: T[keyof T] | null, index: number }, event: any) => void; - /** - * Key of the collumn to sort by - */ - sortKey: string; - /** - * Boolen to indicate whether the sort is ascending or descending - */ - ascending: boolean; - onSort(data: { colKey: string; colField?: keyof T; ascending: boolean }, event: any): void; - tableClass?: string; - tableStyle?: React.CSSProperties; - theadStyle?: React.CSSProperties; - theadClass?: string; - tbodyStyle?: React.CSSProperties; - tbodyClass?: string; - selected?(data: T): boolean; - onDragStart?:((data: { colKey: string, colField?: keyof T, row: T, data: T[keyof T] | null, index: number }, e: any) => void); - rowStyle?: React.CSSProperties; - keySelector?: (data: T) => string; - /** - * Optional Element to display in the last row of the Table - * use this for displaying warnings when the Table content gets cut off - */ - lastRow?: string|React.ReactNode; -} - -export default function Table(props: TableProps) { - - return ( -
    - Class={props.theadClass} Style={props.theadStyle} Cols={props.cols} SortKey={props.sortKey} Ascending={props.ascending} Click={(d, e) => handleSort(d, e)} /> - - DragStart={props.onDragStart} Data={props.data} Cols={props.cols} RowStyle={props.rowStyle} BodyStyle={props.tbodyStyle} BodyClass={props.tbodyClass} Click={(data, e) => (props.onClick === undefined? null : props.onClick(data, e))} Selected={props.selected} KeySelector={props.keySelector} /> - {props.lastRow !== undefined? - {props.lastRow} - : null} -
    - ); - - function handleSort( - data: { colKey: string; colField?: keyof T; ascending: boolean }, - event: React.MouseEvent, - ) { - if (data.colKey !== null) - props.onSort(data,event); - } -} - -interface IRowProps { - Data: T[], - Cols: Column[], - RowStyle?: React.CSSProperties, - BodyStyle?: React.CSSProperties, - BodyClass?: string, - Click: (data: { colKey: string, colField?: keyof T, row: T, data: T[keyof T] | null, index: number }, e: React.MouseEvent) => void, - DragStart?:((data: { colKey: string, colField?: keyof T, row: T, data: T[keyof T] | null, index: number }, e: any) => void) - Selected?: ((data: T) => boolean); - KeySelector?: (data: T) => string; -} -export function Rows(props: IRowProps) { - - if (props.Data.length === 0) return null; - - const rows = props.Data.map((item, rowIndex) => { - const cells = props.Cols.map((colData) => { - return key={colData.key} Style={colData.rowStyle} DataKey={colData.key} DataField={colData.field} Object={item} RowIndex={rowIndex} Content={colData.content} Click={(data, e) => props.Click(data, e)} DragStart={props.DragStart} /> - }); - - const style: React.CSSProperties = (props.RowStyle !== undefined) ? { ...props.RowStyle } : {}; - - if (style.cursor === undefined) - style.cursor = 'pointer'; - - if (props.Selected !== undefined && props.Selected(item)) - style.backgroundColor = 'yellow'; - - function ToKey(index: number, data: T): string { - if (props.KeySelector === undefined) - return index.toString(); - return props.KeySelector(data); - - } - - return ( - - {cells} - - ); - }); - - return ( - {rows} - ); -} - -interface ICellProps { - Style?: React.CSSProperties, - DataKey: string, - DataField?: keyof T, - Object: T, - RowIndex: number, - Content?: ((item: T, key: string, field: keyof T | undefined, style: React.CSSProperties, index: number) => React.ReactNode), - Click: (data: { colKey: string, colField?: keyof T, row: T, data: T[keyof T] | null, index: number }, e: React.MouseEvent) => void, - DragStart?: (data: { colKey: string, colField?: keyof T, row: T, data: T[keyof T] | null, index: number }, e: any) => void, -} - -function Cell(props: ICellProps) { - const css: React.CSSProperties = (props.Style !== undefined) ? { ...props.Style } : {}; - if (props.DragStart !== undefined) css.cursor = "grab"; - - const getFieldValue = () => props.DataField !== undefined ? props.Object[props.DataField] : null; - - const getFieldContent = () => props.Content !== undefined ? props.Content(props.Object, props.DataKey, props.DataField, css, props.RowIndex) : getFieldValue(); - - return ( - props.Click({ colKey: props.DataKey, colField: props.DataField, row: props.Object, data: getFieldValue(), index: props.RowIndex }, e)} - draggable={props.DragStart !== undefined} - onDragStart={(e) => { if (props.DragStart !== undefined) props.DragStart({ colKey: props.DataKey, colField: props.DataField, row: props.Object, data: getFieldValue(), index: props.RowIndex }, e);}} - > - {getFieldContent() as string} - - ); -} - -interface IHeaderProps { - Class?: string, - Style?: React.CSSProperties, - Cols: Column[], - SortKey: string, - Ascending: boolean, - Click: (data: { colKey: string; colField?: keyof T; ascending: boolean }, event: React.MouseEvent) => void - -} -export function Header(props: IHeaderProps) { - - return ({props.Cols.map((col) => props.Click({ colKey: col.key, colField: col.field, ascending: props.Ascending }, e)} Label={col.label} SortKey={props.SortKey} Ascending={props.Ascending} />)}) - -} - -interface IHeaderCellProps { - HeaderStyle?: React.CSSProperties, - DataKey: string, - Click: (e: any) => void, - Label: string|React.ReactNode, - SortKey: string, - Ascending: boolean -} -function HeaderCell(props: IHeaderCellProps) { - const style: React.CSSProperties = (props.HeaderStyle !== undefined) ? props.HeaderStyle : {}; - - if (style.cursor === undefined && props.DataKey !== null) { - style.cursor = 'pointer'; - } - - if (style.position === undefined) { - style.position = 'relative'; - } - - return ( - props.Click(e)} - > - - -
    {props.Label}
    - - ); -} - -interface IRenderAngleProps { - SortKey: string, - Key: string, - Ascending: boolean -} - -function RenderAngleIcon(props: IRenderAngleProps) { - - const AngleIcon: React.FunctionComponent<{ ascending: boolean }> = (a) => a.ascending ? SVGIcons.ArrowDropUp : SVGIcons.ArrowDropDown; - - if (props.SortKey === null) - return null; - - if (props.SortKey !== props.Key) - return null; - - return
    - -
    -} \ No newline at end of file diff --git a/react-table/src/index.ts b/react-table/src/index.ts index 246ee52b..d6c85c68 100644 --- a/react-table/src/index.ts +++ b/react-table/src/index.ts @@ -23,24 +23,19 @@ // // ****************************************************************************************************** -import Table, {TableProps,Rows, Column} from './Table'; -import {SelectTable, ISelectTableProps} from './SelectTable'; -import { SearchableTable } from './SearchableTable'; -import { DynamicTableProps, DynamicTable } from './DynamicTable'; import Paging from './Paging'; -import AdjustableTable from './AdjustableTable/Table'; -import UpdatedColumn from './AdjustableTable/Column'; -import AdjustableColumn from './AdjustableTable/AdjustableColumn'; +import * as ReactTableProps from './AdjustableTable/Types'; +import { AdjustableTable } from './AdjustableTable/Table'; +import { Column, AdjustableColumn } from './AdjustableTable/Column'; const ReactTable = { Table: AdjustableTable, - Column: UpdatedColumn, + Column: Column, AdjustableColumn: AdjustableColumn, } export { ReactTable, - TableProps, SelectTable, ISelectTableProps, SearchableTable, DynamicTable, DynamicTableProps , Rows, Column, Paging + ReactTableProps, + Paging } - -export default Table;