DEV Community

Armaan Khan
Armaan Khan

Posted on

aa

// Enhanced DataTable Component with hierarchical headers and column fixing
// const DataTable = ({ data, columns, onCellClick, title, searchable = true, groupedColumns = null, fixedColumns = [] }) => {
//   const [searchTerm, setSearchTerm] = useState('');
//   const [sortConfig, setSortConfig] = useState({ key: null, direction: 'asc' });
//   const [columnSettings, setColumnSettings] = useState(false);
//   const [fixedCols, setFixedCols] = useState(fixedColumns);

//   if (!data || data.length === 0) {
//     return (
//       <div className="bg-white/90 backdrop-blur-xl rounded-3xl shadow-2xl border border-white/20 p-16 text-center relative overflow-hidden">
//         <div className="absolute inset-0 bg-gradient-to-br from-blue-50/60 to-purple-50/60"></div>
//         <div className="relative z-10">
//           <div className="w-24 h-24 bg-gradient-to-br from-slate-200 to-slate-300 rounded-full flex items-center justify-center mx-auto mb-8 shadow-xl">
//             <Database className="h-12 w-12 text-slate-500" />
//           </div>
//           <h3 className="text-2xl font-bold text-slate-800 mb-3">No Data Available</h3>
//           <p className="text-slate-500 text-lg">Check back later or adjust your filters</p>
//         </div>
//       </div>
//     );
//   }

//   const filteredData = searchable ? data?.filter(row =>
//     Object.values(row).some(value =>
//       String(value).toLowerCase().includes(searchTerm.toLowerCase())
//     )
//   ) : data;

//   const sortedData = sortConfig.key
//     ? [...filteredData].sort((a, b) => {
//         if (a[sortConfig.key] < b[sortConfig.key]) {
//           return sortConfig.direction === 'asc' ? -1 : 1;
//         }
//         if (a[sortConfig.key] > b[sortConfig.key]) {
//           return sortConfig.direction === 'asc' ? 1 : -1;
//         }
//         return 0;
//       })
//     : filteredData;

//   const handleSort = (key) => {
//     setSortConfig({
//       key,
//       direction: sortConfig.key === key && sortConfig.direction === 'asc' ? 'desc' : 'asc'
//     });
//   };

//   const toggleColumnFixed = (columnKey) => {
//     setFixedCols(prev => 
//       prev.includes(columnKey) 
//         ? prev.filter(key => key !== columnKey)
//         : [...prev, columnKey]
//     );
//   };

//   const isColumnFixed = (columnKey) => fixedCols.includes(columnKey);

//   // Calculate fixed columns width
//   const getFixedColumnsWidth = () => {
//     const allColumns = groupedColumns ? groupedColumns.flatMap(g => g.columns) : columns;
//     return fixedCols.reduce((width, colKey) => {
//       const col = allColumns.find(c => c.key === colKey);
//       return width + (col?.width || 150); // default width
//     }, 0);
//   };

//   // Column Settings Panel
//   const ColumnSettingsPanel = () => {
//     const allColumns = groupedColumns ? groupedColumns.flatMap(g => g.columns) : columns;

//     return (
//       <div className={`fixed top-full right-0 mt-2 bg-white/95 backdrop-blur-xl rounded-2xl shadow-2xl border border-white/30 p-6 w-80 z-50 transform transition-all duration-300 ${columnSettings ? 'scale-100 opacity-100' : 'scale-95 opacity-0 pointer-events-none'}`}>
//         <div className="flex items-center justify-between mb-4">
//           <h4 className="text-lg font-bold text-slate-800">Column Settings</h4>
//           <button
//             onClick={() => setColumnSettings(false)}
//             className="p-2 hover:bg-slate-100 rounded-xl transition-colors"
//           >
//             <XCircle className="h-5 w-5 text-slate-500" />
//           </button>
//         </div>

