473 lines
19 KiB
TypeScript
473 lines
19 KiB
TypeScript
import './App.css'
|
|
import * as FluentUI from '@fluentui/react';
|
|
import type { IElixFormsComponentFormState } from './IElixFormsComponentFormState';
|
|
import type { IElixFormsComponentProperties } from './IElixFormsComponentProperties';
|
|
import { ElixFormsElement } from './ElixFormsElement';
|
|
import { QueryParamHelper } from './QueryParamHelper';
|
|
import { Component, type FormEvent, type JSX, useState } from 'react';
|
|
import React from 'react';
|
|
import type { IElixFormsComponentCustomFormFieldFactory } from './IElixFormsComponentCustomFormFieldFactory';
|
|
import type { ElixFormsCheckboxOption, ElixFormsDropdownOption, ElixFormsRadioOption } from './ElixFormsTypes';
|
|
|
|
export default abstract class ElixFormsComponentAbstract extends Component<IElixFormsComponentProperties, IElixFormsComponentFormState> {
|
|
constructor(props: IElixFormsComponentProperties) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
formData: {}
|
|
};
|
|
|
|
// const [formData, setFormData] = useState({});
|
|
// const handleAnyInputChange = (name: string) => (event: any, valueOrOption: any) => { //ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement> | SyntheticEvent<HTMLElement, Event>
|
|
// let value: any = null;
|
|
// // Fluent UI Dropdown passes (event, option)
|
|
// if (valueOrOption && valueOrOption.key !== undefined) {
|
|
// value = valueOrOption.key;
|
|
// }
|
|
// // Fluent UI TextField passes (event, newValue)
|
|
// else if (typeof valueOrOption === "string") {
|
|
// value = valueOrOption;
|
|
// }
|
|
// // Fluent UI Checkbox passes (event, checked)
|
|
// else if (typeof valueOrOption === "boolean") {
|
|
// value = valueOrOption;
|
|
// }
|
|
// // Native HTML inputs
|
|
// else if (event?.target) {
|
|
// value = event.target.type === "checkbox"
|
|
// ? event.target.checked
|
|
// : event.target.value;
|
|
// }
|
|
// setFormData(prev => ({
|
|
// ...prev,
|
|
// [name]: value
|
|
// }));
|
|
// console.log(`Input ${name} changed to ${value}. Current form data:`, JSON.stringify(formData));
|
|
// };
|
|
}
|
|
|
|
private checkboxValues = new Map<string, number[]>();
|
|
|
|
private mandatoryFormFieldNames: string[] = [
|
|
"RWE2_MODULE_ID",
|
|
"RWE2_REQUEST_ID",
|
|
"custom-workflow-back-url",
|
|
"custom-workflow-generic-id",
|
|
"custom-workflow-current-tabrel-genid",
|
|
"custom-workflow-source-field",
|
|
"crc",
|
|
"MODULE_TESTMODE_KEY",
|
|
"ELANG"
|
|
];
|
|
|
|
private createMandatoryFormFields(queryParams: URLSearchParams): JSX.Element {
|
|
const hiddenInputs: JSX.Element[] = [];
|
|
|
|
// Create a hidden input for each mandatory Elix parameter
|
|
this.mandatoryFormFieldNames.forEach(paramName => {
|
|
const value = queryParams.get(paramName) ?? "";
|
|
// Create the input element
|
|
hiddenInputs.push(this.createHiddenInput(paramName));
|
|
});
|
|
|
|
return <>{hiddenInputs}</>;
|
|
}
|
|
|
|
private createAdditionalFormFields(): React.ReactElement {
|
|
const schema = JSON.parse(this.props.additionalFieldsJson || "[]");
|
|
|
|
return (
|
|
<>
|
|
{schema.map((field: { key: React.Key | null | undefined; }) => (
|
|
<div key={field.key}>
|
|
{this.renderField(field)}
|
|
</div>
|
|
))}
|
|
</>
|
|
);
|
|
}
|
|
|
|
protected createCustomFormFields(formFieldFactory: IElixFormsComponentCustomFormFieldFactory): JSX.Element {
|
|
return <></>;
|
|
}
|
|
|
|
private renderCustomFormFields(): JSX.Element {
|
|
const formFieldFactory = {
|
|
createBooleanInput: (name: string, label: string, required: boolean = false) =>
|
|
this.createBooleanInput(name, label, required),
|
|
createCheckboxInput: (name: string, label: string, options: Array<ElixFormsCheckboxOption>) =>
|
|
this.createCheckboxInput(name, label, options),
|
|
createDropdownInput: (name: string, label: string, options: Array<ElixFormsDropdownOption>, required: boolean = false) =>
|
|
this.createDropdownInput(name, label, options, required),
|
|
createHiddenInput: (name: string) =>
|
|
this.createHiddenInput(name),
|
|
createNumberInput: (name: string, label: string, required: boolean = false) =>
|
|
this.createNumberInput(name, label, required),
|
|
createRadioInput: (name: string, label: string, options: Array<ElixFormsRadioOption>, required: boolean = false) =>
|
|
this.createRadioInput(name, label, options, required),
|
|
createTextInput: (name: string, label: string, required: boolean = false) =>
|
|
this.createTextInput(name, label, required),
|
|
createTextAreaInput: (name: string, label: string, required: boolean = false) =>
|
|
this.createTextAreaInput(name, label, required),
|
|
};
|
|
|
|
return this.createCustomFormFields(formFieldFactory);
|
|
}
|
|
|
|
protected renderExtraContentPre(): JSX.Element | null {
|
|
return null;
|
|
}
|
|
|
|
protected renderExtraContentPost(): JSX.Element | null {
|
|
return null;
|
|
}
|
|
|
|
public override render(): React.ReactElement<IElixFormsComponentProperties> {
|
|
const {
|
|
description,
|
|
isDarkTheme,
|
|
environmentMessage,
|
|
hasTeamsContext,
|
|
userDisplayName,
|
|
additionalFieldsJson,
|
|
headerTitle,
|
|
pageTitle,
|
|
pageDescription,
|
|
submitDescription,
|
|
heroImageSrc
|
|
} = this.props;
|
|
|
|
const queryParams = new URLSearchParams(window.location.search);
|
|
const missingMandatoryParams = this.mandatoryFormFieldNames.filter(paramName => !queryParams.has(paramName));
|
|
const hasAuthenticationError = missingMandatoryParams.length > 0;
|
|
|
|
return (
|
|
<div className="container_12" id="pageBody">
|
|
<div className="grid_12">
|
|
<div id="userConsole" className="fe-behaviour">
|
|
<header>
|
|
<div className="neutral">
|
|
<div className="grid_12">
|
|
<div className="console_header">
|
|
<div className="leftDiv">
|
|
<h1>elixForms</h1>
|
|
<div className="logo"><a href="https://www.unipr.it/" title="Torna alla homepage dell'Università: di Parma"><img src="https://console-unipr.elixforms.it/elixFormsCustom/images/logo.png" alt="Logo Università: di Parma" /></a>
|
|
</div>
|
|
</div>
|
|
<div className="rightDiv">
|
|
<div className="rightDivInside info padding_r">
|
|
<div className="clear"></div>
|
|
</div>
|
|
</div>
|
|
<div className="clear"></div>
|
|
</div>
|
|
</div>
|
|
<div className="clear"></div>
|
|
</div>
|
|
</header>
|
|
|
|
<div className="grid_12 console_main">
|
|
<div className="inside">
|
|
{headerTitle && (
|
|
<div>
|
|
<div className="fe-module-header-container">
|
|
<h2>{headerTitle}</h2>
|
|
</div>
|
|
</div>
|
|
)}
|
|
<div className="mainview">
|
|
<div className="container">
|
|
{pageTitle && <h1>{pageTitle}</h1>}
|
|
{heroImageSrc && <img src={heroImageSrc} alt="Hero Image" className="hero-image" />}
|
|
{pageDescription && <p>{pageDescription}</p>}
|
|
|
|
{environmentMessage && <div>{environmentMessage}</div>}
|
|
{description && <div>{description}</div>}
|
|
|
|
{hasAuthenticationError ? (
|
|
<FluentUI.MessageBar messageBarType={FluentUI.MessageBarType.error} isMultiline={false}>
|
|
Accesso non autorizzato. Parametri obbligatori mancanti ({missingMandatoryParams.join(', ')}).
|
|
</FluentUI.MessageBar>
|
|
) : (
|
|
<>
|
|
{this.renderExtraContentPre()}
|
|
|
|
<form action="https://procedure.unipr.it/rwe2/ComeBackToElixAndSave" method="post" acceptCharset="ISO-8859-1">
|
|
{this.createMandatoryFormFields(queryParams)}
|
|
{this.createAdditionalFormFields()}
|
|
{this.renderCustomFormFields()}
|
|
<br/>
|
|
<input type="submit" value={submitDescription ?? 'Submit'} />
|
|
</form>
|
|
|
|
{this.renderExtraContentPost()}
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="clear"></div>
|
|
|
|
<footer className="it-footer">
|
|
<div className="it-footer-main">
|
|
<div className="container">
|
|
<div className="row clearfix">
|
|
<div className="col-sm-12">
|
|
<div className="footer-content">
|
|
<div className="subfooter-top">powered by <span className="highlight">elixForms</span></div>
|
|
<div className="subfooter-bottom"><div>versione 1.24.0</div></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
<div className="clear"></div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
private createCheckboxInput(paramName: string, label: string, values: Array<ElixFormsCheckboxOption>): JSX.Element {
|
|
const checkedValues = QueryParamHelper.getCheckedFromQuery(paramName);
|
|
|
|
if (!this.checkboxValues.get(paramName)) {
|
|
this.checkboxValues.set(paramName, checkedValues);
|
|
}
|
|
|
|
const checkboxes: React.ReactElement[] = [];
|
|
const stackTokens = { childrenGap: 10 };
|
|
|
|
values.forEach((entry, entryIndex) => {
|
|
const id = `${paramName}_${entryIndex}`;
|
|
checkboxes.push(
|
|
<FluentUI.Checkbox title={label} key={id} id={id} label={entry.label} required={entry.required ?? false} defaultChecked={checkedValues.indexOf(entryIndex) !== -1} className='isiportalPartialAdminCheckboxFieldItem'
|
|
onChange={(event, checked) => {
|
|
const currentCheckboxValues = this.checkboxValues.get(paramName)!;
|
|
const checkboxIndex = currentCheckboxValues.indexOf(entryIndex);
|
|
if (checked && checkboxIndex === -1) {
|
|
currentCheckboxValues.push(entryIndex);
|
|
} else if (!checked && checkboxIndex !== -1) {
|
|
currentCheckboxValues.splice(checkboxIndex, 1);
|
|
}
|
|
const newValue = currentCheckboxValues.join(',').trim();
|
|
this.setState({ formData: { ...this.state.formData, [paramName]: newValue } }, () => {
|
|
console.log(`Checkbox ${id} changed to ${checked}. Current values for ${paramName}: ${this.state.formData[paramName]}`);
|
|
});
|
|
}}
|
|
/>
|
|
);
|
|
});
|
|
|
|
const currentValue = this.state.formData[paramName] !== undefined
|
|
? this.state.formData[paramName]
|
|
: (this.checkboxValues.get(paramName)?.join(',').trim() ?? '');
|
|
|
|
return new ElixFormsElement(
|
|
<><FluentUI.Label htmlFor={paramName}>{label}</FluentUI.Label></>,
|
|
<><FluentUI.Stack tokens={stackTokens}>
|
|
{checkboxes}
|
|
</FluentUI.Stack>
|
|
<input type='hidden' id={paramName} name={paramName} value={currentValue} />
|
|
</>
|
|
).render();
|
|
}
|
|
|
|
private createRadioInput(paramName: string, label: string, values: Array<ElixFormsRadioOption>, required: boolean = false): JSX.Element {
|
|
const radioValue = QueryParamHelper.getOptionFromQuery(paramName);
|
|
const radios: FluentUI.IChoiceGroupOption[] = [];
|
|
|
|
values.forEach((entry, _) => {
|
|
const id = `${paramName}_${entry.value}`;
|
|
radios.push(
|
|
{
|
|
key: id,
|
|
text: entry.label,
|
|
id: id,
|
|
name: paramName,
|
|
value: entry.value,
|
|
className: 'isiportalPartialAdminFormFieldRadio'
|
|
}
|
|
);
|
|
});
|
|
|
|
return new ElixFormsElement(
|
|
<></>,
|
|
<FluentUI.ChoiceGroup
|
|
name={paramName}
|
|
label={label}
|
|
options={radios}
|
|
required={required}
|
|
defaultSelectedKey={`${paramName}_${radioValue}`}
|
|
onChange={(event, option) => {
|
|
const value = option?.value ?? '';
|
|
this.setState({ formData: { ...this.state.formData, [paramName]: value } }, () =>
|
|
console.log(`Radio ${option?.id} changed to ${value}. Current value for ${paramName}: ${this.state.formData[paramName]}`)
|
|
);
|
|
}}
|
|
/>
|
|
).render();
|
|
}
|
|
|
|
private createBooleanInput(paramName: string, label: string, required: boolean = false): JSX.Element {
|
|
const boolValue = QueryParamHelper.getOptionFromQuery(paramName);
|
|
const radios: FluentUI.IChoiceGroupOption[] = [];
|
|
|
|
[{ "value": "true", "label": "Sì" }, { "value": "false", "label": "No" }].forEach((val) => {
|
|
const id = `${paramName}_${val.value}`;
|
|
radios.push(
|
|
{
|
|
key: id,
|
|
text: val.label,
|
|
id: id,
|
|
name: paramName,
|
|
value: val.value,
|
|
className: 'isiportalPartialAdminFormFieldRadio'
|
|
}
|
|
);
|
|
});
|
|
|
|
return new ElixFormsElement(
|
|
<></>,
|
|
<FluentUI.ChoiceGroup
|
|
name={paramName}
|
|
label={label}
|
|
options={radios}
|
|
required={required}
|
|
defaultSelectedKey={`${paramName}_${boolValue}`}
|
|
onChange={(event, option) => {
|
|
const value = option?.value ?? '';
|
|
this.setState({ formData: { ...this.state.formData, [paramName]: value } }, () =>
|
|
console.log(`Boolean ${option?.id} changed to ${value}. Current value for ${paramName}: ${this.state.formData[paramName]}`)
|
|
);
|
|
}}
|
|
/>
|
|
).render();
|
|
}
|
|
|
|
private createTextAreaInput(paramName: string, label: string, required: boolean = false): JSX.Element {
|
|
const textareaValue = QueryParamHelper.getDecodedTextFromQuery(paramName) ?? "";
|
|
return new ElixFormsElement(
|
|
<FluentUI.Label htmlFor={paramName}>{label}</FluentUI.Label>,
|
|
<FluentUI.TextField id={paramName} name={paramName} defaultValue={textareaValue} multiline rows={4} required={required} className='isiportalPartialAdminFormFieldMultiLineText'
|
|
onChange={(event, value) => {
|
|
this.setState({ formData: { ...this.state.formData, [paramName]: value } }, () =>
|
|
console.log(`TextArea ${paramName} changed to ${value}. Current value for ${paramName}: ${this.state.formData[paramName]}`)
|
|
);
|
|
}}
|
|
/>
|
|
).render();
|
|
}
|
|
|
|
private createTextInput(paramName: string, label: string, required: boolean = false): JSX.Element {
|
|
const textValue = QueryParamHelper.getDecodedTextFromQuery(paramName) ?? "";
|
|
return new ElixFormsElement(
|
|
<FluentUI.Label htmlFor={paramName}>{label}</FluentUI.Label>,
|
|
<FluentUI.TextField id={paramName} name={paramName} defaultValue={textValue} required={required} className='isiportalPartialAdminFormFieldSingleLineText'
|
|
onChange={(event, value) => {
|
|
this.setState({ formData: { ...this.state.formData, [paramName]: value } }, () =>
|
|
console.log(`TextInput ${paramName} changed to ${value}. Current value for ${paramName}: ${this.state.formData[paramName]}`)
|
|
);
|
|
}}
|
|
/>
|
|
).render();
|
|
}
|
|
|
|
private createHiddenInput(paramName: string): JSX.Element {
|
|
const textValue = QueryParamHelper.getDecodedTextFromQuery(paramName) ?? "";
|
|
return <input type="hidden" id={`${paramName}_hidden`} name={paramName} defaultValue={textValue} />;
|
|
}
|
|
|
|
private createNumberInput(paramName: string, label: string, required: boolean = false): JSX.Element {
|
|
const textValue = QueryParamHelper.getDecodedTextFromQuery(paramName) ?? "";
|
|
return new ElixFormsElement(
|
|
<FluentUI.Label htmlFor={paramName}>{label}</FluentUI.Label>,
|
|
<FluentUI.TextField id={paramName} name={paramName} defaultValue={textValue} type="number" required={required} className='isiportalPartialAdminFormFieldSingleLineText'
|
|
onChange={(event, value) => {
|
|
this.setState({ formData: { ...this.state.formData, [paramName]: value } }, () =>
|
|
console.log(`NumberInput ${paramName} changed to ${value}. Current value for ${paramName}: ${this.state.formData[paramName]}`)
|
|
);
|
|
}}
|
|
/>
|
|
).render();
|
|
}
|
|
|
|
private createDropdownInput(paramName: string, label: string, values: Array<ElixFormsDropdownOption>, required: boolean = false): JSX.Element {
|
|
const queryValue = QueryParamHelper.getOptionFromQuery(paramName);
|
|
const currentValue = this.state.formData[paramName] !== undefined
|
|
? this.state.formData[paramName]
|
|
: (queryValue?.toString() ?? '');
|
|
|
|
const options: FluentUI.IDropdownOption[] = [];
|
|
|
|
values.forEach(entry => {
|
|
options.push(
|
|
{
|
|
key: entry.value.toString(),
|
|
text: entry.label
|
|
}
|
|
);
|
|
});
|
|
|
|
return new ElixFormsElement(
|
|
<FluentUI.Label htmlFor={paramName}>{label}</FluentUI.Label>,
|
|
<>
|
|
<FluentUI.Dropdown
|
|
id={paramName}
|
|
options={options}
|
|
selectedKey={currentValue}
|
|
placeholder='---'
|
|
required={required}
|
|
className='isiportalPartialAdminFormFieldSelect'
|
|
onChange={(event, option) => {
|
|
if (option) {
|
|
const value = option.key.toString();
|
|
this.setState({ formData: { ...this.state.formData, [paramName]: value } }, () =>
|
|
console.log(`Dropdown ${paramName} changed to ${value}. Current value for ${paramName}: ${this.state.formData[paramName]}`)
|
|
);
|
|
}
|
|
}}
|
|
/>
|
|
<input type="hidden" name={paramName} id={paramName + '_hidden'} value={currentValue} />
|
|
</>
|
|
).render();
|
|
}
|
|
|
|
private renderField(field: any): JSX.Element {
|
|
//const value = this.state.formData[field.key] || "";
|
|
|
|
switch (field.type) {
|
|
case "text":
|
|
return this.createTextInput(field.key, field.label, field.required ?? false);
|
|
|
|
case "textarea":
|
|
return this.createTextAreaInput(field.key, field.label, field.required ?? false);
|
|
|
|
case "radio":
|
|
return this.createRadioInput(field.key, field.label,
|
|
new Array<ElixFormsRadioOption>().concat(...field.options.map((o: any) => [[o.value, o.label]])),
|
|
field.required ?? false);
|
|
|
|
case "boolean":
|
|
return this.createBooleanInput(field.key, field.label, field.required ?? false);
|
|
|
|
case "checkbox":
|
|
return this.createCheckboxInput(field.key, field.label,
|
|
new Array<ElixFormsCheckboxOption>().concat(...field.options.map((o: any) => [[o.value, o.label, o.required ?? false]])));
|
|
|
|
case "number":
|
|
return this.createNumberInput(field.key, field.label, field.required ?? false);
|
|
|
|
case "dropdown":
|
|
return this.createDropdownInput(field.key, field.label,
|
|
new Array<ElixFormsDropdownOption>().concat(...field.options.map((o: any) => [[o.value, o.label]])),
|
|
field.required ?? false);
|
|
|
|
default:
|
|
return <></>;
|
|
}
|
|
}
|
|
} |