/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import {
Button,
Modal,
TextControl,
ColorPicker,
Card,
CardBody,
Popover,
TextareaControl
} from '@wordpress/components';
import { useState, useRef } from '@wordpress/element';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import apiFetch from '@wordpress/api-fetch';
const ItemTypes = {
DATASET_ITEM: 'dataset_item'
};
const DatasetItem = ({ item, datasetKey, moveItem, updateItem, items, onDelete }) => {
const [showColorPicker, setShowColorPicker] = useState(false);
const [showHtmlEditor, setShowHtmlEditor] = useState(false);
const ref = useRef(null);
const [{ isDragging }, drag] = useDrag({
type: ItemTypes.DATASET_ITEM,
item: { id: item.id, datasetKey },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
});
const [{ isOver }, drop] = useDrop({
accept: ItemTypes.DATASET_ITEM,
hover(draggedItem, monitor) {
if (!ref.current) return;
if (draggedItem.id === item.id) return;
const hoverBoundingRect = ref.current.getBoundingClientRect();
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
const hoverMiddleX = (hoverBoundingRect.right - hoverBoundingRect.left) / 2;
const clientOffset = monitor.getClientOffset();
if (!clientOffset) return;
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
const hoverClientX = clientOffset.x - hoverBoundingRect.left;
// If hovering in the right half and below middle, make it a child
const shouldBeChild = hoverClientX > hoverMiddleX && hoverClientY > hoverMiddleY;
moveItem(draggedItem.id, item.id, shouldBeChild);
},
collect: (monitor) => ({
isOver: monitor.isOver(),
}),
});
// Initialize drag and drop refs
drag(drop(ref));
// Get child items
const childItems = items.filter(i => i.parent === item.id);
// Calculate the nesting level
const getItemDepth = (itemId) => {
let depth = 0;
let currentItem = items.find(i => i.id === itemId);
while (currentItem && currentItem.parent) {
depth++;
currentItem = items.find(i => i.id === currentItem.parent);
}
return depth;
};
const depth = getItemDepth(item.id);
const style = {
marginLeft: `${depth * 20}px`, // 20px indentation per level
opacity: isDragging ? 0.5 : 1,
cursor: 'move',
background: isOver ? '#f0f0f0' : 'white',
border: isOver ? '2px dashed #0073aa' : '1px solid #e2e4e7',
marginBottom: '8px',
transition: 'all 0.2s ease',
};
return (
updateItem(datasetKey, item.id, 'label', value)}
style={{ flex: 2 }}
/>
updateItem(datasetKey, item.id, 'value', parseFloat(value))}
style={{ flex: 1 }}
/>
{showHtmlEditor && (
setShowHtmlEditor(false)}
>
{
updateItem(datasetKey, item.id, 'popoverHtml', content);
}}
rows={10}
style={{ width: '100%', minHeight: '200px', fontFamily: 'monospace' }}
/>
)}
{/* Render child items */}
{childItems.map(childItem => (
))}
);
};
const LCPDatasetBuilder = ({ value, onChange }) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const [datasets, setDatasets] = useState(value || {});
const [isSaving, setIsSaving] = useState(false);
const [editingDataset, setEditingDataset] = useState(null);
const addNewDataset = () => {
const defaultName = 'New Dataset';
let newName = defaultName;
let counter = 1;
// Ensure unique name
while (datasets[newName]) {
newName = `${defaultName} ${counter}`;
counter++;
}
const updatedDatasets = {
...datasets,
[newName]: [{
id: `dataset-${Date.now()}`,
label: 'New Item',
parent: null,
value: 0,
color: '#000000'
}]
};
setDatasets(updatedDatasets);
onChange(updatedDatasets);
setEditingDataset(newName);
};
const renameDataset = (oldName, newName) => {
if (oldName === newName || !newName.trim() || datasets[newName]) {
setEditingDataset(null);
return;
}
const updatedDatasets = { ...datasets };
updatedDatasets[newName] = updatedDatasets[oldName];
delete updatedDatasets[oldName];
setDatasets(updatedDatasets);
onChange(updatedDatasets);
setEditingDataset(null);
};
const deleteDataset = (datasetKey) => {
const updatedDatasets = { ...datasets };
delete updatedDatasets[datasetKey];
setDatasets(updatedDatasets);
onChange(updatedDatasets);
};
const deleteItem = (datasetKey, itemId) => {
const updatedDatasets = { ...datasets };
// Remove the item and its children
const removeItemAndChildren = (items, targetId) => {
return items.filter(item => {
if (item.id === targetId) return false;
if (item.parent === targetId) return false;
return true;
});
};
updatedDatasets[datasetKey] = removeItemAndChildren(updatedDatasets[datasetKey], itemId);
setDatasets(updatedDatasets);
onChange(updatedDatasets);
};
const addNewItem = (datasetKey) => {
const newItem = {
id: `item-${Date.now()}`,
label: 'New Item',
parent: null,
value: 0,
color: '#000000'
};
const updatedDatasets = {
...datasets,
[datasetKey]: [...datasets[datasetKey], newItem]
};
setDatasets(updatedDatasets);
onChange(updatedDatasets);
};
const updateItem = (datasetKey, itemId, field, value) => {
const updatedDatasets = {
...datasets,
[datasetKey]: datasets[datasetKey].map(item =>
item.id === itemId ? { ...item, [field]: value } : item
)
};
setDatasets(updatedDatasets);
onChange(updatedDatasets);
};
const moveItem = (draggedId, targetId, makeChild) => {
const datasetKey = Object.keys(datasets).find(key =>
datasets[key].some(item => item.id === draggedId)
);
if (!datasetKey) return;
const items = [...datasets[datasetKey]];
const draggedItem = items.find(item => item.id === draggedId);
const targetItem = items.find(item => item.id === targetId);
if (!draggedItem || !targetItem) return;
// If target is a descendant of dragged item, prevent the move
let current = targetItem;
while (current.parent) {
if (current.parent === draggedId) return;
current = items.find(item => item.id === current.parent);
}
// Update parent reference
draggedItem.parent = makeChild ? targetId : targetItem.parent;
const updatedDatasets = {
...datasets,
[datasetKey]: items
};
setDatasets(updatedDatasets);
onChange(updatedDatasets);
};
const saveToCollection = async () => {
try {
setIsSaving(true);
console.log('Chart data:', datasets);
const formattedDatasets = Object.entries(datasets).map(([name, data]) => ({
dataset_name: name,
dataset_source: 'json',
dataset_json: data
}));
console.log('Formatted datasets:', formattedDatasets);
// Create the post first
const postResponse = await apiFetch({
path: '/wp/v2/lcp-data-collection',
method: 'POST',
data: {
title: Object.keys(datasets)[0] || 'Dataset Collection',
status: 'publish',
meta: {
lcp_datasets: formattedDatasets
}
}
});
console.log('Post created:', postResponse);
console.log('Meta data in response:', postResponse.meta);
// Double-check the meta was saved by fetching the post
const savedPost = await apiFetch({
path: `/wp/v2/lcp-data-collection/${postResponse.id}`,
method: 'GET'
});
console.log('Saved post data:', savedPost);
console.log('Saved meta data:', savedPost.meta?.lcp_datasets);
} catch (error) {
console.error('Error saving dataset:', error);
console.log('Error details:', {
message: error.message,
code: error.code,
data: error.data
});
} finally {
setIsSaving(false);
}
};
return (
<>
{isModalOpen && (
setIsModalOpen(false)}
style={{ width: '100%', maxWidth: '800px' }}
>
{Object.entries(datasets).map(([datasetKey, items]) => (
{editingDataset === datasetKey ? (
renameDataset(datasetKey, newName)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
renameDataset(datasetKey, e.target.value);
} else if (e.key === 'Escape') {
setEditingDataset(null);
}
}}
autoFocus
/>
) : (
<>
{datasetKey}
>
)}
{items
.filter(item => !item.parent)
.map(item => (
deleteItem(datasetKey, itemId)}
/>
))}
))}
)}
>
);
};
export default LCPDatasetBuilder;