263 lines
10 KiB
JavaScript
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;
|