UNPKG

@blocktion/json-to-table

Version:

A powerful, modular React component for converting JSON data to navigable tables with advanced features like automatic column detection, theming, and sub-table navigation. Part of the Blocktion SaaS project ecosystem.

129 lines (128 loc) 6.5 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { useEffect, useMemo } from "react"; import { useTableData } from "../hooks/useTableData"; import { useColumnGeneration } from "../hooks/useColumnGeneration"; import { useNavigation } from "../hooks/useNavigation"; import { useDataMutation } from "../hooks/useDataMutation"; import { useValidation } from "../hooks/useValidation"; import { ThemeProvider } from "../styles/theme-provider"; import { enhanceColumnsForEditing } from "../utils/columnEnhancer"; import { LoadingSpinner, ErrorMessage, EmptyState, } from "../styles/styled-components"; import { TableContainer } from "./Table/TableContainer"; import { NavigationControls } from "./Navigation/NavigationControls"; import { ValidationProvider } from "./Validation/ValidationProvider"; export const JsonTable = ({ data, className = "", options = {}, theme = "default", showBreadcrumbs = true, onRowClick, onCellClick, customRenderers = {}, // Editing event handlers onDataChange, onRowAdd, onRowDelete, onFieldUpdate, onFieldDelete, onBulkDelete, }) => { const { processedData, isLoading, error } = useTableData(data, options); const { navigationState, navigateToSubTable, navigateBack, navigateToRoot } = useNavigation(processedData?.validated || []); // Enhanced with editing capabilities const { data: editableData, changes, editState, setEditState, deleteRow, addRow, updateField, deleteField, addField, bulkDelete, saveChanges, discardChanges, hasChanges, } = useDataMutation(data, { ...options, onDataChange, onRowAdd, onRowDelete, onFieldUpdate, onFieldDelete, onBulkDelete, }, navigationState.currentData, navigationState.breadcrumb, navigationState.stack.length > 0 ? navigationState.stack[navigationState.stack.length - 1] .rootDocumentIndex : undefined); // Validation const { validateField, validateRow } = useValidation(options.validationRules || []); // Keep navigation root in sync with processed root data when not in a subtable useEffect(() => { if (processedData?.validated && Array.isArray(processedData.validated) && navigationState.stack.length === 0 && navigationState.currentData !== processedData.validated) { navigateToRoot(processedData.validated); } }, [ processedData?.validated, navigationState.stack.length, navigationState.currentData, navigateToRoot, ]); const effectiveData = useMemo(() => { if (!processedData) return null; // Use editable data if editing is enabled and we have editable data, otherwise use navigation data const currentData = options.enableEditing && editableData ? editableData : navigationState.currentData; // If we have no current data, return the original processed data if (!currentData || (Array.isArray(currentData) && currentData.length === 0)) { return processedData; } const isArray = Array.isArray(currentData); let analyzed; if (isArray) { const { ArrayAnalyzer } = require("../utils/arrayUtils"); const analysis = ArrayAnalyzer.analyzeContent(currentData); analyzed = { isArray: true, isObject: false, objectKeys: analysis.type === "objects" ? ArrayAnalyzer.extractAllKeys(analysis.sampleObjects || []) : [], depth: 1, sampleData: currentData.slice(0, 1), arrayContentType: analysis.type, }; } else { analyzed = processedData.analyzed; } return { ...processedData, validated: currentData, raw: currentData, parsed: currentData, analyzed, }; }, [ processedData, navigationState.currentData, editableData, options.enableEditing, ]); const generatedColumns = useColumnGeneration(effectiveData, { maxDepth: options.maxDepth, mergeRepeatedColumns: options.mergeRepeatedColumns, customRenderers, }); const columns = useMemo(() => { return enhanceColumnsForEditing(generatedColumns, options.enableEditing); }, [generatedColumns, options.enableEditing]); const renderContent = () => { // Loading state if (isLoading) { return _jsx(LoadingSpinner, { className: className }); } // Error state if (error) { return (_jsx(ErrorMessage, { className: className, title: "Error Loading Data", message: error })); } // Empty state if (!processedData || !navigationState.currentData || navigationState.currentData.length === 0) { return (_jsx(EmptyState, { className: className, title: navigationState.currentTitle, message: "No data available" })); } // Validation errors if (processedData.metadata.errors.length > 0) { return (_jsx(ErrorMessage, { className: className, title: "Data Validation Error", message: processedData.metadata.errors.join(", ") })); } return (_jsxs("div", { className: `w-full ${className}`, children: [showBreadcrumbs && (_jsx(NavigationControls, { navigationState: navigationState, onNavigateBack: navigateBack })), _jsx(TableContainer, { data: effectiveData, columns: columns, options: options, onRowClick: onRowClick, onCellClick: onCellClick, onNavigateToSubTable: (path, value, title, rowIndex) => { // For root table navigation, pass the row index as root document index const rootDocIndex = navigationState.stack.length === 0 ? rowIndex : undefined; navigateToSubTable(path, value, title, rootDocIndex); }, customRenderers: customRenderers, // New editing props editState: editState, setEditState: setEditState, onDeleteRow: deleteRow, onAddRow: addRow, onUpdateField: updateField, onDeleteField: deleteField, onAddField: addField, validateField: validateField })] })); }; return (_jsx(ThemeProvider, { theme: theme, children: _jsx(ValidationProvider, { rules: options.validationRules || [], children: renderContent() }) })); };