| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>MultiView Data Grid</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| .card-grid { |
| display: grid; |
| grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); |
| gap: 1.5rem; |
| } |
| |
| .data-table { |
| width: 100%; |
| border-collapse: collapse; |
| } |
| |
| .data-table th, .data-table td { |
| padding: 0.75rem 1rem; |
| text-align: left; |
| border-bottom: 1px solid #e5e7eb; |
| } |
| |
| .data-table th { |
| background-color: #f3f4f6; |
| font-weight: 600; |
| } |
| |
| .data-table tr:hover { |
| background-color: #f9fafb; |
| } |
| |
| .pagination { |
| display: flex; |
| justify-content: center; |
| gap: 0.5rem; |
| margin-top: 1.5rem; |
| } |
| |
| .pagination button { |
| padding: 0.5rem 1rem; |
| border: 1px solid #e5e7eb; |
| border-radius: 0.375rem; |
| background-color: white; |
| } |
| |
| .pagination button.active { |
| background-color: #3b82f6; |
| color: white; |
| border-color: #3b82f6; |
| } |
| |
| .search-filter { |
| transition: all 0.3s ease; |
| } |
| |
| .search-filter:focus { |
| outline: none; |
| box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3); |
| } |
| |
| .toggle-switch { |
| position: relative; |
| display: inline-block; |
| width: 60px; |
| height: 30px; |
| } |
| |
| .toggle-switch input { |
| opacity: 0; |
| width: 0; |
| height: 0; |
| } |
| |
| .slider { |
| position: absolute; |
| cursor: pointer; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| background-color: #ccc; |
| transition: .4s; |
| border-radius: 34px; |
| } |
| |
| .slider:before { |
| position: absolute; |
| content: ""; |
| height: 22px; |
| width: 22px; |
| left: 4px; |
| bottom: 4px; |
| background-color: white; |
| transition: .4s; |
| border-radius: 50%; |
| } |
| |
| input:checked + .slider { |
| background-color: #3b82f6; |
| } |
| |
| input:checked + .slider:before { |
| transform: translateX(30px); |
| } |
| |
| .toggle-icons { |
| position: absolute; |
| top: 50%; |
| transform: translateY(-50%); |
| color: white; |
| font-size: 12px; |
| } |
| |
| .table-icon { |
| left: 8px; |
| } |
| |
| .grid-icon { |
| right: 8px; |
| } |
| |
| input:checked + .slider .table-icon { |
| opacity: 0; |
| } |
| |
| input:not(:checked) + .slider .grid-icon { |
| opacity: 0; |
| } |
| |
| @media (max-width: 640px) { |
| .card-grid { |
| grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); |
| } |
| |
| .data-table th, .data-table td { |
| padding: 0.5rem; |
| font-size: 0.875rem; |
| } |
| |
| .controls-container { |
| flex-direction: column; |
| gap: 1rem; |
| } |
| } |
| </style> |
| </head> |
| <body class="bg-gray-50 min-h-screen"> |
| <div class="container mx-auto px-4 py-8 max-w-7xl"> |
| <div class="bg-white rounded-lg shadow-md p-6"> |
| <h1 class="text-2xl font-bold text-gray-800 mb-6">MultiView Data Grid</h1> |
| |
| <div id="multiViewGrid" class="space-y-6"> |
| |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', function() { |
| |
| const sampleData = [ |
| { id: 1, name: 'John Doe', email: 'john@example.com', age: 28, status: 'Active', department: 'Engineering' }, |
| { id: 2, name: 'Jane Smith', email: 'jane@example.com', age: 32, status: 'Active', department: 'Marketing' }, |
| { id: 3, name: 'Robert Johnson', email: 'robert@example.com', age: 45, status: 'Inactive', department: 'Sales' }, |
| { id: 4, name: 'Emily Davis', email: 'emily@example.com', age: 24, status: 'Active', department: 'Engineering' }, |
| { id: 5, name: 'Michael Brown', email: 'michael@example.com', age: 38, status: 'Active', department: 'HR' }, |
| { id: 6, name: 'Sarah Wilson', email: 'sarah@example.com', age: 29, status: 'Inactive', department: 'Marketing' }, |
| { id: 7, name: 'David Taylor', email: 'david@example.com', age: 41, status: 'Active', department: 'Engineering' }, |
| { id: 8, name: 'Lisa Anderson', email: 'lisa@example.com', age: 35, status: 'Active', department: 'Finance' }, |
| { id: 9, name: 'James Martinez', email: 'james@example.com', age: 27, status: 'Inactive', department: 'Sales' }, |
| { id: 10, name: 'Jennifer Thomas', email: 'jennifer@example.com', age: 31, status: 'Active', department: 'Marketing' }, |
| { id: 11, name: 'Christopher Lee', email: 'chris@example.com', age: 40, status: 'Active', department: 'Engineering' }, |
| { id: 12, name: 'Amanda White', email: 'amanda@example.com', age: 26, status: 'Inactive', department: 'HR' } |
| ]; |
| |
| |
| const columns = [ |
| { field: 'id', headerName: 'ID', width: 70 }, |
| { field: 'name', headerName: 'Name', width: 150 }, |
| { field: 'email', headerName: 'Email', width: 200 }, |
| { field: 'age', headerName: 'Age', width: 90 }, |
| { field: 'department', headerName: 'Department', width: 150 }, |
| { field: 'status', headerName: 'Status', width: 120 } |
| ]; |
| |
| |
| function cardRenderer(item) { |
| return ` |
| <div class="bg-white rounded-lg shadow-md overflow-hidden border border-gray-200 hover:shadow-lg transition-shadow duration-300"> |
| <div class="p-4"> |
| <div class="flex items-center justify-between mb-3"> |
| <h3 class="text-lg font-semibold text-gray-800">${item.name}</h3> |
| <span class="px-2 py-1 text-xs rounded-full ${item.status === 'Active' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}"> |
| ${item.status} |
| </span> |
| </div> |
| <div class="space-y-2 text-sm text-gray-600"> |
| <p><i class="fas fa-envelope mr-2"></i> ${item.email}</p> |
| <p><i class="fas fa-id-card mr-2"></i> ID: ${item.id}</p> |
| <p><i class="fas fa-birthday-cake mr-2"></i> Age: ${item.age}</p> |
| <p><i class="fas fa-building mr-2"></i> ${item.department}</p> |
| </div> |
| </div> |
| <div class="bg-gray-50 px-4 py-3 flex justify-end"> |
| <button class="text-blue-600 hover:text-blue-800 text-sm font-medium"> |
| View Details |
| </button> |
| </div> |
| </div> |
| `; |
| } |
| |
| |
| const multiViewGrid = new MultiViewDataGrid({ |
| container: document.getElementById('multiViewGrid'), |
| data: sampleData, |
| columns: columns, |
| cardRenderer: cardRenderer, |
| defaultView: 'table', |
| itemsPerPage: 6 |
| }); |
| |
| multiViewGrid.render(); |
| }); |
| |
| class MultiViewDataGrid { |
| constructor(options) { |
| this.container = options.container; |
| this.data = options.data || []; |
| this.columns = options.columns || []; |
| this.cardRenderer = options.cardRenderer || ((item) => `<div>${JSON.stringify(item)}</div>`); |
| this.currentView = options.defaultView || 'table'; |
| this.itemsPerPage = options.itemsPerPage || 10; |
| this.currentPage = 1; |
| this.searchTerm = ''; |
| this.sortConfig = { field: null, direction: 'asc' }; |
| this.filteredData = [...this.data]; |
| } |
| |
| render() { |
| |
| this.container.innerHTML = ''; |
| |
| |
| this.renderControls(); |
| |
| |
| if (this.currentView === 'table') { |
| this.renderTableView(); |
| } else { |
| this.renderGridView(); |
| } |
| |
| |
| this.renderPagination(); |
| } |
| |
| renderControls() { |
| const controlsContainer = document.createElement('div'); |
| controlsContainer.className = 'controls-container flex flex-wrap items-center justify-between gap-4 mb-6'; |
| |
| |
| const searchContainer = document.createElement('div'); |
| searchContainer.className = 'relative flex-1 min-w-[200px]'; |
| |
| const searchInput = document.createElement('input'); |
| searchInput.type = 'text'; |
| searchInput.placeholder = 'Search...'; |
| searchInput.className = 'search-filter w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:border-blue-500'; |
| searchInput.value = this.searchTerm; |
| searchInput.addEventListener('input', (e) => { |
| this.searchTerm = e.target.value.toLowerCase(); |
| this.currentPage = 1; |
| this.filterData(); |
| this.render(); |
| }); |
| |
| const searchIcon = document.createElement('i'); |
| searchIcon.className = 'fas fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400'; |
| |
| searchContainer.appendChild(searchIcon); |
| searchContainer.appendChild(searchInput); |
| |
| |
| const toggleContainer = document.createElement('div'); |
| toggleContainer.className = 'flex items-center gap-3'; |
| |
| const toggleLabel = document.createElement('span'); |
| toggleLabel.className = 'text-sm font-medium text-gray-700'; |
| toggleLabel.textContent = 'Toggle View:'; |
| |
| const toggleWrapper = document.createElement('label'); |
| toggleWrapper.className = 'toggle-switch'; |
| |
| const toggleInput = document.createElement('input'); |
| toggleInput.type = 'checkbox'; |
| toggleInput.checked = this.currentView === 'grid'; |
| toggleInput.addEventListener('change', () => { |
| this.currentView = toggleInput.checked ? 'grid' : 'table'; |
| this.render(); |
| }); |
| |
| const toggleSlider = document.createElement('span'); |
| toggleSlider.className = 'slider'; |
| |
| const tableIcon = document.createElement('i'); |
| tableIcon.className = 'toggle-icons table-icon fas fa-table'; |
| |
| const gridIcon = document.createElement('i'); |
| gridIcon.className = 'toggle-icons grid-icon fas fa-th'; |
| |
| toggleSlider.appendChild(tableIcon); |
| toggleSlider.appendChild(gridIcon); |
| toggleWrapper.appendChild(toggleInput); |
| toggleWrapper.appendChild(toggleSlider); |
| |
| toggleContainer.appendChild(toggleLabel); |
| toggleContainer.appendChild(toggleWrapper); |
| |
| |
| if (this.currentView === 'table') { |
| const columnSelectContainer = document.createElement('div'); |
| columnSelectContainer.className = 'w-full md:w-auto'; |
| |
| const columnSelectLabel = document.createElement('label'); |
| columnSelectLabel.className = 'block text-sm font-medium text-gray-700 mb-1'; |
| columnSelectLabel.textContent = 'Columns:'; |
| |
| const columnSelect = document.createElement('select'); |
| columnSelect.className = 'block w-full pl-3 pr-10 py-2 text-base border border-gray-300 focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm rounded-md'; |
| columnSelect.multiple = true; |
| |
| |
| this.columns.forEach(col => { |
| const option = document.createElement('option'); |
| option.value = col.field; |
| option.textContent = col.headerName; |
| option.selected = true; |
| columnSelect.appendChild(option); |
| }); |
| |
| columnSelectContainer.appendChild(columnSelectLabel); |
| columnSelectContainer.appendChild(columnSelect); |
| controlsContainer.appendChild(columnSelectContainer); |
| } |
| |
| controlsContainer.appendChild(searchContainer); |
| controlsContainer.appendChild(toggleContainer); |
| |
| this.container.appendChild(controlsContainer); |
| } |
| |
| renderTableView() { |
| const tableContainer = document.createElement('div'); |
| tableContainer.className = 'overflow-x-auto'; |
| |
| const table = document.createElement('table'); |
| table.className = 'data-table w-full'; |
| |
| |
| const thead = document.createElement('thead'); |
| const headerRow = document.createElement('tr'); |
| |
| this.columns.forEach(col => { |
| const th = document.createElement('th'); |
| th.textContent = col.headerName; |
| th.className = 'cursor-pointer hover:bg-gray-100'; |
| th.addEventListener('click', () => { |
| if (this.sortConfig.field === col.field) { |
| this.sortConfig.direction = this.sortConfig.direction === 'asc' ? 'desc' : 'asc'; |
| } else { |
| this.sortConfig.field = col.field; |
| this.sortConfig.direction = 'asc'; |
| } |
| this.filterData(); |
| this.render(); |
| }); |
| |
| |
| if (this.sortConfig.field === col.field) { |
| const sortIcon = document.createElement('i'); |
| sortIcon.className = `fas fa-sort-${this.sortConfig.direction === 'asc' ? 'up' : 'down'} ml-1`; |
| th.appendChild(sortIcon); |
| } |
| |
| headerRow.appendChild(th); |
| }); |
| |
| thead.appendChild(headerRow); |
| table.appendChild(thead); |
| |
| |
| const tbody = document.createElement('tbody'); |
| |
| const paginatedData = this.getPaginatedData(); |
| paginatedData.forEach(item => { |
| const row = document.createElement('tr'); |
| row.className = 'hover:bg-gray-50'; |
| |
| this.columns.forEach(col => { |
| const td = document.createElement('td'); |
| td.textContent = item[col.field]; |
| row.appendChild(td); |
| }); |
| |
| tbody.appendChild(row); |
| }); |
| |
| table.appendChild(tbody); |
| tableContainer.appendChild(table); |
| this.container.appendChild(tableContainer); |
| } |
| |
| renderGridView() { |
| const gridContainer = document.createElement('div'); |
| gridContainer.className = 'card-grid'; |
| |
| const paginatedData = this.getPaginatedData(); |
| paginatedData.forEach(item => { |
| const card = document.createElement('div'); |
| card.innerHTML = this.cardRenderer(item); |
| gridContainer.appendChild(card); |
| }); |
| |
| this.container.appendChild(gridContainer); |
| } |
| |
| renderPagination() { |
| const totalPages = Math.ceil(this.filteredData.length / this.itemsPerPage); |
| if (totalPages <= 1) return; |
| |
| const paginationContainer = document.createElement('div'); |
| paginationContainer.className = 'pagination'; |
| |
| |
| const prevButton = document.createElement('button'); |
| prevButton.innerHTML = '<i class="fas fa-chevron-left"></i>'; |
| prevButton.disabled = this.currentPage === 1; |
| prevButton.addEventListener('click', () => { |
| if (this.currentPage > 1) { |
| this.currentPage--; |
| this.render(); |
| } |
| }); |
| paginationContainer.appendChild(prevButton); |
| |
| |
| const maxVisiblePages = 5; |
| let startPage = Math.max(1, this.currentPage - Math.floor(maxVisiblePages / 2)); |
| let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1); |
| |
| if (endPage - startPage + 1 < maxVisiblePages) { |
| startPage = Math.max(1, endPage - maxVisiblePages + 1); |
| } |
| |
| if (startPage > 1) { |
| const firstPageButton = document.createElement('button'); |
| firstPageButton.textContent = '1'; |
| firstPageButton.addEventListener('click', () => { |
| this.currentPage = 1; |
| this.render(); |
| }); |
| paginationContainer.appendChild(firstPageButton); |
| |
| if (startPage > 2) { |
| const ellipsis = document.createElement('span'); |
| ellipsis.textContent = '...'; |
| ellipsis.className = 'flex items-center'; |
| paginationContainer.appendChild(ellipsis); |
| } |
| } |
| |
| for (let i = startPage; i <= endPage; i++) { |
| const pageButton = document.createElement('button'); |
| pageButton.textContent = i; |
| if (i === this.currentPage) { |
| pageButton.className = 'active'; |
| } |
| pageButton.addEventListener('click', () => { |
| this.currentPage = i; |
| this.render(); |
| }); |
| paginationContainer.appendChild(pageButton); |
| } |
| |
| if (endPage < totalPages) { |
| if (endPage < totalPages - 1) { |
| const ellipsis = document.createElement('span'); |
| ellipsis.textContent = '...'; |
| ellipsis.className = 'flex items-center'; |
| paginationContainer.appendChild(ellipsis); |
| } |
| |
| const lastPageButton = document.createElement('button'); |
| lastPageButton.textContent = totalPages; |
| lastPageButton.addEventListener('click', () => { |
| this.currentPage = totalPages; |
| this.render(); |
| }); |
| paginationContainer.appendChild(lastPageButton); |
| } |
| |
| |
| const nextButton = document.createElement('button'); |
| nextButton.innerHTML = '<i class="fas fa-chevron-right"></i>'; |
| nextButton.disabled = this.currentPage === totalPages; |
| nextButton.addEventListener('click', () => { |
| if (this.currentPage < totalPages) { |
| this.currentPage++; |
| this.render(); |
| } |
| }); |
| paginationContainer.appendChild(nextButton); |
| |
| this.container.appendChild(paginationContainer); |
| } |
| |
| filterData() { |
| |
| let filtered = this.data; |
| if (this.searchTerm) { |
| filtered = this.data.filter(item => { |
| return Object.values(item).some( |
| val => val.toString().toLowerCase().includes(this.searchTerm) |
| ); |
| }); |
| } |
| |
| |
| if (this.sortConfig.field) { |
| filtered.sort((a, b) => { |
| const aValue = a[this.sortConfig.field]; |
| const bValue = b[this.sortConfig.field]; |
| |
| if (aValue < bValue) { |
| return this.sortConfig.direction === 'asc' ? -1 : 1; |
| } |
| if (aValue > bValue) { |
| return this.sortConfig.direction === 'asc' ? 1 : -1; |
| } |
| return 0; |
| }); |
| } |
| |
| this.filteredData = filtered; |
| } |
| |
| getPaginatedData() { |
| const startIndex = (this.currentPage - 1) * this.itemsPerPage; |
| const endIndex = startIndex + this.itemsPerPage; |
| return this.filteredData.slice(startIndex, endIndex); |
| } |
| } |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=MVCavalheiroJr/poc-multiview-data-grid" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |