feat: add job maintenance dashboard and automated stale job expiration system
This commit is contained in:
@@ -18,6 +18,7 @@ const jobsTabHTML = `{{ define "jobsTab" }}
|
||||
<option value="not-qualified">❌ Not Qualified</option>
|
||||
<option value="saved">💾 Saved / In Pipeline</option>
|
||||
<option value="all">🌐 All Jobs</option>
|
||||
<option value="expired">⚠️ Expired / Closed</option>
|
||||
<option value="opted-out">🚫 Opted-out</option>
|
||||
</select>
|
||||
<button hx-get="/jobs" hx-target="#jobs-container" hx-swap="innerHTML"
|
||||
@@ -45,7 +46,7 @@ const jobListHTML = `{{ define "jobs" }}
|
||||
<th class="px-4 py-3" data-sort="status">Analysis</th>
|
||||
<th class="px-4 py-3" data-sort="score">Score & Qualification</th>
|
||||
<th class="px-4 py-3">Reason / Tags</th>
|
||||
<th class="px-4 py-3" data-sort="date">Crawled</th>
|
||||
<th class="px-4 py-3" data-sort="date">Last Updated</th>
|
||||
<th class="px-4 py-3 text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -54,7 +55,7 @@ const jobListHTML = `{{ define "jobs" }}
|
||||
{{- $qual := "pending" -}}
|
||||
{{- if .Analysis }}{{ if .Analysis.IsQualified }}{{ $qual = "qualified" }}{{ else }}{{ $qual = "not-qualified" }}{{ end }}{{ end -}}
|
||||
{{- $appClass := "none" -}}
|
||||
{{- if .App }}{{ if eq .App.Status "OPTED_OUT" }}{{ $appClass = "opted-out" }}{{ else }}{{ $appClass = "pipeline" }}{{ end }}{{ end -}}
|
||||
{{- if .App }}{{ if eq .App.Status "OPTED_OUT" }}{{ $appClass = "opted-out" }}{{ else if eq .App.Status "EXPIRED" }}{{ $appClass = "expired" }}{{ else }}{{ $appClass = "pipeline" }}{{ end }}{{ end -}}
|
||||
<tr class="hover:bg-slate-800/40 transition-colors group" data-row data-qual="{{ $qual }}" data-app="{{ $appClass }}">
|
||||
|
||||
<!-- Title & Company -->
|
||||
@@ -117,9 +118,13 @@ const jobListHTML = `{{ define "jobs" }}
|
||||
|
||||
<!-- Reason / Matched Tags -->
|
||||
<td class="px-4 py-3 max-w-[180px]">
|
||||
{{ if .App }}{{ if eq .App.Status "OPTED_OUT" }}
|
||||
<span class="text-[10px] text-slate-500 italic">Opted out</span>
|
||||
{{ end }}{{ end }}
|
||||
{{ if .App }}
|
||||
{{ if eq .App.Status "OPTED_OUT" }}
|
||||
<span class="text-[10px] text-slate-500 italic block">Opted out</span>
|
||||
{{ else if eq .App.Status "EXPIRED" }}
|
||||
<span class="text-[10px] text-amber-400/80 italic block">⚠️ No longer active</span>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if .Analysis }}
|
||||
{{ if .Analysis.IsQualified }}
|
||||
<div class="flex flex-wrap gap-1">
|
||||
@@ -139,8 +144,8 @@ const jobListHTML = `{{ define "jobs" }}
|
||||
</td>
|
||||
|
||||
<!-- Date -->
|
||||
<td class="px-4 py-3 text-slate-400 text-[11px]" data-sort-val="{{ .CrawledAt.Unix }}">
|
||||
{{ formatDate .CrawledAt }}
|
||||
<td class="px-4 py-3 text-slate-400 text-[11px]" data-sort-val="{{ .LastSeenAt.Unix }}">
|
||||
{{ formatDate .LastSeenAt }}
|
||||
</td>
|
||||
|
||||
<!-- Actions: Save + Opt-out OR status badge -->
|
||||
@@ -148,14 +153,32 @@ const jobListHTML = `{{ define "jobs" }}
|
||||
{{ if .App }}
|
||||
{{ if eq .App.Status "OPTED_OUT" }}
|
||||
<!-- Opted out: show undo button -->
|
||||
<button hx-post="/applications"
|
||||
hx-vals='{"raw_job_id": {{ .ID }}, "status": "SAVED"}'
|
||||
hx-target="#jobs-container"
|
||||
hx-swap="innerHTML"
|
||||
title="Undo opt-out"
|
||||
class="px-2.5 py-1 rounded bg-slate-800 hover:bg-slate-700 text-slate-400 border border-slate-700 text-[10px] transition-colors">
|
||||
↩ Undo
|
||||
</button>
|
||||
<div class="flex items-center justify-end gap-1.5">
|
||||
<span class="text-[10px] text-slate-500">Opted-out</span>
|
||||
<button hx-post="/applications"
|
||||
hx-vals='{"raw_job_id": {{ .ID }}, "status": "SAVED"}'
|
||||
hx-target="#jobs-container"
|
||||
hx-swap="innerHTML"
|
||||
title="Undo opt-out"
|
||||
class="px-2 py-0.5 rounded bg-slate-800 hover:bg-slate-700 text-slate-300 border border-slate-700 text-[10px] transition-colors">
|
||||
↩
|
||||
</button>
|
||||
</div>
|
||||
{{ else if eq .App.Status "EXPIRED" }}
|
||||
<!-- Expired -->
|
||||
<div class="flex items-center justify-end gap-1.5">
|
||||
<span class="px-2 py-0.5 rounded bg-slate-800 text-slate-400 text-[10px] font-bold border border-slate-700">
|
||||
⚠️ EXPIRED
|
||||
</span>
|
||||
<button hx-post="/applications"
|
||||
hx-vals='{"raw_job_id": {{ .ID }}, "status": "SAVED"}'
|
||||
hx-target="#jobs-container"
|
||||
hx-swap="innerHTML"
|
||||
title="Re-open / Save"
|
||||
class="px-2 py-0.5 rounded bg-slate-800 hover:bg-slate-700 text-slate-300 border border-slate-700 text-[10px] transition-colors">
|
||||
↩
|
||||
</button>
|
||||
</div>
|
||||
{{ else }}
|
||||
<!-- In pipeline -->
|
||||
<span class="px-2 py-1 rounded bg-violet-500/20 text-violet-300 text-[10px] font-bold border border-violet-500/30">
|
||||
@@ -203,7 +226,7 @@ const jobDetailHTML = `{{ define "jobDetail" }}
|
||||
<div class="flex-1 min-w-0 pr-4">
|
||||
<span class="text-xs font-bold text-indigo-400 uppercase tracking-wide">{{ .Company }}</span>
|
||||
<h2 class="text-xl font-extrabold text-white mt-0.5 leading-tight">{{ .Title }}</h2>
|
||||
<p class="text-xs text-slate-400 mt-1">📍 {{ .Location }} • Ingested {{ formatDate .CrawledAt }}</p>
|
||||
<p class="text-xs text-slate-400 mt-1">📍 {{ .Location }} • Last Updated {{ formatDate .LastSeenAt }}</p>
|
||||
</div>
|
||||
<button onclick="closeDetailModal()" class="flex-shrink-0 text-slate-400 hover:text-white text-lg p-1 rounded hover:bg-slate-800 transition-colors">✕</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user