| 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] || '';
|
|
|
|
|
| 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' });
|
| 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;
|
|
|