// Mobile Table Enhancements for TimeTrack document.addEventListener('DOMContentLoaded', function() { // Configuration const MOBILE_BREAKPOINT = 768; const CARD_VIEW_BREAKPOINT = 576; // Initialize all data tables function initMobileTables() { const tables = document.querySelectorAll('.data-table, table'); tables.forEach(table => { // Wrap tables in responsive container if (!table.closest('.table-responsive')) { const wrapper = document.createElement('div'); wrapper.className = 'table-responsive'; table.parentNode.insertBefore(wrapper, table); wrapper.appendChild(table); // Check if table is scrollable checkTableScroll(wrapper); } // Add mobile-specific attributes if (window.innerWidth <= CARD_VIEW_BREAKPOINT) { convertTableToCards(table); } }); } // Check if table needs horizontal scroll function checkTableScroll(wrapper) { const table = wrapper.querySelector('table'); if (table.scrollWidth > wrapper.clientWidth) { wrapper.classList.add('scrollable'); addScrollIndicator(wrapper); } else { wrapper.classList.remove('scrollable'); } } // Add visual scroll indicator function addScrollIndicator(wrapper) { if (!wrapper.querySelector('.scroll-indicator')) { const indicator = document.createElement('div'); indicator.className = 'scroll-indicator'; indicator.innerHTML = ' Scroll for more'; wrapper.appendChild(indicator); // Hide indicator when scrolled to end wrapper.addEventListener('scroll', function() { const maxScroll = this.scrollWidth - this.clientWidth; if (this.scrollLeft >= maxScroll - 10) { indicator.style.opacity = '0'; } else { indicator.style.opacity = '1'; } }); } } // Convert table to card layout for mobile function convertTableToCards(table) { // Skip if already converted or marked to skip if (table.classList.contains('no-card-view') || table.dataset.mobileCards === 'true') { return; } const headers = Array.from(table.querySelectorAll('thead th')).map(th => th.textContent.trim()); const rows = table.querySelectorAll('tbody tr'); // Create card container const cardContainer = document.createElement('div'); cardContainer.className = 'mobile-card-view'; cardContainer.setAttribute('role', 'list'); rows.forEach((row, rowIndex) => { const card = createCardFromRow(row, headers); cardContainer.appendChild(card); }); // Insert card view before table table.parentNode.insertBefore(cardContainer, table); // Add classes for toggling table.classList.add('desktop-table-view'); table.dataset.mobileCards = 'true'; } // Create a card element from table row function createCardFromRow(row, headers) { const cells = row.querySelectorAll('td'); const card = document.createElement('div'); card.className = 'table-card'; card.setAttribute('role', 'listitem'); // Check for special data attributes const primaryField = row.dataset.primaryField || 0; const secondaryField = row.dataset.secondaryField || 1; // Create card header with primary info if (cells[primaryField]) { const cardHeader = document.createElement('div'); cardHeader.className = 'table-card-header'; cardHeader.innerHTML = cells[primaryField].innerHTML; card.appendChild(cardHeader); } // Create card body with other fields const cardBody = document.createElement('div'); cardBody.className = 'table-card-body'; cells.forEach((cell, index) => { // Skip primary field as it's in header if (index === primaryField) return; const field = document.createElement('div'); field.className = 'table-card-field'; const label = document.createElement('span'); label.className = 'table-card-label'; label.textContent = headers[index] || ''; const value = document.createElement('span'); value.className = 'table-card-value'; value.innerHTML = cell.innerHTML; field.appendChild(label); field.appendChild(value); cardBody.appendChild(field); }); card.appendChild(cardBody); // Copy any data attributes from row Array.from(row.attributes).forEach(attr => { if (attr.name.startsWith('data-')) { card.setAttribute(attr.name, attr.value); } }); // Copy click handlers if any if (row.onclick) { card.onclick = row.onclick; card.style.cursor = 'pointer'; } return card; } // Time entry table specific enhancements function enhanceTimeEntryTable() { const timeTable = document.querySelector('.time-entries-table'); if (!timeTable) return; if (window.innerWidth <= MOBILE_BREAKPOINT) { // Add swipe actions for time entries addSwipeActions(timeTable); // Compact view for mobile timeTable.classList.add('mobile-compact'); } } // Add swipe gestures to table rows function addSwipeActions(table) { const rows = table.querySelectorAll('tbody tr'); rows.forEach(row => { let startX = 0; let currentX = 0; let isDragging = false; row.addEventListener('touchstart', handleTouchStart, { passive: true }); row.addEventListener('touchmove', handleTouchMove, { passive: true }); row.addEventListener('touchend', handleTouchEnd); function handleTouchStart(e) { startX = e.touches[0].clientX; isDragging = true; row.style.transition = 'none'; } function handleTouchMove(e) { if (!isDragging) return; currentX = e.touches[0].clientX; const diffX = currentX - startX; // Limit swipe distance const maxSwipe = 100; const swipeX = Math.max(-maxSwipe, Math.min(maxSwipe, diffX)); row.style.transform = `translateX(${swipeX}px)`; // Show action indicators if (swipeX < -50) { row.classList.add('swipe-delete'); } else if (swipeX > 50) { row.classList.add('swipe-edit'); } else { row.classList.remove('swipe-delete', 'swipe-edit'); } } function handleTouchEnd(e) { if (!isDragging) return; const diffX = currentX - startX; row.style.transition = 'transform 0.3s ease'; row.style.transform = ''; // Trigger actions based on swipe distance if (diffX < -80) { // Delete action const deleteBtn = row.querySelector('.delete-btn'); if (deleteBtn) deleteBtn.click(); } else if (diffX > 80) { // Edit action const editBtn = row.querySelector('.edit-btn'); if (editBtn) editBtn.click(); } row.classList.remove('swipe-delete', 'swipe-edit'); isDragging = false; } }); } // Handle responsive table on window resize function handleResize() { const tables = document.querySelectorAll('.table-responsive'); tables.forEach(wrapper => { checkTableScroll(wrapper); }); // Re-initialize tables if crossing breakpoint if (window.innerWidth <= CARD_VIEW_BREAKPOINT) { initMobileTables(); } // Update time entry table enhanceTimeEntryTable(); } // Add CSS for card view function injectCardStyles() { if (document.getElementById('mobile-table-styles')) return; const styles = ` `; document.head.insertAdjacentHTML('beforeend', styles); } // Initialize on load injectCardStyles(); initMobileTables(); enhanceTimeEntryTable(); // Handle window resize let resizeTimer; window.addEventListener('resize', function() { clearTimeout(resizeTimer); resizeTimer = setTimeout(handleResize, 250); }); // Export functions for external use window.MobileTables = { init: initMobileTables, convertToCards: convertTableToCards, enhanceTimeEntry: enhanceTimeEntryTable }; });