519 lines
21 KiB
TypeScript
519 lines
21 KiB
TypeScript
import './App.css';
|
|
|
|
import type { IElixFormsComponentFormState } from './IElixFormsComponentFormState';
|
|
import type { IElixFormsComponentProperties } from './IElixFormsComponentProperties';
|
|
import type { IElixFormsComponentCustomFormFieldFactory } from './IElixFormsComponentCustomFormFieldFactory';
|
|
import type { ElixFormsCheckboxOption, ElixFormsDropdownOption, ElixFormsRadioOption } from './ElixFormsTypes';
|
|
import { ElixFormsElement } from './ElixFormsElement';
|
|
import { QueryParamHelper } from './QueryParamHelper';
|
|
import { Component, type JSX } from 'react';
|
|
import React from 'react';
|
|
|
|
export default abstract class ElixFormsComponentAbstract extends Component<IElixFormsComponentProperties, IElixFormsComponentFormState> {
|
|
constructor(props: IElixFormsComponentProperties) {
|
|
super(props);
|
|
|
|
this.state = {
|
|
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[] = [];
|
|
|
|
this.mandatoryFormFieldNames.forEach(paramName => {
|
|
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 {
|
|
isDarkTheme,
|
|
userDisplayName,
|
|
moduleName,
|
|
headerTitle,
|
|
headerHeroImageSrc: headerTitleImageSrc,
|
|
cardTitle,
|
|
cardDescription,
|
|
alertInfoTitle,
|
|
alertInfoMessage,
|
|
instructionsTitle,
|
|
instructionsMessage,
|
|
submitText,
|
|
} = 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>
|
|
<header className="it-header-wrapper text-white">
|
|
{userDisplayName &&
|
|
<div className="it-header-slim-wrapper">
|
|
<div className="container">
|
|
<div className="row">
|
|
<div className="col-12">
|
|
<div className="it-header-slim-wrapper-content">
|
|
{/* <a className="d-none d-lg-block navbar-brand" href="#">Ente appartenenza</a> */}
|
|
<div className="nav-mobile">
|
|
{/* <nav aria-label="Navigazione accessoria">
|
|
<a className="it-opener d-lg-none" data-bs-toggle="collapse" href="#menu1a" role="button"
|
|
aria-expanded="false" aria-controls="menu4">
|
|
<span>Ente appartenenza</span>
|
|
<svg className="icon" aria-hidden="true">
|
|
<use href="/bootstrap-italia/dist/svg/sprites.svg#it-expand"></use>
|
|
</svg>
|
|
</a>
|
|
<div className="link-list-wrapper collapse" id="menu1a">
|
|
<ul className="link-list">
|
|
<li><a className="dropdown-item list-item" href="#">Link 1</a></li>
|
|
<li><a className="list-item active" href="#" aria-current="page">Link 2 (Attivo)</a></li>
|
|
</ul>
|
|
</div>
|
|
</nav> */}
|
|
</div>
|
|
<div className="it-header-slim-right-zone">
|
|
{/* <div className="nav-item dropdown">
|
|
<a className="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown"
|
|
aria-expanded="false">
|
|
<span className="visually-hidden">Selezione lingua: lingua selezionata</span>
|
|
<span>ITA</span>
|
|
<svg className="icon d-none d-lg-block">
|
|
<use href="/bootstrap-italia/dist/svg/sprites.svg#it-expand"></use>
|
|
</svg>
|
|
</a>
|
|
<div className="dropdown-menu">
|
|
<div className="row">
|
|
<div className="col-12">
|
|
<div className="link-list-wrapper">
|
|
<ul className="link-list">
|
|
<li><a className="dropdown-item list-item" href="#"><span>ITA <span
|
|
className="visually-hidden">selezionata</span></span></a></li>
|
|
<li><a className="dropdown-item list-item" href="#"><span>ENG</span></a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div> */}
|
|
<div className="it-access-top-wrapper">
|
|
{userDisplayName}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>}
|
|
|
|
<div className="it-header-center-wrapper">
|
|
<div className="container">
|
|
<div className="it-header-center-content-wrapper">
|
|
<div className="it-brand-wrapper">
|
|
<a href="https://www.unipr.it/" title="Torna alla homepage dell'Università di Parma">
|
|
<img className='icon' src="https://procedure.unipr.it/UploadImgs/12_marchio_UNIPR_neg_b1_squareLogo.png" alt="Università di Parma" />
|
|
<div className="it-brand-text">
|
|
<p className="h2 no_toc">Università di Parma</p>
|
|
{/* <p className="h3 no_toc d-none d-md-block">Procedure Online</p> */}
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<div className="it-right-zone">
|
|
{/* <div className="it-search-wrapper">
|
|
<span className="d-none d-md-block">Cerca</span>
|
|
<button className="search-link" aria-label="Cerca">
|
|
<span className="it-search"></span>
|
|
</button>
|
|
</div> */}
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<div className="container my-2 mx-4">
|
|
<div className='cmp-breadcrumb' role='navigation'>
|
|
<nav className="breadcrumb-container" aria-label="breadcrumb">
|
|
<ol className="breadcrumb">
|
|
<li className='breadcrumb-item'>Procedure Online<span className="separator">></span></li>
|
|
<li className="breadcrumb-item active" aria-current="page">{moduleName}</li>
|
|
</ol>
|
|
</nav>
|
|
</div>
|
|
|
|
{headerTitleImageSrc && <img src={headerTitleImageSrc} alt="Hero Image" className="hero-image img-fluid mb-3" />}
|
|
{headerTitle && (<p className='h2'>{headerTitle}</p>)}
|
|
|
|
<div className="container">
|
|
<div className="card shadow-sm mb-4">
|
|
<div className="card-body">
|
|
{cardTitle && <p className="h3 mb-3">{cardTitle}</p>}
|
|
{cardDescription && <p className="lead text-secondary">{cardDescription}</p>}
|
|
|
|
{(alertInfoTitle || alertInfoMessage) &&
|
|
<div className="custom-alert custom-alert-warning" role="status">
|
|
{alertInfoTitle && <div><b>{alertInfoTitle}</b></div>}
|
|
{alertInfoMessage && <div className='small'>{alertInfoMessage}</div>}
|
|
</div>}
|
|
|
|
{hasAuthenticationError ? (
|
|
<div className="alert alert-danger" role="alert">Accesso non autorizzato!</div>
|
|
) : (
|
|
<>
|
|
{this.renderExtraContentPre()}
|
|
|
|
{(instructionsTitle || instructionsMessage) &&
|
|
<div className="custom-alert custom-alert-info" role="status">
|
|
{instructionsTitle && <div><b>{instructionsTitle}</b></div>}
|
|
{instructionsMessage && <div className='small'>{instructionsMessage}</div>}
|
|
</div>
|
|
}
|
|
<form action="https://procedure.unipr.it/rwe2/ComeBackToElixAndSave" method="post" acceptCharset="ISO-8859-1" className="mt-3">
|
|
{this.createMandatoryFormFields(queryParams)}
|
|
{this.createAdditionalFormFields()}
|
|
{this.renderCustomFormFields()}
|
|
<div className="mt-4 d-flex justify-content-end">
|
|
<button type="submit" className="btn btn-primary">{submitText ?? 'INVIA'} <span className="it-arrow-right text-white ms-2"></span></button>
|
|
</div>
|
|
</form>
|
|
{this.renderExtraContentPost()}
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<footer className="it-footer text-white">
|
|
<div className="it-footer-small-prints clearfix">
|
|
<div className="container">
|
|
<section>
|
|
<div className="row clearfix">
|
|
<div className="col-sm-12">
|
|
<div className="footer-content text-center p-2">
|
|
<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>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</footer>
|
|
</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 currentValue = this.state.formData[paramName] !== undefined
|
|
? this.state.formData[paramName]
|
|
: (this.checkboxValues.get(paramName)?.join(',').trim() ?? '');
|
|
|
|
const selectedValues = (currentValue || '').split(',').filter(Boolean).map((value: string) => Number(value));
|
|
|
|
return new ElixFormsElement(
|
|
<label className="form-label fw-semibold" htmlFor={paramName}>{label}</label>,
|
|
<>
|
|
<div className="d-flex flex-column gap-2" role="group" aria-label={label}>
|
|
{values.map((entry, entryIndex) => {
|
|
const id = `${paramName}_${entryIndex}`;
|
|
const isChecked = selectedValues.includes(entryIndex);
|
|
|
|
return (
|
|
<div className="form-check" key={id}>
|
|
<input
|
|
id={id}
|
|
className="form-check-input"
|
|
type="checkbox"
|
|
checked={isChecked}
|
|
onChange={(event) => {
|
|
const currentCheckboxValues = this.checkboxValues.get(paramName) ?? [];
|
|
const checkboxIndex = currentCheckboxValues.indexOf(entryIndex);
|
|
const nextValues = [...currentCheckboxValues];
|
|
|
|
if (event.target.checked && checkboxIndex === -1) {
|
|
nextValues.push(entryIndex);
|
|
} else if (!event.target.checked && checkboxIndex !== -1) {
|
|
nextValues.splice(checkboxIndex, 1);
|
|
}
|
|
|
|
this.checkboxValues.set(paramName, nextValues);
|
|
const newValue = nextValues.join(',').trim();
|
|
this.setState({ formData: { ...this.state.formData, [paramName]: newValue } });
|
|
}}
|
|
/>
|
|
<label className="form-check-label" htmlFor={id}>{entry.label}</label>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
<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 currentValue = this.state.formData[paramName] !== undefined
|
|
? String(this.state.formData[paramName])
|
|
: String(QueryParamHelper.getOptionFromQuery(paramName) ?? '');
|
|
|
|
return new ElixFormsElement(
|
|
<span className="form-label fw-semibold">{label}</span>,
|
|
<div className="d-flex flex-column gap-2" role="radiogroup" aria-label={label}>
|
|
{values.map((entry) => {
|
|
const id = `${paramName}_${entry.value}`;
|
|
return (
|
|
<div className="form-check" key={id}>
|
|
<input
|
|
id={id}
|
|
className="form-check-input"
|
|
type="radio"
|
|
name={paramName}
|
|
value={entry.value}
|
|
checked={currentValue === String(entry.value)}
|
|
onChange={() => {
|
|
this.setState({ formData: { ...this.state.formData, [paramName]: String(entry.value) } });
|
|
}}
|
|
required={required}
|
|
/>
|
|
<label className="form-check-label" htmlFor={id}>{entry.label}</label>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
).render();
|
|
}
|
|
|
|
private createBooleanInput(paramName: string, label: string, required: boolean = false): JSX.Element {
|
|
const currentValue = this.state.formData[paramName] !== undefined
|
|
? String(this.state.formData[paramName])
|
|
: String(QueryParamHelper.getOptionFromQuery(paramName) ?? '');
|
|
|
|
return new ElixFormsElement(
|
|
<span className="form-label fw-semibold">{label}</span>,
|
|
<div className="d-flex flex-column gap-2" role="radiogroup" aria-label={label}>
|
|
{[{ value: 'true', label: 'Sì' }, { value: 'false', label: 'No' }].map((val) => {
|
|
const id = `${paramName}_${val.value}`;
|
|
return (
|
|
<div className="form-check" key={id}>
|
|
<input
|
|
id={id}
|
|
className="form-check-input"
|
|
type="radio"
|
|
name={paramName}
|
|
value={val.value}
|
|
checked={currentValue === val.value}
|
|
onChange={() => {
|
|
this.setState({ formData: { ...this.state.formData, [paramName]: val.value } });
|
|
}}
|
|
required={required}
|
|
/>
|
|
<label className="form-check-label" htmlFor={id}>{val.label}</label>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
).render();
|
|
}
|
|
|
|
private createTextAreaInput(paramName: string, label: string, required: boolean = false): JSX.Element {
|
|
const currentValue = this.state.formData[paramName] !== undefined
|
|
? String(this.state.formData[paramName])
|
|
: (QueryParamHelper.getDecodedTextFromQuery(paramName) ?? '');
|
|
|
|
return new ElixFormsElement(
|
|
<label className="form-label fw-semibold" htmlFor={paramName}>{label}</label>,
|
|
<textarea
|
|
id={paramName}
|
|
name={paramName}
|
|
className="form-control"
|
|
rows={4}
|
|
required={required}
|
|
value={currentValue}
|
|
onChange={(event) => {
|
|
this.setState({ formData: { ...this.state.formData, [paramName]: event.target.value } });
|
|
}}
|
|
/>
|
|
).render();
|
|
}
|
|
|
|
private createTextInput(paramName: string, label: string, required: boolean = false): JSX.Element {
|
|
const currentValue = this.state.formData[paramName] !== undefined
|
|
? String(this.state.formData[paramName])
|
|
: (QueryParamHelper.getDecodedTextFromQuery(paramName) ?? '');
|
|
|
|
return new ElixFormsElement(
|
|
<label className="form-label fw-semibold" htmlFor={paramName}>{label}</label>,
|
|
<input
|
|
id={paramName}
|
|
name={paramName}
|
|
className="form-control"
|
|
type="text"
|
|
required={required}
|
|
value={currentValue}
|
|
onChange={(event) => {
|
|
this.setState({ formData: { ...this.state.formData, [paramName]: event.target.value } });
|
|
}}
|
|
/>
|
|
).render();
|
|
}
|
|
|
|
private createHiddenInput(paramName: string): JSX.Element {
|
|
const textValue = QueryParamHelper.getDecodedTextFromQuery(paramName) ?? '';
|
|
return <input type="hidden" id={`${paramName}_hidden`} name={paramName} value={textValue} />;
|
|
}
|
|
|
|
private createNumberInput(paramName: string, label: string, required: boolean = false): JSX.Element {
|
|
const currentValue = this.state.formData[paramName] !== undefined
|
|
? String(this.state.formData[paramName])
|
|
: (QueryParamHelper.getDecodedTextFromQuery(paramName) ?? '');
|
|
|
|
return new ElixFormsElement(
|
|
<label className="form-label fw-semibold" htmlFor={paramName}>{label}</label>,
|
|
<input
|
|
id={paramName}
|
|
name={paramName}
|
|
className="form-control"
|
|
type="number"
|
|
required={required}
|
|
value={currentValue}
|
|
onChange={(event) => {
|
|
this.setState({ formData: { ...this.state.formData, [paramName]: event.target.value } });
|
|
}}
|
|
/>
|
|
).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
|
|
? String(this.state.formData[paramName])
|
|
: (queryValue?.toString() ?? '');
|
|
|
|
return new ElixFormsElement(
|
|
<label className="form-label fw-semibold" htmlFor={paramName}>{label}</label>,
|
|
<select
|
|
id={paramName}
|
|
name={paramName}
|
|
className="form-select"
|
|
required={required}
|
|
value={currentValue}
|
|
onChange={(event) => {
|
|
this.setState({ formData: { ...this.state.formData, [paramName]: event.target.value } });
|
|
}}
|
|
>
|
|
<option value="">Seleziona...</option>
|
|
{values.map(entry => (
|
|
<option key={entry.value} value={entry.value}>{entry.label}</option>
|
|
))}
|
|
</select>
|
|
).render();
|
|
}
|
|
|
|
private renderField(field: any): JSX.Element {
|
|
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,
|
|
field.options.map((o: any) => ({ value: o.value, label: 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,
|
|
field.options.map((o: any) => ({ value: o.value, label: o.label, required: o.required ?? false })));
|
|
|
|
case 'number':
|
|
return this.createNumberInput(field.key, field.label, field.required ?? false);
|
|
|
|
case 'dropdown':
|
|
return this.createDropdownInput(field.key, field.label,
|
|
field.options.map((o: any) => ({ value: o.value, label: o.label })),
|
|
field.required ?? false);
|
|
|
|
default:
|
|
return <></>;
|
|
}
|
|
}
|
|
} |