// import React, { useRef, useState, useEffect } from 'react'
// import { HotTable } from '@handsontable/react'
// import 'handsontable/dist/handsontable.full.min.css'
// import Handsontable from 'handsontable'
// import {
// registerControl,
// RegisterControlProps,
// RegisteredControlProperties,
// } from '../hoc/registerControl'
// import { CustomContentRenderer } from './utils/customButtonRenderer'
// import { hyperformulaInstance, sheetId } from './utils/hyperformulaConfig'
// import { reformatDate, reformatCurrency } from './utils/formatters'
// import { SpreadsheetProperties } from '@components/features/FormBuilder/FormBuilderFieldProperties/Spreadsheet/types'
// import { registerAllCellTypes } from 'handsontable/cellTypes'
// import { registerAllPlugins } from 'handsontable/plugins'
// import { useFormBuilderWorksheetGrid } from '../../hooks/useFormBuilderWorksheetGrid'
// import { createSpreadsheetDefaultValue } from '../../FormBuilderFieldProperties/Spreadsheet/constants'
// import { useFormBuilder } from '../../hooks/useFormBuilder'
// import { useFormService } from '../../hooks/useFormService'
// registerAllCellTypes()
// registerAllPlugins()
// type Properties = RegisteredControlProperties &
// SpreadsheetProperties & {
// externalVariables?: {
// [key: string]: {
// propertyName?: string
// defaultValue?: number
// }
// }
// }
// const SpreadsheetControl: React.FC<RegisterControlProps<Properties>> = (
// props
// ) => {
// const hotTableRef = useRef<any>(null)
// const containerRef = useRef<HTMLDivElement>(null)
// const { rows = 1, columns = 1 } = props.properties || {}
// const DEFAULT_COLUMN_WIDTH = 100
// const DEFAULT_ROW_HEIGHT = 25
// const defaultMeta = {
// data: createSpreadsheetDefaultValue(rows, columns),
// cellFormats: {} as { [key: string]: 'date' | 'currency' | undefined },
// mergeCellsConfig: [],
// }
// const meta = props.properties?.meta || defaultMeta
// const { actions: formBuilderWorksheetActions } = useFormBuilderWorksheetGrid()
// const { actions, data: formBuilderData } = useFormBuilder()
// const { watch } = useFormService()
// const formData = watch()
// if (!actions || !actions.getAllFields) {
// }
// const [data, setData] = useState(() => JSON.parse(JSON.stringify(meta.data)))
// const [cellFormats, setCellFormats] = useState(meta.cellFormats)
// const [mergeCellsConfig, setMergeCellsConfig] = useState(
// meta.mergeCellsConfig
// )
// useEffect(() => {
// if (formBuilderData?.fields) {
// const existingExpressions = hyperformulaInstance.listNamedExpressions()
// formBuilderData.fields.forEach((field) => {
// const propertyName = field.properties?.propertyName
// const defaultValue = Number(field.properties?.defaultValue || 0)
// if (propertyName) {
// if (!existingExpressions.includes(propertyName)) {
// hyperformulaInstance.addNamedExpression(propertyName, defaultValue)
// } else {
// hyperformulaInstance.changeNamedExpression(
// propertyName,
// defaultValue
// )
// }
// }
// })
// hyperformulaInstance.rebuildAndRecalculate()
// }
// }, [formBuilderData])
// useEffect(() => {
// if (props.externalVariables) {
// const existingExpressions = new Set(
// hyperformulaInstance.listNamedExpressions()
// )
// Object.entries(props.externalVariables).forEach(([key, property]) => {
// const typedProperty = property as {
// propertyName?: string
// defaultValue?: number
// }
// const variableName = typedProperty.propertyName || key
// const value = Number(typedProperty.defaultValue || 0)
// if (!existingExpressions.has(variableName)) {
// try {
// hyperformulaInstance.addNamedExpression(
// variableName,
// value,
// sheetId
// )
// existingExpressions.add(variableName)
// } catch (error) {
// console.error(
// `Error adding named expression ${variableName}:`,
// error
// )
// }
// } else {
// try {
// hyperformulaInstance.changeNamedExpression(
// variableName,
// value,
// sheetId
// )
// } catch (error) {
// console.error(
// `Error updating named expression ${variableName}:`,
// error
// )
// }
// }
// })
// try {
// hyperformulaInstance.rebuildAndRecalculate()
// } catch (error) {
// console.error(
// 'Error during HyperFormula rebuild and recalculation:',
// error
// )
// }
// }
// }, [props.externalVariables])
// useEffect(() => {
// if (formBuilderData) {
// const existingExpressions = hyperformulaInstance.listNamedExpressions()
// Object.entries(formBuilderData).forEach(([key, value]) => {
// const numericValue = Number(value) || 0
// if (!existingExpressions.includes(key)) {
// hyperformulaInstance.addNamedExpression(key, numericValue)
// } else {
// hyperformulaInstance.changeNamedExpression(key, numericValue)
// }
// })
// hyperformulaInstance.rebuildAndRecalculate()
// }
// }, [formBuilderData])
// useEffect(() => {
// if (formBuilderData?.fields) {
// const existingExpressions = new Set(
// hyperformulaInstance.listNamedExpressions()
// )
// formBuilderData.fields.forEach((field) => {
// const propertyName = field.properties?.propertyName
// const defaultValue = Number(field.properties?.defaultValue || 0)
// if (propertyName && !existingExpressions.has(propertyName)) {
// try {
// hyperformulaInstance.addNamedExpression(propertyName, defaultValue)
// existingExpressions.add(propertyName)
// } catch (error) {
// console.error(
// `Error adding named expression for ${propertyName}: ${error}`
// )
// }
// } else if (propertyName) {
// try {
// hyperformulaInstance.changeNamedExpression(
// propertyName,
// defaultValue
// )
// } catch (error) {
// console.error(
// `Error updating named expression for ${propertyName}: ${error}`
// )
// }
// }
// })
// try {
// hyperformulaInstance.rebuildAndRecalculate()
// } catch (error) {
// console.error(
// 'Error during HyperFormula rebuild and recalculation:',
// error
// )
// }
// }
// }, [formBuilderData?.fields])
// useEffect(() => {
// if (formData && Object.keys(formData).length > 0) {
// const existingExpressions = hyperformulaInstance.listNamedExpressions()
// Object.entries(formData).forEach(([key, value]) => {
// if (!existingExpressions.includes(key)) {
// hyperformulaInstance.addNamedExpression(key, Number(value) || 0)
// } else {
// hyperformulaInstance.changeNamedExpression(key, Number(value) || 0)
// }
// })
// try {
// hyperformulaInstance.rebuildAndRecalculate()
// } catch (error) {}
// }
// }, [formData])
// useEffect(() => {
// const adjustedData = adjustDataDimensions(data, rows, columns)
// if (JSON.stringify(data) !== JSON.stringify(adjustedData)) {
// setData(adjustedData)
// }
// }, [rows, columns])
// const adjustDataDimensions = (
// currentData: any[][],
// rows: number,
// columns: number
// ) => {
// const newData = currentData.map((row) => [...row])
// if (newData.length < rows) {
// for (let i = newData.length; i < rows; i++) {
// newData.push(Array(columns).fill(null))
// }
// } else if (newData.length > rows) {
// newData.length = rows
// }
// newData.forEach((row) => {
// if (row.length < columns) {
// row.push(...Array(columns - row.length).fill(null))
// } else if (row.length > columns) {
// row.length = columns
// }
// })
// return newData
// }
// const handleAfterChange = (changes: any, source: string) => {
// if (!changes || source === 'loadData') return
// const validSheetId = sheetId ?? 0
// changes.forEach(([row, col, oldValue, newValue]: any) => {
// if (
// newValue &&
// typeof newValue === 'string' &&
// newValue.startsWith('=')
// ) {
// try {
// hyperformulaInstance.setCellContents(
// { sheet: validSheetId, row, col },
// [[newValue]]
// )
// } catch (error) {
// console.error('Error setting formula:', error)
// }
// }
// })
// const updatedData = hotTableRef.current?.hotInstance?.getData()
// if (JSON.stringify(data) !== JSON.stringify(updatedData)) {
// setData(updatedData)
// updateFieldProperties({ data: updatedData })
// }
// }
// const handleMergeCells = () => {
// const hotInstance = hotTableRef.current?.hotInstance
// const selected = hotInstance?.getSelected()
// if (selected) {
// const [startRow, startCol, endRow, endCol] = selected[0]
// const newMerge = {
// row: startRow,
// col: startCol,
// rowspan: endRow - startRow + 1,
// colspan: endCol - startCol + 1,
// }
// const updatedMergeCellsConfig = [...mergeCellsConfig, newMerge]
// hotInstance.updateSettings({ mergeCells: updatedMergeCellsConfig })
// setMergeCellsConfig(updatedMergeCellsConfig)
// updateFieldProperties({
// data,
// cellFormats,
// mergeCellsConfig: updatedMergeCellsConfig,
// })
// }
// }
// const handleUnmergeCells = () => {
// const hotInstance = hotTableRef.current?.hotInstance
// const selected = hotInstance?.getSelected()
// if (selected) {
// const [startRow, startCol] = selected[0]
// const mergeCellsPlugin = hotInstance.getPlugin('mergeCells')
// mergeCellsPlugin.unmerge(startRow, startCol)
// const updatedMergeCellsConfig = mergeCellsConfig.filter(
// (cell) => cell.row !== startRow || cell.col !== startCol
// )
// hotInstance.updateSettings({ mergeCells: updatedMergeCellsConfig })
// setMergeCellsConfig(updatedMergeCellsConfig)
// updateFieldProperties({
// data,
// cellFormats,
// mergeCellsConfig: updatedMergeCellsConfig,
// })
// }
// }
// const handleSetAsDate = () => {
// const selected = hotTableRef.current?.hotInstance.getSelected()
// if (selected) {
// const [row, col] = selected[0]
// const newFormats = { ...cellFormats, [`${row}-${col}`]: 'date' as const }
// setCellFormats(newFormats)
// updateFieldProperties({ data, cellFormats: newFormats, mergeCellsConfig })
// }
// }
// const handleSetAsCurrency = () => {
// const selected = hotTableRef.current?.hotInstance.getSelected()
// if (selected) {
// const [row, col] = selected[0]
// const newFormats = {
// ...cellFormats,
// [`${row}-${col}`]: 'currency' as const,
// }
// setCellFormats(newFormats)
// updateFieldProperties({ data, cellFormats: newFormats, mergeCellsConfig })
// }
// }
// const updateFieldProperties = (
// updatedMeta: Partial<SpreadsheetProperties['meta']>
// ) => {
// if (!props?.runtime) {
// const safeData = data ?? []
// formBuilderWorksheetActions?.setFieldConfigProperty(props.config?.id!, {
// meta: {
// data: [...safeData.map((row: any) => (row ? [...row] : []))],
// cellFormats: { ...cellFormats },
// mergeCellsConfig: [...mergeCellsConfig],
// ...updatedMeta,
// },
// } as Properties)
// }
// }
// const cellsHandler = (row: number, col: number) => {
// const cellProperties = {} as Handsontable.CellProperties
// cellProperties.width = DEFAULT_COLUMN_WIDTH
// cellProperties.height = DEFAULT_ROW_HEIGHT
// // Check if the cell format exists, or default to undefined
// const format = cellFormats?.[`${row}-${col}`]
// if (format === 'date') {
// cellProperties.renderer = (
// instance,
// td,
// row,
// col,
// prop,
// value,
// cellProperties
// ) =>
// CustomContentRenderer(instance, td, row, col, () => {
// const span = document.createElement('span')
// span.innerText = reformatDate(
// instance.getDataAtCell(row, col) as string
// )
// return span
// })
// } else if (format === 'currency') {
// cellProperties.renderer = (
// instance,
// td,
// row,
// col,
// prop,
// value,
// cellProperties
// ) =>
// CustomContentRenderer(instance, td, row, col, () => {
// const span = document.createElement('span')
// span.innerText = reformatCurrency(
// instance.getDataAtCell(row, col) as string
// )
// return span
// })
// }
// return cellProperties
// }
// return (
// <div
// ref={containerRef}
// className="h-[400px] w-1/2 resize-x overflow-auto border md:w-full"
// style={{ minWidth: '50%', maxWidth: '100%', resize: 'horizontal' }}
// onMouseDown={(e) => e.stopPropagation()}
// >
// <HotTable
// id="mySpreadsheet"
// data={data}
// colHeaders={true}
// rowHeaders={true}
// width="100%"
// height="100%"
// selectionMode="multiple"
// copyPaste={true}
// contextMenu={{
// items: {
// row_above: {},
// row_below: {},
// col_left: {},
// col_right: {},
// remove_row: {},
// remove_col: {},
// clear_column: {},
// mergeCells: { name: 'Merge Cells', callback: handleMergeCells },
// unmergeCells: {
// name: 'Unmerge Cells',
// callback: handleUnmergeCells,
// },
// set_as_date: { name: 'Set as Date', callback: handleSetAsDate },
// set_as_currency: {
// name: 'Set as Currency',
// callback: handleSetAsCurrency,
// },
// },
// }}
// ref={hotTableRef}
// afterChange={handleAfterChange}
// persistentState={true}
// licenseKey="non-commercial-and-evaluation"
// manualColumnResize={false}
// manualRowResize={false}
// autoColumnSize={false}
// autoRowSize={false}
// stretchH="all"
// mergeCells={mergeCellsConfig}
// formulas={{ engine: hyperformulaInstance }}
// cells={cellsHandler}
// colWidths={DEFAULT_COLUMN_WIDTH}
// rowHeights={DEFAULT_ROW_HEIGHT}
// />
// </div>
// )
// }
// export default registerControl<Properties>(SpreadsheetControl, {
// noDisplayLabel: true,
// })
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// import React, { useRef, useState, useEffect } from 'react'
// import { HotTable } from '@handsontable/react'
// import 'handsontable/dist/handsontable.full.min.css'
// import Handsontable from 'handsontable'
// import {
// registerControl,
// RegisterControlProps,
// RegisteredControlProperties,
// } from '../hoc/registerControl'
// import { CustomContentRenderer } from './utils/customButtonRenderer'
// import { hyperformulaInstance, sheetId } from './utils/hyperformulaConfig'
// import { reformatDate, reformatCurrency } from './utils/formatters'
// import { SpreadsheetProperties } from '@components/features/FormBuilder/FormBuilderFieldProperties/Spreadsheet/types'
// import { registerAllCellTypes } from 'handsontable/cellTypes'
// import { registerAllPlugins } from 'handsontable/plugins'
// import { useFormBuilderWorksheetGrid } from '../../hooks/useFormBuilderWorksheetGrid'
// import { createSpreadsheetDefaultValue } from '../../FormBuilderFieldProperties/Spreadsheet/constants'
// import { useFormBuilder } from '../../hooks/useFormBuilder'
// import { useFormService } from '../../hooks/useFormService'
// registerAllCellTypes()
// registerAllPlugins()
// type Properties = RegisteredControlProperties &
// SpreadsheetProperties & {
// externalVariables?: {
// [key: string]: {
// propertyName?: string
// defaultValue?: number
// }
// }
// }
// const SpreadsheetControl: React.FC<RegisterControlProps<Properties>> = (
// props
// ) => {
// const hotTableRef = useRef<any>(null)
// const containerRef = useRef<HTMLDivElement>(null)
// const { rows = 1, columns = 1 } = props.properties || {}
// const DEFAULT_COLUMN_WIDTH = 100
// const DEFAULT_ROW_HEIGHT = 25
// const defaultMeta = {
// data: createSpreadsheetDefaultValue(rows, columns),
// cellFormats: {} as { [key: string]: 'date' | 'currency' | undefined },
// mergeCellsConfig: [],
// }
// const meta = props.properties?.meta || defaultMeta
// const { actions: formBuilderWorksheetActions } = useFormBuilderWorksheetGrid()
// const { actions, data: formBuilderData } = useFormBuilder()
// const { watch } = useFormService()
// const formData = watch()
// if (!actions || !actions.getAllFields) {
// }
// const [data, setData] = useState(() => JSON.parse(JSON.stringify(meta.data)))
// const [cellFormats, setCellFormats] = useState(meta.cellFormats)
// const [mergeCellsConfig, setMergeCellsConfig] = useState(
// meta.mergeCellsConfig
// )
// useEffect(() => {
// console.log('Initializing meta from props:', props.properties?.meta)
// const initialMeta = props.properties?.meta || defaultMeta
// setData(JSON.parse(JSON.stringify(initialMeta.data)))
// setCellFormats(initialMeta.cellFormats)
// setMergeCellsConfig(initialMeta.mergeCellsConfig)
// }, [props.properties?.meta])
// useEffect(() => {
// console.log('Data updated:', data)
// }, [data])
// useEffect(() => {
// console.log('Cell formats updated:', cellFormats)
// }, [cellFormats])
// useEffect(() => {
// console.log('Merge cells configuration updated:', mergeCellsConfig)
// }, [mergeCellsConfig])
// useEffect(() => {
// if (formBuilderData?.fields) {
// const existingExpressions = hyperformulaInstance.listNamedExpressions()
// formBuilderData.fields.forEach((field) => {
// const propertyName = field.properties?.propertyName
// const defaultValue = Number(field.properties?.defaultValue || 0)
// if (propertyName) {
// if (!existingExpressions.includes(propertyName)) {
// hyperformulaInstance.addNamedExpression(propertyName, defaultValue)
// } else {
// hyperformulaInstance.changeNamedExpression(
// propertyName,
// defaultValue
// )
// }
// }
// })
// hyperformulaInstance.rebuildAndRecalculate()
// }
// }, [formBuilderData])
// useEffect(() => {
// if (props.externalVariables) {
// const existingExpressions = new Set(
// hyperformulaInstance.listNamedExpressions()
// )
// Object.entries(props.externalVariables).forEach(([key, variable]) => {
// const typedVariable = variable as {
// propertyName?: string
// defaultValue?: number
// }
// const variableName = typedVariable.propertyName || key
// const value = Number(typedVariable.defaultValue || 0)
// if (!existingExpressions.has(variableName)) {
// hyperformulaInstance.addNamedExpression(variableName, value, sheetId)
// } else {
// hyperformulaInstance.changeNamedExpression(
// variableName,
// value,
// sheetId
// )
// }
// })
// hyperformulaInstance.rebuildAndRecalculate()
// syncSpreadsheetWithEngine()
// }
// }, [props.externalVariables])
// useEffect(() => {
// if (formBuilderData) {
// const existingExpressions = hyperformulaInstance.listNamedExpressions()
// Object.entries(formBuilderData).forEach(([key, value]) => {
// const numericValue = Number(value) || 0
// if (!existingExpressions.includes(key)) {
// hyperformulaInstance.addNamedExpression(key, numericValue)
// } else {
// hyperformulaInstance.changeNamedExpression(key, numericValue)
// }
// })
// hyperformulaInstance.rebuildAndRecalculate()
// }
// }, [formBuilderData])
// useEffect(() => {
// if (formBuilderData?.fields) {
// const existingExpressions = new Set(
// hyperformulaInstance.listNamedExpressions()
// )
// formBuilderData.fields.forEach((field) => {
// const propertyName = field.properties?.propertyName
// const defaultValue = Number(field.properties?.defaultValue || 0)
// if (propertyName && !existingExpressions.has(propertyName)) {
// try {
// hyperformulaInstance.addNamedExpression(propertyName, defaultValue)
// existingExpressions.add(propertyName)
// } catch (error) {
// console.error(
// `Error adding named expression for ${propertyName}: ${error}`
// )
// }
// } else if (propertyName) {
// try {
// hyperformulaInstance.changeNamedExpression(
// propertyName,
// defaultValue
// )
// } catch (error) {
// console.error(
// `Error updating named expression for ${propertyName}: ${error}`
// )
// }
// }
// })
// try {
// hyperformulaInstance.rebuildAndRecalculate()
// } catch (error) {
// console.error(
// 'Error during HyperFormula rebuild and recalculation:',
// error
// )
// }
// }
// }, [formBuilderData?.fields])
// useEffect(() => {
// if (formData && Object.keys(formData).length > 0) {
// const existingExpressions = hyperformulaInstance.listNamedExpressions()
// Object.entries(formData).forEach(([key, value]) => {
// if (!existingExpressions.includes(key)) {
// hyperformulaInstance.addNamedExpression(key, Number(value) || 0)
// } else {
// hyperformulaInstance.changeNamedExpression(key, Number(value) || 0)
// }
// })
// try {
// hyperformulaInstance.rebuildAndRecalculate()
// } catch (error) {}
// }
// }, [formData])
// useEffect(() => {
// const adjustedData = adjustDataDimensions(data, rows, columns)
// if (JSON.stringify(data) !== JSON.stringify(adjustedData)) {
// setData(adjustedData)
// updateFieldProperties({ data: adjustedData })
// }
// }, [rows, columns])
// const adjustDataDimensions = (
// currentData: any[][],
// rows: number,
// columns: number
// ) => {
// const newData = currentData.map((row) => [...row])
// if (newData.length < rows) {
// for (let i = newData.length; i < rows; i++) {
// newData.push(Array(columns).fill(null))
// }
// } else if (newData.length > rows) {
// newData.length = rows
// }
// newData.forEach((row) => {
// if (row.length < columns) {
// row.push(...Array(columns - row.length).fill(null))
// } else if (row.length > columns) {
// row.length = columns
// }
// })
// return newData
// }
// const syncSpreadsheetWithEngine = () => {
// if (hotTableRef.current?.hotInstance) {
// const updatedData = hyperformulaInstance.getSheetValues(sheetId)
// console.log('Syncing spreadsheet with engine. Updated Data:', updatedData)
// hotTableRef.current.hotInstance.loadData(updatedData)
// }
// }
// const handleAfterChange = (changes: any, source: string) => {
// console.log('After Change Triggered. Source:', source)
// console.log('Changes:', changes)
// if (!changes || source === 'loadData') return
// const validSheetId = sheetId ?? 0
// changes.forEach(([row, col, oldValue, newValue]: any) => {
// console.log(
// `Updating cell [${row}, ${col}] from ${oldValue} to ${newValue}`
// )
// if (
// newValue &&
// typeof newValue === 'string' &&
// newValue.startsWith('=')
// ) {
// hyperformulaInstance.setCellContents(
// { sheet: validSheetId, row, col },
// [[newValue]]
// )
// } else {
// hyperformulaInstance.setCellContents(
// { sheet: validSheetId, row, col },
// [[newValue || null]]
// )
// }
// })
// // Recalculate and sync data
// hyperformulaInstance.rebuildAndRecalculate()
// console.log(
// 'Recalculated HyperFormula Instance:',
// hyperformulaInstance.getSheetValues(sheetId)
// )
// syncSpreadsheetWithEngine()
// }
// const handleMergeCells = () => {
// const hotInstance = hotTableRef.current?.hotInstance
// const selected = hotInstance?.getSelected()
// if (selected) {
// const [startRow, startCol, endRow, endCol] = selected[0]
// const newMerge = {
// row: startRow,
// col: startCol,
// rowspan: endRow - startRow + 1,
// colspan: endCol - startCol + 1,
// }
// const updatedMergeCellsConfig = [...mergeCellsConfig, newMerge]
// hotInstance.updateSettings({ mergeCells: updatedMergeCellsConfig })
// setMergeCellsConfig(updatedMergeCellsConfig)
// updateFieldProperties({
// data,
// cellFormats,
// mergeCellsConfig: updatedMergeCellsConfig,
// })
// }
// }
// const handleUnmergeCells = () => {
// const hotInstance = hotTableRef.current?.hotInstance
// const selected = hotInstance?.getSelected()
// if (selected) {
// const [startRow, startCol] = selected[0]
// const mergeCellsPlugin = hotInstance.getPlugin('mergeCells')
// mergeCellsPlugin.unmerge(startRow, startCol)
// const updatedMergeCellsConfig = mergeCellsConfig.filter(
// (cell) => cell.row !== startRow || cell.col !== startCol
// )
// hotInstance.updateSettings({ mergeCells: updatedMergeCellsConfig })
// setMergeCellsConfig(updatedMergeCellsConfig)
// updateFieldProperties({
// data,
// cellFormats,
// mergeCellsConfig: updatedMergeCellsConfig,
// })
// }
// }
// const handleSetAsDate = () => {
// const selected = hotTableRef.current?.hotInstance.getSelected()
// if (selected) {
// const [row, col] = selected[0]
// const newFormats = { ...cellFormats, [`${row}-${col}`]: 'date' as const }
// setCellFormats(newFormats)
// updateFieldProperties({ data, cellFormats: newFormats, mergeCellsConfig })
// }
// }
// const handleSetAsCurrency = () => {
// const selected = hotTableRef.current?.hotInstance.getSelected()
// if (selected) {
// const [row, col] = selected[0]
// const newFormats = {
// ...cellFormats,
// [`${row}-${col}`]: 'currency' as const,
// }
// setCellFormats(newFormats)
// updateFieldProperties({ data, cellFormats: newFormats, mergeCellsConfig })
// }
// }
// const updateFieldProperties = (
// updatedMeta: Partial<SpreadsheetProperties['meta']>
// ) => {
// console.log('Updating field properties with meta:', updatedMeta)
// console.log('Current data:', data)
// if (!props.runtime) {
// formBuilderWorksheetActions?.setFieldConfigProperty(props.config?.id!, {
// meta: {
// data: [...data.map((row: any) => (row ? [...row] : []))],
// cellFormats: { ...cellFormats },
// mergeCellsConfig: [...mergeCellsConfig],
// ...updatedMeta,
// },
// } as Properties)
// }
// }
// const cellsHandler = (row: number, col: number) => {
// const cellProperties = {} as Handsontable.CellProperties
// cellProperties.width = DEFAULT_COLUMN_WIDTH
// cellProperties.height = DEFAULT_ROW_HEIGHT
// // Check if the cell format exists, or default to undefined
// const format = cellFormats?.[`${row}-${col}`]
// if (format === 'date') {
// cellProperties.renderer = (
// instance,
// td,
// row,
// col,
// prop,
// value,
// cellProperties
// ) =>
// CustomContentRenderer(instance, td, row, col, () => {
// const span = document.createElement('span')
// span.innerText = reformatDate(
// instance.getDataAtCell(row, col) as string
// )
// return span
// })
// } else if (format === 'currency') {
// cellProperties.renderer = (
// instance,
// td,
// row,
// col,
// prop,
// value,
// cellProperties
// ) =>
// CustomContentRenderer(instance, td, row, col, () => {
// const span = document.createElement('span')
// span.innerText = reformatCurrency(
// instance.getDataAtCell(row, col) as string
// )
// return span
// })
// }
// return cellProperties
// }
// return (
// <div
// ref={containerRef}
// className="h-[400px] w-1/2 resize-x overflow-auto border md:w-full"
// style={{ minWidth: '50%', maxWidth: '100%', resize: 'horizontal' }}
// onMouseDown={(e) => e.stopPropagation()}
// >
// <HotTable
// id="mySpreadsheet"
// data={data}
// colHeaders={true}
// rowHeaders={true}
// width="100%"
// height="100%"
// selectionMode="multiple"
// copyPaste={true}
// contextMenu={{
// items: {
// row_above: {},
// row_below: {},
// col_left: {},
// col_right: {},
// remove_row: {},
// remove_col: {},
// clear_column: {},
// mergeCells: { name: 'Merge Cells', callback: handleMergeCells },
// unmergeCells: {
// name: 'Unmerge Cells',
// callback: handleUnmergeCells,
// },
// set_as_date: { name: 'Set as Date', callback: handleSetAsDate },
// set_as_currency: {
// name: 'Set as Currency',
// callback: handleSetAsCurrency,
// },
// },
// }}
// ref={hotTableRef}
// afterChange={handleAfterChange}
// persistentState={true}
// licenseKey="non-commercial-and-evaluation"
// manualColumnResize={false}
// manualRowResize={false}
// autoColumnSize={false}
// autoRowSize={false}
// stretchH="all"
// mergeCells={mergeCellsConfig}
// formulas={{ engine: hyperformulaInstance }}
// cells={cellsHandler}
// colWidths={DEFAULT_COLUMN_WIDTH}
// rowHeights={DEFAULT_ROW_HEIGHT}
// />
// </div>
// )
// }
// export default registerControl<Properties>(SpreadsheetControl, {
// noDisplayLabel: true,
// })
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
import React, { useRef, useState, useEffect } from 'react'
import { HotTable } from '@handsontable/react'
import 'handsontable/dist/handsontable.full.min.css'
import Handsontable from 'handsontable'
import {
registerControl,
RegisterControlProps,
RegisteredControlProperties,
} from '../hoc/registerControl'
import { CustomContentRenderer } from './utils/customButtonRenderer'
import { hyperformulaInstance, sheetId } from './utils/hyperformulaConfig'
import { reformatDate, reformatCurrency } from './utils/formatters'
import { SpreadsheetProperties } from '@components/features/FormBuilder/FormBuilderFieldProperties/Spreadsheet/types'
import { registerAllCellTypes } from 'handsontable/cellTypes'
import { registerAllPlugins } from 'handsontable/plugins'
import { useFormBuilderWorksheetGrid } from '../../hooks/useFormBuilderWorksheetGrid'
import { createSpreadsheetDefaultValue } from '../../FormBuilderFieldProperties/Spreadsheet/constants'
import { useFormBuilder } from '../../hooks/useFormBuilder'
import { useFormService } from '../../hooks/useFormService'
registerAllCellTypes()
registerAllPlugins()
type Properties = RegisteredControlProperties &
SpreadsheetProperties & {
externalVariables?: {
[key: string]: {
propertyName?: string
defaultValue?: number
}
}
}
const SpreadsheetControl: React.FC<RegisterControlProps<Properties>> = (
props
) => {
const hotTableRef = useRef<any>(null)
const containerRef = useRef<HTMLDivElement>(null)
const { rows = 1, columns = 1 } = props.properties || {}
const DEFAULT_COLUMN_WIDTH = 100
const DEFAULT_ROW_HEIGHT = 25
const defaultMeta = {
data: createSpreadsheetDefaultValue(rows, columns),
cellFormats: {} as { [key: string]: 'date' | 'currency' | undefined },
mergeCellsConfig: [],
}
const meta = props.properties?.meta || defaultMeta
const { actions: formBuilderWorksheetActions } = useFormBuilderWorksheetGrid()
const { actions, data: formBuilderData } = useFormBuilder()
const { watch } = useFormService()
const formData = watch()
if (!actions || !actions.getAllFields) {
}
const [data, setData] = useState(() => JSON.parse(JSON.stringify(meta.data)))
const [cellFormats, setCellFormats] = useState(meta.cellFormats)
const [mergeCellsConfig, setMergeCellsConfig] = useState(
meta.mergeCellsConfig
)
useEffect(() => {
if (props.externalVariables) {
const existingExpressions = new Set(
hyperformulaInstance.listNamedExpressions()
)
Object.entries(props.externalVariables).forEach(([key, property]) => {
const typedProperty = property as {
propertyName?: string
defaultValue?: number
}
const variableName = typedProperty.propertyName || key
const value = Number(typedProperty.defaultValue || 0)
if (!existingExpressions.has(variableName)) {
try {
hyperformulaInstance.addNamedExpression(
variableName,
value,
sheetId
)
existingExpressions.add(variableName)
} catch (error) {
console.error(
`Error adding named expression ${variableName}:`,
error
)
}
} else {
try {
hyperformulaInstance.changeNamedExpression(
variableName,
value,
sheetId
)
} catch (error) {
console.error(
`Error updating named expression ${variableName}:`,
error
)
}
}
})
try {
hyperformulaInstance.rebuildAndRecalculate()
syncSpreadsheetWithEngine()
} catch (error) {
console.error(
'Error during HyperFormula rebuild and recalculation:',
error
)
}
}
}, [props.externalVariables])
const syncSpreadsheetWithEngine = () => {
if (hotTableRef.current?.hotInstance) {
const updatedData = hyperformulaInstance.getSheetValues(sheetId)
console.log('Syncing spreadsheet with engine. Updated Data:', updatedData)
hotTableRef.current.hotInstance.loadData(updatedData)
}
}
useEffect(() => {
const syncSpreadsheetWithEngine = () => {
if (hotTableRef.current?.hotInstance) {
const updatedData = hyperformulaInstance.getSheetValues(sheetId)
console.log(
'Syncing spreadsheet with engine. Updated Data:',
updatedData
)
hotTableRef.current.hotInstance.loadData(updatedData)
}
}
if (formBuilderData) {
const existingExpressions = hyperformulaInstance.listNamedExpressions()
Object.entries(formBuilderData).forEach(([key, value]) => {
const numericValue = Number(value) || 0
if (!existingExpressions.includes(key)) {
hyperformulaInstance.addNamedExpression(key, numericValue)
} else {
hyperformulaInstance.changeNamedExpression(key, numericValue)
}
})
hyperformulaInstance.rebuildAndRecalculate()
syncSpreadsheetWithEngine()
}
}, [formBuilderData])
useEffect(() => {
if (formBuilderData?.fields) {
const existingExpressions = new Set(
hyperformulaInstance.listNamedExpressions()
)
formBuilderData.fields.forEach((field) => {
const propertyName = field.properties?.propertyName
const defaultValue = Number(field.properties?.defaultValue || 0)
if (propertyName && !existingExpressions.has(propertyName)) {
try {
hyperformulaInstance.addNamedExpression(propertyName, defaultValue)
existingExpressions.add(propertyName)
} catch (error) {
console.error(
`Error adding named expression for ${propertyName}: ${error}`
)
}
} else if (propertyName) {
try {
hyperformulaInstance.changeNamedExpression(
propertyName,
defaultValue
)
} catch (error) {
console.error(
`Error updating named expression for ${propertyName}: ${error}`
)
}
}
})
try {
hyperformulaInstance.rebuildAndRecalculate()
} catch (error) {
console.error(
'Error during HyperFormula rebuild and recalculation:',
error
)
}
}
}, [formBuilderData?.fields])
useEffect(() => {
if (formData && Object.keys(formData).length > 0) {
const existingExpressions = hyperformulaInstance.listNamedExpressions()
Object.entries(formData).forEach(([key, value]) => {
if (!existingExpressions.includes(key)) {
hyperformulaInstance.addNamedExpression(key, Number(value) || 0)
} else {
hyperformulaInstance.changeNamedExpression(key, Number(value) || 0)
}
})
try {
hyperformulaInstance.rebuildAndRecalculate()
} catch (error) {}
}
}, [formData])
useEffect(() => {
const adjustedData = adjustDataDimensions(data, rows, columns)
if (JSON.stringify(data) !== JSON.stringify(adjustedData)) {
setData(adjustedData)
}
}, [rows, columns])
const adjustDataDimensions = (
currentData: any[][],
rows: number,
columns: number
) => {
const newData = currentData.map((row) => [...row])
if (newData.length < rows) {
for (let i = newData.length; i < rows; i++) {
newData.push(Array(columns).fill(null))
}
} else if (newData.length > rows) {
newData.length = rows
}
newData.forEach((row) => {
if (row.length < columns) {
row.push(...Array(columns - row.length).fill(null))
} else if (row.length > columns) {
row.length = columns
}
})
return newData
}
const handleAfterChange = (changes: any, source: string) => {
if (!changes || source === 'loadData') return
const validSheetId = sheetId ?? 0
changes.forEach(([row, col, oldValue, newValue]: any) => {
if (
newValue &&
typeof newValue === 'string' &&
newValue.startsWith('=')
) {
try {
hyperformulaInstance.setCellContents(
{ sheet: validSheetId, row, col },
[[newValue]]
)
} catch (error) {
console.error('Error setting formula:', error)
}
}
})
try {
hyperformulaInstance.rebuildAndRecalculate()
syncSpreadsheetWithEngine() // Ensure the spreadsheet reflects changes
} catch (error) {
console.error('Error recalculating formulas:', error)
}
}
const handleMergeCells = () => {
const hotInstance = hotTableRef.current?.hotInstance
const selected = hotInstance?.getSelected()
if (selected) {
const [startRow, startCol, endRow, endCol] = selected[0]
const newMerge = {
row: startRow,
col: startCol,
rowspan: endRow - startRow + 1,
colspan: endCol - startCol + 1,
}
const updatedMergeCellsConfig = [...mergeCellsConfig, newMerge]
hotInstance.updateSettings({ mergeCells: updatedMergeCellsConfig })
setMergeCellsConfig(updatedMergeCellsConfig)
updateFieldProperties({
data,
cellFormats,
mergeCellsConfig: updatedMergeCellsConfig,
})
}
}
const handleUnmergeCells = () => {
const hotInstance = hotTableRef.current?.hotInstance
const selected = hotInstance?.getSelected()
if (selected) {
const [startRow, startCol] = selected[0]
const mergeCellsPlugin = hotInstance.getPlugin('mergeCells')
mergeCellsPlugin.unmerge(startRow, startCol)
const updatedMergeCellsConfig = mergeCellsConfig.filter(
(cell) => cell.row !== startRow || cell.col !== startCol
)
hotInstance.updateSettings({ mergeCells: updatedMergeCellsConfig })
setMergeCellsConfig(updatedMergeCellsConfig)
updateFieldProperties({
data,
cellFormats,
mergeCellsConfig: updatedMergeCellsConfig,
})
}
}
const handleSetAsDate = () => {
const selected = hotTableRef.current?.hotInstance.getSelected()
if (selected) {
const [row, col] = selected[0]
const newFormats = { ...cellFormats, [`${row}-${col}`]: 'date' as const }
setCellFormats(newFormats)
updateFieldProperties({ data, cellFormats: newFormats, mergeCellsConfig })
}
}
const handleSetAsCurrency = () => {
const selected = hotTableRef.current?.hotInstance.getSelected()
if (selected) {
const [row, col] = selected[0]
const newFormats = {
...cellFormats,
[`${row}-${col}`]: 'currency' as const,
}
setCellFormats(newFormats)
updateFieldProperties({ data, cellFormats: newFormats, mergeCellsConfig })
}
}
const updateFieldProperties = (
updatedMeta: Partial<SpreadsheetProperties['meta']>
) => {
if (!props?.runtime) {
const safeData = data ?? []
formBuilderWorksheetActions?.setFieldConfigProperty(props.config?.id!, {
meta: {
data: [...safeData.map((row: any) => (row ? [...row] : []))],
cellFormats: { ...cellFormats },
mergeCellsConfig: [...mergeCellsConfig],
...updatedMeta,
},
} as Properties)
}
}
const cellsHandler = (row: number, col: number) => {
const cellProperties = {} as Handsontable.CellProperties
cellProperties.width = DEFAULT_COLUMN_WIDTH
cellProperties.height = DEFAULT_ROW_HEIGHT
// Check if the cell format exists, or default to undefined
const format = cellFormats?.[`${row}-${col}`]
if (format === 'date') {
cellProperties.renderer = (
instance,
td,
row,
col,
prop,
value,
cellProperties
) =>
CustomContentRenderer(instance, td, row, col, () => {
const span = document.createElement('span')
span.innerText = reformatDate(
instance.getDataAtCell(row, col) as string
)
return span
})
} else if (format === 'currency') {
cellProperties.renderer = (
instance,
td,
row,
col,
prop,
value,
cellProperties
) =>
CustomContentRenderer(instance, td, row, col, () => {
const span = document.createElement('span')
span.innerText = reformatCurrency(
instance.getDataAtCell(row, col) as string
)
return span
})
}
return cellProperties
}
return (
<div
ref={containerRef}
className="h-[400px] w-1/2 resize-x overflow-auto border md:w-full"
style={{ minWidth: '50%', maxWidth: '100%', resize: 'horizontal' }}
onMouseDown={(e) => e.stopPropagation()}
>
<HotTable
id="mySpreadsheet"
data={data}
colHeaders={true}
rowHeaders={true}
width="100%"
height="100%"
selectionMode="multiple"
copyPaste={true}
contextMenu={{
items: {
row_above: {},
row_below: {},
col_left: {},
col_right: {},
remove_row: {},
remove_col: {},
clear_column: {},
mergeCells: { name: 'Merge Cells', callback: handleMergeCells },
unmergeCells: {
name: 'Unmerge Cells',
callback: handleUnmergeCells,
},
set_as_date: { name: 'Set as Date', callback: handleSetAsDate },
set_as_currency: {
name: 'Set as Currency',
callback: handleSetAsCurrency,
},
},
}}
ref={hotTableRef}
afterChange={handleAfterChange}
persistentState={true}
licenseKey="non-commercial-and-evaluation"
manualColumnResize={false}
manualRowResize={false}
autoColumnSize={false}
autoRowSize={false}
stretchH="all"
mergeCells={mergeCellsConfig}
formulas={{ engine: hyperformulaInstance }}
cells={cellsHandler}
colWidths={DEFAULT_COLUMN_WIDTH}
rowHeights={DEFAULT_ROW_HEIGHT}
/>
</div>
)
}
export default registerControl<Properties>(SpreadsheetControl, {
noDisplayLabel: true,
})