Files
2026-03-26 14:27:38 +01:00

263 lines
10 KiB
JavaScript

import React, { useCallback, useEffect, useRef, useState } from 'react';
import { __ } from '@wordpress/i18n';
import { isEmpty } from 'ramda';
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';
// store
import { useStoreValue } from '../../../../../../store';
// tools
import { xlsxToWorkbookData } from '../../../../../BandoEdit/components/BandoEditFormStep3Excel/xlsxToWorkbookData';
const TAG_RE = /^\{\{gepafin_field:([^|]+)\|([^}]+)}}$/;
const ElementSettingSpreadsheet = ({ value, name, setDataFn }) => {
const callFormFields = useStoreValue('callFormFields');
const [tagTooltip, setTagTooltip] = useState(null);
const containerRef = useRef(null);
const univerRef = useRef(null);
const univerAPIRef = useRef(null);
const formFieldsRef = useRef([]);
const isRestoringRef = useRef(false);
const mousePos = useRef({ x: 0, y: 0 });
const fileInputRef = useRef(null);
const insertFieldTag = (ids, type, field) => {
const api = univerAPIRef.current;
if (!api) return;
const range = api.getActiveWorkbook()?.getActiveSheet()?.getActiveRange();
if (!range) return;
const displayValue = type === 'value' ? (field.placeholder || '') : field.label;
range.setValues([[{
v: displayValue,
f: `{{gepafin_field:${ids.join(',')}|${type}}}`
}]]);
range.setBackground('#dbeafe');
};
const initializeUniver = useCallback((workbookData) => {
if (univerRef.current) {
univerRef.current.dispose();
univerRef.current = null;
univerAPIRef.current = null;
}
if (!containerRef.current) return;
const { univer, univerAPI } = createUniver({
locale: LocaleType.EN_US,
locales: {
[LocaleType.EN_US]: mergeLocales(UniverPresetSheetsCoreEnUS),
},
presets: [
UniverSheetsCorePreset({
container: containerRef.current,
}),
],
});
univerAPI.createWorkbook(workbookData || { name: 'Sheet' });
univerRef.current = univer;
univerAPIRef.current = univerAPI;
// Restore tag cells whose display value was overwritten by raw tag on blur
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]) => {
const match = typeof cell.v === 'string' && cell.v.match(TAG_RE);
if (match) {
const tagIds = match[1].split(',');
const type = match[2];
const field = formFieldsRef.current.find(f =>
f.ids.some(id => tagIds.includes(String(id)))
);
const displayValue = type === 'value'
? (field?.placeholder || '')
: (field?.label || match[1]);
restorations.push({ r: +r, c: +c, v: displayValue, f: cell.v });
}
});
});
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;
// Disable adding new sheets; also schedule tag-cell restoration after each command
univerAPI.addEvent(univerAPI.Event.BeforeCommandExecute, (event) => {
if (event.id === 'sheet.command.insert-sheet') {
event.cancel = true;
}
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',
});
fields.forEach((field, index) => {
if (index > 0) submenu.addSeparator();
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}-value`,
title: `${field.label}${countLabel} (valore)`,
action: () => insertFieldTag(field.ids, 'value', field),
}));
});
submenu.appendTo(['contextMenu.mainArea', 'contextMenu.others']);
}
// Tooltip on hover over tagged cells — read formula field to detect tag
univerAPI.addEvent(univerAPI.Event.CellHover, (params) => {
const { row, column, worksheet } = params;
const formula = worksheet?.getRange(row, column)?.getFormulas()?.[0]?.[0];
if (typeof formula === 'string' && formula.startsWith('{{gepafin_field:')) {
const match = formula.match(/\{\{gepafin_field:([^|]+)\|([^}]+)\}\}/);
if (match) {
const tagIds = match[1].split(',');
const field = formFieldsRef.current.find(f => f.ids.some(fid => tagIds.includes(fid)));
const countSuffix = tagIds.length > 1 ? `${tagIds.length} moduli` : '';
setTagTooltip({ label: `${field ? field.label : match[1]} (${match[2]})${countSuffix}` });
}
} else {
setTagTooltip(null);
}
});
}, []); // refs are stable
// Keep formFieldsRef in sync with store value
useEffect(() => {
formFieldsRef.current = callFormFields;
}, [callFormFields]);
// Initialize Univer on mount
useEffect(() => {
const workbook = value && !isEmpty(value) && value.sheets ? value : null;
initializeUniver(workbook);
return () => {
if (univerRef.current) {
univerRef.current.dispose();
univerRef.current = null;
univerAPIRef.current = null;
}
};
}, []); // run once on mount
// Prevent page scroll while hovering over the spreadsheet
useEffect(() => {
const el = containerRef.current;
if (!el) return;
const handler = (e) => { e.stopPropagation(); e.preventDefault(); };
el.addEventListener('wheel', handler, { passive: false });
return () => el.removeEventListener('wheel', handler);
}, []);
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);
initializeUniver(data);
};
reader.readAsArrayBuffer(file);
e.target.value = '';
};
const handleSave = () => {
const workbookData = univerAPIRef.current?.getActiveWorkbook()?.save() ?? {};
setDataFn(name, workbookData);
};
return (
<div style={{ width: '100%' }}>
<div style={{ marginBottom: '8px', display: 'flex', gap: '8px', justifyContent: 'flex-end' }}>
<input
ref={fileInputRef}
type="file"
accept=".xlsx,.xls,.csv,.ods"
style={{ display: 'none' }}
onChange={handleImport}
/>
<button
type="button"
className="p-button p-button-outlined p-button-sm"
onClick={() => fileInputRef.current?.click()}
>
{__('Importa foglio', 'gepafin')}
</button>
<button
type="button"
className="p-button p-button-sm"
onClick={handleSave}
>
{__('Salva template', 'gepafin')}
</button>
</div>
<div
ref={containerRef}
style={{ width: '100%', height: '500px' }}
onMouseMove={(e) => { mousePos.current = { x: e.clientX, y: e.clientY }; }}
onMouseLeave={() => setTagTooltip(null)}
/>
{tagTooltip && (
<div style={{
position: 'fixed',
top: mousePos.current.y - 36,
left: mousePos.current.x + 12,
background: '#1e293b',
color: '#fff',
padding: '4px 8px',
borderRadius: '4px',
fontSize: '12px',
pointerEvents: 'none',
zIndex: 9999,
whiteSpace: 'nowrap',
}}>
{tagTooltip.label}
</div>
)}
</div>
);
};
export default ElementSettingSpreadsheet;