//         <div className="space-y-3 max-h-96 overflow-y-auto">
//           {allColumns.map(col => (
//             <div key={col.key} className="flex items-center justify-between p-3 bg-slate-50/50 rounded-xl hover:bg-slate-100/50 transition-colors">
//               <div className="flex-1">
//                 <div className="font-medium text-slate-700">{col.title}</div>
//                 <div className="text-sm text-slate-500">{col.key}</div>
//               </div>
//               <button
//                 onClick={() => toggleColumnFixed(col.key)}
//                 className={`flex items-center gap-2 px-3 py-2 rounded-lg text-sm font-medium transition-all duration-200 ${
//                   isColumnFixed(col.key)
//                     ? 'bg-blue-500 text-white shadow-lg'
//                     : 'bg-white border border-slate-200 text-slate-600 hover:bg-blue-50 hover:border-blue-200'
//                 }`}
//               >
//                 {isColumnFixed(col.key) ? (
//                   <>
//                     <CheckCircle className="h-4 w-4" />
//                     Fixed
//                   </>
//                 ) : (
//                   <>
//                     <Database className="h-4 w-4" />
//                     Fix
//                   </>
//                 )}
//               </button>
//             </div>
//           ))}
//         </div>
//       </div>
//     );
//   };

//   // Render regular table headers
//   const renderRegularHeaders = () => (
//     <tr className="bg-gradient-to-r from-slate-100/95 to-slate-200/95 backdrop-blur-sm">
//       {columns.map(col => (
//         <th
//           key={col.key}
//           className={`px-6 py-5 text-left text-sm font-bold text-slate-700 uppercase tracking-wider border-r border-white/50 last:border-r-0 transition-all duration-200 ${
//             isColumnFixed(col.key) 
//               ? 'sticky left-0 bg-gradient-to-r from-blue-100/95 to-indigo-100/95 z-20 shadow-lg' 
//               : ''
//           } ${
//             col.sortable ? 'cursor-pointer hover:bg-slate-200/70 group' : ''
//           }`}
//           onClick={() => col.sortable && handleSort(col.key)}
//           style={isColumnFixed(col.key) ? { left: `${getFixedColumnsWidth() - (col.width || 150)}px` } : {}}
//         >
//           <div className="flex items-center gap-3">
//             <span className="group-hover:text-blue-600 transition-colors">{col.title}</span>
//             {isColumnFixed(col.key) && (
//               <div className="w-2 h-2 bg-blue-500 rounded-full animate-pulse"></div>
//             )}
//             {col.sortable && (
//               <ChevronRight 
//                 className={`h-4 w-4 transition-all duration-300 ${
//                   sortConfig.key === col.key 
//                     ? sortConfig.direction === 'desc' ? 'rotate-90 text-blue-600' : '-rotate-90 text-blue-600'
//                     : 'opacity-0 group-hover:opacity-70'
//                 }`} 
//               />
//             )}
//           </div>
//         </th>
//       ))}
//     </tr>
//   );

//   // Render grouped headers for warehouse table
//   const renderGroupedHeaders = () => {
//     return (
//       <>
//         {/* Main group headers */}
//         <tr className="bg-gradient-to-r from-blue-700 to-purple-700 text-white">
//           {groupedColumns.map((group, idx) => (
//             <th 
//               key={idx}
//               colSpan={group.columns.length} 
//               className="px-6 py-6 text-center text-sm font-bold uppercase tracking-wider border-r border-white/30 last:border-r-0 relative overflow-hidden"
//             >
//               <div className="absolute inset-0 bg-gradient-to-br from-white/10 to-transparent"></div>
//               <div className="relative flex items-center justify-center gap-3">
//                 {group.icon && (
//                   <div className="p-2 bg-white/20 rounded-xl">
//                     <group.icon className="h-5 w-5" />
//                   </div>
//                 )}
//                 <span className="text-lg font-extrabold">{group.title}</span>
//               </div>
//             </th>
//           ))}
//         </tr>

//         {/* Sub headers */}
//         <tr className="bg-gradient-to-r from-slate-50/98 to-slate-100/98 backdrop-blur-sm border-b-2 border-slate-200">
//           {groupedColumns.map(group => 
//             group.columns.map(col => {
//               const fixedLeft = isColumnFixed(col.key) ? 
//                 fixedCols.slice(0, fixedCols.indexOf(col.key))
//                   .reduce((acc, key) => acc + 150, 0) : 0;

