New blueprint-based module system (hm_qc, video_qc, video_master, reporting), core framework (database, config, templates), and unified web interface with progress tracking and tab navigation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
288 lines
8.6 KiB
JavaScript
288 lines
8.6 KiB
JavaScript
/**
|
|
* Tab Management for HM QC Platform
|
|
*
|
|
* Handles tab state preservation, navigation, and cross-tab communication
|
|
*/
|
|
|
|
const TabManager = {
|
|
/**
|
|
* Save state for a specific tab
|
|
* @param {string} tabId - Tab identifier (e.g., 'hm-qc', 'video-qc')
|
|
* @param {object} state - State object to save
|
|
*/
|
|
saveState(tabId, state) {
|
|
try {
|
|
const stateKey = `tab_${tabId}_state`;
|
|
sessionStorage.setItem(stateKey, JSON.stringify({
|
|
...state,
|
|
timestamp: new Date().toISOString()
|
|
}));
|
|
} catch (error) {
|
|
console.error(`Error saving state for tab ${tabId}:`, error);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Load state for a specific tab
|
|
* @param {string} tabId - Tab identifier
|
|
* @returns {object|null} Saved state or null if not found
|
|
*/
|
|
loadState(tabId) {
|
|
try {
|
|
const stateKey = `tab_${tabId}_state`;
|
|
const stateStr = sessionStorage.getItem(stateKey);
|
|
|
|
if (!stateStr) {
|
|
return null;
|
|
}
|
|
|
|
return JSON.parse(stateStr);
|
|
} catch (error) {
|
|
console.error(`Error loading state for tab ${tabId}:`, error);
|
|
return null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Clear state for a specific tab
|
|
* @param {string} tabId - Tab identifier
|
|
*/
|
|
clearState(tabId) {
|
|
try {
|
|
const stateKey = `tab_${tabId}_state`;
|
|
sessionStorage.removeItem(stateKey);
|
|
} catch (error) {
|
|
console.error(`Error clearing state for tab ${tabId}:`, error);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Clear all tab states
|
|
*/
|
|
clearAllStates() {
|
|
try {
|
|
const keys = Object.keys(sessionStorage);
|
|
keys.forEach(key => {
|
|
if (key.startsWith('tab_')) {
|
|
sessionStorage.removeItem(key);
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Error clearing all tab states:', error);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Get current active tab
|
|
* @returns {string|null} Active tab ID or null
|
|
*/
|
|
getActiveTab() {
|
|
const activeLink = document.querySelector('.hm-tabs .nav-link.active');
|
|
return activeLink ? activeLink.dataset.tab : null;
|
|
},
|
|
|
|
/**
|
|
* Set active tab
|
|
* @param {string} tabId - Tab identifier to activate
|
|
*/
|
|
setActiveTab(tabId) {
|
|
// Remove active class from all tabs
|
|
document.querySelectorAll('.hm-tabs .nav-link').forEach(link => {
|
|
link.classList.remove('active');
|
|
});
|
|
|
|
// Add active class to target tab
|
|
const targetLink = document.querySelector(`.hm-tabs .nav-link[data-tab="${tabId}"]`);
|
|
if (targetLink) {
|
|
targetLink.classList.add('active');
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Check if a tab has new content/notifications
|
|
* @param {string} tabId - Tab identifier
|
|
* @returns {boolean} True if tab has notifications
|
|
*/
|
|
hasNotification(tabId) {
|
|
const state = this.loadState(tabId);
|
|
return state && state.hasNotification === true;
|
|
},
|
|
|
|
/**
|
|
* Set notification status for a tab
|
|
* @param {string} tabId - Tab identifier
|
|
* @param {boolean} hasNotification - Notification status
|
|
*/
|
|
setNotification(tabId, hasNotification) {
|
|
const state = this.loadState(tabId) || {};
|
|
state.hasNotification = hasNotification;
|
|
this.saveState(tabId, state);
|
|
|
|
// Update UI to show notification indicator
|
|
this.updateNotificationIndicator(tabId, hasNotification);
|
|
},
|
|
|
|
/**
|
|
* Update notification indicator in tab UI
|
|
* @param {string} tabId - Tab identifier
|
|
* @param {boolean} show - Whether to show indicator
|
|
*/
|
|
updateNotificationIndicator(tabId, show) {
|
|
const tabLink = document.querySelector(`.hm-tabs .nav-link[data-tab="${tabId}"]`);
|
|
if (!tabLink) return;
|
|
|
|
// Remove existing indicator
|
|
const existingIndicator = tabLink.querySelector('.notification-indicator');
|
|
if (existingIndicator) {
|
|
existingIndicator.remove();
|
|
}
|
|
|
|
// Add new indicator if needed
|
|
if (show) {
|
|
const indicator = document.createElement('span');
|
|
indicator.className = 'notification-indicator';
|
|
indicator.innerHTML = '<i class="bi bi-dot"></i>';
|
|
indicator.style.cssText = 'color: #dc3545; font-size: 2rem; line-height: 0; margin-left: -8px;';
|
|
tabLink.appendChild(indicator);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Initialize tab management when DOM is loaded
|
|
*/
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
console.log('Tab management initialized');
|
|
|
|
// Handle tab click events
|
|
document.querySelectorAll('.hm-tabs .nav-link').forEach(link => {
|
|
link.addEventListener('click', function(e) {
|
|
const currentTab = TabManager.getActiveTab();
|
|
const targetTab = this.dataset.tab;
|
|
|
|
// Don't do anything if clicking the same tab
|
|
if (currentTab === targetTab) {
|
|
e.preventDefault();
|
|
return;
|
|
}
|
|
|
|
// Save current tab state before switching
|
|
if (currentTab) {
|
|
const currentState = getCurrentTabState(currentTab);
|
|
if (currentState) {
|
|
TabManager.saveState(currentTab, currentState);
|
|
}
|
|
}
|
|
|
|
// Clear notification when switching to a tab
|
|
TabManager.setNotification(targetTab, false);
|
|
|
|
console.log(`Switching from ${currentTab} to ${targetTab}`);
|
|
});
|
|
});
|
|
|
|
// Restore state for current active tab
|
|
const activeTab = TabManager.getActiveTab();
|
|
if (activeTab) {
|
|
const state = TabManager.loadState(activeTab);
|
|
if (state) {
|
|
console.log(`Restoring state for ${activeTab}:`, state);
|
|
restoreTabState(activeTab, state);
|
|
}
|
|
}
|
|
|
|
// Check for notifications from other tabs
|
|
checkForNotifications();
|
|
});
|
|
|
|
/**
|
|
* Get current state for a tab (to be implemented per module)
|
|
* @param {string} tabId - Tab identifier
|
|
* @returns {object} Current state object
|
|
*/
|
|
function getCurrentTabState(tabId) {
|
|
// This function should be overridden by each module
|
|
// to return its current state
|
|
return {
|
|
scrollPosition: window.scrollY,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Restore state for a tab (to be implemented per module)
|
|
* @param {string} tabId - Tab identifier
|
|
* @param {object} state - State to restore
|
|
*/
|
|
function restoreTabState(tabId, state) {
|
|
// This function should be overridden by each module
|
|
// to restore its state
|
|
if (state.scrollPosition) {
|
|
window.scrollTo(0, state.scrollPosition);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check for notifications from other tabs
|
|
*/
|
|
function checkForNotifications() {
|
|
const tabs = ['hm-qc', 'video-qc', 'video-master', 'reporting'];
|
|
|
|
tabs.forEach(tabId => {
|
|
if (TabManager.hasNotification(tabId)) {
|
|
TabManager.updateNotificationIndicator(tabId, true);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Show notification toast
|
|
* @param {string} message - Notification message
|
|
* @param {string} type - Notification type (success, error, warning, info)
|
|
*/
|
|
function showNotification(message, type = 'info') {
|
|
// Create toast container if it doesn't exist
|
|
let toastContainer = document.querySelector('.toast-container');
|
|
|
|
if (!toastContainer) {
|
|
toastContainer = document.createElement('div');
|
|
toastContainer.className = 'toast-container position-fixed top-0 end-0 p-3';
|
|
toastContainer.style.zIndex = '9999';
|
|
document.body.appendChild(toastContainer);
|
|
}
|
|
|
|
// Create toast element
|
|
const toast = document.createElement('div');
|
|
toast.className = `toast align-items-center text-white bg-${type === 'error' ? 'danger' : type} border-0`;
|
|
toast.setAttribute('role', 'alert');
|
|
toast.setAttribute('aria-live', 'assertive');
|
|
toast.setAttribute('aria-atomic', 'true');
|
|
|
|
toast.innerHTML = `
|
|
<div class="d-flex">
|
|
<div class="toast-body">
|
|
${message}
|
|
</div>
|
|
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast"></button>
|
|
</div>
|
|
`;
|
|
|
|
toastContainer.appendChild(toast);
|
|
|
|
// Show toast
|
|
const bsToast = new bootstrap.Toast(toast, {
|
|
delay: 5000,
|
|
autohide: true
|
|
});
|
|
|
|
bsToast.show();
|
|
|
|
// Remove toast after it's hidden
|
|
toast.addEventListener('hidden.bs.toast', function() {
|
|
toast.remove();
|
|
});
|
|
}
|
|
|
|
// Make TabManager available globally
|
|
window.TabManager = TabManager;
|
|
window.showNotification = showNotification;
|