import React, { useEffect, useState } from 'react'; import { useApplyOp } from '../api/hooks'; import { useSessionStore } from '../store/session'; import { useUIStore } from '../store/ui'; import { useNotebookStore } from '../store/notebook'; import type { OperationInfo } from '../api/types'; import { listOperations } from '../api/client'; interface OperationCategory { key: string; label: string; icon: React.ReactNode; ops: string[]; // operation keys that belong to this category } const OPERATION_CATEGORIES: OperationCategory[] = [ { key: 'filter_clean', label: 'Filter & Clean', icon: ( ), ops: ['filter', 'fill_nulls', 'drop_columns', 'replace_values', 'distinct', 'remove_nulls', 'sample'], }, { key: 'transform', label: 'Transform', icon: ( ), ops: ['sort', 'select', 'rename', 'cast_type', 'clip', 'add_row'], }, { key: 'aggregate', label: 'Aggregate', icon: ( ), ops: ['groupby', 'pivot_table', 'describe', 'value_counts'], }, { key: 'compute', label: 'Compute', icon: ( ), ops: ['compute_column', 'lag_lead', 'rolling_agg', 'correlation'], }, ]; export function ManipulationPanel() { const { columns } = useSessionStore(); const { addCell } = useNotebookStore(); const { apply, loading, error } = useApplyOp(); const { addError } = useUIStore(); const [operations, setOperations] = useState([]); const [selectedOp, setSelectedOp] = useState(''); const [params, setParams] = useState>({}); const [collapsedCategories, setCollapsedCategories] = useState>(new Set()); useEffect(() => { loadOps(); }, []); const loadOps = async () => { const data = await listOperations(); if (data) setOperations(data.operations); }; const currentOp = operations.find((op) => op.key === selectedOp); const handleApply = async () => { if (!selectedOp) return; const result = await apply(selectedOp, params); if (result) { addCell('table', `${selectedOp} result`); // Update the schema in the session store after transformation if (result.columns_schema) { useSessionStore.getState().setSchema(result.columns_schema, result.row_count); } } else if (error) { addError(error); } }; const toggleCategory = (key: string) => { setCollapsedCategories((prev) => { const next = new Set(prev); if (next.has(key)) { next.delete(key); } else { next.add(key); } return next; }); }; // Categorize operations const categorizedOps = OPERATION_CATEGORIES.map((cat) => ({ ...cat, operations: operations.filter((op) => cat.ops.includes(op.key)), })); // Find operations that don't belong to any category const categorizedKeys = new Set(OPERATION_CATEGORIES.flatMap((c) => c.ops)); const uncategorizedOps = operations.filter((op) => !categorizedKeys.has(op.key)); const renderParamField = (name: string, schema: any) => { const colNames = columns.map((c) => c.name); const numericCols = columns.filter((c) => c.column_type === 'numeric').map((c) => c.name); const categoricalCols = columns.filter((c) => c.column_type === 'categorical').map((c) => c.name); const datetimeCols = columns.filter((c) => c.column_type === 'datetime').map((c) => c.name); const type = schema.type; if (name === 'column' || name === 'x_col' || name === 'y_col') { const relevantCols = numericCols.length > 0 ? numericCols : colNames; return ( ); } if (name === 'category_col' || name === 'group_col') { const relevantCols = categoricalCols.length > 0 ? categoricalCols : colNames; return ( ); } if (name === 'date_col') { const relevantCols = datetimeCols.length > 0 ? datetimeCols : colNames; return ( ); } if (name === 'value_col' || name === 'lat_col' || name === 'lon_col' || name === 'color_col' || name === 'label_col') { return ( ); } if (type === 'boolean') { return ( ); } if (schema.enum) { return ( ); } if (type === 'array') { return ( setParams({ ...params, [name]: e.target.value.split(',').map((s) => s.trim()).filter(Boolean) })} /> ); } if (type === 'number') { return ( setParams({ ...params, [name]: parseFloat(e.target.value) || 0 })} /> ); } return ( setParams({ ...params, [name]: e.target.value })} /> ); }; return (

Transform Data

{/* Operation categories */}
{categorizedOps.map((cat) => { if (cat.operations.length === 0) return null; const isCollapsed = collapsedCategories.has(cat.key); return (
{!isCollapsed && (
{cat.operations.map((op) => ( ))}
)}
); })} {/* Uncategorized operations */} {uncategorizedOps.length > 0 && (
{!collapsedCategories.has('_other') && (
{uncategorizedOps.map((op) => ( ))}
)}
)}
{/* Selected operation parameters */} {currentOp && (
{currentOp.label}

{currentOp.description}

{Object.entries(currentOp.param_schema.properties || {}).map(([name, schema]: [string, any]) => { const isRequired = currentOp.param_schema.required?.includes(name); return (
{renderParamField(name, schema)}
); })}
)} {error &&

{error}

}
); }