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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 === 'loading'} />
<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