Skip to content
Draft
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ function ByDataOperationsFailure(props: IProps) {
</div>
</div>
<hr />
<LoadingScreen Show={pageStatus == 'idle'} />
<LoadingScreen Show={pageStatus !== 'idle'} />
<Table<OpenXDA.Types.DataOperationFailure>
TableClass="table table-hover"
Data={failureData}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ 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';
import { useAppSelector, useAppDispatch } from '../hooks';
import { ValueListSlice } from '../Store/Store';

interface IProps {
Record: SystemCenter.Types.ValueListItem,
Expand All @@ -33,15 +35,27 @@ interface IProps {
}

export default function ValueListForm(props: IProps) {
const dispatch = useAppDispatch();

const data = useAppSelector(ValueListSlice.Data);
const status = useAppSelector(ValueListSlice.Status);
const parentID = useAppSelector(ValueListSlice.ParentID);

React.useEffect(() => {
if (status == 'uninitiated' || status == 'changed' || parentID != props.Record.GroupID)
dispatch(ValueListSlice.Fetch(props.Record.GroupID));
}, [status, parentID, props.Record.GroupID]);

React.useEffect(() => {
if (props.SetErrors == undefined)
return;
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)
else if (props.Record.Value.length > 200)
e.push('Value must be less than 200 characters.');
else if (data.findIndex(valueItem => props.Record.ID !== valueItem.ID && props.Record.Value.localeCompare(valueItem.Value, undefined, { sensitivity: 'base' }) === 0) >= 0)
e.push('Value may not be a duplicate of another in the same group.');
if (props.Record.AltValue != null && props.Record.AltValue.length > 200)
e.push('Label must be less than 200 characters.');
if (props.Record.SortOrder != null && !IsInteger(props.Record.SortOrder))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,22 @@
//******************************************************************************************************

import * as React from 'react';
import { useAppSelector, useAppDispatch } from '../hooks';
import { Warning } from '@gpa-gemstone/react-interactive';
import { SystemCenter } from '@gpa-gemstone/application-typings';
interface IProps {
CallBack: (conf: boolean) => void,
Record: SystemCenter.Types.ValueListGroup,
Show: boolean
}

declare var homePath: string;
interface IProps { CallBack: (conf: boolean) => void, Record: SystemCenter.Types.ValueListGroup, Show: boolean }
export const requiredValueLists = ["TimeZones", "Make", "Model", "Unit", "Category", "SpareChannel", "TrendLabelDefaults", "TrendLabelOptions"]
export const RequiredValueLists = ["TimeZones", "Make", "Model", "Unit", "Category", "SpareChannel", "TrendLabelDefaults", "TrendLabelOptions"]

export function ValueListGroupDelete(props: IProps) {
const [message, setMessage] = React.useState<string>('')
const [prevent, setPrevent] = React.useState<boolean>(false)

React.useEffect(() => {
if (requiredValueLists.includes(props.Record?.Name)) {
if (RequiredValueLists.includes(props.Record?.Name)) {
setPrevent(true);
setMessage('This Value List Group is required and cannot be removed.')
return
Expand All @@ -52,65 +54,33 @@ export function ValueListGroupDelete(props: IProps) {
)
}

interface IPropsItem {
interface IPropsItem {
CallBack: (conf: boolean) => void,
Record: SystemCenter.Types.ValueListItem,
Show: boolean,
ItemCount: number,
Record: SystemCenter.Types.ValueListItem,
Show: boolean,
GroupItemCount: number,
AssignedDictionary: { [key: string]: number },
Group: SystemCenter.Types.ValueListGroup
}

export function ValueListItemDelete(props: IPropsItem) {
const message = React.useMemo(() => {
const assignedCount = props.AssignedDictionary?.[props.Record.ID] ?? 0;

const [message, setMessage] = React.useState<string>('')
const [prevent, setPrevent] = React.useState<boolean>(false)
const [removalCount, setRemovalCount] = React.useState<number>(0);

React.useEffect(() => {
if ((props.Group?.Name?.length ?? 0) === 0 || (props.Record?.Value ?? 0) === 0)
return;

const h = $.ajax({
type: "GET",
url: `${homePath}api/ValueList/Count/${props.Group?.Name ?? 'Make'}/${props.Record?.Value}`,
contentType: "application/json; charset=utf-8",
dataType: 'json',
cache: false,
async: true
});
h.then((c: number) => {setRemovalCount(c);});
return () => { if (h != null && h.abort != null) h.abort();}
}, [props.Group, props.Record])

React.useEffect(() => {
if (requiredValueLists.includes(props.Group?.Name) && props.ItemCount == 1) {
setPrevent(true);
setMessage('This Value List Group is required and must contain at least 1 item.')
}
else if (props.ItemCount == 1 && removalCount > 0)
{
setMessage('Removing this Value List Item will result in an empty Value List Group. All Fields using this Value List Group will be changed to strings.')
setPrevent(false);
}
else if (requiredValueLists.includes(props.Group?.Name) && removalCount > 0) {
setPrevent(true);
setMessage('This Value List Group is required and this Value List Item is still in use. Use of this Value List Item must be removed before it can be deleted.')
}
else if (removalCount > 0)
{
setMessage(`This Value List Group is in use, with ${removalCount} values corresponding to this Value List Item. These values will be unassigned.`)
setPrevent(false);
}
else {
setPrevent(false);
setMessage('This will permanently delete this Value List Item and cannot be undone.')
}
}, [props.Group, removalCount, props.ItemCount])
if (props.GroupItemCount == 1 && assignedCount > 0)
return 'Removing this Value List Item will result in an empty Value List Group. All Fields using this Value List Group will be changed to strings.';
else if (assignedCount > 0)
return `This Value List Group is in use, with ${assignedCount} values corresponding to this Value List Item. These values will be unassigned.`;
else
return 'This will permanently delete this Value List Item and cannot be undone.';
}, [props.Group?.Name, props.Record.ID, props.AssignedDictionary, props.GroupItemCount])

return ( <Warning
Message={message}
Show={props.Show} Title={'Delete ' + (props.Record.AltValue ?? props.Record.Value)}
ShowCancel={!prevent}
CallBack={(c) => {props.CallBack(c && !prevent)}} />
return (
<Warning
Message={message}
Show={props.Show}
Title={'Delete ' + (props.Record.AltValue ?? props.Record.Value)}
CallBack={props.CallBack}
/>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import * as React from 'react';
import { SystemCenter } from '@gpa-gemstone/application-typings';
import { Input, TextArea } from '@gpa-gemstone/react-forms';
import { requiredValueLists } from './ValueListGroupDelete';
import { RequiredValueLists } from './ValueListGroupDelete';

export default function ValueListGroupForm(props: { Record: SystemCenter.Types.ValueListGroup, Setter: (record: SystemCenter.Types.ValueListGroup) => void, setErrors?: (e: string[]) => void }) {
function Valid(field: keyof (SystemCenter.Types.ValueListGroup)): boolean {
Expand All @@ -35,7 +35,8 @@ export default function ValueListGroupForm(props: { Record: SystemCenter.Types.V
return false;
}

const required = requiredValueLists.includes(props.Record?.Name)
const required = RequiredValueLists.includes(props.Record?.Name);

return (
<form>
<Input<SystemCenter.Types.ValueListGroup> Record={props.Record} Field={'Name'} Feedback={'Name must be less than 200 characters.'}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@ import { ValueListSlice } from '../Store/Store';
import ValueListForm from './ValueListForm';
import { Table, Column } from '@gpa-gemstone/react-table';
import { ReactIcons } from '@gpa-gemstone/gpa-symbols';
import { Modal, Warning } from '@gpa-gemstone/react-interactive';
import { ValueListItemDelete } from './ValueListGroupDelete';
import { Modal } from '@gpa-gemstone/react-interactive';
import { ValueListItemDelete, RequiredValueLists } from './ValueListGroupDelete';
import { ToolTip } from '@gpa-gemstone/react-forms';

interface IProps {
Record: SystemCenter.Types.ValueListGroup
}

interface IProps { Record: SystemCenter.Types.ValueListGroup }
export default function ValueListGroupItems(props: IProps) {
const dispatch = useAppDispatch();

Expand All @@ -48,16 +52,40 @@ export default function ValueListGroupItems(props: IProps) {
const [showModal, setShowModal] = React.useState<boolean>(false);
const [errors, setErrors] = React.useState<string[]>([]);

const [countDictionary, setCountDictionary] = React.useState<{ [key: string]: number }>({});
const [hover, setHover] = React.useState<string>('');

const disallowReason = React.useCallback((ID: string) => {
if (!RequiredValueLists.includes(props.Record?.Name))
return null;
if (data.length == 1)
return 'This Value List Group is required and must contain at least 1 item.';
if ((countDictionary?.[ID] ?? 0) !== 0)
return 'This Value List Group is required and this Value List Item is still in use. Use of this Value List Item must be removed before it can be deleted.';

return null;
}, [props.Record?.Name, data.length, countDictionary]);

React.useEffect(() => {
if (status == 'uninitiated' || status == 'changed' || parentID != props.Record.ID)
dispatch(ValueListSlice.Fetch(props.Record.ID));
}, [status, parentID, props.Record.ID]);

function Delete() {
dispatch(ValueListSlice.DBAction({ verb: 'DELETE', record: { ...record } }));
setShowWarning(false);
setRecord(emptyRecord);
}
React.useEffect(() => {
if (props.Record?.Name == null) return;

const h = $.ajax({
type: "GET",
url: `${homePath}api/ValueList/Count/${props.Record.Name}`,
contentType: "application/json; charset=utf-8",
dataType: 'json',
cache: false,
async: true
});
h.then(setCountDictionary);

return () => { if (h?.abort != null) h.abort(); }
}, [props.Record?.Name]);

return (
<div className="card" style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
Expand Down Expand Up @@ -116,18 +144,38 @@ export default function ValueListGroupItems(props: IProps) {
AllowSort={false}
HeaderStyle={{ width: 'auto' }}
RowStyle={{ width: 'auto' }}
Content={({ item }) => <>
<button className="btn btn-sm" onClick={(e) => {
e.preventDefault();
setRecord(item);
setShowModal(true);
}}><ReactIcons.Pencil Color="var(--warning)" Size={20} /></button>
<button className="btn btn-sm" onClick={(e) => {
e.preventDefault();
setRecord(item);
setShowWarning(true)
}}><ReactIcons.TrashCan Color="var(--danger)" Size={20} /></button>
</> }
Content={({ item }) => {
const id = item.ID.toString();
const isDisallowed = disallowReason(id) != null;
return (
<>
<button
className="btn btn-sm"
onClick={(e) => {
e.preventDefault();
setRecord(item);
setShowModal(true);
}}
>
<ReactIcons.Pencil Color="var(--warning)" Size={20} />
</button>
<button
className={`btn btn-sm${isDisallowed ? " disabled" : ""}`}
onClick={(e) => {
e.preventDefault();
if (isDisallowed) return;
setRecord(item);
setShowWarning(true);
}}
onMouseEnter={() => { if (isDisallowed) setHover(id); }}
onMouseLeave={() => setHover('')}
data-tooltip={id}
>
<ReactIcons.TrashCan Color="var(--danger)" Size={20} />
</button>
</>
);
}}
> <p></p>
</Column>
</Table>
Expand All @@ -142,14 +190,22 @@ export default function ValueListGroupItems(props: IProps) {
</div>
<ValueListItemDelete
Show={showWarning}
CallBack={(conf) => { if (conf) Delete(); setShowWarning(false); }}
CallBack={(conf) => {
if (conf)
dispatch(ValueListSlice.DBAction({ verb: 'DELETE', record: { ...record } }));
setShowWarning(false);
}}
Record={record}
ItemCount={data.length}
GroupItemCount={data.length}
AssignedDictionary={countDictionary}
Group={props.Record}
/>
/>
<ToolTip Show={hover !== ''} Position={'bottom'} Target={hover}>
{disallowReason(hover)}
</ToolTip>
<Modal Title={record.ID == 0 ? 'Add New Value List Item' : 'Edit ' + (record.AltValue ?? record.Value)} Show={showModal} ShowCancel={false} ConfirmText={'Save'}
ConfirmShowToolTip={errors.length > 0}
CancelToolTipContent={<> {errors.map(e => <p><ReactIcons.CrossMark Color="var(--danger)" /> {e}</p>)}</>}
ConfirmToolTipContent={errors.map((e, i) => <p key={i}><ReactIcons.CrossMark Color="var(--danger)" /> {e}</p>)}
DisableConfirm={errors.length > 0}
ShowX={true} CallBack={(conf) => {
setShowModal(false);
Expand Down