diff --git a/api/manage_workflows.php b/api/manage_workflows.php index 628b5ab..de27db5 100644 --- a/api/manage_workflows.php +++ b/api/manage_workflows.php @@ -79,6 +79,12 @@ try { case 'POST': $data = json_decode(file_get_contents('php://input'), true); + if (($data['from_status'] ?? '') === ($data['to_status'] ?? '')) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'From Status and To Status cannot be the same']); + exit; + } + $stmt = $conn->prepare("INSERT INTO status_transitions (from_status, to_status, requires_comment, requires_admin, is_active) VALUES (?, ?, ?, ?, ?)"); $wf_from = $data['from_status']; @@ -116,6 +122,12 @@ try { $data = json_decode(file_get_contents('php://input'), true); + if (($data['from_status'] ?? '') === ($data['to_status'] ?? '')) { + http_response_code(400); + echo json_encode(['success' => false, 'error' => 'From Status and To Status cannot be the same']); + exit; + } + $stmt = $conn->prepare("UPDATE status_transitions SET from_status = ?, to_status = ?, requires_comment = ?, requires_admin = ?, is_active = ? WHERE transition_id = ?"); diff --git a/assets/css/ticket.css b/assets/css/ticket.css index 82b67d0..b34b055 100644 --- a/assets/css/ticket.css +++ b/assets/css/ticket.css @@ -365,3 +365,7 @@ kbd { /* Metadata selects use .lt-display-field (base.css) in read mode instead of disabled — full opacity, non-interactive, no fading. */ + + +/* Skeleton placeholder for comment lazy-load */ +.comment-skeleton { margin-bottom: 0.75rem; } diff --git a/models/RecurringTicketModel.php b/models/RecurringTicketModel.php index 3d2b77d..8d0f3f8 100644 --- a/models/RecurringTicketModel.php +++ b/models/RecurringTicketModel.php @@ -181,10 +181,13 @@ class RecurringTicketModel { break; case 'monthly': - $day = max(1, min(28, $scheduleDay)); // Limit to 28 for safety + $day = max(1, min(31, $scheduleDay)); $next = new DateTime(); $next->modify('first day of next month'); - $next->setDate($next->format('Y'), $next->format('m'), $day); + // Clamp to the last day of the target month (handles Feb, 30-day months, etc.) + $daysInMonth = (int)$next->format('t'); + $day = min($day, $daysInMonth); + $next->setDate((int)$next->format('Y'), (int)$next->format('m'), $day); $next->setTime($time->format('H'), $time->format('i'), 0); break; diff --git a/views/TicketView.php b/views/TicketView.php index 21cba8e..37589bf 100644 --- a/views/TicketView.php +++ b/views/TicketView.php @@ -1141,10 +1141,33 @@ document.addEventListener('DOMContentLoaded', function () { loadMoreBtn.disabled = true; loadMoreBtn.textContent = 'Loading\u2026'; + // Insert skeleton placeholders while fetching + var list = document.getElementById('commentsList'); + var wrap = document.getElementById('loadMoreComments'); + var skeletons = []; + for (var s = 0; s < 3; s++) { + var sk = document.createElement('div'); + sk.className = 'lt-skeleton-card comment-skeleton'; + sk.setAttribute('aria-hidden', 'true'); + sk.innerHTML = + '