Added tabbed interface for mutliple LCPDataGrid.js components
This commit is contained in:
@ -39,6 +39,84 @@
|
|||||||
"type": "number",
|
"type": "number",
|
||||||
"default": 1
|
"default": 1
|
||||||
},
|
},
|
||||||
|
"datasets": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [
|
||||||
|
{
|
||||||
|
"name": "Data",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"Department": "Sheriffs Office",
|
||||||
|
"Budget": "150",
|
||||||
|
"MeetAt": "12/12/2025",
|
||||||
|
"PreferredColor": "#e0e0e0",
|
||||||
|
"PostContent": "<div> </div>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Department": "Assessor",
|
||||||
|
"Budget": "100",
|
||||||
|
"MeetAt": "12/12/2025",
|
||||||
|
"PreferredColor": "#e0e0e0",
|
||||||
|
"PostContent": "<div> </div>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Department": "Treasurer",
|
||||||
|
"Budget": "50",
|
||||||
|
"MeetAt": "12/12/2025",
|
||||||
|
"PreferredColor": "#e0e0e0",
|
||||||
|
"PostContent": "<div> </div>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Locations",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"State": "California",
|
||||||
|
"Coordinates": "150,000",
|
||||||
|
"MeetAt": "12/12/2025",
|
||||||
|
"PreferredColor": "#e0e0e0",
|
||||||
|
"PostContent": "<div> </div>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"State": "Texas",
|
||||||
|
"Coordinates": "100,000",
|
||||||
|
"MeetAt": "12/12/2025",
|
||||||
|
"PreferredColor": "#e0e0e0",
|
||||||
|
"PostContent": "<div> </div>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"State": "Florida",
|
||||||
|
"Coordinates": "50,000",
|
||||||
|
"MeetAt": "12/12/2025",
|
||||||
|
"PreferredColor": "#e0e0e0",
|
||||||
|
"PostContent": "<div> </div>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Dataset 3",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"ID": "lcpDatapoint-1",
|
||||||
|
"Label": "Sample 1",
|
||||||
|
"Value": 100,
|
||||||
|
"Color": "#007cba",
|
||||||
|
"Content": "<p>First item</p>",
|
||||||
|
"Parent": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ID": "lcpDatapoint-2",
|
||||||
|
"Label": "Sample 2",
|
||||||
|
"Value": 50,
|
||||||
|
"Color": "#ff0000",
|
||||||
|
"Content": "<p>Second item</p>",
|
||||||
|
"Parent": "lcpDatapoint-1"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"chartData": {
|
"chartData": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"default": [
|
"default": [
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
<?php return array('dependencies' => array('react', 'react-dom', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n'), 'version' => 'b5bae9f327b824fdd55c');
|
<?php return array('dependencies' => array('react', 'react-dom', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n'), 'version' => 'a6d1538e10aef4aeb8ff');
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -41,6 +41,33 @@
|
|||||||
"type": "number",
|
"type": "number",
|
||||||
"default": 1
|
"default": 1
|
||||||
},
|
},
|
||||||
|
"datasets": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [{
|
||||||
|
"name": "Data",
|
||||||
|
"data": [
|
||||||
|
{ "Department": "Sheriffs Office", "Budget": "150", "MeetAt": "12/12/2025", "PreferredColor": "#e0e0e0", "PostContent": "<div> </div>" },
|
||||||
|
{ "Department": "Assessor", "Budget": "100", "MeetAt": "12/12/2025", "PreferredColor": "#e0e0e0", "PostContent": "<div> </div>" },
|
||||||
|
{ "Department": "Treasurer", "Budget": "50", "MeetAt": "12/12/2025", "PreferredColor": "#e0e0e0", "PostContent": "<div> </div>" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Locations",
|
||||||
|
"data": [
|
||||||
|
{ "State": "California", "Coordinates": "150,000", "MeetAt": "12/12/2025", "PreferredColor": "#e0e0e0", "PostContent": "<div> </div>" },
|
||||||
|
{ "State": "Texas", "Coordinates": "100,000", "MeetAt": "12/12/2025", "PreferredColor": "#e0e0e0", "PostContent": "<div> </div>" },
|
||||||
|
{ "State": "Florida", "Coordinates": "50,000", "MeetAt": "12/12/2025", "PreferredColor": "#e0e0e0", "PostContent": "<div> </div>" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Dataset 3",
|
||||||
|
"data": [
|
||||||
|
{ "ID": "lcpDatapoint-1", "Label": "Sample 1", "Value": 100, "Color": "#007cba", "Content": "<p>First item</p>", "Parent": "" },
|
||||||
|
{ "ID": "lcpDatapoint-2", "Label": "Sample 2", "Value": 50, "Color": "#ff0000", "Content": "<p>Second item</p>", "Parent": "lcpDatapoint-1" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"chartData": {
|
"chartData": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"default": [
|
"default": [
|
||||||
|
|||||||
@ -3,12 +3,14 @@ import { AgGridReact } from 'ag-grid-react';
|
|||||||
import 'ag-grid-community/styles/ag-grid.css';
|
import 'ag-grid-community/styles/ag-grid.css';
|
||||||
import 'ag-grid-community/styles/ag-theme-alpine.css';
|
import 'ag-grid-community/styles/ag-theme-alpine.css';
|
||||||
|
|
||||||
const LCPDataGrid = () => {
|
const LCPDataGrid = ({dataset}) => {
|
||||||
let gridData = [
|
// let gridData = [
|
||||||
{ Department2: 'Sheriffs Office 2', Budget: '150000', MeetAt: '12/12/2025', PreferredColor: '#e0e0e0', PostContent: '<div> </div>' },
|
// { 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: 'Treasurer2', Budget: '10000', MeetAt: '12-05', PreferredColor: '#232323', PostContent: '<p> </p>' },
|
||||||
{ Department2: 'Assessor2', Budget: '40000', MeetAt: 1737718512, PreferredColor: 'red', PostContent: '<h1> </h1>' },
|
// { Department2: 'Assessor2', Budget: '40000', MeetAt: 1737718512, PreferredColor: 'red', PostContent: '<h1> </h1>' },
|
||||||
];
|
// ];
|
||||||
|
|
||||||
|
const gridData = dataset;
|
||||||
// Helper function to detect the data type of a value
|
// Helper function to detect the data type of a value
|
||||||
const getDataType = (value) => {
|
const getDataType = (value) => {
|
||||||
if (typeof value === 'number' && !isNaN(value)) {
|
if (typeof value === 'number' && !isNaN(value)) {
|
||||||
|
|||||||
69
blocks/components/LCPDataGridHeader.js
Normal file
69
blocks/components/LCPDataGridHeader.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
const LCPDataGridHeader = (props) => {
|
||||||
|
const { displayName, column, updateData, sort, menu } = props;
|
||||||
|
|
||||||
|
const [editing, setEditing] = useState(false);
|
||||||
|
const [newHeader, setNewHeader] = useState(displayName);
|
||||||
|
const colId = column.colId;
|
||||||
|
const handleEditClick = () => {
|
||||||
|
setEditing(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBlur = () => {
|
||||||
|
setEditing(false);
|
||||||
|
if (updateData && typeof updateData === 'function') {
|
||||||
|
updateData(newHeader); // Save the new header name
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (e) => {
|
||||||
|
setNewHeader(e.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="custom-header">
|
||||||
|
|
||||||
|
{/* Column ID
|
||||||
|
<div>
|
||||||
|
<span style={{ marginRight: '8px' }}>
|
||||||
|
<strong>{colId}</strong>
|
||||||
|
</span>
|
||||||
|
</div>*/}
|
||||||
|
{/* Editable Header Text */}
|
||||||
|
{/* Icon */}
|
||||||
|
<span style={{ marginRight: '8px' }}>
|
||||||
|
<img src="your-icon-path.svg" alt="icon" style={{ width: '20px', height: '20px' }} />
|
||||||
|
</span>
|
||||||
|
{editing ? (
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={newHeader}
|
||||||
|
onBlur={handleBlur}
|
||||||
|
onChange={handleChange}
|
||||||
|
style={{ fontSize: '14px', padding: '2px 5px', width: '100%' }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
onClick={handleEditClick}
|
||||||
|
style={{ cursor: 'pointer', fontWeight: 'bold', fontSize: '16px' }}
|
||||||
|
>
|
||||||
|
{newHeader}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Sorting *
|
||||||
|
<span style={{ marginLeft: '8px' }}>
|
||||||
|
{sort === 'asc' && <span>↑</span>}
|
||||||
|
{sort === 'desc' && <span>↓</span>}
|
||||||
|
</span> */}
|
||||||
|
|
||||||
|
{/* Additional Menu *
|
||||||
|
<span style={{ marginLeft: '8px' }}>
|
||||||
|
<button onClick={menu}>Menu</button>
|
||||||
|
</span> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default LCPDataGridHeader;
|
||||||
@ -1,344 +1,69 @@
|
|||||||
import { useState, useEffect, useMemo, useCallback } from '@wordpress/element';
|
import { useState } from '@wordpress/element';
|
||||||
import { TextControl, Button, Modal, SelectControl, ToggleControl } from '@wordpress/components';
|
import { Button, Modal } from '@wordpress/components';
|
||||||
import { __ } from '@wordpress/i18n';
|
import { __ } from '@wordpress/i18n';
|
||||||
import LCPDataGrid from './LCPDataGrid';
|
import LCPDataGrid from './LCPDataGrid';
|
||||||
import LCPDataUploader from './LCPDataUploader';
|
|
||||||
|
|
||||||
const LCPDatasetBuilder = ({ chartData = [], onChange, attributes, setAttributes, chartType }) => {
|
const LCPDatasetBuilder = ({ attributes }) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [options, setOptions] = useState([]);
|
const [activeTab, setActiveTab] = useState(0); // Track the active tab
|
||||||
|
|
||||||
// List of CSS color names
|
|
||||||
const CSS_COLOR_NAMES = [
|
|
||||||
'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black',
|
|
||||||
'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse',
|
|
||||||
'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan',
|
|
||||||
'darkgoldenrod', 'darkgray', 'darkgreen', 'darkkhaki', 'darkmagenta', 'darkolivegreen',
|
|
||||||
'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue',
|
|
||||||
'darkslategray', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray',
|
|
||||||
'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite',
|
|
||||||
'gold', 'goldenrod', 'gray', 'green', 'greenyellow', 'honeydew', 'hotpink', 'indianred',
|
|
||||||
'indigo', 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon',
|
|
||||||
'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgray', 'lightgreen',
|
|
||||||
'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategray', 'lightsteelblue',
|
|
||||||
'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon', 'mediumaquamarine', 'mediumblue',
|
|
||||||
'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen',
|
|
||||||
'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin',
|
|
||||||
'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid',
|
|
||||||
'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff',
|
|
||||||
'peru', 'pink', 'plum', 'powderblue', 'purple', 'rebeccapurple', 'red', 'rosybrown', 'royalblue',
|
|
||||||
'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue',
|
|
||||||
'slateblue', 'slategray', 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle',
|
|
||||||
'tomato', 'turquoise', 'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen'
|
|
||||||
];
|
|
||||||
|
|
||||||
// Helper function to check if a string is a valid hex color
|
|
||||||
const isHexColor = (str) => {
|
|
||||||
return /^#([0-9A-F]{3}){1,2}$/i.test(str) || /^#[0-9A-F]{6}$/i.test(str);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper function to check if a string is a valid color name
|
|
||||||
const isColorName = (str) => {
|
|
||||||
return CSS_COLOR_NAMES.includes(str.toLowerCase());
|
|
||||||
};
|
|
||||||
|
|
||||||
// Helper function to guess column type based on value
|
|
||||||
const guessColumnType = useCallback((value) => {
|
|
||||||
if (value === null || value === undefined) return 'text';
|
|
||||||
|
|
||||||
// Handle numeric values
|
|
||||||
if (typeof value === 'number') return 'number';
|
|
||||||
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
const trimmedValue = value.trim();
|
|
||||||
|
|
||||||
// Try parsing as number first
|
|
||||||
if (!isNaN(trimmedValue) && !isNaN(parseFloat(trimmedValue))) {
|
|
||||||
return 'number';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for colors (hex or named)
|
|
||||||
if (isHexColor(trimmedValue) || isColorName(trimmedValue)) {
|
|
||||||
return 'lcpColor';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for date
|
|
||||||
if (!isNaN(Date.parse(trimmedValue))) {
|
|
||||||
return 'lcpDate';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for HTML
|
|
||||||
if (/<[a-z][\s\S]*>/i.test(trimmedValue)) {
|
|
||||||
return 'html';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'text';
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Function to analyze and update column types
|
|
||||||
const analyzeColumnTypes = useCallback((data) => {
|
|
||||||
if (!data || !data.length) return {};
|
|
||||||
|
|
||||||
// Get all unique keys except special columns
|
|
||||||
const keys = Array.from(new Set(
|
|
||||||
data.flatMap(item => Object.keys(item))
|
|
||||||
)).filter(key => !['ID', 'Parent'].includes(key));
|
|
||||||
|
|
||||||
// For each key, collect all values and determine type
|
|
||||||
const newTypes = keys.reduce((acc, key) => {
|
|
||||||
const values = data.map(item => item[key]).filter(v => v != null);
|
|
||||||
// Try to determine the most appropriate type
|
|
||||||
const types = values.map(guessColumnType);
|
|
||||||
// If any value is a number, treat the whole column as number
|
|
||||||
if (types.includes('number')) {
|
|
||||||
acc[key] = 'number';
|
|
||||||
} else {
|
|
||||||
// Otherwise use the most common type
|
|
||||||
const typeCounts = types.reduce((counts, type) => {
|
|
||||||
counts[type] = (counts[type] || 0) + 1;
|
|
||||||
return counts;
|
|
||||||
}, {});
|
|
||||||
acc[key] = Object.entries(typeCounts)
|
|
||||||
.sort(([,a], [,b]) => b - a)[0][0];
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
return newTypes;
|
|
||||||
}, [guessColumnType]);
|
|
||||||
|
|
||||||
// Function to update chartData attribute with parsed JSON data from CSV
|
|
||||||
const handleJsonDataUpdate = (newData) => {
|
|
||||||
console.log('Received new data from CSV:', newData);
|
|
||||||
// Update chartData
|
|
||||||
setAttributes({ chartData: newData });
|
|
||||||
// Analyze and update column types
|
|
||||||
const newTypes = analyzeColumnTypes(newData);
|
|
||||||
setAttributes({ columnTypes: newTypes });
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle Edit Dataset button click
|
|
||||||
const handleEditClick = useCallback(() => {
|
|
||||||
// Update column types before opening the editor
|
|
||||||
if (chartData?.length > 0) {
|
|
||||||
const newTypes = analyzeColumnTypes(chartData);
|
|
||||||
setAttributes({ columnTypes: newTypes });
|
|
||||||
}
|
|
||||||
setIsOpen(true);
|
|
||||||
}, [chartData, analyzeColumnTypes, setAttributes]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (chartData && chartData.length > 0) {
|
|
||||||
const columns = getAvailableColumns();
|
|
||||||
setOptions(columns.map(col => ({
|
|
||||||
label: col.name,
|
|
||||||
value: col.key
|
|
||||||
})));
|
|
||||||
}
|
|
||||||
}, [chartData]);
|
|
||||||
|
|
||||||
// Get available columns from chartData
|
|
||||||
const getAvailableColumns = () => {
|
|
||||||
if (!chartData.length) return [];
|
|
||||||
|
|
||||||
// Get the first row which contains our column names
|
|
||||||
const columnNames = Object.entries(chartData[0])
|
|
||||||
.filter(([key]) => !['ID', 'Parent'].includes(key))
|
|
||||||
.map(([key, value]) => ({ key, name: value }));
|
|
||||||
|
|
||||||
return columnNames;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get column options based on type filter
|
|
||||||
const getColumnOptions = (columns, typeFilter = null) => {
|
|
||||||
return columns
|
|
||||||
.filter(col => {
|
|
||||||
if (!typeFilter) return true;
|
|
||||||
const columnType = attributes.columnTypes?.[col.key];
|
|
||||||
// Handle both 'number' and 'lcpNumber' for backward compatibility
|
|
||||||
if (typeFilter === 'number') {
|
|
||||||
return columnType === 'number';
|
|
||||||
}
|
|
||||||
return columnType === typeFilter;
|
|
||||||
})
|
|
||||||
.map(col => ({
|
|
||||||
label: col.name, // Use the column name from first row
|
|
||||||
value: col.key // Use the field key for internal reference
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDataChange = (newData) => {
|
|
||||||
console.log('DatasetBuilder updating chartData:', newData);
|
|
||||||
onChange(newData);
|
|
||||||
};
|
|
||||||
|
|
||||||
const downloadCSV = useCallback(() => {
|
|
||||||
if (!chartData || chartData.length === 0) return;
|
|
||||||
|
|
||||||
// Get all columns except internal ones
|
|
||||||
const columns = Object.keys(chartData[0]).filter(key => !['ID', 'Parent'].includes(key));
|
|
||||||
|
|
||||||
// Create CSV header
|
|
||||||
const header = columns.join(',');
|
|
||||||
|
|
||||||
// Create CSV rows
|
|
||||||
const rows = chartData.map(row =>
|
|
||||||
columns.map(col => {
|
|
||||||
let value = row[col] || '';
|
|
||||||
// Handle special characters in CSV
|
|
||||||
if (typeof value === 'string' && (value.includes(',') || value.includes('"') || value.includes('\n'))) {
|
|
||||||
value = `"${value.replace(/"/g, '""')}"`;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}).join(',')
|
|
||||||
).join('\n');
|
|
||||||
|
|
||||||
// Combine header and rows
|
|
||||||
const csv = `${header}\n${rows}`;
|
|
||||||
|
|
||||||
// Create and trigger download
|
|
||||||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.setAttribute('href', url);
|
|
||||||
link.setAttribute('download', 'chart-data.csv');
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
document.body.removeChild(link);
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
}, [chartData]);
|
|
||||||
|
|
||||||
// Get default value for valueColumn
|
|
||||||
const defaultValueColumn = useMemo(() => {
|
|
||||||
return attributes.valueColumn || (options[0]?.value || '');
|
|
||||||
}, [attributes.valueColumn, options]);
|
|
||||||
|
|
||||||
// Get all available columns
|
|
||||||
const availableColumns = getAvailableColumns();
|
|
||||||
|
|
||||||
// Get all column options
|
|
||||||
const allOptions = getColumnOptions(availableColumns);
|
|
||||||
|
|
||||||
// Get numeric column options for bar chart value
|
|
||||||
const numericOptions = getColumnOptions(availableColumns, 'number');
|
|
||||||
|
|
||||||
// Determine which options to use for value column
|
|
||||||
const valueColumnOptions = chartType === 'bar' ? numericOptions : allOptions;
|
|
||||||
|
|
||||||
// Add "None" option for optional fields
|
|
||||||
const optionalOptions = [{ label: __('None', 'lcp-visualize'), value: '' }, ...allOptions];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<Button
|
<Button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
onClick={handleEditClick}
|
onClick={() => setIsOpen(true)}
|
||||||
style={{ marginBottom: '10px', width: '100%' }}
|
style={{ marginBottom: '10px', width: '100%' }}
|
||||||
>
|
>
|
||||||
{__('Edit Dataset', 'lcp-visualize')}
|
{__('Edit Dataset', 'lcp-visualize')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<Modal
|
<Modal
|
||||||
onRequestClose={() => setIsOpen(false)}
|
onRequestClose={() => setIsOpen(false)}
|
||||||
title="Dataset Builder"
|
title={__('Dataset Builder', 'lcp-visualize')}
|
||||||
style={{ width: '90vw', height: '90vh' }}
|
style={{ width: '90vw', height: '90vh' }}
|
||||||
>
|
>
|
||||||
<div style={{ height: 'calc(90vh - 40px)', padding: '20px', display: 'flex', gap: '20px', marginBottom: '20px' }}>
|
<div style={{ height: 'calc(90vh - 40px)', padding: '20px' }}>
|
||||||
<div style={{ flex: 1, minWidth: 0 }}>
|
{/* Tabs */}
|
||||||
<LCPDataGrid
|
<div style={{ display: 'flex', marginBottom: '20px' }}>
|
||||||
chartData={chartData}
|
{attributes.datasets.map((dataset, index) => (
|
||||||
onDataChange={handleDataChange}
|
<button
|
||||||
attributes={attributes}
|
key={index}
|
||||||
setAttributes={setAttributes}
|
onClick={() => setActiveTab(index)} // Set the active tab
|
||||||
columnTypes={attributes.columnTypes}
|
style={{
|
||||||
onColumnTypeChange={(field, type) => {
|
padding: '10px 20px',
|
||||||
const newColumnTypes = {
|
margin: '0 5px',
|
||||||
...attributes.columnTypes,
|
backgroundColor: activeTab === index ? '#007cba' : '#f1f1f1',
|
||||||
[field]: type
|
color: activeTab === index ? 'white' : 'black',
|
||||||
};
|
border: '1px solid #ccc',
|
||||||
|
borderRadius: '5px',
|
||||||
setAttributes({
|
cursor: 'pointer',
|
||||||
columnTypes: newColumnTypes,
|
transition: 'background-color 0.3s ease',
|
||||||
// If we're changing from a numeric type and this is the value column, reset it
|
}}
|
||||||
valueColumn: field === attributes.valueColumn &&
|
|
||||||
type !== 'number' &&
|
|
||||||
chartType === 'bar' ? '' : attributes.valueColumn
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div style={{
|
|
||||||
width: '300px',
|
|
||||||
padding: '16px',
|
|
||||||
backgroundColor: '#f8f9fa',
|
|
||||||
border: '1px solid #ddd',
|
|
||||||
borderRadius: '4px',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
gap: '16px'
|
|
||||||
}}>
|
|
||||||
<SelectControl
|
|
||||||
label={__('Value Column', 'lcp-visualize')}
|
|
||||||
help={chartType === 'bar' ?
|
|
||||||
__('Select a numeric column for bar heights', 'lcp-visualize') :
|
|
||||||
__('Column to use for values', 'lcp-visualize')
|
|
||||||
}
|
|
||||||
value={attributes.valueColumn || ''}
|
|
||||||
options={valueColumnOptions}
|
|
||||||
onChange={(value) => setAttributes({ valueColumn: value })}
|
|
||||||
/>
|
|
||||||
{chartType === 'bar' && valueColumnOptions.length === 0 && (
|
|
||||||
<div style={{ color: '#cc1818', marginTop: '4px' }}>
|
|
||||||
{__('Please set at least one column type to Number to use as the value column', 'lcp-visualize')}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<SelectControl
|
|
||||||
label={__('Labels Column', 'lcp-visualize')}
|
|
||||||
help={__('Column to use for bar labels', 'lcp-visualize')}
|
|
||||||
value={attributes.labelsColumn || (options[0]?.value || '')}
|
|
||||||
options={options}
|
|
||||||
onChange={(value) => setAttributes({ labelsColumn: value })}
|
|
||||||
/>
|
|
||||||
<TextControl
|
|
||||||
label={__('Hierarchical Columns', 'lcp')}
|
|
||||||
help={__('Categorical columns', 'lcp')}
|
|
||||||
value={attributes.hierarchicalColumnOrder || ''}
|
|
||||||
onChange={(value) => setAttributes({ hierarchicalColumnOrder: value })}
|
|
||||||
/>
|
|
||||||
<LCPDataUploader onJsonDataUpdate={handleJsonDataUpdate} />
|
|
||||||
<SelectControl
|
|
||||||
label={__('Color Column', 'lcp-visualize')}
|
|
||||||
help={__('Column containing bar colors (optional)', 'lcp-visualize')}
|
|
||||||
value={attributes.colorColumn || ''}
|
|
||||||
options={optionalOptions}
|
|
||||||
onChange={(value) => setAttributes({ colorColumn: value })}
|
|
||||||
/>
|
|
||||||
<SelectControl
|
|
||||||
label={__('Popover Content Column', 'lcp-visualize')}
|
|
||||||
help={__('Column containing HTML content for popovers (optional)', 'lcp-visualize')}
|
|
||||||
value={attributes.popoverColumn || ''}
|
|
||||||
options={optionalOptions}
|
|
||||||
onChange={(value) => setAttributes({ popoverColumn: value })}
|
|
||||||
/>
|
|
||||||
<div style={{ marginBottom: '10px' }}>
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
onClick={downloadCSV}
|
|
||||||
icon={
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" style={{ fill: 'currentColor' }}>
|
|
||||||
<path d="M19 9h-4V3H9v6H5l7 7 7-7zM5 18v2h14v-2H5z"/>
|
|
||||||
</svg>
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{__('Download CSV', 'lcp-visualize')}
|
{dataset.name}
|
||||||
</Button>
|
</button>
|
||||||
</div>
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Render all the LCPDataGrid components */}
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||||
|
{attributes.datasets.map((dataset, index) => (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
style={{
|
||||||
|
display: activeTab === index ? 'block' : 'none', // Show only the active tab
|
||||||
|
transition: 'display 0.3s ease',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LCPDataGrid dataset={dataset.data} />
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user