peregrine/web/src/stores/jobs.ts
pyr0ball 8f1ad9176b feat(web): implement design spec — peregrine.css, sidebar nav, HomeView
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
2026-03-17 22:00:42 -07:00

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 }
})