//               return (
//                 <th
//                   key={col.key}
//                   className={`px-4 py-4 text-left text-xs font-bold text-slate-700 uppercase tracking-wider border-r border-slate-200/50 last:border-r-0 transition-all duration-200 ${
//                     isColumnFixed(col.key) 
//                       ? 'sticky left-0 bg-gradient-to-r from-blue-50/98 to-indigo-50/98 z-20 shadow-lg border-r-2 border-blue-200' 
//                       : ''
//                   } ${
//                     col.sortable ? 'cursor-pointer hover:bg-slate-200/60 group' : ''
//                   }`}
//                   onClick={() => col.sortable && handleSort(col.key)}
//                   style={isColumnFixed(col.key) ? { left: `${fixedLeft}px` } : {}}
//                 >
//                   <div className="flex items-center gap-2">
//                     <span className={`group-hover:text-blue-600 transition-colors font-semibold ${col.clickable ? 'text-blue-600' : ''}`}>
//                       {col.title}
//                     </span>
//                     {isColumnFixed(col.key) && (
//                       <div className="w-1.5 h-1.5 bg-blue-500 rounded-full animate-pulse"></div>
//                     )}
//                     {col.sortable && (
//                       <ChevronRight 
//                         className={`h-3 w-3 transition-all duration-300 ${
//                           sortConfig.key === col.key 
//                             ? sortConfig.direction === 'desc' ? 'rotate-90 text-blue-600' : '-rotate-90 text-blue-600'
//                             : 'opacity-0 group-hover:opacity-70'
//                         }`} 
//                       />
//                     )}
//                   </div>
//                 </th>
//               );
//             })
//           )}
//         </tr>
//       </>
//     );
//   };

//   return (
//     <div className="bg-white/95 backdrop-blur-2xl rounded-3xl shadow-2xl border border-white/40 overflow-hidden relative">
//       {/* Enhanced gradient overlay */}
//       <div className="absolute inset-0 bg-gradient-to-br from-blue-50/40 via-transparent to-purple-50/40 pointer-events-none"></div>

//       {/* Enhanced Header */}
//       <div className="relative z-10 p-8 bg-gradient-to-r from-slate-50/90 to-slate-100/90 backdrop-blur-sm border-b-2 border-white/30">
//         <div className="flex items-center justify-between">
//           <div className="flex items-center gap-6">
//             <div className="p-4 bg-gradient-to-br from-blue-600 to-purple-600 rounded-2xl shadow-xl">
//               <BarChart3 className="h-8 w-8 text-white" />
//             </div>
//             <div>
//               <h3 className="text-2xl font-bold bg-gradient-to-r from-slate-900 to-slate-700 bg-clip-text text-transparent">
//                 {title}
//               </h3>
//               <p className="text-slate-500 mt-1 text-lg">Advanced interactive data analysis</p>
//             </div>
//           </div>

//           <div className="flex items-center gap-4">
//             {/* Column Settings Button */}
//             <div className="relative">
//               <button
//                 onClick={() => setColumnSettings(!columnSettings)}
//                 className="flex items-center gap-2 px-4 py-3 bg-white/80 backdrop-blur border border-slate-200 rounded-xl hover:bg-blue-50 hover:border-blue-300 transition-all duration-300 shadow-lg hover:shadow-xl group"
//               >
//                 <Database className="h-5 w-5 text-slate-600 group-hover:text-blue-600 transition-colors" />
//                 <span className="text-sm font-medium text-slate-700 group-hover:text-blue-700">Column Settings</span>
//               </button>
//               <ColumnSettingsPanel />
//             </div>

//             {/* Enhanced Search */}
//             {searchable && (
//               <div className="relative group">
//                 <Search className="h-5 w-5 absolute left-4 top-1/2 transform -translate-y-1/2 text-slate-400 group-focus-within:text-blue-600 transition-colors" />
//                 <input
//                   type="text"
//                   placeholder="Search across all data..."
//                   value={searchTerm}
//                   onChange={(e) => setSearchTerm(e.target.value)}
//                   className="pl-12 pr-6 py-3 w-96 bg-white/90 backdrop-blur-sm border border-slate-200 rounded-xl focus:ring-4 focus:ring-blue-500/20 focus:border-blue-400 transition-all duration-300 shadow-lg hover:shadow-xl text-slate-700 placeholder-slate-400"
//                 />
//                 <div className="absolute inset-0 bg-gradient-to-r from-blue-500/5 to-purple-500/5 rounded-xl opacity-0 group-focus-within:opacity-100 transition-opacity duration-300 pointer-events-none"></div>
//               </div>
//             )}
//           </div>
//         </div>

