Applies the full design spec from circuitforge-plans/peregrine/2026-03-03-nuxt-design-system.md: CSS tokens: - Falcon Blue (#2B6CB0 / #68A8D8 dark) — was incorrectly using forest green - Talon Orange (#E06820 / #F6872A dark) with --app-accent-text dark navy (never white) - Full pipeline status token set (--status-pending/approve/reject/applied/synced/...) - Match score tokens, motion tokens, type scale tokens - Dark mode + hacker mode overrides AppNav: sidebar layout (replaces top bar) - Desktop ≥1024px: persistent sidebar with brand, links, hacker-exit, settings footer - Mobile <1024px: bottom tab bar with 5 primary destinations - Click-the-bird easter egg (9.6): 5 rapid clicks → ruffle animation - Heroicons via @heroicons/vue/24/outline App.vue: - Skip-to-content link (a11y) - Sidebar margin-left layout (desktop) / tab bar clearance (mobile) HomeView: full dashboard implementation - Pipeline metric cards (Pending/Approved/Applied/Synced/Rejected) with status colors - Primary workflow buttons (Run Discovery, Sync Emails, Score Unscored) + sync banner - Auto-enrichment status row - Backlog management (conditionally visible) - Add Jobs by URL / CSV upload tabs - Advanced/danger zone in collapsible <details> - Stoop speed toast easter egg (9.2) - Midnight mode greeting easter egg (9.7) WorkflowButton component with loading spinner, proper touch targets (min-height 44px) Pinia jobs store (setup form) with counts + system status Build: clean 2.28s, 0 errors
50 lines
1.5 KiB
TypeScript
50 lines
1.5 KiB
TypeScript
import { defineStore } from 'pinia'
|
|
import { ref, computed } from 'vue'
|
|
import { useApiFetch } from '../composables/useApi'
|
|
|
|
export interface JobCounts {
|
|
pending: number
|
|
approved: number
|
|
applied: number
|
|
synced: number
|
|
rejected: number
|
|
total: number
|
|
}
|
|
|
|
export interface SystemStatus {
|
|
enrichment_enabled: boolean
|
|
enrichment_last_run: string | null
|
|
enrichment_next_run: string | null
|
|
tasks_running: number
|
|
integration_name: string | null // e.g. "Notion", "Airtable"
|
|
integration_unsynced: number
|
|
}
|
|
|
|
// Pinia setup store — function form, not options form (gotcha #10)
|
|
export const useJobsStore = defineStore('jobs', () => {
|
|
const counts = ref<JobCounts | null>(null)
|
|
const status = ref<SystemStatus | null>(null)
|
|
const loading = ref(false)
|
|
const error = ref<string | null>(null)
|
|
|
|
const hasPending = computed(() => (counts.value?.pending ?? 0) > 0)
|
|
|
|
async function fetchCounts() {
|
|
loading.value = true
|
|
const { data, error: err } = await useApiFetch<JobCounts>('/api/jobs/counts')
|
|
loading.value = false
|
|
if (err) { error.value = err.kind === 'network' ? 'Network error' : `Error ${err.status}`; return }
|
|
counts.value = data
|
|
}
|
|
|
|
async function fetchStatus() {
|
|
const { data } = await useApiFetch<SystemStatus>('/api/system/status')
|
|
if (data) status.value = data
|
|
}
|
|
|
|
async function refresh() {
|
|
await Promise.all([fetchCounts(), fetchStatus()])
|
|
}
|
|
|
|
return { counts, status, loading, error, hasPending, fetchCounts, fetchStatus, refresh }
|
|
})
|