diff --git a/src/assets/scss/components/formBuilder.scss b/src/assets/scss/components/formBuilder.scss
index 0fc994f..91c873f 100644
--- a/src/assets/scss/components/formBuilder.scss
+++ b/src/assets/scss/components/formBuilder.scss
@@ -204,14 +204,14 @@
.formElementSettings__fieldDescription, .formElementSettings__fieldVarsList {
padding: 15px;
- background-color: #ffe0c5;
+ background-color: #fdf1e5;
border: 1px solid #e6a973;
- p {
+ p, li {
margin: 0;
color: #c68e5e;
font-size: 15px;
- line-height: 1.5;
+ line-height: 1.8;
}
code {
diff --git a/src/components/FormField/components/Spreadsheet/index.js b/src/components/FormField/components/Spreadsheet/index.js
index ccc04b9..72ec039 100644
--- a/src/components/FormField/components/Spreadsheet/index.js
+++ b/src/components/FormField/components/Spreadsheet/index.js
@@ -1,11 +1,28 @@
import React, { useEffect, useRef } from 'react';
+import { classNames } from 'primereact/utils';
import { createUniver, LocaleType, mergeLocales } from '@univerjs/presets';
import { UniverSheetsCorePreset } from '@univerjs/preset-sheets-core';
import UniverPresetSheetsCoreEnUS from '@univerjs/preset-sheets-core/locales/en-US';
import '@univerjs/preset-sheets-core/lib/index.css';
+import { __ } from '@wordpress/i18n';
+
+// tools
+import { xlsxToWorkbookData } from '../../../../pages/BandoEdit/components/BandoEditFormStep3Excel/xlsxToWorkbookData';
const TAG_RE = /^\{\{gepafin_field:[^|]+\|[^}]+}}$/;
+const BLOCKED_COMMANDS = new Set([
+ 'sheet.command.set-range-values',
+ 'sheet.command.clear-selection-content',
+ 'sheet.command.clear-selection-all',
+ 'sheet.command.delete-range',
+ 'sheet.command.insert-range',
+ 'sheet.command.set-range-format',
+ 'sheet.command.insert-sheet',
+ 'sheet.command.delete-sheet',
+ 'sheet.command.rename-sheet',
+]);
+
const buildTagMap = (workbookData) => {
const map = {};
if (!workbookData?.sheets) return map;
@@ -36,82 +53,131 @@ const parseWorkbook = (val) => {
return null;
};
-const Spreadsheet = ({ fieldName, defaultValue, setDataFn, template, register, config = {} }) => {
+const Spreadsheet = ({ fieldName, label, errors = {}, defaultValue, setDataFn, template, register, config = {} }) => {
const containerRef = useRef(null);
const univerRef = useRef(null);
const univerAPIRef = useRef(null);
const saveTimerRef = useRef(null);
const isRestoringRef = useRef(false);
const tagCellMapRef = useRef({});
+ const reinitializeRef = useRef(null);
+ const fileInputRef = useRef(null);
+ const addSheetsFileInputRef = useRef(null);
useEffect(() => {
if (!containerRef.current) return;
- const { univer, univerAPI } = createUniver({
- locale: LocaleType.EN_US,
- locales: {
- [LocaleType.EN_US]: mergeLocales(UniverPresetSheetsCoreEnUS),
- },
- presets: [
- UniverSheetsCorePreset({
- container: containerRef.current,
- }),
- ],
- });
+ const templateData = parseWorkbook(template);
+ const gepafin = templateData?.gepafin ?? {};
+ const editableSet = new Set(gepafin.editableCells ?? []);
+ const inputOnly = editableSet.size > 0;
+ const validationCellKeys = new Set((gepafin.validationCells ?? []).map(vc => vc.key ?? vc));
- univerRef.current = univer;
- univerAPIRef.current = univerAPI;
+ const doInit = (workbookData) => {
+ if (univerRef.current) {
+ univerRef.current.dispose();
+ univerRef.current = null;
+ univerAPIRef.current = null;
+ }
- const initialData = parseWorkbook(defaultValue) || parseWorkbook(template) || { name: 'Sheet' };
- tagCellMapRef.current = buildTagMap(initialData);
- univerAPI.createWorkbook(initialData);
+ const { univer, univerAPI } = createUniver({
+ locale: LocaleType.EN_US,
+ locales: {
+ [LocaleType.EN_US]: mergeLocales(UniverPresetSheetsCoreEnUS),
+ },
+ presets: [
+ UniverSheetsCorePreset({
+ container: containerRef.current,
+ }),
+ ],
+ });
- const restoreTagCells = () => {
- if (isRestoringRef.current) return;
- const wb = univerAPIRef.current?.getActiveWorkbook();
- if (!wb) return;
- const sheet = wb.getActiveSheet();
- if (!sheet) return;
- const saved = wb.save();
- if (!saved?.sheets) return;
- const sheetId = saved.sheetOrder?.[0];
- const sheetData = sheetId ? saved.sheets[sheetId] : Object.values(saved.sheets)[0];
- if (!sheetData?.cellData) return;
+ univerRef.current = univer;
+ univerAPIRef.current = univerAPI;
- const restorations = [];
- Object.entries(sheetData.cellData).forEach(([r, row]) => {
- Object.entries(row).forEach(([c, cell]) => {
- if (typeof cell.v === 'string' && TAG_RE.test(cell.v)) {
- const orig = tagCellMapRef.current[`${r}:${c}`];
- if (orig) restorations.push({ r: +r, c: +c, v: orig.v, f: orig.f });
- }
+ tagCellMapRef.current = buildTagMap(workbookData);
+ univerAPI.createWorkbook(workbookData);
+
+ // Apply visual markers for editable and validation cells in input-only mode
+ if (inputOnly && (editableSet.size > 0 || validationCellKeys.size > 0)) {
+ setTimeout(() => {
+ const wb = univerAPIRef.current?.getActiveWorkbook();
+ const sheet = wb?.getActiveSheet();
+ if (!sheet) return;
+ editableSet.forEach((key) => {
+ const [r, c] = key.split(':').map(Number);
+ sheet.getRange(r, c, 1, 1).setBackground('#dcfce7');
+ });
+ validationCellKeys.forEach((key) => {
+ const [r, c] = key.split(':').map(Number);
+ sheet.getRange(r, c, 1, 1).setBackground('#fef3c7');
+ });
+ }, 300);
+ }
+
+ const restoreTagCells = () => {
+ if (isRestoringRef.current) return;
+ const wb = univerAPIRef.current?.getActiveWorkbook();
+ if (!wb) return;
+ const sheet = wb.getActiveSheet();
+ if (!sheet) return;
+ const saved = wb.save();
+ if (!saved?.sheets) return;
+ const sheetId = saved.sheetOrder?.[0];
+ const sheetData = sheetId ? saved.sheets[sheetId] : Object.values(saved.sheets)[0];
+ if (!sheetData?.cellData) return;
+
+ const restorations = [];
+ Object.entries(sheetData.cellData).forEach(([r, row]) => {
+ Object.entries(row).forEach(([c, cell]) => {
+ if (typeof cell.v === 'string' && TAG_RE.test(cell.v)) {
+ const orig = tagCellMapRef.current[`${r}:${c}`];
+ if (orig) restorations.push({ r: +r, c: +c, v: orig.v, f: orig.f });
+ }
+ });
});
- });
- if (restorations.length === 0) return;
- isRestoringRef.current = true;
- restorations.forEach(({ r, c, v, f }) => {
- sheet.getRange(r, c, 1, 1).setValues([[{ v, f }]]);
+ if (restorations.length === 0) return;
+ isRestoringRef.current = true;
+ restorations.forEach(({ r, c, v, f }) => {
+ sheet.getRange(r, c, 1, 1).setValues([[{ v, f }]]);
+ });
+ isRestoringRef.current = false;
+ };
+
+ let restoreTimer;
+ univerAPI.addEvent(univerAPI.Event.BeforeCommandExecute, (event) => {
+ // Input-only mode: block write commands on non-editable cells
+ if (inputOnly && BLOCKED_COMMANDS.has(event.id)) {
+ const sheet = univerAPIRef.current?.getActiveWorkbook()?.getActiveSheet();
+ const range = sheet?.getActiveRange();
+ if (range) {
+ const key = `${range.getRow()}:${range.getColumn()}`;
+ if (!editableSet.has(key)) {
+ event.cancel = true;
+ return;
+ }
+ }
+ }
+
+ clearTimeout(saveTimerRef.current);
+ saveTimerRef.current = setTimeout(() => {
+ const wb = univerAPIRef.current?.getActiveWorkbook();
+ if (wb) {
+ setDataFn(fieldName, wb.save(), { shouldValidate: true });
+ }
+ }, 300);
+ clearTimeout(restoreTimer);
+ restoreTimer = setTimeout(restoreTagCells, 80);
});
- isRestoringRef.current = false;
};
- let restoreTimer;
- univerAPI.addEvent(univerAPI.Event.BeforeCommandExecute, () => {
- clearTimeout(saveTimerRef.current);
- saveTimerRef.current = setTimeout(() => {
- const wb = univerAPIRef.current?.getActiveWorkbook();
- if (wb) {
- setDataFn(fieldName, wb.save(), { shouldValidate: true });
- }
- }, 300);
- clearTimeout(restoreTimer);
- restoreTimer = setTimeout(restoreTagCells, 80);
- });
+ const initialData = parseWorkbook(defaultValue) || templateData || { name: 'Sheet' };
+ doInit(initialData);
+ reinitializeRef.current = doInit;
return () => {
clearTimeout(saveTimerRef.current);
- clearTimeout(restoreTimer);
if (univerRef.current) {
univerRef.current.dispose();
univerRef.current = null;
@@ -132,11 +198,111 @@ const Spreadsheet = ({ fieldName, defaultValue, setDataFn, template, register, c
register(fieldName, config);
}, []); // eslint-disable-line react-hooks/exhaustive-deps
+ const handleImport = (e) => {
+ const file = e.target.files?.[0];
+ if (!file) return;
+ const reader = new FileReader();
+ reader.onload = (ev) => {
+ const data = xlsxToWorkbookData(ev.target.result);
+ reinitializeRef.current?.(data);
+ setTimeout(() => {
+ const wb = univerAPIRef.current?.getActiveWorkbook();
+ if (wb) setDataFn(fieldName, wb.save(), { shouldValidate: true });
+ }, 400);
+ };
+ reader.readAsArrayBuffer(file);
+ e.target.value = '';
+ };
+
+ const handleImportAsSheets = (e) => {
+ const file = e.target.files?.[0];
+ if (!file) return;
+ const reader = new FileReader();
+ reader.onload = (ev) => {
+ const imported = xlsxToWorkbookData(ev.target.result);
+ const wb = univerAPIRef.current?.getActiveWorkbook();
+ if (!wb) return;
+ const current = wb.save();
+ const existingIds = new Set(current.sheetOrder || []);
+ const mergedSheets = { ...(current.sheets || {}) };
+ const mergedOrder = [...(current.sheetOrder || [])];
+ (imported.sheetOrder || []).forEach((importedId) => {
+ let newId = importedId;
+ let counter = 0;
+ while (existingIds.has(newId)) {
+ counter++;
+ newId = `${importedId}_${counter}`;
+ }
+ existingIds.add(newId);
+ mergedSheets[newId] = { ...imported.sheets[importedId], id: newId };
+ mergedOrder.push(newId);
+ });
+ const merged = { ...current, sheets: mergedSheets, sheetOrder: mergedOrder };
+ reinitializeRef.current?.(merged);
+ setTimeout(() => {
+ const wbAfter = univerAPIRef.current?.getActiveWorkbook();
+ if (wbAfter) setDataFn(fieldName, wbAfter.save(), { shouldValidate: true });
+ }, 400);
+ };
+ reader.readAsArrayBuffer(file);
+ e.target.value = '';
+ };
+
+ const templateData = parseWorkbook(template);
+ const inputOnly = (templateData?.gepafin?.editableCells?.length ?? 0) > 0;
+
return (
-
+ <>
+ {label
+ ?
+ {label}{config.required || config.isRequired ?
+ * : null}
+
+ : null}
+ {inputOnly && (
+
+
+ {__('Modalità inserimento dati — modifica solo le celle evidenziate in verde', 'gepafin')}
+
+
+ )}
+ {!inputOnly && (
+
+
+
+ fileInputRef.current?.click()}
+ >
+ {__('Importa foglio', 'gepafin')}
+
+ addSheetsFileInputRef.current?.click()}
+ >
+ {__('Aggiungi fogli', 'gepafin')}
+
+
+ )}
+
+ >
);
};
diff --git a/src/helpers/validators.js b/src/helpers/validators.js
index dcf06c7..f891ee4 100644
--- a/src/helpers/validators.js
+++ b/src/helpers/validators.js
@@ -66,6 +66,28 @@ export const maxChecks = (v, num) => {
return is(Array, v) ? v.length <= parseInt(num) : false;
}
+export const createSpreadsheetValidator = (gepafin) => (workbookValue) => {
+ if (!gepafin || !workbookValue?.sheets) return true;
+ const sheetId = workbookValue.sheetOrder?.[0];
+ const sheet = sheetId
+ ? workbookValue.sheets[sheetId]
+ : Object.values(workbookValue.sheets)[0];
+ const cellData = sheet?.cellData ?? {};
+
+ const getCellValue = (key) => {
+ const [r, c] = key.split(':');
+ return cellData[r]?.[c]?.v;
+ };
+
+ for (const { key, errorMessage } of (gepafin.validationCells ?? [])) {
+ const v = getCellValue(key);
+ if (v === undefined || v === null || v === 0 || v === false || v === 'FALSE' || v === '') {
+ return errorMessage || 'Validazione foglio di calcolo fallita';
+ }
+ }
+ return true;
+};
+
export const nonEmptyTables = (v = [], tableCfg = []) => {
const colsCfg = pathOr([], ['stateFieldData'], tableCfg);
const nonPredefinedCells = colsCfg
diff --git a/src/pages/BandoApplication/index.js b/src/pages/BandoApplication/index.js
index e33c453..b1f0518 100644
--- a/src/pages/BandoApplication/index.js
+++ b/src/pages/BandoApplication/index.js
@@ -25,7 +25,7 @@ import {
isEmail,
isEmailPEC,
isUrl,
- isMarcaDaBollo, minChecks, maxChecks, nonEmptyTables
+ isMarcaDaBollo, minChecks, maxChecks, nonEmptyTables, createSpreadsheetValidator
} from '../../helpers/validators';
import renderHtmlContent from '../../helpers/renderHtmlContent';
import set404FromErrorResponse from '../../helpers/set404FromErrorResponse';
@@ -761,6 +761,14 @@ const BandoApplication = () => {
return acc;
}, {});
+ if (o.name === 'spreadsheet') {
+ const gepafin = template?.value?.gepafin;
+ if (gepafin?.validationCells?.length) {
+ if (!validations.validate) validations.validate = {};
+ validations.validate.spreadsheetInputValid = createSpreadsheetValidator(gepafin);
+ }
+ }
+
/*if (o.name === 'fileselect') {
console.log('options::', options)
}*/
diff --git a/src/pages/BandoEdit/components/BandoEditFormStep3/index.js b/src/pages/BandoEdit/components/BandoEditFormStep3/index.js
index 4c9412e..afc9dc9 100644
--- a/src/pages/BandoEdit/components/BandoEditFormStep3/index.js
+++ b/src/pages/BandoEdit/components/BandoEditFormStep3/index.js
@@ -95,7 +95,6 @@ const BandoEditFormStep3 = forwardRef(function () {
const getFormsCallback = (resp) => {
if (resp.status === 'SUCCESS') {
const EXCLUDED_TYPES = new Set(['fileupload', 'fileselect', 'spreadsheet']);
- console.log('resp.data', resp.data)
const raw = (resp.data ?? []).flatMap(form =>
(form.content ?? [])
.filter(f => !EXCLUDED_TYPES.has(f.name))
@@ -110,7 +109,6 @@ const BandoEditFormStep3 = forwardRef(function () {
if (!byLabel.has(f.label)) byLabel.set(f.label, { label: f.label, ids: [], placeholder: f.placeholder });
byLabel.get(f.label).ids.push(f.id);
});
- console.log('byLabel', Array.from(byLabel.values()))
storeSet('callFormFields', Array.from(byLabel.values()));
}
}
diff --git a/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSetting/index.js b/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSetting/index.js
index 02ccccf..5b817c8 100644
--- a/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSetting/index.js
+++ b/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSetting/index.js
@@ -26,7 +26,7 @@ import getTokens from '../../../../../../helpers/getTokens';
const ElementSettingSpreadsheet = React.lazy(() => import('../ElementSettingSpreadsheet'));
-const ElementSetting = ({ setting, changeFn, updateDataFn, bandoStatus }) => {
+const ElementSetting = ({ setting, changeFn, updateDataFn, bandoStatus, context }) => {
const [existingVars, setExistingVars] = useState([]);
const documentCategories = useStoreValue('documentCategories');
@@ -163,6 +163,7 @@ const ElementSetting = ({ setting, changeFn, updateDataFn, bandoStatus }) => {
;
} else if (setting.name === 'formula') {
diff --git a/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSettingSpreadsheet/index.js b/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSettingSpreadsheet/index.js
index 52ebc16..aa7625d 100644
--- a/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSettingSpreadsheet/index.js
+++ b/src/pages/BandoFormsEdit/components/BuilderElementSettings/components/ElementSettingSpreadsheet/index.js
@@ -14,7 +14,7 @@ import { xlsxToWorkbookData } from '../../../../../BandoEdit/components/BandoEdi
const TAG_RE = /^\{\{gepafin_field:([^|]+)\|([^}]+)}}$/;
-const ElementSettingSpreadsheet = ({ value, name, setDataFn }) => {
+const ElementSettingSpreadsheet = ({ value, name, setDataFn, context }) => {
const callFormFields = useStoreValue('callFormFields');
const [tagTooltip, setTagTooltip] = useState(null);
const containerRef = useRef(null);
@@ -27,6 +27,10 @@ const ElementSettingSpreadsheet = ({ value, name, setDataFn }) => {
const taggedCellsRef = useRef(new Set());
const saveTimerRef = useRef(null);
const setDataFnRef = useRef(setDataFn);
+ const gepafinMetaRef = useRef({ inputOnlyMode: false, editableCells: [], validationCells: [] });
+ const contextRef = useRef(context);
+ const addSheetsFileInputRef = useRef(null);
+ const [inputOnlyActive, setInputOnlyActive] = useState(false);
useEffect(() => { setDataFnRef.current = setDataFn; }, [setDataFn]);
@@ -43,6 +47,70 @@ const ElementSettingSpreadsheet = ({ value, name, setDataFn }) => {
range.setBackground('#dbeafe');
};
+ const getActiveCellKey = () => {
+ const api = univerAPIRef.current;
+ if (!api) return null;
+ const range = api.getActiveWorkbook()?.getActiveSheet()?.getActiveRange();
+ if (!range) return null;
+ return `${range.getRow()}:${range.getColumn()}`;
+ };
+
+ const saveWithMeta = useCallback(() => {
+ const wb = univerAPIRef.current?.getActiveWorkbook();
+ if (wb) {
+ const raw = wb.save();
+ const meta = gepafinMetaRef.current;
+ // inputOnlyMode is derived: active whenever any editable cells are defined
+ setDataFnRef.current(name, {
+ ...raw,
+ gepafin: { ...meta, inputOnlyMode: meta.editableCells.length > 0 }
+ });
+ }
+ }, [name]);
+
+ const toggleEditableCell = useCallback(() => {
+ const key = getActiveCellKey();
+ if (!key) return;
+ const meta = gepafinMetaRef.current;
+ const api = univerAPIRef.current;
+ const sheet = api?.getActiveWorkbook()?.getActiveSheet();
+ const [r, c] = key.split(':').map(Number);
+
+ if (meta.editableCells.includes(key)) {
+ meta.editableCells = meta.editableCells.filter(k => k !== key);
+ sheet?.getRange(r, c, 1, 1).setBackground('');
+ } else {
+ meta.editableCells = [...meta.editableCells, key];
+ sheet?.getRange(r, c, 1, 1).setBackground('#dcfce7');
+ }
+ setInputOnlyActive(meta.editableCells.length > 0);
+ saveWithMeta();
+ }, [saveWithMeta]);
+
+ const toggleValidationCell = useCallback(() => {
+ const key = getActiveCellKey();
+ if (!key) return;
+ const meta = gepafinMetaRef.current;
+ const api = univerAPIRef.current;
+ const sheet = api?.getActiveWorkbook()?.getActiveSheet();
+ const [r, c] = key.split(':').map(Number);
+
+ const existing = meta.validationCells.find(vc => (vc.key ?? vc) === key);
+ if (existing) {
+ meta.validationCells = meta.validationCells.filter(vc => (vc.key ?? vc) !== key);
+ sheet?.getRange(r, c, 1, 1).setBackground('');
+ } else {
+ // eslint-disable-next-line no-alert
+ const errorMessage = window.prompt(
+ __('Messaggio di errore di validazione (lascia vuoto per il default):', 'gepafin'),
+ ''
+ ) ?? '';
+ meta.validationCells = [...meta.validationCells, { key, errorMessage: errorMessage || 'Validazione foglio di calcolo fallita' }];
+ sheet?.getRange(r, c, 1, 1).setBackground('#fef3c7');
+ }
+ saveWithMeta();
+ }, [saveWithMeta]);
+
const initializeUniver = useCallback((workbookData) => {
if (univerRef.current) {
univerRef.current.dispose();
@@ -52,6 +120,12 @@ const ElementSettingSpreadsheet = ({ value, name, setDataFn }) => {
if (!containerRef.current) return;
+ // Initialise gepafin metadata from workbook
+ gepafinMetaRef.current = workbookData?.gepafin
+ ? { inputOnlyMode: false, editableCells: [], validationCells: [], ...workbookData.gepafin }
+ : { inputOnlyMode: false, editableCells: [], validationCells: [] };
+ setInputOnlyActive(gepafinMetaRef.current.editableCells.length > 0);
+
const { univer, univerAPI } = createUniver({
locale: LocaleType.EN_US,
locales: {
@@ -84,6 +158,23 @@ const ElementSettingSpreadsheet = ({ value, name, setDataFn }) => {
}
taggedCellsRef.current = initialTagged;
+ // Re-apply gepafin cell background markers after a short delay (Univer needs to settle)
+ setTimeout(() => {
+ const wb = univerAPIRef.current?.getActiveWorkbook();
+ const sheet = wb?.getActiveSheet();
+ if (!sheet) return;
+ const meta = gepafinMetaRef.current;
+ meta.editableCells.forEach((key) => {
+ const [r, c] = key.split(':').map(Number);
+ sheet.getRange(r, c, 1, 1).setBackground('#dcfce7');
+ });
+ meta.validationCells.forEach((vc) => {
+ const key = vc.key ?? vc;
+ const [r, c] = key.split(':').map(Number);
+ sheet.getRange(r, c, 1, 1).setBackground('#fef3c7');
+ });
+ }, 300);
+
// Restore tag cells whose display value was overwritten by raw tag on blur;
// also clear background for cells whose tag formula was removed.
const restoreTagCells = () => {
@@ -152,48 +243,66 @@ const ElementSettingSpreadsheet = ({ value, name, setDataFn }) => {
let restoreTimer;
- // Disable adding new sheets; auto-save on every command; schedule tag restoration
+ // Auto-save on every command; schedule tag restoration
univerAPI.addEvent(univerAPI.Event.BeforeCommandExecute, (event) => {
- if (event.id === 'sheet.command.insert-sheet') {
- event.cancel = true;
- }
clearTimeout(saveTimerRef.current);
saveTimerRef.current = setTimeout(() => {
const wb = univerAPIRef.current?.getActiveWorkbook();
- if (wb) setDataFnRef.current(name, wb.save());
+ if (wb) {
+ const raw = wb.save();
+ const m = gepafinMetaRef.current;
+ setDataFnRef.current(name, { ...raw, gepafin: { ...m, inputOnlyMode: m.editableCells.length > 0 } });
+ }
}, 300);
clearTimeout(restoreTimer);
restoreTimer = setTimeout(restoreTagCells, 80);
});
- // Context menu: "Inserisci variabile GEPAFIN"
- const fields = formFieldsRef.current;
- if (fields.length > 0) {
- const submenu = univerAPI.createSubmenu({
- id: 'gepafin-insert-variable',
- title: 'Inserisci variabile GEPAFIN',
- });
+ // Context menu: "Inserisci variabile GEPAFIN" — only for evaluation form builder (context="call")
+ if (contextRef.current === 'call') {
+ const fields = formFieldsRef.current;
+ if (fields.length > 0) {
+ const submenu = univerAPI.createSubmenu({
+ id: 'gepafin-insert-variable',
+ title: 'Inserisci variabile GEPAFIN',
+ });
- fields.forEach((field, index) => {
- if (index > 0) submenu.addSeparator();
+ fields.forEach((field, index) => {
+ if (index > 0) submenu.addSeparator();
- const menuKey = field.ids[0];
- const countLabel = field.ids.length > 1 ? ` [${field.ids.length}]` : '';
+ const menuKey = field.ids[0];
+ const countLabel = field.ids.length > 1 ? ` [${field.ids.length}]` : '';
- submenu.addSubmenu(univerAPI.createMenu({
- id: `gepafin-field-${menuKey}-label`,
- title: `${field.label}${countLabel} (etichetta)`,
- action: () => insertFieldTag(field.ids, 'label', field),
- }));
+ submenu.addSubmenu(univerAPI.createMenu({
+ id: `gepafin-field-${menuKey}-label`,
+ title: `${field.label}${countLabel} (etichetta)`,
+ action: () => insertFieldTag(field.ids, 'label', field),
+ }));
- submenu.addSubmenu(univerAPI.createMenu({
- id: `gepafin-field-${menuKey}-value`,
- title: `${field.label}${countLabel} (valore)`,
- action: () => insertFieldTag(field.ids, 'value', field),
- }));
- });
+ submenu.addSubmenu(univerAPI.createMenu({
+ id: `gepafin-field-${menuKey}-value`,
+ title: `${field.label}${countLabel} (valore)`,
+ action: () => insertFieldTag(field.ids, 'value', field),
+ }));
+ });
- submenu.appendTo(['contextMenu.mainArea', 'contextMenu.others']);
+ submenu.appendTo(['contextMenu.mainArea', 'contextMenu.others']);
+ }
+ }
+
+ // Context menu: input-only mode management — only for regular form builder (context="application")
+ if (contextRef.current === 'application') {
+ univerAPI.createMenu({
+ id: 'gepafin-toggle-editable-cell',
+ title: 'Modalità inserimento: segna/rimuovi cella modificabile',
+ action: toggleEditableCell,
+ }).appendTo(['contextMenu.mainArea', 'contextMenu.others']);
+
+ univerAPI.createMenu({
+ id: 'gepafin-toggle-validation-cell',
+ title: 'Modalità inserimento: segna/rimuovi cella di validazione',
+ action: toggleValidationCell,
+ }).appendTo(['contextMenu.mainArea', 'contextMenu.others']);
}
// Tooltip on hover over tagged cells — read formula field to detect tag
@@ -213,7 +322,7 @@ const ElementSettingSpreadsheet = ({ value, name, setDataFn }) => {
}
});
- }, []); // refs are stable
+ }, [toggleEditableCell, toggleValidationCell]); // refs are stable
// Keep formFieldsRef in sync with store value; rebuild Univer (and its context menu)
// once fields arrive, because the menu is constructed at init time from formFieldsRef.current.
@@ -222,7 +331,11 @@ const ElementSettingSpreadsheet = ({ value, name, setDataFn }) => {
formFieldsRef.current = callFormFields;
if (callFormFields.length > 0 && univerAPIRef.current) {
const currentWorkbook = univerAPIRef.current.getActiveWorkbook()?.save() ?? null;
- initializeUniver(currentWorkbook);
+ // Preserve gepafin metadata across reinit
+ const withMeta = currentWorkbook
+ ? { ...currentWorkbook, gepafin: gepafinMetaRef.current }
+ : null;
+ initializeUniver(withMeta);
}
}, [callFormFields, initializeUniver]);
@@ -242,8 +355,6 @@ const ElementSettingSpreadsheet = ({ value, name, setDataFn }) => {
}, []); // run once on mount
// Flush workbook data to parent immediately when the user clicks outside the spreadsheet.
- // This runs in the capture phase — before the clicked element's own handler — so the
- // parent's store is up to date by the time any outer "Salva" button handler reads it.
useEffect(() => {
const container = containerRef.current;
if (!container) return;
@@ -251,11 +362,15 @@ const ElementSettingSpreadsheet = ({ value, name, setDataFn }) => {
if (container.contains(e.target)) return;
clearTimeout(saveTimerRef.current);
const wb = univerAPIRef.current?.getActiveWorkbook();
- if (wb) setDataFnRef.current(name, wb.save());
+ if (wb) {
+ const raw = wb.save();
+ const m = gepafinMetaRef.current;
+ setDataFnRef.current(name, { ...raw, gepafin: { ...m, inputOnlyMode: m.editableCells.length > 0 } });
+ }
};
document.addEventListener('click', flushOnExternalClick, true);
return () => document.removeEventListener('click', flushOnExternalClick, true);
- }, [name]); // name is stable ('template')
+ }, [name]);
// Prevent page scroll while hovering over the spreadsheet
useEffect(() => {
@@ -278,13 +393,56 @@ const ElementSettingSpreadsheet = ({ value, name, setDataFn }) => {
e.target.value = '';
};
+ const handleImportAsSheets = (e) => {
+ const file = e.target.files?.[0];
+ if (!file) return;
+ const reader = new FileReader();
+ reader.onload = (ev) => {
+ const imported = xlsxToWorkbookData(ev.target.result);
+ const wb = univerAPIRef.current?.getActiveWorkbook();
+ if (!wb) return;
+ const current = wb.save();
+ const meta = gepafinMetaRef.current;
+ const existingIds = new Set(current.sheetOrder || []);
+ const mergedSheets = { ...(current.sheets || {}) };
+ const mergedOrder = [...(current.sheetOrder || [])];
+ (imported.sheetOrder || []).forEach((importedId) => {
+ let newId = importedId;
+ let counter = 0;
+ while (existingIds.has(newId)) {
+ counter++;
+ newId = `${importedId}_${counter}`;
+ }
+ existingIds.add(newId);
+ mergedSheets[newId] = { ...imported.sheets[importedId], id: newId };
+ mergedOrder.push(newId);
+ });
+ initializeUniver({ ...current, sheets: mergedSheets, sheetOrder: mergedOrder, gepafin: meta });
+ };
+ reader.readAsArrayBuffer(file);
+ e.target.value = '';
+ };
+
const handleSave = () => {
- const workbookData = univerAPIRef.current?.getActiveWorkbook()?.save() ?? {};
- setDataFn(name, workbookData);
+ const wb = univerAPIRef.current?.getActiveWorkbook();
+ if (wb) {
+ const raw = wb.save();
+ const m = gepafinMetaRef.current;
+ setDataFn(name, { ...raw, gepafin: { ...m, inputOnlyMode: m.editableCells.length > 0 } });
+ }
};
return (
+ {context === 'application' &&
+
{__('Celle modificabili (verde)', 'gepafin')} — {__('Clicca destro su una cella e scegli "Segna/rimuovi cella modificabile" per definire quali celle il beneficiario potrà compilare. La modalità inserimento dati si attiva automaticamente non appena viene definita almeno una cella modificabile.', 'gepafin')}
+
{__('Celle di validazione (ambra)', 'gepafin')} — {__('Segna una cella come "cella di validazione" e inserisci una formula che restituisce TRUE/FALSE. Se il risultato non è vero al momento dell\'invio, il campo viene considerato non valido. Esempi di formule utili:', 'gepafin')}
+
+ =NOT(ISBLANK(A1)) — {__('cella A1 obbligatoria', 'gepafin')}
+ =AND(A1>0, A1<100) — {__('valore numerico tra 0 e 100', 'gepafin')}
+ =LEN(TRIM(B2))>0 — {__('testo non vuoto in B2', 'gepafin')}
+
+
}
{
style={{ display: 'none' }}
onChange={handleImport}
/>
+
fileInputRef.current?.click()}
>
{__('Importa foglio', 'gepafin')}
+ addSheetsFileInputRef.current?.click()}
+ >
+ {__('Aggiungi fogli', 'gepafin')}
+
{
.map((o) => )
@@ -260,6 +261,7 @@ const BuilderElementSettings = ({ closeSettingsFn, callStatus, context }) => {
.map((o) => )
@@ -272,6 +274,7 @@ const BuilderElementSettings = ({ closeSettingsFn, callStatus, context }) => {
.map((o) => )
diff --git a/src/pages/BandoFormsPreview/index.js b/src/pages/BandoFormsPreview/index.js
index d81fafc..86d3533 100644
--- a/src/pages/BandoFormsPreview/index.js
+++ b/src/pages/BandoFormsPreview/index.js
@@ -180,6 +180,7 @@ const BandoFormsPreview = () => {
if (!tableColumns) {
tableColumns = head(o.settings.filter(o => o.name === 'criteria_table_columns'));
}
+ const template = head(o.settings.filter(o => o.name === 'template'));
const step = head(o.settings.filter(o => o.name === 'step'));
const mime = head(o.settings.filter(o => o.name === 'mime'));
const formula = head(o.settings.filter(o => o.name === 'formula'));
@@ -233,6 +234,7 @@ const BandoFormsPreview = () => {
sourceId={0}
useGrouping={false}
tableColumns={tableColumns ? tableColumns.value : {}}
+ template={template ? template.value : null}
/>
}) : null}