//         {/* Fixed columns indicator */}
//         {fixedCols.length > 0 && (
//           <div className="mt-4 flex items-center gap-2">
//             <div className="flex items-center gap-2 px-3 py-2 bg-blue-100/80 rounded-xl">
//               <CheckCircle className="h-4 w-4 text-blue-600" />
//               <span className="text-sm font-medium text-blue-800">{fixedCols.length} Fixed Column{fixedCols.length > 1 ? 's' : ''}</span>
//             </div>
//           </div>
//         )}
//       </div>

//       {/* Enhanced Table */}
//       <div className="relative z-10 overflow-x-auto">
//         <table className="w-full">
//           <thead>
//             {groupedColumns ? renderGroupedHeaders() : renderRegularHeaders()}
//           </thead>
//           <tbody className="divide-y divide-slate-100/80">
//             {sortedData.map((row, idx) => (
//               <tr key={idx} className="hover:bg-gradient-to-r hover:from-blue-50/60 hover:to-purple-50/60 transition-all duration-300 group relative">
//                 {(groupedColumns ? groupedColumns.flatMap(g => g.columns) : columns).map(col => {
//                   const fixedLeft = isColumnFixed(col.key) ? 
//                     fixedCols.slice(0, fixedCols.indexOf(col.key))
//                       .reduce((acc, key) => acc + 150, 0) : 0;

//                   return (
//                     <td
//                       key={col.key}
//                       className={`px-4 py-5 whitespace-nowrap text-sm border-r border-slate-100/60 last:border-r-0 relative transition-all duration-200 ${
//                         isColumnFixed(col.key) 
//                           ? 'sticky left-0 bg-gradient-to-r from-blue-50/90 to-indigo-50/90 z-10 shadow-lg border-r-2 border-blue-200' 
//                           : ''
//                       } ${
//                         col.clickable 
//                           ? 'text-blue-600 hover:text-blue-800 cursor-pointer font-semibold hover:bg-blue-50/40 transition-all duration-300' 
//                           : col.fixed 
//                             ? 'text-slate-900 font-bold bg-slate-50/60' 
//                             : 'text-slate-700'
//                       }`}
//                       onClick={() => col.clickable && onCellClick && onCellClick(row, col)}
//                       style={isColumnFixed(col.key) ? { left: `${fixedLeft}px` } : {}}
//                     >
//                       {col.clickable && (
//                         <div className="absolute inset-0 bg-gradient-to-r from-blue-100/0 via-blue-100/40 to-blue-100/0 transform scale-x-0 group-hover:scale-x-100 transition-transform duration-300 origin-left"></div>
//                       )}
//                       <span className="relative z-10 font-medium">
//                         {col.render ? col.render(row[col.key], row) : (row[col.key] || 0)}
//                       </span>
//                       {col.clickable && (
//                         <div className="absolute right-3 top-1/2 transform -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
//                           <ChevronRight className="h-3 w-3 text-blue-500" />
//                         </div>
//                       )}
//                     </td>
//                   );
//                 })}
//               </tr>
//             ))}
//           </tbody>
//         </table>
//       </div>

//       {/* Enhanced Footer */}
//       <div className="relative z-10 px-8 py-6 bg-gradient-to-r from-slate-50/90 to-slate-100/90 backdrop-blur-sm border-t-2 border-white/30">
//         <div className="flex items-center justify-between">
//           <div className="flex items-center gap-6">
//             <div className="text-sm text-slate-600">
//               Showing <span className="font-bold text-slate-900 text-lg">{sortedData.length}</span> of <span className="font-bold text-slate-900 text-lg">{data.length}</span> records
//             </div>
//             {searchTerm && (
//               <div className="flex items-center gap-2 px-3 py-1 bg-blue-100/60 rounded-full">
//                 <Search className="h-3 w-3 text-blue-600" />
//                 <span className="text-xs font-medium text-blue-700">Filtered results</span>
//               </div>
//             )}
//           </div>
//           <div className="flex items-center gap-4">
//             <div className="flex items-center gap-2 text-sm text-slate-500">
//               <div className="w-2 h-2 bg-emerald-400 rounded-full animate-pulse shadow-lg"></div>
//               <span className="font-medium">Real-time data</span>
//             </div>
//             <div className="text-xs text-slate-400">
//               Last updated: {new Date().toLocaleTimeString()}
//             </div>
//           </div>
//         </div>
//       </div>
//     </div>
//   );
// };
Enter fullscreen mode Exit fullscreen mode

Top comments (0)