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 { .two-column-grid-1-1 {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
grid-gap: $gridGap; grid-gap: $gridGap;
} }
.two-column-grid-1-3 { .two-column-grid-1-3 {
display: grid; display: grid;
grid-template-columns: 1fr 3fr; grid-template-columns: 1fr 3fr;
grid-gap: $gridGap; grid-gap: $gridGap;
} }
.mail-types { .mail-types {
padding-left: 0rem; padding-left: 0rem;
list-style: none; list-style: none;
width: $mailtemplateNameListWidth; 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 { interface HorizontalTabsProps {
children: JSX.Element[]; children: JSX.Element[];
initialTab?: string;
} }
const HorizontalTabs: React.FC<HorizontalTabsProps> = ({ children }) => { const HorizontalTabs: React.FC<HorizontalTabsProps> = ({
children,
initialTab,
}) => {
const [activeTab, setActiveTab] = useState<string>(""); const [activeTab, setActiveTab] = useState<string>("");
// Set initial tab on mount // Set initial tab on mount
useEffect(() => { useEffect(() => {
if (children.length > 0) { 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) => { const onClickTabItem = useCallback((tab: string) => {
setActiveTab((prev) => (prev !== tab ? tab : prev)); 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 Joi from "joi";
import React from "react"; import React, { useEffect } from "react";
import { Navigate } from "react-router-dom"; import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import Form from "../../../components/common/Form";
import { InputType } from "../../../components/common/Input"; import { InputType } from "../../../components/common/Input";
import { FormState } from "../../../components/common/Form"; import { useForm } from "../../../components/common/useForm";
import withRouter from "../../../utils/withRouter"; import {
renderInput,
renderButton,
renderError,
renderSelect,
renderInputNumber,
renderInputTextarea,
renderGlossaryPicker,
renderSequencePicker,
} from "../../../components/common/formHelpers";
import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef"; import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
import customFieldsService, { import customFieldsService, {
numberParams, numberParams,
@ -19,25 +27,31 @@ import {
} from "../glossary/services/glossaryService"; } from "../glossary/services/glossaryService";
import Loading from "../../../components/common/Loading"; import Loading from "../../../components/common/Loading";
interface CustomFieldDetailsState extends FormState { interface CustomFieldDetailsProps {
data: { editMode?: boolean;
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;
} }
class CustomFieldDetails extends Form<any, any, CustomFieldDetailsState> { const CustomFieldDetails: React.FC<CustomFieldDetailsProps> = ({
state: CustomFieldDetailsState = { 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, loaded: false,
data: { data: {
name: "", name: "",
@ -54,29 +68,14 @@ class CustomFieldDetails extends Form<any, any, CustomFieldDetailsState> {
}, },
errors: {}, errors: {},
redirect: "", redirect: "",
}; });
labelName = "Name"; form.schema = {
labelFieldType = "Field Type"; name: Joi.string().required().max(450).label(labelName),
labelMultiLine = "Multi-line"; fieldType: Joi.string().required().label(labelFieldType),
labelDefaultValue = "Default Value"; multiLine: Joi.boolean().label(labelMultiLine),
labelMinValue = "Minimum Value"; minEntries: Joi.number().min(0).label(labelMinEntries),
labelMaxValue = "Maximum Value"; maxEntries: Joi.number().empty("").label(labelMaxEntries),
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),
refElementId: Joi.when("fieldType", { refElementId: Joi.when("fieldType", {
is: Joi.string().valid("Sequence"), is: Joi.string().valid("Sequence"),
then: Joi.object({ then: Joi.object({
@ -98,12 +97,12 @@ class CustomFieldDetails extends Form<any, any, CustomFieldDetailsState> {
) )
.required(), .required(),
}), }),
minValue: Joi.number().allow("").label(this.labelMinValue), minValue: Joi.number().allow("").label(labelMinValue),
maxValue: Joi.number().allow("").label(this.labelMaxValue), maxValue: Joi.number().allow("").label(labelMaxValue),
step: Joi.number().optional().allow("").min(0).label(this.labelStep), step: Joi.number().optional().allow("").min(0).label(labelStep),
required: Joi.boolean().label(this.labelRequired), required: Joi.boolean().label(labelRequired),
//defaultValue: Joi.string().allow("").label(this.labelDefaultValue) //defaultValue: Joi.string().allow("").label(labelDefaultValue)
defaultValue: Joi.when("fieldType", { defaultValue: Joi.when("fieldType", {
is: Joi.string().valid("Number"), is: Joi.string().valid("Number"),
@ -114,7 +113,7 @@ class CustomFieldDetails extends Form<any, any, CustomFieldDetailsState> {
.min(Joi.ref("minValue")) .min(Joi.ref("minValue"))
.message( .message(
'"Default Value" must be greater than or equal to "' + '"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")) .max(Joi.ref("maxValue"))
.message( .message(
'"Default Value" must be less than or equal to "' + '"Default Value" must be less than or equal to "' +
this.labelMaxValue + labelMaxValue +
'"', '"',
), ),
}) })
.allow(""), .allow(""),
otherwise: Joi.string().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 { 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 } = let { refElementId, defaultValue, minEntries, maxEntries, required } =
this.state.data; form.state.data;
let numberParams: numberParams | undefined = undefined; let numberParams: numberParams | undefined = undefined;
let textParams: textParams | undefined = undefined; let textParams: textParams | undefined = undefined;
let params; let params;
let refElementIdValue: GeneralIdRef | undefined; let refElementIdValue: GeneralIdRef | undefined;
switch (fieldType) { switch (fieldTypeStr) {
case "Sequence": case "Sequence":
minEntries = 1; minEntries = 1;
maxEntries = 1; maxEntries = 1;
@ -171,15 +243,31 @@ class CustomFieldDetails extends Form<any, any, CustomFieldDetailsState> {
case "Text": case "Text":
minEntries = 1; minEntries = 1;
maxEntries = 1; maxEntries = 1;
let { multiLine } = this.state.data; let { multiLine } = form.state.data;
textParams = { multiLine }; textParams = { multiLine: Boolean(multiLine) };
params = textParams; params = textParams;
refElementIdValue = undefined; refElementIdValue = undefined;
break; break;
case "Number": case "Number":
refElementIdValue = undefined; refElementIdValue = undefined;
let { minValue, maxValue, step } = this.state.data; let { minValue, maxValue, step } = form.state.data;
numberParams = { minValue, maxValue, step }; 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; params = numberParams;
minEntries = required ? 1 : 0; minEntries = required ? 1 : 0;
maxEntries = 1; maxEntries = 1;
@ -188,19 +276,30 @@ class CustomFieldDetails extends Form<any, any, CustomFieldDetailsState> {
refElementIdValue = undefined; refElementIdValue = undefined;
} }
const minEntriesValue =
typeof minEntries === "number"
? minEntries
: minEntries === undefined || minEntries === null || minEntries === ""
? 0
: Number(minEntries);
const cleanMaxEntries: Number | undefined = const cleanMaxEntries: Number | undefined =
maxEntries === "" ? undefined : Number(maxEntries); maxEntries === "" ? undefined : Number(maxEntries);
if (this.isEditMode()) { const defaultValueStr =
const { customFieldId } = this.props.router.params; 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( const response = await customFieldsService.putField(
generalIdRef, generalIdRef,
name, nameStr,
fieldType, fieldTypeStr,
defaultValue, defaultValueStr,
minEntries, minEntriesValue,
cleanMaxEntries, cleanMaxEntries,
refElementIdValue, refElementIdValue,
params, params,
@ -210,10 +309,10 @@ class CustomFieldDetails extends Form<any, any, CustomFieldDetailsState> {
} }
} else { } else {
const response = await customFieldsService.postField( const response = await customFieldsService.postField(
name, nameStr,
fieldType, fieldTypeStr,
defaultValue, defaultValueStr,
minEntries, minEntriesValue,
cleanMaxEntries, cleanMaxEntries,
refElementIdValue, refElementIdValue,
params, params,
@ -223,262 +322,324 @@ class CustomFieldDetails extends Form<any, any, CustomFieldDetailsState> {
} }
} }
if (buttonName === this.labelSave) if (buttonName === "save") form.setState({ redirect: "/customfields" });
this.setState({ redirect: "/customfields" });
} catch (ex: any) { } catch (ex: any) {
this.handleGeneralError(ex); form.handleGeneralError(ex);
} }
}; };
isEditMode = () => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
const { editMode } = this.props; form.handleSubmit(e, doSubmit);
return editMode;
}; };
componentDidMount = async () => { if (form.state.redirect) return <Navigate to={form.state.redirect} />;
const { customFieldId } = this.props.router.params;
if (customFieldId !== undefined) { const { fieldType } = form.state.data;
try { const fieldTypeValue = typeof fieldType === "string" ? fieldType : "";
const loadedData = await customFieldsService.getField(customFieldId);
const { data } = this.state; let mode = "Add";
if (loadedData) { if (isEditMode()) mode = "Edit";
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,
};
data.refElementId = [convertedRefElementId]; const fieldTypeOptions: Option[] = [
data.required = loadedData.minEntries > 0; { _id: "Text", name: "Text" },
break; { _id: "Number", name: "Number" },
case "Sequence": { _id: "Sequence", name: "Sequence" },
data.refElementId = loadedData.refElementId; { _id: "FormTemplate", name: "Form Template" },
break; { _id: "Glossary", name: "Glossary" },
case "Domain": { _id: "Domain", name: "Domain" },
data.required = loadedData.minEntries > 0; ];
break;
}
if (loadedData.parameters !== undefined) { switch (fieldTypeValue) {
switch (data.fieldType) { case "Sequence":
case "Number": labelRefElementId = "Sequence";
data.required = loadedData.minEntries > 0; break;
const parameters: numberParams = JSON.parse( case "FormTemplate":
loadedData.parameters, labelRefElementId = "Form";
); break;
data.minValue = parameters.minValue ?? undefined; case "Glossary":
data.maxValue = parameters.maxValue ?? undefined; labelRefElementId = "Glossary";
data.step = parameters.step ?? undefined; break;
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>
);
} }
}
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 React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useSearchParams } from "react-router-dom";
import HorizontalTabs from "../../../components/common/HorizionalTabs"; import HorizontalTabs from "../../../components/common/HorizionalTabs";
import Tab from "../../../components/common/Tab"; import Tab from "../../../components/common/Tab";
import authentication from "../../frame/services/authenticationService"; import authentication from "../../frame/services/authenticationService";
@ -13,11 +14,15 @@ interface DomainsDetailsProps {
const DomainsDetails: React.FC<DomainsDetailsProps> = ({ editMode }) => { const DomainsDetails: React.FC<DomainsDetailsProps> = ({ editMode }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [searchParams] = useSearchParams();
const canViewMailTemplates = authentication.hasAccess("ViewDomain"); const canViewMailTemplates = authentication.hasAccess("ViewDomain");
const canViewSecurityRoles = authentication.hasAccess("ViewRole"); const canViewSecurityRoles = authentication.hasAccess("ViewRole");
const heading = editMode ? t("EditDomain") : t("AddDomain"); 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[] = []; const tabs: JSX.Element[] = [];
@ -38,7 +43,7 @@ const DomainsDetails: React.FC<DomainsDetailsProps> = ({ editMode }) => {
if (canViewSecurityRoles) { if (canViewSecurityRoles) {
tabs.push( tabs.push(
<Tab key={3} label={t("SecurityRoles")}> <Tab key={3} label={t("SecurityRoles")}>
<SecurityRolesTab /> <SecurityRolesTab initialRoleId={roleId} initialInnerTab={innerTab} />
</Tab>, </Tab>,
); );
} }
@ -47,7 +52,7 @@ const DomainsDetails: React.FC<DomainsDetailsProps> = ({ editMode }) => {
return ( return (
<div> <div>
<h1>{heading}</h1> <h1>{heading}</h1>
<HorizontalTabs>{tabs}</HorizontalTabs> <HorizontalTabs initialTab={initialTab}>{tabs}</HorizontalTabs>
</div> </div>
); );
}; };

View File

@ -1,82 +1,89 @@
import Joi from "joi"; import Joi from "joi";
import React from "react"; import React from "react";
import { Navigate } from "react-router-dom"; import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import Form, { FormState } from "../../../../components/common/Form"; import { useForm } from "../../../../components/common/useForm";
import { GeneralIdRef, MakeGeneralIdRef } from "../../../../utils/GeneralIdRef"; import { MakeGeneralIdRef } from "../../../../utils/GeneralIdRef";
import withRouter, { RouterProps } from "../../../../utils/withRouter";
import roleService from "../serrvices/rolesService"; import roleService from "../serrvices/rolesService";
import Loading from "../../../../components/common/Loading"; import Loading from "../../../../components/common/Loading";
import {
renderError,
renderButton,
renderUserPicker,
} from "../../../../components/common/formHelpers";
interface LocAddUserToRoleProps extends RouterProps { interface LocAddUserToRoleProps {
isEditMode : boolean; 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: { data: {
userId?: GeneralIdRef; userId: undefined,
}; },
redirect: string; errors: {},
} redirect: "",
});
class LocAddUserToRole extends Form<LocAddUserToRoleProps, any, LocAddUserToRoleState> { form.schema = {
state: LocAddUserToRoleState = { userId: Joi.any().required().label(labelUserId),
loaded: true, };
data: {
//userId: null;
},
errors: {},
redirect: "",
};
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"; if (buttonName === labelSave)
labelSave = "Save and close"; form.setState({
redirect: `/domains/edit/${domainId}?tab=SecurityRoles&roleId=${roleId}&innerTab=Users`,
});
schema = { }
userId: Joi.any().required().label(this.labelUserId), } catch (ex: any) {
}; form.handleGeneralError(ex);
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>
);
} }
} };
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; export default AddUserToRole;

View File

@ -1,130 +1,172 @@
import Joi from "joi"; import Joi from "joi";
import React from "react"; import React, { useEffect } from "react";
import { Navigate } from "react-router-dom"; import { Navigate } from "react-router-dom";
import { toast } from "react-toastify"; 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 { InputType } from "../../../../components/common/Input";
import { MakeGeneralIdRef } from "../../../../utils/GeneralIdRef"; import { MakeGeneralIdRef } from "../../../../utils/GeneralIdRef";
import withRouter from "../../../../utils/withRouter";
import mailTemplatesService from "../serrvices/mailTemplatesService"; import mailTemplatesService from "../serrvices/mailTemplatesService";
import Loading from "../../../../components/common/Loading"; import {
renderError,
renderButton,
renderInput,
renderTemplateEditor,
} from "../../../../components/common/formHelpers";
interface EmailTemplateEditorProps{ interface EmailTemplateEditorProps {
domainId : number, domainId?: string;
currentMailType : 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: { data: {
currentMailType? : string currentMailType: undefined,
isOverridden : boolean isOverridden: false,
subject: string; subject: "",
definition: string; definition: "",
}; },
redirect: string; errors: {},
} redirect: "",
});
class EmailTemplateEditor extends Form<EmailTemplateEditorProps, any, EmailTemplateEditorState> { form.schema = {
state: EmailTemplateEditorState = { currentMailType: Joi.optional(),
loaded : false, isOverridden: Joi.optional(),
data: { subject: Joi.string().required().max(450).label(labelName),
currentMailType : undefined, definition: Joi.string().required().label(labelDefinition),
isOverridden : false, };
useEffect(() => {
const loadTemplate = async () => {
try {
if (!domainId) return;
// Reset form state while loading
form.setState({
loaded: false,
data: {
currentMailType: undefined,
isOverridden: false,
subject: "", subject: "",
definition: "", definition: "",
}, },
errors: {}, });
redirect: "",
};
labelName = "Subject"; const domainIdValue = BigInt(domainId);
labelDefinition = "Definition"; var mailTemplate = await mailTemplatesService.getTemplate(
MakeGeneralIdRef(domainIdValue),
labelApply = "Save"; currentMailType,
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>
); );
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 Joi from "joi";
import React from "react"; import React, { useEffect } from "react";
import { Navigate } from "react-router-dom"; import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify"; 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 { InputType } from "../../../../components/common/Input";
import { GeneralIdRef, MakeGeneralIdRef } from "../../../../utils/GeneralIdRef"; import { MakeGeneralIdRef } from "../../../../utils/GeneralIdRef";
import withRouter, { RouterProps } from "../../../../utils/withRouter";
import domainsService from "../serrvices/domainsService"; import domainsService from "../serrvices/domainsService";
import Loading from "../../../../components/common/Loading"; import Loading from "../../../../components/common/Loading";
import {
renderError,
renderButton,
renderInput,
renderSsoProviderPicker,
} from "../../../../components/common/formHelpers";
interface GeneralTabProps extends RouterProps { interface GeneralTabProps {
isEditMode: boolean; 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: { data: {
name: string; name: "",
ssoProviderId: GeneralIdRef | null; ssoProviderId: null,
sunriseHostName: string; sunriseHostName: "",
sunriseAppId: string; sunriseAppId: "",
sunriseCategoryId: string; sunriseCategoryId: "",
sigmaId: bigint | null; sigmaId: null,
}; },
redirect: string; errors: {},
} redirect: "",
});
class LocGeneralTab extends Form<GeneralTabProps, any, GeneralTabState> { form.schema = {
state: GeneralTabState = { name: Joi.string().required().max(450).label(labelName),
loaded: false, ssoProviderId: Joi.object().allow(null).optional().label(labelSsoProvider),
data: { sunriseHostName: Joi.string()
name: "", .required()
ssoProviderId: null, .allow("")
sunriseHostName: "", .max(16)
sunriseAppId: "", .label(labelSunriseHostName),
sunriseCategoryId: "", sunriseAppId: Joi.string().required().allow("").label(labelSunriseAppId),
sigmaId: null, sunriseCategoryId: Joi.string()
}, .required()
errors: {}, .allow("")
redirect: "", .label(labelSunriseCategoryId),
}; sigmaId: Joi.number().allow(null).optional().label(labelSigmaId),
};
labelName = "Name"; const doSubmit = async (buttonName: string) => {
labelSsoProvider = "Sso Provider"; try {
labelSunriseHostName = "e-flow hostname"; const {
labelSunriseAppId = "e-flow AppId"; name,
labelSunriseCategoryId = "e-flow CategoryId"; ssoProviderId,
labelSigmaId = "Sigma Id"; 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"; if (isEditMode) {
labelSave = "Save and close"; var generalIdRef = MakeGeneralIdRef(BigInt(domainId!));
const response = await domainsService.putDomain(
schema = { generalIdRef,
name: Joi.string().required().max(450).label(this.labelName), nameStr,
ssoProviderId: Joi.object().allow(null).optional().label(this.labelSsoProvider), sunriseHostNameStr,
sunriseHostName: Joi.string().required().allow("").max(16).label(this.labelSunriseHostName), sunriseAppIdStr,
sunriseAppId: Joi.string().required().allow("").label(this.labelSunriseAppId), sunriseCategoryIdStr,
sunriseCategoryId: Joi.string().required().allow("").label(this.labelSunriseCategoryId), ssoProviderId as any,
sigmaId: Joi.number().allow(null).optional().label(this.labelSigmaId), sigmaIdValue,
};
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 (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; 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 { useParams } from "react-router-dom";
import mailTemplatesService from "../serrvices/mailTemplatesService"; import mailTemplatesService from "../serrvices/mailTemplatesService";
import HOCEmailTemplateEditor from "./EmailTemplateEditor"; import HOCEmailTemplateEditor from "./EmailTemplateEditor";
@ -10,7 +10,7 @@ interface MailType {
} }
const MailTemplatesTab: React.FC = () => { const MailTemplatesTab: React.FC = () => {
const [loaded, setLoaded] = useState(false); const [loaded, setLoaded] = useState(true);
const [currentMailType, setCurrentMailType] = useState(""); const [currentMailType, setCurrentMailType] = useState("");
const [types, setTypes] = useState<MailType[]>([]); const [types, setTypes] = useState<MailType[]>([]);
@ -22,21 +22,23 @@ const MailTemplatesTab: React.FC = () => {
setTypes(nextTypes); setTypes(nextTypes);
if (nextTypes.length > 0) { if (nextTypes.length > 0) {
setLoaded(true);
setCurrentMailType(nextTypes[0].mailType); setCurrentMailType(nextTypes[0].mailType);
} }
setLoaded(true);
} }
}; };
void loadTypes(); void loadTypes();
}, []); }, []);
const selectTemplate = (emailType: string) => { const selectTemplate = useCallback(
if (currentMailType !== emailType) { (emailType: string) => {
setLoaded(true); if (currentMailType !== emailType) {
setCurrentMailType(emailType); setCurrentMailType(emailType);
} }
}; },
[currentMailType],
);
const onClick = (e: React.MouseEvent<HTMLElement>) => { const onClick = (e: React.MouseEvent<HTMLElement>) => {
const value = (e.target as HTMLElement).getAttribute("value"); const value = (e.target as HTMLElement).getAttribute("value");
@ -54,7 +56,12 @@ const MailTemplatesTab: React.FC = () => {
<ul className="mail-types"> <ul className="mail-types">
{types.map((x) => { {types.map((x) => {
return ( 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} {x.description}
</li> </li>
); );

View File

@ -1,129 +1,134 @@
import Joi from "joi"; import Joi from "joi";
import React from "react"; import React, { useEffect } from "react";
import { Navigate } from "react-router-dom"; import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify"; 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 { InputType } from "../../../../components/common/Input";
import { MakeGeneralIdRef } from "../../../../utils/GeneralIdRef"; import { MakeGeneralIdRef } from "../../../../utils/GeneralIdRef";
import withRouter, { RouterProps } from "../../../../utils/withRouter";
import roleService from "../serrvices/rolesService"; import roleService from "../serrvices/rolesService";
import Loading from "../../../../components/common/Loading"; import Loading from "../../../../components/common/Loading";
import {
renderError,
renderButton,
renderInput,
} from "../../../../components/common/formHelpers";
interface RolesDetailsProps extends RouterProps { interface RolesDetailsProps {
isEditMode : boolean; 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: { data: {
name: string; name: "",
}; },
redirect: string; errors: {},
} redirect: "",
});
class LocRolesDetails extends Form<RolesDetailsProps, any, RolesDetailsState> { form.schema = {
state: RolesDetailsState = { name: Joi.string().required().max(450).label(labelName),
loaded: false, };
data: {
name: ""
},
errors: {},
redirect: "",
};
labelName = "Name"; const doSubmit = async (buttonName: string) => {
try {
const { name } = form.state.data;
const nameStr = typeof name === "string" ? name : "";
labelApply = "Save"; var domainIdGeneralIdRef = MakeGeneralIdRef(BigInt(domainId!));
labelSave = "Save and close";
schema = { if (isEditMode) {
name: Joi.string().required().max(450).label(this.labelName), var generalIdRef = MakeGeneralIdRef(BigInt(roleId!));
}; const response = await roleService.putRole(generalIdRef, nameStr);
if (response) {
doSubmit = async (buttonName : string) => { toast.info("Role edited");
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 });
} }
catch(ex: any) { } else {
this.handleGeneralError(ex); const response = await roleService.postRole(
} domainIdGeneralIdRef,
}; nameStr,
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>
); );
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; export default RolesDetails;

View File

@ -22,12 +22,14 @@ interface RolesEditorProps {
selectedRole?: GetRoleResponse; selectedRole?: GetRoleResponse;
onSelectRole?: (keyValue: any) => void; onSelectRole?: (keyValue: any) => void;
onUnselectRole?: () => void; onUnselectRole?: () => void;
initialRoleId?: string;
} }
const RolesEditor: React.FC<RolesEditorProps> = ({ const RolesEditor: React.FC<RolesEditorProps> = ({
selectedRole, selectedRole,
onSelectRole, onSelectRole,
onUnselectRole, onUnselectRole,
initialRoleId,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [loaded, setLoaded] = useState(false); const [loaded, setLoaded] = useState(false);
@ -74,7 +76,19 @@ const RolesEditor: React.FC<RolesEditorProps> = ({
}; };
void loadInitial(); 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 onSort = async (nextSortColumn: Column<GetRoleResponse>) => {
const { page, pageSize } = pagedData; const { page, pageSize } = pagedData;

View File

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

View File

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

View File

@ -94,8 +94,18 @@ const Forms: React.FC = () => {
}; };
useEffect(() => { useEffect(() => {
changePage(pagedData.page, pagedData.pageSize); const loadInitial = async () => {
}, [changePage, pagedData.page, pagedData.pageSize]); 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 ( return (
<Loading loaded={loaded}> <Loading loaded={loaded}>

View File

@ -1,141 +1,142 @@
import Joi from "joi"; import Joi from "joi";
import React from "react"; import React, { useEffect } from "react";
import { Navigate } from "react-router-dom"; import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import Form from "../../../components/common/Form"; import { useForm } from "../../../components/common/useForm";
import { InputType } from "../../../components/common/Input"; import { InputType } from "../../../components/common/Input";
import { FormState } from "../../../components/common/Form";
import withRouter from "../../../utils/withRouter";
import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef"; import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
import formsService from "./services/formsService"; import formsService from "./services/formsService";
import Loading from "../../../components/common/Loading"; import Loading from "../../../components/common/Loading";
// import Tab from "../../../components/common/Tab"; import {
// import HorizontalTabs from "../../../components/common/HorizionalTabs"; 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: { data: {
name: string; name: "",
definition: string; definition: "",
}; },
redirect: string; errors: {},
} redirect: "",
});
class FormsDetails extends Form<any, any, FormsDetailsState> { form.schema = {
state: FormsDetailsState = { name: Joi.string().required().max(450).label(labelName),
loaded: false, definition: Joi.string().required().label(labelDefinition),
data: { };
name: "",
definition: "",
},
errors: {},
redirect: "",
};
labelName = "Name"; useEffect(() => {
labelDefinition = "Definition"; const loadData = async () => {
if (formId !== undefined) {
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) => {
try { try {
const { name, definition } = this.state.data; const loadedData = await formsService.getForm(BigInt(formId));
if (this.isEditMode()) { if (loadedData) {
const { formId } = this.props.router.params; const newData = { ...form.state.data };
newData.name = loadedData.name;
newData.definition = loadedData.definition;
var generalIdRef = MakeGeneralIdRef(formId); form.setState({ loaded: true, data: newData });
const response = await formsService.putForm(generalIdRef, name, definition); } else {
if (response) { form.setState({ loaded: false });
toast.info("Form template edited"); }
} } catch (ex: any) {
} else { form.handleGeneralError(ex);
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);
} }
}
if (!editMode) form.setState({ loaded: true });
}; };
isEditMode = () => { loadData();
const { editMode } = this.props; }, [formId, editMode]); // eslint-disable-line react-hooks/exhaustive-deps
return editMode;
};
componentDidMount = async () => { const doSubmit = async (buttonName: string) => {
const { formId } = this.props.router.params; try {
const { name, definition } = form.state.data;
const nameStr = typeof name === "string" ? name : "";
const definitionStr = typeof definition === "string" ? definition : "";
if (formId !== undefined) { if (editMode) {
try { var generalIdRef = MakeGeneralIdRef(BigInt(formId!));
const loadedData = await formsService.getForm(formId); const response = await formsService.putForm(
generalIdRef,
const { data } = this.state; nameStr,
if (loadedData) { definitionStr,
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 (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 Joi from "joi";
import React from "react"; import React, { useEffect } from "react";
import { Navigate } from "react-router-dom"; import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import Form from "../../../components/common/Form"; import { useForm } from "../../../components/common/useForm";
import { InputType } from "../../../components/common/Input"; import { InputType } from "../../../components/common/Input";
import { FormState } from "../../../components/common/Form"; import { GeneralIdRef, MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
import withRouter from "../../../utils/withRouter"; import glossariesService, {
import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef"; CustomFieldValue,
import glossariesService, { Glossary, SystemGlossaries } from "./services/glossaryService"; Glossary,
SystemGlossaries,
} from "./services/glossaryService";
import { CustomField } from "../customfields/services/customFieldsService"; import { CustomField } from "../customfields/services/customFieldsService";
import Loading from "../../../components/common/Loading"; 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: { data: {
id? : bigint, id: undefined,
guid? : string, guid: undefined,
name: string, name: "",
parent?: Glossary, parent: undefined,
childCustomFieldDefinition : CustomField[] childCustomFieldDefinition: [],
}; },
redirect: string; errors: {},
} redirect: "",
});
class GlossariesDetails extends Form<any, any, GlossariesState> { form.schema = {
state: GlossariesState = { id: Joi.optional(),
loaded: false, guid: Joi.optional(),
data: { name: Joi.string().required().max(450).label(labelName),
id: undefined, parent: Joi.optional(),
guid: undefined, childCustomFieldDefinition: Joi.optional().label(
name: "", labelChildCustomFieldDefinition,
parent : undefined, ),
childCustomFieldDefinition : [] };
},
errors: {},
redirect: "",
};
labelName = "Name"; useEffect(() => {
labelParent = "Parent" const loadData = async () => {
labelChildCustomFieldDefinition = "Custom field for child entries" const newData = { ...form.state.data };
labelApply = "Save"; if (editMode) {
labelSave = "Save and close"; const generalIdRef = MakeGeneralIdRef(BigInt(glossaryId!));
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) => {
try { 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()) { form.setCustomFieldValues(
const generalIdRef = MakeGeneralIdRef(id, guid); newData,
loadedData.customFieldValues,
const response = await glossariesService.putGlossaryItem(generalIdRef, parent, name, childCustomFieldDefinition, customfieldValues); parentGlossary?.childCustomFieldDefinition ?? [],
if (response) { );
toast.info("Glossary Item edited"); }
} } catch (ex: any) {
} else { form.handleGeneralError(ex);
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 });
} }
catch(ex: any) { } else {
this.handleGeneralError(ex); 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 = () => { loadData();
const { editMode } = this.props; }, [glossaryId, editMode]); // eslint-disable-line react-hooks/exhaustive-deps
return editMode;
};
componentDidMount = async () => { const handleAdd = (customfield: CustomField) => {
const { data } = this.state; const newData = { ...form.state.data };
const childDefs =
(newData.childCustomFieldDefinition as CustomField[]) ?? [];
childDefs.push(customfield);
newData.childCustomFieldDefinition = childDefs;
if (this.isEditMode()) { form.setState({ data: newData });
const { glossaryId } = this.props.router.params; };
const generalIdRef = MakeGeneralIdRef(glossaryId); const handleDelete = (fieldToDelete: CustomField) => {
try { const newData = { ...form.state.data };
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 ?? [];
this.setCustomFieldValues(data, loadedData.customFieldValues, (data.parent as Glossary)?.childCustomFieldDefinition); if (fieldToDelete) {
} const childDefs =
} (newData.childCustomFieldDefinition as CustomField[]) ?? [];
catch(ex: any) { newData.childCustomFieldDefinition = childDefs.filter(
this.handleGeneralError(ex); (x) => x !== fieldToDelete,
} );
}
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 });
} }
handleDelete = ( fieldToDelete : CustomField ) => { form.setState({ data: newData });
let { data } = this.state; };
if (fieldToDelete){ const doSubmit = async (buttonName: string) => {
data.childCustomFieldDefinition = data.childCustomFieldDefinition.filter( x => x !== fieldToDelete); 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() { if (editMode) {
const { loaded, redirect, data } = this.state; const generalIdRef = MakeGeneralIdRef(idValue, guidValue);
if (redirect !== "") return <Navigate to={redirect} />;
let mode = "Add"; const response = await glossariesService.putGlossaryItem(
const isEditMode = this.isEditMode(); generalIdRef,
if (isEditMode) mode = "Edit"; parentGlossary
? MakeGeneralIdRef(parentGlossary.id, parentGlossary.guid)
return ( : undefined,
<Loading loaded={loaded}> nameStr,
<h1>{mode} Glossary Item</h1> childDefs,
<form onSubmit={this.handleSubmit}> customfieldValues,
{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>
); );
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 { useTranslation } from "react-i18next";
import Button, { ButtonType } from "../../../components/common/Button"; import Button, { ButtonType } from "../../../components/common/Button";
import Column from "../../../components/common/columns"; import Column from "../../../components/common/columns";
@ -29,21 +29,24 @@ const Organisations: React.FC = () => {
new Map<string, string>(), new Map<string, string>(),
); );
const changePage = async (page: number, pageSize: number) => { const changePage = useCallback(
const data = await organisationsService.getOrganisations( async (page: number, pageSize: number) => {
page, const data = await organisationsService.getOrganisations(
pageSize, page,
sortColumn.key, pageSize,
sortColumn.order === "asc", sortColumn.key,
filters, sortColumn.order === "asc",
); filters,
if (data) { );
setLoaded(true); if (data) {
setPagedData(data); setLoaded(true);
} else { setPagedData(data);
setLoaded(false); } else {
} setLoaded(false);
}; }
},
[filters, sortColumn.key, sortColumn.order],
);
const onSort = async (newSortColumn: Column<ReadOrganisation>) => { const onSort = async (newSortColumn: Column<ReadOrganisation>) => {
const { page, pageSize } = pagedData; const { page, pageSize } = pagedData;
@ -96,9 +99,24 @@ const Organisations: React.FC = () => {
}; };
useEffect(() => { useEffect(() => {
const { page, pageSize } = pagedData; const loadInitial = async () => {
changePage(page, pageSize); 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 ( return (
<Loading loaded={loaded}> <Loading loaded={loaded}>

View File

@ -1,140 +1,175 @@
import Joi from "joi"; import Joi from "joi";
import React from "react"; import React, { useEffect } from "react";
import { Navigate } from "react-router-dom"; import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import Form from "../../../components/common/Form"; import { useForm } from "../../../components/common/useForm";
import { InputType } from "../../../components/common/Input"; import { InputType } from "../../../components/common/Input";
import { FormState } from "../../../components/common/Form";
import withRouter from "../../../utils/withRouter";
import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef"; import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
import Option from "../../../components/common/option"; import Option from "../../../components/common/option";
import organisationsService from "./services/organisationsService"; import organisationsService from "./services/organisationsService";
import Loading from "../../../components/common/Loading"; 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: { data: {
name: string; name: "",
address: string; address: "",
status: string; status: "Active",
}; },
redirect: string; errors: {},
organisationStatusOptions: Option[]; redirect: "",
} });
class OrganisationsDetails extends Form<any, any, OrganisationsDetailsState> { form.schema = {
state: OrganisationsDetailsState = { name: Joi.string().required().max(450).label(labelName),
loaded: false, address: Joi.string().required().max(450).label(labelAddress),
data: { status: Joi.string().required().max(450).label(labelStatus),
name: "", };
address: "",
status: "Active"
},
errors: {},
redirect: "",
organisationStatusOptions: [
{ _id: "Active", name: "Active" },
{ _id: "Pending", name: "Pending" },
{ _id: "Blocked", name: "Blocked" },
]
};
labelName = "Name"; useEffect(() => {
labelAddress = "Address"; const loadData = async () => {
labelStatus = "Status"; if (organisationId !== undefined) {
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) => {
try { try {
const { name, address, status } = this.state.data; const loadedData = await organisationsService.getOrganisation(
BigInt(organisationId),
if (this.isEditMode()) { );
const { organisationId } = this.props.router.params;
var generalIdRef = MakeGeneralIdRef(organisationId); if (loadedData) {
const response = await organisationsService.putOrganisation(generalIdRef, name, address, status); const newData = { ...form.state.data };
if (response) { newData.name = loadedData.name;
toast.info("Organisation edited"); newData.address = loadedData.address;
} newData.status = loadedData.status;
} else {
const response = await organisationsService.postOrganisation(name, address, status);
if (response) {
toast.info("New Organisation added");
}
}
if (buttonName === this.labelSave) form.setState({ loaded: true, data: newData });
this.setState({ redirect: "/organisations" }); } else {
} form.setState({ loaded: false });
catch(ex: any) { }
this.handleGeneralError(ex); } catch (ex: any) {
form.handleGeneralError(ex);
} }
}
if (!editMode) form.setState({ loaded: true });
}; };
isEditMode = () => { loadData();
const { editMode } = this.props; }, [organisationId, editMode]); // eslint-disable-line react-hooks/exhaustive-deps
return editMode;
};
componentDidMount = async () => { const doSubmit = async (buttonName: string) => {
const { organisationId } = this.props.router.params; 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) { if (editMode) {
try var generalIdRef = MakeGeneralIdRef(BigInt(organisationId!));
{ const response = await organisationsService.putOrganisation(
const loadedData = await organisationsService.getOrganisation(organisationId); generalIdRef,
nameStr,
const { data } = this.state; addressStr,
if (loadedData) { statusStr,
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 (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 Joi from "joi";
import React from "react"; import React, { useEffect } from "react";
import { Navigate } from "react-router-dom"; import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import Form from "../../../components/common/Form"; import { useForm } from "../../../components/common/useForm";
import { InputType } from "../../../components/common/Input"; 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 sequenceService from "./services/sequenceService";
import Option from "../../../components/common/option"; import Option from "../../../components/common/option";
import withRouter from "../../../utils/withRouter";
import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef"; import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
import Loading from "../../../components/common/Loading"; 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: { data: {
name: string; name: "",
seed: number; seed: 1,
increment: number; increment: 1,
pattern: string; pattern: "[0]",
rolloverType: string; rolloverType: "Continuous",
}; },
redirect: string; errors: {},
} redirect: "",
});
class SequenceDetails extends Form<any, any, SequenceDetailsState> { form.schema = {
state: SequenceDetailsState = { name: Joi.string().required().max(450).label(labelName),
loaded : false, seed: Joi.number().required().label(labelSeed),
data: { increment: Joi.number().required().label(labelIncrement),
name: "", pattern: Joi.string().required().label(labelPattern),
seed: 1, rolloverType: Joi.string().required().label(labelRolloverType),
increment: 1, };
pattern: "[0]",
rolloverType: "Continuous",
},
errors: {},
redirect: "",
};
labelName = "Name"; useEffect(() => {
labelSeed = "Seed"; const loadData = async () => {
labelIncrement = "Increment"; if (sequenceId !== undefined) {
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) => {
try { try {
const { name, seed, increment, pattern, rolloverType } = this.state.data; const loadedData = await sequenceService.getSequence(
BigInt(sequenceId),
if (this.isEditMode()) { );
const { sequenceId } = this.props.router.params; if (loadedData) {
const newData = { ...form.state.data };
var generalIdRef = MakeGeneralIdRef(sequenceId); newData.increment = loadedData.increment;
const response = await sequenceService.putSequence(generalIdRef, name, seed, increment, pattern, rolloverType); newData.name = loadedData.name;
if (response) { newData.pattern = loadedData.pattern;
toast.info("Sequence edited"); newData.rolloverType = loadedData.rolloverType;
} newData.seed = loadedData.seed;
} else { form.setState({ loaded: true, data: newData });
const response = await sequenceService.postSequence(name, seed, increment, pattern, rolloverType); } else {
if (response) { form.setState({ loaded: false });
toast.info("New sequence added"); }
} } catch (ex: any) {
} form.handleGeneralError(ex);
if (buttonName === this.labelSave)
this.setState({ redirect: "/sequence" });
}
catch(ex: any) {
this.handleGeneralError(ex);
} }
}
if (!editMode) {
form.setState({ loaded: true });
}
}; };
isEditMode = () => { loadData();
const { editMode } = this.props; }, [sequenceId, editMode]); // eslint-disable-line react-hooks/exhaustive-deps
return editMode;
};
componentDidMount = async () => { const doSubmit = async (buttonName: string) => {
const { sequenceId } = this.props.router.params; 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) { if (editMode) {
try { const generalIdRef = MakeGeneralIdRef(
const loadedData = await sequenceService.getSequence(sequenceId); sequenceId ? BigInt(sequenceId) : undefined,
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>
); );
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(() => { useEffect(() => {
changePage(pagedData.page, pagedData.pageSize); const loadInitial = async () => {
}, [changePage, pagedData.page, pagedData.pageSize]); 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 ( return (
<Loading loaded={loaded}> <Loading loaded={loaded}>

View File

@ -1,143 +1,186 @@
import Joi from "joi"; import Joi from "joi";
import React from "react"; import React, { useEffect } from "react";
import { Navigate } from "react-router-dom"; import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import Form from "../../../components/common/Form"; import { useForm } from "../../../components/common/useForm";
import { InputType } from "../../../components/common/Input"; import { InputType } from "../../../components/common/Input";
import { FormState } from "../../../components/common/Form"; import {
import withRouter from "../../../utils/withRouter"; renderInput,
renderButton,
renderError,
renderSelect,
} from "../../../components/common/formHelpers";
import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef"; import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
import Option from "../../../components/common/option"; import Option from "../../../components/common/option";
import siteService from "./services/sitessService"; import siteService from "./services/sitessService";
import Loading from "../../../components/common/Loading"; 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: { data: {
name: string; name: "",
address: string; address: "",
status: string; status: "Active",
}; },
redirect: string; errors: {},
organisationStatusOptions: Option[]; redirect: "",
} });
class LocSiteDetails extends Form<any, any, SiteDetailsState> { form.schema = {
state: SiteDetailsState = { name: Joi.string().required().max(450).label(labelName),
loaded : false, address: Joi.string().required().max(450).label(labelAddress),
data: { status: Joi.string().required().max(450).label(labelStatus),
name: "", };
address: "",
status: "Active"
},
errors: {},
redirect: "",
organisationStatusOptions: [
{ _id: "Active", name: "Active" },
{ _id: "Pending", name: "Pending" },
{ _id: "Blocked", name: "Blocked" },
]
};
labelName = "Name"; useEffect(() => {
labelAddress = "Address"; const loadData = async () => {
labelStatus = "Status"; if (siteId !== undefined) {
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) => {
try { try {
const { name, address, status } = this.state.data; const loadedData = await siteService.getSite(BigInt(siteId));
if (loadedData) {
const { organisationId } = this.props.router.params; const newData = { ...form.state.data };
var organisationGeneralIdRef = MakeGeneralIdRef(organisationId); newData.name = loadedData.name;
newData.address = loadedData.address;
if (this.isEditMode()) { newData.status = loadedData.status;
const { siteId } = this.props.router.params; form.setState({ loaded: true, data: newData });
var siteGeneralIdRef = MakeGeneralIdRef(siteId); } else {
form.setState({ loaded: false });
const resposne = await siteService.putSite(siteGeneralIdRef, name, address, status, organisationGeneralIdRef); }
if (resposne) { } catch (ex: any) {
toast.info("Site edited"); form.handleGeneralError(ex);
}
} 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);
} }
}
if (!editMode) {
form.setState({ loaded: true });
}
}; };
isEditMode = () => { loadData();
const { editMode } = this.props; }, [siteId, editMode]); // eslint-disable-line react-hooks/exhaustive-deps
return editMode;
};
componentDidMount = async () => { const doSubmit = async (buttonName: string) => {
const { siteId } = this.props.router.params; 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) { if (editMode) {
try const siteGeneralIdRef = MakeGeneralIdRef(
{ siteId ? BigInt(siteId) : undefined,
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>
); );
} 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; export default SiteDetails;

View File

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

View File

@ -68,8 +68,26 @@ const Specifications: React.FC = () => {
); );
useEffect(() => { useEffect(() => {
void changePage(initialPagedData.page, initialPagedData.pageSize); const loadInitial = async () => {
}, [changePage, siteId]); 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( const onSort = useCallback(
async (nextSortColumn: Column<ReadSpecification>) => { async (nextSortColumn: Column<ReadSpecification>) => {
@ -91,7 +109,8 @@ const Specifications: React.FC = () => {
setLoaded(false); setLoaded(false);
} }
}, },
[filters, pagedData, updateFiltersWithSiteId], // eslint-disable-next-line react-hooks/exhaustive-deps
[filters, updateFiltersWithSiteId],
); );
const onSearch = useCallback( const onSearch = useCallback(
@ -115,13 +134,8 @@ const Specifications: React.FC = () => {
setLoaded(false); setLoaded(false);
} }
}, },
[ // eslint-disable-next-line react-hooks/exhaustive-deps
filters, [filters, sortColumn.key, sortColumn.order, updateFiltersWithSiteId],
pagedData,
sortColumn.key,
sortColumn.order,
updateFiltersWithSiteId,
],
); );
const onDelete = useCallback( const onDelete = useCallback(
@ -134,7 +148,8 @@ const Specifications: React.FC = () => {
await changePage(pagedData.page, pagedData.pageSize); await changePage(pagedData.page, pagedData.pageSize);
} }
}, },
[changePage, pagedData.page, pagedData.pageSize], // eslint-disable-next-line react-hooks/exhaustive-deps
[changePage],
); );
const translatedSortColumn = useMemo( const translatedSortColumn = useMemo(

View File

@ -1,198 +1,292 @@
import Joi from "joi"; import Joi from "joi";
import React from "react"; import React, { useEffect, useRef, useState, useCallback } from "react";
import { Navigate } from "react-router-dom"; import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify"; 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 { InputType } from "../../../components/common/Input";
import Loading from "../../../components/common/Loading"; import Loading from "../../../components/common/Loading";
import TemplateFiller from "../../../components/common/TemplateFiller"; import TemplateFiller from "../../../components/common/TemplateFiller";
import { GeneralIdRef, MakeGeneralIdRef } from "../../../utils/GeneralIdRef"; import { GeneralIdRef, MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
import withRouter from "../../../utils/withRouter"; import {
import { CustomFieldValue, PrintSpecificationsGlossary } from "../glossary/services/glossaryService"; CustomFieldValue,
PrintSpecificationsGlossary,
} from "../glossary/services/glossaryService";
import specificationService from "./services/specificationService"; import specificationService from "./services/specificationService";
import {
renderInput,
renderButton,
renderError,
renderGlossaryPicker,
} from "../../../components/common/formHelpers";
interface SpecificationsDetailsState extends FormState { interface SpecificationsDetailsProps {
formTemplate?: GeneralIdRef; 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: { data: {
name: string; name: "",
printSpecifications?: CustomFieldValue[]; sigmaId: null as bigint | null,
formInstanceId?: GeneralIdRef; printSpecifications: [] as CustomFieldValue[],
sigmaId: bigint | null; formInstanceId: undefined,
}; },
redirect: string; errors: {},
hasErrors: boolean; redirect: "",
} });
class LocSpecificationsDetails extends Form<any, any, SpecificationsDetailsState> { const [formTemplate, setFormTemplate] = useState<GeneralIdRef | undefined>(
private TemplateFiller: React.RefObject<TemplateFiller>; undefined,
);
const [hasErrors, setHasErrors] = useState(false);
constructor(props: any) { form.schema = {
super(props); name: Joi.string().required().max(450).label(labelName),
this.TemplateFiller = React.createRef<TemplateFiller>(); printSpecifications: Joi.optional(),
} formInstanceId: Joi.optional(),
sigmaId: Joi.number().allow(null).label(labelSigmaId),
};
state: SpecificationsDetailsState = { const isEditMode = () => editMode;
loaded: false,
formTemplate: undefined,
data: {
name: "",
sigmaId: null,
},
errors: {},
redirect: "",
hasErrors: true,
};
labelName = "Name"; const loadFormTemplate = useCallback(
labelPrintSpecification = "Print Specification"; async (printSpecifications?: GeneralIdRef) => {
labelStatus = "Status"; const { data } = form.state;
labelSigmaId = "SigmaId"; const printSpecArray =
(data.printSpecifications as CustomFieldValue[]) || [];
labelApply = "Save"; if (printSpecArray && printSpecArray.length > 0) {
labelSave = "Save and close"; if (
((printSpecArray[0] as CustomFieldValue).value as GeneralIdRef).id ===
schema = { BigInt(0)
name: Joi.string().required().max(450).label(this.labelName), ) {
printSpecifications: Joi.optional(), data.printSpecifications = undefined;
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);
} }
}; }
isEditMode = () => { let template;
const { editMode } = this.props; if (!printSpecArray || printSpecArray.length === 0) {
return editMode; template = undefined;
}; } else {
template = await specificationService.GetTemplateForPrintSpec(
componentDidMount = async () => { (printSpecArray[0] as CustomFieldValue).value as GeneralIdRef,
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>
); );
} }
} 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; export default SpecificationsDetails;

View File

@ -1,161 +1,291 @@
import Joi from "joi"; import Joi from "joi";
import React from "react"; import React, { useEffect } from "react";
import { Navigate } from "react-router-dom"; import { Navigate, useParams, useLocation } from "react-router-dom";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import Form from "../../../components/common/Form"; import { useForm } from "../../../components/common/useForm";
import { InputType } from "../../../components/common/Input"; import { InputType } from "../../../components/common/Input";
import { FormState } from "../../../components/common/Form"; import {
import withRouter from "../../../utils/withRouter"; renderInput,
renderButton,
renderError,
} from "../../../components/common/formHelpers";
import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef"; import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
import Loading from "../../../components/common/Loading"; import Loading from "../../../components/common/Loading";
import ssoManagerService from "./services/ssoManagerService"; 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: { data: {
name: string; name: "",
clientId: string, clientId: "",
clientSecret: string, clientSecret: "",
validIssuer: string, validIssuer: "",
authorizationEndpoint: string, authorizationEndpoint: "",
tokenEndpoint: string, tokenEndpoint: "",
isPublic: boolean, isPublic: true,
}; },
redirect: string; errors: {},
} redirect: "",
});
class SsoProviderDetails extends Form<any, any, SsoProviderDetailsState> { form.schema = {
state: SsoProviderDetailsState = { name: Joi.string().required().max(450).label(labelName),
loaded : false, clientId: Joi.string().required().max(450).label(labelClientId),
data: { clientSecret: Joi.string().required().max(450).label(labelClientSecret),
name: "", validIssuer: Joi.string().required().max(450).label(labelValidIssuer),
clientId: "", authorizationEndpoint: Joi.string()
clientSecret: "", .required()
validIssuer: "", .max(450)
authorizationEndpoint: "", .label(labelAuthorizationEndpoint),
tokenEndpoint: "", tokenEndpoint: Joi.string().required().max(450).label(labelTokenEndpoint),
isPublic: true, isPublic: Joi.bool().required().label(labelIsPublic),
}, };
errors: {},
redirect: "",
};
labelName = "Name"; useEffect(() => {
labelClientId = "Client Id"; const loadData = async () => {
labelClientSecret = "Client Secret"; if (ssoProviderId !== undefined) {
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) => {
try { try {
const { name, clientId, clientSecret, validIssuer, authorizationEndpoint, tokenEndpoint, isPublic } = this.state.data; const loadedData = await ssoManagerService.getSsoProvider(
BigInt(ssoProviderId),
if (this.isEditMode()) { );
const { ssoProviderId } = this.props.router.params; if (loadedData) {
const newData = { ...form.state.data };
var generalIdRef = MakeGeneralIdRef(ssoProviderId); newData.name = loadedData.name;
const response = await ssoManagerService.putSsoProvider(generalIdRef, name, clientId, clientSecret, validIssuer, authorizationEndpoint, tokenEndpoint, isPublic); newData.clientId = loadedData.clientId;
if (response) { newData.clientSecret = loadedData.clientSecret;
toast.info("Sso Provider edited"); newData.validIssuer = loadedData.validIssuer;
} newData.authorizationEndpoint = loadedData.authorizationEndpoint;
} else { newData.tokenEndpoint = loadedData.tokenEndpoint;
const response = await ssoManagerService.postSsoProvider(name, clientId, clientSecret, validIssuer, authorizationEndpoint, tokenEndpoint, isPublic); newData.isPublic = loadedData.isPublic;
if (response) { form.setState({ loaded: true, data: newData });
toast.info("New Sso Provider added"); } else {
} form.setState({ loaded: false });
} }
} catch (ex: any) {
if (buttonName === this.labelSave) form.handleGeneralError(ex);
this.setState({ redirect: "/ssoManager" });
}
catch(ex: any) {
this.handleGeneralError(ex);
} }
}
if (!editMode) {
form.setState({ loaded: true });
}
}; };
isEditMode = () => { loadData();
const { editMode } = this.props; }, [ssoProviderId, editMode]); // eslint-disable-line react-hooks/exhaustive-deps
return editMode;
};
componentDidMount = async () => { const doSubmit = async (buttonName: string) => {
const { ssoProviderId } = this.props.router.params; 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) { if (editMode) {
try { const generalIdRef = MakeGeneralIdRef(
const loadedData = await ssoManagerService.getSsoProvider(ssoProviderId); ssoProviderId ? BigInt(ssoProviderId) : undefined,
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>
); );
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(() => { useEffect(() => {
changePage(pagedData.page, pagedData.pageSize); const loadInitial = async () => {
}, [changePage, pagedData.page, pagedData.pageSize]); 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 ( return (
<Loading loaded={loaded}> <Loading loaded={loaded}>

View File

@ -1,144 +1,238 @@
import Joi from "joi"; import Joi from "joi";
import React from "react"; import React, { useEffect } from "react";
import { Navigate } from "react-router-dom"; import { Navigate, useParams } from "react-router-dom";
import { toast } from "react-toastify"; 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 { InputType } from "../../../../components/common/Input";
import { GeneralIdRef, MakeGeneralIdRef } from "../../../../utils/GeneralIdRef"; import { GeneralIdRef, MakeGeneralIdRef } from "../../../../utils/GeneralIdRef";
import withRouter, { RouterProps } from "../../../../utils/withRouter";
import authentication from "../../../frame/services/authenticationService"; import authentication from "../../../frame/services/authenticationService";
import userService from "../services/usersService"; import userService from "../services/usersService";
import Loading from "../../../../components/common/Loading"; import Loading from "../../../../components/common/Loading";
import { CustomFieldValue } from "../../glossary/services/glossaryService"; import { CustomFieldValue } from "../../glossary/services/glossaryService";
import {
renderInput,
renderButton,
renderError,
renderDomainPicker,
} from "../../../../components/common/formHelpers";
interface GeneralTabProps extends RouterProps { interface GeneralTabProps {
isEditMode: boolean; 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: { data: {
firstName: string; firstName: "",
lastName: string; lastName: "",
middleNames: string; middleNames: "",
email: string; email: "",
domain: CustomFieldValue[]; domain: [] as CustomFieldValue[],
}; },
redirect: string; errors: {},
} redirect: "",
});
class LocGeneralTab extends Form<GeneralTabProps, any, GeneralTabState> { form.schema = {
state: GeneralTabState = { firstName: Joi.string().required().max(450).label(labelFirstName),
loaded: false, middleNames: Joi.string().allow("").required().label(labelMiddleNames),
data: { lastName: Joi.string().required().label(labelLastName),
firstName: "", email: Joi.string()
lastName: "", .required()
middleNames: "", .email({ tlds: { allow: false } })
email: "", .label(labelEmail),
domain: [], domain: Joi.optional(),
}, };
errors: {},
redirect: "",
};
labelFirstName = "First name"; useEffect(() => {
labelMiddleNames = "Middle names"; const loadData = async () => {
labelLastName = "Last name"; const newData = { ...form.state.data };
labelEmail = "Mail";
labelDomain = "Domain";
labelApply = "Save"; if (userId !== undefined) {
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) => {
try { try {
const { isEditMode } = this.props; const loadedData = await userService.getUser(BigInt(userId));
if (loadedData) {
const { firstName, middleNames, lastName, email, domain } = this.state.data; newData.firstName = loadedData.firstName;
newData.lastName = loadedData.lastName;
if (isEditMode) { newData.middleNames = loadedData.middleNames;
const { userId } = this.props.router.params; newData.email = loadedData.email;
newData.domain = [{ value: loadedData.domain } as CustomFieldValue];
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" });
} catch (ex: any) { } 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 () => { loadData();
const { userId } = this.props.router.params; }, [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) { if (isEditMode) {
try { const generalIdRef = MakeGeneralIdRef(
const loadedData = await userService.getUser(userId); userId ? BigInt(userId) : undefined,
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>
); );
} 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; export default GeneralTab;

View File

@ -102,8 +102,24 @@ const Users: React.FC = () => {
}; };
useEffect(() => { useEffect(() => {
changePage(pagedData.page, pagedData.pageSize); const loadInitial = async () => {
}, [changePage, pagedData.page, pagedData.pageSize]); 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 ( return (
<Loading loaded={loaded}> <Loading loaded={loaded}>

View File

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