reponsive
This commit is contained in:
120
public/js/app.js
120
public/js/app.js
@@ -28,6 +28,8 @@ class AccountManager {
|
|||||||
this.accountServiceFilter = '';
|
this.accountServiceFilter = '';
|
||||||
this.userSearchTerm = '';
|
this.userSearchTerm = '';
|
||||||
this.userRoleFilter = '';
|
this.userRoleFilter = '';
|
||||||
|
this.mobileBreakpoint = 900;
|
||||||
|
this.boundResizeHandler = null;
|
||||||
this.configureNotifications();
|
this.configureNotifications();
|
||||||
this.initPromise = this.init();
|
this.initPromise = this.init();
|
||||||
this.pendingAccountAppId = undefined;
|
this.pendingAccountAppId = undefined;
|
||||||
@@ -121,6 +123,7 @@ class AccountManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
|
this.setupResponsiveShell();
|
||||||
this.loadModals(); // Load modals từ file riêng
|
this.loadModals(); // Load modals từ file riêng
|
||||||
// Single-page navigation based on hash
|
// Single-page navigation based on hash
|
||||||
this.handleRoute(location.hash || '#dashboard');
|
this.handleRoute(location.hash || '#dashboard');
|
||||||
@@ -129,6 +132,9 @@ class AccountManager {
|
|||||||
|
|
||||||
handleRoute(hash) {
|
handleRoute(hash) {
|
||||||
const route = (hash || '#dashboard').replace('#', '') || 'dashboard';
|
const route = (hash || '#dashboard').replace('#', '') || 'dashboard';
|
||||||
|
if (this.isMobileViewport()) {
|
||||||
|
this.closeMobileNav();
|
||||||
|
}
|
||||||
this.renderView(route);
|
this.renderView(route);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,6 +186,74 @@ class AccountManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isMobileViewport() {
|
||||||
|
return window.matchMedia(`(max-width: ${this.mobileBreakpoint}px)`).matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
setupResponsiveShell() {
|
||||||
|
const menuBtn = document.getElementById('mobileMenuBtn');
|
||||||
|
const backdrop = document.getElementById('sidebarBackdrop');
|
||||||
|
|
||||||
|
if (menuBtn && !menuBtn.dataset.boundClick) {
|
||||||
|
menuBtn.addEventListener('click', () => this.toggleMobileNav());
|
||||||
|
menuBtn.dataset.boundClick = 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backdrop && !backdrop.dataset.boundClick) {
|
||||||
|
backdrop.addEventListener('click', () => this.closeMobileNav());
|
||||||
|
backdrop.dataset.boundClick = 'true';
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('[data-nav]').forEach(link => {
|
||||||
|
if (!link.dataset.boundMobileClose) {
|
||||||
|
link.addEventListener('click', () => {
|
||||||
|
if (this.isMobileViewport()) {
|
||||||
|
this.closeMobileNav();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
link.dataset.boundMobileClose = 'true';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!this.boundResizeHandler) {
|
||||||
|
this.boundResizeHandler = () => {
|
||||||
|
if (!this.isMobileViewport()) {
|
||||||
|
this.closeMobileNav();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.addEventListener('resize', this.boundResizeHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isMobileViewport()) {
|
||||||
|
this.closeMobileNav();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMobileNav() {
|
||||||
|
if (document.body.classList.contains('mobile-nav-open')) {
|
||||||
|
this.closeMobileNav();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.openMobileNav();
|
||||||
|
}
|
||||||
|
|
||||||
|
openMobileNav() {
|
||||||
|
if (!this.isMobileViewport()) return;
|
||||||
|
document.body.classList.add('mobile-nav-open');
|
||||||
|
const menuBtn = document.getElementById('mobileMenuBtn');
|
||||||
|
if (menuBtn) {
|
||||||
|
menuBtn.setAttribute('aria-expanded', 'true');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closeMobileNav() {
|
||||||
|
document.body.classList.remove('mobile-nav-open');
|
||||||
|
const menuBtn = document.getElementById('mobileMenuBtn');
|
||||||
|
if (menuBtn) {
|
||||||
|
menuBtn.setAttribute('aria-expanded', 'false');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fetchApplications() {
|
async fetchApplications() {
|
||||||
const res = await fetch(`${this.apiBase}/applications`);
|
const res = await fetch(`${this.apiBase}/applications`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
@@ -284,6 +358,7 @@ class AccountManager {
|
|||||||
// Close with Escape key
|
// Close with Escape key
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
|
this.closeMobileNav();
|
||||||
this.closeModals();
|
this.closeModals();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -306,6 +381,7 @@ class AccountManager {
|
|||||||
// Account table row clicks
|
// Account table row clicks
|
||||||
this.setupAccountRowListeners();
|
this.setupAccountRowListeners();
|
||||||
this.setupFilters();
|
this.setupFilters();
|
||||||
|
this.setupResponsiveShell();
|
||||||
}
|
}
|
||||||
|
|
||||||
setupFormListeners() {
|
setupFormListeners() {
|
||||||
@@ -390,15 +466,15 @@ class AccountManager {
|
|||||||
|
|
||||||
renderDashboard() {
|
renderDashboard() {
|
||||||
return `
|
return `
|
||||||
<div class="flex-1 flex flex-col p-6 space-y-6 min-h-0 overflow-auto">
|
<div class="dashboard-page flex-1 flex flex-col p-4 md:p-6 space-y-6 min-h-0 overflow-auto">
|
||||||
<!-- Title and Stats -->
|
<!-- Title and Stats -->
|
||||||
<div class="flex items-end justify-between shrink-0">
|
<div class="dashboard-header flex items-end justify-between shrink-0">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-extrabold text-on-surface tracking-tight leading-none">System Overview</h1>
|
<h1 class="text-2xl font-extrabold text-on-surface tracking-tight leading-none">System Overview</h1>
|
||||||
<p class="text-xs text-on-surface-variant font-medium mt-1">Account & Service Management</p>
|
<p class="text-xs text-on-surface-variant font-medium mt-1">Account & Service Management</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="dashboard-actions flex gap-2">
|
||||||
<a href="./accounts.html" class="py-1.5 px-3 bg-primary text-on-primary rounded-lg text-xs font-bold flex items-center gap-1.5 shadow-sm active:scale-95 duration-100">
|
<a href="#accounts" class="py-1.5 px-3 bg-primary text-on-primary rounded-lg text-xs font-bold flex items-center gap-1.5 shadow-sm active:scale-95 duration-100">
|
||||||
<span class="material-symbols-outlined text-sm">add</span>
|
<span class="material-symbols-outlined text-sm">add</span>
|
||||||
Add Account
|
Add Account
|
||||||
</a>
|
</a>
|
||||||
@@ -406,7 +482,7 @@ class AccountManager {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Metric Grid -->
|
<!-- Metric Grid -->
|
||||||
<div class="grid grid-cols-4 gap-4 shrink-0">
|
<div class="dashboard-stats grid grid-cols-4 gap-4 shrink-0">
|
||||||
<div class="bg-surface-container-lowest p-4 rounded-xl border border-outline-variant/15 flex flex-col">
|
<div class="bg-surface-container-lowest p-4 rounded-xl border border-outline-variant/15 flex flex-col">
|
||||||
<span class="text-[10px] font-bold text-on-surface-variant uppercase tracking-wider mb-2">Applications</span>
|
<span class="text-[10px] font-bold text-on-surface-variant uppercase tracking-wider mb-2">Applications</span>
|
||||||
<div class="flex items-baseline justify-between">
|
<div class="flex items-baseline justify-between">
|
||||||
@@ -461,7 +537,7 @@ class AccountManager {
|
|||||||
</div>
|
</div>
|
||||||
` : `
|
` : `
|
||||||
<div class="flex-1 flex items-center justify-center text-center">
|
<div class="flex-1 flex items-center justify-center text-center">
|
||||||
<p class="text-sm text-on-surface-variant">No accounts yet. <a href="./accounts.html" class="text-primary font-bold">Create one</a></p>
|
<p class="text-sm text-on-surface-variant">No accounts yet. <a href="#accounts" class="text-primary font-bold">Create one</a></p>
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
@@ -475,9 +551,9 @@ class AccountManager {
|
|||||||
const pageInfo = this.getPaged(filteredAccounts, this.accountPage, this.accountPageSize);
|
const pageInfo = this.getPaged(filteredAccounts, this.accountPage, this.accountPageSize);
|
||||||
this.accountPage = pageInfo.current;
|
this.accountPage = pageInfo.current;
|
||||||
return `
|
return `
|
||||||
<div class="p-4 md:p-6 flex flex-col h-full overflow-hidden">
|
<div class="accounts-page p-4 md:p-6 flex flex-col h-full overflow-hidden">
|
||||||
<!-- Page Header -->
|
<!-- Page Header -->
|
||||||
<div class="flex items-center justify-between mb-4 shrink-0">
|
<div class="page-header flex items-center justify-between mb-4 shrink-0">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-xl font-extrabold text-on-surface tracking-tight">Accounts Management</h1>
|
<h1 class="text-xl font-extrabold text-on-surface tracking-tight">Accounts Management</h1>
|
||||||
<p class="text-[10px] text-on-surface-variant uppercase font-semibold tracking-widest mt-0.5">Administrative Access Control</p>
|
<p class="text-[10px] text-on-surface-variant uppercase font-semibold tracking-widest mt-0.5">Administrative Access Control</p>
|
||||||
@@ -489,7 +565,7 @@ class AccountManager {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filters -->
|
<!-- Filters -->
|
||||||
<div class="flex items-center gap-3 mb-4">
|
<div class="page-filters flex items-center gap-3 mb-4">
|
||||||
<div class="flex items-center gap-1.5">
|
<div class="flex items-center gap-1.5">
|
||||||
<span class="text-[10px] font-bold uppercase text-on-surface-variant">Service</span>
|
<span class="text-[10px] font-bold uppercase text-on-surface-variant">Service</span>
|
||||||
<select id="serviceFilter" class="bg-surface-container-low border-slate-200 rounded-md text-[11px] py-1 px-2 pr-6 focus:ring-1 focus:ring-primary shadow-sm">
|
<select id="serviceFilter" class="bg-surface-container-low border-slate-200 rounded-md text-[11px] py-1 px-2 pr-6 focus:ring-1 focus:ring-primary shadow-sm">
|
||||||
@@ -506,7 +582,7 @@ class AccountManager {
|
|||||||
<!-- Accounts Table -->
|
<!-- Accounts Table -->
|
||||||
<div class="flex-1 bg-white rounded-xl border border-slate-200 shadow-sm flex flex-col overflow-hidden min-h-0">
|
<div class="flex-1 bg-white rounded-xl border border-slate-200 shadow-sm flex flex-col overflow-hidden min-h-0">
|
||||||
${pageInfo.data.length > 0 ? `
|
${pageInfo.data.length > 0 ? `
|
||||||
<div class="overflow-y-auto overflow-x-auto flex-1">
|
<div class="table-wrap overflow-y-auto overflow-x-auto flex-1">
|
||||||
<table class="w-full text-left border-collapse w-full">
|
<table class="w-full text-left border-collapse w-full">
|
||||||
<thead class="sticky top-0 z-10">
|
<thead class="sticky top-0 z-10">
|
||||||
<tr class="bg-slate-50 border-b border-slate-200">
|
<tr class="bg-slate-50 border-b border-slate-200">
|
||||||
@@ -545,7 +621,7 @@ class AccountManager {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between px-4 py-2 border-t border-slate-200 bg-slate-50 text-xs text-slate-600" id="accountsPager">
|
<div class="page-pager flex items-center justify-between px-4 py-2 border-t border-slate-200 bg-slate-50 text-xs text-slate-600" id="accountsPager">
|
||||||
<span>Showing ${pageInfo.start}-${pageInfo.end} of ${pageInfo.total}</span>
|
<span>Showing ${pageInfo.start}-${pageInfo.end} of ${pageInfo.total}</span>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button class="account-page-btn px-2 py-1 border border-slate-200 rounded text-xs ${pageInfo.current === 1 ? 'opacity-50 cursor-not-allowed' : ''}" data-page="${pageInfo.current - 1}" ${pageInfo.current === 1 ? 'disabled' : ''}>Prev</button>
|
<button class="account-page-btn px-2 py-1 border border-slate-200 rounded text-xs ${pageInfo.current === 1 ? 'opacity-50 cursor-not-allowed' : ''}" data-page="${pageInfo.current - 1}" ${pageInfo.current === 1 ? 'disabled' : ''}>Prev</button>
|
||||||
@@ -575,9 +651,9 @@ class AccountManager {
|
|||||||
const pageInfo = this.getPaged(filteredApps, this.appPage, this.appPageSize);
|
const pageInfo = this.getPaged(filteredApps, this.appPage, this.appPageSize);
|
||||||
this.appPage = pageInfo.current;
|
this.appPage = pageInfo.current;
|
||||||
return `
|
return `
|
||||||
<div class="flex flex-col p-6 overflow-hidden h-full">
|
<div class="apps-page flex flex-col p-4 md:p-6 overflow-hidden h-full">
|
||||||
<!-- Header Section -->
|
<!-- Header Section -->
|
||||||
<div class="flex items-center justify-between gap-6 mb-6 shrink-0">
|
<div class="page-header flex items-center justify-between gap-6 mb-6 shrink-0">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-2xl font-extrabold text-on-surface tracking-tight">Applications</h1>
|
<h1 class="text-2xl font-extrabold text-on-surface tracking-tight">Applications</h1>
|
||||||
<p class="text-sm text-on-surface-variant">Manage and monitor active infrastructure services.</p>
|
<p class="text-sm text-on-surface-variant">Manage and monitor active infrastructure services.</p>
|
||||||
@@ -589,7 +665,7 @@ class AccountManager {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Dashboard Stats -->
|
<!-- Dashboard Stats -->
|
||||||
<div class="grid grid-cols-3 gap-4 mb-6 shrink-0">
|
<div class="apps-stats grid grid-cols-3 gap-4 mb-6 shrink-0">
|
||||||
<div class="bg-surface-container-lowest px-4 py-3 rounded-xl shadow-sm border border-outline-variant/5 flex items-center gap-4">
|
<div class="bg-surface-container-lowest px-4 py-3 rounded-xl shadow-sm border border-outline-variant/5 flex items-center gap-4">
|
||||||
<div class="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center text-primary">
|
<div class="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center text-primary">
|
||||||
<span class="material-symbols-outlined text-xl">lan</span>
|
<span class="material-symbols-outlined text-xl">lan</span>
|
||||||
@@ -621,11 +697,11 @@ class AccountManager {
|
|||||||
|
|
||||||
<!-- Applications List -->
|
<!-- Applications List -->
|
||||||
<div class="bg-surface-container-lowest rounded-xl shadow-sm border border-outline-variant/10 overflow-hidden flex flex-col flex-1 min-h-0">
|
<div class="bg-surface-container-lowest rounded-xl shadow-sm border border-outline-variant/10 overflow-hidden flex flex-col flex-1 min-h-0">
|
||||||
<div class="px-4 py-3 border-b border-outline-variant/10 flex items-center gap-2 bg-surface-container-low/40">
|
<div class="page-filters px-4 py-3 border-b border-outline-variant/10 flex items-center gap-2 bg-surface-container-low/40">
|
||||||
<span class="text-[10px] font-bold uppercase text-on-surface-variant">Search</span>
|
<span class="text-[10px] font-bold uppercase text-on-surface-variant">Search</span>
|
||||||
<input id="appSearch" class="flex-1 bg-white border border-slate-200 rounded-md text-[11px] py-1 px-2 focus:ring-1 focus:ring-primary shadow-sm" placeholder="Search by name, type, description, url">
|
<input id="appSearch" class="flex-1 bg-white border border-slate-200 rounded-md text-[11px] py-1 px-2 focus:ring-1 focus:ring-primary shadow-sm" placeholder="Search by name, type, description, url">
|
||||||
</div>
|
</div>
|
||||||
<div class="overflow-y-auto flex-1">
|
<div class="table-wrap overflow-y-auto overflow-x-auto flex-1">
|
||||||
<table class="w-full text-left border-collapse">
|
<table class="w-full text-left border-collapse">
|
||||||
<thead class="sticky top-0 bg-surface-container-lowest z-10">
|
<thead class="sticky top-0 bg-surface-container-lowest z-10">
|
||||||
<tr class="bg-surface-container-low/30 border-b border-outline-variant/10">
|
<tr class="bg-surface-container-low/30 border-b border-outline-variant/10">
|
||||||
@@ -677,7 +753,7 @@ class AccountManager {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between px-4 py-2 border-t border-outline-variant/10 bg-surface-container-low/60 text-xs text-on-surface-variant" id="appsPager">
|
<div class="page-pager flex items-center justify-between px-4 py-2 border-t border-outline-variant/10 bg-surface-container-low/60 text-xs text-on-surface-variant" id="appsPager">
|
||||||
<span>Showing ${pageInfo.start}-${pageInfo.end} of ${pageInfo.total}</span>
|
<span>Showing ${pageInfo.start}-${pageInfo.end} of ${pageInfo.total}</span>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button class="app-page-btn px-2 py-1 border border-slate-200 rounded text-xs ${pageInfo.current === 1 ? 'opacity-50 cursor-not-allowed' : ''}" data-page="${pageInfo.current - 1}" ${pageInfo.current === 1 ? 'disabled' : ''}>Prev</button>
|
<button class="app-page-btn px-2 py-1 border border-slate-200 rounded text-xs ${pageInfo.current === 1 ? 'opacity-50 cursor-not-allowed' : ''}" data-page="${pageInfo.current - 1}" ${pageInfo.current === 1 ? 'disabled' : ''}>Prev</button>
|
||||||
@@ -1501,8 +1577,8 @@ class AccountManager {
|
|||||||
const pageInfo = this.getPaged(filteredUsers, this.userPage, this.userPageSize);
|
const pageInfo = this.getPaged(filteredUsers, this.userPage, this.userPageSize);
|
||||||
this.userPage = pageInfo.current;
|
this.userPage = pageInfo.current;
|
||||||
return `
|
return `
|
||||||
<div class="p-6 w-full h-full flex flex-col overflow-hidden">
|
<div class="users-page p-4 md:p-6 w-full h-full flex flex-col overflow-hidden">
|
||||||
<div class="flex justify-between items-center mb-6 shrink-0">
|
<div class="page-header flex justify-between items-center mb-6 shrink-0">
|
||||||
<h1 class="text-2xl font-bold text-slate-900 dark:text-slate-50">Users Management</h1>
|
<h1 class="text-2xl font-bold text-slate-900 dark:text-slate-50">Users Management</h1>
|
||||||
<button id="addUserBtn" class="bg-primary hover:bg-primary-dim text-on-primary font-bold py-2 px-4 rounded-lg transition-all active:scale-95 flex items-center gap-2">
|
<button id="addUserBtn" class="bg-primary hover:bg-primary-dim text-on-primary font-bold py-2 px-4 rounded-lg transition-all active:scale-95 flex items-center gap-2">
|
||||||
<span class="material-symbols-outlined text-base">add</span>
|
<span class="material-symbols-outlined text-base">add</span>
|
||||||
@@ -1511,7 +1587,7 @@ class AccountManager {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Search and Filter -->
|
<!-- Search and Filter -->
|
||||||
<div class="mb-4 flex gap-3 shrink-0">
|
<div class="users-controls mb-4 flex gap-3 shrink-0">
|
||||||
<div class="flex-1 relative">
|
<div class="flex-1 relative">
|
||||||
<input type="text" id="userSearch" placeholder="Search users..." value="${this.userSearchTerm}" class="w-full px-4 py-2 border border-outline-variant/30 rounded-lg bg-surface-container-low dark:bg-slate-800 focus:ring-2 focus:ring-primary focus:border-transparent transition-all">
|
<input type="text" id="userSearch" placeholder="Search users..." value="${this.userSearchTerm}" class="w-full px-4 py-2 border border-outline-variant/30 rounded-lg bg-surface-container-low dark:bg-slate-800 focus:ring-2 focus:ring-primary focus:border-transparent transition-all">
|
||||||
<span class="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 material-symbols-outlined">search</span>
|
<span class="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 material-symbols-outlined">search</span>
|
||||||
@@ -1524,7 +1600,7 @@ class AccountManager {
|
|||||||
|
|
||||||
<!-- Users Table -->
|
<!-- Users Table -->
|
||||||
<div class="flex-1 overflow-hidden border border-outline-variant/20 rounded-lg flex flex-col min-h-0">
|
<div class="flex-1 overflow-hidden border border-outline-variant/20 rounded-lg flex flex-col min-h-0">
|
||||||
<div class="overflow-auto flex-1">
|
<div class="table-wrap overflow-auto flex-1">
|
||||||
<table class="w-full text-sm">
|
<table class="w-full text-sm">
|
||||||
<thead class="bg-slate-100 dark:bg-slate-800 border-b border-outline-variant/20 sticky top-0">
|
<thead class="bg-slate-100 dark:bg-slate-800 border-b border-outline-variant/20 sticky top-0">
|
||||||
<tr>
|
<tr>
|
||||||
@@ -1574,7 +1650,7 @@ class AccountManager {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between px-4 py-2 border-t border-outline-variant/20 bg-slate-50 dark:bg-slate-800/40 text-xs text-on-surface-variant" id="usersPager">
|
<div class="page-pager flex items-center justify-between px-4 py-2 border-t border-outline-variant/20 bg-slate-50 dark:bg-slate-800/40 text-xs text-on-surface-variant" id="usersPager">
|
||||||
<span>Showing ${pageInfo.start}-${pageInfo.end} of ${pageInfo.total}</span>
|
<span>Showing ${pageInfo.start}-${pageInfo.end} of ${pageInfo.total}</span>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button class="user-page-btn px-2 py-1 border border-slate-200 rounded text-xs ${pageInfo.current === 1 ? 'opacity-50 cursor-not-allowed' : ''}" data-page="${pageInfo.current - 1}" ${pageInfo.current === 1 ? 'disabled' : ''}>Prev</button>
|
<button class="user-page-btn px-2 py-1 border border-slate-200 rounded text-xs ${pageInfo.current === 1 ? 'opacity-50 cursor-not-allowed' : ''}" data-page="${pageInfo.current - 1}" ${pageInfo.current === 1 ? 'disabled' : ''}>Prev</button>
|
||||||
|
|||||||
@@ -43,11 +43,150 @@
|
|||||||
.modal-backdrop.open .modal-content {
|
.modal-backdrop.open .modal-content {
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.app-shell {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mobileMenuBtn,
|
||||||
|
#sidebarBackdrop {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
body.app-shell {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 100dvh;
|
||||||
|
height: 100dvh;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mobileMenuBtn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebarBackdrop {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 70;
|
||||||
|
background: rgba(15, 23, 42, 0.45);
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
transition: opacity 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#appSidebar {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0 auto 0 0;
|
||||||
|
height: 100dvh;
|
||||||
|
width: min(82vw, 16rem);
|
||||||
|
z-index: 80;
|
||||||
|
transform: translateX(-100%);
|
||||||
|
transition: transform 0.2s ease-in-out;
|
||||||
|
box-shadow: 0 20px 45px rgba(15, 23, 42, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile-nav-open #sidebarBackdrop {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile-nav-open #appSidebar {
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#appMain {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
height: 100dvh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#appMain > header {
|
||||||
|
padding-left: 0.75rem;
|
||||||
|
padding-right: 0.75rem;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar-actions {
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#profileBtn {
|
||||||
|
padding: 0.4rem 0.55rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#profileBtn .profile-meta {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-header,
|
||||||
|
.page-header,
|
||||||
|
.page-filters,
|
||||||
|
.users-controls {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-filters > div,
|
||||||
|
.users-controls > div,
|
||||||
|
.users-controls select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-actions,
|
||||||
|
.page-header button,
|
||||||
|
.page-header > button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-stats,
|
||||||
|
.apps-stats {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrap {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrap table {
|
||||||
|
min-width: 700px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-pager {
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-backdrop {
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-backdrop .modal-content {
|
||||||
|
width: calc(100% - 1rem);
|
||||||
|
max-height: min(88dvh, 700px);
|
||||||
|
margin: 0.5rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
border-radius: 0.9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 560px) {
|
||||||
|
.dashboard-stats,
|
||||||
|
.apps-stats {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-background text-on-surface antialiased flex h-screen w-screen">
|
<body class="app-shell bg-background text-on-surface antialiased flex h-screen w-screen">
|
||||||
<!-- SideNavBar -->
|
<!-- SideNavBar -->
|
||||||
<aside class="h-screen w-56 flex flex-col bg-slate-100 dark:bg-slate-900 font-manrope text-sm font-medium border-r border-outline-variant/10 shrink-0">
|
<aside id="appSidebar" class="h-screen w-56 flex flex-col bg-slate-100 dark:bg-slate-900 font-manrope text-sm font-medium border-r border-outline-variant/10 shrink-0">
|
||||||
<div class="flex flex-col h-full py-6">
|
<div class="flex flex-col h-full py-6">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="px-6 mb-8">
|
<div class="px-6 mb-8">
|
||||||
@@ -80,17 +219,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
|
<button id="sidebarBackdrop" type="button" aria-label="Close navigation menu"></button>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<main class="flex-1 flex flex-col h-screen min-w-0">
|
<main id="appMain" class="flex-1 flex flex-col h-screen min-w-0">
|
||||||
<!-- TopAppBar -->
|
<!-- TopAppBar -->
|
||||||
<header class="h-14 flex items-center justify-between px-6 bg-slate-50/80 dark:bg-slate-950/80 backdrop-blur-xl border-b border-outline-variant/10 shrink-0">
|
<header class="h-14 flex items-center justify-between px-6 bg-slate-50/80 dark:bg-slate-950/80 backdrop-blur-xl border-b border-outline-variant/10 shrink-0">
|
||||||
<div class="flex items-center gap-4 flex-1">
|
<div class="flex items-center gap-4 flex-1">
|
||||||
|
<button id="mobileMenuBtn" type="button" class="p-2 rounded-lg text-slate-600 dark:text-slate-300 hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors" aria-label="Open navigation menu" aria-controls="appSidebar" aria-expanded="false">
|
||||||
|
<span class="material-symbols-outlined">menu</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-4">
|
<div class="topbar-actions flex items-center gap-4">
|
||||||
<button id="profileBtn" type="button" class="flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-100 dark:bg-slate-800 hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors" title="Edit profile">
|
<button id="profileBtn" type="button" class="profile-btn flex items-center gap-2 px-4 py-2 rounded-lg bg-slate-100 dark:bg-slate-800 hover:bg-slate-200 dark:hover:bg-slate-700 transition-colors" title="Edit profile">
|
||||||
<span class="material-symbols-outlined text-slate-600 dark:text-slate-400">account_circle</span>
|
<span class="material-symbols-outlined text-slate-600 dark:text-slate-400">account_circle</span>
|
||||||
<div class="flex flex-col">
|
<div class="profile-meta flex flex-col">
|
||||||
<span id="accountUsername" class="text-xs font-semibold text-slate-900 dark:text-slate-50">User Account</span>
|
<span id="accountUsername" class="text-xs font-semibold text-slate-900 dark:text-slate-50">User Account</span>
|
||||||
<span id="accountRole" class="text-[10px] text-slate-500 dark:text-slate-400">Administrator</span>
|
<span id="accountRole" class="text-[10px] text-slate-500 dark:text-slate-400">Administrator</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user