MVCavalheiroJr's picture
undefined - Initial Deployment
8f10c12 verified
<!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">
<!-- Controls will be inserted here by JavaScript -->
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Sample data
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' }
];
// Columns configuration
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 }
];
// Card renderer function
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>
`;
}
// Initialize the MultiViewDataGrid
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() {
// Clear the container
this.container.innerHTML = '';
// Render controls
this.renderControls();
// Render the current view
if (this.currentView === 'table') {
this.renderTableView();
} else {
this.renderGridView();
}
// Render pagination
this.renderPagination();
}
renderControls() {
const controlsContainer = document.createElement('div');
controlsContainer.className = 'controls-container flex flex-wrap items-center justify-between gap-4 mb-6';
// Search input
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);
// View toggle
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);
// Column selector (for table view)
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;
// Add all columns as options
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';
// Create table header
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();
});
// Add sort indicator
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);
// Create table body
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';
// Previous button
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);
// Page buttons
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);
}
// Next button
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() {
// Apply search filter
let filtered = this.data;
if (this.searchTerm) {
filtered = this.data.filter(item => {
return Object.values(item).some(
val => val.toString().toLowerCase().includes(this.searchTerm)
);
});
}
// Apply sorting
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>