Compare commits
7 Commits
7695c6134c
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| ce95e555d5 | |||
| f45ec9b0f7 | |||
| 5a41ebf180 | |||
| e35401d54e | |||
| 913e294f9d | |||
| 28aa9e33ea | |||
| 31aa7d1b81 |
@@ -1413,6 +1413,11 @@ h1 {
|
|||||||
.loading-overlay--hiding { opacity: 0; }
|
.loading-overlay--hiding { opacity: 0; }
|
||||||
.has-overlay { position: relative; }
|
.has-overlay { position: relative; }
|
||||||
|
|
||||||
|
/* Visibility utilities — !important required to override HTML inline style="display:none" */
|
||||||
|
.is-hidden { display: none !important; }
|
||||||
|
.bulk-actions-inline.is-visible { display: flex !important; }
|
||||||
|
.export-dropdown.is-visible { display: inline-block !important; }
|
||||||
|
|
||||||
.loading-overlay .loading-text {
|
.loading-overlay .loading-text {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
animation: blink-cursor 1s step-end infinite;
|
animation: blink-cursor 1s step-end infinite;
|
||||||
@@ -2720,6 +2725,7 @@ input[type="checkbox"]:checked {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
transition: opacity 0.5s;
|
transition: opacity 0.5s;
|
||||||
}
|
}
|
||||||
|
.boot-overlay--fade-out { opacity: 0; }
|
||||||
|
|
||||||
#boot-text {
|
#boot-text {
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
@@ -4580,6 +4586,7 @@ tr:hover .quick-actions {
|
|||||||
transition: border-color 0.2s ease, transform 0.15s ease;
|
transition: border-color 0.2s ease, transform 0.15s ease;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Corner accent */
|
/* Corner accent */
|
||||||
|
|||||||
@@ -584,6 +584,8 @@ textarea.editable {
|
|||||||
background: var(--bg-secondary);
|
background: var(--bg-secondary);
|
||||||
cursor: default;
|
cursor: default;
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
|
overflow: hidden;
|
||||||
|
resize: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Button Styles */
|
/* Button Styles */
|
||||||
@@ -989,6 +991,7 @@ textarea.editable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.animate-fadein { animation: fadeIn 0.3s ease; }
|
.animate-fadein { animation: fadeIn 0.3s ease; }
|
||||||
|
.is-hidden { display: none !important; }
|
||||||
.animate-fadeout { animation: fadeIn 0.2s ease reverse; }
|
.animate-fadeout { animation: fadeIn 0.2s ease reverse; }
|
||||||
.comment--deleting { opacity: 0; transform: translateX(-20px); transition: opacity 0.3s, transform 0.3s; }
|
.comment--deleting { opacity: 0; transform: translateX(-20px); transition: opacity 0.3s, transform 0.3s; }
|
||||||
|
|
||||||
|
|||||||
@@ -683,22 +683,14 @@ function updateSelectionCount() {
|
|||||||
const exportCount = document.getElementById('exportCount');
|
const exportCount = document.getElementById('exportCount');
|
||||||
|
|
||||||
if (toolbar && countDisplay) {
|
if (toolbar && countDisplay) {
|
||||||
if (count > 0) {
|
toolbar.classList.toggle('is-visible', count > 0);
|
||||||
toolbar.style.display = 'flex';
|
if (count > 0) countDisplay.textContent = count;
|
||||||
countDisplay.textContent = count;
|
|
||||||
} else {
|
|
||||||
toolbar.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show/hide export dropdown based on selection
|
// Show/hide export dropdown based on selection
|
||||||
if (exportDropdown) {
|
if (exportDropdown) {
|
||||||
if (count > 0) {
|
exportDropdown.classList.toggle('is-visible', count > 0);
|
||||||
exportDropdown.style.display = '';
|
if (count > 0 && exportCount) exportCount.textContent = count;
|
||||||
if (exportCount) exportCount.textContent = count;
|
|
||||||
} else {
|
|
||||||
exportDropdown.style.display = 'none';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1359,14 +1351,14 @@ function setViewMode(mode) {
|
|||||||
if (!tableView || !cardView) return;
|
if (!tableView || !cardView) return;
|
||||||
|
|
||||||
if (mode === 'card') {
|
if (mode === 'card') {
|
||||||
tableView.style.display = 'none';
|
tableView.classList.add('is-hidden');
|
||||||
cardView.style.display = 'block';
|
cardView.classList.remove('is-hidden');
|
||||||
tableBtn.classList.remove('active');
|
tableBtn.classList.remove('active');
|
||||||
cardBtn.classList.add('active');
|
cardBtn.classList.add('active');
|
||||||
populateKanbanCards();
|
populateKanbanCards();
|
||||||
} else {
|
} else {
|
||||||
tableView.style.display = 'block';
|
tableView.classList.remove('is-hidden');
|
||||||
cardView.style.display = 'none';
|
cardView.classList.add('is-hidden');
|
||||||
tableBtn.classList.add('active');
|
tableBtn.classList.add('active');
|
||||||
cardBtn.classList.remove('active');
|
cardBtn.classList.remove('active');
|
||||||
}
|
}
|
||||||
@@ -1444,8 +1436,7 @@ function populateKanbanCards() {
|
|||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const savedMode = localStorage.getItem('ticketViewMode');
|
const savedMode = localStorage.getItem('ticketViewMode');
|
||||||
if (savedMode === 'card') {
|
if (savedMode === 'card') {
|
||||||
// Delay to ensure DOM is ready
|
setViewMode('card');
|
||||||
setTimeout(() => setViewMode('card'), 100);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1460,8 +1451,7 @@ function initTicketPreview() {
|
|||||||
// Create preview element
|
// Create preview element
|
||||||
const preview = document.createElement('div');
|
const preview = document.createElement('div');
|
||||||
preview.id = 'ticketPreview';
|
preview.id = 'ticketPreview';
|
||||||
preview.className = 'ticket-preview-popup';
|
preview.className = 'ticket-preview-popup is-hidden';
|
||||||
preview.style.display = 'none';
|
|
||||||
document.body.appendChild(preview);
|
document.body.appendChild(preview);
|
||||||
currentPreview = preview;
|
currentPreview = preview;
|
||||||
|
|
||||||
@@ -1544,7 +1534,7 @@ function showTicketPreview(event) {
|
|||||||
|
|
||||||
currentPreview.style.left = left + 'px';
|
currentPreview.style.left = left + 'px';
|
||||||
currentPreview.style.top = top + 'px';
|
currentPreview.style.top = top + 'px';
|
||||||
currentPreview.style.display = 'block';
|
currentPreview.classList.remove('is-hidden');
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1554,7 +1544,7 @@ function hideTicketPreview() {
|
|||||||
}
|
}
|
||||||
previewTimeout = setTimeout(() => {
|
previewTimeout = setTimeout(() => {
|
||||||
if (currentPreview) {
|
if (currentPreview) {
|
||||||
currentPreview.style.display = 'none';
|
currentPreview.classList.add('is-hidden');
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
@@ -1755,6 +1745,7 @@ function initRelativeTimes() {
|
|||||||
document.addEventListener('DOMContentLoaded', initRelativeTimes);
|
document.addEventListener('DOMContentLoaded', initRelativeTimes);
|
||||||
setInterval(initRelativeTimes, 60000);
|
setInterval(initRelativeTimes, 60000);
|
||||||
|
|
||||||
|
|
||||||
// Export for use in other scripts
|
// Export for use in other scripts
|
||||||
window.generateSkeletonRows = generateSkeletonRows;
|
window.generateSkeletonRows = generateSkeletonRows;
|
||||||
window.generateSkeletonComments = generateSkeletonComments;
|
window.generateSkeletonComments = generateSkeletonComments;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ function toggleVisibilityGroupsEdit() {
|
|||||||
const visibility = document.getElementById('visibilitySelect')?.value;
|
const visibility = document.getElementById('visibilitySelect')?.value;
|
||||||
const groupsField = document.getElementById('visibilityGroupsField');
|
const groupsField = document.getElementById('visibilityGroupsField');
|
||||||
if (groupsField) {
|
if (groupsField) {
|
||||||
groupsField.style.display = visibility === 'internal' ? 'block' : 'none';
|
groupsField.classList.toggle('is-hidden', visibility !== 'internal');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +86,8 @@ function toggleEditMode() {
|
|||||||
// Enable description (textarea)
|
// Enable description (textarea)
|
||||||
if (descriptionField) {
|
if (descriptionField) {
|
||||||
descriptionField.disabled = false;
|
descriptionField.disabled = false;
|
||||||
|
descriptionField.style.height = 'auto';
|
||||||
|
descriptionField.style.height = descriptionField.scrollHeight + 'px';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable metadata fields (priority, category, type)
|
// Enable metadata fields (priority, category, type)
|
||||||
@@ -199,7 +201,7 @@ function togglePreview() {
|
|||||||
if (!preview || !textarea || !toggleEl) return;
|
if (!preview || !textarea || !toggleEl) return;
|
||||||
|
|
||||||
const isPreviewEnabled = toggleEl.checked;
|
const isPreviewEnabled = toggleEl.checked;
|
||||||
preview.style.display = isPreviewEnabled ? 'block' : 'none';
|
preview.classList.toggle('is-hidden', !isPreviewEnabled);
|
||||||
|
|
||||||
if (isPreviewEnabled) {
|
if (isPreviewEnabled) {
|
||||||
preview.innerHTML = parseMarkdown(textarea.value);
|
preview.innerHTML = parseMarkdown(textarea.value);
|
||||||
@@ -220,9 +222,9 @@ function updatePreview() {
|
|||||||
|
|
||||||
if (isMarkdownEnabled && commentText.trim()) {
|
if (isMarkdownEnabled && commentText.trim()) {
|
||||||
previewDiv.innerHTML = parseMarkdown(commentText);
|
previewDiv.innerHTML = parseMarkdown(commentText);
|
||||||
previewDiv.style.display = 'block';
|
previewDiv.classList.remove('is-hidden');
|
||||||
} else {
|
} else {
|
||||||
previewDiv.style.display = 'none';
|
previewDiv.classList.add('is-hidden');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -236,7 +238,7 @@ function toggleMarkdownMode() {
|
|||||||
if (!isMasterEnabled) {
|
if (!isMasterEnabled) {
|
||||||
previewToggle.checked = false;
|
previewToggle.checked = false;
|
||||||
const preview = document.getElementById('markdownPreview');
|
const preview = document.getElementById('markdownPreview');
|
||||||
if (preview) preview.style.display = 'none';
|
if (preview) preview.classList.add('is-hidden');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,17 +450,7 @@ function showTab(tabName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Hide all tabs
|
// Hide all tabs
|
||||||
descriptionTab.style.display = 'none';
|
document.querySelectorAll('.tab-content').forEach(tab => tab.classList.remove('active'));
|
||||||
commentsTab.style.display = 'none';
|
|
||||||
if (attachmentsTab) {
|
|
||||||
attachmentsTab.style.display = 'none';
|
|
||||||
}
|
|
||||||
if (dependenciesTab) {
|
|
||||||
dependenciesTab.style.display = 'none';
|
|
||||||
}
|
|
||||||
if (activityTab) {
|
|
||||||
activityTab.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove active class and aria-selected from all buttons
|
// Remove active class and aria-selected from all buttons
|
||||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||||
@@ -468,7 +460,7 @@ function showTab(tabName) {
|
|||||||
|
|
||||||
// Show selected tab and activate its button
|
// Show selected tab and activate its button
|
||||||
const tabEl = document.getElementById(`${tabName}-tab`);
|
const tabEl = document.getElementById(`${tabName}-tab`);
|
||||||
if (tabEl) tabEl.style.display = 'block';
|
if (tabEl) tabEl.classList.add('active');
|
||||||
const activeBtn = document.querySelector(`.tab-btn[data-tab="${tabName}"]`);
|
const activeBtn = document.querySelector(`.tab-btn[data-tab="${tabName}"]`);
|
||||||
if (activeBtn) {
|
if (activeBtn) {
|
||||||
activeBtn.classList.add('active');
|
activeBtn.classList.add('active');
|
||||||
@@ -711,7 +703,7 @@ function handleFileUpload(files) {
|
|||||||
let uploadedCount = 0;
|
let uploadedCount = 0;
|
||||||
const totalFiles = files.length;
|
const totalFiles = files.length;
|
||||||
|
|
||||||
progressDiv.style.display = 'block';
|
progressDiv.classList.remove('is-hidden');
|
||||||
statusText.textContent = `Uploading 0 of ${totalFiles} files...`;
|
statusText.textContent = `Uploading 0 of ${totalFiles} files...`;
|
||||||
progressFill.style.width = '0%';
|
progressFill.style.width = '0%';
|
||||||
|
|
||||||
@@ -777,7 +769,7 @@ function resetUploadUI() {
|
|||||||
const progressDiv = document.getElementById('uploadProgress');
|
const progressDiv = document.getElementById('uploadProgress');
|
||||||
const fileInput = document.getElementById('fileInput');
|
const fileInput = document.getElementById('fileInput');
|
||||||
|
|
||||||
progressDiv.style.display = 'none';
|
progressDiv.classList.add('is-hidden');
|
||||||
if (fileInput) {
|
if (fileInput) {
|
||||||
fileInput.value = '';
|
fileInput.value = '';
|
||||||
}
|
}
|
||||||
@@ -1036,7 +1028,6 @@ function showMentionSuggestions(query, textarea) {
|
|||||||
|
|
||||||
mentionAutocomplete.innerHTML = html;
|
mentionAutocomplete.innerHTML = html;
|
||||||
mentionAutocomplete.classList.add('active');
|
mentionAutocomplete.classList.add('active');
|
||||||
const textarea = document.getElementById('newComment');
|
|
||||||
if (textarea) textarea.setAttribute('aria-expanded', 'true');
|
if (textarea) textarea.setAttribute('aria-expanded', 'true');
|
||||||
selectedMentionIndex = 0;
|
selectedMentionIndex = 0;
|
||||||
|
|
||||||
@@ -1180,7 +1171,7 @@ function editComment(commentId) {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
// Hide original text, show edit form
|
// Hide original text, show edit form
|
||||||
textDiv.style.display = 'none';
|
textDiv.classList.add('is-hidden');
|
||||||
textDiv.after(editForm);
|
textDiv.after(editForm);
|
||||||
commentDiv.classList.add('editing');
|
commentDiv.classList.add('editing');
|
||||||
|
|
||||||
@@ -1247,7 +1238,7 @@ function saveEditComment(commentId) {
|
|||||||
|
|
||||||
// Remove edit form and show text
|
// Remove edit form and show text
|
||||||
if (editForm) editForm.remove();
|
if (editForm) editForm.remove();
|
||||||
textDiv.style.display = '';
|
textDiv.classList.remove('is-hidden');
|
||||||
commentDiv.classList.remove('editing');
|
commentDiv.classList.remove('editing');
|
||||||
|
|
||||||
lt.toast.success('Comment updated successfully');
|
lt.toast.success('Comment updated successfully');
|
||||||
@@ -1269,7 +1260,7 @@ function cancelEditComment(commentId) {
|
|||||||
const editForm = document.getElementById(`comment-edit-form-${commentId}`);
|
const editForm = document.getElementById(`comment-edit-form-${commentId}`);
|
||||||
|
|
||||||
if (editForm) editForm.remove();
|
if (editForm) editForm.remove();
|
||||||
if (textDiv) textDiv.style.display = '';
|
if (textDiv) textDiv.classList.remove('is-hidden');
|
||||||
if (commentDiv) commentDiv.classList.remove('editing');
|
if (commentDiv) commentDiv.classList.remove('editing');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
<input type="text" id="title" name="title" class="editable" required placeholder="Enter a descriptive title for this ticket">
|
<input type="text" id="title" name="title" class="editable" required placeholder="Enter a descriptive title for this ticket">
|
||||||
</div>
|
</div>
|
||||||
<!-- Duplicate Warning Area -->
|
<!-- Duplicate Warning Area -->
|
||||||
<div id="duplicateWarning" class="inline-warning" role="alert" aria-live="polite" aria-atomic="true" style="display: none;">
|
<div id="duplicateWarning" class="inline-warning is-hidden" role="alert" aria-live="polite" aria-atomic="true">
|
||||||
<div class="text-amber fw-bold duplicate-heading">
|
<div class="text-amber fw-bold duplicate-heading">
|
||||||
Possible Duplicates Found
|
Possible Duplicates Found
|
||||||
</div>
|
</div>
|
||||||
@@ -210,7 +210,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
Controls who can view this ticket
|
Controls who can view this ticket
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div id="visibilityGroupsContainer" class="detail-group" style="display: none;">
|
<div id="visibilityGroupsContainer" class="detail-group is-hidden">
|
||||||
<label>Allowed Groups</label>
|
<label>Allowed Groups</label>
|
||||||
<div class="visibility-groups-list">
|
<div class="visibility-groups-list">
|
||||||
<?php
|
<?php
|
||||||
@@ -277,7 +277,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
const title = this.value.trim();
|
const title = this.value.trim();
|
||||||
|
|
||||||
if (title.length < 5) {
|
if (title.length < 5) {
|
||||||
document.getElementById('duplicateWarning').style.display = 'none';
|
document.getElementById('duplicateWarning').classList.add('is-hidden');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,9 +308,9 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
html += '<p class="duplicate-hint">Consider checking these tickets before creating a new one.</p>';
|
html += '<p class="duplicate-hint">Consider checking these tickets before creating a new one.</p>';
|
||||||
|
|
||||||
listDiv.innerHTML = html;
|
listDiv.innerHTML = html;
|
||||||
warningDiv.style.display = 'block';
|
warningDiv.classList.remove('is-hidden');
|
||||||
} else {
|
} else {
|
||||||
warningDiv.style.display = 'none';
|
warningDiv.classList.add('is-hidden');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@@ -322,9 +322,9 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
const visibility = document.getElementById('visibility').value;
|
const visibility = document.getElementById('visibility').value;
|
||||||
const groupsContainer = document.getElementById('visibilityGroupsContainer');
|
const groupsContainer = document.getElementById('visibilityGroupsContainer');
|
||||||
if (visibility === 'internal') {
|
if (visibility === 'internal') {
|
||||||
groupsContainer.style.display = 'block';
|
groupsContainer.classList.remove('is-hidden');
|
||||||
} else {
|
} else {
|
||||||
groupsContainer.style.display = 'none';
|
groupsContainer.classList.add('is-hidden');
|
||||||
document.querySelectorAll('.visibility-group-checkbox').forEach(cb => cb.checked = false);
|
document.querySelectorAll('.visibility-group-checkbox').forEach(cb => cb.checked = false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
i++;
|
i++;
|
||||||
} else {
|
} else {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
bootOverlay.style.opacity = '0';
|
bootOverlay.classList.add('boot-overlay--fade-out');
|
||||||
setTimeout(() => bootOverlay.remove(), 500);
|
setTimeout(() => bootOverlay.remove(), 500);
|
||||||
}, 500);
|
}, 500);
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
@@ -144,7 +144,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
<label>
|
<label>
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
name="category"
|
name="category"
|
||||||
value="<?php echo $cat; ?>"
|
value="<?php echo htmlspecialchars($cat); ?>"
|
||||||
<?php echo in_array($cat, $currentCategories) ? 'checked' : ''; ?>>
|
<?php echo in_array($cat, $currentCategories) ? 'checked' : ''; ?>>
|
||||||
<?php echo htmlspecialchars($cat); ?>
|
<?php echo htmlspecialchars($cat); ?>
|
||||||
</label>
|
</label>
|
||||||
@@ -161,7 +161,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
<label>
|
<label>
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
name="type"
|
name="type"
|
||||||
value="<?php echo $type; ?>"
|
value="<?php echo htmlspecialchars($type); ?>"
|
||||||
<?php echo in_array($type, $currentTypes) ? 'checked' : ''; ?>>
|
<?php echo in_array($type, $currentTypes) ? 'checked' : ''; ?>>
|
||||||
<?php echo htmlspecialchars($type); ?>
|
<?php echo htmlspecialchars($type); ?>
|
||||||
</label>
|
</label>
|
||||||
@@ -291,7 +291,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
// Previous page button
|
// Previous page button
|
||||||
if ($page > 1) {
|
if ($page > 1) {
|
||||||
$currentParams['page'] = $page - 1;
|
$currentParams['page'] = $page - 1;
|
||||||
$prevUrl = '?' . http_build_query($currentParams);
|
$prevUrl = htmlspecialchars('?' . http_build_query($currentParams), ENT_QUOTES, 'UTF-8');
|
||||||
echo "<button data-action='navigate' data-url='$prevUrl' aria-label='Previous page'>[ « ]</button>";
|
echo "<button data-action='navigate' data-url='$prevUrl' aria-label='Previous page'>[ « ]</button>";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,14 +299,14 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
for ($i = 1; $i <= $totalPages; $i++) {
|
for ($i = 1; $i <= $totalPages; $i++) {
|
||||||
$activeClass = ($i === $page) ? 'active' : '';
|
$activeClass = ($i === $page) ? 'active' : '';
|
||||||
$currentParams['page'] = $i;
|
$currentParams['page'] = $i;
|
||||||
$pageUrl = '?' . http_build_query($currentParams);
|
$pageUrl = htmlspecialchars('?' . http_build_query($currentParams), ENT_QUOTES, 'UTF-8');
|
||||||
echo "<button class='$activeClass' data-action='navigate' data-url='$pageUrl'>$i</button>";
|
echo "<button class='$activeClass' data-action='navigate' data-url='$pageUrl'>$i</button>";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next page button
|
// Next page button
|
||||||
if ($page < $totalPages) {
|
if ($page < $totalPages) {
|
||||||
$currentParams['page'] = $page + 1;
|
$currentParams['page'] = $page + 1;
|
||||||
$nextUrl = '?' . http_build_query($currentParams);
|
$nextUrl = htmlspecialchars('?' . http_build_query($currentParams), ENT_QUOTES, 'UTF-8');
|
||||||
echo "<button data-action='navigate' data-url='$nextUrl' aria-label='Next page'>[ » ]</button>";
|
echo "<button data-action='navigate' data-url='$nextUrl' aria-label='Next page'>[ » ]</button>";
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
@@ -393,7 +393,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php
|
<?php
|
||||||
$currentSort = isset($_GET['sort']) ? $_GET['sort'] : 'ticket_id';
|
$currentSort = isset($_GET['sort']) ? $_GET['sort'] : 'ticket_id';
|
||||||
$currentDir = isset($_GET['dir']) ? $_GET['dir'] : 'desc';
|
$currentDir = (isset($_GET['dir']) && $_GET['dir'] === 'asc') ? 'asc' : 'desc';
|
||||||
|
|
||||||
$columns = [
|
$columns = [
|
||||||
'ticket_id' => 'Ticket ID',
|
'ticket_id' => 'Ticket ID',
|
||||||
@@ -417,7 +417,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
$sortClass = ($currentSort === $col) ? "sort-$currentDir" : '';
|
$sortClass = ($currentSort === $col) ? "sort-$currentDir" : '';
|
||||||
$ariaSort = ($currentSort === $col) ? "aria-sort='" . ($currentDir === 'asc' ? 'ascending' : 'descending') . "'" : '';
|
$ariaSort = ($currentSort === $col) ? "aria-sort='" . ($currentDir === 'asc' ? 'ascending' : 'descending') . "'" : '';
|
||||||
$sortParams = array_merge($_GET, ['sort' => $col, 'dir' => $newDir]);
|
$sortParams = array_merge($_GET, ['sort' => $col, 'dir' => $newDir]);
|
||||||
$sortUrl = '?' . http_build_query($sortParams);
|
$sortUrl = htmlspecialchars('?' . http_build_query($sortParams), ENT_QUOTES, 'UTF-8');
|
||||||
echo "<th scope='col' class='$sortClass' data-action='navigate' data-url='$sortUrl' $ariaSort>$label</th>";
|
echo "<th scope='col' class='$sortClass' data-action='navigate' data-url='$sortUrl' $ariaSort>$label</th>";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -440,9 +440,10 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
echo "<td><a href='/ticket/{$row['ticket_id']}' class='ticket-link'>{$row['ticket_id']}</a></td>";
|
echo "<td><a href='/ticket/{$row['ticket_id']}' class='ticket-link'>{$row['ticket_id']}</a></td>";
|
||||||
echo "<td><span>{$row['priority']}</span></td>";
|
echo "<td><span>{$row['priority']}</span></td>";
|
||||||
echo "<td>" . htmlspecialchars($row['title']) . "</td>";
|
echo "<td>" . htmlspecialchars($row['title']) . "</td>";
|
||||||
echo "<td>{$row['category']}</td>";
|
echo "<td>" . htmlspecialchars($row['category']) . "</td>";
|
||||||
echo "<td>{$row['type']}</td>";
|
echo "<td>" . htmlspecialchars($row['type']) . "</td>";
|
||||||
echo "<td><span class='status-" . str_replace(' ', '-', $row['status']) . "'>{$row['status']}</span></td>";
|
$statusSlug = htmlspecialchars(str_replace(' ', '-', $row['status']), ENT_QUOTES);
|
||||||
|
echo "<td><span class='status-" . $statusSlug . "'>" . htmlspecialchars($row['status']) . "</span></td>";
|
||||||
echo "<td>" . htmlspecialchars($creator) . "</td>";
|
echo "<td>" . htmlspecialchars($creator) . "</td>";
|
||||||
echo "<td>" . htmlspecialchars($assignedTo) . "</td>";
|
echo "<td>" . htmlspecialchars($assignedTo) . "</td>";
|
||||||
echo "<td class='ts-cell' data-ts='" . htmlspecialchars($row['created_at'], ENT_QUOTES, 'UTF-8') . "' title='" . date('Y-m-d H:i T', strtotime($row['created_at'])) . "'>" . date('Y-m-d H:i', strtotime($row['created_at'])) . "</td>";
|
echo "<td class='ts-cell' data-ts='" . htmlspecialchars($row['created_at'], ENT_QUOTES, 'UTF-8') . "' title='" . date('Y-m-d H:i T', strtotime($row['created_at'])) . "'>" . date('Y-m-d H:i', strtotime($row['created_at'])) . "</td>";
|
||||||
@@ -451,7 +452,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
echo "<td class='quick-actions-cell'>";
|
echo "<td class='quick-actions-cell'>";
|
||||||
echo "<div class='quick-actions'>";
|
echo "<div class='quick-actions'>";
|
||||||
echo "<button data-action='view-ticket' data-ticket-id='" . $row['ticket_id'] . "' class='quick-action-btn' title='View' aria-label='View ticket " . $row['ticket_id'] . "'>></button>";
|
echo "<button data-action='view-ticket' data-ticket-id='" . $row['ticket_id'] . "' class='quick-action-btn' title='View' aria-label='View ticket " . $row['ticket_id'] . "'>></button>";
|
||||||
echo "<button data-action='quick-status' data-ticket-id='" . $row['ticket_id'] . "' data-status='" . $row['status'] . "' class='quick-action-btn' title='Change Status' aria-label='Change status for ticket " . $row['ticket_id'] . "'>~</button>";
|
echo "<button data-action='quick-status' data-ticket-id='" . (int)$row['ticket_id'] . "' data-status='" . htmlspecialchars($row['status'], ENT_QUOTES) . "' class='quick-action-btn' title='Change Status' aria-label='Change status for ticket " . (int)$row['ticket_id'] . "'>~</button>";
|
||||||
echo "<button data-action='quick-assign' data-ticket-id='" . $row['ticket_id'] . "' class='quick-action-btn' title='Assign' aria-label='Assign ticket " . $row['ticket_id'] . "'>@</button>";
|
echo "<button data-action='quick-assign' data-ticket-id='" . $row['ticket_id'] . "' class='quick-action-btn' title='Assign' aria-label='Assign ticket " . $row['ticket_id'] . "'>@</button>";
|
||||||
echo "</div>";
|
echo "</div>";
|
||||||
echo "</td>";
|
echo "</td>";
|
||||||
@@ -496,12 +497,12 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
<span class="ts-cell" data-ts="<?php echo htmlspecialchars($row['updated_at'], ENT_QUOTES, 'UTF-8'); ?>" title="<?php echo date('Y-m-d H:i', strtotime($row['updated_at'])); ?>"><?php echo date('M j', strtotime($row['updated_at'])); ?></span>
|
<span class="ts-cell" data-ts="<?php echo htmlspecialchars($row['updated_at'], ENT_QUOTES, 'UTF-8'); ?>" title="<?php echo date('Y-m-d H:i', strtotime($row['updated_at'])); ?>"><?php echo date('M j', strtotime($row['updated_at'])); ?></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ticket-card-status <?php echo $statusClass; ?>">
|
<div class="ticket-card-status <?php echo htmlspecialchars($statusClass); ?>">
|
||||||
<?php echo $row['status']; ?>
|
<?php echo htmlspecialchars($row['status']); ?>
|
||||||
</div>
|
</div>
|
||||||
<div class="ticket-card-actions">
|
<div class="ticket-card-actions">
|
||||||
<button data-action="view-ticket" data-ticket-id="<?php echo $row['ticket_id']; ?>" title="View" aria-label="View ticket <?php echo $row['ticket_id']; ?>">></button>
|
<button data-action="view-ticket" data-ticket-id="<?php echo (int)$row['ticket_id']; ?>" title="View" aria-label="View ticket <?php echo (int)$row['ticket_id']; ?>">></button>
|
||||||
<button data-action="quick-status" data-ticket-id="<?php echo $row['ticket_id']; ?>" data-status="<?php echo $row['status']; ?>" title="Status" aria-label="Change status for ticket <?php echo $row['ticket_id']; ?>">~</button>
|
<button data-action="quick-status" data-ticket-id="<?php echo (int)$row['ticket_id']; ?>" data-status="<?php echo htmlspecialchars($row['status'], ENT_QUOTES); ?>" title="Status" aria-label="Change status for ticket <?php echo (int)$row['ticket_id']; ?>">~</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<?php
|
<?php
|
||||||
@@ -523,7 +524,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
<!-- END OUTER FRAME -->
|
<!-- END OUTER FRAME -->
|
||||||
|
|
||||||
<!-- Kanban Card View -->
|
<!-- Kanban Card View -->
|
||||||
<section id="cardView" class="card-view-container" style="display: none;" aria-label="Kanban board view">
|
<section id="cardView" class="card-view-container is-hidden" aria-label="Kanban board view">
|
||||||
<div class="kanban-board">
|
<div class="kanban-board">
|
||||||
<div class="kanban-column" data-status="Open">
|
<div class="kanban-column" data-status="Open">
|
||||||
<div class="kanban-column-header status-Open">
|
<div class="kanban-column-header status-Open">
|
||||||
@@ -991,7 +992,6 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
|
|
||||||
// Stat card click handlers for filtering
|
// Stat card click handlers for filtering
|
||||||
document.querySelectorAll('.stat-card').forEach(card => {
|
document.querySelectorAll('.stat-card').forEach(card => {
|
||||||
card.style.cursor = 'pointer';
|
|
||||||
card.addEventListener('click', function() {
|
card.addEventListener('click', function() {
|
||||||
const classList = this.classList;
|
const classList = this.classList;
|
||||||
let url = '/?';
|
let url = '/?';
|
||||||
|
|||||||
@@ -222,7 +222,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
<option value="confidential" <?php echo $currentVisibility == 'confidential' ? 'selected' : ''; ?>>Confidential</option>
|
<option value="confidential" <?php echo $currentVisibility == 'confidential' ? 'selected' : ''; ?>>Confidential</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="metadata-field" id="visibilityGroupsField" <?php echo $currentVisibility !== 'internal' ? 'style="display: none;"' : ''; ?>>
|
<div class="metadata-field<?php echo $currentVisibility !== 'internal' ? ' is-hidden' : ''; ?>" id="visibilityGroupsField">
|
||||||
<label class="metadata-label metadata-label-cyan">Allowed Groups:</label>
|
<label class="metadata-label metadata-label-cyan">Allowed Groups:</label>
|
||||||
<div class="visibility-groups-edit">
|
<div class="visibility-groups-edit">
|
||||||
<?php foreach ($allAvailableGroups as $group):
|
<?php foreach ($allAvailableGroups as $group):
|
||||||
@@ -246,14 +246,14 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
<div class="header-controls">
|
<div class="header-controls">
|
||||||
<div class="status-priority-group">
|
<div class="status-priority-group">
|
||||||
<select id="statusSelect" class="editable status-select status-<?php echo str_replace(' ', '-', strtolower($ticket["status"])); ?>" data-field="status" data-action="update-ticket-status" aria-label="Change ticket status">
|
<select id="statusSelect" class="editable status-select status-<?php echo str_replace(' ', '-', strtolower($ticket["status"])); ?>" data-field="status" data-action="update-ticket-status" aria-label="Change ticket status">
|
||||||
<option value="<?php echo $ticket['status']; ?>" selected>
|
<option value="<?php echo htmlspecialchars($ticket['status']); ?>" selected>
|
||||||
<?php echo $ticket['status']; ?> (current)
|
<?php echo htmlspecialchars($ticket['status']); ?> (current)
|
||||||
</option>
|
</option>
|
||||||
<?php foreach ($allowedTransitions as $transition): ?>
|
<?php foreach ($allowedTransitions as $transition): ?>
|
||||||
<option value="<?php echo $transition['to_status']; ?>"
|
<option value="<?php echo htmlspecialchars($transition['to_status']); ?>"
|
||||||
data-requires-comment="<?php echo $transition['requires_comment'] ? '1' : '0'; ?>"
|
data-requires-comment="<?php echo $transition['requires_comment'] ? '1' : '0'; ?>"
|
||||||
data-requires-admin="<?php echo $transition['requires_admin'] ? '1' : '0'; ?>">
|
data-requires-admin="<?php echo $transition['requires_admin'] ? '1' : '0'; ?>">
|
||||||
<?php echo $transition['to_status']; ?>
|
<?php echo htmlspecialchars($transition['to_status']); ?>
|
||||||
<?php if ($transition['requires_comment']): ?> *<?php endif; ?>
|
<?php if ($transition['requires_comment']): ?> *<?php endif; ?>
|
||||||
<?php if ($transition['requires_admin']): ?> (Admin)<?php endif; ?>
|
<?php if ($transition['requires_admin']): ?> (Admin)<?php endif; ?>
|
||||||
</option>
|
</option>
|
||||||
@@ -295,7 +295,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
<div class="ascii-subsection-header">Description</div>
|
<div class="ascii-subsection-header">Description</div>
|
||||||
<div class="detail-group full-width">
|
<div class="detail-group full-width">
|
||||||
<label>Description</label>
|
<label>Description</label>
|
||||||
<textarea class="editable" data-field="description" disabled><?php echo $ticket["description"]; ?></textarea>
|
<textarea class="editable" data-field="description" disabled><?php echo htmlspecialchars($ticket["description"] ?? ''); ?></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -326,7 +326,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
</div>
|
</div>
|
||||||
<button id="addCommentBtn" class="btn">ADD COMMENT</button>
|
<button id="addCommentBtn" class="btn">ADD COMMENT</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="markdownPreview" class="markdown-preview" style="display: none;"></div>
|
<div id="markdownPreview" class="markdown-preview is-hidden"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -430,7 +430,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
<button type="button" id="browseFilesBtn" class="btn upload-browse-btn">BROWSE FILES</button>
|
<button type="button" id="browseFilesBtn" class="btn upload-browse-btn">BROWSE FILES</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="uploadProgress" class="upload-progress" style="display: none;">
|
<div id="uploadProgress" class="upload-progress is-hidden">
|
||||||
<div class="progress-bar">
|
<div class="progress-bar">
|
||||||
<div class="progress-fill" id="progressFill"></div>
|
<div class="progress-fill" id="progressFill"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- New Key Display (hidden by default) -->
|
<!-- New Key Display (hidden by default) -->
|
||||||
<div id="newKeyDisplay" class="ascii-frame-inner key-generated-alert" style="display: none;">
|
<div id="newKeyDisplay" class="ascii-frame-inner key-generated-alert is-hidden">
|
||||||
<h3 class="admin-section-title">New API Key Generated</h3>
|
<h3 class="admin-section-title">New API Key Generated</h3>
|
||||||
<p class="text-danger text-sm mb-1">
|
<p class="text-danger text-sm mb-1">
|
||||||
Copy this key now. You won't be able to see it again!
|
Copy this key now. You won't be able to see it again!
|
||||||
@@ -195,7 +195,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
if (data.success) {
|
if (data.success) {
|
||||||
// Show the new key
|
// Show the new key
|
||||||
document.getElementById('newKeyValue').value = data.api_key;
|
document.getElementById('newKeyValue').value = data.api_key;
|
||||||
document.getElementById('newKeyDisplay').style.display = 'block';
|
document.getElementById('newKeyDisplay').classList.remove('is-hidden');
|
||||||
document.getElementById('keyName').value = '';
|
document.getElementById('keyName').value = '';
|
||||||
|
|
||||||
lt.toast.success('API key generated successfully');
|
lt.toast.success('API key generated successfully');
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
for ($i = 1; $i <= min($totalPages, 10); $i++) {
|
for ($i = 1; $i <= min($totalPages, 10); $i++) {
|
||||||
$params['page'] = $i;
|
$params['page'] = $i;
|
||||||
$activeClass = ($i == $page) ? 'active' : '';
|
$activeClass = ($i == $page) ? 'active' : '';
|
||||||
$url = '?' . http_build_query($params);
|
$url = htmlspecialchars('?' . http_build_query($params), ENT_QUOTES, 'UTF-8');
|
||||||
echo "<a href='$url' class='btn btn-small $activeClass'>$i</a> ";
|
echo "<a href='$url' class='btn btn-small $activeClass'>$i</a> ";
|
||||||
}
|
}
|
||||||
if ($totalPages > 10) {
|
if ($totalPages > 10) {
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
<option value="number">Number</option>
|
<option value="number">Number</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row" id="options_row" style="display: none;">
|
<div class="setting-row is-hidden" id="options_row">
|
||||||
<label for="field_options">Options (one per line)</label>
|
<label for="field_options">Options (one per line)</label>
|
||||||
<textarea id="field_options" name="field_options" rows="4" placeholder="Option 1 Option 2 Option 3"></textarea>
|
<textarea id="field_options" name="field_options" rows="4" placeholder="Option 1 Option 2 Option 3"></textarea>
|
||||||
</div>
|
</div>
|
||||||
@@ -208,7 +208,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
|
|
||||||
function toggleOptionsField() {
|
function toggleOptionsField() {
|
||||||
const type = document.getElementById('field_type').value;
|
const type = document.getElementById('field_type').value;
|
||||||
document.getElementById('options_row').style.display = type === 'select' ? 'block' : 'none';
|
document.getElementById('options_row').classList.toggle('is-hidden', type !== 'select');
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveField(e) {
|
function saveField(e) {
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
$schedule .= ' (Day ' . $rt['schedule_day'] . ')';
|
$schedule .= ' (Day ' . $rt['schedule_day'] . ')';
|
||||||
}
|
}
|
||||||
$schedule .= ' @ ' . substr($rt['schedule_time'], 0, 5);
|
$schedule .= ' @ ' . substr($rt['schedule_time'], 0, 5);
|
||||||
echo $schedule;
|
echo htmlspecialchars($schedule);
|
||||||
?>
|
?>
|
||||||
</td>
|
</td>
|
||||||
<td><?php echo htmlspecialchars($rt['category']); ?></td>
|
<td><?php echo htmlspecialchars($rt['category']); ?></td>
|
||||||
@@ -135,7 +135,7 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
<option value="monthly">Monthly</option>
|
<option value="monthly">Monthly</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row" id="schedule_day_row" style="display: none;">
|
<div class="setting-row is-hidden" id="schedule_day_row">
|
||||||
<label for="schedule_day">Schedule Day</label>
|
<label for="schedule_day">Schedule Day</label>
|
||||||
<select id="schedule_day" name="schedule_day"></select>
|
<select id="schedule_day" name="schedule_day"></select>
|
||||||
</div>
|
</div>
|
||||||
@@ -251,15 +251,15 @@ $nonce = SecurityHeadersMiddleware::getNonce();
|
|||||||
daySelect.innerHTML = '';
|
daySelect.innerHTML = '';
|
||||||
|
|
||||||
if (type === 'daily') {
|
if (type === 'daily') {
|
||||||
dayRow.style.display = 'none';
|
dayRow.classList.add('is-hidden');
|
||||||
} else if (type === 'weekly') {
|
} else if (type === 'weekly') {
|
||||||
dayRow.style.display = 'flex';
|
dayRow.classList.remove('is-hidden');
|
||||||
const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
||||||
days.forEach((day, i) => {
|
days.forEach((day, i) => {
|
||||||
daySelect.innerHTML += `<option value="${i + 1}">${day}</option>`;
|
daySelect.innerHTML += `<option value="${i + 1}">${day}</option>`;
|
||||||
});
|
});
|
||||||
} else if (type === 'monthly') {
|
} else if (type === 'monthly') {
|
||||||
dayRow.style.display = 'flex';
|
dayRow.classList.remove('is-hidden');
|
||||||
for (let i = 1; i <= 28; i++) {
|
for (let i = 1; i <= 28; i++) {
|
||||||
daySelect.innerHTML += `<option value="${i}">Day ${i}</option>`;
|
daySelect.innerHTML += `<option value="${i}">Day ${i}</option>`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user