|
45 | 45 | .strip-spacer{flex:1;} |
46 | 46 | .btn-fill-url{display:flex;align-items:center;gap:6px;padding:7px 13px;background:var(--bg-subtle);color:var(--text-secondary);border:1px solid var(--border);border-radius:var(--radius);font-family:'Inter',sans-serif;font-size:12.5px;font-weight:600;cursor:pointer;transition:all var(--transition);margin-right:6px;} |
47 | 47 | .btn-fill-url:hover{background:var(--bg-card);color:var(--accent);border-color:var(--accent-mid);} |
| 48 | +.btn-reject-stale{display:none;align-items:center;gap:6px;padding:7px 13px;background:var(--red-light);color:var(--red);border:1px solid var(--red-mid);border-radius:var(--radius);font-family:'Inter',sans-serif;font-size:12.5px;font-weight:600;cursor:pointer;transition:all var(--transition);margin-right:6px;} |
| 49 | +.btn-reject-stale:hover{background:var(--red-mid);color:var(--red);border-color:var(--red);} |
| 50 | +.btn-reject-stale.visible{display:flex;} |
48 | 51 | .btn-pipeline{display:flex;align-items:center;gap:8px;padding:8px 16px;background:var(--accent);color:#fff;border:none;border-radius:var(--radius);font-family:'Inter',sans-serif;font-size:13px;font-weight:600;cursor:pointer;transition:all var(--transition);box-shadow:var(--shadow-sm);} |
49 | 52 | .btn-pipeline:hover{background:#2563eb;box-shadow:0 0 0 3px rgba(59,130,246,.25);} |
50 | 53 | .btn-pipeline svg{width:14px;height:14px;} |
|
286 | 289 | <span>Expired</span><span class="count">—</span> |
287 | 290 | </div> |
288 | 291 | <div class="strip-spacer"></div> |
| 292 | + <button class="btn-reject-stale" id="rejectStaleBtn" onclick="openRejectStaleModal()" title="Reject all jobs in this tab older than 2 weeks"> |
| 293 | + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" width="13" height="13"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg> |
| 294 | + Reject Stale |
| 295 | + </button> |
289 | 296 | <button class="btn-fill-url" onclick="openFillUrlModal()" title="Fill a known application form URL directly"> |
290 | 297 | <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" width="13" height="13"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg> |
291 | 298 | Fill URL |
|
546 | 553 | </div> |
547 | 554 | </div> |
548 | 555 |
|
| 556 | +<!-- Reject Stale modal --> |
| 557 | +<div class="modal-overlay" id="rejectStaleOverlay" onclick="closeRejectStaleModal()" style="display:none"></div> |
| 558 | +<div class="modal-box" id="rejectStaleModal" style="display:none"> |
| 559 | + <div class="modal-header"> |
| 560 | + <span class="modal-title">Reject Stale Jobs</span> |
| 561 | + <button class="btn-close" onclick="closeRejectStaleModal()">×</button> |
| 562 | + </div> |
| 563 | + <div class="modal-body"> |
| 564 | + <p class="modal-hint" id="rejectStaleMsg"></p> |
| 565 | + </div> |
| 566 | + <div class="modal-footer"> |
| 567 | + <button class="btn-modal-cancel" onclick="closeRejectStaleModal()">Cancel</button> |
| 568 | + <button class="btn-modal-fill" style="background:var(--red);border:none;" id="rejectStaleConfirmBtn" onclick="confirmRejectStale()">Reject All</button> |
| 569 | + </div> |
| 570 | +</div> |
| 571 | + |
549 | 572 | <script> |
550 | 573 | // ── State ────────────────────────────────────────────────────────────────── |
551 | 574 | let jobs = []; |
|
625 | 648 | document.querySelectorAll('.status-pill').forEach(p => p.classList.remove('active')); |
626 | 649 | if (el) el.classList.add('active'); |
627 | 650 | loadJobs(status); |
| 651 | + const staleBtn = document.getElementById('rejectStaleBtn'); |
| 652 | + if (staleBtn) staleBtn.classList.toggle('visible', status === 'review' || status === 'shortlisted'); |
| 653 | +} |
| 654 | + |
| 655 | +function openRejectStaleModal() { |
| 656 | + const cutoff = Date.now() - 14 * 24 * 60 * 60 * 1000; |
| 657 | + const staleCount = jobs.filter(j => j.created_at && new Date(j.created_at).getTime() < cutoff).length; |
| 658 | + const label = currentStatus === 'shortlisted' ? 'Shortlisted' : 'Review'; |
| 659 | + document.getElementById('rejectStaleMsg').textContent = |
| 660 | + staleCount > 0 |
| 661 | + ? `Permanently reject ${staleCount} ${label} job${staleCount === 1 ? '' : 's'} added more than 14 days ago?` |
| 662 | + : `No jobs older than 14 days found in the ${label} queue.`; |
| 663 | + document.getElementById('rejectStaleConfirmBtn').style.display = staleCount > 0 ? '' : 'none'; |
| 664 | + document.getElementById('rejectStaleOverlay').style.display = ''; |
| 665 | + document.getElementById('rejectStaleModal').style.display = ''; |
| 666 | +} |
| 667 | + |
| 668 | +function closeRejectStaleModal() { |
| 669 | + document.getElementById('rejectStaleOverlay').style.display = 'none'; |
| 670 | + document.getElementById('rejectStaleModal').style.display = 'none'; |
| 671 | +} |
| 672 | + |
| 673 | +async function confirmRejectStale() { |
| 674 | + closeRejectStaleModal(); |
| 675 | + try { |
| 676 | + const res = await api('/api/jobs/bulk-reject-stale', { |
| 677 | + method: 'POST', |
| 678 | + headers: {'Content-Type': 'application/json'}, |
| 679 | + body: JSON.stringify({status: currentStatus, older_than_days: 14}) |
| 680 | + }); |
| 681 | + showToast(`Rejected ${res.rejected} stale job${res.rejected === 1 ? '' : 's'}`, ''); |
| 682 | + loadStats(); |
| 683 | + loadJobs(currentStatus); |
| 684 | + } catch(e) { |
| 685 | + showToast('Bulk reject failed', 'error'); |
| 686 | + } |
628 | 687 | } |
629 | 688 |
|
630 | 689 | // ── Card render ──────────────────────────────────────────────────────────── |
|
0 commit comments