feat: implement task queue statistics and add UI templates for job management and visualization.
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
package ui
|
||||
|
||||
const applyTabHTML = `{{ define "applyTab" }}
|
||||
<div class="space-y-4">
|
||||
<!-- Filter Bar -->
|
||||
<div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
|
||||
<div>
|
||||
<h2 class="text-base font-bold text-white">Application Pipeline</h2>
|
||||
<p class="text-xs text-slate-400 mt-0.5">Track all saved and in-progress applications</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<input id="apply-filter-text" type="search" placeholder="Search title, company…"
|
||||
class="px-3 py-1.5 text-xs rounded-lg bg-slate-800 border border-slate-700 text-white focus:outline-none focus:border-indigo-500 w-44 placeholder:text-slate-500">
|
||||
<select id="apply-filter-status"
|
||||
class="px-3 py-1.5 text-xs rounded-lg bg-slate-800 border border-slate-700 text-white focus:outline-none focus:border-indigo-500">
|
||||
<option value="active">Active (Saved + In Progress)</option>
|
||||
<option value="SAVED">💾 Saved</option>
|
||||
<option value="APPLIED">🚀 Applied</option>
|
||||
<option value="INTERVIEW">💬 Interview</option>
|
||||
<option value="OFFER">🎉 Offer</option>
|
||||
<option value="REJECTED">❌ Rejected</option>
|
||||
<option value="all">All</option>
|
||||
</select>
|
||||
<button hx-get="/applications" hx-target="#apply-container" hx-swap="innerHTML"
|
||||
class="px-3 py-1.5 text-xs font-semibold text-indigo-400 hover:text-indigo-300 bg-slate-800 border border-slate-700 rounded-lg transition-colors">
|
||||
↺ Refresh
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Apply Table Container -->
|
||||
<div id="apply-container"
|
||||
hx-get="/applications" hx-trigger="refresh" hx-swap="innerHTML">
|
||||
{{ template "applyTable" .Applications }}
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}`
|
||||
|
||||
const applyTableHTML = `{{ define "applyTable" }}
|
||||
<div class="overflow-x-auto rounded-xl border border-slate-800">
|
||||
<table id="apply-table" class="w-full text-left text-xs text-slate-300">
|
||||
<thead class="bg-slate-900/80 text-slate-400 uppercase font-semibold text-[10px]">
|
||||
<tr>
|
||||
<th class="px-4 py-3" data-sort="title">Job & Company</th>
|
||||
<th class="px-4 py-3">Pipeline Stage</th>
|
||||
<th class="px-4 py-3" data-sort="score">Score</th>
|
||||
<th class="px-4 py-3 text-center">Link</th>
|
||||
<th class="px-4 py-3" data-sort="updated">Last Updated</th>
|
||||
<th class="px-4 py-3 text-right">Change Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-slate-800 bg-slate-900/40">
|
||||
{{ range . }}
|
||||
{{ if .App }}
|
||||
<tr class="hover:bg-slate-800/40 transition-colors group" data-row data-app-status="{{ .App.Status }}">
|
||||
|
||||
<!-- Title & Company -->
|
||||
<td class="px-4 py-3" data-sort-val="{{ .Title }}">
|
||||
<button hx-get="/jobs/{{ .ID }}"
|
||||
hx-target="#detail-modal-body"
|
||||
hx-swap="innerHTML"
|
||||
onclick="openDetailModal()"
|
||||
class="text-left font-bold text-white group-hover:text-indigo-300 hover:underline block truncate max-w-[200px]">
|
||||
{{ .Title }}
|
||||
</button>
|
||||
<span class="text-[11px] text-indigo-400 font-semibold">{{ .Company }}</span>
|
||||
<span class="text-[11px] text-slate-500 ml-1">📍 {{ .Location }}</span>
|
||||
</td>
|
||||
|
||||
<!-- Pipeline Stage (arrow indicator) -->
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex items-center gap-0.5 text-[10px] font-semibold">
|
||||
<span class="px-2 py-0.5 rounded-l-full {{ if eq .App.Status "SAVED" }}bg-slate-500 text-white{{ else }}bg-slate-800/60 text-slate-600{{ end }}">Saved</span>
|
||||
<span class="text-slate-700">▶</span>
|
||||
<span class="px-2 py-0.5 {{ if eq .App.Status "APPLIED" }}bg-indigo-600 text-white{{ else }}bg-slate-800/60 text-slate-600{{ end }}">Applied</span>
|
||||
<span class="text-slate-700">▶</span>
|
||||
<span class="px-2 py-0.5 {{ if eq .App.Status "INTERVIEW" }}bg-amber-500 text-white{{ else }}bg-slate-800/60 text-slate-600{{ end }}">Interview</span>
|
||||
<span class="text-slate-700">▶</span>
|
||||
{{ if eq .App.Status "OFFER" }}
|
||||
<span class="px-2 py-0.5 rounded-r-full bg-emerald-500 text-white">Offer 🎉</span>
|
||||
{{ else if eq .App.Status "REJECTED" }}
|
||||
<span class="px-2 py-0.5 rounded-r-full bg-rose-500 text-white">Rejected</span>
|
||||
{{ else }}
|
||||
<span class="px-2 py-0.5 rounded-r-full bg-slate-800/60 text-slate-600">Offer</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<!-- Score -->
|
||||
<td class="px-4 py-3" data-sort-val="{{ if .Analysis }}{{ printf "%.0f" .Analysis.Score }}{{ else }}-1{{ end }}">
|
||||
{{ if .Analysis }}
|
||||
<span class="font-bold {{ if .Analysis.IsQualified }}text-emerald-400{{ else }}text-slate-500{{ end }}">
|
||||
{{ printf "%.0f" .Analysis.Score }}
|
||||
</span>
|
||||
{{ else }}
|
||||
<span class="text-slate-600">–</span>
|
||||
{{ end }}
|
||||
</td>
|
||||
|
||||
<!-- Link -->
|
||||
<td class="px-4 py-3 text-center">
|
||||
<a href="{{ .ApplyURL }}" target="_blank"
|
||||
class="inline-flex items-center justify-center w-7 h-7 rounded bg-slate-800 hover:bg-slate-700 text-indigo-400 border border-slate-700 transition-colors">
|
||||
🔗
|
||||
</a>
|
||||
</td>
|
||||
|
||||
<!-- Last Updated -->
|
||||
<td class="px-4 py-3 text-slate-400 text-[11px]" data-sort-val="{{ .App.UpdatedAt.Unix }}">
|
||||
{{ formatDate .App.UpdatedAt }}
|
||||
</td>
|
||||
|
||||
<!-- Change Status -->
|
||||
<td class="px-4 py-3 text-right">
|
||||
<select hx-patch="/applications/{{ .App.ID }}/status"
|
||||
hx-target="#apply-container"
|
||||
hx-swap="innerHTML"
|
||||
class="px-2 py-1 bg-slate-900 border border-slate-700 rounded-lg text-slate-200 text-[10px] focus:outline-none focus:border-indigo-500">
|
||||
<option value="SAVED" {{ if eq .App.Status "SAVED" }}selected{{ end }}>💾 Saved</option>
|
||||
<option value="APPLIED" {{ if eq .App.Status "APPLIED" }}selected{{ end }}>🚀 Applied</option>
|
||||
<option value="INTERVIEW" {{ if eq .App.Status "INTERVIEW" }}selected{{ end }}>💬 Interview</option>
|
||||
<option value="OFFER" {{ if eq .App.Status "OFFER" }}selected{{ end }}>🎉 Offer</option>
|
||||
<option value="REJECTED" {{ if eq .App.Status "REJECTED" }}selected{{ end }}>❌ Rejected</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ else }}
|
||||
<tr>
|
||||
<td colspan="6" class="px-4 py-12 text-center text-slate-500">
|
||||
<p class="text-lg mb-2">📋</p>
|
||||
<p>No saved applications yet. Go to <strong>Jobs</strong> tab and click <strong>+ Save</strong> on any job.</p>
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{ end }}`
|
||||
Reference in New Issue
Block a user