Vertical.ai / frontend /src /components /DataTableModal.jsx
Abhisingh-18's picture
Mirror of github.com/Abhisingh18/Vertical.ai
1f7ead8 verified
Raw
History Blame Contribute Delete
7.12 kB
import React, { useState, useMemo } from 'react';
import { X, Download, ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react';
import html2canvas from 'html2canvas';
const DataTableModal = ({ isOpen, onClose, data }) => {
const [sortConfig, setSortConfig] = useState({ key: null, direction: 'ascending' });
if (!isOpen || !data) return null;
const sortedRows = useMemo(() => {
let sortableItems = [...(data.rows || [])];
if (sortConfig.key !== null) {
sortableItems.sort((a, b) => {
let aValue = a.values[sortConfig.key] || '';
let bValue = b.values[sortConfig.key] || '';
// Try numeric sort
const aNum = parseFloat(aValue.replace(/[^0-9.-]+/g, ""));
const bNum = parseFloat(bValue.replace(/[^0-9.-]+/g, ""));
if (!isNaN(aNum) && !isNaN(bNum)) {
if (aNum < bNum) return sortConfig.direction === 'ascending' ? -1 : 1;
if (aNum > bNum) return sortConfig.direction === 'ascending' ? 1 : -1;
return 0;
}
if (aValue < bValue) {
return sortConfig.direction === 'ascending' ? -1 : 1;
}
if (aValue > bValue) {
return sortConfig.direction === 'ascending' ? 1 : -1;
}
return 0;
});
}
return sortableItems;
}, [data.rows, sortConfig]);
const requestSort = (key) => {
let direction = 'ascending';
if (sortConfig.key === key && sortConfig.direction === 'ascending') {
direction = 'descending';
}
setSortConfig({ key, direction });
};
const handleDownloadImage = async () => {
const element = document.getElementById('data-table-export');
if (!element) return;
try {
const canvas = await html2canvas(element, { backgroundColor: '#1f1f1f' }); // Dark bg match
const dataUrl = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.download = `${data.title || 'data-table'}.png`;
link.href = dataUrl;
link.click();
} catch (err) {
console.error("Export failed:", err);
}
};
return (
<div className="fixed inset-0 bg-black/80 backdrop-blur-sm z-50 flex items-center justify-center p-4">
<div className="bg-[#1f1f1f] rounded-2xl w-full max-w-5xl h-[85vh] flex flex-col shadow-2xl border border-white/10 overflow-hidden">
{/* Header */}
<div className="p-6 border-b border-white/10 flex justify-between items-center bg-[#1f1f1f]">
<div>
<h2 className="text-2xl font-semibold text-white mb-1">{data.title || "Data Table"}</h2>
<p className="text-gray-400 text-sm">AI-extracted comparative analysis</p>
</div>
<div className="flex gap-3">
<button
onClick={handleDownloadImage}
className="flex items-center gap-2 px-4 py-2 bg-purple-600/20 hover:bg-purple-600/30 text-purple-300 rounded-lg transition-colors border border-purple-500/30 font-medium"
>
<Download className="w-4 h-4" />
Export PNG
</button>
<button
onClick={onClose}
className="p-2 hover:bg-white/10 rounded-full transition-colors text-gray-400 hover:text-white"
>
<X className="w-6 h-6" />
</button>
</div>
</div>
{/* Content - Scrollable */}
<div className="flex-1 overflow-auto p-6 bg-[#121212]" id="data-table-export">
<div className="bg-[#1f1f1f] rounded-xl border border-white/10 overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full text-left border-collapse">
<thead>
<tr className="bg-white/5 border-b border-white/10">
<th className="p-4 text-sm font-semibold text-gray-300 border-r border-white/10 min-w-[200px]">
Entity
</th>
{data.columns?.map(col => (
<th
key={col.key}
onClick={() => requestSort(col.key)}
className="p-4 text-sm font-semibold text-gray-300 border-r border-white/10 cursor-pointer hover:bg-white/5 min-w-[150px] group select-none"
>
<div className="flex items-center gap-2">
{col.label}
<span className="text-gray-600 group-hover:text-gray-400">
{sortConfig.key === col.key ? (
sortConfig.direction === 'ascending' ? <ArrowUp className="w-3 h-3"/> : <ArrowDown className="w-3 h-3"/>
) : <ArrowUpDown className="w-3 h-3"/>}
</span>
</div>
</th>
))}
</tr>
</thead>
<tbody>
{sortedRows.length > 0 ? (
sortedRows.map((row, idx) => (
<tr key={idx} className="border-b border-white/5 hover:bg-white/5 transition-colors">
<td className="p-4 text-white font-medium border-r border-white/10 bg-white/5">
{row.entity}
</td>
{data.columns?.map(col => (
<td key={col.key} className="p-4 text-gray-300 border-r border-white/10 text-sm">
{row.values[col.key] || '-'}
</td>
))}
</tr>
))
) : (
<tr>
<td colSpan={data.columns?.length + 1} className="p-8 text-center text-gray-500 italic">
No data available.
</td>
</tr>
)}
</tbody>
</table>
</div>
</div>
<div className="mt-4 text-center text-xs text-gray-600">
Generated by Vertical.ai • Research Assistant
</div>
</div>
</div>
</div>
);
};
export default DataTableModal;