Refactored all class components to be function components

This commit is contained in:
Colin Dawson 2026-01-31 17:09:29 +00:00
parent adffa4f448
commit dce95c8345
31 changed files with 4961 additions and 1880 deletions

View File

@ -1,19 +1,36 @@
@import './_esuiteVariables.scss';
@import "./_esuiteVariables.scss";
.two-column-grid-1-1 {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: $gridGap;
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: $gridGap;
}
.two-column-grid-1-3 {
display: grid;
grid-template-columns: 1fr 3fr;
grid-gap: $gridGap;
display: grid;
grid-template-columns: 1fr 3fr;
grid-gap: $gridGap;
}
.mail-types {
padding-left: 0rem;
list-style: none;
width: $mailtemplateNameListWidth;
}
padding-left: 0rem;
list-style: none;
width: $mailtemplateNameListWidth;
li {
cursor: pointer;
padding: 0.5rem 0.75rem;
border-radius: 4px;
transition: background-color 0.2s ease;
&:hover {
background-color: rgba(0, 0, 0, 0.05);
}
&.selected {
background-color: #0078d4;
color: white;
font-weight: 500;
}
}
}

View File

@ -3,17 +3,22 @@ import TabHeader from "./TabHeader";
interface HorizontalTabsProps {
children: JSX.Element[];
initialTab?: string;
}
const HorizontalTabs: React.FC<HorizontalTabsProps> = ({ children }) => {
const HorizontalTabs: React.FC<HorizontalTabsProps> = ({
children,
initialTab,
}) => {
const [activeTab, setActiveTab] = useState<string>("");
// Set initial tab on mount
useEffect(() => {
if (children.length > 0) {
setActiveTab(children[0].props.label);
const tabToSelect = initialTab || children[0].props.label;
setActiveTab(tabToSelect);
}
}, [children]);
}, [children, initialTab]);
const onClickTabItem = useCallback((tab: string) => {
setActiveTab((prev) => (prev !== tab ? tab : prev));

View File

@ -0,0 +1,644 @@
export * from "./formHelpers.tsx";
/*
import React from "react";
import Input, { InputType } from "./Input";
import ToggleSlider from "./ToggleSlider";
import Select from "./Select";
import Option from "./option";
import { GeneralIdRef } from "../../utils/GeneralIdRef";
import SequencePicker from "../pickers/SequencePicker";
import GlossaryPicker from "../pickers/GlossaryPicker";
import CustomFieldsEditor, { CustomFieldEditorAdd, CustomFieldEditorDelete } from "./CustomFieldsEditor";
import { CustomField, numberParams, textParams } from "../../modules/manager/customfields/services/customFieldsService";
import FormTemplatePicker from "../pickers/FormTemplatePicker";
import { CustomFieldValue } from "../../modules/manager/glossary/services/glossaryService";
import TemplateEditor from "./TemplateEditor";
import DomainPicker from "../pickers/DomainPicker";
import UserPicker from "../pickers/UserPicker";
import Button, { ButtonType } from "./Button";
import Expando from "./expando";
import ErrorBlock from "./ErrorBlock";
import SsoProviderPicker from "../pickers/SsoProviderPicker";
import { FormData, FormError } from "./useForm";
export const renderButton = (
label: string,
errors: FormError,
name?: string,
onClick?: (keyValue: any) => void,
testid?: string,
enabled: boolean = true,
buttonType: ButtonType = ButtonType.primary,
overrideErrorChecking: boolean = false
) => {
let disabled = !enabled || Object.keys(errors).filter((x) => !x.startsWith("_")).length > 0;
if (overrideErrorChecking) disabled = !enabled;
return (
<Button testid={testid} disabled={disabled} name={name ?? label} buttonType={buttonType} onClick={onClick}>
{label}
</Button>
);
};
export const renderError = (name: string, errors: FormError) => {
return <ErrorBlock error={errors[name]} />;
};
export const renderInput = (
name: string,
label: string,
data: FormData,
errors: FormError,
type: InputType = InputType.text,
readOnly = false,
defaultValue: string = "",
placeHolder: string = "",
maxLength: number = 0,
visible: boolean = true,
autoComplete: string | undefined = undefined,
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
) => {
let value = data[name];
let cleanValue: string | undefined;
if (value === undefined) {
cleanValue = defaultValue;
} else if (typeof value === "string") {
cleanValue = value as string;
} else if (typeof value === "number") {
cleanValue = String(value);
} else if (typeof value === "boolean") {
cleanValue = String(value);
} else if (value as CustomFieldValue) {
cleanValue = (value as CustomFieldValue).displayValue ?? (value as CustomFieldValue).value.toString();
}
if (readOnly) {
return (
<Input
includeLabel={true}
type={type}
name={name}
label={label}
value={cleanValue}
error={errors[name]}
maxLength={maxLength}
readOnly
placeHolder={placeHolder}
hidden={!visible}
autoComplete={autoComplete}
/>
);
} else {
return (
<Input
includeLabel={true}
type={type}
name={name}
label={label}
value={cleanValue}
error={errors[name]}
maxLength={maxLength}
onChange={onChange}
placeHolder={placeHolder}
hidden={!visible}
autoComplete={autoComplete}
/>
);
}
};
export const renderInputWithChangeEvent = (
name: string,
label: string,
data: FormData,
errors: FormError,
type: InputType = InputType.text,
readOnly = false,
handleChangeEvent?: (e: React.ChangeEvent<HTMLInputElement>) => void,
defaultValue: string = "",
placeHolder: string = "",
maxLength: number = 0
) => {
let value = data[name];
let cleanValue: string | undefined;
if (value === undefined) {
cleanValue = defaultValue;
} else if (typeof value === "string") {
cleanValue = value as string;
} else if (typeof value === "number") {
cleanValue = String(value);
} else if (typeof value === "boolean") {
cleanValue = String(value);
} else if (value as CustomFieldValue) {
cleanValue = (value as CustomFieldValue).displayValue ?? (value as CustomFieldValue).value.toString();
}
if (readOnly) {
return (
<Input
includeLabel={true}
type={type}
name={name}
label={label}
value={cleanValue}
error={errors[name]}
maxLength={maxLength}
readOnly
placeHolder={placeHolder}
/>
);
} else {
return (
<Input
includeLabel={true}
type={type}
name={name}
label={label}
value={cleanValue}
error={errors[name]}
maxLength={maxLength}
onChange={handleChangeEvent}
placeHolder={placeHolder}
/>
);
}
};
export const renderInputNumber = (
name: string,
label: string,
data: FormData,
errors: FormError,
readOnly = false,
defaultValue: string = "",
min?: number,
max?: number,
step: number = 1,
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
) => {
let value = data[name];
let cleanValue: string | undefined;
if (value === undefined) {
cleanValue = defaultValue;
} else if (typeof value === "string") {
cleanValue = value as string;
} else if (typeof value === "number") {
cleanValue = String(value);
} else if (typeof value === "boolean") {
cleanValue = String(value);
} else if (value as CustomFieldValue) {
cleanValue = (value as CustomFieldValue).displayValue ?? (value as CustomFieldValue).value.toString();
}
if (readOnly) {
return <Input includeLabel={true} type={InputType.number} name={name} label={label} value={cleanValue} error={errors[name]} readOnly />;
} else {
return (
<Input
includeLabel={true}
type={InputType.number}
name={name}
label={label}
value={cleanValue}
error={errors[name]}
min={min}
max={max}
step={step}
onChange={onChange}
/>
);
}
};
export const renderInputTextarea = (
includeLabel: boolean,
name: string,
label: string,
data: FormData,
errors: FormError,
readOnly = false,
defaultValue: string = "",
onTextAreaChange?: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
) => {
let value = data[name];
let cleanValue: string | undefined;
if (value === undefined) {
cleanValue = defaultValue;
} else if (typeof value === "string") {
cleanValue = value as string;
}
if (readOnly) {
return (
<Input
includeLabel={includeLabel}
type={InputType.textarea}
key={name}
name={name}
label={label}
value={cleanValue}
error={errors[name]}
readOnly
/>
);
} else {
return (
<Input
includeLabel={includeLabel}
type={InputType.textarea}
key={name}
name={name}
label={label}
value={cleanValue}
error={errors[name]}
onTextAreaChange={onTextAreaChange}
/>
);
}
};
export const renderCustomFieldInput = (
includeLabel: boolean,
name: string,
label: string,
data: FormData,
errors: FormError,
type: InputType = InputType.text,
readOnly = false,
defaultValue: string = "",
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
) => {
let value = data[name];
let cleanValue: string | undefined;
if (value === undefined) {
cleanValue = defaultValue;
} else if (typeof value === "string") {
cleanValue = value as string;
} else if (typeof value === "number") {
cleanValue = String(value);
} else if (typeof value === "boolean") {
cleanValue = String(value);
} else if (value as CustomFieldValue) {
const customFieldValue = value as CustomFieldValue[];
cleanValue = customFieldValue[0].displayValue ?? customFieldValue[0].value?.toString();
}
if (readOnly) {
return <Input includeLabel={includeLabel} type={type} name={name} label={label} value={cleanValue} error={errors[name]} readOnly />;
} else {
return (
<Input
includeLabel={includeLabel}
type={type}
name={name}
label={label}
value={cleanValue}
error={errors[name]}
onChange={onChange}
/>
);
}
};
export const renderCustomFieldNumber = (
includeLabel: boolean,
name: string,
label: string,
data: FormData,
errors: FormError,
readOnly = false,
defaultValue: string = "",
min?: number,
max?: number,
step: number = 1,
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
) => {
let values: CustomFieldValue[] = data[name] as CustomFieldValue[];
let value: CustomFieldValue | undefined = undefined;
if (values) {
if (values.length > 0) {
value = values[0];
}
}
let cleanValue: string | undefined;
if (value === undefined) {
cleanValue = defaultValue;
} else if (typeof value === "string") {
cleanValue = value as string;
} else if (typeof value === "number") {
cleanValue = String(value);
} else if (value as CustomFieldValue) {
cleanValue = (value as CustomFieldValue).displayValue ?? (value as CustomFieldValue).value?.toString();
}
if (readOnly) {
return <Input includeLabel={includeLabel} type={InputType.number} name={name} label={label} value={cleanValue} error={errors[name]} readOnly />;
} else {
return (
<Input
includeLabel={includeLabel}
type={InputType.number}
name={name}
label={label}
value={cleanValue}
error={errors[name]}
min={min}
max={max}
step={step}
onChange={onChange}
/>
);
}
};
export const renderTemplateEditor = (
className: string,
name: string,
label: string,
data: FormData,
allowCustomFields: boolean,
onChange?: (name: string, value: string) => void
) => {
let value = data[name] as string;
return (
<div>
<label htmlFor={name}>{label}</label>
<TemplateEditor className={className} name={name} data={value} onChange={onChange} showFields={allowCustomFields} />
</div>
);
};
export const renderSelect = (
name: string,
label: string,
data: FormData,
errors: FormError,
options: Option[],
onChange?: (e: React.ChangeEvent<HTMLSelectElement>) => void
) => {
return <Select name={name} label={label} value={data[name]} options={options} error={errors[name]} onChange={onChange} />;
};
export const renderToggle = (
name: string,
label: string,
data: FormData,
errors: FormError,
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
) => {
return <ToggleSlider name={name} label={label} defaultChecked={Boolean(data[name])} error={errors[name]} onChange={onChange} />;
};
export const renderSequencePicker = (
includeLabel: boolean,
name: string,
label: string,
data: FormData,
errors: FormError,
onChange?: (name: string, value: GeneralIdRef) => void
) => {
return (
<SequencePicker includeLabel={includeLabel} name={name} label={label} value={data[name]} error={errors[name]} onChange={onChange} />
);
};
export const renderGlossaryPicker = (
includeLabel: boolean,
name: string,
label: string,
data: FormData,
errors: FormError,
maxEntries?: number,
refElementId?: GeneralIdRef,
onChange?: (name: string, values: CustomFieldValue[]) => void
) => {
const glossaryValues: CustomFieldValue[] | undefined = data[name] as any as CustomFieldValue[];
return (
<GlossaryPicker
includeLabel={includeLabel}
name={name}
label={label}
maxEntries={maxEntries}
values={glossaryValues}
error={errors[name]}
rootItem={refElementId}
onChange={onChange}
/>
);
};
export const renderDomainPicker = (
includeLabel: boolean,
name: string,
label: string,
data: FormData,
errors: FormError,
minEntries: number,
maxEntries?: number,
onChange?: (name: string, values: CustomFieldValue[]) => void
) => {
const domainValues: CustomFieldValue[] | undefined = data[name] as any as CustomFieldValue[];
return (
<DomainPicker
includeLabel={includeLabel}
name={name}
label={label}
minEntries={minEntries}
maxEntries={maxEntries}
values={domainValues}
error={errors[name]}
onChange={onChange}
/>
);
};
export const renderTemplatePicker = (
includeLabel: boolean,
name: string,
label: string,
data: FormData,
errors: FormError,
onChange?: (name: string, value: GeneralIdRef) => void
) => {
const templateValue: GeneralIdRef = data[name] as any as GeneralIdRef;
return (
<FormTemplatePicker
includeLabel={includeLabel}
name={name}
label={label}
value={templateValue}
error={errors[name]}
onChange={onChange}
/>
);
};
export const renderUserPicker = (
name: string,
label: string,
data: FormData,
errors: FormError,
onChange?: (name: string, value: GeneralIdRef) => void
) => {
const glossaryValue: GeneralIdRef | undefined = data[name] as any as GeneralIdRef;
return <UserPicker name={name} label={label} value={glossaryValue} error={errors[name]} onChange={onChange} />;
};
export const renderSsoProviderPicker = (
name: string,
label: string,
data: FormData,
errors: FormError,
onChange?: (name: string, value: GeneralIdRef) => void
) => {
const glossaryValue: GeneralIdRef | undefined = data[name] as any as GeneralIdRef;
return <SsoProviderPicker name={name} label={label} value={glossaryValue} error={errors[name]} onChange={onChange} />;
};
export const renderCustomFieldsEditor = (
name: string,
label: string,
data: FormData,
errors: FormError,
selected: CustomField[],
onAdd: CustomFieldEditorAdd,
onDelete: CustomFieldEditorDelete
) => {
return (
<CustomFieldsEditor name={name} label={label} value={data[name] as []} error={errors[name]} exclude={selected} onAdd={onAdd} onDelete={onDelete} />
);
};
export const renderCustomField = (
customField: CustomField,
includeLabel: boolean,
data: FormData,
errors: FormError,
onChange: (e: React.ChangeEvent<HTMLInputElement> | React.ChangeEvent<HTMLTextAreaElement>) => void,
onPickerChange: (name: string, value: GeneralIdRef | CustomFieldValue[]) => void,
getCustomFieldType: (field: CustomFieldValue, customFields: CustomField[]) => string
) => {
switch (customField.fieldType.toLowerCase()) {
case "text":
const textParameters: textParams = JSON.parse(customField.parameters!);
if (textParameters.multiLine) {
return renderInputTextarea(
includeLabel,
"customfield_" + customField.id,
customField.name,
data,
errors,
false,
customField.defaultValue,
onChange as (e: React.ChangeEvent<HTMLTextAreaElement>) => void
);
} else {
return renderCustomFieldInput(
includeLabel,
"customfield_" + customField.id,
customField.name,
data,
errors,
InputType.text,
false,
customField.defaultValue,
onChange as (e: React.ChangeEvent<HTMLInputElement>) => void
);
}
case "sequence":
return renderCustomFieldInput(includeLabel, "customfield_" + customField.id, customField.name, data, errors, InputType.text, true);
case "formtemplate":
return renderTemplatePicker(
includeLabel,
"customfield_" + customField.id,
customField.name,
data,
errors,
(name, value) => onPickerChange(name, value)
);
case "glossary":
return renderGlossaryPicker(
includeLabel,
"customfield_" + customField.id,
customField.name,
data,
errors,
customField.maxEntries,
customField.refElementId,
(name, values) => onPickerChange(name, values)
);
case "number":
const numberParameters: numberParams = JSON.parse(customField.parameters!);
return renderCustomFieldNumber(
includeLabel,
"customfield_" + customField.id,
customField.name,
data,
errors,
false,
customField.defaultValue,
numberParameters.minValue ? Number(numberParameters.minValue) : undefined,
numberParameters.maxValue ? Number(numberParameters.maxValue) : undefined,
numberParameters.step ? Number(numberParameters.step) : undefined,
onChange as (e: React.ChangeEvent<HTMLInputElement>) => void
);
case "domain":
return renderDomainPicker(
includeLabel,
"customfield_" + customField.id,
customField.name,
data,
errors,
customField.minEntries,
customField.maxEntries,
(name, values) => onPickerChange(name, values)
);
default:
return <>{customField.name + " " + customField.fieldType}</>;
}
};
export const renderCustomFields = (
customFields: CustomField[] | undefined,
data: FormData,
errors: FormError,
onChange: (e: React.ChangeEvent<HTMLInputElement> | React.ChangeEvent<HTMLTextAreaElement>) => void,
onPickerChange: (name: string, value: GeneralIdRef | CustomFieldValue[]) => void,
getCustomFieldType: (field: CustomFieldValue, customFields: CustomField[]) => string
) => {
if (customFields === undefined) return <></>;
let customFieldsBlock: JSX.Element[] = [];
for (const customField of customFields) {
customFieldsBlock.push(
renderCustomField(customField, true, data, errors, onChange, onPickerChange, getCustomFieldType)
);
}
return <>{customFieldsBlock.map((x) => x)}</>;
};
export const renderDropSection = (name: string, title: JSX.Element, content: JSX.Element, errors: FormError) => {
return (
<Expando name={name} title={title} error={errors[name]}>
{content}
</Expando>
);
};
*/

View File

@ -0,0 +1,804 @@
import React from "react";
import Input, { InputType } from "./Input";
import ToggleSlider from "./ToggleSlider";
import Select from "./Select";
import Option from "./option";
import { GeneralIdRef } from "../../utils/GeneralIdRef";
import SequencePicker from "../pickers/SequencePicker";
import GlossaryPicker from "../pickers/GlossaryPicker";
import CustomFieldsEditor, {
CustomFieldEditorAdd,
CustomFieldEditorDelete,
} from "./CustomFieldsEditor";
import {
CustomField,
numberParams,
textParams,
} from "../../modules/manager/customfields/services/customFieldsService";
import FormTemplatePicker from "../pickers/FormTemplatePicker";
import { CustomFieldValue } from "../../modules/manager/glossary/services/glossaryService";
import TemplateEditor from "./TemplateEditor";
import DomainPicker from "../pickers/DomainPicker";
import UserPicker from "../pickers/UserPicker";
import Button, { ButtonType } from "./Button";
import Expando from "./expando";
import ErrorBlock from "./ErrorBlock";
import SsoProviderPicker from "../pickers/SsoProviderPicker";
import { FormData, FormError } from "./useForm";
export const renderButton = (
label: string,
errors: FormError,
name?: string,
onClick?: (keyValue: any) => void,
testid?: string,
enabled: boolean = true,
buttonType: ButtonType = ButtonType.primary,
overrideErrorChecking: boolean = false,
) => {
let disabled =
!enabled ||
Object.keys(errors).filter((x) => !x.startsWith("_")).length > 0;
if (overrideErrorChecking) disabled = !enabled;
return (
<Button
testid={testid}
disabled={disabled}
name={name ?? label}
buttonType={buttonType}
onClick={onClick}
>
{label}
</Button>
);
};
export const renderError = (name: string, errors: FormError) => {
return <ErrorBlock error={errors[name]} />;
};
export const renderInput = (
name: string,
label: string,
data: FormData,
errors: FormError,
type: InputType = InputType.text,
readOnly = false,
defaultValue: string = "",
placeHolder: string = "",
maxLength: number = 0,
visible: boolean = true,
autoComplete: string | undefined = undefined,
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void,
) => {
let value = data[name];
let cleanValue: string | undefined;
if (value === undefined) {
cleanValue = defaultValue;
} else if (typeof value === "string") {
cleanValue = value as string;
} else if (typeof value === "number") {
cleanValue = String(value);
} else if (typeof value === "boolean") {
cleanValue = String(value);
} else if (value as CustomFieldValue) {
cleanValue =
(value as CustomFieldValue).displayValue ??
(value as CustomFieldValue).value.toString();
}
if (readOnly) {
return (
<Input
includeLabel={true}
type={type}
name={name}
label={label}
value={cleanValue}
error={errors[name]}
maxLength={maxLength}
readOnly
placeHolder={placeHolder}
hidden={!visible}
autoComplete={autoComplete}
/>
);
} else {
return (
<Input
includeLabel={true}
type={type}
name={name}
label={label}
value={cleanValue}
error={errors[name]}
maxLength={maxLength}
onChange={onChange}
placeHolder={placeHolder}
hidden={!visible}
autoComplete={autoComplete}
/>
);
}
};
export const renderInputWithChangeEvent = (
name: string,
label: string,
data: FormData,
errors: FormError,
type: InputType = InputType.text,
readOnly = false,
handleChangeEvent?: (e: React.ChangeEvent<HTMLInputElement>) => void,
defaultValue: string = "",
placeHolder: string = "",
maxLength: number = 0,
) => {
let value = data[name];
let cleanValue: string | undefined;
if (value === undefined) {
cleanValue = defaultValue;
} else if (typeof value === "string") {
cleanValue = value as string;
} else if (typeof value === "number") {
cleanValue = String(value);
} else if (typeof value === "boolean") {
cleanValue = String(value);
} else if (value as CustomFieldValue) {
cleanValue =
(value as CustomFieldValue).displayValue ??
(value as CustomFieldValue).value.toString();
}
if (readOnly) {
return (
<Input
includeLabel={true}
type={type}
name={name}
label={label}
value={cleanValue}
error={errors[name]}
maxLength={maxLength}
readOnly
placeHolder={placeHolder}
/>
);
} else {
return (
<Input
includeLabel={true}
type={type}
name={name}
label={label}
value={cleanValue}
error={errors[name]}
maxLength={maxLength}
onChange={handleChangeEvent}
placeHolder={placeHolder}
/>
);
}
};
export const renderInputNumber = (
name: string,
label: string,
data: FormData,
errors: FormError,
readOnly = false,
defaultValue: string = "",
min?: number,
max?: number,
step: number = 1,
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void,
) => {
let value = data[name];
let cleanValue: string | undefined;
if (value === undefined) {
cleanValue = defaultValue;
} else if (typeof value === "string") {
cleanValue = value as string;
} else if (typeof value === "number") {
cleanValue = String(value);
} else if (typeof value === "boolean") {
cleanValue = String(value);
} else if (value as CustomFieldValue) {
cleanValue =
(value as CustomFieldValue).displayValue ??
(value as CustomFieldValue).value.toString();
}
if (readOnly) {
return (
<Input
includeLabel={true}
type={InputType.number}
name={name}
label={label}
value={cleanValue}
error={errors[name]}
readOnly
/>
);
} else {
return (
<Input
includeLabel={true}
type={InputType.number}
name={name}
label={label}
value={cleanValue}
error={errors[name]}
min={min}
max={max}
step={step}
onChange={onChange}
/>
);
}
};
export const renderInputTextarea = (
includeLabel: boolean,
name: string,
label: string,
data: FormData,
errors: FormError,
readOnly = false,
defaultValue: string = "",
onTextAreaChange?: (e: React.ChangeEvent<HTMLTextAreaElement>) => void,
) => {
let value = data[name];
let cleanValue: string | undefined;
if (value === undefined) {
cleanValue = defaultValue;
} else if (typeof value === "string") {
cleanValue = value as string;
}
if (readOnly) {
return (
<Input
includeLabel={includeLabel}
type={InputType.textarea}
key={name}
name={name}
label={label}
value={cleanValue}
error={errors[name]}
readOnly
/>
);
} else {
return (
<Input
includeLabel={includeLabel}
type={InputType.textarea}
key={name}
name={name}
label={label}
value={cleanValue}
error={errors[name]}
onTextAreaChange={onTextAreaChange}
/>
);
}
};
export const renderCustomFieldInput = (
includeLabel: boolean,
name: string,
label: string,
data: FormData,
errors: FormError,
type: InputType = InputType.text,
readOnly = false,
defaultValue: string = "",
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void,
) => {
let value = data[name];
let cleanValue: string | undefined;
if (value === undefined) {
cleanValue = defaultValue;
} else if (typeof value === "string") {
cleanValue = value as string;
} else if (typeof value === "number") {
cleanValue = String(value);
} else if (typeof value === "boolean") {
cleanValue = String(value);
} else if (value as CustomFieldValue) {
const customFieldValue = value as CustomFieldValue[];
cleanValue =
customFieldValue[0].displayValue ?? customFieldValue[0].value?.toString();
}
if (readOnly) {
return (
<Input
includeLabel={includeLabel}
type={type}
name={name}
label={label}
value={cleanValue}
error={errors[name]}
readOnly
/>
);
} else {
return (
<Input
includeLabel={includeLabel}
type={type}
name={name}
label={label}
value={cleanValue}
error={errors[name]}
onChange={onChange}
/>
);
}
};
export const renderCustomFieldNumber = (
includeLabel: boolean,
name: string,
label: string,
data: FormData,
errors: FormError,
readOnly = false,
defaultValue: string = "",
min?: number,
max?: number,
step: number = 1,
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void,
) => {
let values: CustomFieldValue[] = data[name] as CustomFieldValue[];
let value: CustomFieldValue | undefined = undefined;
if (values) {
if (values.length > 0) {
value = values[0];
}
}
let cleanValue: string | undefined;
if (value === undefined) {
cleanValue = defaultValue;
} else if (typeof value === "string") {
cleanValue = value as string;
} else if (typeof value === "number") {
cleanValue = String(value);
} else if (value as CustomFieldValue) {
cleanValue =
(value as CustomFieldValue).displayValue ??
(value as CustomFieldValue).value?.toString();
}
if (readOnly) {
return (
<Input
includeLabel={includeLabel}
type={InputType.number}
name={name}
label={label}
value={cleanValue}
error={errors[name]}
readOnly
/>
);
} else {
return (
<Input
includeLabel={includeLabel}
type={InputType.number}
name={name}
label={label}
value={cleanValue}
error={errors[name]}
min={min}
max={max}
step={step}
onChange={onChange}
/>
);
}
};
export const renderTemplateEditor = (
className: string,
name: string,
label: string,
data: FormData,
allowCustomFields: boolean,
onChange?: (name: string, value: string) => void,
) => {
let value = data[name] as string;
return (
<div>
<label htmlFor={name}>{label}</label>
<TemplateEditor
className={className}
name={name}
data={value}
onChange={onChange ?? (() => {})}
showFields={allowCustomFields}
/>
</div>
);
};
export const renderSelect = (
name: string,
label: string,
data: FormData,
errors: FormError,
options: Option[],
onChange?: (e: React.ChangeEvent<HTMLSelectElement>) => void,
) => {
return (
<Select
name={name}
label={label}
value={data[name]}
options={options}
error={errors[name]}
onChange={onChange}
/>
);
};
export const renderToggle = (
name: string,
label: string,
data: FormData,
errors: FormError,
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void,
) => {
return (
<ToggleSlider
name={name}
label={label}
defaultChecked={Boolean(data[name])}
error={errors[name]}
onChange={onChange}
/>
);
};
export const renderSequencePicker = (
includeLabel: boolean,
name: string,
label: string,
data: FormData,
errors: FormError,
onChange?: (name: string, value: GeneralIdRef) => void,
) => {
return (
<SequencePicker
includeLabel={includeLabel}
name={name}
label={label}
value={data[name]}
error={errors[name]}
onChange={onChange}
/>
);
};
export const renderGlossaryPicker = (
includeLabel: boolean,
name: string,
label: string,
data: FormData,
errors: FormError,
maxEntries?: number,
refElementId?: GeneralIdRef,
onChange?: (name: string, values: CustomFieldValue[]) => void,
) => {
const glossaryValues: CustomFieldValue[] | undefined = data[
name
] as any as CustomFieldValue[];
return (
<GlossaryPicker
includeLabel={includeLabel}
name={name}
label={label}
maxEntries={maxEntries}
values={glossaryValues}
error={errors[name]}
rootItem={refElementId}
onChange={onChange}
/>
);
};
export const renderDomainPicker = (
includeLabel: boolean,
name: string,
label: string,
data: FormData,
errors: FormError,
minEntries: number,
maxEntries?: number,
onChange?: (name: string, values: CustomFieldValue[]) => void,
) => {
const domainValues: CustomFieldValue[] | undefined = data[
name
] as any as CustomFieldValue[];
return (
<DomainPicker
includeLabel={includeLabel}
name={name}
label={label}
minEntries={minEntries}
maxEntries={maxEntries}
values={domainValues}
error={errors[name]}
onChange={onChange}
/>
);
};
export const renderTemplatePicker = (
includeLabel: boolean,
name: string,
label: string,
data: FormData,
errors: FormError,
onChange?: (name: string, value: GeneralIdRef) => void,
) => {
const templateValue: GeneralIdRef = data[name] as any as GeneralIdRef;
return (
<FormTemplatePicker
includeLabel={includeLabel}
name={name}
label={label}
value={templateValue}
error={errors[name]}
onChange={onChange}
/>
);
};
export const renderUserPicker = (
name: string,
label: string,
data: FormData,
errors: FormError,
onChange?: (name: string, value: GeneralIdRef) => void,
) => {
const glossaryValue: GeneralIdRef | undefined = data[
name
] as any as GeneralIdRef;
return (
<UserPicker
name={name}
label={label}
value={glossaryValue}
error={errors[name]}
onChange={onChange}
/>
);
};
export const renderSsoProviderPicker = (
name: string,
label: string,
data: FormData,
errors: FormError,
onChange?: (name: string, value: GeneralIdRef) => void,
) => {
const glossaryValue: GeneralIdRef | undefined = data[
name
] as any as GeneralIdRef;
return (
<SsoProviderPicker
name={name}
label={label}
value={glossaryValue}
error={errors[name]}
onChange={onChange}
/>
);
};
export const renderCustomFieldsEditor = (
name: string,
label: string,
data: FormData,
errors: FormError,
selected: CustomField[],
onAdd: CustomFieldEditorAdd,
onDelete: CustomFieldEditorDelete,
) => {
return (
<CustomFieldsEditor
name={name}
label={label}
value={data[name] as []}
error={errors[name]}
exclude={selected}
onAdd={onAdd}
onDelete={onDelete}
/>
);
};
export const renderCustomField = (
customField: CustomField,
includeLabel: boolean,
data: FormData,
errors: FormError,
onChange: (
e:
| React.ChangeEvent<HTMLInputElement>
| React.ChangeEvent<HTMLTextAreaElement>,
) => void,
onPickerChange: (
name: string,
value: GeneralIdRef | CustomFieldValue[],
) => void,
getCustomFieldType: (
field: CustomFieldValue,
customFields: CustomField[],
) => string,
) => {
switch (customField.fieldType.toLowerCase()) {
case "text":
const textParameters: textParams = JSON.parse(customField.parameters!);
if (textParameters.multiLine) {
return renderInputTextarea(
includeLabel,
"customfield_" + customField.id,
customField.name,
data,
errors,
false,
customField.defaultValue,
onChange as (e: React.ChangeEvent<HTMLTextAreaElement>) => void,
);
} else {
return renderCustomFieldInput(
includeLabel,
"customfield_" + customField.id,
customField.name,
data,
errors,
InputType.text,
false,
customField.defaultValue,
onChange as (e: React.ChangeEvent<HTMLInputElement>) => void,
);
}
case "sequence":
return renderCustomFieldInput(
includeLabel,
"customfield_" + customField.id,
customField.name,
data,
errors,
InputType.text,
true,
);
case "formtemplate":
return renderTemplatePicker(
includeLabel,
"customfield_" + customField.id,
customField.name,
data,
errors,
(name, value) => onPickerChange(name, value),
);
case "glossary":
return renderGlossaryPicker(
includeLabel,
"customfield_" + customField.id,
customField.name,
data,
errors,
customField.maxEntries,
customField.refElementId,
(name, values) => onPickerChange(name, values),
);
case "number":
const numberParameters: numberParams = JSON.parse(
customField.parameters!,
);
return renderCustomFieldNumber(
includeLabel,
"customfield_" + customField.id,
customField.name,
data,
errors,
false,
customField.defaultValue,
numberParameters.minValue
? Number(numberParameters.minValue)
: undefined,
numberParameters.maxValue
? Number(numberParameters.maxValue)
: undefined,
numberParameters.step ? Number(numberParameters.step) : undefined,
onChange as (e: React.ChangeEvent<HTMLInputElement>) => void,
);
case "domain":
return renderDomainPicker(
includeLabel,
"customfield_" + customField.id,
customField.name,
data,
errors,
customField.minEntries,
customField.maxEntries,
(name, values) => onPickerChange(name, values),
);
default:
return <>{customField.name + " " + customField.fieldType}</>;
}
};
export const renderCustomFields = (
customFields: CustomField[] | undefined,
data: FormData,
errors: FormError,
onChange: (
e:
| React.ChangeEvent<HTMLInputElement>
| React.ChangeEvent<HTMLTextAreaElement>,
) => void,
onPickerChange: (
name: string,
value: GeneralIdRef | CustomFieldValue[],
) => void,
getCustomFieldType: (
field: CustomFieldValue,
customFields: CustomField[],
) => string,
) => {
if (customFields === undefined) return <></>;
let customFieldsBlock: JSX.Element[] = [];
for (const customField of customFields) {
customFieldsBlock.push(
renderCustomField(
customField,
true,
data,
errors,
onChange,
onPickerChange,
getCustomFieldType,
),
);
}
return <>{customFieldsBlock.map((x) => x)}</>;
};
export const renderDropSection = (
name: string,
title: JSX.Element,
content: JSX.Element,
errors: FormError,
) => {
return (
<Expando name={name} title={title} error={errors[name]}>
{content}
</Expando>
);
};

View File

@ -0,0 +1,588 @@
import { useState, useCallback, useRef } from "react";
import Joi from "joi";
import { GeneralIdRef } from "../../utils/GeneralIdRef";
import {
CustomField,
numberParams,
textParams,
} from "../../modules/manager/customfields/services/customFieldsService";
import {
CustomFieldValue,
CustomFieldValues,
Glossary,
} from "../../modules/manager/glossary/services/glossaryService";
import { InputType } from "./Input";
export interface FormError {
[key: string]: string;
}
export interface FormData {
[key: string]:
| string
| number
| boolean
| CustomFieldValue[]
| GeneralIdRef
| CustomField[]
| bigint
| Glossary
| null
| undefined;
}
export interface businessValidationError {
path: string;
message: string;
}
export interface businessValidationResult {
details: businessValidationError[];
}
export interface joiSchema {
[key: string]: object;
}
export interface FormState {
loaded: boolean;
data: FormData;
customFields?: CustomField[];
errors: FormError;
redirect?: string;
}
interface UseFormReturn {
state: FormState;
schema: joiSchema;
validate: (data: FormData) => FormError;
GetCustomFieldValues: (customField: CustomField) => CustomFieldValue[];
CustomFieldValues: () => CustomFieldValues[];
setCustomFieldValues: (
data: object,
customFieldValues: CustomFieldValues[],
customFields: CustomField[],
) => void;
getCustomFieldType: (
field: CustomFieldValues,
childCustomFieldDefinition: CustomField[],
) => string;
handleSubmit: (
e: React.FormEvent<HTMLFormElement>,
doSubmit: (buttonName: string) => Promise<void>,
) => void;
handleGeneralError: (ex: any) => void;
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
handleTextAreaChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
handleCustomFieldChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
handleTemplateEditorChange: (name: string, value: string) => void;
handleSelectChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
handlePickerChange: (name: string, value: GeneralIdRef) => void;
handleDomainPickerChange: (name: string, values: CustomFieldValue[]) => void;
handleGlossaryPickerChange: (
name: string,
values: CustomFieldValue[],
) => void;
handleTemplateFormPickerChange: (name: string, value: GeneralIdRef) => void;
handleUserPickerChange: (name: string, value: GeneralIdRef) => void;
handleSsoProviderPickerChange: (name: string, value: GeneralIdRef) => void;
handleToggleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
setState: (updates: Partial<FormState>) => void;
}
export const useForm = (initialState: FormState): UseFormReturn => {
const [state, setStateInternal] = useState<FormState>(initialState);
const schemaRef = useRef<joiSchema>({});
const setState = useCallback((updates: Partial<FormState>) => {
setStateInternal((prev) => ({ ...prev, ...updates }));
}, []);
const validate = useCallback(
(data: FormData): FormError => {
let options: Joi.ValidationOptions = {
context: {},
abortEarly: false,
};
const customFields = state.customFields;
let validationSchema = schemaRef.current;
if (customFields !== undefined) {
for (const customfield of customFields) {
const name = "customfield_" + customfield.id;
switch (customfield.fieldType) {
case "Number":
if (customfield.parameters !== undefined) {
const parameters: numberParams = JSON.parse(
customfield.parameters!,
);
options.context![name + "_minEntries"] = customfield.minEntries;
if (parameters.minValue)
options.context![name + "_minValue"] = Number(
parameters.minValue,
);
if (parameters.maxValue)
options.context![name + "_maxValue"] = Number(
parameters.maxValue,
);
let minCheck = options.context![name + "_minValue"]
? Joi.number()
.empty("")
.min(options.context![name + "_minValue"])
: Joi.number().empty("");
let maxCheck = options.context![name + "_maxValue"]
? Joi.number()
.empty("")
.max(options.context![name + "_maxValue"])
: Joi.number().empty("");
validationSchema[name] = Joi.array()
.min(1)
.items(
Joi.object({
displayValue: Joi.string().allow(""),
value: Joi.when("$" + name + "_minEntries", {
is: 0,
then: Joi.number().empty(""),
otherwise: Joi.number().required(),
})
.when("$" + name + "_minValue", {
is: Joi.number(),
then: minCheck,
})
.when("$" + name + "_maxValue", {
is: Joi.number(),
then: maxCheck,
})
.label(customfield.name),
}),
);
} else {
validationSchema[name] = Joi.optional().label(customfield.name);
}
break;
default:
validationSchema[name] = Joi.optional().label(customfield.name);
}
}
}
const joiSchema = Joi.object(validationSchema);
const { error } = joiSchema.validate(data, options);
let errors: FormError = {};
if (error) {
if (error.details === undefined) {
errors[error.name] = error.message;
} else {
for (let item of error.details) {
errors[item.path[0]] = item.message;
}
}
}
return errors;
},
[state.customFields],
);
const GetCustomFieldValues = useCallback(
(customField: CustomField): CustomFieldValue[] => {
const name = "customfield_" + customField.id;
const codedValue = state.data[name];
let values: CustomFieldValue[] = [];
switch (customField.fieldType) {
case "FormTemplate":
if (codedValue !== undefined) {
const formTemplateValue = {
value: JSON.stringify(codedValue as GeneralIdRef),
};
values.push(formTemplateValue);
}
break;
case "Sequence":
if (codedValue !== undefined) {
values = codedValue as CustomFieldValue[];
}
break;
case "Glossary":
if (codedValue !== undefined) {
values = codedValue as CustomFieldValue[];
}
break;
case "Domain":
if (codedValue !== undefined) {
values = codedValue as CustomFieldValue[];
}
break;
case "Text":
const textParameters: textParams = JSON.parse(
customField.parameters!,
);
if (textParameters.multiLine) {
const textValue = {
value:
codedValue === undefined
? customField.defaultValue
: codedValue,
displayValue:
codedValue === undefined
? customField.defaultValue
: codedValue,
} as CustomFieldValue;
values.push(textValue);
} else {
if (codedValue === undefined) {
const numberValue = {
value: customField.defaultValue,
displayValue: customField.defaultValue,
} as CustomFieldValue;
values.push(numberValue);
} else {
values = codedValue as CustomFieldValue[];
}
}
break;
case "Number":
if (codedValue === undefined) {
const numberValue = {
value: customField.defaultValue,
displayValue: customField.defaultValue,
} as CustomFieldValue;
values.push(numberValue);
} else {
values = codedValue as CustomFieldValue[];
}
break;
default:
const textValue = {
value:
codedValue === undefined
? customField.defaultValue
: String((codedValue as CustomFieldValue[])[0].displayValue),
};
values.push(textValue);
break;
}
return values;
},
[state.data],
);
const CustomFieldValues = useCallback((): CustomFieldValues[] => {
const customFields = state.customFields;
let result: CustomFieldValues[] = [];
if (customFields === undefined) {
return result;
}
for (const customfield of customFields) {
const values = GetCustomFieldValues(customfield);
const id: GeneralIdRef = {
id: customfield.id,
guid: customfield.guid,
};
const newItem: CustomFieldValues = {
id,
values,
};
result.push(newItem);
}
return result;
}, [state.customFields, GetCustomFieldValues]);
const getCustomFieldType = useCallback(
(
field: CustomFieldValues,
childCustomFieldDefinition: CustomField[],
): string => {
const fieldDefinition = childCustomFieldDefinition.filter(
(x) => x.id === field.id.id,
)[0];
if (fieldDefinition.parameters) {
const textParameters: textParams = JSON.parse(
fieldDefinition.parameters!,
);
if (textParameters.multiLine) return "multilinetext";
}
return fieldDefinition.fieldType;
},
[],
);
const setCustomFieldValues = useCallback(
(
data: object,
customFieldValues: CustomFieldValues[],
customFields: CustomField[],
) => {
if (customFieldValues !== undefined) {
for (const x of customFieldValues) {
const customfieldName = "customfield_" + x.id.id;
switch (getCustomFieldType(x, customFields).toLowerCase()) {
case "glossary":
case "domain":
case "number":
case "text":
(data as any)[customfieldName] = x.values.map((x) => {
return {
displayValue: x.displayValue,
value: x.value,
};
});
break;
case "formtemplate":
case "multilinetext":
(data as any)[customfieldName] = x.values[0].value;
break;
default:
(data as any)[customfieldName] = x.values;
break;
}
}
}
},
[getCustomFieldType],
);
const handleSubmit = useCallback(
(
e: React.FormEvent<HTMLFormElement>,
doSubmit: (buttonName: string) => Promise<void>,
) => {
e.preventDefault();
const submitEvent = e.nativeEvent as SubmitEvent;
const submitter = submitEvent.submitter as any;
const errors = validate(state.data);
setState({ errors });
const disabled = Object.keys(errors).length > 0;
if (disabled) return;
void doSubmit(submitter.name);
},
[state.data, validate, setState],
);
const handleGeneralError = useCallback(
(ex: any) => {
const errors: FormError = { ...state.errors };
if (ex.response) {
errors._general = ex.response.data.detail;
} else {
errors._general = ex.message;
}
setState({ errors });
},
[state.errors, setState],
);
const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const input = e.currentTarget;
const data: FormData = { ...state.data };
if ((input as any).type === InputType.checkbox) {
data[input.name] = !data[input.name];
} else data[input.name] = input.value;
const errors = validate(data);
setState({ data, errors });
},
[state.data, validate, setState],
);
const handleTextAreaChange = useCallback(
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
const input = e.currentTarget;
const data: FormData = { ...state.data };
data[input.name] = input.value;
const errors = validate(data);
setState({ data, errors });
},
[state.data, validate, setState],
);
const handleCustomFieldChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const input = e.currentTarget;
const data: FormData = { ...state.data };
switch ((input as any).type) {
case InputType.checkbox:
data[input.name] = !data[input.name];
break;
default:
const customFieldValue: CustomFieldValue = {
displayValue: input.value,
value: input.value,
};
data[input.name] = [customFieldValue];
break;
}
const errors = validate(data);
setState({ data, errors });
},
[state.data, validate, setState],
);
const handleTemplateEditorChange = useCallback(
(name: string, value: string) => {
const data: FormData = { ...state.data };
data[name] = value;
const errors = validate(data);
setState({ data, errors });
},
[state.data, validate, setState],
);
const handleSelectChange = useCallback(
(e: React.ChangeEvent<HTMLSelectElement>) => {
const input = e.currentTarget;
const data: FormData = { ...state.data };
data[input.name] = input.value;
const errors = validate(data);
setState({ data, errors });
},
[state.data, validate, setState],
);
const handlePickerChange = useCallback(
(name: string, value: GeneralIdRef) => {
const data: FormData = { ...state.data };
data[name] = value;
const errors = validate(data);
setState({ data, errors });
},
[state.data, validate, setState],
);
const handleDomainPickerChange = useCallback(
(name: string, values: CustomFieldValue[]) => {
const data: FormData = { ...state.data };
data[name] = values;
const errors = validate(data);
setState({ data, errors });
},
[state.data, validate, setState],
);
const handleGlossaryPickerChange = useCallback(
(name: string, values: CustomFieldValue[]) => {
const data: FormData = { ...state.data };
data[name] = values;
const errors = validate(data);
setState({ data, errors });
},
[state.data, validate, setState],
);
const handleTemplateFormPickerChange = useCallback(
(name: string, value: GeneralIdRef) => {
const data: FormData = { ...state.data };
data[name] = value;
const errors = validate(data);
setState({ data, errors });
},
[state.data, validate, setState],
);
const handleUserPickerChange = useCallback(
(name: string, value: GeneralIdRef) => {
const data: FormData = { ...state.data };
data[name] = value;
const errors = validate(data);
setState({ data, errors });
},
[state.data, validate, setState],
);
const handleSsoProviderPickerChange = useCallback(
(name: string, value: GeneralIdRef) => {
const data: FormData = { ...state.data };
data[name] = value;
const errors = validate(data);
setState({ data, errors });
},
[state.data, validate, setState],
);
const handleToggleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const input = e.currentTarget;
const { name, checked } = input;
const data: FormData = { ...state.data };
data[name] = checked;
const errors = validate(data);
setState({ data, errors });
},
[state.data, validate, setState],
);
const api: any = {
state,
schema: schemaRef.current,
validate,
GetCustomFieldValues,
CustomFieldValues,
setCustomFieldValues,
getCustomFieldType,
handleSubmit,
handleGeneralError,
handleChange,
handleTextAreaChange,
handleCustomFieldChange,
handleTemplateEditorChange,
handleSelectChange,
handlePickerChange,
handleDomainPickerChange,
handleGlossaryPickerChange,
handleTemplateFormPickerChange,
handleUserPickerChange,
handleSsoProviderPickerChange,
handleToggleChange,
setState,
};
Object.defineProperty(api, "schema", {
get: () => schemaRef.current,
set: (value: joiSchema) => {
schemaRef.current = value || {};
},
});
return api as UseFormReturn;
};

