Services & Adapters

FortuneSheetAdapter

Adapter che incapsula @fortune-sheet/core. Lifecycle initialize/destroy, snapshot I/O, API celle, undo/redo, funzioni ERP-native, eventi.

Costruttore

class FortuneSheetAdapter implements IFortuneSheetAdapter {
  constructor()
}

Adapter Strategy che è l'unico punto di contatto tra la libreria e @fortune-sheet/core. Tutti i componenti e composables comunicano con il workbook solo tramite questa interfaccia.

Pattern Adapter: il resto della libreria parla solo di FortuneSheetSnapshot (blob opaco), CellValue (primitivi) e SpreadsheetChangeEvent. Nessuna dipendenza diretta su tipi/API di Fortune Sheet. Questo permette test isolati e potenziale sostituzione dell'engine.

Interfaccia IFortuneSheetAdapter

interface IFortuneSheetAdapter {
  // Lifecycle
  initialize(container: HTMLElement): Promise<void>
  destroy(): void

  // Snapshot I/O
  loadSnapshot(snapshot: FortuneSheetSnapshot | null): void
  getSnapshot(): FortuneSheetSnapshot

  // Celle
  getCellValue(row: number, col: number, sheetIndex?: number): CellValue
  setCellValue(row: number, col: number, value: CellValue, sheetIndex?: number): void
  setCellError(row: number, col: number, errorCode: string, sheetIndex?: number): void
  setRange(startRow: number, startCol: number, data: CellValue[][], sheetIndex?: number): void

  // Undo/Redo
  undo(): void
  redo(): void
  canUndo(): boolean
  canRedo(): boolean

  // Funzioni custom ERP
  registerCustomFunctions(functions: ErpFormulaFunction[]): void

  // Eventi
  onChange(handler: (event: SpreadsheetChangeEvent) => void): void
  offChange(): void
}

Lifecycle

initialize(container)

Istanzia un'app Vue contenente FortuneSheetWorkbook e la monta nel container DOM fornito.

const adapter = new FortuneSheetAdapter()
const containerEl = document.querySelector('#workbook-container')
await adapter.initialize(containerEl)
Chiamare una sola volta per istanza. Una seconda initialize() lancia errore. Per re-mount, chiamare destroy() prima.

destroy()

Smonta l'app Vue interna e rilascia riferimenti. Da chiamare in onUnmounted o quando si vuole ri-inizializzare l'adapter.

Snapshot I/O

loadSnapshot(snapshot)

Carica un snapshot JSONB nell'editor. Se snapshot === null, crea un workbook vuoto con un solo tab.

const versione = await fogliService.caricaUltimaVersione(42)
adapter.loadSnapshot(versione?.snapshot ?? null)

Pre-buffering: se loadSnapshot è chiamato prima di initialize, il valore viene memorizzato in pendingSnapshot e applicato al mount del workbook.

getSnapshot()

Estrae lo snapshot corrente in formato Fortune Sheet:

const snapshot = adapter.getSnapshot()
// → Record<string, unknown> — blob opaco da persistere

Usato da useSpreadsheet.save() per ottenere il payload da inviare a salvaVersione.

API celle

const v: CellValue = adapter.getCellValue(0, 0)        // riga 0, col 0, foglio 0
adapter.setCellValue(0, 0, 'Ciao')
adapter.setCellValue(0, 1, 42)
adapter.setCellValue(1, 0, true, 1)                    // foglio 1

CellValue è string | number | boolean | null.

setRange(startRow, startCol, data, sheetIndex?)

Scrive un blocco di celle a partire da (startRow, startCol):

adapter.setRange(0, 0, [
  ['Codice', 'Nome',    'Imponibile'],
  ['F001',   'Cliente A', 1234.56],
  ['F002',   'Cliente B', 987.65],
])

Usato da useSpreadsheetERP per scrivere risultati di una sorgente nelle celle target.

setCellError(row, col, errorCode, sheetIndex?)

Scrive un errore visivo nella cella (es. #ERP_ERR):

adapter.setCellError(0, 0, '#ERP_ERR_FATTURE')

Usato per segnalare errori di fetch ERP senza interrompere il workbook.

Undo/Redo

if (adapter.canUndo()) adapter.undo()
if (adapter.canRedo()) adapter.redo()

Sfruttano lo stack di history di Fortune Sheet. Non richiedono interazione col DB: undo/redo agiscono solo localmente fino al prossimo save().

Funzioni custom ERP

registerCustomFunctions(functions)

Registra funzioni formula ERP-native nel motore di Fortune Sheet:

interface ErpFormulaFunction {
  name: string                                                 // es. 'ERP.FATTURATO'
  handler: (...args: (string | number | null)[]) => Promise<number | string>
}
import { erpFunctions } from '@pzeta/vue-foglidicalcolo/erp-functions'

adapter.registerCustomFunctions(erpFunctions)

Una volta registrate, le funzioni sono disponibili come formule:

=ERP.FATTURATO("V"; "2026-01-01"; "2026-12-31"; 42)
Le funzioni di default in erpFunctions sono placeholder (lanciano Not implemented). Il consumer deve fornire implementazioni concrete. Vedi erp-functions.
Chiamare una sola volta dopo initialize(). La registrazione multipla può causare doppi handler.

Eventi

onChange(handler)

Registra una callback chiamata ad ogni modifica cella, debounced 500ms internamente:

adapter.onChange((event) => {
  console.log(`Cella (${event.row}, ${event.col}) modificata: ${event.value}`)
  console.log(`Foglio ${event.sheetIndex}, dirty: ${event.isDirty}`)
})
interface SpreadsheetChangeEvent {
  sheetIndex: number
  row: number
  col: number
  value: CellValue
  isDirty: boolean
}

useSpreadsheet/SpreadsheetEditor usano questa callback per:

  • aggiornare isDirty.value = true
  • emettere l'evento change verso il consumer

offChange()

Rimuove il handler registrato. Chiamare in onUnmounted per evitare leak.

Esempio standalone

import { FortuneSheetAdapter, erpFunctions } from '@pzeta/vue-foglidicalcolo'

const adapter = new FortuneSheetAdapter()
const container = document.getElementById('workbook')!

// 1. Setup
await adapter.initialize(container)
adapter.registerCustomFunctions(erpFunctions)

// 2. Carica snapshot da localStorage o API
const stored = localStorage.getItem('my-snapshot')
adapter.loadSnapshot(stored ? JSON.parse(stored) : null)

// 3. Scrivi dati
adapter.setRange(0, 0, [
  ['Mese', 'Vendite'],
  ['Gen',  1000],
  ['Feb',  1500],
])

// 4. Ascolta modifiche
adapter.onChange((e) => {
  console.log('modified', e)
})

// 5. Più tardi: salva
const snapshot = adapter.getSnapshot()
localStorage.setItem('my-snapshot', JSON.stringify(snapshot))

// 6. Cleanup
adapter.offChange()
adapter.destroy()

Tipi correlati