From b220f68c01863180443a431c84fb1ecd233e7c51 Mon Sep 17 00:00:00 2001 From: Michael Wu Date: Sat, 23 May 2026 21:37:45 +0800 Subject: [PATCH] Improve gig activity and historical status handling --- .env.example | 1 + .../src/dashboard-utils.test.ts | 5 + apps/admin_dashboard/src/dashboard-utils.ts | 1 + apps/admin_dashboard/src/main.tsx | 24 +- apps/api/src/five08/backend/api.py | 4 + .../static/dashboard/.vite/manifest.json | 4 +- ...{index-0wkHV6At.css => index-D2CiH-sJ.css} | 2 +- .../static/dashboard/assets/index-DAy1pv-J.js | 9 - .../static/dashboard/assets/index-DT8AhUVi.js | 9 + .../backend/static/dashboard/index.html | 4 +- .../src/five08/discord_bot/cogs/jobs.py | 38 +++- docs/configuration.md | 1 + docs/discord-gig-dashboard.md | 8 + packages/shared/src/five08/engagements.py | 23 +- packages/shared/src/five08/settings.py | 1 + tests/unit/test_backend_api.py | 43 ++++ tests/unit/test_engagements.py | 207 ++++++++++++++++++ tests/unit/test_jobs.py | 57 +++++ 18 files changed, 417 insertions(+), 24 deletions(-) rename apps/api/src/five08/backend/static/dashboard/assets/{index-0wkHV6At.css => index-D2CiH-sJ.css} (81%) delete mode 100644 apps/api/src/five08/backend/static/dashboard/assets/index-DAy1pv-J.js create mode 100644 apps/api/src/five08/backend/static/dashboard/assets/index-DT8AhUVi.js diff --git a/.env.example b/.env.example index 1dcedefb..9384cf49 100644 --- a/.env.example +++ b/.env.example @@ -36,6 +36,7 @@ JOB_RETRY_MAX_SECONDS=300 JOB_TIMEOUT_SECONDS=600 JOB_RESULT_TTL_SECONDS=3600 GIG_RECRUITING_STALE_DAYS=7 +GIG_RECRUITING_REMINDER_MAX_AGE_DAYS=90 # Internal transfer storage (optional defaults for host-run app services) MINIO_ENDPOINT=http://127.0.0.1:9000 diff --git a/apps/admin_dashboard/src/dashboard-utils.test.ts b/apps/admin_dashboard/src/dashboard-utils.test.ts index f8cc09ee..884773fa 100644 --- a/apps/admin_dashboard/src/dashboard-utils.test.ts +++ b/apps/admin_dashboard/src/dashboard-utils.test.ts @@ -3,6 +3,7 @@ import { describe, expect, it } from "vitest" import { daysSince, displayOnboarder, + formatDate, githubUrl, labelForOnboardingState, linkedinUrl, @@ -45,4 +46,8 @@ describe("dashboard utility helpers", () => { expect(daysSince("2026-05-17T00:00:00Z", now)).toBe(0) expect(daysSince("not a date", now)).toBeNull() }) + + it("includes the year in formatted timestamps", () => { + expect(formatDate("2026-01-27T02:26:00Z")).toContain("2026") + }) }) diff --git a/apps/admin_dashboard/src/dashboard-utils.ts b/apps/admin_dashboard/src/dashboard-utils.ts index c9a7715c..038f57b3 100644 --- a/apps/admin_dashboard/src/dashboard-utils.ts +++ b/apps/admin_dashboard/src/dashboard-utils.ts @@ -30,6 +30,7 @@ export function formatDate(value?: string | null) { const date = new Date(value) if (Number.isNaN(date.getTime())) return value return date.toLocaleString(undefined, { + year: "numeric", month: "short", day: "numeric", hour: "2-digit", diff --git a/apps/admin_dashboard/src/main.tsx b/apps/admin_dashboard/src/main.tsx index 74cc06b0..60c2ed3c 100644 --- a/apps/admin_dashboard/src/main.tsx +++ b/apps/admin_dashboard/src/main.tsx @@ -786,6 +786,7 @@ function App() { const [status, setStatus] = useState("") const [jobType, setJobType] = useState("") const [gigStatus, setGigStatus] = useState("") + const [gigIncludeHistorical, setGigIncludeHistorical] = useState(false) const [gigLimit, setGigLimit] = useState(100) const [projectQuery, setProjectQuery] = useState("") const [projectStatus, setProjectStatus] = useState(initialProjectDetailId ? "" : "Open") @@ -934,6 +935,7 @@ function App() { function gigsUrl() { const params = new URLSearchParams({ limit: String(gigLimit) }) if (gigStatus) params.set("status", gigStatus) + if (gigIncludeHistorical) params.set("include_historical", "true") return `/dashboard/api/gigs?${params.toString()}` } @@ -1793,10 +1795,10 @@ function App() { if (view === "jobs" && permissions.length > 0) void loadJobs() }, [minutes, status]) - // biome-ignore lint/correctness/useExhaustiveDependencies: gigs reload intentionally follows status filter changes only while gigs is active. + // biome-ignore lint/correctness/useExhaustiveDependencies: gigs reload intentionally follows list filter changes only while gigs is active. useEffect(() => { if (view === "gigs" && permissions.length > 0) void loadGigs() - }, [gigStatus, gigLimit]) + }, [gigStatus, gigIncludeHistorical, gigLimit]) // biome-ignore lint/correctness/useExhaustiveDependencies: projects reload intentionally follows status changes only while projects is active. useEffect(() => { @@ -2094,12 +2096,15 @@ function App() { sort={sort.gigs} loading={loading} status={gigStatus} + includeHistorical={gigIncludeHistorical} limit={gigLimit} staleDays={staleRecruitingDays} canWrite={can("gigs:write")} + canIncludeHistorical={can("people:read")} crmContactUrl={crmContactUrl} crmAttachmentUrl={crmAttachmentUrl} setStatus={setGigStatus} + setIncludeHistorical={setGigIncludeHistorical} setLimit={setGigLimit} onRefresh={refreshGigsView} onSort={(key) => handleSort("gigs", key)} @@ -4417,12 +4422,15 @@ function GigsView(props: { sort: { key: string; direction: SortDirection } loading: Record status: string + includeHistorical: boolean limit: number staleDays: number canWrite: boolean + canIncludeHistorical: boolean crmContactUrl: (contactId?: string) => string crmAttachmentUrl: (attachmentId?: string) => string setStatus: (value: string) => void + setIncludeHistorical: (value: boolean) => void setLimit: (value: number) => void onRefresh: () => void onSort: (key: string) => void @@ -4442,7 +4450,7 @@ function GigsView(props: { { total: 0, applications: 0, interested: 0, stale: 0 }, ) const filterBar = ( - + + ) : null}