Sidebar with no hamburger menu
This commit is contained in:
@@ -90,7 +90,6 @@ h1 {
|
|||||||
.user-header {
|
.user-header {
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
margin-left: 50px; /* Space for hamburger menu */
|
|
||||||
color: var(--terminal-green);
|
color: var(--terminal-green);
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -1163,223 +1162,307 @@ input[type="checkbox"]:checked {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== HAMBURGER MENU STYLES - TERMINAL EDITION ===== */
|
/* ===== COLLAPSIBLE ASCII BANNER ===== */
|
||||||
.hamburger-menu {
|
.ascii-banner-wrapper {
|
||||||
position: absolute;
|
max-width: 1600px;
|
||||||
top: 20px;
|
margin: 0 auto 1rem auto;
|
||||||
left: 20px;
|
border: 2px solid var(--terminal-green);
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hamburger-icon {
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 24px;
|
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
padding: 12px;
|
|
||||||
border: 2px solid var(--terminal-green);
|
|
||||||
border-radius: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
color: var(--terminal-green);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hamburger-icon::before {
|
.ascii-banner-wrapper.collapsed .banner-content {
|
||||||
content: '[';
|
display: none;
|
||||||
margin-right: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hamburger-icon::after {
|
.banner-toggle {
|
||||||
content: ']';
|
|
||||||
margin-left: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hamburger-content {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: -300px;
|
|
||||||
width: 250px;
|
|
||||||
height: 100%;
|
|
||||||
background: var(--bg-primary);
|
|
||||||
border-right: 3px double var(--terminal-green);
|
|
||||||
transition: left 0.3s ease;
|
|
||||||
padding: 20px;
|
|
||||||
overflow-y: auto;
|
|
||||||
z-index: 99;
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
box-shadow: 0 0 30px rgba(0, 255, 65, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ASCII decoration at top */
|
|
||||||
.hamburger-content::before {
|
|
||||||
content: '╔═══════════════════╗\A║ MENU SYSTEM ║\A╚═══════════════════╝';
|
|
||||||
white-space: pre;
|
|
||||||
display: block;
|
|
||||||
color: var(--terminal-green);
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
font-size: 0.8rem;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hamburger-content.open {
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hamburger-content h3 {
|
|
||||||
color: var(--terminal-amber);
|
|
||||||
text-shadow: var(--glow-amber);
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
text-transform: uppercase;
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hamburger-content h3::before {
|
|
||||||
content: '> ';
|
|
||||||
color: var(--terminal-green);
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-hamburger {
|
|
||||||
position: absolute;
|
|
||||||
top: 10px;
|
|
||||||
right: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 24px;
|
|
||||||
background: transparent;
|
|
||||||
padding: 10px;
|
|
||||||
border: 2px solid var(--terminal-green);
|
|
||||||
border-radius: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
color: var(--terminal-green);
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-hamburger::before {
|
|
||||||
content: '[';
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-hamburger::after {
|
|
||||||
content: ']';
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-hamburger:hover {
|
|
||||||
color: var(--priority-1);
|
|
||||||
border-color: var(--priority-1);
|
|
||||||
text-shadow: var(--glow-red);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hamburger menu inline editing styles - TERMINAL */
|
|
||||||
.ticket-info-editable {
|
|
||||||
padding: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.editable-field, .info-field {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
position: relative;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid var(--terminal-green);
|
|
||||||
background: rgba(0, 255, 65, 0.03);
|
|
||||||
}
|
|
||||||
|
|
||||||
.editable-field label, .info-field label {
|
|
||||||
flex: 0 0 auto;
|
|
||||||
margin-right: 10px;
|
|
||||||
color: var(--terminal-green);
|
|
||||||
font-family: var(--font-mono);
|
|
||||||
}
|
|
||||||
|
|
||||||
.editable-value {
|
|
||||||
flex: 1;
|
|
||||||
text-align: right;
|
|
||||||
min-height: 20px;
|
|
||||||
display: inline-block;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 0;
|
|
||||||
transition: all 0.2s;
|
|
||||||
color: var(--terminal-amber);
|
|
||||||
}
|
|
||||||
|
|
||||||
.editable-value::before {
|
|
||||||
content: '[ ';
|
|
||||||
color: var(--terminal-green);
|
|
||||||
}
|
|
||||||
|
|
||||||
.editable-value::after {
|
|
||||||
content: ' ]';
|
|
||||||
color: var(--terminal-green);
|
|
||||||
}
|
|
||||||
|
|
||||||
.editable-value:hover {
|
|
||||||
background-color: rgba(0, 255, 65, 0.1) !important;
|
|
||||||
text-shadow: var(--glow-amber);
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-dropdown {
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
right: 0;
|
|
||||||
background: var(--bg-primary);
|
|
||||||
border: 2px solid var(--terminal-green);
|
|
||||||
border-radius: 0;
|
|
||||||
padding: 8px;
|
|
||||||
box-shadow: 0 0 20px rgba(0, 255, 65, 0.3);
|
|
||||||
z-index: 1000;
|
|
||||||
min-width: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-select {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 4px 8px;
|
background: var(--bg-secondary);
|
||||||
|
border: none;
|
||||||
|
color: var(--terminal-amber);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-align: left;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-shadow: var(--glow-amber);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-toggle:hover {
|
||||||
|
background: var(--hover-bg);
|
||||||
|
box-shadow: inset 0 0 20px rgba(255, 176, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-icon {
|
||||||
|
display: inline-block;
|
||||||
|
width: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--terminal-green);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.banner-content {
|
||||||
|
padding: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== CONDENSED TOOLBAR ===== */
|
||||||
|
.dashboard-toolbar {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 1rem;
|
||||||
|
background: var(--bg-secondary);
|
||||||
border: 2px solid var(--terminal-green);
|
border: 2px solid var(--terminal-green);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
box-shadow: var(--glow-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-title {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
color: var(--terminal-green);
|
||||||
|
text-shadow: var(--glow-green);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-search {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-center {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ticket-count {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
color: var(--terminal-amber);
|
||||||
|
text-shadow: var(--glow-amber);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-search-btn {
|
||||||
|
background: var(--bg-primary);
|
||||||
|
color: var(--priority-1);
|
||||||
|
border: 2px solid var(--priority-1);
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
text-decoration: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-search-btn:hover {
|
||||||
|
background: var(--priority-1);
|
||||||
|
color: var(--bg-primary);
|
||||||
|
box-shadow: var(--glow-red);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-results-info {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 2px solid var(--terminal-amber);
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
color: var(--terminal-amber);
|
||||||
|
text-shadow: var(--glow-amber);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline Bulk Actions */
|
||||||
|
.bulk-actions-inline {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border: 2px solid var(--terminal-amber);
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 1rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
color: var(--terminal-amber);
|
||||||
|
text-shadow: var(--glow-amber);
|
||||||
|
box-shadow: inset 0 0 20px rgba(255, 176, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-actions-inline .btn {
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile: Stack toolbar items */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.dashboard-toolbar {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-left,
|
||||||
|
.toolbar-center,
|
||||||
|
.toolbar-right {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-search {
|
||||||
|
min-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-title {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.toolbar-left {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-title {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== DASHBOARD SIDEBAR LAYOUT ===== */
|
||||||
|
.dashboard-layout {
|
||||||
|
display: flex;
|
||||||
|
gap: 1.5rem;
|
||||||
|
max-width: 1600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-sidebar {
|
||||||
|
width: 250px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: sticky;
|
||||||
|
top: 1rem;
|
||||||
|
max-height: calc(100vh - 2rem);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-sidebar .ascii-frame-inner {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
padding: 1rem;
|
||||||
|
border: 2px solid var(--terminal-green);
|
||||||
|
box-shadow: var(--glow-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-sidebar .ascii-subsection-header {
|
||||||
|
color: var(--terminal-amber);
|
||||||
|
text-shadow: var(--glow-amber);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
border-bottom: 2px solid var(--terminal-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-group {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-group h4 {
|
||||||
|
color: var(--terminal-amber);
|
||||||
|
text-shadow: var(--glow-amber);
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-group label {
|
||||||
|
display: block;
|
||||||
|
margin: 0.4rem 0;
|
||||||
|
color: var(--terminal-green);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-group label:hover {
|
||||||
|
color: var(--terminal-amber);
|
||||||
|
text-shadow: var(--glow-amber);
|
||||||
|
padding-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-group input[type="checkbox"] {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
accent-color: var(--terminal-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-sidebar .btn {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
color: var(--terminal-green);
|
color: var(--terminal-green);
|
||||||
|
border: 2px solid var(--terminal-green);
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.edit-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 4px;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.save-btn, .cancel-btn {
|
|
||||||
padding: 4px 8px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 12px;
|
transition: all 0.2s ease;
|
||||||
min-width: 24px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.save-btn {
|
.dashboard-sidebar .btn:hover {
|
||||||
background: #28a745;
|
background: var(--terminal-green);
|
||||||
color: white;
|
color: var(--bg-primary);
|
||||||
|
box-shadow: var(--glow-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.save-btn:hover {
|
.dashboard-sidebar .btn-secondary {
|
||||||
background: #218838;
|
background: transparent;
|
||||||
|
color: var(--terminal-amber);
|
||||||
|
border-color: var(--terminal-amber);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cancel-btn {
|
.dashboard-sidebar .btn-secondary:hover {
|
||||||
background: #dc3545;
|
background: var(--terminal-amber);
|
||||||
color: white;
|
color: var(--bg-primary);
|
||||||
|
box-shadow: var(--glow-amber);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cancel-btn:hover {
|
.dashboard-main {
|
||||||
background: #c82333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-field span {
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
text-align: right;
|
min-width: 0;
|
||||||
color: var(--text-secondary);
|
}
|
||||||
|
|
||||||
|
/* Mobile: Stack sidebar above content */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.dashboard-layout {
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-sidebar {
|
||||||
|
width: 100%;
|
||||||
|
position: static;
|
||||||
|
max-height: none;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== UTILITY STYLES ===== */
|
/* ===== UTILITY STYLES ===== */
|
||||||
|
|||||||
@@ -17,23 +17,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
// Dashboard-specific initialization
|
// Dashboard-specific initialization
|
||||||
initStatusFilter();
|
initStatusFilter();
|
||||||
initTableSorting();
|
initTableSorting();
|
||||||
|
initSidebarFilters();
|
||||||
console.log('Creating hamburger menu for dashboard...');
|
|
||||||
try {
|
|
||||||
createHamburgerMenu();
|
|
||||||
console.log('Hamburger menu created successfully');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error creating hamburger menu:', error);
|
|
||||||
}
|
|
||||||
} else if (isTicketPage) {
|
|
||||||
// Ticket page initialization
|
|
||||||
console.log('Creating hamburger menu for ticket page...');
|
|
||||||
try {
|
|
||||||
createHamburgerMenu();
|
|
||||||
console.log('Hamburger menu created successfully');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error creating hamburger menu:', error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize for all pages
|
// Initialize for all pages
|
||||||
@@ -55,6 +39,72 @@ function initTableSorting() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initSidebarFilters() {
|
||||||
|
const applyFiltersBtn = document.getElementById('apply-filters-btn');
|
||||||
|
const clearFiltersBtn = document.getElementById('clear-filters-btn');
|
||||||
|
|
||||||
|
if (applyFiltersBtn) {
|
||||||
|
applyFiltersBtn.addEventListener('click', () => {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
|
// Collect selected statuses
|
||||||
|
const selectedStatuses = Array.from(
|
||||||
|
document.querySelectorAll('.filter-group input[name="status"]:checked')
|
||||||
|
).map(cb => cb.value);
|
||||||
|
|
||||||
|
// Collect selected categories
|
||||||
|
const selectedCategories = Array.from(
|
||||||
|
document.querySelectorAll('.filter-group input[name="category"]:checked')
|
||||||
|
).map(cb => cb.value);
|
||||||
|
|
||||||
|
// Collect selected types
|
||||||
|
const selectedTypes = Array.from(
|
||||||
|
document.querySelectorAll('.filter-group input[name="type"]:checked')
|
||||||
|
).map(cb => cb.value);
|
||||||
|
|
||||||
|
// Update URL parameters
|
||||||
|
if (selectedStatuses.length > 0) {
|
||||||
|
params.set('status', selectedStatuses.join(','));
|
||||||
|
} else {
|
||||||
|
params.delete('status');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedCategories.length > 0) {
|
||||||
|
params.set('category', selectedCategories.join(','));
|
||||||
|
} else {
|
||||||
|
params.delete('category');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedTypes.length > 0) {
|
||||||
|
params.set('type', selectedTypes.join(','));
|
||||||
|
} else {
|
||||||
|
params.delete('type');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset to page 1 when filters change
|
||||||
|
params.set('page', '1');
|
||||||
|
|
||||||
|
// Reload with new parameters
|
||||||
|
window.location.search = params.toString();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clearFiltersBtn) {
|
||||||
|
clearFiltersBtn.addEventListener('click', () => {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
|
||||||
|
// Remove filter parameters
|
||||||
|
params.delete('status');
|
||||||
|
params.delete('category');
|
||||||
|
params.delete('type');
|
||||||
|
params.set('page', '1');
|
||||||
|
|
||||||
|
// Reload with cleared filters
|
||||||
|
window.location.search = params.toString();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function initSettingsModal() {
|
function initSettingsModal() {
|
||||||
const settingsIcon = document.querySelector('.settings-icon');
|
const settingsIcon = document.querySelector('.settings-icon');
|
||||||
if (settingsIcon) {
|
if (settingsIcon) {
|
||||||
@@ -354,452 +404,6 @@ function quickSave() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createHamburgerMenu() {
|
|
||||||
console.log('createHamburgerMenu called');
|
|
||||||
|
|
||||||
// Remove any existing hamburger menu first
|
|
||||||
const existingMenu = document.querySelector('.hamburger-menu');
|
|
||||||
if (existingMenu) {
|
|
||||||
console.log('Removing existing menu');
|
|
||||||
existingMenu.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
const hamburgerMenu = document.createElement('div');
|
|
||||||
hamburgerMenu.className = 'hamburger-menu';
|
|
||||||
|
|
||||||
// Better detection for ticket pages
|
|
||||||
const isTicketPage = window.location.pathname.includes('/ticket/') ||
|
|
||||||
window.location.href.includes('ticket.php') ||
|
|
||||||
document.querySelector('.ticket-details') !== null;
|
|
||||||
|
|
||||||
console.log('Is ticket page:', isTicketPage);
|
|
||||||
console.log('Has ticketData:', !!window.ticketData);
|
|
||||||
console.log('TicketData contents:', window.ticketData);
|
|
||||||
|
|
||||||
if (isTicketPage) {
|
|
||||||
// Wait for ticketData if it's not loaded yet
|
|
||||||
if (!window.ticketData) {
|
|
||||||
console.log('Waiting for ticket data...');
|
|
||||||
setTimeout(() => {
|
|
||||||
if (window.ticketData) {
|
|
||||||
console.log('Ticket data now available, recreating menu');
|
|
||||||
createHamburgerMenu();
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Creating ticket hamburger menu with data:', window.ticketData);
|
|
||||||
|
|
||||||
// Ticket page hamburger menu with inline editing
|
|
||||||
hamburgerMenu.innerHTML = `
|
|
||||||
<div class="hamburger-icon">☰</div>
|
|
||||||
<div class="hamburger-content">
|
|
||||||
<div class="close-hamburger">☰</div>
|
|
||||||
|
|
||||||
<div class="ascii-subsection-header">Ticket Actions</div>
|
|
||||||
|
|
||||||
<div class="ascii-frame-inner">
|
|
||||||
<div class="ticket-info-editable">
|
|
||||||
<div class="editable-field" data-field="priority">
|
|
||||||
<label><strong>Priority:</strong></label>
|
|
||||||
<span class="editable-value" data-current="${window.ticketData.priority}">P${window.ticketData.priority}</span>
|
|
||||||
<div class="edit-dropdown" style="display: none;">
|
|
||||||
<select class="field-select">
|
|
||||||
<option value="1" ${String(window.ticketData.priority) === '1' ? 'selected' : ''}>P1 - Critical</option>
|
|
||||||
<option value="2" ${String(window.ticketData.priority) === '2' ? 'selected' : ''}>P2 - High</option>
|
|
||||||
<option value="3" ${String(window.ticketData.priority) === '3' ? 'selected' : ''}>P3 - Medium</option>
|
|
||||||
<option value="4" ${String(window.ticketData.priority) === '4' ? 'selected' : ''}>P4 - Low</option>
|
|
||||||
<option value="5" ${String(window.ticketData.priority) === '5' ? 'selected' : ''}>P5 - Lowest</option>
|
|
||||||
</select>
|
|
||||||
<div class="edit-actions">
|
|
||||||
<button class="save-btn">✓</button>
|
|
||||||
<button class="cancel-btn">✗</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="editable-field" data-field="category">
|
|
||||||
<label><strong>Category:</strong></label>
|
|
||||||
<span class="editable-value" data-current="${window.ticketData.category}">${window.ticketData.category}</span>
|
|
||||||
<div class="edit-dropdown" style="display: none;">
|
|
||||||
<select class="field-select">
|
|
||||||
<option value="Hardware" ${window.ticketData.category === 'Hardware' ? 'selected' : ''}>Hardware</option>
|
|
||||||
<option value="Software" ${window.ticketData.category === 'Software' ? 'selected' : ''}>Software</option>
|
|
||||||
</select>
|
|
||||||
<div class="edit-actions">
|
|
||||||
<button class="save-btn">✓</button>
|
|
||||||
<button class="cancel-btn">✗</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="editable-field" data-field="type">
|
|
||||||
<label><strong>Type:</strong></label>
|
|
||||||
<span class="editable-value" data-current="${window.ticketData.type}">${window.ticketData.type}</span>
|
|
||||||
<div class="edit-dropdown" style="display: none;">
|
|
||||||
<select class="field-select">
|
|
||||||
<option value="Install" ${window.ticketData.type === 'Install' ? 'selected' : ''}>Install</option>
|
|
||||||
<option value="Maintenance" ${window.ticketData.type === 'Maintenance' ? 'selected' : ''}>Maintenance</option>
|
|
||||||
<option value="Problem" ${window.ticketData.type === 'Problem' ? 'selected' : ''}>Problem</option>
|
|
||||||
<option value="Request" ${window.ticketData.type === 'Request' ? 'selected' : ''}>Request</option>
|
|
||||||
<option value="Task" ${window.ticketData.type === 'Task' ? 'selected' : ''}>Task</option>
|
|
||||||
<option value="Upgrade" ${window.ticketData.type === 'Upgrade' ? 'selected' : ''}>Upgrade</option>
|
|
||||||
</select>
|
|
||||||
<div class="edit-actions">
|
|
||||||
<button class="save-btn">✓</button>
|
|
||||||
<button class="cancel-btn">✗</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
console.log('Ticket hamburger menu HTML created');
|
|
||||||
|
|
||||||
// Add inline editing functionality
|
|
||||||
setupInlineEditing(hamburgerMenu);
|
|
||||||
} else {
|
|
||||||
console.log('Creating dashboard hamburger menu');
|
|
||||||
|
|
||||||
// Dashboard hamburger menu (your existing code)
|
|
||||||
hamburgerMenu.innerHTML = `
|
|
||||||
<div class="hamburger-icon">☰</div>
|
|
||||||
<div class="hamburger-content">
|
|
||||||
<div class="close-hamburger">☰</div>
|
|
||||||
|
|
||||||
<div class="ascii-subsection-header">Filters</div>
|
|
||||||
|
|
||||||
<div class="ascii-frame-inner">
|
|
||||||
<div class="dashboard-filters">
|
|
||||||
<div class="filter-section">
|
|
||||||
<h4>Categories</h4>
|
|
||||||
<div id="category-filters"></div>
|
|
||||||
</div>
|
|
||||||
<div class="filter-section">
|
|
||||||
<h4>Types</h4>
|
|
||||||
<div id="type-filters"></div>
|
|
||||||
</div>
|
|
||||||
<div class="filter-actions">
|
|
||||||
<button id="apply-filters">Apply Filters</button>
|
|
||||||
<button id="clear-filters">Clear Filters</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Get current URL parameters
|
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
|
||||||
const currentCategories = urlParams.get('category') ? urlParams.get('category').split(',') : [];
|
|
||||||
const currentTypes = urlParams.get('type') ? urlParams.get('type').split(',') : [];
|
|
||||||
|
|
||||||
// Get containers
|
|
||||||
const categoriesContainer = hamburgerMenu.querySelector('#category-filters');
|
|
||||||
const typesContainer = hamburgerMenu.querySelector('#type-filters');
|
|
||||||
|
|
||||||
// Get data from body attributes
|
|
||||||
const categories = JSON.parse(document.body.dataset.categories || '[]');
|
|
||||||
const types = JSON.parse(document.body.dataset.types || '[]');
|
|
||||||
|
|
||||||
// Create checkboxes for categories
|
|
||||||
categories.forEach(category => {
|
|
||||||
const label = document.createElement('label');
|
|
||||||
label.style.display = 'block';
|
|
||||||
label.style.marginBottom = '5px';
|
|
||||||
|
|
||||||
const checkbox = document.createElement('input');
|
|
||||||
checkbox.type = 'checkbox';
|
|
||||||
checkbox.value = category;
|
|
||||||
checkbox.name = 'category';
|
|
||||||
|
|
||||||
const isChecked = currentCategories.includes(category);
|
|
||||||
|
|
||||||
label.appendChild(checkbox);
|
|
||||||
label.appendChild(document.createTextNode(' ' + category));
|
|
||||||
categoriesContainer.appendChild(label);
|
|
||||||
|
|
||||||
checkbox.checked = isChecked;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create checkboxes for types
|
|
||||||
types.forEach(type => {
|
|
||||||
const label = document.createElement('label');
|
|
||||||
label.style.display = 'block';
|
|
||||||
label.style.marginBottom = '5px';
|
|
||||||
|
|
||||||
const checkbox = document.createElement('input');
|
|
||||||
checkbox.type = 'checkbox';
|
|
||||||
checkbox.value = type;
|
|
||||||
checkbox.name = 'type';
|
|
||||||
|
|
||||||
const isChecked = currentTypes.includes(type);
|
|
||||||
|
|
||||||
label.appendChild(checkbox);
|
|
||||||
label.appendChild(document.createTextNode(' ' + type));
|
|
||||||
typesContainer.appendChild(label);
|
|
||||||
|
|
||||||
checkbox.checked = isChecked;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Apply filters event
|
|
||||||
const applyFiltersBtn = hamburgerMenu.querySelector('#apply-filters');
|
|
||||||
applyFiltersBtn.addEventListener('click', () => {
|
|
||||||
const selectedCategories = Array.from(
|
|
||||||
categoriesContainer.querySelectorAll('input:checked')
|
|
||||||
).map(cb => cb.value);
|
|
||||||
|
|
||||||
const selectedTypes = Array.from(
|
|
||||||
typesContainer.querySelectorAll('input:checked')
|
|
||||||
).map(cb => cb.value);
|
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
|
|
||||||
if (selectedCategories.length > 0) {
|
|
||||||
params.set('category', selectedCategories.join(','));
|
|
||||||
} else {
|
|
||||||
params.delete('category');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedTypes.length > 0) {
|
|
||||||
params.set('type', selectedTypes.join(','));
|
|
||||||
} else {
|
|
||||||
params.delete('type');
|
|
||||||
}
|
|
||||||
|
|
||||||
params.set('page', '1');
|
|
||||||
window.location.search = params.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clear filters event
|
|
||||||
const clearFiltersBtn = hamburgerMenu.querySelector('#clear-filters');
|
|
||||||
clearFiltersBtn.addEventListener('click', () => {
|
|
||||||
const params = new URLSearchParams(window.location.search);
|
|
||||||
params.delete('category');
|
|
||||||
params.delete('type');
|
|
||||||
params.set('page', '1');
|
|
||||||
window.location.search = params.toString();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Adding hamburger menu to body');
|
|
||||||
|
|
||||||
// Add to body
|
|
||||||
document.body.appendChild(hamburgerMenu);
|
|
||||||
|
|
||||||
console.log('Hamburger menu added, setting up event listeners');
|
|
||||||
|
|
||||||
// Toggle hamburger menu
|
|
||||||
const hamburgerIcon = hamburgerMenu.querySelector('.hamburger-icon');
|
|
||||||
const hamburgerContent = hamburgerMenu.querySelector('.hamburger-content');
|
|
||||||
|
|
||||||
if (hamburgerIcon && hamburgerContent) {
|
|
||||||
hamburgerIcon.addEventListener('click', () => {
|
|
||||||
console.log('Hamburger icon clicked');
|
|
||||||
hamburgerContent.classList.toggle('open');
|
|
||||||
document.body.classList.toggle('menu-open');
|
|
||||||
});
|
|
||||||
|
|
||||||
// Close hamburger menu
|
|
||||||
const closeButton = hamburgerMenu.querySelector('.close-hamburger');
|
|
||||||
if (closeButton) {
|
|
||||||
closeButton.addEventListener('click', () => {
|
|
||||||
console.log('Close button clicked');
|
|
||||||
hamburgerContent.classList.remove('open');
|
|
||||||
document.body.classList.remove('menu-open');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Hamburger menu created successfully');
|
|
||||||
} else {
|
|
||||||
console.error('Failed to find hamburger icon or content');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupInlineEditing(hamburgerMenu) {
|
|
||||||
const editableFields = hamburgerMenu.querySelectorAll('.editable-field');
|
|
||||||
|
|
||||||
editableFields.forEach(field => {
|
|
||||||
const valueSpan = field.querySelector('.editable-value');
|
|
||||||
const dropdown = field.querySelector('.edit-dropdown');
|
|
||||||
const select = field.querySelector('.field-select');
|
|
||||||
const saveBtn = field.querySelector('.save-btn');
|
|
||||||
const cancelBtn = field.querySelector('.cancel-btn');
|
|
||||||
const fieldName = field.dataset.field;
|
|
||||||
|
|
||||||
// Make value span clickable
|
|
||||||
valueSpan.style.cursor = 'pointer';
|
|
||||||
valueSpan.style.padding = '4px 8px';
|
|
||||||
valueSpan.style.borderRadius = '4px';
|
|
||||||
valueSpan.style.transition = 'background-color 0.2s';
|
|
||||||
|
|
||||||
// Hover effect
|
|
||||||
valueSpan.addEventListener('mouseenter', () => {
|
|
||||||
valueSpan.style.backgroundColor = 'var(--hover-bg, #f0f0f0)';
|
|
||||||
});
|
|
||||||
|
|
||||||
valueSpan.addEventListener('mouseleave', () => {
|
|
||||||
if (dropdown.style.display === 'none') {
|
|
||||||
valueSpan.style.backgroundColor = 'transparent';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Click to edit
|
|
||||||
valueSpan.addEventListener('click', () => {
|
|
||||||
dropdown.style.display = 'block';
|
|
||||||
valueSpan.style.backgroundColor = 'var(--hover-bg, #f0f0f0)';
|
|
||||||
select.focus();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Save changes
|
|
||||||
saveBtn.addEventListener('click', () => {
|
|
||||||
const newValue = select.value;
|
|
||||||
const oldValue = valueSpan.dataset.current;
|
|
||||||
|
|
||||||
if (newValue !== oldValue) {
|
|
||||||
saveFieldChange(fieldName, newValue, valueSpan, dropdown);
|
|
||||||
} else {
|
|
||||||
cancelEdit(valueSpan, dropdown);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Cancel changes
|
|
||||||
cancelBtn.addEventListener('click', () => {
|
|
||||||
select.value = valueSpan.dataset.current;
|
|
||||||
cancelEdit(valueSpan, dropdown);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Cancel on escape key
|
|
||||||
select.addEventListener('keydown', (e) => {
|
|
||||||
if (e.key === 'Escape') {
|
|
||||||
select.value = valueSpan.dataset.current;
|
|
||||||
cancelEdit(valueSpan, dropdown);
|
|
||||||
} else if (e.key === 'Enter') {
|
|
||||||
saveBtn.click();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Cancel when clicking outside
|
|
||||||
document.addEventListener('click', (e) => {
|
|
||||||
if (!field.contains(e.target) && dropdown.style.display === 'block') {
|
|
||||||
select.value = valueSpan.dataset.current;
|
|
||||||
cancelEdit(valueSpan, dropdown);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveFieldChange(fieldName, newValue, valueSpan, dropdown) {
|
|
||||||
if (!window.ticketData) {
|
|
||||||
console.error('No ticket data available');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
ticket_id: parseInt(window.ticketData.id),
|
|
||||||
[fieldName]: fieldName === 'priority' ? parseInt(newValue) : newValue
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('Saving field change:', data);
|
|
||||||
|
|
||||||
// Show loading state
|
|
||||||
valueSpan.style.opacity = '0.6';
|
|
||||||
|
|
||||||
fetch('/api/update_ticket.php', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data)
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
return response.text().then(text => {
|
|
||||||
try {
|
|
||||||
return JSON.parse(text);
|
|
||||||
} catch (e) {
|
|
||||||
throw new Error('Invalid JSON response: ' + text);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.then(result => {
|
|
||||||
console.log('Update result:', result);
|
|
||||||
if (result.success) {
|
|
||||||
// Update the hamburger menu display
|
|
||||||
if (fieldName === 'priority') {
|
|
||||||
valueSpan.textContent = 'P' + newValue;
|
|
||||||
} else {
|
|
||||||
valueSpan.textContent = newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
valueSpan.dataset.current = newValue;
|
|
||||||
|
|
||||||
// Update window.ticketData
|
|
||||||
window.ticketData[fieldName] = fieldName === 'priority' ? parseInt(newValue) : newValue;
|
|
||||||
|
|
||||||
// Update main page elements
|
|
||||||
if (fieldName === 'status') {
|
|
||||||
const statusDisplay = document.getElementById('statusDisplay');
|
|
||||||
if (statusDisplay) {
|
|
||||||
// Remove all existing status classes
|
|
||||||
statusDisplay.className = statusDisplay.className.replace(/status-\S+/g, '').trim();
|
|
||||||
|
|
||||||
// Create the correct CSS class name to match your CSS
|
|
||||||
// "Open" -> "status-Open"
|
|
||||||
// "In Progress" -> "status-In-Progress"
|
|
||||||
// "Closed" -> "status-Closed"
|
|
||||||
const cssClass = newValue.replace(/\s+/g, '-'); // "In Progress" -> "In-Progress"
|
|
||||||
const fullClassName = `status-${cssClass}`;
|
|
||||||
|
|
||||||
statusDisplay.className = fullClassName;
|
|
||||||
statusDisplay.textContent = newValue;
|
|
||||||
|
|
||||||
console.log('Updated status display class to:', fullClassName);
|
|
||||||
console.log('Status display element:', statusDisplay);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fieldName === 'priority') {
|
|
||||||
const priorityDisplay = document.querySelector('.priority-indicator');
|
|
||||||
if (priorityDisplay) {
|
|
||||||
// Remove all priority classes first
|
|
||||||
priorityDisplay.className = priorityDisplay.className.replace(/priority-\d+/g, '');
|
|
||||||
priorityDisplay.className = `priority-indicator priority-${newValue}`;
|
|
||||||
priorityDisplay.textContent = 'P' + newValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the ticket container's data-priority attribute for styling
|
|
||||||
const ticketContainer = document.querySelector('.ticket-container');
|
|
||||||
if (ticketContainer) {
|
|
||||||
ticketContainer.setAttribute('data-priority', newValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Field updated successfully');
|
|
||||||
cancelEdit(valueSpan, dropdown);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
console.error('Error updating field:', result.error || 'Unknown error');
|
|
||||||
alert('Error updating ' + fieldName + ': ' + (result.error || 'Unknown error'));
|
|
||||||
cancelEdit(valueSpan, dropdown);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error updating field:', error);
|
|
||||||
alert('Error updating ' + fieldName + ': ' + error.message);
|
|
||||||
cancelEdit(valueSpan, dropdown);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
valueSpan.style.opacity = '1';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancelEdit(valueSpan, dropdown) {
|
|
||||||
dropdown.style.display = 'none';
|
|
||||||
valueSpan.style.backgroundColor = 'transparent';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Ticket page functions (if needed)
|
// Ticket page functions (if needed)
|
||||||
function saveTicket() {
|
function saveTicket() {
|
||||||
@@ -921,9 +525,10 @@ function toggleSelectAll() {
|
|||||||
function updateSelectionCount() {
|
function updateSelectionCount() {
|
||||||
const checkboxes = document.querySelectorAll('.ticket-checkbox:checked');
|
const checkboxes = document.querySelectorAll('.ticket-checkbox:checked');
|
||||||
const count = checkboxes.length;
|
const count = checkboxes.length;
|
||||||
const toolbar = document.querySelector('.bulk-actions-toolbar');
|
const toolbar = document.querySelector('.bulk-actions-inline');
|
||||||
const countDisplay = document.getElementById('selected-count');
|
const countDisplay = document.getElementById('selected-count');
|
||||||
|
|
||||||
|
if (toolbar && countDisplay) {
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
toolbar.style.display = 'flex';
|
toolbar.style.display = 'flex';
|
||||||
countDisplay.textContent = count;
|
countDisplay.textContent = count;
|
||||||
@@ -931,6 +536,7 @@ function updateSelectionCount() {
|
|||||||
toolbar.style.display = 'none';
|
toolbar.style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getSelectedTicketIds() {
|
function getSelectedTicketIds() {
|
||||||
const checkboxes = document.querySelectorAll('.ticket-checkbox:checked');
|
const checkboxes = document.querySelectorAll('.ticket-checkbox:checked');
|
||||||
|
|||||||
@@ -28,46 +28,105 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ASCII Banner Container -->
|
<!-- Collapsible ASCII Banner -->
|
||||||
<div id="ascii-banner-container" style="margin: 2rem auto; text-align: center; max-width: 1200px;"></div>
|
<div class="ascii-banner-wrapper collapsed">
|
||||||
|
<button class="banner-toggle" onclick="toggleBanner()">
|
||||||
|
<span class="toggle-icon">▼</span> ASCII Banner
|
||||||
|
</button>
|
||||||
|
<div id="ascii-banner-container" class="banner-content"></div>
|
||||||
|
</div>
|
||||||
<script>
|
<script>
|
||||||
// Render ASCII banner on page load with typewriter effect
|
function toggleBanner() {
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
const wrapper = document.querySelector('.ascii-banner-wrapper');
|
||||||
renderResponsiveBanner('#ascii-banner-container', 3);
|
const icon = document.querySelector('.toggle-icon');
|
||||||
});
|
wrapper.classList.toggle('collapsed');
|
||||||
|
icon.textContent = wrapper.classList.contains('collapsed') ? '▼' : '▲';
|
||||||
|
|
||||||
|
// Render banner on first expand (no animation for instant display)
|
||||||
|
if (!wrapper.classList.contains('collapsed') && !wrapper.dataset.rendered) {
|
||||||
|
renderResponsiveBanner('#ascii-banner-container', 0); // Speed 0 = no animation
|
||||||
|
wrapper.dataset.rendered = 'true';
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- OUTER FRAME: Dashboard Container -->
|
<!-- Dashboard Layout with Sidebar -->
|
||||||
<div class="ascii-frame-outer">
|
<div class="dashboard-layout">
|
||||||
<span class="bottom-left-corner">╚</span>
|
<!-- Left Sidebar with Filters -->
|
||||||
<span class="bottom-right-corner">╝</span>
|
<aside class="dashboard-sidebar">
|
||||||
|
|
||||||
<!-- SECTION 1: Dashboard Header & Actions -->
|
|
||||||
<div class="ascii-section-header">Dashboard Control Center</div>
|
|
||||||
<div class="ascii-content">
|
|
||||||
<div class="ascii-frame-inner">
|
<div class="ascii-frame-inner">
|
||||||
<div class="dashboard-header">
|
<div class="ascii-subsection-header">Filters</div>
|
||||||
<h1>Ticket Dashboard</h1>
|
|
||||||
<button onclick="window.location.href='<?php echo $GLOBALS['config']['BASE_URL']; ?>/ticket/create'" class="btn create-ticket">New Ticket</button>
|
<!-- Status Filter -->
|
||||||
</div>
|
<div class="filter-group">
|
||||||
</div>
|
<h4>Status</h4>
|
||||||
|
<?php
|
||||||
|
$currentStatus = isset($_GET['status']) ? explode(',', $_GET['status']) : ['Open', 'In Progress'];
|
||||||
|
$allStatuses = ['Open', 'In Progress', 'Closed'];
|
||||||
|
foreach ($allStatuses as $status):
|
||||||
|
?>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox"
|
||||||
|
name="status"
|
||||||
|
value="<?php echo $status; ?>"
|
||||||
|
<?php echo in_array($status, $currentStatus) ? 'checked' : ''; ?>>
|
||||||
|
<?php echo $status; ?>
|
||||||
|
</label>
|
||||||
|
<?php endforeach; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- DIVIDER -->
|
<!-- Category Filter -->
|
||||||
<div class="ascii-divider"></div>
|
<div class="filter-group">
|
||||||
|
<h4>Category</h4>
|
||||||
|
<?php
|
||||||
|
$currentCategories = isset($_GET['category']) ? explode(',', $_GET['category']) : [];
|
||||||
|
foreach ($categories as $cat):
|
||||||
|
?>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox"
|
||||||
|
name="category"
|
||||||
|
value="<?php echo $cat; ?>"
|
||||||
|
<?php echo in_array($cat, $currentCategories) ? 'checked' : ''; ?>>
|
||||||
|
<?php echo htmlspecialchars($cat); ?>
|
||||||
|
</label>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- SECTION 2: Search & Filter -->
|
<!-- Type Filter -->
|
||||||
<div class="ascii-section-header">Search & Filter</div>
|
<div class="filter-group">
|
||||||
<div class="ascii-content">
|
<h4>Type</h4>
|
||||||
<div class="ascii-frame-inner">
|
<?php
|
||||||
<form method="GET" action="" class="search-form">
|
$currentTypes = isset($_GET['type']) ? explode(',', $_GET['type']) : [];
|
||||||
|
foreach ($types as $type):
|
||||||
|
?>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox"
|
||||||
|
name="type"
|
||||||
|
value="<?php echo $type; ?>"
|
||||||
|
<?php echo in_array($type, $currentTypes) ? 'checked' : ''; ?>>
|
||||||
|
<?php echo htmlspecialchars($type); ?>
|
||||||
|
</label>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="apply-filters-btn" class="btn">Apply Filters</button>
|
||||||
|
<button id="clear-filters-btn" class="btn btn-secondary">Clear All</button>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- Main Content Area -->
|
||||||
|
<main class="dashboard-main">
|
||||||
|
|
||||||
|
<!-- CONDENSED TOOLBAR: Combined Header, Search, Actions, Pagination -->
|
||||||
|
<div class="dashboard-toolbar">
|
||||||
|
<!-- Left: Title + Search -->
|
||||||
|
<div class="toolbar-left">
|
||||||
|
<h1 class="dashboard-title">🎫 Tickets</h1>
|
||||||
|
<form method="GET" action="" class="toolbar-search">
|
||||||
<!-- Preserve existing parameters -->
|
<!-- Preserve existing parameters -->
|
||||||
<?php if (isset($_GET['status'])): ?>
|
<?php if (isset($_GET['status'])): ?>
|
||||||
<input type="hidden" name="status" value="<?php echo htmlspecialchars($_GET['status']); ?>">
|
<input type="hidden" name="status" value="<?php echo htmlspecialchars($_GET['status']); ?>">
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php if (isset($_GET['show_all'])): ?>
|
|
||||||
<input type="hidden" name="show_all" value="<?php echo htmlspecialchars($_GET['show_all']); ?>">
|
|
||||||
<?php endif; ?>
|
|
||||||
<?php if (isset($_GET['category'])): ?>
|
<?php if (isset($_GET['category'])): ?>
|
||||||
<input type="hidden" name="category" value="<?php echo htmlspecialchars($_GET['category']); ?>">
|
<input type="hidden" name="category" value="<?php echo htmlspecialchars($_GET['category']); ?>">
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
@@ -83,35 +142,24 @@
|
|||||||
|
|
||||||
<input type="text"
|
<input type="text"
|
||||||
name="search"
|
name="search"
|
||||||
placeholder="Search tickets..."
|
placeholder="🔍 Search tickets..."
|
||||||
class="search-box"
|
class="search-box"
|
||||||
value="<?php echo isset($_GET['search']) ? htmlspecialchars($_GET['search']) : ''; ?>">
|
value="<?php echo isset($_GET['search']) ? htmlspecialchars($_GET['search']) : ''; ?>">
|
||||||
<button type="submit" class="search-btn">Search</button>
|
<button type="submit" class="btn search-btn">Search</button>
|
||||||
<?php if (isset($_GET['search']) && !empty($_GET['search'])): ?>
|
<?php if (isset($_GET['search']) && !empty($_GET['search'])): ?>
|
||||||
<a href="?" class="clear-search-btn">Clear</a>
|
<a href="?" class="clear-search-btn">✗</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</form>
|
</form>
|
||||||
<?php if (isset($_GET['search']) && !empty($_GET['search'])): ?>
|
|
||||||
<div class="search-results-info">
|
|
||||||
Showing results for: "<strong><?php echo htmlspecialchars($_GET['search']); ?></strong>"
|
|
||||||
(<?php echo $totalTickets; ?> ticket<?php echo $totalTickets != 1 ? 's' : ''; ?> found)
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- DIVIDER -->
|
<!-- Center: Actions + Count -->
|
||||||
<div class="ascii-divider"></div>
|
<div class="toolbar-center">
|
||||||
|
<button onclick="window.location.href='<?php echo $GLOBALS['config']['BASE_URL']; ?>/ticket/create'" class="btn create-ticket">+ New Ticket</button>
|
||||||
<!-- SECTION 3: Table Controls -->
|
<span class="ticket-count">Total: <?php echo $totalTickets; ?></span>
|
||||||
<div class="ascii-section-header">Table Controls</div>
|
|
||||||
<div class="ascii-content">
|
|
||||||
<div class="ascii-frame-inner">
|
|
||||||
<div class="table-controls">
|
|
||||||
<div class="ticket-count">
|
|
||||||
Total Tickets: <?php echo $totalTickets; ?>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="table-actions">
|
|
||||||
|
<!-- Right: Pagination -->
|
||||||
|
<div class="toolbar-right">
|
||||||
<div class="pagination">
|
<div class="pagination">
|
||||||
<?php
|
<?php
|
||||||
$currentParams = $_GET;
|
$currentParams = $_GET;
|
||||||
@@ -139,47 +187,36 @@
|
|||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-icon">
|
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<circle cx="12" cy="12" r="3"></circle>
|
|
||||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- DIVIDER -->
|
|
||||||
<div class="ascii-divider"></div>
|
|
||||||
|
|
||||||
<!-- SECTION 4: Bulk Actions (if admin) -->
|
|
||||||
<?php if ($GLOBALS['currentUser']['is_admin'] ?? false): ?>
|
|
||||||
<div class="ascii-section-header">Bulk Operations</div>
|
|
||||||
<div class="ascii-content">
|
|
||||||
<div class="ascii-frame-inner">
|
|
||||||
<div class="bulk-actions-toolbar" style="display: none;">
|
|
||||||
<div class="bulk-actions-info">
|
|
||||||
<span id="selected-count">0</span> tickets selected
|
|
||||||
</div>
|
|
||||||
<div class="bulk-actions-buttons">
|
|
||||||
<button onclick="bulkClose()" class="btn btn-bulk">Close Selected</button>
|
|
||||||
<button onclick="showBulkAssignModal()" class="btn btn-bulk">Assign Selected</button>
|
|
||||||
<button onclick="showBulkPriorityModal()" class="btn btn-bulk">Change Priority</button>
|
|
||||||
<button onclick="clearSelection()" class="btn btn-secondary">Clear Selection</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- DIVIDER -->
|
<?php if (isset($_GET['search']) && !empty($_GET['search'])): ?>
|
||||||
<div class="ascii-divider"></div>
|
<div class="search-results-info">
|
||||||
|
Showing results for: "<strong><?php echo htmlspecialchars($_GET['search']); ?></strong>"
|
||||||
|
(<?php echo $totalTickets; ?> ticket<?php echo $totalTickets != 1 ? 's' : ''; ?> found)
|
||||||
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- SECTION 5: Ticket Table -->
|
<!-- TICKET TABLE WITH INLINE BULK ACTIONS -->
|
||||||
|
<div class="ascii-frame-outer">
|
||||||
|
<span class="bottom-left-corner">╚</span>
|
||||||
|
<span class="bottom-right-corner">╝</span>
|
||||||
|
|
||||||
<div class="ascii-section-header">Ticket List</div>
|
<div class="ascii-section-header">Ticket List</div>
|
||||||
<div class="ascii-content">
|
<div class="ascii-content">
|
||||||
<div class="ascii-frame-inner">
|
<div class="ascii-frame-inner">
|
||||||
|
<!-- Inline Bulk Actions (appears above table when items selected) -->
|
||||||
|
<?php if ($GLOBALS['currentUser']['is_admin'] ?? false): ?>
|
||||||
|
<div class="bulk-actions-inline" style="display: none;">
|
||||||
|
<span id="selected-count">0</span> tickets selected
|
||||||
|
<button onclick="bulkClose()" class="btn btn-bulk">Close</button>
|
||||||
|
<button onclick="showBulkAssignModal()" class="btn btn-bulk">Assign</button>
|
||||||
|
<button onclick="showBulkPriorityModal()" class="btn btn-bulk">Priority</button>
|
||||||
|
<button onclick="clearSelection()" class="btn btn-secondary">Clear</button>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Table -->
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
Reference in New Issue
Block a user