- added import individual sheets;
- fixed bugs reated to init of univerjs;
This commit is contained in:
@@ -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 (
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={{ width: '100%', height: '500px' }}
|
||||
/>
|
||||
<>
|
||||
{label
|
||||
? <label htmlFor={fieldName} className={classNames({ 'p-error': errors[fieldName] })}>
|
||||
{label}{config.required || config.isRequired ?
|
||||
<span className="appForm__field--required">*</span> : null}
|
||||
</label>
|
||||
: null}
|
||||
{inputOnly && (
|
||||
<div className="appPageSection__message warning">
|
||||
<span>
|
||||
{__('Modalità inserimento dati — modifica solo le celle evidenziate in verde', 'gepafin')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{!inputOnly && (
|
||||
<div style={{ marginBottom: '8px', display: 'flex', gap: '8px' }}>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept=".xlsx,.xls,.csv,.ods"
|
||||
style={{ display: 'none' }}
|
||||
onChange={handleImport}
|
||||
/>
|
||||
<input
|
||||
ref={addSheetsFileInputRef}
|
||||
type="file"
|
||||
accept=".xlsx,.xls,.csv,.ods"
|
||||
style={{ display: 'none' }}
|
||||
onChange={handleImportAsSheets}
|
||||
/>
|
||||
<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-outlined p-button-sm"
|
||||
onClick={() => addSheetsFileInputRef.current?.click()}
|
||||
>
|
||||
{__('Aggiungi fogli', 'gepafin')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={{ width: '100%', height: '500px' }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user