View File

@ -1,11 +1,19 @@
import Joi from "joi";
import React from "react";
import { Navigate } from "react-router-dom";
import React, { useEffect } from "react";
import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify";
import Form from "../../../components/common/Form";
import { InputType } from "../../../components/common/Input";
import { FormState } from "../../../components/common/Form";
import withRouter from "../../../utils/withRouter";
import { useForm } from "../../../components/common/useForm";
import {
renderInput,
renderButton,
renderError,
renderSelect,
renderInputNumber,
renderInputTextarea,
renderGlossaryPicker,
renderSequencePicker,
} from "../../../components/common/formHelpers";
import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
import customFieldsService, {
numberParams,
@ -19,25 +27,31 @@ import {
} from "../glossary/services/glossaryService";
import Loading from "../../../components/common/Loading";
interface CustomFieldDetailsState extends FormState {
data: {
name: string;
fieldType: string;
multiLine: boolean;
defaultValue: string;
minEntries: number;
maxEntries: string | number | undefined;
refElementId: CustomFieldValue[] | GeneralIdRef | undefined;
minValue: number | undefined;
maxValue: number | undefined;
step: number | undefined;
required: boolean;
};
redirect: string;
interface CustomFieldDetailsProps {
editMode?: boolean;
}
class CustomFieldDetails extends Form<any, any, CustomFieldDetailsState> {
state: CustomFieldDetailsState = {
const CustomFieldDetails: React.FC<CustomFieldDetailsProps> = ({
editMode = false,
}) => {
const { customFieldId } = useParams<{ customFieldId: string }>();
const labelName = "Name";
const labelFieldType = "Field Type";
const labelMultiLine = "Multi-line";
const labelDefaultValue = "Default Value";
const labelMinValue = "Minimum Value";
const labelMaxValue = "Maximum Value";
const labelStep = "Step";
const labelRequired = "Required";
const labelMinEntries = "Min Entries";
const labelMaxEntries = "Max Entries (empty=unlimited)";
let labelRefElementId = "Sequence/Form/Glossary";
const labelApply = "Save";
const labelSave = "Save and close";
const form = useForm({
loaded: false,
data: {
name: "",
@ -54,29 +68,14 @@ class CustomFieldDetails extends Form<any, any, CustomFieldDetailsState> {
},
errors: {},
redirect: "",
};
});
labelName = "Name";
labelFieldType = "Field Type";
labelMultiLine = "Multi-line";
labelDefaultValue = "Default Value";
labelMinValue = "Minimum Value";
labelMaxValue = "Maximum Value";
labelStep = "Step";
labelRequired = "Required";
labelMinEntries = "Min Entries";
labelMaxEntries = "Max Entries (empty=unlimited)";
labelRefElementId = "Sequence/Form/Glossary";
labelApply = "Save";
labelSave = "Save and close";
schema = {
name: Joi.string().required().max(450).label(this.labelName),
fieldType: Joi.string().required().label(this.labelFieldType),
multiLine: Joi.boolean().label(this.labelMultiLine),
minEntries: Joi.number().min(0).label(this.labelMinEntries),
maxEntries: Joi.number().empty("").label(this.labelMaxEntries),
form.schema = {
name: Joi.string().required().max(450).label(labelName),
fieldType: Joi.string().required().label(labelFieldType),
multiLine: Joi.boolean().label(labelMultiLine),
minEntries: Joi.number().min(0).label(labelMinEntries),
maxEntries: Joi.number().empty("").label(labelMaxEntries),
refElementId: Joi.when("fieldType", {
is: Joi.string().valid("Sequence"),
then: Joi.object({
@ -98,12 +97,12 @@ class CustomFieldDetails extends Form<any, any, CustomFieldDetailsState> {
)
.required(),
}),
minValue: Joi.number().allow("").label(this.labelMinValue),
maxValue: Joi.number().allow("").label(this.labelMaxValue),
step: Joi.number().optional().allow("").min(0).label(this.labelStep),
required: Joi.boolean().label(this.labelRequired),
minValue: Joi.number().allow("").label(labelMinValue),
maxValue: Joi.number().allow("").label(labelMaxValue),
step: Joi.number().optional().allow("").min(0).label(labelStep),
required: Joi.boolean().label(labelRequired),
//defaultValue: Joi.string().allow("").label(this.labelDefaultValue)
//defaultValue: Joi.string().allow("").label(labelDefaultValue)
defaultValue: Joi.when("fieldType", {
is: Joi.string().valid("Number"),
@ -114,7 +113,7 @@ class CustomFieldDetails extends Form<any, any, CustomFieldDetailsState> {
.min(Joi.ref("minValue"))
.message(
'"Default Value" must be greater than or equal to "' +
this.labelMinValue +
labelMinValue +
'"',
),
})
@ -125,26 +124,99 @@ class CustomFieldDetails extends Form<any, any, CustomFieldDetailsState> {
.max(Joi.ref("maxValue"))
.message(
'"Default Value" must be less than or equal to "' +
this.labelMaxValue +
labelMaxValue +
'"',
),
})
.allow(""),
otherwise: Joi.string().allow(""),
}).label(this.labelDefaultValue),
}).label(labelDefaultValue),
};
doSubmit = async (buttonName: string) => {
const isEditMode = () => {
return editMode;
};
useEffect(() => {
const loadData = async () => {
if (customFieldId !== undefined) {
try {
const loadedData = await customFieldsService.getField(
BigInt(customFieldId),
);
if (loadedData) {
const newData = { ...form.state.data };
newData.name = loadedData.name;
newData.fieldType = loadedData.fieldType;
newData.defaultValue = loadedData.defaultValue;
newData.minEntries = loadedData.minEntries;
newData.maxEntries = loadedData.maxEntries;
switch (newData.fieldType) {
case "Glossary":
let convertedRefElementId: CustomFieldValue = {
value: loadedData.refElementId,
};
newData.refElementId = [convertedRefElementId];
newData.required = loadedData.minEntries > 0;
break;
case "Sequence":
newData.refElementId = loadedData.refElementId;
break;
case "Domain":
newData.required = loadedData.minEntries > 0;
break;
}
if (loadedData.parameters !== undefined) {
switch (newData.fieldType) {
case "Number":
newData.required = loadedData.minEntries > 0;
const parameters: numberParams = JSON.parse(
loadedData.parameters,
);
newData.minValue = parameters.minValue ?? undefined;
newData.maxValue = parameters.maxValue ?? undefined;
newData.step = parameters.step ?? undefined;
break;
case "Text":
const textParameters: textParams = JSON.parse(
loadedData.parameters,
);
newData.multiLine = textParameters.multiLine ?? false;
break;
}
}
form.setState({ loaded: true, data: newData });
} else {
form.setState({ loaded: false });
}
} catch (ex: any) {
form.handleGeneralError(ex);
}
}
if (!editMode) form.setState({ loaded: true });
};
loadData();
}, [customFieldId, editMode]); // eslint-disable-line react-hooks/exhaustive-deps
const doSubmit = async (buttonName: string) => {
try {
const { name, fieldType } = this.state.data;
const { name, fieldType } = form.state.data;
const nameStr = typeof name === "string" ? name : "";
const fieldTypeStr = typeof fieldType === "string" ? fieldType : "";
let { refElementId, defaultValue, minEntries, maxEntries, required } =
this.state.data;
form.state.data;
let numberParams: numberParams | undefined = undefined;
let textParams: textParams | undefined = undefined;
let params;
let refElementIdValue: GeneralIdRef | undefined;
switch (fieldType) {
switch (fieldTypeStr) {
case "Sequence":
minEntries = 1;
maxEntries = 1;
@ -171,15 +243,31 @@ class CustomFieldDetails extends Form<any, any, CustomFieldDetailsState> {
case "Text":
minEntries = 1;
maxEntries = 1;
let { multiLine } = this.state.data;
textParams = { multiLine };
let { multiLine } = form.state.data;
textParams = { multiLine: Boolean(multiLine) };
params = textParams;
refElementIdValue = undefined;
break;
case "Number":
refElementIdValue = undefined;
let { minValue, maxValue, step } = this.state.data;
numberParams = { minValue, maxValue, step };
let { minValue, maxValue, step } = form.state.data;
const minValueNum =
minValue === null || minValue === undefined || minValue === ""
? undefined
: Number(minValue);
const maxValueNum =
maxValue === null || maxValue === undefined || maxValue === ""
? undefined
: Number(maxValue);
const stepNum =
step === null || step === undefined || step === ""
? undefined
: Number(step);
numberParams = {
minValue: minValueNum,
maxValue: maxValueNum,
step: stepNum,
};
params = numberParams;
minEntries = required ? 1 : 0;
maxEntries = 1;
@ -188,19 +276,30 @@ class CustomFieldDetails extends Form<any, any, CustomFieldDetailsState> {
refElementIdValue = undefined;
}
const minEntriesValue =
typeof minEntries === "number"
? minEntries
: minEntries === undefined || minEntries === null || minEntries === ""
? 0
: Number(minEntries);
const cleanMaxEntries: Number | undefined =
maxEntries === "" ? undefined : Number(maxEntries);
if (this.isEditMode()) {
const { customFieldId } = this.props.router.params;
const defaultValueStr =
typeof defaultValue === "string"
? defaultValue
: defaultValue === undefined || defaultValue === null
? ""
: String(defaultValue);
var generalIdRef = MakeGeneralIdRef(customFieldId);
if (isEditMode()) {
var generalIdRef = MakeGeneralIdRef(BigInt(customFieldId!));
const response = await customFieldsService.putField(
generalIdRef,
name,
fieldType,
defaultValue,
minEntries,
nameStr,
fieldTypeStr,
defaultValueStr,
minEntriesValue,
cleanMaxEntries,
refElementIdValue,
params,
@ -210,10 +309,10 @@ class CustomFieldDetails extends Form<any, any, CustomFieldDetailsState> {
}
} else {
const response = await customFieldsService.postField(
name,
fieldType,
defaultValue,
minEntries,
nameStr,
fieldTypeStr,
defaultValueStr,
minEntriesValue,
cleanMaxEntries,
refElementIdValue,
params,
@ -223,262 +322,324 @@ class CustomFieldDetails extends Form<any, any, CustomFieldDetailsState> {
}
}
if (buttonName === this.labelSave)
this.setState({ redirect: "/customfields" });
if (buttonName === "save") form.setState({ redirect: "/customfields" });
} catch (ex: any) {
this.handleGeneralError(ex);
form.handleGeneralError(ex);
}
};
isEditMode = () => {
const { editMode } = this.props;
return editMode;
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
form.handleSubmit(e, doSubmit);
};
componentDidMount = async () => {
const { customFieldId } = this.props.router.params;
if (form.state.redirect) return <Navigate to={form.state.redirect} />;
if (customFieldId !== undefined) {
try {
const loadedData = await customFieldsService.getField(customFieldId);
const { fieldType } = form.state.data;
const fieldTypeValue = typeof fieldType === "string" ? fieldType : "";
const { data } = this.state;
if (loadedData) {
data.name = loadedData.name;
data.fieldType = loadedData.fieldType;
data.defaultValue = loadedData.defaultValue;
data.minEntries = loadedData.minEntries;
data.maxEntries = loadedData.maxEntries;
switch (data.fieldType) {
case "Glossary":
let convertedRefElementId: CustomFieldValue = {
value: loadedData.refElementId,
};
let mode = "Add";
if (isEditMode()) mode = "Edit";
data.refElementId = [convertedRefElementId];
data.required = loadedData.minEntries > 0;
break;
case "Sequence":
data.refElementId = loadedData.refElementId;
break;
case "Domain":
data.required = loadedData.minEntries > 0;
break;
}
const fieldTypeOptions: Option[] = [
{ _id: "Text", name: "Text" },
{ _id: "Number", name: "Number" },
{ _id: "Sequence", name: "Sequence" },
{ _id: "FormTemplate", name: "Form Template" },
{ _id: "Glossary", name: "Glossary" },
{ _id: "Domain", name: "Domain" },
];
if (loadedData.parameters !== undefined) {
switch (data.fieldType) {
case "Number":
data.required = loadedData.minEntries > 0;
const parameters: numberParams = JSON.parse(
loadedData.parameters,
);
data.minValue = parameters.minValue ?? undefined;
data.maxValue = parameters.maxValue ?? undefined;
data.step = parameters.step ?? undefined;
break;
case "Text":
const textParameters: textParams = JSON.parse(
loadedData.parameters,
);
data.multiLine = textParameters.multiLine ?? false;
break;
}
}
this.setState({ loaded: true, data });
} else {
this.setState({ loaded: false });
}
} catch (ex: any) {
this.handleGeneralError(ex);
}
}
if (!this.isEditMode()) this.setState({ loaded: true });
};
render() {
const { loaded, redirect } = this.state;
if (redirect !== "") return <Navigate to={redirect} />;
const { fieldType, minValue, maxValue, step } = this.state.data;
let mode = "Add";
if (this.isEditMode()) mode = "Edit";
const fieldTypeOptions: Option[] = [
{ _id: "Text", name: "Text" },
{ _id: "Number", name: "Number" },
// { _id: "Boolean", name: "Boolean" },
// { _id: "Date", name: "Date" },
// { _id: "Time", name: "Time" },
// { _id: "DateTime", name: "DateTime" },
{ _id: "Sequence", name: "Sequence" },
{ _id: "FormTemplate", name: "Form Template" },
{ _id: "Glossary", name: "Glossary" },
{ _id: "Domain", name: "Domain" },
];
switch (fieldType) {
case "Sequence":
this.labelRefElementId = "Sequence";
break;
case "FormTemplate":
this.labelRefElementId = "Form";
break;
case "Glossary":
this.labelRefElementId = "Glossary";
break;
}
return (
<Loading loaded={loaded}>
<h1>{mode} Custom Field</h1>
<form onSubmit={this.handleSubmit}>
{this.renderError("_general")}
{this.renderInput("name", this.labelName, InputType.text)}
{this.renderSelect(
"fieldType",
this.labelFieldType,
fieldTypeOptions,
)}
{this.state.data.fieldType === "Domain" && (
<>
{this.renderInput(
"required",
this.labelRequired,
InputType.checkbox,
)}
{this.renderInput(
"maxEntries",
this.labelMaxEntries,
InputType.number,
)}
</>
)}
{this.state.data.fieldType === "Glossary" && (
<>
{this.renderGlossaryPicker(
true,
"refElementId",
this.labelRefElementId,
1,
SystemGlossaries,
)}
{this.renderInput(
"required",
this.labelRequired,
InputType.checkbox,
)}
{this.renderInput(
"maxEntries",
this.labelMaxEntries,
InputType.number,
)}
</>
)}
{this.state.data.fieldType === "Sequence" && (
<>
{this.renderSequencePicker(
true,
"refElementId",
this.labelRefElementId,
)}
</>
)}
{this.state.data.fieldType === "Text" && (
<>
{this.renderInput(
"multiLine",
this.labelMultiLine,
InputType.checkbox,
)}
{this.state.data.multiLine === true &&
this.renderInputTextarea(
true,
"defaultValue",
this.labelDefaultValue,
)}
{this.state.data.multiLine === false &&
this.renderInput(
"defaultValue",
this.labelDefaultValue,
InputType.text,
)}
</>
)}
{this.state.data.fieldType === "Number" && (
<>
{this.renderInput(
"required",
this.labelRequired,
InputType.checkbox,
)}
{this.renderInputNumber(
"minValue",
this.labelMinValue,
false,
undefined,
undefined,
maxValue,
undefined,
)}
{this.renderInputNumber(
"maxValue",
this.labelMaxValue,
false,
undefined,
minValue,
undefined,
undefined,
)}
{this.renderInput("step", this.labelStep, InputType.number)}
{this.renderInputNumber(
"defaultValue",
this.labelDefaultValue,
false,
undefined,
minValue,
maxValue,
step,
)}
</>
)}
{![
"Domain",
"Glossary",
"Sequence",
"FormTemplate",
"Text",
"Number",
].includes(this.state.data.fieldType) && (
<>
{this.renderInput(
"defaultValue",
this.labelDefaultValue,
InputType.text,
)}
{this.renderInput(
"minEntries",
this.labelMinEntries,
InputType.number,
)}
{this.renderInput(
"maxEntries",
this.labelMaxEntries,
InputType.number,
)}
</>
)}
{this.isEditMode() && this.renderButton(this.labelApply)}
{this.renderButton(this.labelSave)}
</form>
</Loading>
);
switch (fieldTypeValue) {
case "Sequence":
labelRefElementId = "Sequence";
break;
case "FormTemplate":
labelRefElementId = "Form";
break;
case "Glossary":
labelRefElementId = "Glossary";
break;
}
}
const HOCCustomFieldDetails = withRouter(CustomFieldDetails);
return (
<Loading loaded={form.state.loaded}>
<h1>{mode} Custom Field</h1>
<form onSubmit={handleSubmit}>
{renderError("_general", form.state.errors)}
{renderInput(
"name",
labelName,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderSelect(
"fieldType",
labelFieldType,
form.state.data,
form.state.errors,
fieldTypeOptions,
form.handleSelectChange,
)}
{fieldTypeValue === "Domain" && (
<>
{renderInput(
"required",
labelRequired,
form.state.data,
form.state.errors,
InputType.checkbox,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInputNumber(
"maxEntries",
labelMaxEntries,
form.state.data,
form.state.errors,
false,
"",
undefined,
undefined,
1,
form.handleChange,
)}
</>
)}
{fieldTypeValue === "Glossary" && (
<>
{renderGlossaryPicker(
true,
"refElementId",
labelRefElementId,
form.state.data,
form.state.errors,
1,
SystemGlossaries,
form.handleGlossaryPickerChange,
)}
{renderInput(
"required",
labelRequired,
form.state.data,
form.state.errors,
InputType.checkbox,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInputNumber(
"maxEntries",
labelMaxEntries,
form.state.data,
form.state.errors,
false,
"",
undefined,
undefined,
1,
form.handleChange,
)}
</>
)}
{fieldTypeValue === "Sequence" && (
<>
{renderSequencePicker(
true,
"refElementId",
labelRefElementId,
form.state.data,
form.state.errors,
form.handlePickerChange,
)}
</>
)}
{fieldTypeValue === "Text" && (
<>
{renderInput(
"multiLine",
labelMultiLine,
form.state.data,
form.state.errors,
InputType.checkbox,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{form.state.data.multiLine === true &&
renderInputTextarea(
true,
"defaultValue",
labelDefaultValue,
form.state.data,
form.state.errors,
false,
"",
form.handleTextAreaChange,
)}
{form.state.data.multiLine === false &&
renderInput(
"defaultValue",
labelDefaultValue,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
</>
)}
{fieldTypeValue === "Number" && (
<>
{renderInput(
"required",
labelRequired,
form.state.data,
form.state.errors,
InputType.checkbox,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInputNumber(
"minValue",
labelMinValue,
form.state.data,
form.state.errors,
false,
"",
undefined,
undefined,
1,
form.handleChange,
)}
{renderInputNumber(
"maxValue",
labelMaxValue,
form.state.data,
form.state.errors,
false,
"",
undefined,
undefined,
1,
form.handleChange,
)}
{renderInput(
"step",
labelStep,
form.state.data,
form.state.errors,
InputType.number,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInputNumber(
"defaultValue",
labelDefaultValue,
form.state.data,
form.state.errors,
false,
"",
undefined,
undefined,
1,
form.handleChange,
)}
</>
)}
{![
"Domain",
"Glossary",
"Sequence",
"FormTemplate",
"Text",
"Number",
].includes(fieldTypeValue) && (
<>
{renderInput(
"defaultValue",
labelDefaultValue,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInput(
"minEntries",
labelMinEntries,
form.state.data,
form.state.errors,
InputType.number,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInput(
"maxEntries",
labelMaxEntries,
form.state.data,
form.state.errors,
InputType.number,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
</>
)}
{isEditMode() && renderButton(labelApply, form.state.errors, "apply")}
{renderButton(labelSave, form.state.errors, "save")}
</form>
</Loading>
);
};
export default HOCCustomFieldDetails;
export default CustomFieldDetails;

View File

@ -1,5 +1,6 @@
import React from "react";
import { useTranslation } from "react-i18next";
import { useSearchParams } from "react-router-dom";
import HorizontalTabs from "../../../components/common/HorizionalTabs";
import Tab from "../../../components/common/Tab";
import authentication from "../../frame/services/authenticationService";
@ -13,11 +14,15 @@ interface DomainsDetailsProps {
const DomainsDetails: React.FC<DomainsDetailsProps> = ({ editMode }) => {
const { t } = useTranslation();
const [searchParams] = useSearchParams();
const canViewMailTemplates = authentication.hasAccess("ViewDomain");
const canViewSecurityRoles = authentication.hasAccess("ViewRole");
const heading = editMode ? t("EditDomain") : t("AddDomain");
const initialTab = searchParams.get("tab") || undefined;
const roleId = searchParams.get("roleId") || undefined;
const innerTab = searchParams.get("innerTab") || undefined;
const tabs: JSX.Element[] = [];
@ -38,7 +43,7 @@ const DomainsDetails: React.FC<DomainsDetailsProps> = ({ editMode }) => {
if (canViewSecurityRoles) {
tabs.push(
<Tab key={3} label={t("SecurityRoles")}>
<SecurityRolesTab />
<SecurityRolesTab initialRoleId={roleId} initialInnerTab={innerTab} />
</Tab>,
);
}
@ -47,7 +52,7 @@ const DomainsDetails: React.FC<DomainsDetailsProps> = ({ editMode }) => {
return (
<div>
<h1>{heading}</h1>
<HorizontalTabs>{tabs}</HorizontalTabs>
<HorizontalTabs initialTab={initialTab}>{tabs}</HorizontalTabs>
</div>
);
};

View File

@ -1,82 +1,89 @@
import Joi from "joi";
import React from "react";
import { Navigate } from "react-router-dom";
import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify";
import Form, { FormState } from "../../../../components/common/Form";
import { GeneralIdRef, MakeGeneralIdRef } from "../../../../utils/GeneralIdRef";
import withRouter, { RouterProps } from "../../../../utils/withRouter";
import { useForm } from "../../../../components/common/useForm";
import { MakeGeneralIdRef } from "../../../../utils/GeneralIdRef";
import roleService from "../serrvices/rolesService";
import Loading from "../../../../components/common/Loading";
import {
renderError,
renderButton,
renderUserPicker,
} from "../../../../components/common/formHelpers";
interface LocAddUserToRoleProps extends RouterProps {
isEditMode : boolean;
interface LocAddUserToRoleProps {
isEditMode: boolean;
}
interface LocAddUserToRoleState extends FormState {
const AddUserToRole: React.FC<LocAddUserToRoleProps> = ({ isEditMode }) => {
const { domainId, roleId } = useParams<{
domainId: string;
roleId: string;
}>();
const labelUserId = "User";
const labelApply = "Save";
const labelSave = "Save and close";
const form = useForm({
loaded: true,
data: {
userId?: GeneralIdRef;
};
redirect: string;
}
userId: undefined,
},
errors: {},
redirect: "",
});
class LocAddUserToRole extends Form<LocAddUserToRoleProps, any, LocAddUserToRoleState> {
state: LocAddUserToRoleState = {
loaded: true,
data: {
//userId: null;
},
errors: {},
redirect: "",
};
form.schema = {
userId: Joi.any().required().label(labelUserId),
};
labelUserId = "User";
const doSubmit = async (buttonName: string) => {
try {
const { userId } = form.state.data;
const userIdValue = userId as any;
const response = await roleService.postRoleUser(
userIdValue,
MakeGeneralIdRef(BigInt(roleId!)),
);
if (response) {
toast.info("User added to role");
labelApply = "Save";
labelSave = "Save and close";
schema = {
userId: Joi.any().required().label(this.labelUserId),
};
doSubmit = async (buttonName : string) => {
try {
const { userId } = this.state.data;
const { domainId, roleId } = this.props.router.params;
const response = await roleService.postRoleUser(userId!, MakeGeneralIdRef(roleId));
if (response) {
toast.info("User added to role");
if (buttonName === this.labelSave)
this.setState({ redirect: "/domains/edit/" + domainId });
}
}
catch(ex: any) {
this.handleGeneralError(ex);
}
};
render() {
const { loaded, redirect } = this.state;
if (redirect !== "") return <Navigate to={redirect} />;
const { isEditMode } = this.props;
return (
<Loading loaded={loaded}>
<form onSubmit={this.handleSubmit}>
{this.renderError("_general")}
{this.renderUserPicker("userId", this.labelUserId)}
{isEditMode && this.renderButton(this.labelApply)}
{this.renderButton(this.labelSave)}
</form>
</Loading>
);
if (buttonName === labelSave)
form.setState({
redirect: `/domains/edit/${domainId}?tab=SecurityRoles&roleId=${roleId}&innerTab=Users`,
});
}
} catch (ex: any) {
form.handleGeneralError(ex);
}
}
};
const AddUserToRole = withRouter(LocAddUserToRole);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
form.handleSubmit(e, doSubmit);
};
const { loaded, redirect } = form.state;
if (redirect) return <Navigate to={redirect} />;
return (
<Loading loaded={loaded}>
<form onSubmit={handleSubmit}>
{renderError("_general", form.state.errors)}
{renderUserPicker(
"userId",
labelUserId,
form.state.data,
form.state.errors,
form.handleUserPickerChange,
)}
{isEditMode && renderButton(labelApply, form.state.errors, "apply")}
{renderButton(labelSave, form.state.errors, "save")}
</form>
</Loading>
);
};
export default AddUserToRole;

View File

@ -1,130 +1,172 @@
import Joi from "joi";
import React from "react";
import React, { useEffect } from "react";
import { Navigate } from "react-router-dom";
import { toast } from "react-toastify";
import Form, { FormState } from "../../../../components/common/Form";
import { useForm } from "../../../../components/common/useForm";
import { InputType } from "../../../../components/common/Input";
import { MakeGeneralIdRef } from "../../../../utils/GeneralIdRef";
import withRouter from "../../../../utils/withRouter";
import mailTemplatesService from "../serrvices/mailTemplatesService";
import Loading from "../../../../components/common/Loading";
import {
renderError,
renderButton,
renderInput,
renderTemplateEditor,
} from "../../../../components/common/formHelpers";
interface EmailTemplateEditorProps{
domainId : number,
currentMailType : string
interface EmailTemplateEditorProps {
domainId?: string;
currentMailType: string;
}
interface EmailTemplateEditorState extends FormState {
const EmailTemplateEditor: React.FC<EmailTemplateEditorProps> = ({
domainId,
currentMailType,
}) => {
const labelName = "Subject";
const labelDefinition = "Definition";
const labelApply = "Save";
const labelSave = "Save and close";
const form = useForm({
loaded: false,
data: {
currentMailType? : string
isOverridden : boolean
subject: string;
definition: string;
};
redirect: string;
}
currentMailType: undefined,
isOverridden: false,
subject: "",
definition: "",
},
errors: {},
redirect: "",
});
class EmailTemplateEditor extends Form<EmailTemplateEditorProps, any, EmailTemplateEditorState> {
state: EmailTemplateEditorState = {
loaded : false,
data: {
currentMailType : undefined,
isOverridden : false,
form.schema = {
currentMailType: Joi.optional(),
isOverridden: Joi.optional(),
subject: Joi.string().required().max(450).label(labelName),
definition: Joi.string().required().label(labelDefinition),
};
useEffect(() => {
const loadTemplate = async () => {
try {
if (!domainId) return;
// Reset form state while loading
form.setState({
loaded: false,
data: {
currentMailType: undefined,
isOverridden: false,
subject: "",
definition: "",
},
errors: {},
redirect: "",
};
},
});
labelName = "Subject";
labelDefinition = "Definition";
labelApply = "Save";
labelSave = "Save and close";
schema = {
currentMailType : Joi.optional(),
isOverridden : Joi.optional(),
subject: Joi.string().required().max(450).label(this.labelName),
definition: Joi.string().required().label(this.labelDefinition),
};
componentDidMount = async () => {
this.handleTemplateChange();
};
componentDidUpdate = async (prevProps : EmailTemplateEditorProps) => {
if (prevProps.domainId !== this.props.domainId || prevProps.currentMailType !== this.props.currentMailType ) {
this.handleTemplateChange();
}
}
handleTemplateChange = async() => {
const { domainId, currentMailType } = this.props;
try{
var mailTemplate = await mailTemplatesService.getTemplate( MakeGeneralIdRef(domainId, undefined), currentMailType )
if (mailTemplate !== null)
{
this.setState({
loaded: true,
data : {
currentMailType : currentMailType,
isOverridden : mailTemplate.isOverridden,
subject : mailTemplate.subject,
definition : mailTemplate.templateDefinition
}});
}
}
catch(ex: any) {
this.handleGeneralError(ex);
}
}
doSubmit = async (buttonName : string) => {
try {
const { domainId, currentMailType } = this.props;
const { subject, definition } = this.state.data;
var domainGeneralIdRef = MakeGeneralIdRef(domainId);
const response = await mailTemplatesService.postTemplate(domainGeneralIdRef, currentMailType, subject, definition);
if (response) {
toast.info("Email Template saved");
if (buttonName === this.labelSave)
this.setState({ redirect: "/domains" });
}
}
catch(ex: any) {
this.handleGeneralError(ex);
}
};
render() {
const { loaded, redirect } = this.state;
const { currentMailType, isOverridden } = this.state.data;
if (redirect !== "") return <Navigate to={redirect} />;
return (
<Loading loaded={loaded}>
{isOverridden && <p>This template is custom for this domain only</p>}
{!isOverridden && <p>The details below are loaded from the master template. Saving this template will mean that any changes to the master template will not automatically appear here.</p>}
<form onSubmit={this.handleSubmit} template-name={currentMailType}>
{this.renderError("_general")}
{this.renderInput("subject", this.labelName, InputType.text)}
{this.renderTemplateEditor("mailTemplate-editor","definition", this.labelDefinition, false)}
{this.renderButton(this.labelApply)}
{this.renderButton(this.labelSave)}
</form>
</Loading>
const domainIdValue = BigInt(domainId);
var mailTemplate = await mailTemplatesService.getTemplate(
MakeGeneralIdRef(domainIdValue),
currentMailType,
);
if (mailTemplate !== null) {
form.setState({
loaded: true,
data: {
currentMailType: currentMailType,
isOverridden: mailTemplate.isOverridden,
subject: mailTemplate.subject,
definition: mailTemplate.templateDefinition,
},
});
}
} catch (ex: any) {
form.handleGeneralError(ex);
}
};
void loadTemplate();
}, [domainId, currentMailType]); // eslint-disable-line react-hooks/exhaustive-deps
const doSubmit = async (buttonName: string) => {
try {
const { subject, definition } = form.state.data;
if (!domainId) return;
const domainGeneralIdRef = MakeGeneralIdRef(BigInt(domainId));
const subjectStr = typeof subject === "string" ? subject : "";
const definitionStr = typeof definition === "string" ? definition : "";
const response = await mailTemplatesService.postTemplate(
domainGeneralIdRef,
currentMailType,
subjectStr,
definitionStr,
);
if (response) {
toast.info("Email Template saved");
if (buttonName === labelSave) form.setState({ redirect: "/domains" });
}
} catch (ex: any) {
form.handleGeneralError(ex);
}
}
};
const HOCEmailTemplateEditor = withRouter(EmailTemplateEditor);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
form.handleSubmit(e, doSubmit);
};
export default HOCEmailTemplateEditor;
const { redirect } = form.state;
const { isOverridden } = form.state.data;
const isOverriddenValue = Boolean(isOverridden);
if (redirect) return <Navigate to={redirect} />;
return (
<>
<div style={{ position: "relative" }}>
{isOverriddenValue && (
<p>This template is custom for this domain only</p>
)}
{!isOverriddenValue && (
<p>
The details below are loaded from the master template. Saving this
template will mean that any changes to the master template will not
automatically appear here.
</p>
)}
<form
onSubmit={handleSubmit}
template-name={currentMailType}
key={currentMailType}
>
{renderError("_general", form.state.errors)}
{renderInput(
"subject",
labelName,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderTemplateEditor(
"mailTemplate-editor",
"definition",
labelDefinition,
form.state.data,
false,
form.handleTemplateEditorChange,
)}
{renderButton(labelApply, form.state.errors, "apply")}
{renderButton(labelSave, form.state.errors, "save")}
</form>
</div>
</>
);
};
export default EmailTemplateEditor;

View File

@ -1,148 +1,247 @@
import Joi from "joi";
import React from "react";
import { Navigate } from "react-router-dom";
import React, { useEffect } from "react";
import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify";
import Form, { FormState } from "../../../../components/common/Form";
import { useForm } from "../../../../components/common/useForm";
import { InputType } from "../../../../components/common/Input";
import { GeneralIdRef, MakeGeneralIdRef } from "../../../../utils/GeneralIdRef";
import withRouter, { RouterProps } from "../../../../utils/withRouter";
import { MakeGeneralIdRef } from "../../../../utils/GeneralIdRef";
import domainsService from "../serrvices/domainsService";
import Loading from "../../../../components/common/Loading";
import {
renderError,
renderButton,
renderInput,
renderSsoProviderPicker,
} from "../../../../components/common/formHelpers";
interface GeneralTabProps extends RouterProps {
isEditMode: boolean;
interface GeneralTabProps {
isEditMode: boolean;
}
interface GeneralTabState extends FormState {
const GeneralTab: React.FC<GeneralTabProps> = ({ isEditMode }) => {
const { domainId } = useParams<{ domainId: string }>();
const labelName = "Name";
const labelSsoProvider = "Sso Provider";
const labelSunriseHostName = "e-flow hostname";
const labelSunriseAppId = "e-flow AppId";
const labelSunriseCategoryId = "e-flow CategoryId";
const labelSigmaId = "Sigma Id";
const labelApply = "Save";
const labelSave = "Save and close";
const form = useForm({
loaded: false,
data: {
name: string;
ssoProviderId: GeneralIdRef | null;
sunriseHostName: string;
sunriseAppId: string;
sunriseCategoryId: string;
sigmaId: bigint | null;
};
redirect: string;
}
name: "",
ssoProviderId: null,
sunriseHostName: "",
sunriseAppId: "",
sunriseCategoryId: "",
sigmaId: null,
},
errors: {},
redirect: "",
});
class LocGeneralTab extends Form<GeneralTabProps, any, GeneralTabState> {
state: GeneralTabState = {
loaded: false,
data: {
name: "",
ssoProviderId: null,
sunriseHostName: "",
sunriseAppId: "",
sunriseCategoryId: "",
sigmaId: null,
},
errors: {},
redirect: "",
};
form.schema = {
name: Joi.string().required().max(450).label(labelName),
ssoProviderId: Joi.object().allow(null).optional().label(labelSsoProvider),
sunriseHostName: Joi.string()
.required()
.allow("")
.max(16)
.label(labelSunriseHostName),
sunriseAppId: Joi.string().required().allow("").label(labelSunriseAppId),
sunriseCategoryId: Joi.string()
.required()
.allow("")
.label(labelSunriseCategoryId),
sigmaId: Joi.number().allow(null).optional().label(labelSigmaId),
};
labelName = "Name";
labelSsoProvider = "Sso Provider";
labelSunriseHostName = "e-flow hostname";
labelSunriseAppId = "e-flow AppId";
labelSunriseCategoryId = "e-flow CategoryId";
labelSigmaId = "Sigma Id";
const doSubmit = async (buttonName: string) => {
try {
const {
name,
ssoProviderId,
sunriseHostName,
sunriseAppId,
sunriseCategoryId,
sigmaId,
} = form.state.data;
const nameStr = typeof name === "string" ? name : "";
const sunriseHostNameStr =
typeof sunriseHostName === "string" ? sunriseHostName : "";
const sunriseAppIdStr =
typeof sunriseAppId === "string" ? sunriseAppId : "";
const sunriseCategoryIdStr =
typeof sunriseCategoryId === "string" ? sunriseCategoryId : "";
const sigmaIdValue =
sigmaId === null || sigmaId === undefined || sigmaId === ""
? null
: typeof sigmaId === "bigint"
? sigmaId
: BigInt(sigmaId as string | number);
labelApply = "Save";
labelSave = "Save and close";
schema = {
name: Joi.string().required().max(450).label(this.labelName),
ssoProviderId: Joi.object().allow(null).optional().label(this.labelSsoProvider),
sunriseHostName: Joi.string().required().allow("").max(16).label(this.labelSunriseHostName),
sunriseAppId: Joi.string().required().allow("").label(this.labelSunriseAppId),
sunriseCategoryId: Joi.string().required().allow("").label(this.labelSunriseCategoryId),
sigmaId: Joi.number().allow(null).optional().label(this.labelSigmaId),
};
doSubmit = async (buttonName: string) => {
try {
const { name, ssoProviderId, sunriseHostName, sunriseAppId, sunriseCategoryId, sigmaId } = this.state.data;
const { isEditMode } = this.props;
if (isEditMode) {
const { domainId } = this.props.router.params;
var generalIdRef = MakeGeneralIdRef(domainId);
const response = await domainsService.putDomain(generalIdRef, name, sunriseHostName, sunriseAppId, sunriseCategoryId, ssoProviderId, sigmaId);
if (response) {
toast.info("Domain edited");
}
} else {
const response = await domainsService.postDomain(name, sunriseHostName, sunriseAppId, sunriseCategoryId, ssoProviderId, sigmaId);
if (response) {
toast.info("New Domain added");
}
}
if (buttonName === this.labelSave) this.setState({ redirect: "/domains" });
} catch (ex: any) {
this.handleGeneralError(ex);
}
};
isEditMode = () => {
const { editMode } = this.props;
return editMode;
};
componentDidMount = async () => {
const { domainId } = this.props.router.params;
try {
if (domainId !== undefined) {
const loadedData = await domainsService.getDomain(domainId);
if (loadedData) {
const { data } = this.state;
data.name = loadedData.name;
data.ssoProviderId = loadedData.ssoProviderId;
data.sunriseHostName = loadedData.sunriseHostName;
data.sunriseAppId = loadedData.sunriseAppId;
data.sunriseCategoryId = loadedData.sunriseCategoryId;
data.sigmaId = loadedData.sigmaId;
this.setState({ loaded: true, data });
} else {
this.setState({ loaded: false });
}
}
} catch (ex: any) {
this.handleGeneralError(ex);
}
if (!this.isEditMode()) this.setState({ loaded: true });
};
render() {
const { loaded } = this.state;
const isEditMode = this.isEditMode();
const { redirect } = this.state;
if (redirect !== "") return <Navigate to={redirect} />;
return (
<Loading loaded={loaded}>
<form onSubmit={this.handleSubmit}>
{this.renderError("_general")}
{this.renderInput("name", this.labelName, InputType.text)}
{this.renderInput("sunriseHostName", this.labelSunriseHostName, InputType.text)}
{this.renderInput("sunriseAppId", this.labelSunriseAppId, InputType.text)}
{this.renderInput("sunriseCategoryId", this.labelSunriseCategoryId, InputType.text)}
{this.renderSsoProviderPicker("ssoProviderId", this.labelSsoProvider)}
{this.renderInput("sigmaId", this.labelSigmaId, InputType.number)}
{isEditMode && this.renderButton(this.labelApply)}
{this.renderButton(this.labelSave)}
</form>
</Loading>
if (isEditMode) {
var generalIdRef = MakeGeneralIdRef(BigInt(domainId!));
const response = await domainsService.putDomain(
generalIdRef,
nameStr,
sunriseHostNameStr,
sunriseAppIdStr,
sunriseCategoryIdStr,
ssoProviderId as any,
sigmaIdValue,
);
if (response) {
toast.info("Domain edited");
}
} else {
const response = await domainsService.postDomain(
nameStr,
sunriseHostNameStr,
sunriseAppIdStr,
sunriseCategoryIdStr,
ssoProviderId as any,
sigmaIdValue,
);
if (response) {
toast.info("New Domain added");
}
}
if (buttonName === "save") form.setState({ redirect: "/domains" });
} catch (ex: any) {
form.handleGeneralError(ex);
}
}
};
const GeneralTab = withRouter(LocGeneralTab);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
form.handleSubmit(e, doSubmit);
};
useEffect(() => {
const loadData = async () => {
try {
if (domainId !== undefined) {
const loadedData = await domainsService.getDomain(BigInt(domainId));
if (loadedData) {
const newData = { ...form.state.data };
newData.name = loadedData.name;
newData.ssoProviderId = loadedData.ssoProviderId;
newData.sunriseHostName = loadedData.sunriseHostName;
newData.sunriseAppId = loadedData.sunriseAppId;
newData.sunriseCategoryId = loadedData.sunriseCategoryId;
newData.sigmaId = loadedData.sigmaId;
form.setState({ loaded: true, data: newData });
} else {
form.setState({ loaded: false });
}
}
} catch (ex: any) {
form.handleGeneralError(ex);
}
if (!isEditMode) form.setState({ loaded: true });
};
loadData();
}, [domainId, isEditMode]); // eslint-disable-line react-hooks/exhaustive-deps
const { loaded, redirect } = form.state;
if (redirect) return <Navigate to={redirect} />;
return (
<Loading loaded={loaded}>
<form onSubmit={handleSubmit}>
{renderError("_general", form.state.errors)}
{renderInput(
"name",
labelName,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInput(
"sunriseHostName",
labelSunriseHostName,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInput(
"sunriseAppId",
labelSunriseAppId,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInput(
"sunriseCategoryId",
labelSunriseCategoryId,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderSsoProviderPicker(
"ssoProviderId",
labelSsoProvider,
form.state.data,
form.state.errors,
form.handleSsoProviderPickerChange,
)}
{renderInput(
"sigmaId",
labelSigmaId,
form.state.data,
form.state.errors,
InputType.number,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{isEditMode && renderButton(labelApply, form.state.errors, "apply")}
{renderButton(labelSave, form.state.errors, "save")}
</form>
</Loading>
);
};
export default GeneralTab;

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useEffect, useState, useCallback } from "react";
import { useParams } from "react-router-dom";
import mailTemplatesService from "../serrvices/mailTemplatesService";
import HOCEmailTemplateEditor from "./EmailTemplateEditor";
@ -10,7 +10,7 @@ interface MailType {
}
const MailTemplatesTab: React.FC = () => {
const [loaded, setLoaded] = useState(false);
const [loaded, setLoaded] = useState(true);
const [currentMailType, setCurrentMailType] = useState("");
const [types, setTypes] = useState<MailType[]>([]);
@ -22,21 +22,23 @@ const MailTemplatesTab: React.FC = () => {
setTypes(nextTypes);
if (nextTypes.length > 0) {
setLoaded(true);
setCurrentMailType(nextTypes[0].mailType);
}
setLoaded(true);
}
};
void loadTypes();
}, []);
const selectTemplate = (emailType: string) => {
if (currentMailType !== emailType) {
setLoaded(true);
setCurrentMailType(emailType);
}
};
const selectTemplate = useCallback(
(emailType: string) => {
if (currentMailType !== emailType) {
setCurrentMailType(emailType);
}
},
[currentMailType],
);
const onClick = (e: React.MouseEvent<HTMLElement>) => {
const value = (e.target as HTMLElement).getAttribute("value");
@ -54,7 +56,12 @@ const MailTemplatesTab: React.FC = () => {
<ul className="mail-types">
{types.map((x) => {
return (
<li key={x.mailType} value={x.mailType} onClick={onClick}>
<li
key={x.mailType}
value={x.mailType}
onClick={onClick}
className={currentMailType === x.mailType ? "selected" : ""}
>
{x.description}
</li>
);

View File

@ -1,129 +1,134 @@
import Joi from "joi";
import React from "react";
import { Navigate } from "react-router-dom";
import React, { useEffect } from "react";
import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify";
import Form, { FormState } from "../../../../components/common/Form";
import { useForm } from "../../../../components/common/useForm";
import { InputType } from "../../../../components/common/Input";
import { MakeGeneralIdRef } from "../../../../utils/GeneralIdRef";
import withRouter, { RouterProps } from "../../../../utils/withRouter";
import roleService from "../serrvices/rolesService";
import Loading from "../../../../components/common/Loading";
import {
renderError,
renderButton,
renderInput,
} from "../../../../components/common/formHelpers";
interface RolesDetailsProps extends RouterProps {
isEditMode : boolean;
interface RolesDetailsProps {
isEditMode: boolean;
}
interface RolesDetailsState extends FormState {
const RolesDetails: React.FC<RolesDetailsProps> = ({ isEditMode }) => {
const { domainId, roleId } = useParams<{
domainId: string;
roleId: string;
}>();
const labelName = "Name";
const labelApply = "Save";
const labelSave = "Save and close";
const form = useForm({
loaded: false,
data: {
name: string;
};
redirect: string;
}
name: "",
},
errors: {},
redirect: "",
});
class LocRolesDetails extends Form<RolesDetailsProps, any, RolesDetailsState> {
state: RolesDetailsState = {
loaded: false,
data: {
name: ""
},
errors: {},
redirect: "",
};
form.schema = {
name: Joi.string().required().max(450).label(labelName),
};
labelName = "Name";
const doSubmit = async (buttonName: string) => {
try {
const { name } = form.state.data;
const nameStr = typeof name === "string" ? name : "";
labelApply = "Save";
labelSave = "Save and close";
var domainIdGeneralIdRef = MakeGeneralIdRef(BigInt(domainId!));
schema = {
name: Joi.string().required().max(450).label(this.labelName),
};
doSubmit = async (buttonName : string) => {
try {
const { name } = this.state.data;
const { domainId } = this.props.router.params;
var domainIdGeneralIdRef = MakeGeneralIdRef(domainId);
if (this.isEditMode()) {
const { roleId } = this.props.router.params;
var generalIdRef = MakeGeneralIdRef(roleId);
const response = await roleService.putRole(generalIdRef, name);
if (response) {
toast.info("Role edited");
}
} else {
const response = await roleService.postRole(domainIdGeneralIdRef, name);
if (response) {
toast.info("New Role added");
}
}
if (buttonName === this.labelSave)
this.setState({ redirect: "/domains/edit/" + domainId });
if (isEditMode) {
var generalIdRef = MakeGeneralIdRef(BigInt(roleId!));
const response = await roleService.putRole(generalIdRef, nameStr);
if (response) {
toast.info("Role edited");
}
catch(ex: any) {
this.handleGeneralError(ex);
}
};
isEditMode = () => {
const { editMode } = this.props;
return editMode;
};
componentDidMount = async () => {
const { roleId } = this.props.router.params;
if (roleId !== undefined) {
try {
const loadedData = await roleService.getRole(roleId);
const { data } = this.state;
if (loadedData) {
data.name = loadedData.name;
this.setState({ loaded: true, data });
}
else {
this.setState({ loaded: false });
}
}
catch(ex: any) {
this.handleGeneralError(ex);
}
}
else {
this.setState({ loaded : true });
}
};
render() {
const { loaded, redirect } = this.state;
if (redirect !== "") return <Navigate to={redirect} />;
const isEditMode = this.isEditMode();
let mode = "Add";
if (isEditMode) mode = "Edit";
return (
<Loading loaded={loaded}>
<h1>{mode} Role</h1>
<form onSubmit={this.handleSubmit}>
{this.renderError("_general")}
{this.renderInput("name", this.labelName, InputType.text)}
{isEditMode && this.renderButton(this.labelApply)}
{this.renderButton(this.labelSave)}
</form>
</Loading>
} else {
const response = await roleService.postRole(
domainIdGeneralIdRef,
nameStr,
);
if (response) {
toast.info("New Role added");
}
}
if (buttonName === labelSave)
form.setState({ redirect: "/domains/edit/" + domainId });
} catch (ex: any) {
form.handleGeneralError(ex);
}
}
};
const RolesDetails = withRouter(LocRolesDetails);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
form.handleSubmit(e, doSubmit);
};
useEffect(() => {
const loadData = async () => {
if (roleId !== undefined) {
try {
const loadedData = await roleService.getRole(BigInt(roleId));
if (loadedData) {
const newData = { ...form.state.data };
newData.name = loadedData.name;
form.setState({ loaded: true, data: newData });
} else {
form.setState({ loaded: false });
}
} catch (ex: any) {
form.handleGeneralError(ex);
}
} else {
form.setState({ loaded: true });
}
};
loadData();
}, [roleId, form]);
const { loaded, redirect } = form.state;
if (redirect) return <Navigate to={redirect} />;
let mode = "Add";
if (isEditMode) mode = "Edit";
return (
<Loading loaded={loaded}>
<h1>{mode} Role</h1>
<form onSubmit={handleSubmit}>
{renderError("_general", form.state.errors)}
{renderInput(
"name",
labelName,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{isEditMode && renderButton(labelApply, form.state.errors, "apply")}
{renderButton(labelSave, form.state.errors, "save")}
</form>
</Loading>
);
};
export default RolesDetails;

View File

@ -22,12 +22,14 @@ interface RolesEditorProps {
selectedRole?: GetRoleResponse;
onSelectRole?: (keyValue: any) => void;
onUnselectRole?: () => void;
initialRoleId?: string;
}
const RolesEditor: React.FC<RolesEditorProps> = ({
selectedRole,
onSelectRole,
onUnselectRole,
initialRoleId,
}) => {
const { t } = useTranslation();
const [loaded, setLoaded] = useState(false);
@ -74,7 +76,19 @@ const RolesEditor: React.FC<RolesEditorProps> = ({
};
void loadInitial();
}, [changePage, domainId]);
}, [changePage]); // eslint-disable-line react-hooks/exhaustive-deps
// Auto-select role when initialRoleId is provided
useEffect(() => {
if (initialRoleId && pagedData.data.length > 0 && onSelectRole) {
const roleToSelect = pagedData.data.find(
(role) => role.id.toString() === initialRoleId,
);
if (roleToSelect) {
onSelectRole(roleToSelect);
}
}
}, [initialRoleId, pagedData.data, onSelectRole]);
const onSort = async (nextSortColumn: Column<GetRoleResponse>) => {
const { page, pageSize } = pagedData;

View File

@ -8,7 +8,15 @@ import UserRoleEditor from "./UserRoleEditor";
import { GetRoleResponse } from "../serrvices/rolesService";
import authentication from "../../../frame/services/authenticationService";
const SecurityRolesTab: React.FC = () => {
interface SecurityRolesTabProps {
initialRoleId?: string;
initialInnerTab?: string;
}
const SecurityRolesTab: React.FC<SecurityRolesTabProps> = ({
initialRoleId,
initialInnerTab,
}) => {
const { t } = useTranslation();
const [selectedRole, setSelectedRole] = useState<GetRoleResponse | undefined>(
undefined,
@ -49,12 +57,13 @@ const SecurityRolesTab: React.FC = () => {
selectedRole={selectedRole}
onSelectRole={onSelectRow}
onUnselectRole={onUnselectRow}
initialRoleId={initialRoleId}
/>
</div>
<div>
{selectedRole !== undefined &&
(canViewRoleAccess || canViewRoleUsers) && (
<HorizontalTabs>{tabs}</HorizontalTabs>
<HorizontalTabs initialTab={initialInnerTab}>{tabs}</HorizontalTabs>
)}
</div>
</div>

View File

@ -78,7 +78,7 @@ const UserRoleEditor: React.FC<UserRoleEditorProps> = ({ role }) => {
};
void loadInitial();
}, [getRoleUsers, role?.id, sortColumn]);
}, [getRoleUsers]); // eslint-disable-line react-hooks/exhaustive-deps
const changePage = async (page: number, pageSize: number) => {
await getRoleUsers(page, pageSize, sortColumn, filters);

View File

@ -94,8 +94,18 @@ const Forms: React.FC = () => {
};
useEffect(() => {
changePage(pagedData.page, pagedData.pageSize);
}, [changePage, pagedData.page, pagedData.pageSize]);
const loadInitial = async () => {
const data = await formsService.getForms(1, 10, "name", true, filters);
if (data) {
setLoaded(true);
setPagedData(data);
} else {
setLoaded(false);
}
};
void loadInitial();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (
<Loading loaded={loaded}>

View File

@ -1,141 +1,142 @@
import Joi from "joi";
import React from "react";
import { Navigate } from "react-router-dom";
import React, { useEffect } from "react";
import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify";
import Form from "../../../components/common/Form";
import { useForm } from "../../../components/common/useForm";
import { InputType } from "../../../components/common/Input";
import { FormState } from "../../../components/common/Form";
import withRouter from "../../../utils/withRouter";
import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
import formsService from "./services/formsService";
import Loading from "../../../components/common/Loading";
// import Tab from "../../../components/common/Tab";
// import HorizontalTabs from "../../../components/common/HorizionalTabs";
import {
renderError,
renderButton,
renderInput,
renderTemplateEditor,
} from "../../../components/common/formHelpers";
interface FormsDetailsState extends FormState {
const FormsDetails: React.FC<{ editMode?: boolean }> = ({
editMode = false,
}) => {
const { formId } = useParams<{ formId: string }>();
const labelName = "Name";
const labelDefinition = "Definition";
const labelApply = "Save";
const labelSave = "Save and close";
const form = useForm({
loaded: false,
data: {
name: string;
definition: string;
};
redirect: string;
}
name: "",
definition: "",
},
errors: {},
redirect: "",
});
class FormsDetails extends Form<any, any, FormsDetailsState> {
state: FormsDetailsState = {
loaded: false,
data: {
name: "",
definition: "",
},
errors: {},
redirect: "",
};
form.schema = {
name: Joi.string().required().max(450).label(labelName),
definition: Joi.string().required().label(labelDefinition),
};
labelName = "Name";
labelDefinition = "Definition";
labelApply = "Save";
labelSave = "Save and close";
schema = {
name: Joi.string().required().max(450).label(this.labelName),
definition: Joi.string().required().label(this.labelDefinition),
};
doSubmit = async (buttonName : string) => {
useEffect(() => {
const loadData = async () => {
if (formId !== undefined) {
try {
const { name, definition } = this.state.data;
const loadedData = await formsService.getForm(BigInt(formId));
if (this.isEditMode()) {
const { formId } = this.props.router.params;
if (loadedData) {
const newData = { ...form.state.data };
newData.name = loadedData.name;
newData.definition = loadedData.definition;
var generalIdRef = MakeGeneralIdRef(formId);
const response = await formsService.putForm(generalIdRef, name, definition);
if (response) {
toast.info("Form template edited");
}
} else {
const response = await formsService.postForm(name, definition);
if (response) {
toast.info("New Form Template added");
}
}
if (buttonName === this.labelSave)
this.setState({ redirect: "/forms" });
}
catch(ex: any) {
this.handleGeneralError(ex);
form.setState({ loaded: true, data: newData });
} else {
form.setState({ loaded: false });
}
} catch (ex: any) {
form.handleGeneralError(ex);
}
}
if (!editMode) form.setState({ loaded: true });
};
isEditMode = () => {
const { editMode } = this.props;
return editMode;
};
loadData();
}, [formId, editMode]); // eslint-disable-line react-hooks/exhaustive-deps
componentDidMount = async () => {
const { formId } = this.props.router.params;
const doSubmit = async (buttonName: string) => {
try {
const { name, definition } = form.state.data;
const nameStr = typeof name === "string" ? name : "";
const definitionStr = typeof definition === "string" ? definition : "";
if (formId !== undefined) {
try {
const loadedData = await formsService.getForm(formId);
const { data } = this.state;
if (loadedData) {
data.name = loadedData.name;
data.definition = loadedData.definition;
this.setState({ loaded: true, data });
}
else {
this.setState({ loaded: false });
}
}
catch(ex: any) {
this.handleGeneralError(ex);
}
}
if (!this.isEditMode())
this.setState({ loaded: true } );
};
render() {
const { loaded, redirect } = this.state;
if (redirect !== "") return <Navigate to={redirect} />;
let mode = "Add";
if (this.isEditMode()) mode = "Edit";
// let tabs : JSX.Element[] = [];
// tabs.push( <Tab key={1} label="Form Mode">
// {this.renderTemplateEditor("definition", this.labelDefinition, true)}
// </Tab> );
// tabs.push( <Tab key={1} label="Wizard Mode">
// <>This is Wizard Mode</>
// </Tab> );
return (
<Loading loaded={loaded}>
<h1>{mode} Form Template</h1>
<form onSubmit={this.handleSubmit}>
{this.renderError("_general")}
{this.renderInput("name", this.labelName, InputType.text)}
{/* <HorizontalTabs>
{tabs}
</HorizontalTabs> */}
{this.renderTemplateEditor("form-editor","definition", this.labelDefinition, true)}
{this.isEditMode() && this.renderButton(this.labelApply)}
{this.renderButton(this.labelSave)}
</form>
</Loading>
if (editMode) {
var generalIdRef = MakeGeneralIdRef(BigInt(formId!));
const response = await formsService.putForm(
generalIdRef,
nameStr,
definitionStr,
);
if (response) {
toast.info("Form template edited");
}
} else {
const response = await formsService.postForm(nameStr, definitionStr);
if (response) {
toast.info("New Form Template added");
}
}
if (buttonName === "save") form.setState({ redirect: "/forms" });
} catch (ex: any) {
form.handleGeneralError(ex);
}
}
};
const HOCFormsDetails = withRouter(FormsDetails);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
form.handleSubmit(e, doSubmit);
};
export default HOCFormsDetails;
const { loaded, redirect } = form.state;
if (redirect) return <Navigate to={redirect} />;
let mode = "Add";
if (editMode) mode = "Edit";
return (
<Loading loaded={loaded}>
<h1>{mode} Form Template</h1>
<form onSubmit={handleSubmit}>
{renderError("_general", form.state.errors)}
{renderInput(
"name",
labelName,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderTemplateEditor(
"form-editor",
"definition",
labelDefinition,
form.state.data,
true,
form.handleTemplateEditorChange,
)}
{editMode && renderButton(labelApply, form.state.errors, "apply")}
{renderButton(labelSave, form.state.errors, "save")}
</form>
</Loading>
);
};
export default FormsDetails;

View File

@ -1,177 +1,269 @@
import Joi from "joi";
import React from "react";
import { Navigate } from "react-router-dom";
import React, { useEffect } from "react";
import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify";
import Form from "../../../components/common/Form";
import { useForm } from "../../../components/common/useForm";
import { InputType } from "../../../components/common/Input";
import { FormState } from "../../../components/common/Form";
import withRouter from "../../../utils/withRouter";
import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
import glossariesService, { Glossary, SystemGlossaries } from "./services/glossaryService";
import { GeneralIdRef, MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
import glossariesService, {
CustomFieldValue,
Glossary,
SystemGlossaries,
} from "./services/glossaryService";
import { CustomField } from "../customfields/services/customFieldsService";
import Loading from "../../../components/common/Loading";
import {
renderError,
renderButton,
renderInput,
renderCustomFields,
renderCustomFieldsEditor,
} from "../../../components/common/formHelpers";
interface GlossariesState extends FormState {
interface GlossariesDetailsProps {
editMode?: boolean;
}
const GlossariesDetails: React.FC<GlossariesDetailsProps> = ({
editMode = false,
}) => {
const { glossaryId } = useParams<{ glossaryId: string }>();
const labelName = "Name";
const labelChildCustomFieldDefinition = "Custom field for child entries";
const labelApply = "Save";
const labelSave = "Save and close";
const form = useForm({
loaded: false,
data: {
id? : bigint,
guid? : string,
name: string,
parent?: Glossary,
childCustomFieldDefinition : CustomField[]
};
redirect: string;
}
id: undefined,
guid: undefined,
name: "",
parent: undefined,
childCustomFieldDefinition: [],
},
errors: {},
redirect: "",
});
class GlossariesDetails extends Form<any, any, GlossariesState> {
state: GlossariesState = {
loaded: false,
data: {
id: undefined,
guid: undefined,
name: "",
parent : undefined,
childCustomFieldDefinition : []
},
errors: {},
redirect: "",
};
form.schema = {
id: Joi.optional(),
guid: Joi.optional(),
name: Joi.string().required().max(450).label(labelName),
parent: Joi.optional(),
childCustomFieldDefinition: Joi.optional().label(
labelChildCustomFieldDefinition,
),
};
labelName = "Name";
labelParent = "Parent"
labelChildCustomFieldDefinition = "Custom field for child entries"
useEffect(() => {
const loadData = async () => {
const newData = { ...form.state.data };
labelApply = "Save";
labelSave = "Save and close";
schema = {
id : Joi.optional(),
guid : Joi.optional(),
name: Joi.string().required().max(450).label(this.labelName),
parent: Joi.optional(),
childCustomFieldDefinition : Joi.optional().label(this.labelChildCustomFieldDefinition)
};
doSubmit = async (buttonName : string) => {
if (editMode) {
const generalIdRef = MakeGeneralIdRef(BigInt(glossaryId!));
try {
const { id, guid, name, parent, childCustomFieldDefinition } = this.state.data;
const loadedData =
await glossariesService.getGlossaryItem(generalIdRef);
if (loadedData) {
newData.name = loadedData.name;
newData.parent = loadedData.parent;
newData.id = loadedData.id;
newData.guid = loadedData.guid;
newData.childCustomFieldDefinition =
loadedData.childCustomFieldDefinition ?? [];
const customfieldValues = this.CustomFieldValues();
const parentGlossary = newData.parent as Glossary | undefined;
if (this.isEditMode()) {
const generalIdRef = MakeGeneralIdRef(id, guid);
const response = await glossariesService.putGlossaryItem(generalIdRef, parent, name, childCustomFieldDefinition, customfieldValues);
if (response) {
toast.info("Glossary Item edited");
}
} else {
const generalIdRef = parent ? MakeGeneralIdRef(parent.id, parent.guid) : SystemGlossaries;
const response = await glossariesService.postGlossaryItem(generalIdRef, name, childCustomFieldDefinition, customfieldValues);
if (response) {
toast.info("New Glossary Item added");
}
}
const navigateId = parent ? parent.id : "";
if (buttonName === this.labelSave)
this.setState({ redirect: "/glossaries/" + navigateId });
form.setCustomFieldValues(
newData,
loadedData.customFieldValues,
parentGlossary?.childCustomFieldDefinition ?? [],
);
}
} catch (ex: any) {
form.handleGeneralError(ex);
}
catch(ex: any) {
this.handleGeneralError(ex);
} else {
const generalIdRef = MakeGeneralIdRef(BigInt(glossaryId!));
try {
const loadedData =
await glossariesService.getGlossaryItem(generalIdRef);
if (loadedData) {
newData.parent = loadedData;
}
} catch (ex: any) {
form.handleGeneralError(ex);
}
}
form.setState({
loaded: true,
data: newData,
customFields: (newData.parent as Glossary | undefined)
?.childCustomFieldDefinition,
});
};
isEditMode = () => {
const { editMode } = this.props;
return editMode;
};
loadData();
}, [glossaryId, editMode]); // eslint-disable-line react-hooks/exhaustive-deps
componentDidMount = async () => {
const { data } = this.state;
const handleAdd = (customfield: CustomField) => {
const newData = { ...form.state.data };
const childDefs =
(newData.childCustomFieldDefinition as CustomField[]) ?? [];
childDefs.push(customfield);
newData.childCustomFieldDefinition = childDefs;
if (this.isEditMode()) {
const { glossaryId } = this.props.router.params;
form.setState({ data: newData });
};
const generalIdRef = MakeGeneralIdRef(glossaryId);
try {
const loadedData = await glossariesService.getGlossaryItem( generalIdRef)
if (loadedData) {
data.name = loadedData.name;
data.parent = loadedData.parent;
data.id = loadedData.id;
data.guid = loadedData.guid;
data.childCustomFieldDefinition = loadedData.childCustomFieldDefinition ?? [];
const handleDelete = (fieldToDelete: CustomField) => {
const newData = { ...form.state.data };
this.setCustomFieldValues(data, loadedData.customFieldValues, (data.parent as Glossary)?.childCustomFieldDefinition);
}
}
catch(ex: any) {
this.handleGeneralError(ex);
}
}
else{
const { glossaryId } = this.props.router.params;
const generalIdRef = MakeGeneralIdRef(glossaryId);
try{
const loadedData = await glossariesService.getGlossaryItem( generalIdRef)
if (loadedData) {
data.parent = loadedData;
}
}
catch(ex: any) {
this.handleGeneralError(ex);
}
}
this.setState({ loaded: true, data, customFields : data.parent?.childCustomFieldDefinition });
};
handleAdd = ( customfield : CustomField) => {
let { data } = this.state;
data.childCustomFieldDefinition.push( customfield);
this.setState({ data });
if (fieldToDelete) {
const childDefs =
(newData.childCustomFieldDefinition as CustomField[]) ?? [];
newData.childCustomFieldDefinition = childDefs.filter(
(x) => x !== fieldToDelete,
);
}
handleDelete = ( fieldToDelete : CustomField ) => {
let { data } = this.state;
form.setState({ data: newData });
};
if (fieldToDelete){
data.childCustomFieldDefinition = data.childCustomFieldDefinition.filter( x => x !== fieldToDelete);
}
const doSubmit = async (buttonName: string) => {
try {
const { id, guid, name, parent, childCustomFieldDefinition } =
form.state.data;
const nameStr = typeof name === "string" ? name : "";
const parentGlossary = parent as Glossary | undefined;
const childDefs = (childCustomFieldDefinition as CustomField[]) ?? [];
const idValue = typeof id === "bigint" ? id : undefined;
const guidValue = typeof guid === "string" ? guid : undefined;
this.setState({ data });
}
const customfieldValues = form.CustomFieldValues();
render() {
const { loaded, redirect, data } = this.state;
if (redirect !== "") return <Navigate to={redirect} />;
if (editMode) {
const generalIdRef = MakeGeneralIdRef(idValue, guidValue);
let mode = "Add";
const isEditMode = this.isEditMode();
if (isEditMode) mode = "Edit";
return (
<Loading loaded={loaded}>
<h1>{mode} Glossary Item</h1>
<form onSubmit={this.handleSubmit}>
{this.renderError("_general")}
{this.renderInput("name", this.labelName, InputType.text)}
{this.renderCustomFields(data.parent?.childCustomFieldDefinition)}
<hr/>
{this.renderCustomFieldsEditor("childCustomFieldDefinition", this.labelChildCustomFieldDefinition, data.childCustomFieldDefinition, this.handleAdd, this.handleDelete)}
<br/>
{isEditMode && this.renderButton(this.labelApply)}
{this.renderButton(this.labelSave)}
</form>
</Loading>
const response = await glossariesService.putGlossaryItem(
generalIdRef,
parentGlossary
? MakeGeneralIdRef(parentGlossary.id, parentGlossary.guid)
: undefined,
nameStr,
childDefs,
customfieldValues,
);
if (response) {
toast.info("Glossary Item edited");
}
} else {
const generalIdRef = parentGlossary
? MakeGeneralIdRef(parentGlossary.id, parentGlossary.guid)
: SystemGlossaries;
const response = await glossariesService.postGlossaryItem(
generalIdRef,
nameStr,
childDefs,
customfieldValues,
);
if (response) {
toast.info("New Glossary Item added");
}
}
const navigateId = parentGlossary ? parentGlossary.id.toString() : "";
if (buttonName === "save")
form.setState({ redirect: "/glossaries/" + navigateId });
} catch (ex: any) {
form.handleGeneralError(ex);
}
}
};
const HOCGlossariesDetails = withRouter(GlossariesDetails);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
form.handleSubmit(e, doSubmit);
};
export default HOCGlossariesDetails;
const { loaded, redirect, data } = form.state;
if (redirect) return <Navigate to={redirect} />;
let mode = "Add";
if (editMode) mode = "Edit";
const parentGlossary = data.parent as Glossary | undefined;
const handleCustomFieldChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
if (e.target instanceof HTMLTextAreaElement) {
form.handleTextAreaChange(e as React.ChangeEvent<HTMLTextAreaElement>);
} else {
form.handleCustomFieldChange(e as React.ChangeEvent<HTMLInputElement>);
}
};
const getCustomFieldType = (
field: CustomFieldValue,
customFields: CustomField[],
) => {
return form.getCustomFieldType(field as any, customFields);
};
const handleCustomFieldPickerChange = (
name: string,
value: GeneralIdRef | CustomFieldValue[],
) => {
if (Array.isArray(value)) {
form.handleGlossaryPickerChange(name, value);
} else {
form.handlePickerChange(name, value as GeneralIdRef);
}
};
return (
<Loading loaded={loaded}>
<h1>{mode} Glossary Item</h1>
<form onSubmit={handleSubmit}>
{renderError("_general", form.state.errors)}
{renderInput(
"name",
labelName,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderCustomFields(
parentGlossary?.childCustomFieldDefinition,
form.state.data,
form.state.errors,
handleCustomFieldChange,
handleCustomFieldPickerChange,
getCustomFieldType,
)}
<hr />
{renderCustomFieldsEditor(
"childCustomFieldDefinition",
labelChildCustomFieldDefinition,
form.state.data,
form.state.errors,
(data.childCustomFieldDefinition as CustomField[]) ?? [],
handleAdd,
handleDelete,
)}
<br />
{editMode && renderButton(labelApply, form.state.errors, "apply")}
{renderButton(labelSave, form.state.errors, "save")}
</form>
</Loading>
);
};
export default GlossariesDetails;

View File

@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import Button, { ButtonType } from "../../../components/common/Button";
import Column from "../../../components/common/columns";
@ -29,21 +29,24 @@ const Organisations: React.FC = () => {
new Map<string, string>(),
);
const changePage = async (page: number, pageSize: number) => {
const data = await organisationsService.getOrganisations(
page,
pageSize,
sortColumn.key,
sortColumn.order === "asc",
filters,
);
if (data) {
setLoaded(true);
setPagedData(data);
} else {
setLoaded(false);
}
};
const changePage = useCallback(
async (page: number, pageSize: number) => {
const data = await organisationsService.getOrganisations(
page,
pageSize,
sortColumn.key,
sortColumn.order === "asc",
filters,
);
if (data) {
setLoaded(true);
setPagedData(data);
} else {
setLoaded(false);
}
},
[filters, sortColumn.key, sortColumn.order],
);
const onSort = async (newSortColumn: Column<ReadOrganisation>) => {
const { page, pageSize } = pagedData;
@ -96,9 +99,24 @@ const Organisations: React.FC = () => {
};
useEffect(() => {
const { page, pageSize } = pagedData;
changePage(page, pageSize);
});
const loadInitial = async () => {
const data = await organisationsService.getOrganisations(
1,
10,
"name",
true,
filters,
);
if (data) {
setLoaded(true);
setPagedData(data);
} else {
setLoaded(false);
}
};
void loadInitial();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (
<Loading loaded={loaded}>

View File

@ -1,140 +1,175 @@
import Joi from "joi";
import React from "react";
import { Navigate } from "react-router-dom";
import React, { useEffect } from "react";
import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify";
import Form from "../../../components/common/Form";
import { useForm } from "../../../components/common/useForm";
import { InputType } from "../../../components/common/Input";
import { FormState } from "../../../components/common/Form";
import withRouter from "../../../utils/withRouter";
import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
import Option from "../../../components/common/option";
import organisationsService from "./services/organisationsService";
import Loading from "../../../components/common/Loading";
import {
renderError,
renderButton,
renderInput,
renderSelect,
} from "../../../components/common/formHelpers";
interface OrganisationsDetailsState extends FormState {
const OrganisationsDetails: React.FC<{ editMode?: boolean }> = ({
editMode = false,
}) => {
const { organisationId } = useParams<{ organisationId: string }>();
const labelName = "Name";
const labelAddress = "Address";
const labelStatus = "Status";
const labelApply = "Save";
const labelSave = "Save and close";
const organisationStatusOptions: Option[] = [
{ _id: "Active", name: "Active" },
{ _id: "Pending", name: "Pending" },
{ _id: "Blocked", name: "Blocked" },
];
const form = useForm({
loaded: false,
data: {
name: string;
address: string;
status: string;
};
redirect: string;
organisationStatusOptions: Option[];
}
name: "",
address: "",
status: "Active",
},
errors: {},
redirect: "",
});
class OrganisationsDetails extends Form<any, any, OrganisationsDetailsState> {
state: OrganisationsDetailsState = {
loaded: false,
data: {
name: "",
address: "",
status: "Active"
},
errors: {},
redirect: "",
organisationStatusOptions: [
{ _id: "Active", name: "Active" },
{ _id: "Pending", name: "Pending" },
{ _id: "Blocked", name: "Blocked" },
]
};
form.schema = {
name: Joi.string().required().max(450).label(labelName),
address: Joi.string().required().max(450).label(labelAddress),
status: Joi.string().required().max(450).label(labelStatus),
};
labelName = "Name";
labelAddress = "Address";
labelStatus = "Status";
labelApply = "Save";
labelSave = "Save and close";
schema = {
name: Joi.string().required().max(450).label(this.labelName),
address:Joi.string().required().max(450).label(this.labelAddress),
status:Joi.string().required().max(450).label(this.labelAddress)
};
doSubmit = async (buttonName : string) => {
useEffect(() => {
const loadData = async () => {
if (organisationId !== undefined) {
try {
const { name, address, status } = this.state.data;
if (this.isEditMode()) {
const { organisationId } = this.props.router.params;
const loadedData = await organisationsService.getOrganisation(
BigInt(organisationId),
);
var generalIdRef = MakeGeneralIdRef(organisationId);
const response = await organisationsService.putOrganisation(generalIdRef, name, address, status);
if (response) {
toast.info("Organisation edited");
}
} else {
const response = await organisationsService.postOrganisation(name, address, status);
if (response) {
toast.info("New Organisation added");
}
}
if (loadedData) {
const newData = { ...form.state.data };
newData.name = loadedData.name;
newData.address = loadedData.address;
newData.status = loadedData.status;
if (buttonName === this.labelSave)
this.setState({ redirect: "/organisations" });
}
catch(ex: any) {
this.handleGeneralError(ex);
form.setState({ loaded: true, data: newData });
} else {
form.setState({ loaded: false });
}
} catch (ex: any) {
form.handleGeneralError(ex);
}
}
if (!editMode) form.setState({ loaded: true });
};
isEditMode = () => {
const { editMode } = this.props;
return editMode;
};
loadData();
}, [organisationId, editMode]); // eslint-disable-line react-hooks/exhaustive-deps
componentDidMount = async () => {
const { organisationId } = this.props.router.params;
const doSubmit = async (buttonName: string) => {
try {
const { name, address, status } = form.state.data;
const nameStr = typeof name === "string" ? name : "";
const addressStr = typeof address === "string" ? address : "";
const statusStr = typeof status === "string" ? status : "";
if (organisationId !== undefined) {
try
{
const loadedData = await organisationsService.getOrganisation(organisationId);
const { data } = this.state;
if (loadedData) {
data.name = loadedData.name;
data.address = loadedData.address;
data.status = loadedData.status;
this.setState({ loaded: true, data });
}
else {
this.setState({ loaded: false });
}
}
catch(ex: any) {
this.handleGeneralError(ex);
}
}
if (!this.isEditMode())
this.setState({loaded:true});
};
render() {
const { loaded, redirect, organisationStatusOptions } = this.state;
if (redirect !== "") return <Navigate to={redirect} />;
let mode = "Add";
if (this.isEditMode()) mode = "Edit";
return (
<Loading loaded={loaded}>
<h1>{mode} Organisation</h1>
<form onSubmit={this.handleSubmit}>
{this.renderError("_general")}
{this.renderInput("name", this.labelName, InputType.text)}
{this.renderInput("address", this.labelAddress, InputType.text)}
{this.renderSelect("status", this.labelStatus, organisationStatusOptions)}
{this.isEditMode() && this.renderButton(this.labelApply)}
{this.renderButton(this.labelSave)}
</form>
</Loading>
if (editMode) {
var generalIdRef = MakeGeneralIdRef(BigInt(organisationId!));
const response = await organisationsService.putOrganisation(
generalIdRef,
nameStr,
addressStr,
statusStr,
);
if (response) {
toast.info("Organisation edited");
}
} else {
const response = await organisationsService.postOrganisation(
nameStr,
addressStr,
statusStr,
);
if (response) {
toast.info("New Organisation added");
}
}
if (buttonName === "save") form.setState({ redirect: "/organisations" });
} catch (ex: any) {
form.handleGeneralError(ex);
}
}
};
const HOCOrganisationsDetails = withRouter(OrganisationsDetails);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
form.handleSubmit(e, doSubmit);
};
export default HOCOrganisationsDetails;
const { loaded, redirect } = form.state;
if (redirect) return <Navigate to={redirect} />;
let mode = "Add";
if (editMode) mode = "Edit";
return (
<Loading loaded={loaded}>
<h1>{mode} Organisation</h1>
<form onSubmit={handleSubmit}>
{renderError("_general", form.state.errors)}
{renderInput(
"name",
labelName,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInput(
"address",
labelAddress,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderSelect(
"status",
labelStatus,
form.state.data,
form.state.errors,
organisationStatusOptions,
form.handleSelectChange,
)}
{editMode && renderButton(labelApply, form.state.errors, "apply")}
{renderButton(labelSave, form.state.errors, "save")}
</form>
</Loading>
);
};
export default OrganisationsDetails;

View File

@ -1,152 +1,224 @@
import Joi from "joi";
import React from "react";
import { Navigate } from "react-router-dom";
import React, { useEffect } from "react";
import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify";
import Form from "../../../components/common/Form";
import { useForm } from "../../../components/common/useForm";
import { InputType } from "../../../components/common/Input";
import { FormState } from "../../../components/common/Form";
import {
renderInput,
renderButton,
renderError,
renderSelect,
renderInputNumber,
} from "../../../components/common/formHelpers";
import sequenceService from "./services/sequenceService";
import Option from "../../../components/common/option";
import withRouter from "../../../utils/withRouter";
import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
import Loading from "../../../components/common/Loading";
interface SequenceDetailsState extends FormState {
interface SequenceDetailsProps {
editMode?: boolean;
}
const SequenceDetails: React.FC<SequenceDetailsProps> = ({
editMode = false,
}) => {
const { sequenceId } = useParams<{ sequenceId: string }>();
const labelName = "Name";
const labelSeed = "Seed";
const labelIncrement = "Increment";
const labelPattern = "Pattern";
const labelRolloverType = "Rollover Type";
const labelApply = "Save";
const labelSave = "Save and close";
const form = useForm({
loaded: false,
data: {
name: string;
seed: number;
increment: number;
pattern: string;
rolloverType: string;
};
redirect: string;
}
name: "",
seed: 1,
increment: 1,
pattern: "[0]",
rolloverType: "Continuous",
},
errors: {},
redirect: "",
});
class SequenceDetails extends Form<any, any, SequenceDetailsState> {
state: SequenceDetailsState = {
loaded : false,
data: {
name: "",
seed: 1,
increment: 1,
pattern: "[0]",
rolloverType: "Continuous",
},
errors: {},
redirect: "",
};
form.schema = {
name: Joi.string().required().max(450).label(labelName),
seed: Joi.number().required().label(labelSeed),
increment: Joi.number().required().label(labelIncrement),
pattern: Joi.string().required().label(labelPattern),
rolloverType: Joi.string().required().label(labelRolloverType),
};
labelName = "Name";
labelSeed = "Seed";
labelIncrement = "Increment";
labelPattern = "Pattern";
labelRolloverType = "Rollover Type";
labelApply = "Save";
labelSave = "Save and close";
schema = {
name: Joi.string().required().max(450).label(this.labelName),
seed: Joi.number().required().label(this.labelSeed),
increment: Joi.number().required().label(this.labelIncrement),
pattern: Joi.string().required().label(this.labelPattern),
rolloverType: Joi.string().required().label(this.labelRolloverType),
};
doSubmit = async (buttonName : string) => {
useEffect(() => {
const loadData = async () => {
if (sequenceId !== undefined) {
try {
const { name, seed, increment, pattern, rolloverType } = this.state.data;
if (this.isEditMode()) {
const { sequenceId } = this.props.router.params;
var generalIdRef = MakeGeneralIdRef(sequenceId);
const response = await sequenceService.putSequence(generalIdRef, name, seed, increment, pattern, rolloverType);
if (response) {
toast.info("Sequence edited");
}
} else {
const response = await sequenceService.postSequence(name, seed, increment, pattern, rolloverType);
if (response) {
toast.info("New sequence added");
}
}
if (buttonName === this.labelSave)
this.setState({ redirect: "/sequence" });
}
catch(ex: any) {
this.handleGeneralError(ex);
const loadedData = await sequenceService.getSequence(
BigInt(sequenceId),
);
if (loadedData) {
const newData = { ...form.state.data };
newData.increment = loadedData.increment;
newData.name = loadedData.name;
newData.pattern = loadedData.pattern;
newData.rolloverType = loadedData.rolloverType;
newData.seed = loadedData.seed;
form.setState({ loaded: true, data: newData });
} else {
form.setState({ loaded: false });
}
} catch (ex: any) {
form.handleGeneralError(ex);
}
}
if (!editMode) {
form.setState({ loaded: true });
}
};
isEditMode = () => {
const { editMode } = this.props;
return editMode;
};
loadData();
}, [sequenceId, editMode]); // eslint-disable-line react-hooks/exhaustive-deps
componentDidMount = async () => {
const { sequenceId } = this.props.router.params;
const doSubmit = async (buttonName: string) => {
try {
const { name, seed, increment, pattern, rolloverType } = form.state.data;
const nameStr = typeof name === "string" ? name : "";
const patternStr = typeof pattern === "string" ? pattern : "";
const rolloverTypeStr =
typeof rolloverType === "string" ? rolloverType : "";
const seedValue = typeof seed === "number" ? seed : Number(seed);
const incrementValue =
typeof increment === "number" ? increment : Number(increment);
if (sequenceId !== undefined) {
try {
const loadedData = await sequenceService.getSequence(sequenceId);
if (loadedData) {
const { data } = this.state;
data.increment = loadedData.increment;
data.name = loadedData.name;
data.pattern = loadedData.pattern;
data.rolloverType = loadedData.rolloverType;
data.seed = loadedData.seed;
this.setState({ loaded: true, data });
}
else {
this.setState({ loaded: false });
}
}
catch(ex: any) {
this.handleGeneralError(ex);
}
}
if (!this.isEditMode())
this.setState({ loaded: true } );
};
render() {
const { loaded, redirect } = this.state;
if (redirect !== "") return <Navigate to={redirect} />;
const rolloverOptions: Option[] = [
{ _id: "Continuous", name: "Continuous" },
{ _id: "Day", name: "Day" },
{ _id: "Month", name: "Month" },
{ _id: "Year", name: "Year" },
];
let mode = "Add";
if (this.isEditMode()) mode = "Edit";
return (
<Loading loaded={loaded}>
<h1>{mode} Sequence</h1>
<form onSubmit={this.handleSubmit}>
{this.renderError("_general")}
{this.renderInput("name", this.labelName, InputType.text)}
{this.renderInput("seed", this.labelSeed, InputType.text)}
{this.renderInput("increment", this.labelIncrement)}
{this.renderInput("pattern", this.labelPattern)}
{this.renderSelect("rolloverType", this.labelRolloverType, rolloverOptions)}
{this.isEditMode() && this.renderButton(this.labelApply)}
{this.renderButton(this.labelSave)}
</form>
</Loading>
if (editMode) {
const generalIdRef = MakeGeneralIdRef(
sequenceId ? BigInt(sequenceId) : undefined,
);
const response = await sequenceService.putSequence(
generalIdRef,
nameStr,
seedValue,
incrementValue,
patternStr,
rolloverTypeStr,
);
if (response) {
toast.info("Sequence edited");
}
} else {
const response = await sequenceService.postSequence(
nameStr,
seedValue,
incrementValue,
patternStr,
rolloverTypeStr,
);
if (response) {
toast.info("New sequence added");
}
}
if (buttonName === "save") {
form.setState({ redirect: "/sequence" });
}
} catch (ex: any) {
form.handleGeneralError(ex);
}
}
};
const HOCSequenceDetails = withRouter(SequenceDetails);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
form.handleSubmit(e, doSubmit);
};
export default HOCSequenceDetails;
const { loaded, redirect } = form.state;
if (redirect) return <Navigate to={redirect} />;
const rolloverOptions: Option[] = [
{ _id: "Continuous", name: "Continuous" },
{ _id: "Day", name: "Day" },
{ _id: "Month", name: "Month" },
{ _id: "Year", name: "Year" },
];
const mode = editMode ? "Edit" : "Add";
return (
<Loading loaded={loaded}>
<h1>{mode} Sequence</h1>
<form onSubmit={handleSubmit}>
{renderError("_general", form.state.errors)}
{renderInput(
"name",
labelName,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInputNumber(
"seed",
labelSeed,
form.state.data,
form.state.errors,
false,
"",
undefined,
undefined,
1,
form.handleChange,
)}
{renderInputNumber(
"increment",
labelIncrement,
form.state.data,
form.state.errors,
false,
"",
undefined,
undefined,
1,
form.handleChange,
)}
{renderInput(
"pattern",
labelPattern,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderSelect(
"rolloverType",
labelRolloverType,
form.state.data,
form.state.errors,
rolloverOptions,
form.handleSelectChange,
)}
{editMode && renderButton(labelApply, form.state.errors, "save")}
{renderButton(labelSave, form.state.errors, "save")}
</form>
</Loading>
);
};
export default SequenceDetails;

View File

@ -93,8 +93,24 @@ const Sequence: React.FC = () => {
};
useEffect(() => {
changePage(pagedData.page, pagedData.pageSize);
}, [changePage, pagedData.page, pagedData.pageSize]);
const loadInitial = async () => {
const data = await sequenceService.getSequences(
1,
10,
"name",
true,
filters,
);
if (data) {
setLoaded(true);
setPagedData(data);
} else {
setLoaded(false);
}
};
void loadInitial();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (
<Loading loaded={loaded}>

View File

@ -1,143 +1,186 @@
import Joi from "joi";
import React from "react";
import { Navigate } from "react-router-dom";
import React, { useEffect } from "react";
import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify";
import Form from "../../../components/common/Form";
import { useForm } from "../../../components/common/useForm";
import { InputType } from "../../../components/common/Input";
import { FormState } from "../../../components/common/Form";
import withRouter from "../../../utils/withRouter";
import {
renderInput,
renderButton,
renderError,
renderSelect,
} from "../../../components/common/formHelpers";
import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
import Option from "../../../components/common/option";
import siteService from "./services/sitessService";
import Loading from "../../../components/common/Loading";
interface SiteDetailsState extends FormState {
interface SiteDetailsProps {
editMode?: boolean;
}
const SiteDetails: React.FC<SiteDetailsProps> = ({ editMode = false }) => {
const { organisationId, siteId } = useParams<{
organisationId: string;
siteId?: string;
}>();
const labelName = "Name";
const labelAddress = "Address";
const labelStatus = "Status";
const labelApply = "Save";
const labelSave = "Save and close";
const form = useForm({
loaded: false,
data: {
name: string;
address: string;
status: string;
};
redirect: string;
organisationStatusOptions: Option[];
}
name: "",
address: "",
status: "Active",
},
errors: {},
redirect: "",
});
class LocSiteDetails extends Form<any, any, SiteDetailsState> {
state: SiteDetailsState = {
loaded : false,
data: {
name: "",
address: "",
status: "Active"
},
errors: {},
redirect: "",
organisationStatusOptions: [
{ _id: "Active", name: "Active" },
{ _id: "Pending", name: "Pending" },
{ _id: "Blocked", name: "Blocked" },
]
};
form.schema = {
name: Joi.string().required().max(450).label(labelName),
address: Joi.string().required().max(450).label(labelAddress),
status: Joi.string().required().max(450).label(labelStatus),
};
labelName = "Name";
labelAddress = "Address";
labelStatus = "Status";
labelApply = "Save";
labelSave = "Save and close";
schema = {
name: Joi.string().required().max(450).label(this.labelName),
address:Joi.string().required().max(450).label(this.labelAddress),
status:Joi.string().required().max(450).label(this.labelAddress)
};
doSubmit = async (buttonName : string) => {
useEffect(() => {
const loadData = async () => {
if (siteId !== undefined) {
try {
const { name, address, status } = this.state.data;
const { organisationId } = this.props.router.params;
var organisationGeneralIdRef = MakeGeneralIdRef(organisationId);
if (this.isEditMode()) {
const { siteId } = this.props.router.params;
var siteGeneralIdRef = MakeGeneralIdRef(siteId);
const resposne = await siteService.putSite(siteGeneralIdRef, name, address, status, organisationGeneralIdRef);
if (resposne) {
toast.info("Site edited");
}
} else {
const response = await siteService.postSite(name, address, status, organisationGeneralIdRef);
if (response) {
toast.info("New site added");
}
}
if (buttonName === this.labelSave)
this.setState({ redirect: "/site/" + organisationId });
}
catch(ex: any) {
this.handleGeneralError(ex);
const loadedData = await siteService.getSite(BigInt(siteId));
if (loadedData) {
const newData = { ...form.state.data };
newData.name = loadedData.name;
newData.address = loadedData.address;
newData.status = loadedData.status;
form.setState({ loaded: true, data: newData });
} else {
form.setState({ loaded: false });
}
} catch (ex: any) {
form.handleGeneralError(ex);
}
}
if (!editMode) {
form.setState({ loaded: true });
}
};
isEditMode = () => {
const { editMode } = this.props;
return editMode;
};
loadData();
}, [siteId, editMode]); // eslint-disable-line react-hooks/exhaustive-deps
componentDidMount = async () => {
const { siteId } = this.props.router.params;
const doSubmit = async (buttonName: string) => {
try {
const { name, address, status } = form.state.data;
const nameStr = typeof name === "string" ? name : "";
const addressStr = typeof address === "string" ? address : "";
const statusStr = typeof status === "string" ? status : "";
const organisationGeneralIdRef = MakeGeneralIdRef(
organisationId ? BigInt(organisationId) : undefined,
);
if (siteId !== undefined) {
try
{
const loadedData = await siteService.getSite(siteId);
const { data } = this.state;
if (loadedData) {
data.name = loadedData.name;
data.address = loadedData.address;
data.status = loadedData.status;
this.setState({ loaded: true, data });
}
else {
this.setState({ loaded: false });
}
}
catch(ex: any) {
this.handleGeneralError(ex);
}
}
if (!this.isEditMode())
this.setState({loaded:true});
};
render() {
const { loaded, redirect, organisationStatusOptions } = this.state;
if (redirect !== "") return <Navigate to={redirect} />;
let mode = "Add";
if (this.isEditMode()) mode = "Edit";
return (
<Loading loaded={loaded}>
<h1>{mode} Site</h1>
<form onSubmit={this.handleSubmit}>
{this.renderError("_general")}
{this.renderInput("name", this.labelName, InputType.text)}
{this.renderInput("address", this.labelAddress, InputType.text)}
{this.renderSelect("status", this.labelStatus, organisationStatusOptions)}
{this.isEditMode() && this.renderButton(this.labelApply)}
{this.renderButton(this.labelSave)}
</form>
</Loading>
if (editMode) {
const siteGeneralIdRef = MakeGeneralIdRef(
siteId ? BigInt(siteId) : undefined,
);
}
}
const response = await siteService.putSite(
siteGeneralIdRef,
nameStr,
addressStr,
statusStr,
organisationGeneralIdRef,
);
if (response) {
toast.info("Site edited");
}
} else {
const response = await siteService.postSite(
nameStr,
addressStr,
statusStr,
organisationGeneralIdRef,
);
if (response) {
toast.info("New site added");
}
}
const SiteDetails = withRouter(LocSiteDetails);
if (buttonName === "save") {
form.setState({ redirect: "/site/" + organisationId });
}
} catch (ex: any) {
form.handleGeneralError(ex);
}
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
form.handleSubmit(e, doSubmit);
};
const { loaded, redirect } = form.state;
if (redirect) return <Navigate to={redirect} />;
const organisationStatusOptions: Option[] = [
{ _id: "Active", name: "Active" },
{ _id: "Pending", name: "Pending" },
{ _id: "Blocked", name: "Blocked" },
];
const mode = editMode ? "Edit" : "Add";
return (
<Loading loaded={loaded}>
<h1>{mode} Site</h1>
<form onSubmit={handleSubmit}>
{renderError("_general", form.state.errors)}
{renderInput(
"name",
labelName,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInput(
"address",
labelAddress,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderSelect(
"status",
labelStatus,
form.state.data,
form.state.errors,
organisationStatusOptions,
form.handleSelectChange,
)}
{editMode && renderButton(labelApply, form.state.errors, "save")}
{renderButton(labelSave, form.state.errors, "save")}
</form>
</Loading>
);
};
export default SiteDetails;

View File

@ -71,8 +71,28 @@ const Sites: React.FC = () => {
);
useEffect(() => {
void changePage(initialPagedData.page, initialPagedData.pageSize);
}, [changePage, organisationId]);
const loadInitial = async () => {
const nextFilters = updateFiltersWithOrganisationId(
new Map<string, string>(),
);
const pagedDataResult = await siteService.getSites(
initialPagedData.page,
initialPagedData.pageSize,
"name",
true,
nextFilters,
);
if (pagedDataResult) {
setLoaded(true);
setFilters(nextFilters);
setPagedData(pagedDataResult);
} else {
setLoaded(false);
}
};
void loadInitial();
}, [updateFiltersWithOrganisationId]); // eslint-disable-line react-hooks/exhaustive-deps
const onSort = useCallback(
async (nextSortColumn: Column<ReadSite>) => {
@ -94,7 +114,8 @@ const Sites: React.FC = () => {
setLoaded(false);
}
},
[filters, pagedData, updateFiltersWithOrganisationId],
// eslint-disable-next-line react-hooks/exhaustive-deps
[filters, updateFiltersWithOrganisationId],
);
const onSearch = useCallback(
@ -118,9 +139,9 @@ const Sites: React.FC = () => {
setLoaded(false);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[
filters,
pagedData,
sortColumn.key,
sortColumn.order,
updateFiltersWithOrganisationId,
@ -132,7 +153,7 @@ const Sites: React.FC = () => {
await siteService.deleteSite(item?.id, item?.guid);
await changePage(pagedData.page, pagedData.pageSize);
},
[changePage, pagedData.page, pagedData.pageSize],
[changePage], // eslint-disable-line react-hooks/exhaustive-deps
);
const translatedSortColumn = useMemo(

View File

@ -68,8 +68,26 @@ const Specifications: React.FC = () => {
);
useEffect(() => {
void changePage(initialPagedData.page, initialPagedData.pageSize);
}, [changePage, siteId]);
const loadInitial = async () => {
const nextFilters = updateFiltersWithSiteId(new Map<string, string>());
const pagedDataResult = await specificationService.GetSSpecifications(
initialPagedData.page,
initialPagedData.pageSize,
"name",
true,
nextFilters,
);
if (pagedDataResult) {
setLoaded(true);
setFilters(nextFilters);
setPagedData(pagedDataResult);
} else {
setLoaded(false);
}
};
void loadInitial();
}, [updateFiltersWithSiteId]); // eslint-disable-line react-hooks/exhaustive-deps
const onSort = useCallback(
async (nextSortColumn: Column<ReadSpecification>) => {
@ -91,7 +109,8 @@ const Specifications: React.FC = () => {
setLoaded(false);
}
},
[filters, pagedData, updateFiltersWithSiteId],
// eslint-disable-next-line react-hooks/exhaustive-deps
[filters, updateFiltersWithSiteId],
);
const onSearch = useCallback(
@ -115,13 +134,8 @@ const Specifications: React.FC = () => {
setLoaded(false);
}
},
[
filters,
pagedData,
sortColumn.key,
sortColumn.order,
updateFiltersWithSiteId,
],
// eslint-disable-next-line react-hooks/exhaustive-deps
[filters, sortColumn.key, sortColumn.order, updateFiltersWithSiteId],
);
const onDelete = useCallback(
@ -134,7 +148,8 @@ const Specifications: React.FC = () => {
await changePage(pagedData.page, pagedData.pageSize);
}
},
[changePage, pagedData.page, pagedData.pageSize],
// eslint-disable-next-line react-hooks/exhaustive-deps
[changePage],
);
const translatedSortColumn = useMemo(

View File

@ -1,198 +1,292 @@
import Joi from "joi";
import React from "react";
import { Navigate } from "react-router-dom";
import React, { useEffect, useRef, useState, useCallback } from "react";
import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify";
import Form, { FormState } from "../../../components/common/Form";
import { useForm } from "../../../components/common/useForm";
import { InputType } from "../../../components/common/Input";
import Loading from "../../../components/common/Loading";
import TemplateFiller from "../../../components/common/TemplateFiller";
import { GeneralIdRef, MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
import withRouter from "../../../utils/withRouter";
import { CustomFieldValue, PrintSpecificationsGlossary } from "../glossary/services/glossaryService";
import {
CustomFieldValue,
PrintSpecificationsGlossary,
} from "../glossary/services/glossaryService";
import specificationService from "./services/specificationService";
import {
renderInput,
renderButton,
renderError,
renderGlossaryPicker,
} from "../../../components/common/formHelpers";
interface SpecificationsDetailsState extends FormState {
formTemplate?: GeneralIdRef;
interface SpecificationsDetailsProps {
editMode?: boolean;
}
const SpecificationsDetails: React.FC<SpecificationsDetailsProps> = ({
editMode = false,
}) => {
const { organisationId, siteId, specificationId } = useParams<{
organisationId: string;
siteId: string;
specificationId?: string;
}>();
const TemplateFillerRef = useRef<TemplateFiller>(null);
const labelName = "Name";
const labelPrintSpecification = "Print Specification";
const labelSigmaId = "SigmaId";
const labelApply = "Save";
const labelSave = "Save and close";
const form = useForm({
loaded: false,
data: {
name: string;
printSpecifications?: CustomFieldValue[];
formInstanceId?: GeneralIdRef;
sigmaId: bigint | null;
};
redirect: string;
hasErrors: boolean;
}
name: "",
sigmaId: null as bigint | null,
printSpecifications: [] as CustomFieldValue[],
formInstanceId: undefined,
},
errors: {},
redirect: "",
});
class LocSpecificationsDetails extends Form<any, any, SpecificationsDetailsState> {
private TemplateFiller: React.RefObject<TemplateFiller>;
const [formTemplate, setFormTemplate] = useState<GeneralIdRef | undefined>(
undefined,
);
const [hasErrors, setHasErrors] = useState(false);
constructor(props: any) {
super(props);
this.TemplateFiller = React.createRef<TemplateFiller>();
}
form.schema = {
name: Joi.string().required().max(450).label(labelName),
printSpecifications: Joi.optional(),
formInstanceId: Joi.optional(),
sigmaId: Joi.number().allow(null).label(labelSigmaId),
};
state: SpecificationsDetailsState = {
loaded: false,
formTemplate: undefined,
data: {
name: "",
sigmaId: null,
},
errors: {},
redirect: "",
hasErrors: true,
};
const isEditMode = () => editMode;
labelName = "Name";
labelPrintSpecification = "Print Specification";
labelStatus = "Status";
labelSigmaId = "SigmaId";
const loadFormTemplate = useCallback(
async (printSpecifications?: GeneralIdRef) => {
const { data } = form.state;
const printSpecArray =
(data.printSpecifications as CustomFieldValue[]) || [];
labelApply = "Save";
labelSave = "Save and close";
schema = {
name: Joi.string().required().max(450).label(this.labelName),
printSpecifications: Joi.optional(),
formInstanceId: Joi.optional(),
sigmaId: Joi.number().allow(null).label(this.labelSigmaId),
};
doSubmit = async (buttonName: string) => {
try {
const { name, formInstanceId, sigmaId } = this.state.data;
const { siteId, organisationId, specificationId } = this.props.router.params;
const siteIdGeneralIdRef = MakeGeneralIdRef(siteId);
const templateFiller = this.TemplateFiller.current!;
if (this.isEditMode()) {
await templateFiller.Save();
const specificationIdGeneralIdRef = MakeGeneralIdRef(specificationId);
const response = await specificationService.PutSpecification(specificationIdGeneralIdRef, siteIdGeneralIdRef, name, formInstanceId!, sigmaId);
if (response) {
toast.info("Specifications edited");
}
} else {
const formInstanceId = await templateFiller.Save();
if (!formInstanceId) {
toast.error("Failed to save form instance");
return;
}
const response = await specificationService.PostSpecification(siteIdGeneralIdRef, name, formInstanceId!, sigmaId);
if (response) {
toast.info("New Specifications added");
}
}
if (buttonName === this.labelSave) this.setState({ redirect: "/Specifications/" + organisationId + "/" + siteId });
} catch (ex: any) {
this.handleGeneralError(ex);
if (printSpecArray && printSpecArray.length > 0) {
if (
((printSpecArray[0] as CustomFieldValue).value as GeneralIdRef).id ===
BigInt(0)
) {
data.printSpecifications = undefined;
}
};
}
isEditMode = () => {
const { editMode } = this.props;
return editMode;
};
componentDidMount = async () => {
const { specificationId } = this.props.router.params;
if (specificationId !== undefined) {
try {
const loadedData = await specificationService.GetSSpecification(BigInt(specificationId));
const { data } = this.state;
if (loadedData) {
data.name = loadedData.name;
data.formInstanceId = loadedData.formInstanceId;
this.setState({ loaded: true, data });
} else {
this.setState({ loaded: false });
}
} catch (ex: any) {
this.handleGeneralError(ex);
}
}
if (!this.isEditMode()) this.setState({ loaded: true });
};
componentDidUpdate(prevProps: Readonly<any>, prevState: Readonly<SpecificationsDetailsState>, snapshot?: any): void {
let { printSpecifications } = this.state.data;
if (printSpecifications !== prevState.data.printSpecifications) {
if (printSpecifications && printSpecifications.length > 0) {
this.LoadFormTemplate(printSpecifications[0].value as GeneralIdRef);
}
}
}
LoadFormTemplate = async (printSpecifications?: GeneralIdRef) => {
let { data } = this.state;
if (data.printSpecifications && data.printSpecifications.length > 0) {
if (((data.printSpecifications[0] as CustomFieldValue).value as GeneralIdRef).id === BigInt(0)) data.printSpecifications = undefined;
}
let formTemplate;
if (data.printSpecifications === undefined) {
formTemplate = undefined;
} else {
formTemplate = await specificationService.GetTemplateForPrintSpec(data.printSpecifications[0].value as GeneralIdRef);
}
if (formTemplate) {
this.setState({ loaded: true, data, formTemplate });
} else {
this.setState({ loaded: false });
}
};
handleValidationChanged = () => {
const templateFiller = this.TemplateFiller.current;
let templateErrors: boolean = false;
if (templateFiller) templateErrors = !templateFiller.hasValidationErrors();
this.setState({ hasErrors: templateErrors });
};
render() {
const { loaded, redirect, formTemplate, data, hasErrors } = this.state;
if (redirect !== "") return <Navigate to={redirect} />;
let mode = "Add";
const isEditMode = this.isEditMode();
if (isEditMode) mode = "Edit";
return (
<Loading loaded={loaded}>
<h1>{mode} Specifications</h1>
<form onSubmit={this.handleSubmit}>
{this.renderError("_general")}
{this.renderInput("name", this.labelName, InputType.text)}
{this.renderInput("sigmaId", this.labelSigmaId, InputType.number)}
{!this.isEditMode() && this.renderGlossaryPicker(true, "printSpecifications", this.labelPrintSpecification, 1, PrintSpecificationsGlossary)}
<TemplateFiller
templateId={formTemplate}
formInstanceId={data.formInstanceId}
ref={this.TemplateFiller}
onValidationChanged={this.handleValidationChanged}
/>
<br />
{isEditMode && this.renderButton(this.labelApply, undefined, undefined, undefined, hasErrors)}
{this.renderButton(this.labelSave, undefined, undefined, undefined, hasErrors)}
</form>
</Loading>
let template;
if (!printSpecArray || printSpecArray.length === 0) {
template = undefined;
} else {
template = await specificationService.GetTemplateForPrintSpec(
(printSpecArray[0] as CustomFieldValue).value as GeneralIdRef,
);
}
}
}
if (template) {
setFormTemplate(template);
form.setState({ loaded: true });
} else {
form.setState({ loaded: false });
}
},
[form],
);
const SpecificationsDetails = withRouter(LocSpecificationsDetails);
const handleValidationChanged = useCallback(() => {
const templateFiller = TemplateFillerRef.current;
let templateErrors: boolean = false;
if (templateFiller) templateErrors = templateFiller.hasValidationErrors();
setHasErrors(templateErrors);
}, []);
useEffect(() => {
const loadData = async () => {
if (specificationId !== undefined) {
try {
const loadedData = await specificationService.GetSSpecification(
BigInt(specificationId),
);
if (loadedData) {
const newData = { ...form.state.data };
newData.name = loadedData.name;
newData.formInstanceId = loadedData.formInstanceId;
form.setState({ loaded: true, data: newData });
} else {
form.setState({ loaded: false });
}
} catch (ex: any) {
form.handleGeneralError(ex);
}
}
if (!editMode) {
form.setState({ loaded: true });
}
};
loadData();
}, [specificationId, editMode]); // eslint-disable-line react-hooks/exhaustive-deps
useEffect(() => {
const { printSpecifications } = form.state.data;
const printSpecArray = (printSpecifications as CustomFieldValue[]) || [];
if (printSpecArray && printSpecArray.length > 0) {
loadFormTemplate(
(printSpecArray[0] as CustomFieldValue).value as GeneralIdRef,
);
}
}, [form.state.data.printSpecifications, loadFormTemplate, form]);
const doSubmit = async (buttonName: string) => {
try {
const { name, formInstanceId, sigmaId } = form.state.data;
const nameStr = typeof name === "string" ? name : "";
const sigmaIdValue =
sigmaId === null || sigmaId === undefined || sigmaId === ""
? null
: typeof sigmaId === "bigint"
? sigmaId
: BigInt(sigmaId as string | number);
const siteIdGeneralIdRef = MakeGeneralIdRef(
siteId ? BigInt(siteId) : undefined,
);
const templateFiller = TemplateFillerRef.current!;
if (isEditMode()) {
await templateFiller.Save();
const specificationIdGeneralIdRef = MakeGeneralIdRef(
specificationId ? BigInt(specificationId) : undefined,
);
const response = await specificationService.PutSpecification(
specificationIdGeneralIdRef,
siteIdGeneralIdRef,
nameStr,
formInstanceId as GeneralIdRef,
sigmaIdValue,
);
if (response) {
toast.info("Specifications edited");
}
} else {
const newFormInstanceId = await templateFiller.Save();
if (!newFormInstanceId) {
toast.error("Failed to save form instance");
return;
}
const response = await specificationService.PostSpecification(
siteIdGeneralIdRef,
nameStr,
newFormInstanceId!,
sigmaIdValue,
);
if (response) {
toast.info("New Specifications added");
}
}
if (buttonName === "save") {
form.setState({
redirect: "/Specifications/" + organisationId + "/" + siteId,
});
}
} catch (ex: any) {
form.handleGeneralError(ex);
}
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
form.handleSubmit(e, doSubmit);
};
const { loaded, redirect } = form.state;
if (redirect && redirect !== "") return <Navigate to={redirect} />;
const mode = isEditMode() ? "Edit" : "Add";
return (
<Loading loaded={loaded}>
<h1>{mode} Specifications</h1>
<form onSubmit={handleSubmit}>
{renderError("_general", form.state.errors)}
{renderInput(
"name",
labelName,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInput(
"sigmaId",
labelSigmaId,
form.state.data,
form.state.errors,
InputType.number,
false,
"",
"",
0,
false,
undefined,
form.handleChange,
)}
{!isEditMode() &&
renderGlossaryPicker(
true,
"printSpecifications",
labelPrintSpecification,
form.state.data,
form.state.errors,
1,
PrintSpecificationsGlossary,
form.handleGlossaryPickerChange,
)}
<TemplateFiller
templateId={formTemplate}
formInstanceId={form.state.data.formInstanceId}
ref={TemplateFillerRef}
onValidationChanged={handleValidationChanged}
/>
<br />
{isEditMode() &&
renderButton(
labelApply,
form.state.errors,
"apply",
undefined,
undefined,
Object.keys(form.state.errors).length === 0 && !hasErrors,
undefined,
true,
)}
{renderButton(
labelSave,
form.state.errors,
"save",
undefined,
undefined,
Object.keys(form.state.errors).length === 0 && !hasErrors,
undefined,
true,
)}
</form>
</Loading>
);
};
export default SpecificationsDetails;

View File

@ -1,161 +1,291 @@
import Joi from "joi";
import React from "react";
import { Navigate } from "react-router-dom";
import React, { useEffect } from "react";
import { Navigate, useParams, useLocation } from "react-router-dom";
import { toast } from "react-toastify";
import Form from "../../../components/common/Form";
import { useForm } from "../../../components/common/useForm";
import { InputType } from "../../../components/common/Input";
import { FormState } from "../../../components/common/Form";
import withRouter from "../../../utils/withRouter";
import {
renderInput,
renderButton,
renderError,
} from "../../../components/common/formHelpers";
import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
import Loading from "../../../components/common/Loading";
import ssoManagerService from "./services/ssoManagerService";
interface SsoProviderDetailsState extends FormState {
interface SsoProviderDetailsProps {
editMode?: boolean;
}
const SsoProviderDetails: React.FC<SsoProviderDetailsProps> = ({
editMode = false,
}) => {
const { ssoProviderId } = useParams<{ ssoProviderId: string }>();
const location = useLocation();
const labelName = "Name";
const labelClientId = "Client Id";
const labelClientSecret = "Client Secret";
const labelValidIssuer = "Valid Issuer";
const labelAuthorizationEndpoint = "Authorisation Endpoint";
const labelTokenEndpoint = "Token Endpoint";
const labelIsPublic = "Is Public";
const labelApply = "Save";
const labelSave = "Save and close";
const form = useForm({
loaded: false,
data: {
name: string;
clientId: string,
clientSecret: string,
validIssuer: string,
authorizationEndpoint: string,
tokenEndpoint: string,
isPublic: boolean,
};
redirect: string;
}
name: "",
clientId: "",
clientSecret: "",
validIssuer: "",
authorizationEndpoint: "",
tokenEndpoint: "",
isPublic: true,
},
errors: {},
redirect: "",
});
class SsoProviderDetails extends Form<any, any, SsoProviderDetailsState> {
state: SsoProviderDetailsState = {
loaded : false,
data: {
name: "",
clientId: "",
clientSecret: "",
validIssuer: "",
authorizationEndpoint: "",
tokenEndpoint: "",
isPublic: true,
},
errors: {},
redirect: "",
};
form.schema = {
name: Joi.string().required().max(450).label(labelName),
clientId: Joi.string().required().max(450).label(labelClientId),
clientSecret: Joi.string().required().max(450).label(labelClientSecret),
validIssuer: Joi.string().required().max(450).label(labelValidIssuer),
authorizationEndpoint: Joi.string()
.required()
.max(450)
.label(labelAuthorizationEndpoint),
tokenEndpoint: Joi.string().required().max(450).label(labelTokenEndpoint),
isPublic: Joi.bool().required().label(labelIsPublic),
};
labelName = "Name";
labelClientId = "Client Id";
labelClientSecret = "Client Secret";
labelValidIssuer = "Valid Issuer";
labelAuthorizationEndpoint = "Authorisation Endpoint";
labelTokenEndpoint = "Token Endpoint";
labelIsPublic = "Is Public";
labelApply = "Save";
labelSave = "Save and close";
schema = {
name: Joi.string().required().max(450).label(this.labelName),
clientId: Joi.string().required().max(450).label(this.labelClientId),
clientSecret: Joi.string().required().max(450).label(this.labelClientSecret),
validIssuer: Joi.string().required().max(450).label(this.labelValidIssuer),
authorizationEndpoint: Joi.string().required().max(450).label(this.labelAuthorizationEndpoint),
tokenEndpoint: Joi.string().required().max(450).label(this.labelTokenEndpoint),
isPublic: Joi.bool().required().label(this.labelIsPublic),
};
doSubmit = async (buttonName : string) => {
useEffect(() => {
const loadData = async () => {
if (ssoProviderId !== undefined) {
try {
const { name, clientId, clientSecret, validIssuer, authorizationEndpoint, tokenEndpoint, isPublic } = this.state.data;
if (this.isEditMode()) {
const { ssoProviderId } = this.props.router.params;
var generalIdRef = MakeGeneralIdRef(ssoProviderId);
const response = await ssoManagerService.putSsoProvider(generalIdRef, name, clientId, clientSecret, validIssuer, authorizationEndpoint, tokenEndpoint, isPublic);
if (response) {
toast.info("Sso Provider edited");
}
} else {
const response = await ssoManagerService.postSsoProvider(name, clientId, clientSecret, validIssuer, authorizationEndpoint, tokenEndpoint, isPublic);
if (response) {
toast.info("New Sso Provider added");
}
}
if (buttonName === this.labelSave)
this.setState({ redirect: "/ssoManager" });
}
catch(ex: any) {
this.handleGeneralError(ex);
const loadedData = await ssoManagerService.getSsoProvider(
BigInt(ssoProviderId),
);
if (loadedData) {
const newData = { ...form.state.data };
newData.name = loadedData.name;
newData.clientId = loadedData.clientId;
newData.clientSecret = loadedData.clientSecret;
newData.validIssuer = loadedData.validIssuer;
newData.authorizationEndpoint = loadedData.authorizationEndpoint;
newData.tokenEndpoint = loadedData.tokenEndpoint;
newData.isPublic = loadedData.isPublic;
form.setState({ loaded: true, data: newData });
} else {
form.setState({ loaded: false });
}
} catch (ex: any) {
form.handleGeneralError(ex);
}
}
if (!editMode) {
form.setState({ loaded: true });
}
};
isEditMode = () => {
const { editMode } = this.props;
return editMode;
};
loadData();
}, [ssoProviderId, editMode]); // eslint-disable-line react-hooks/exhaustive-deps
componentDidMount = async () => {
const { ssoProviderId } = this.props.router.params;
const doSubmit = async (buttonName: string) => {
try {
const {
name,
clientId,
clientSecret,
validIssuer,
authorizationEndpoint,
tokenEndpoint,
isPublic,
} = form.state.data;
const nameStr = typeof name === "string" ? name : "";
const clientIdStr = typeof clientId === "string" ? clientId : "";
const clientSecretStr =
typeof clientSecret === "string" ? clientSecret : "";
const validIssuerStr = typeof validIssuer === "string" ? validIssuer : "";
const authorizationEndpointStr =
typeof authorizationEndpoint === "string" ? authorizationEndpoint : "";
const tokenEndpointStr =
typeof tokenEndpoint === "string" ? tokenEndpoint : "";
const isPublicValue = Boolean(isPublic);
if (ssoProviderId !== undefined) {
try {
const loadedData = await ssoManagerService.getSsoProvider(ssoProviderId);
if (loadedData) {
const { data } = this.state;
data.name = loadedData.name;
data.clientId = loadedData.clientId;
data.clientSecret = loadedData.clientSecret;
data.validIssuer = loadedData.validIssuer;
data.authorizationEndpoint = loadedData.authorizationEndpoint;
data.tokenEndpoint = loadedData.tokenEndpoint;
data.isPublic = loadedData.isPublic
this.setState({ loaded: true, data });
}
else {
this.setState({ loaded: false });
}
}
catch(ex: any) {
this.handleGeneralError(ex);
}
}
if (!this.isEditMode())
this.setState({ loaded: true } );
};
render() {
const { loaded, redirect } = this.state;
if (redirect !== "") return <Navigate to={redirect} />;
let mode = "Add";
if (this.isEditMode()) mode = "Edit";
let redirectUrl = window.location.href.slice(0,window.location.href.length - this.props.router.location.pathname.length) + "/account/auth/" + this.props.router.params.ssoProviderId;
return (
<Loading loaded={loaded}>
<h1>{mode} Sso Provider</h1>
<form onSubmit={this.handleSubmit}>
{this.renderError("_general")}
{this.renderInput("name", this.labelName, InputType.text)}
{this.renderInput("clientId", this.labelClientId, InputType.text)}
{this.renderInput("clientSecret", this.labelClientSecret, InputType.text)}
{this.renderInput("validIssuer", this.labelValidIssuer, InputType.text)}
{this.renderInput("authorizationEndpoint", this.labelAuthorizationEndpoint, InputType.text)}
{this.renderInput("tokenEndpoint", this.labelTokenEndpoint, InputType.text)}
<div className="allignedCheckBox">{this.renderInput("isPublic", this.labelIsPublic, InputType.checkbox)}</div>
{this.isEditMode() && <div>Redirect URL: {redirectUrl}</div>}
{this.isEditMode() && this.renderButton(this.labelApply)}
{this.renderButton(this.labelSave)}
</form>
</Loading>
if (editMode) {
const generalIdRef = MakeGeneralIdRef(
ssoProviderId ? BigInt(ssoProviderId) : undefined,
);
const response = await ssoManagerService.putSsoProvider(
generalIdRef,
nameStr,
clientIdStr,
clientSecretStr,
validIssuerStr,
authorizationEndpointStr,
tokenEndpointStr,
isPublicValue,
);
if (response) {
toast.info("Sso Provider edited");
}
} else {
const response = await ssoManagerService.postSsoProvider(
nameStr,
clientIdStr,
clientSecretStr,
validIssuerStr,
authorizationEndpointStr,
tokenEndpointStr,
isPublicValue,
);
if (response) {
toast.info("New Sso Provider added");
}
}
if (buttonName === "save") {
form.setState({ redirect: "/ssoManager" });
}
} catch (ex: any) {
form.handleGeneralError(ex);
}
}
};
const HOCSsoProviderDetails = withRouter(SsoProviderDetails);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
form.handleSubmit(e, doSubmit);
};
export default HOCSsoProviderDetails;
const { loaded, redirect } = form.state;
if (redirect) return <Navigate to={redirect} />;
const mode = editMode ? "Edit" : "Add";
const redirectUrl =
window.location.href.slice(
0,
window.location.href.length - location.pathname.length,
) +
"/account/auth/" +
ssoProviderId;
return (
<Loading loaded={loaded}>
<h1>{mode} Sso Provider</h1>
<form onSubmit={handleSubmit}>
{renderError("_general", form.state.errors)}
{renderInput(
"name",
labelName,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInput(
"clientId",
labelClientId,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInput(
"clientSecret",
labelClientSecret,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInput(
"validIssuer",
labelValidIssuer,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInput(
"authorizationEndpoint",
labelAuthorizationEndpoint,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInput(
"tokenEndpoint",
labelTokenEndpoint,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
<div className="allignedCheckBox">
{renderInput(
"isPublic",
labelIsPublic,
form.state.data,
form.state.errors,
InputType.checkbox,
false,
"",
"",
0,
false,
undefined,
form.handleChange,
)}
</div>
{editMode && <div>Redirect URL: {redirectUrl}</div>}
{editMode && renderButton(labelApply, form.state.errors, "save")}
{renderButton(labelSave, form.state.errors, "save")}
</form>
</Loading>
);
};
export default SsoProviderDetails;

View File

@ -98,8 +98,24 @@ const SsoManager: React.FC = () => {
};
useEffect(() => {
changePage(pagedData.page, pagedData.pageSize);
}, [changePage, pagedData.page, pagedData.pageSize]);
const loadInitial = async () => {
const data = await ssoManagerService.getSsoProviders(
1,
10,
"name",
true,
filters,
);
if (data) {
setLoaded(true);
setPagedData(data);
} else {
setLoaded(false);
}
};
void loadInitial();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (
<Loading loaded={loaded}>

View File

@ -1,144 +1,238 @@
import Joi from "joi";
import React from "react";
import { Navigate } from "react-router-dom";
import React, { useEffect } from "react";
import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify";
import Form, { FormState } from "../../../../components/common/Form";
import { useForm } from "../../../../components/common/useForm";
import { InputType } from "../../../../components/common/Input";
import { GeneralIdRef, MakeGeneralIdRef } from "../../../../utils/GeneralIdRef";
import withRouter, { RouterProps } from "../../../../utils/withRouter";
import authentication from "../../../frame/services/authenticationService";
import userService from "../services/usersService";
import Loading from "../../../../components/common/Loading";
import { CustomFieldValue } from "../../glossary/services/glossaryService";
import {
renderInput,
renderButton,
renderError,
renderDomainPicker,
} from "../../../../components/common/formHelpers";
interface GeneralTabProps extends RouterProps {
isEditMode: boolean;
interface GeneralTabProps {
isEditMode: boolean;
}
interface GeneralTabState extends FormState {
const GeneralTab: React.FC<GeneralTabProps> = ({ isEditMode }) => {
const { userId } = useParams<{ userId: string }>();
const labelFirstName = "First name";
const labelMiddleNames = "Middle names";
const labelLastName = "Last name";
const labelEmail = "Mail";
const labelDomain = "Domain";
const labelApply = "Save";
const labelSave = "Save and close";
const form = useForm({
loaded: false,
data: {
firstName: string;
lastName: string;
middleNames: string;
email: string;
domain: CustomFieldValue[];
};
redirect: string;
}
firstName: "",
lastName: "",
middleNames: "",
email: "",
domain: [] as CustomFieldValue[],
},
errors: {},
redirect: "",
});
class LocGeneralTab extends Form<GeneralTabProps, any, GeneralTabState> {
state: GeneralTabState = {
loaded: false,
data: {
firstName: "",
lastName: "",
middleNames: "",
email: "",
domain: [],
},
errors: {},
redirect: "",
};
form.schema = {
firstName: Joi.string().required().max(450).label(labelFirstName),
middleNames: Joi.string().allow("").required().label(labelMiddleNames),
lastName: Joi.string().required().label(labelLastName),
email: Joi.string()
.required()
.email({ tlds: { allow: false } })
.label(labelEmail),
domain: Joi.optional(),
};
labelFirstName = "First name";
labelMiddleNames = "Middle names";
labelLastName = "Last name";
labelEmail = "Mail";
labelDomain = "Domain";
useEffect(() => {
const loadData = async () => {
const newData = { ...form.state.data };
labelApply = "Save";
labelSave = "Save and close";
schema = {
firstName: Joi.string().required().max(450).label(this.labelFirstName),
middleNames: Joi.string().allow("").required().label(this.labelMiddleNames),
lastName: Joi.string().required().label(this.labelLastName),
email: Joi.string()
.required()
.email({ tlds: { allow: false } })
.label(this.labelEmail),
domain: Joi.optional(),
};
doSubmit = async (buttonName: string) => {
if (userId !== undefined) {
try {
const { isEditMode } = this.props;
const { firstName, middleNames, lastName, email, domain } = this.state.data;
if (isEditMode) {
const { userId } = this.props.router.params;
var generalIdRef = MakeGeneralIdRef(userId);
const response = await userService.putUser(generalIdRef, firstName, middleNames, lastName, email, domain[0]?.value as GeneralIdRef);
if (response) {
toast.info("User edited");
}
} else {
const response = await userService.postUser(firstName, middleNames, lastName, email, domain[0]?.value as GeneralIdRef);
if (response) {
toast.info("New User added");
}
}
if (buttonName === this.labelSave) this.setState({ redirect: "/users" });
const loadedData = await userService.getUser(BigInt(userId));
if (loadedData) {
newData.firstName = loadedData.firstName;
newData.lastName = loadedData.lastName;
newData.middleNames = loadedData.middleNames;
newData.email = loadedData.email;
newData.domain = [{ value: loadedData.domain } as CustomFieldValue];
}
} catch (ex: any) {
this.handleGeneralError(ex);
form.handleGeneralError(ex);
}
} else {
const user = authentication.getCurrentUser();
newData.domain = [
{ value: MakeGeneralIdRef(user?.domainid) } as CustomFieldValue,
];
}
form.setState({ loaded: true, data: newData });
};
componentDidMount = async () => {
const { userId } = this.props.router.params;
loadData();
}, [userId]); // eslint-disable-line react-hooks/exhaustive-deps
const { data } = this.state;
const doSubmit = async (buttonName: string) => {
try {
const { firstName, middleNames, lastName, email, domain } =
form.state.data;
const firstNameStr = typeof firstName === "string" ? firstName : "";
const middleNamesStr = typeof middleNames === "string" ? middleNames : "";
const lastNameStr = typeof lastName === "string" ? lastName : "";
const emailStr = typeof email === "string" ? email : "";
const domainValues = (domain as CustomFieldValue[]) ?? [];
const domainValue = domainValues[0]?.value as GeneralIdRef | undefined;
if (userId !== undefined) {
try {
const loadedData = await userService.getUser(userId);
if (loadedData) {
data.firstName = loadedData.firstName;
data.lastName = loadedData.lastName;
data.middleNames = loadedData.middleNames;
data.email = loadedData.email;
data.domain = [{ value: loadedData.domain }];
}
} catch (ex: any) {
this.handleGeneralError(ex);
}
} else {
const user = authentication.getCurrentUser();
data.domain = [{ value: MakeGeneralIdRef(user?.domainid) }];
}
this.setState({ loaded: true, data });
};
render() {
const { loaded, redirect } = this.state;
if (redirect !== "") return <Navigate to={redirect} />;
const { isEditMode } = this.props;
return (
<Loading loaded={loaded}>
<form onSubmit={this.handleSubmit}>
{this.renderError("_general")}
{isEditMode && this.renderInput("email", this.labelEmail, InputType.text, true)}
{!isEditMode && this.renderInput("email", this.labelEmail)}
{this.renderInput("firstName", this.labelFirstName)}
{this.renderInput("middleNames", this.labelMiddleNames)}
{this.renderInput("lastName", this.labelLastName)}
{this.renderDomainPicker(true, "domain", this.labelDomain, 1, 1)}
{isEditMode && this.renderButton(this.labelApply)}
{this.renderButton(this.labelSave)}
</form>
</Loading>
if (isEditMode) {
const generalIdRef = MakeGeneralIdRef(
userId ? BigInt(userId) : undefined,
);
}
}
const response = await userService.putUser(
generalIdRef,
firstNameStr,
middleNamesStr,
lastNameStr,
emailStr,
domainValue,
);
if (response) {
toast.info("User edited");
}
} else {
const response = await userService.postUser(
firstNameStr,
middleNamesStr,
lastNameStr,
emailStr,
domainValue,
);
if (response) {
toast.info("New User added");
}
}
const GeneralTab = withRouter(LocGeneralTab);
if (buttonName === "save") {
form.setState({ redirect: "/users" });
}
} catch (ex: any) {
form.handleGeneralError(ex);
}
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
form.handleSubmit(e, doSubmit);
};
const { loaded, redirect } = form.state;
if (redirect) return <Navigate to={redirect} />;
return (
<Loading loaded={loaded}>
<form onSubmit={handleSubmit}>
{renderError("_general", form.state.errors)}
{isEditMode &&
renderInput(
"email",
labelEmail,
form.state.data,
form.state.errors,
InputType.text,
true,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{!isEditMode &&
renderInput(
"email",
labelEmail,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInput(
"firstName",
labelFirstName,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderInput(
"middleNames",
labelMiddleNames,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
false,
undefined,
form.handleChange,
)}
{renderInput(
"lastName",
labelLastName,
form.state.data,
form.state.errors,
InputType.text,
false,
"",
"",
0,
true,
undefined,
form.handleChange,
)}
{renderDomainPicker(
true,
"domain",
labelDomain,
form.state.data,
form.state.errors,
1,
undefined,
form.handleDomainPickerChange,
)}
{isEditMode && renderButton(labelApply, form.state.errors, "save")}
{renderButton(labelSave, form.state.errors, "save")}
</form>
</Loading>
);
};
export default GeneralTab;

View File

@ -102,8 +102,24 @@ const Users: React.FC = () => {
};
useEffect(() => {
changePage(pagedData.page, pagedData.pageSize);
}, [changePage, pagedData.page, pagedData.pageSize]);
const loadInitial = async () => {
const data = await userService.getUsers(
1,
10,
"displayName",
true,
filters,
);
if (data) {
setLoaded(true);
setPagedData(data);
} else {
setLoaded(false);
}
};
void loadInitial();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (
<Loading loaded={loaded}>

View File

@ -14,7 +14,8 @@
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve"
"jsx": "preserve",
"allowImportingTsExtensions": true
},
"include": ["src", "src/types", "i18next-parser.config.js"]
}