diff --git a/public/js/app.js b/public/js/app.js index 10d138b..ad72027 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -72,11 +72,50 @@ class AccountManager { await this.fetchAccounts(); this.setupEventListeners(); this.loadModals(); // Load modals từ file riêng - // Render dashboard content (only for index.html) + // Single-page navigation based on hash + this.handleRoute(location.hash || '#dashboard'); + window.addEventListener('hashchange', () => this.handleRoute(location.hash)); + } + + handleRoute(hash) { + const route = (hash || '#dashboard').replace('#', '') || 'dashboard'; + this.renderView(route); + } + + renderView(page) { + this.currentPage = page; const mainContent = document.getElementById('mainContent'); - if (mainContent && window.location.pathname.endsWith('index.html')) { + if (!mainContent) return; + + if (page === 'applications') { + mainContent.innerHTML = this.getApplicationsContent(); + this.setupAccountRowListeners(); + this.setupAddButtonListeners(); + this.setupFilters(); + } else if (page === 'accounts') { + mainContent.innerHTML = this.getAccountsContent(); + this.setupAccountRowListeners(); + this.setupAddButtonListeners(); + this.setupFilters(); + } else { mainContent.innerHTML = this.renderDashboard(); } + + this.restoreSearchFocus(); + this.setActiveNav(page); + } + + setActiveNav(page) { + document.querySelectorAll('[data-nav]').forEach(link => { + const isActive = link.dataset.nav === page; + link.classList.toggle('border-l-4', isActive); + link.classList.toggle('border-blue-600', isActive); + link.classList.toggle('bg-slate-200/80', isActive); + link.classList.toggle('dark:bg-slate-800', isActive); + link.classList.toggle('text-slate-900', isActive); + link.classList.toggle('dark:text-slate-50', isActive); + link.classList.toggle('font-bold', isActive); + }); } async fetchApplications() { @@ -103,22 +142,47 @@ class AccountManager { async loadModals() { try { + const existingContainer = document.getElementById('modalsContainer'); + if (existingContainer && existingContainer.children.length) { + return; + } + const response = await fetch('../modals.html'); const modalsHTML = await response.text(); - const modalsContainer = document.getElementById('modalsContainer'); - if (modalsContainer) { - modalsContainer.innerHTML = modalsHTML; - // Re-attach event listeners after modals are loaded - setTimeout(() => { - this.setupAccountRowListeners(); - this.setupFormListeners(); - }, 50); + + const container = existingContainer || document.createElement('div'); + if (!container.id) container.id = 'modalsContainer'; + container.innerHTML = modalsHTML; + if (!container.parentElement) { + document.body.appendChild(container); } + + this.setupFormListeners(); + this.setupAccountRowListeners(); + this.setupAddButtonListeners(); + this.setupFilters(); } catch (error) { console.error('Lỗi load modals:', error); } } + restoreSearchFocus() { + const accountSearch = document.getElementById('accountSearch'); + const appSearch = document.getElementById('appSearch'); + + if (accountSearch && accountSearch.dataset.focused === 'true') { + const pos = accountSearch.selectionStart || accountSearch.value.length; + accountSearch.focus(); + accountSearch.setSelectionRange(pos, pos); + } + + if (appSearch && appSearch.dataset.focused === 'true') { + const pos = appSearch.selectionStart || appSearch.value.length; + appSearch.focus(); + appSearch.setSelectionRange(pos, pos); + } + } + setupEventListeners() { // Modal close buttons document.querySelectorAll('[data-close-modal]').forEach(btn => { @@ -341,7 +405,7 @@ class AccountManager { Actions - + ${filteredAccounts.map(acc => ` ${acc.Email || '-'} @@ -447,7 +511,7 @@ class AccountManager { Actions - + ${filteredApps.map(app => ` @@ -493,6 +557,76 @@ class AccountManager { `; } + renderAccountsTableBody() { + const tbody = document.querySelector('.accounts-table-body'); + if (!tbody) return; + const filteredAccounts = this.getFilteredAccounts(); + tbody.innerHTML = filteredAccounts.map(acc => ` + + ${acc.Email || '-'} + ${acc.AccountUsername || '-'} + + ${acc.AppName || '-'} + + + + + + + + `).join(''); + this.setupAccountRowListeners(); + } + + renderApplicationsTableBody() { + const tbody = document.querySelector('.apps-table-body'); + if (!tbody) return; + const filteredApps = this.getFilteredApplications(); + tbody.innerHTML = filteredApps.map(app => ` + + +
+
+ ${app.Icon || 'apps'} +
+ ${app.Name} +
+ + + ${app.Type} + + ${app.Description || '-'} + ${(app.Url || app.url) ? `${app.Url || app.url}` : '-'} + +
+
+ ${(app.Status || app.status) === 'online' ? 'Online' : 'Offline'} +
+ + +
+ + + +
+ + + `).join(''); + this.setupAccountRowListeners(); + } + setupAccountRowListeners() { // View Account listeners document.querySelectorAll('.view-account').forEach(btn => { @@ -504,8 +638,10 @@ class AccountManager { document.getElementById('viewAccountService').textContent = account?.AppName || '-'; document.getElementById('viewAccountOwner').textContent = account?.Email || '-'; document.getElementById('viewAccountUsername').textContent = account?.AccountUsername || '-'; - document.getElementById('viewAccountPassword').textContent = '••••••••'; - document.getElementById('viewAccountPassword').dataset.visible = 'false'; + const passwordEl = document.getElementById('viewAccountPassword'); + passwordEl.dataset.password = account?.AccountPassword || ''; + passwordEl.textContent = '••••••••'; + passwordEl.dataset.visible = 'false'; document.getElementById('toggleIcon').textContent = 'visibility'; document.getElementById('viewAccountModal').classList.add('open'); }); @@ -596,14 +732,15 @@ class AccountManager { btn.addEventListener('click', () => { const passwordEl = document.getElementById('viewAccountPassword'); const toggleIcon = document.getElementById('toggleIcon'); + const storedPwd = passwordEl.dataset.password || this.currentViewAccount?.AccountPassword || ''; const isVisible = passwordEl.dataset.visible === 'true'; - + if (isVisible) { passwordEl.textContent = '••••••••'; passwordEl.dataset.visible = 'false'; toggleIcon.textContent = 'visibility'; } else { - passwordEl.textContent = this.currentViewAccount?.AccountPassword || ''; + passwordEl.textContent = storedPwd || '(no password stored)'; passwordEl.dataset.visible = 'true'; toggleIcon.textContent = 'visibility_off'; } @@ -733,26 +870,46 @@ class AccountManager { serviceFilter.value = this.accountServiceFilter || ''; serviceFilter.addEventListener('change', (e) => { this.accountServiceFilter = e.target.value; - this.refreshAccountsUI(); + this.renderAccountsTableBody(); }); } const accountSearch = document.getElementById('accountSearch'); if (accountSearch) { accountSearch.value = this.accountSearchTerm; - accountSearch.addEventListener('input', (e) => { - this.accountSearchTerm = e.target.value.toLowerCase(); - this.refreshAccountsUI(); - }); + const handleAccountSearch = event => { + this.accountSearchTerm = event.target.value.toLowerCase(); + this.renderAccountsTableBody(); + }; + + accountSearch.addEventListener('input', handleAccountSearch); + + // Restore focus/selection after renders to avoid typing interruptions + accountSearch.addEventListener('focus', () => { + accountSearch.dataset.focused = 'true'; + }); + accountSearch.addEventListener('blur', () => { + accountSearch.dataset.focused = 'false'; + }); } const appSearch = document.getElementById('appSearch'); if (appSearch) { appSearch.value = this.applicationSearchTerm; - appSearch.addEventListener('input', (e) => { - this.applicationSearchTerm = e.target.value.toLowerCase(); - this.refreshApplicationsUI(); - }); + const handleApplicationSearch = event => { + this.applicationSearchTerm = event.target.value.toLowerCase(); + this.renderApplicationsTableBody(); + }; + + appSearch.addEventListener('input', handleApplicationSearch); + + // Restore focus/selection after renders to avoid typing interruptions + appSearch.addEventListener('focus', () => { + appSearch.dataset.focused = 'true'; + }); + appSearch.addEventListener('blur', () => { + appSearch.dataset.focused = 'false'; + }); } } @@ -820,12 +977,8 @@ class AccountManager { async refreshAccountsUI() { await this.fetchAccounts(); - const mainContent = document.getElementById('mainContent'); - if (mainContent) { - mainContent.innerHTML = this.getAccountsContent(); - this.setupAccountRowListeners(); - this.setupAddButtonListeners(); - this.setupFilters(); + if (this.currentPage === 'accounts') { + this.renderView('accounts'); } } @@ -865,12 +1018,8 @@ class AccountManager { async refreshApplicationsUI() { await this.fetchApplications(); - const mainContent = document.getElementById('mainContent'); - if (mainContent) { - mainContent.innerHTML = this.getApplicationsContent(); - this.setupAccountRowListeners(); - this.setupAddButtonListeners(); - this.setupFilters(); + if (this.currentPage === 'applications') { + this.renderView('applications'); } } diff --git a/public/pages/accounts.html b/public/pages/accounts.html index bf67bc5..621c16b 100644 --- a/public/pages/accounts.html +++ b/public/pages/accounts.html @@ -90,163 +90,9 @@ .modal-backdrop.open .modal-content { transform: scale(1); } - - -
-
-
-
- search - -
-
-
-
- account_circle -
- User Account - Administrator -
-
- -
-
-
-
- - - - - - - - -
-
- + \ No newline at end of file diff --git a/public/pages/applications.html b/public/pages/applications.html index 74ff2bf..75de4aa 100644 --- a/public/pages/applications.html +++ b/public/pages/applications.html @@ -90,150 +90,9 @@ .modal-backdrop.open .modal-content { transform: scale(1); } - - -
-
-
-
- search - -
-
-
-
- account_circle -
- User Account - Administrator -
-
- -
-
-
-
- - - - - - - - -
-
- + \ No newline at end of file diff --git a/public/pages/index.html b/public/pages/index.html index 12d4c6e..a6ab9a6 100644 --- a/public/pages/index.html +++ b/public/pages/index.html @@ -124,15 +124,15 @@