Files
lcp-visualizer/blocks/components/LCPDataGrid.js

265 lines
9.8 KiB
JavaScript

import { useState, useRef, useEffect } from '@wordpress/element';
import { AgGridReact } from 'ag-grid-react';
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
const LCPDataGrid = ({dataset}) => {
// let gridData = [
// { Department2: 'Sheriffs Office 2', Budget: '150000', MeetAt: '12/12/2025', PreferredColor: '#e0e0e0', PostContent: '<div> </div>' },
// { Department2: 'Treasurer2', Budget: '10000', MeetAt: '12-05', PreferredColor: '#232323', PostContent: '<p> </p>' },
// { Department2: 'Assessor2', Budget: '40000', MeetAt: 1737718512, PreferredColor: 'red', PostContent: '<h1> </h1>' },
// ];
const gridData = dataset;
// Helper function to detect the data type of a value
const getDataType = (value) => {
if (typeof value === 'number' && !isNaN(value)) {
return 'number'; // Identifies numerical values
}
return 'text'; // Defaults to 'text' for strings and others
};
// Helper function to detect if a value is a valid CSS color
const isValidCSSColor = (value) => {
const cssColorRegex = /^(#([0-9a-fA-F]{3}){1,2}|[a-zA-Z]+|rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)|rgba\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3}),\s*(0(\.\d+)?|1(\.0+)?)\)|hsl\((\d{1,3}),\s*(\d{1,3})%,\s*(\d{1,3})%\)|hsla\((\d{1,3}),\s*(\d{1,3})%,\s*(\d{1,3})%,\s*(0(\.\d+)?|1(\.0+)?)\))$/i;
return cssColorRegex.test(value);
};
// Helper function to detect if a value contains HTML tags
const isHTML = (value) => {
const htmlRegex = /<([a-z][\s\S]*)>/i; // A simple check for any HTML tag
return htmlRegex.test(value);
};
// Helper function to detect if a value is a valid date
const isDate = (value) => {
const date = new Date(value);
return !isNaN(date.getTime()); // Return true if it's a valid date
};
// Detect the data type of each key by checking all rows
const detectDataTypes = (data) => {
const keys = Object.keys(data[0]);
let dataTypes = {};
keys.forEach((key) => {
// Check all values for the key across all rows
const allValuesAreNumbers = data.every(row => !isNaN(Number(row[key])));
const allValuesAreColors = data.every(row => isValidCSSColor(row[key]));
const allValuesAreHTML = data.every(row => isHTML(row[key]));
const allValuesAreDates = data.every(row => isDate(row[key]));
// If all values for that key are valid numbers, mark it as 'number'
if (allValuesAreNumbers) {
dataTypes[key] = 'number';
}
// If all values for that key are valid CSS colors, mark it as 'color'
else if (allValuesAreColors) {
dataTypes[key] = 'color';
}
// If all values for that key contain HTML, mark it as 'html'
else if (allValuesAreHTML) {
dataTypes[key] = 'html';
}
// If all values for that key are valid dates, mark it as 'date'
else if (allValuesAreDates) {
dataTypes[key] = 'date';
}
// If neither, mark it as 'text'
else {
dataTypes[key] = 'text';
}
});
return dataTypes;
};
// Assuming gridData is already populated
const gridDataTypes = detectDataTypes(gridData); // This will detect the types of each field
console.log(gridDataTypes); // For debugging, will output the detected types for each column
// Helper function to convert index to letters, accounting for 'A1', 'B1', 'C1', etc.
const getColumnLabel = (index) => {
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
let label = '';
while (index >= 0) {
label = alphabet[index % 26] + label;
index = Math.floor(index / 26) - 1;
}
return label;
};
// Data parser for Ag Grid based on detected data types
function lcpDataTypeParser(params) {
const columnName = params.colDef.field; // Get the column field name (e.g., 'Budget', 'MeetAt')
const dataType = gridDataTypes[columnName]; // Get the data type for this column from gridDataTypes
if (params.node.rowPinned) {
// If it's the pinned top row, return the value as-is
return params.newValue;
} else {
// Process based on the detected data type
switch (dataType) {
case 'number':
// If it's a number, return the number or NaN
return Number(params.newValue);
case 'date':
// If it's a date, try to parse the value as a Date
const parsedDate = new Date(params.newValue);
return !isNaN(parsedDate.getTime()) ? parsedDate : params.newValue; // Return parsed date or original value if invalid
case 'color':
// If it's a color, we don't need to change the value (it's already a valid color)
return params.newValue;
case 'html':
// If it's HTML, we can either sanitize or leave the value as is (for now, leaving it as is)
return params.newValue;
default:
// If it's text (or any other type), return the value as-is
return params.newValue;
}
}
}
// Create columnDefs dynamically
const [columnDefs, setColumnDefs] = useState([]);
const [pinnedTopRowData, setPinnedTopRowData] = useState([]);
useEffect(() => {
if (columnDefs.length > 0) return; // Prevent rerun if columnDefs already set
if (gridData.length > 0) {
const keys = Object.keys(gridData[0]); // Get the keys from the first row (Department2, Budget, etc.)
const columns = keys.map((key, index) => {
return {
headerName: getColumnLabel(index), // 'A', 'B', 'C', ...
field: key, // Field will match the key (Department2, Budget, etc.)
valueParser: lcpDataTypeParser,
};
});
// Set the pinned top row data with the actual field names (like 'Department2', 'Budget')
const topRowData = keys.reduce((acc, key) => {
acc[key] = key; // Set key as the value in top row (use field names)
return acc;
}, {});
// Ensure pinnedTopRowData is set with correct data
setPinnedTopRowData([topRowData]);
// Add the row number column as the first column
const rowNumberColumn = {
headerName: '',
valueGetter: (params) => {
const rowIndex = params.node.rowPinned ? params.node.rowIndex + 1 : params.node.rowIndex + 2;
return rowIndex;
},
suppressMovable: true,
editable: false,
filter: false,
sortable: false,
width: 45,
resizable: false,
flex: 0,
cellStyle: { backgroundColor: '#e0e0e0' },
};
setColumnDefs([rowNumberColumn, ...columns]); // Set columns along with row number column
}
}, [gridData, columnDefs]); // R
const [rowData] = useState(gridData);
// AG Grid instance reference
const gridRef = useRef(null);
const [gridApi, setGridApi] = useState(null); // Store grid API in state
// Button click handler to add a new row
const addRow = () => {
if (gridApi) {
const newRow = {}; // Empty Row
// Use the grid API to add the new row
gridApi.applyTransaction({ add: [newRow] });
} else {
console.log('Grid is not ready yet');
}
};
// Default Column Properties
const defaultColDef = {
flex: 1,
minWidth: 45,
editable: true,
sortable: true,
filter: true,
suppressMovable: true,
cellDataType: false, //By default column can be any data type
cellStyle: (params) => {
if (params.node.rowPinned) {
return { backgroundColor: 'rgb(197, 219, 229)' };
}
}
};
const gridOptions = {
rowDragManaged: true,
animateRows: true,
rowHeight: 35,
getRowStyle: (params) => {
if (params.node.rowPinned) {
return { backgroundColor: 'rgb(197, 219, 229)' }; // Light gray for pinned rows
}
return null; // No special style for non-pinned rows
}
};
const onGridReady = (params) => {
// Store the grid API when the grid is ready
setGridApi(params.api);
};
// Reset row number on sorted
const onSortChanged = (e) => {
e.api.refreshCells();
};
// Reset row number on filtered
const onFilterChanged = (e) => {
e.api.refreshCells();
};
return (
<div>
<div
id="lcp-data-grid"
className="ag-theme-alpine"
style={{ width: '100%', height: '300px' }}
>
<AgGridReact
ref={gridRef}
columnDefs={columnDefs}
rowData={rowData}
defaultColDef={defaultColDef}
gridOptions={gridOptions}
pinnedTopRowData={pinnedTopRowData}
pinnedLeftColCount={1}
onSortChanged={onSortChanged}
onFilterChanged={onFilterChanged}
onGridReady={onGridReady}
/>
</div>
{/* Button outside the grid */}
<div>
<button onClick={addRow}>Add Row</button>
</div>
</div>
);
};
export default LCPDataGrid;