Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cfdc9e0f37 | |||
| 55c6fc81db | |||
| fdc6d3d463 | |||
| 72d5061867 | |||
| 1d721eecb4 |
+101
-130
@@ -79,15 +79,6 @@
|
|||||||
--accent-purple: #BF5FFF;
|
--accent-purple: #BF5FFF;
|
||||||
--accent-purple-dim: rgba(191,95,255,0.10);
|
--accent-purple-dim: rgba(191,95,255,0.10);
|
||||||
|
|
||||||
/* ── App semantic aliases (used in ticket.css, dashboard.css) ── */
|
|
||||||
--lt-danger: var(--accent-red);
|
|
||||||
--lt-amber: var(--accent-amber);
|
|
||||||
--lt-cyan: var(--accent-cyan);
|
|
||||||
--lt-success: var(--accent-green);
|
|
||||||
--lt-text-primary: var(--accent-green);
|
|
||||||
--lt-border: var(--border-color);
|
|
||||||
--lt-surface: var(--bg-card);
|
|
||||||
|
|
||||||
/* Legacy aliases — keeps all existing component HTML working */
|
/* Legacy aliases — keeps all existing component HTML working */
|
||||||
--terminal-green: var(--accent-green);
|
--terminal-green: var(--accent-green);
|
||||||
--terminal-green-dim: var(--accent-green-dim);
|
--terminal-green-dim: var(--accent-green-dim);
|
||||||
@@ -155,6 +146,7 @@
|
|||||||
/* --- Typography --- */
|
/* --- Typography --- */
|
||||||
--font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Courier New', monospace;
|
--font-mono: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', 'Courier New', monospace;
|
||||||
--font-display: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace;
|
--font-display: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace;
|
||||||
|
--font-crt: 'VT323', 'Courier New', monospace;
|
||||||
|
|
||||||
/* --- Spacing --- */
|
/* --- Spacing --- */
|
||||||
--space-xs: 0.25rem;
|
--space-xs: 0.25rem;
|
||||||
@@ -225,8 +217,6 @@ body {
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@@ -360,18 +350,6 @@ hr {
|
|||||||
|
|
||||||
.lt-main {
|
.lt-main {
|
||||||
padding-top: calc(var(--header-height) + var(--space-lg));
|
padding-top: calc(var(--header-height) + var(--space-lg));
|
||||||
flex: 1;
|
|
||||||
/* When body is a flex column, margin:0 auto from .lt-container would prevent
|
|
||||||
stretch. Force full width so max-width+auto-margin centering still works. */
|
|
||||||
width: 100%;
|
|
||||||
min-width: 0; /* prevent flex overflow on very small viewports */
|
|
||||||
}
|
|
||||||
/* When both lt-main and lt-container are on the same element, the lt-container
|
|
||||||
shorthand `padding` overrides the lt-main `padding-top` in responsive breakpoints
|
|
||||||
(same cascade specificity, later rule wins). The combined selector has higher
|
|
||||||
specificity (0,2,0 vs 0,1,0) and always wins regardless of source order. */
|
|
||||||
.lt-main.lt-container {
|
|
||||||
padding-top: calc(var(--header-height) + var(--space-lg));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.lt-layout {
|
.lt-layout {
|
||||||
@@ -392,17 +370,6 @@ hr {
|
|||||||
.lt-flex-col { display: flex; flex-direction: column; }
|
.lt-flex-col { display: flex; flex-direction: column; }
|
||||||
.lt-flex-wrap { flex-wrap: wrap; }
|
.lt-flex-wrap { flex-wrap: wrap; }
|
||||||
|
|
||||||
/* Flex gap modifiers (used with lt-flex) */
|
|
||||||
.lt-flex-gap-xs { gap: var(--space-xs); }
|
|
||||||
.lt-flex-gap-sm { gap: var(--space-sm); }
|
|
||||||
.lt-flex-gap-md { gap: var(--space-md); }
|
|
||||||
.lt-flex-gap-lg { gap: var(--space-lg); }
|
|
||||||
|
|
||||||
/* Flex align modifiers */
|
|
||||||
.lt-flex-align-start { align-items: flex-start; }
|
|
||||||
.lt-flex-align-center { align-items: center; }
|
|
||||||
.lt-flex-align-end { align-items: flex-end; }
|
|
||||||
|
|
||||||
.lt-gap-sm { gap: var(--space-sm); }
|
.lt-gap-sm { gap: var(--space-sm); }
|
||||||
.lt-gap-md { gap: var(--space-md); }
|
.lt-gap-md { gap: var(--space-md); }
|
||||||
.lt-gap-lg { gap: var(--space-lg); }
|
.lt-gap-lg { gap: var(--space-lg); }
|
||||||
@@ -941,6 +908,19 @@ hr {
|
|||||||
border-color: var(--border-dim);
|
border-color: var(--border-dim);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Display-only fields — readable, non-editable, not "broken" */
|
||||||
|
.lt-display-field,
|
||||||
|
.lt-input.lt-display-field,
|
||||||
|
.lt-select.lt-display-field,
|
||||||
|
.lt-textarea.lt-display-field {
|
||||||
|
opacity: 1;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
cursor: default;
|
||||||
|
pointer-events: none;
|
||||||
|
background: transparent;
|
||||||
|
border-color: var(--border-dim);
|
||||||
|
}
|
||||||
|
|
||||||
.lt-input::placeholder,
|
.lt-input::placeholder,
|
||||||
.lt-textarea::placeholder { color: var(--text-dim); }
|
.lt-textarea::placeholder { color: var(--text-dim); }
|
||||||
|
|
||||||
@@ -975,26 +955,11 @@ select option:checked {
|
|||||||
clip-path: none;
|
clip-path: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compact size variants for inline filter bars and tight layouts */
|
|
||||||
.lt-select-sm {
|
|
||||||
font-size: 0.7rem;
|
|
||||||
padding: 0.25rem 1.6rem 0.25rem 0.5rem;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
.lt-input-sm {
|
|
||||||
font-size: 0.7rem;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lt-form-hint {
|
.lt-form-hint {
|
||||||
font-size: 0.63rem;
|
font-size: 0.63rem;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
}
|
}
|
||||||
.lt-form-hint--warn { color: var(--accent-amber); }
|
|
||||||
|
|
||||||
.lt-font-mono { font-family: var(--font-mono); }
|
|
||||||
|
|
||||||
/* Search */
|
/* Search */
|
||||||
.lt-search { position: relative; }
|
.lt-search { position: relative; }
|
||||||
@@ -1239,7 +1204,6 @@ select option:checked {
|
|||||||
.lt-badge-green { color: var(--accent-green); }
|
.lt-badge-green { color: var(--accent-green); }
|
||||||
.lt-badge-amber { color: var(--accent-amber); }
|
.lt-badge-amber { color: var(--accent-amber); }
|
||||||
.lt-badge-red { color: var(--accent-red); }
|
.lt-badge-red { color: var(--accent-red); }
|
||||||
.lt-badge-sm { font-size: 0.52rem; padding: 0.05rem 0.3rem; letter-spacing: 0.08em; }
|
|
||||||
|
|
||||||
/* Status + priority badge variants (dark-mode base) */
|
/* Status + priority badge variants (dark-mode base) */
|
||||||
.lt-badge-open { color: var(--accent-green); background: rgba(0,255,136,0.08); border-color: rgba(0,255,136,0.35); text-shadow: var(--glow-green); }
|
.lt-badge-open { color: var(--accent-green); background: rgba(0,255,136,0.08); border-color: rgba(0,255,136,0.35); text-shadow: var(--glow-green); }
|
||||||
@@ -1610,14 +1574,12 @@ select option:checked {
|
|||||||
}
|
}
|
||||||
.lt-msg::before { flex-shrink: 0; font-weight: 700; }
|
.lt-msg::before { flex-shrink: 0; font-weight: 700; }
|
||||||
|
|
||||||
.lt-msg-error,
|
.lt-msg-error { color: var(--accent-red); background: var(--accent-red-dim); border-left-color: var(--accent-red); }
|
||||||
.lt-msg-danger { color: var(--accent-red); background: var(--accent-red-dim); border-left-color: var(--accent-red); }
|
|
||||||
.lt-msg-success { color: var(--accent-green); background: var(--accent-green-dim); border-left-color: var(--accent-green); }
|
.lt-msg-success { color: var(--accent-green); background: var(--accent-green-dim); border-left-color: var(--accent-green); }
|
||||||
.lt-msg-warning { color: var(--accent-amber); background: var(--accent-amber-dim); border-left-color: var(--accent-amber); }
|
.lt-msg-warning { color: var(--accent-amber); background: var(--accent-amber-dim); border-left-color: var(--accent-amber); }
|
||||||
.lt-msg-info { color: var(--accent-cyan); background: var(--accent-cyan-dim); border-left-color: var(--accent-cyan); }
|
.lt-msg-info { color: var(--accent-cyan); background: var(--accent-cyan-dim); border-left-color: var(--accent-cyan); }
|
||||||
|
|
||||||
.lt-msg-error::before,
|
.lt-msg-error::before { content: '✗'; }
|
||||||
.lt-msg-danger::before { content: '✗'; }
|
|
||||||
.lt-msg-success::before { content: '✓'; }
|
.lt-msg-success::before { content: '✓'; }
|
||||||
.lt-msg-warning::before { content: '!'; }
|
.lt-msg-warning::before { content: '!'; }
|
||||||
.lt-msg-info::before { content: 'i'; }
|
.lt-msg-info::before { content: 'i'; }
|
||||||
@@ -2083,7 +2045,6 @@ select option:checked {
|
|||||||
.lt-main { padding-top: calc(50px + var(--space-md)); }
|
.lt-main { padding-top: calc(50px + var(--space-md)); }
|
||||||
|
|
||||||
.lt-container { padding: var(--space-md); }
|
.lt-container { padding: var(--space-md); }
|
||||||
.lt-main.lt-container { padding-top: calc(var(--header-height) + var(--space-md)); }
|
|
||||||
.lt-header { padding: 0 var(--space-md); }
|
.lt-header { padding: 0 var(--space-md); }
|
||||||
.lt-brand-subtitle { display: none; }
|
.lt-brand-subtitle { display: none; }
|
||||||
|
|
||||||
@@ -2184,7 +2145,6 @@ select option:checked {
|
|||||||
.lt-main { padding-top: calc(46px + var(--space-sm)); }
|
.lt-main { padding-top: calc(46px + var(--space-sm)); }
|
||||||
|
|
||||||
.lt-container { padding: var(--space-sm); }
|
.lt-container { padding: var(--space-sm); }
|
||||||
.lt-main.lt-container { padding-top: calc(var(--header-height) + var(--space-sm)); }
|
|
||||||
.lt-stats-grid { grid-template-columns: 1fr 1fr; gap: var(--space-xs); }
|
.lt-stats-grid { grid-template-columns: 1fr 1fr; gap: var(--space-xs); }
|
||||||
.lt-stat-card { padding: var(--space-xs) var(--space-sm); }
|
.lt-stat-card { padding: var(--space-xs) var(--space-sm); }
|
||||||
.lt-stat-value { font-size: 1.4rem; }
|
.lt-stat-value { font-size: 1.4rem; }
|
||||||
@@ -2281,7 +2241,6 @@ select option:checked {
|
|||||||
.lt-stats-grid { grid-template-columns: repeat(6, 1fr); }
|
.lt-stats-grid { grid-template-columns: repeat(6, 1fr); }
|
||||||
.lt-grid-4 { grid-template-columns: repeat(4, 1fr); }
|
.lt-grid-4 { grid-template-columns: repeat(4, 1fr); }
|
||||||
.lt-container { padding: var(--space-xl) var(--space-2xl); }
|
.lt-container { padding: var(--space-xl) var(--space-2xl); }
|
||||||
.lt-main.lt-container { padding-top: calc(var(--header-height) + var(--space-xl)); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2349,12 +2308,6 @@ select option:checked {
|
|||||||
.lt-text-red { color: var(--accent-red); text-shadow: var(--glow-red); }
|
.lt-text-red { color: var(--accent-red); text-shadow: var(--glow-red); }
|
||||||
.lt-text-muted { color: var(--text-muted); }
|
.lt-text-muted { color: var(--text-muted); }
|
||||||
.lt-text-dim { color: var(--text-dim); }
|
.lt-text-dim { color: var(--text-dim); }
|
||||||
/* Semantic aliases */
|
|
||||||
.lt-text-danger { color: var(--accent-red); text-shadow: var(--glow-red); }
|
|
||||||
.lt-text-warning { color: var(--accent-amber); text-shadow: var(--glow-amber); }
|
|
||||||
.lt-text-success { color: var(--accent-green); text-shadow: var(--glow-green); }
|
|
||||||
.lt-text-info { color: var(--accent-cyan); text-shadow: var(--glow-cyan); }
|
|
||||||
.lt-text-primary { color: var(--accent-green); text-shadow: var(--glow-green); }
|
|
||||||
|
|
||||||
.lt-text-xs { font-size: 0.63rem; }
|
.lt-text-xs { font-size: 0.63rem; }
|
||||||
.lt-text-sm { font-size: 0.78rem; }
|
.lt-text-sm { font-size: 0.78rem; }
|
||||||
@@ -2393,9 +2346,6 @@ select option:checked {
|
|||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Global visibility utility — used by JS and all views */
|
|
||||||
.is-hidden { display: none !important; }
|
|
||||||
|
|
||||||
/* Cursor blink */
|
/* Cursor blink */
|
||||||
.lt-cursor::after {
|
.lt-cursor::after {
|
||||||
content: '█';
|
content: '█';
|
||||||
@@ -3229,11 +3179,6 @@ input[type="range"].lt-range::-moz-range-thumb {
|
|||||||
.lt-kv-val--green { color: var(--accent-green); }
|
.lt-kv-val--green { color: var(--accent-green); }
|
||||||
.lt-kv-val--red { color: var(--accent-red); }
|
.lt-kv-val--red { color: var(--accent-red); }
|
||||||
|
|
||||||
/* v1.2 aliases: lt-kv-row wraps label+value as a transparent grid wrapper */
|
|
||||||
.lt-kv-row { display: contents; }
|
|
||||||
.lt-kv-label { padding: var(--space-xs) var(--space-md) var(--space-xs) 0; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.05em; font-size: 0.7rem; white-space: nowrap; border-right: 1px solid var(--border-dim); }
|
|
||||||
.lt-kv-value { padding: var(--space-xs) 0 var(--space-xs) var(--space-md); color: var(--text-primary); }
|
|
||||||
|
|
||||||
|
|
||||||
/* ----------------------------------------------------------------
|
/* ----------------------------------------------------------------
|
||||||
43. HERO / BANNER SECTION
|
43. HERO / BANNER SECTION
|
||||||
@@ -4498,7 +4443,6 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.lt-timeline-actor { color: var(--accent-cyan); }
|
.lt-timeline-actor { color: var(--accent-cyan); }
|
||||||
.lt-timeline-action { color: var(--text-secondary); font-style: italic; }
|
|
||||||
.lt-timeline-time { margin-left: auto; white-space: nowrap; }
|
.lt-timeline-time { margin-left: auto; white-space: nowrap; }
|
||||||
.lt-timeline-body { font-size: 0.78rem; color: var(--text-secondary); line-height: 1.5; }
|
.lt-timeline-body { font-size: 0.78rem; color: var(--text-secondary); line-height: 1.5; }
|
||||||
.lt-timeline-body code { font-size: 0.72rem; color: var(--accent-green); }
|
.lt-timeline-body code { font-size: 0.72rem; color: var(--accent-green); }
|
||||||
@@ -4522,20 +4466,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
/* Photo overlay: img sits on top of initials text; hidden via onerror if no photo */
|
.lt-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||||||
.lt-avatar { position: relative; }
|
|
||||||
.lt-avatar-initials { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; pointer-events: none; }
|
|
||||||
.lt-avatar-img {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: inherit;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
/* Legacy: bare img inside lt-avatar (no .lt-avatar-img class) */
|
|
||||||
.lt-avatar > img:not(.lt-avatar-img) { width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
||||||
/* Sizes */
|
/* Sizes */
|
||||||
.lt-avatar--xs { width: 1.5rem; height: 1.5rem; font-size: 0.55rem; }
|
.lt-avatar--xs { width: 1.5rem; height: 1.5rem; font-size: 0.55rem; }
|
||||||
.lt-avatar--sm { width: 2rem; height: 2rem; font-size: 0.65rem; }
|
.lt-avatar--sm { width: 2rem; height: 2rem; font-size: 0.65rem; }
|
||||||
@@ -5560,7 +5491,7 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: var(--space-sm);
|
gap: var(--space-sm);
|
||||||
padding: var(--space-sm) var(--space-lg);
|
padding: var(--space-md) var(--space-lg);
|
||||||
border-top: 1px solid var(--border-dim);
|
border-top: 1px solid var(--border-dim);
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
@@ -5568,49 +5499,89 @@ body.lt-is-offline .lt-main { margin-top: 2rem; transition: margin-top 0.25s eas
|
|||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Keyboard hint bar */
|
|
||||||
.lt-footer-hints {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.75rem;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
.lt-footer-hint {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.25rem;
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-size: 0.68rem;
|
|
||||||
letter-spacing: 0.03em;
|
|
||||||
text-decoration: none;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
padding: 0;
|
|
||||||
font-family: inherit;
|
|
||||||
cursor: default;
|
|
||||||
transition: color 0.12s;
|
|
||||||
}
|
|
||||||
a.lt-footer-hint,
|
|
||||||
button.lt-footer-hint {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
a.lt-footer-hint:hover,
|
|
||||||
button.lt-footer-hint:hover {
|
|
||||||
color: var(--accent-green);
|
|
||||||
text-shadow: var(--glow-green);
|
|
||||||
}
|
|
||||||
.lt-footer-key {
|
|
||||||
color: var(--accent-green);
|
|
||||||
opacity: 0.75;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
.lt-footer-sep {
|
|
||||||
opacity: 0.25;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 479px) {
|
@media (max-width: 479px) {
|
||||||
.lt-footer { flex-direction: column; align-items: flex-start; gap: 0.25rem; }
|
.lt-footer { flex-direction: column; align-items: flex-start; gap: 0.25rem; }
|
||||||
.lt-footer-hints { gap: 0.5rem; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
BLINKING CURSOR
|
||||||
|
<h1 class="lt-cursor">SYSTEM STATUS</h1>
|
||||||
|
<span class="lt-cursor lt-cursor--cyan">SCANNING</span>
|
||||||
|
================================================================ */
|
||||||
|
.lt-cursor::after {
|
||||||
|
content: '▊';
|
||||||
|
animation: lt-blink 1s step-end infinite;
|
||||||
|
color: var(--accent-green);
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
.lt-cursor--cyan::after { color: var(--accent-cyan); }
|
||||||
|
.lt-cursor--orange::after { color: var(--accent-orange); }
|
||||||
|
.lt-cursor--red::after { color: var(--accent-red); }
|
||||||
|
@keyframes lt-blink {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
CRT SCANLINE OVERLAY
|
||||||
|
Add lt-scanlines to <body> or any container to enable.
|
||||||
|
Automatically suppressed in light theme.
|
||||||
|
================================================================ */
|
||||||
|
.lt-scanlines::after {
|
||||||
|
content: '';
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
0deg,
|
||||||
|
transparent,
|
||||||
|
transparent 2px,
|
||||||
|
rgba(0, 0, 0, 0.04) 2px,
|
||||||
|
rgba(0, 0, 0, 0.04) 4px
|
||||||
|
);
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 9998;
|
||||||
|
}
|
||||||
|
html[data-theme="light"] .lt-scanlines::after { display: none; }
|
||||||
|
|
||||||
|
/* ================================================================
|
||||||
|
RADAR SWEEP LOADING INDICATOR
|
||||||
|
<div class="lt-radar"></div>
|
||||||
|
Drop-in replacement for lt-spinner where a radar aesthetic fits.
|
||||||
|
================================================================ */
|
||||||
|
.lt-radar {
|
||||||
|
display: inline-block;
|
||||||
|
width: 48px; height: 48px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid var(--accent-cyan);
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.lt-radar::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: conic-gradient(
|
||||||
|
from 0deg,
|
||||||
|
transparent 70%,
|
||||||
|
rgba(0, 212, 255, 0.35) 100%
|
||||||
|
);
|
||||||
|
animation: lt-radar-sweep 2s linear infinite;
|
||||||
|
transform-origin: center;
|
||||||
|
}
|
||||||
|
.lt-radar::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 50% 0 0 50%;
|
||||||
|
width: 1px; height: 50%;
|
||||||
|
background: var(--accent-cyan);
|
||||||
|
transform-origin: top left;
|
||||||
|
animation: lt-radar-sweep 2s linear infinite;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
.lt-radar--sm { width: 28px; height: 28px; }
|
||||||
|
.lt-radar--lg { width: 72px; height: 72px; }
|
||||||
|
.lt-radar--green { border-color: var(--accent-green); }
|
||||||
|
.lt-radar--green::before { background: conic-gradient(from 0deg, transparent 70%, rgba(0,255,136,0.35) 100%); }
|
||||||
|
.lt-radar--green::after { background: var(--accent-green); }
|
||||||
|
@keyframes lt-radar-sweep { to { transform: rotate(360deg); } }
|
||||||
|
|||||||
@@ -269,3 +269,25 @@ kbd {
|
|||||||
.thread-depth-2 { margin-left: 1.5rem; }
|
.thread-depth-2 { margin-left: 1.5rem; }
|
||||||
.thread-depth-3 { margin-left: 2.25rem; }
|
.thread-depth-3 { margin-left: 2.25rem; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Description read view ───────────────────────────────────── */
|
||||||
|
/* Shown in read mode instead of a disabled (faded) textarea. */
|
||||||
|
/* Uses lt-markdown typography for full contrast on dark/OLED. */
|
||||||
|
.ticket-description-view {
|
||||||
|
min-height: 8rem;
|
||||||
|
padding: 0.5rem 0.25rem;
|
||||||
|
line-height: 1.75;
|
||||||
|
color: var(--text-primary);
|
||||||
|
/* pre-wrap preserves newlines and multiple spaces so ASCII art aligns correctly.
|
||||||
|
font-mono is inherited from body, so box-drawing characters line up. */
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
.ticket-description-view p {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
margin-bottom: 0.6rem;
|
||||||
|
}
|
||||||
|
.ticket-description-view p:last-child { margin-bottom: 0; }
|
||||||
|
|
||||||
|
/* Metadata selects use .lt-display-field (base.css) in read mode
|
||||||
|
instead of disabled — full opacity, non-interactive, no fading. */
|
||||||
|
|||||||
@@ -466,7 +466,6 @@
|
|||||||
let data;
|
let data;
|
||||||
try { data = await resp.json(); } catch (_) { data = { success: resp.ok }; }
|
try { data = await resp.json(); } catch (_) { data = { success: resp.ok }; }
|
||||||
if (!resp.ok) throw new Error(data.error || data.message || 'HTTP ' + resp.status);
|
if (!resp.ok) throw new Error(data.error || data.message || 'HTTP ' + resp.status);
|
||||||
if (data && data.csrf_token) global.CSRF_TOKEN = data.csrf_token;
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2703,7 +2702,6 @@
|
|||||||
let data;
|
let data;
|
||||||
try { data = await resp.json(); } catch (_) { data = { success: resp.ok }; }
|
try { data = await resp.json(); } catch (_) { data = { success: resp.ok }; }
|
||||||
if (!resp.ok) throw new Error(data.error || data.message || 'HTTP ' + resp.status);
|
if (!resp.ok) throw new Error(data.error || data.message || 'HTTP ' + resp.status);
|
||||||
if (data && data.csrf_token) global.CSRF_TOKEN = data.csrf_token;
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
api.get = url => _apiFetchAuth('GET', url);
|
api.get = url => _apiFetchAuth('GET', url);
|
||||||
|
|||||||
+53
-30
@@ -545,6 +545,8 @@ function performBulkCloseAction(ticketIds) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _bulkAssignUserId = null; // set by combobox onSelect
|
||||||
|
|
||||||
function showBulkAssignModal() {
|
function showBulkAssignModal() {
|
||||||
const ticketIds = getSelectedTicketIds();
|
const ticketIds = getSelectedTicketIds();
|
||||||
|
|
||||||
@@ -553,7 +555,8 @@ function showBulkAssignModal() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create modal HTML
|
_bulkAssignUserId = null;
|
||||||
|
|
||||||
const modalHtml = `
|
const modalHtml = `
|
||||||
<div class="lt-modal-overlay" id="bulkAssignModal" aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="bulkAssignModalTitle">
|
<div class="lt-modal-overlay" id="bulkAssignModal" aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="bulkAssignModalTitle">
|
||||||
<div class="lt-modal">
|
<div class="lt-modal">
|
||||||
@@ -562,10 +565,15 @@ function showBulkAssignModal() {
|
|||||||
<button class="lt-modal-close" data-modal-close aria-label="Close">✕</button>
|
<button class="lt-modal-close" data-modal-close aria-label="Close">✕</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="lt-modal-body">
|
<div class="lt-modal-body">
|
||||||
<label for="bulkAssignUser">Assign to:</label>
|
<label class="lt-label">Assign to:</label>
|
||||||
<select id="bulkAssignUser" class="lt-select">
|
<div class="lt-combobox" id="bulkAssignCombobox">
|
||||||
<option value="">Select User...</option>
|
<div class="lt-combobox-input-wrap">
|
||||||
</select>
|
<input type="text" class="lt-combobox-input" id="bulkAssignUserInput"
|
||||||
|
placeholder="Search users…" autocomplete="off" aria-label="Search users">
|
||||||
|
</div>
|
||||||
|
<ul class="lt-combobox-list" role="listbox" aria-hidden="true"></ul>
|
||||||
|
</div>
|
||||||
|
<span class="lt-field-hint lt-text-muted">Type to search — supports large user lists</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="lt-modal-footer">
|
<div class="lt-modal-footer">
|
||||||
<button data-action="perform-bulk-assign" class="lt-btn lt-btn-primary">ASSIGN</button>
|
<button data-action="perform-bulk-assign" class="lt-btn lt-btn-primary">ASSIGN</button>
|
||||||
@@ -578,20 +586,19 @@ function showBulkAssignModal() {
|
|||||||
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
||||||
lt.modal.open('bulkAssignModal');
|
lt.modal.open('bulkAssignModal');
|
||||||
|
|
||||||
// Fetch users for the dropdown
|
|
||||||
lt.api.get('/api/get_users.php')
|
lt.api.get('/api/get_users.php')
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success && data.users) {
|
if (data.success && data.users) {
|
||||||
const select = document.getElementById('bulkAssignUser');
|
const input = document.getElementById('bulkAssignUserInput');
|
||||||
if (select) {
|
if (!input) return;
|
||||||
data.users.forEach(user => {
|
const items = data.users.map(u => ({
|
||||||
const option = document.createElement('option');
|
value: String(u.user_id),
|
||||||
option.value = user.user_id;
|
label: u.display_name || u.username
|
||||||
option.textContent = user.display_name || user.username;
|
}));
|
||||||
select.appendChild(option);
|
lt.combobox.init(input, items, {
|
||||||
|
onSelect: function(item) { _bulkAssignUserId = item.value; }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch(() => lt.toast.error('Error loading users'));
|
.catch(() => lt.toast.error('Error loading users'));
|
||||||
}
|
}
|
||||||
@@ -603,11 +610,11 @@ function closeBulkAssignModal() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function performBulkAssign() {
|
function performBulkAssign() {
|
||||||
const userId = document.getElementById('bulkAssignUser').value;
|
const userId = _bulkAssignUserId;
|
||||||
const ticketIds = getSelectedTicketIds();
|
const ticketIds = getSelectedTicketIds();
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
lt.toast.warning('Please select a user', 2000);
|
lt.toast.warning('Please select a user from the list', 2000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -997,10 +1004,14 @@ function performQuickStatusChange(ticketId) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _quickAssignUserId = undefined; // undefined = no change; null = unassign; string = user_id
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Quick assign from dashboard
|
* Quick assign from dashboard
|
||||||
*/
|
*/
|
||||||
function quickAssign(ticketId) {
|
function quickAssign(ticketId) {
|
||||||
|
_quickAssignUserId = undefined;
|
||||||
|
|
||||||
const modalHtml = `
|
const modalHtml = `
|
||||||
<div class="lt-modal-overlay" id="quickAssignModal" aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="quickAssignModalTitle">
|
<div class="lt-modal-overlay" id="quickAssignModal" aria-hidden="true" role="dialog" aria-modal="true" aria-labelledby="quickAssignModalTitle">
|
||||||
<div class="lt-modal lt-modal-xs">
|
<div class="lt-modal lt-modal-xs">
|
||||||
@@ -1009,14 +1020,18 @@ function quickAssign(ticketId) {
|
|||||||
<button class="lt-modal-close" data-modal-close aria-label="Close">✕</button>
|
<button class="lt-modal-close" data-modal-close aria-label="Close">✕</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="lt-modal-body">
|
<div class="lt-modal-body">
|
||||||
<p class="lt-mb-xs">Ticket #${lt.escHtml(ticketId)}</p>
|
<p class="lt-mb-xs lt-text-muted lt-text-xs">Ticket #${lt.escHtml(String(ticketId))}</p>
|
||||||
<label for="quickAssignSelect">Assign to:</label>
|
<label class="lt-label">Assign to:</label>
|
||||||
<select id="quickAssignSelect" class="lt-select">
|
<div class="lt-combobox" id="quickAssignCombobox">
|
||||||
<option value="">Unassigned</option>
|
<div class="lt-combobox-input-wrap">
|
||||||
</select>
|
<input type="text" class="lt-combobox-input" id="quickAssignInput"
|
||||||
|
placeholder="Search users…" autocomplete="off" aria-label="Search users">
|
||||||
|
</div>
|
||||||
|
<ul class="lt-combobox-list" role="listbox" aria-hidden="true"></ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="lt-modal-footer">
|
<div class="lt-modal-footer">
|
||||||
<button data-action="perform-quick-assign" data-ticket-id="${ticketId}" class="lt-btn lt-btn-primary">ASSIGN</button>
|
<button data-action="perform-quick-assign" data-ticket-id="${lt.escHtml(String(ticketId))}" class="lt-btn lt-btn-primary">ASSIGN</button>
|
||||||
<button data-action="close-quick-assign-modal" class="lt-btn lt-btn-ghost">CANCEL</button>
|
<button data-action="close-quick-assign-modal" class="lt-btn lt-btn-ghost">CANCEL</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1026,16 +1041,20 @@ function quickAssign(ticketId) {
|
|||||||
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
document.body.insertAdjacentHTML('beforeend', modalHtml);
|
||||||
lt.modal.open('quickAssignModal');
|
lt.modal.open('quickAssignModal');
|
||||||
|
|
||||||
// Load users
|
|
||||||
lt.api.get('/api/get_users.php')
|
lt.api.get('/api/get_users.php')
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.success && data.users) {
|
if (data.success && data.users) {
|
||||||
const select = document.getElementById('quickAssignSelect');
|
const input = document.getElementById('quickAssignInput');
|
||||||
data.users.forEach(user => {
|
if (!input) return;
|
||||||
const option = document.createElement('option');
|
const items = [
|
||||||
option.value = user.user_id;
|
{ value: '', label: 'Unassigned' },
|
||||||
option.textContent = user.display_name || user.username;
|
...data.users.map(u => ({
|
||||||
select.appendChild(option);
|
value: String(u.user_id),
|
||||||
|
label: u.display_name || u.username
|
||||||
|
}))
|
||||||
|
];
|
||||||
|
lt.combobox.init(input, items, {
|
||||||
|
onSelect: function(item) { _quickAssignUserId = item.value || null; }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -1049,7 +1068,11 @@ function closeQuickAssignModal() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function performQuickAssign(ticketId) {
|
function performQuickAssign(ticketId) {
|
||||||
const assignedTo = document.getElementById('quickAssignSelect').value || null;
|
if (_quickAssignUserId === undefined) {
|
||||||
|
lt.toast.warning('Please select a user from the list', 2000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const assignedTo = _quickAssignUserId;
|
||||||
|
|
||||||
lt.api.post('/api/assign_ticket.php', { ticket_id: ticketId, assigned_to: assignedTo })
|
lt.api.post('/api/assign_ticket.php', { ticket_id: ticketId, assigned_to: assignedTo })
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
|||||||
+45
-6
@@ -77,6 +77,38 @@ function saveTicket() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Description read/edit helpers ────────────────────────────────────────────
|
||||||
|
// Read mode: styled lt-markdown div (full contrast, even on OLED).
|
||||||
|
// Edit mode: raw textarea (enabled for editing).
|
||||||
|
|
||||||
|
function renderDescriptionView() {
|
||||||
|
var viewDiv = document.getElementById('ticketDescriptionView');
|
||||||
|
var textarea = document.querySelector('textarea[data-field="description"]');
|
||||||
|
if (!viewDiv || !textarea) return;
|
||||||
|
var raw = textarea.value || '';
|
||||||
|
if (!raw.trim()) {
|
||||||
|
viewDiv.innerHTML = '<p class="lt-text-muted lt-text-sm"><em>No description provided.</em></p>';
|
||||||
|
} else {
|
||||||
|
// Ticket descriptions are plain text. CSS white-space:pre-wrap handles
|
||||||
|
// line breaks and multiple spaces (ASCII art) — no <br> replacement needed.
|
||||||
|
viewDiv.innerHTML = lt.escHtml(raw);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showDescriptionView() {
|
||||||
|
var v = document.getElementById('ticketDescriptionView');
|
||||||
|
var t = document.querySelector('textarea[data-field="description"]');
|
||||||
|
if (v) v.style.display = '';
|
||||||
|
if (t) t.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
function showDescriptionEdit() {
|
||||||
|
var v = document.getElementById('ticketDescriptionView');
|
||||||
|
var t = document.querySelector('textarea[data-field="description"]');
|
||||||
|
if (v) v.style.display = 'none';
|
||||||
|
if (t) t.style.display = '';
|
||||||
|
}
|
||||||
|
|
||||||
function toggleEditMode() {
|
function toggleEditMode() {
|
||||||
const editButton = document.getElementById('editButton');
|
const editButton = document.getElementById('editButton');
|
||||||
const titleField = document.querySelector('.title-input');
|
const titleField = document.querySelector('.title-input');
|
||||||
@@ -94,16 +126,17 @@ function toggleEditMode() {
|
|||||||
titleField.focus();
|
titleField.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable description (textarea)
|
// Enable description (swap to textarea)
|
||||||
if (descriptionField) {
|
if (descriptionField) {
|
||||||
|
showDescriptionEdit();
|
||||||
descriptionField.disabled = false;
|
descriptionField.disabled = false;
|
||||||
descriptionField.style.height = 'auto';
|
descriptionField.style.height = 'auto';
|
||||||
descriptionField.style.height = descriptionField.scrollHeight + 'px';
|
descriptionField.style.height = descriptionField.scrollHeight + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable metadata fields (priority, category, type)
|
// Enable metadata fields (priority, category, type) — remove display-only class
|
||||||
metadataFields.forEach(field => {
|
metadataFields.forEach(field => {
|
||||||
field.disabled = false;
|
field.classList.remove('lt-display-field');
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
saveTicket();
|
saveTicket();
|
||||||
@@ -115,14 +148,16 @@ function toggleEditMode() {
|
|||||||
titleField.setAttribute('contenteditable', 'false');
|
titleField.setAttribute('contenteditable', 'false');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable description
|
// Re-render description view div with latest content
|
||||||
if (descriptionField) {
|
if (descriptionField) {
|
||||||
descriptionField.disabled = true;
|
descriptionField.disabled = true;
|
||||||
|
renderDescriptionView();
|
||||||
|
showDescriptionView();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable metadata fields
|
// Return metadata fields to display-only using .lt-display-field (not disabled)
|
||||||
metadataFields.forEach(field => {
|
metadataFields.forEach(field => {
|
||||||
field.disabled = true;
|
field.classList.add('lt-display-field');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -259,6 +294,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
// Show description tab by default
|
// Show description tab by default
|
||||||
showTab('description');
|
showTab('description');
|
||||||
|
|
||||||
|
// Populate and show description view div on page load
|
||||||
|
renderDescriptionView();
|
||||||
|
showDescriptionView();
|
||||||
|
|
||||||
// Auto-resize function for textareas
|
// Auto-resize function for textareas
|
||||||
function autoResizeTextarea(textarea) {
|
function autoResizeTextarea(textarea) {
|
||||||
// Reset height to auto to get the correct scrollHeight
|
// Reset height to auto to get the correct scrollHeight
|
||||||
|
|||||||
+3
-12
@@ -857,7 +857,9 @@ document.querySelectorAll('.lt-stat-card').forEach(function (card) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Event delegation for click actions
|
// Event delegation for click actions — only handles cases NOT covered by dashboard.js
|
||||||
|
// bulk-*, navigate, view-ticket, quick-*, set-view-mode, clear-selection, toggle-*
|
||||||
|
// are all handled by dashboard.js to avoid double-firing (duplicate handlers = duplicate users in selects).
|
||||||
document.addEventListener('click', function (e) {
|
document.addEventListener('click', function (e) {
|
||||||
var target = e.target.closest('[data-action]');
|
var target = e.target.closest('[data-action]');
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
@@ -870,19 +872,8 @@ document.addEventListener('click', function (e) {
|
|||||||
case 'open-advanced-search': openAdvancedSearch(); break;
|
case 'open-advanced-search': openAdvancedSearch(); break;
|
||||||
case 'close-advanced-search': closeAdvancedSearch(); break;
|
case 'close-advanced-search': closeAdvancedSearch(); break;
|
||||||
case 'reset-advanced-search': resetAdvancedSearch(); break;
|
case 'reset-advanced-search': resetAdvancedSearch(); break;
|
||||||
case 'set-view-mode': setViewMode(target.getAttribute('data-mode')); break;
|
|
||||||
case 'navigate': window.location.href = target.getAttribute('data-url'); break;
|
|
||||||
case 'toggle-export-menu': e.stopPropagation(); toggleExportMenu(e); break;
|
case 'toggle-export-menu': e.stopPropagation(); toggleExportMenu(e); break;
|
||||||
case 'export-tickets': e.preventDefault(); exportSelectedTickets(target.getAttribute('data-format')); break;
|
case 'export-tickets': e.preventDefault(); exportSelectedTickets(target.getAttribute('data-format')); break;
|
||||||
case 'bulk-status': showBulkStatusModal(); break;
|
|
||||||
case 'bulk-assign': showBulkAssignModal(); break;
|
|
||||||
case 'bulk-priority': showBulkPriorityModal(); break;
|
|
||||||
case 'clear-selection': clearSelection(); break;
|
|
||||||
case 'toggle-select-all': toggleSelectAll(); break;
|
|
||||||
case 'toggle-row-checkbox': toggleRowCheckbox(e, target); break;
|
|
||||||
case 'view-ticket': e.stopPropagation(); window.location.href = '/ticket/' + target.getAttribute('data-ticket-id'); break;
|
|
||||||
case 'quick-status': e.stopPropagation(); quickStatusChange(target.getAttribute('data-ticket-id'), target.getAttribute('data-status')); break;
|
|
||||||
case 'quick-assign': e.stopPropagation(); quickAssign(target.getAttribute('data-ticket-id')); break;
|
|
||||||
case 'save-filter': saveCurrentFilter(); break;
|
case 'save-filter': saveCurrentFilter(); break;
|
||||||
case 'delete-filter': deleteSavedFilter(); break;
|
case 'delete-filter': deleteSavedFilter(); break;
|
||||||
case 'remove-filter': removeFilter(target.getAttribute('data-filter-type'), target.getAttribute('data-filter-value')); break;
|
case 'remove-filter': removeFilter(target.getAttribute('data-filter-type'), target.getAttribute('data-filter-value')); break;
|
||||||
|
|||||||
+12
-6
@@ -167,7 +167,7 @@ include __DIR__ . '/layout_header.php';
|
|||||||
<div class="lt-kv-row">
|
<div class="lt-kv-row">
|
||||||
<span class="lt-kv-label">Priority</span>
|
<span class="lt-kv-label">Priority</span>
|
||||||
<span class="lt-kv-value">
|
<span class="lt-kv-value">
|
||||||
<select id="prioritySelect" class="lt-select lt-select-sm editable-metadata" disabled aria-label="Priority">
|
<select id="prioritySelect" class="lt-select lt-select-sm editable-metadata lt-display-field" aria-label="Priority">
|
||||||
<?php foreach ([1=>'P1 - Critical',2=>'P2 - High',3=>'P3 - Medium',4=>'P4 - Low',5=>'P5 - Minimal'] as $v=>$l): ?>
|
<?php foreach ([1=>'P1 - Critical',2=>'P2 - High',3=>'P3 - Medium',4=>'P4 - Low',5=>'P5 - Minimal'] as $v=>$l): ?>
|
||||||
<option value="<?= $v ?>" <?= (int)$ticket['priority'] === $v ? 'selected' : '' ?>><?= $l ?></option>
|
<option value="<?= $v ?>" <?= (int)$ticket['priority'] === $v ? 'selected' : '' ?>><?= $l ?></option>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
@@ -177,7 +177,7 @@ include __DIR__ . '/layout_header.php';
|
|||||||
<div class="lt-kv-row">
|
<div class="lt-kv-row">
|
||||||
<span class="lt-kv-label">Category</span>
|
<span class="lt-kv-label">Category</span>
|
||||||
<span class="lt-kv-value">
|
<span class="lt-kv-value">
|
||||||
<select id="categorySelect" class="lt-select lt-select-sm editable-metadata" disabled aria-label="Category">
|
<select id="categorySelect" class="lt-select lt-select-sm editable-metadata lt-display-field" aria-label="Category">
|
||||||
<?php foreach (['Hardware','Software','Network','Security','General'] as $c): ?>
|
<?php foreach (['Hardware','Software','Network','Security','General'] as $c): ?>
|
||||||
<option value="<?= $c ?>" <?= $ticket['category'] === $c ? 'selected' : '' ?>><?= $c ?></option>
|
<option value="<?= $c ?>" <?= $ticket['category'] === $c ? 'selected' : '' ?>><?= $c ?></option>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
@@ -187,7 +187,7 @@ include __DIR__ . '/layout_header.php';
|
|||||||
<div class="lt-kv-row">
|
<div class="lt-kv-row">
|
||||||
<span class="lt-kv-label">Type</span>
|
<span class="lt-kv-label">Type</span>
|
||||||
<span class="lt-kv-value">
|
<span class="lt-kv-value">
|
||||||
<select id="typeSelect" class="lt-select lt-select-sm editable-metadata" disabled aria-label="Type">
|
<select id="typeSelect" class="lt-select lt-select-sm editable-metadata lt-display-field" aria-label="Type">
|
||||||
<?php foreach (['Maintenance','Install','Task','Upgrade','Issue','Problem'] as $t): ?>
|
<?php foreach (['Maintenance','Install','Task','Upgrade','Issue','Problem'] as $t): ?>
|
||||||
<option value="<?= $t ?>" <?= $ticket['type'] === $t ? 'selected' : '' ?>><?= $t ?></option>
|
<option value="<?= $t ?>" <?= $ticket['type'] === $t ? 'selected' : '' ?>><?= $t ?></option>
|
||||||
<?php endforeach ?>
|
<?php endforeach ?>
|
||||||
@@ -211,7 +211,7 @@ include __DIR__ . '/layout_header.php';
|
|||||||
<div class="lt-kv-row">
|
<div class="lt-kv-row">
|
||||||
<span class="lt-kv-label">Visibility</span>
|
<span class="lt-kv-label">Visibility</span>
|
||||||
<span class="lt-kv-value">
|
<span class="lt-kv-value">
|
||||||
<select id="visibilitySelect" class="lt-select lt-select-sm editable-metadata" disabled
|
<select id="visibilitySelect" class="lt-select lt-select-sm editable-metadata lt-display-field"
|
||||||
data-action="toggle-visibility-groups" aria-label="Visibility">
|
data-action="toggle-visibility-groups" aria-label="Visibility">
|
||||||
<option value="public" <?= $currentVisibility === 'public' ? 'selected' : '' ?>>Public</option>
|
<option value="public" <?= $currentVisibility === 'public' ? 'selected' : '' ?>>Public</option>
|
||||||
<option value="internal" <?= $currentVisibility === 'internal' ? 'selected' : '' ?>>Internal</option>
|
<option value="internal" <?= $currentVisibility === 'internal' ? 'selected' : '' ?>>Internal</option>
|
||||||
@@ -265,7 +265,7 @@ include __DIR__ . '/layout_header.php';
|
|||||||
<?php foreach ($allAvailableGroups as $group):
|
<?php foreach ($allAvailableGroups as $group):
|
||||||
$isChecked = in_array($group, $currentVisibilityGroups, true); ?>
|
$isChecked = in_array($group, $currentVisibilityGroups, true); ?>
|
||||||
<label class="lt-filter-option">
|
<label class="lt-filter-option">
|
||||||
<input type="checkbox" class="lt-checkbox visibility-group-checkbox editable-metadata" disabled
|
<input type="checkbox" class="lt-checkbox visibility-group-checkbox editable-metadata lt-display-field"
|
||||||
value="<?= htmlspecialchars($group, ENT_QUOTES, 'UTF-8') ?>"
|
value="<?= htmlspecialchars($group, ENT_QUOTES, 'UTF-8') ?>"
|
||||||
<?= $isChecked ? 'checked' : '' ?>>
|
<?= $isChecked ? 'checked' : '' ?>>
|
||||||
<span class="lt-badge"><?= htmlspecialchars($group) ?></span>
|
<span class="lt-badge"><?= htmlspecialchars($group) ?></span>
|
||||||
@@ -320,12 +320,18 @@ include __DIR__ . '/layout_header.php';
|
|||||||
<div class="lt-section-body">
|
<div class="lt-section-body">
|
||||||
<div class="lt-form-group">
|
<div class="lt-form-group">
|
||||||
<label class="lt-sr-only lt-label" for="ticketDescription">Description</label>
|
<label class="lt-sr-only lt-label" for="ticketDescription">Description</label>
|
||||||
|
<!-- Read view: shown when not editing — uses lt-markdown for readable typography -->
|
||||||
|
<div id="ticketDescriptionView"
|
||||||
|
class="lt-markdown ticket-description-view"
|
||||||
|
aria-label="Ticket description"></div>
|
||||||
|
<!-- Edit view: shown only when editing -->
|
||||||
<textarea id="ticketDescription"
|
<textarea id="ticketDescription"
|
||||||
class="lt-input lt-textarea editable"
|
class="lt-input lt-textarea editable"
|
||||||
data-field="description"
|
data-field="description"
|
||||||
disabled
|
disabled
|
||||||
rows="18"
|
rows="18"
|
||||||
aria-label="Ticket description"><?= htmlspecialchars($ticket['description'] ?? '') ?></textarea>
|
style="display:none"
|
||||||
|
aria-label="Ticket description (edit)"><?= htmlspecialchars($ticket['description'] ?? '') ?></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ include __DIR__ . '/../../views/layout_header.php';
|
|||||||
<div id="newKeyDisplay" class="lt-frame-inner lt-mt-sm is-hidden">
|
<div id="newKeyDisplay" class="lt-frame-inner lt-mt-sm is-hidden">
|
||||||
<div class="lt-subsection-header lt-text-amber">⚠ Copy this key now — you won't see it again!</div>
|
<div class="lt-subsection-header lt-text-amber">⚠ Copy this key now — you won't see it again!</div>
|
||||||
<div class="lt-flex lt-flex-gap-sm lt-mt-sm">
|
<div class="lt-flex lt-flex-gap-sm lt-mt-sm">
|
||||||
<input type="text" id="newKeyValue" readonly class="lt-input" style="flex:1;font-family:monospace">
|
<input type="text" id="newKeyValue" readonly class="lt-input" style="flex:1;font-family:monospace;opacity:1;cursor:text">
|
||||||
<button type="button" class="lt-btn lt-btn-sm" data-action="copy-api-key">COPY</button>
|
<button type="button" class="lt-btn lt-btn-sm" data-action="copy-api-key">COPY</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ $_lt_assetVer = $GLOBALS['config']['ASSET_VERSION'] ?? '20260329';
|
|||||||
<meta name="robots" content="noindex, nofollow">
|
<meta name="robots" content="noindex, nofollow">
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,400;0,600;0,700;1,400&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,400;0,600;0,700;1,400&family=VT323&display=swap" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="/assets/css/base.css?v=<?= $_lt_assetVer ?>">
|
<link rel="stylesheet" href="/assets/css/base.css?v=<?= $_lt_assetVer ?>">
|
||||||
<?php if (!empty($pageStyles)): ?>
|
<?php if (!empty($pageStyles)): ?>
|
||||||
<?php foreach ($pageStyles as $_lt_css): ?>
|
<?php foreach ($pageStyles as $_lt_css): ?>
|
||||||
@@ -55,7 +55,7 @@ $_lt_assetVer = $GLOBALS['config']['ASSET_VERSION'] ?? '20260329';
|
|||||||
], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG) ?>;
|
], JSON_UNESCAPED_UNICODE | JSON_HEX_TAG) ?>;
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body class="lt-scanlines">
|
||||||
|
|
||||||
<!-- SKIP LINK -->
|
<!-- SKIP LINK -->
|
||||||
<a class="lt-skip-link" href="#main-content">Skip to main content</a>
|
<a class="lt-skip-link" href="#main-content">Skip to main content</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user