import React, { useState, useMemo } from 'react'; import { ChevronRight, ChevronDown, Copy, Search, Filter } from 'lucide-react'; import CopyButton from './CopyButton'; const PostmanTreeTable = ({ data, title = "JSON Data" }) => { const [expandedPaths, setExpandedPaths] = useState(new Set()); const [searchTerm, setSearchTerm] = useState(''); const [filterType, setFilterType] = useState('all'); // Flatten the data structure for table display const flattenData = (obj, path = '', level = 0) => { const result = []; if (obj === null || obj === undefined) { return [{ key: path || 'root', value: obj, type: obj === null ? 'null' : 'undefined', level, path, hasChildren: false }]; } if (typeof obj !== 'object') { return [{ key: path || 'root', value: obj, type: typeof obj, level, path, hasChildren: false }]; } if (Array.isArray(obj)) { const arrayItem = { key: path || 'root', value: `Array(${obj.length})`, type: 'array', level, path, hasChildren: obj.length > 0, isExpandable: true }; result.push(arrayItem); if (expandedPaths.has(path)) { obj.forEach((item, index) => { const itemPath = path ? `${path}[${index}]` : `[${index}]`; result.push(...flattenData(item, itemPath, level + 1)); }); } } else { const keys = Object.keys(obj); const objectItem = { key: path || 'root', value: `Object(${keys.length})`, type: 'object', level, path, hasChildren: keys.length > 0, isExpandable: true }; if (path) result.push(objectItem); if (!path || expandedPaths.has(path)) { keys.forEach(key => { const keyPath = path ? `${path}.${key}` : key; result.push(...flattenData(obj[key], keyPath, path ? level + 1 : level)); }); } } return result; }; const toggleExpanded = (path) => { const newExpanded = new Set(expandedPaths); if (newExpanded.has(path)) { newExpanded.delete(path); } else { newExpanded.add(path); } setExpandedPaths(newExpanded); }; const flatData = useMemo(() => flattenData(data), [data, expandedPaths]); const filteredData = useMemo(() => { return flatData.filter(item => { // Search filter if (searchTerm) { const searchLower = searchTerm.toLowerCase(); const keyMatch = item.key.toLowerCase().includes(searchLower); const valueMatch = String(item.value).toLowerCase().includes(searchLower); if (!keyMatch && !valueMatch) return false; } // Type filter if (filterType !== 'all' && item.type !== filterType) { return false; } return true; }); }, [flatData, searchTerm, filterType]); const getTypeColor = (type) => { const colors = { string: 'bg-green-100 text-green-800', number: 'bg-blue-100 text-blue-800', boolean: 'bg-purple-100 text-purple-800', object: 'bg-orange-100 text-orange-800', array: 'bg-red-100 text-red-800', null: 'bg-gray-100 text-gray-800', undefined: 'bg-gray-100 text-gray-800' }; return colors[type] || 'bg-gray-100 text-gray-800'; }; const formatValue = (value, type) => { if (type === 'string') return `"${value}"`; if (type === 'null') return 'null'; if (type === 'undefined') return 'undefined'; if (type === 'boolean') return value ? 'true' : 'false'; return String(value); }; const getKeyDisplay = (key, level) => { const parts = key.split(/[.\[\]]+/).filter(Boolean); return parts[parts.length - 1] || key; }; return (
{/* Header */}

{title}

{/* Search */}
setSearchTerm(e.target.value)} className="pl-10 pr-4 py-2 border border-gray-300 rounded-md text-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
{/* Type Filter */}
{/* Table */}
{filteredData.map((item, index) => ( {/* Key Column */} {/* Type Column */} {/* Value Column */} {/* Actions Column */} ))}
Key Type Value Actions
{item.isExpandable ? ( ) : (
)} {getKeyDisplay(item.key, item.level)}
{item.type}
{formatValue(item.value, item.type)}
{!item.isExpandable && ( )}
{/* Footer */}
Showing {filteredData.length} of {flatData.length} items {Object.keys(data || {}).length} root properties
); }; export default PostmanTreeTable;