diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ProcessedFile/ByDataOperationsFailure.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ProcessedFile/ByDataOperationsFailure.tsx index ab25ccc56..eb5776817 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ProcessedFile/ByDataOperationsFailure.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ProcessedFile/ByDataOperationsFailure.tsx @@ -117,7 +117,7 @@ function ByDataOperationsFailure(props: IProps) {
- + TableClass="table table-hover" Data={failureData} diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ValueListGroup/ValueListForm.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ValueListGroup/ValueListForm.tsx index 52fec6e67..a7eb03883 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ValueListGroup/ValueListForm.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ValueListGroup/ValueListForm.tsx @@ -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, @@ -33,6 +35,16 @@ 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) @@ -40,8 +52,10 @@ export default function ValueListForm(props: IProps) { 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)) diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ValueListGroup/ValueListGroupDelete.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ValueListGroup/ValueListGroupDelete.tsx index 62bece827..a539726e3 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ValueListGroup/ValueListGroupDelete.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ValueListGroup/ValueListGroupDelete.tsx @@ -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('') const [prevent, setPrevent] = React.useState(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 @@ -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('') - const [prevent, setPrevent] = React.useState(false) - const [removalCount, setRemovalCount] = React.useState(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 ( {props.CallBack(c && !prevent)}} /> + return ( + ) } \ No newline at end of file diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ValueListGroup/ValueListGroupForm.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ValueListGroup/ValueListGroupForm.tsx index 3bc61d862..e7959a156 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ValueListGroup/ValueListGroupForm.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ValueListGroup/ValueListGroupForm.tsx @@ -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 { @@ -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 (
Record={props.Record} Field={'Name'} Feedback={'Name must be less than 200 characters.'} diff --git a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ValueListGroup/ValueListGroupItem.tsx b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ValueListGroup/ValueListGroupItem.tsx index 267eb32b3..dbecd776e 100644 --- a/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ValueListGroup/ValueListGroupItem.tsx +++ b/Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCenter/ValueListGroup/ValueListGroupItem.tsx @@ -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(); @@ -48,16 +52,40 @@ export default function ValueListGroupItems(props: IProps) { const [showModal, setShowModal] = React.useState(false); const [errors, setErrors] = React.useState([]); + const [countDictionary, setCountDictionary] = React.useState<{ [key: string]: number }>({}); + const [hover, setHover] = React.useState(''); + + 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 (
@@ -116,18 +144,38 @@ export default function ValueListGroupItems(props: IProps) { AllowSort={false} HeaderStyle={{ width: 'auto' }} RowStyle={{ width: 'auto' }} - Content={({ item }) => <> - - - } + Content={({ item }) => { + const id = item.ID.toString(); + const isDisallowed = disallowReason(id) != null; + return ( + <> + + + + ); + }} >

@@ -142,14 +190,22 @@ export default function ValueListGroupItems(props: IProps) {
{ 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} - /> + /> + + {disallowReason(hover)} + 0} - CancelToolTipContent={<> {errors.map(e =>

{e}

)}} + ConfirmToolTipContent={errors.map((e, i) =>

{e}

)} DisableConfirm={errors.length > 0} ShowX={true} CallBack={(conf) => { setShowModal(false);