diff --git a/orchestrator/src/client/pages/SettingsPage.tsx b/orchestrator/src/client/pages/SettingsPage.tsx index 858ff9a..51ad272 100644 --- a/orchestrator/src/client/pages/SettingsPage.tsx +++ b/orchestrator/src/client/pages/SettingsPage.tsx @@ -30,9 +30,22 @@ import { Checkbox } from "@/components/ui/checkbox" import { Input } from "@/components/ui/input" import { Separator } from "@/components/ui/separator" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" -import type { AppSettings, ResumeProjectsSettings } from "../../shared/types" +import type { AppSettings, JobStatus, ResumeProjectsSettings } from "../../shared/types" import * as api from "../api" +/** All available job statuses for clearing */ +const ALL_JOB_STATUSES: JobStatus[] = ['discovered', 'processing', 'ready', 'applied', 'skipped', 'expired'] + +/** Status descriptions for UI */ +const STATUS_DESCRIPTIONS: Record = { + discovered: 'Crawled but not processed', + processing: 'Currently generating resume', + ready: 'PDF generated, waiting for user to apply', + applied: 'User marked as applied', + skipped: 'User skipped this job', + expired: 'Deadline passed', +} + function arraysEqual(a: string[], b: string[]) { if (a.length !== b.length) return false for (let i = 0; i < a.length; i++) { @@ -74,6 +87,7 @@ export const SettingsPage: React.FC = () => { const [jobspyLinkedinFetchDescriptionDraft, setJobspyLinkedinFetchDescriptionDraft] = useState(null) const [isSaving, setIsSaving] = useState(false) const [isLoading, setIsLoading] = useState(true) + const [statusesToClear, setStatusesToClear] = useState(['discovered']) useEffect(() => { let isMounted = true @@ -297,18 +311,48 @@ export const SettingsPage: React.FC = () => { } } - const handleClearDiscovered = async () => { + const handleClearByStatuses = async () => { + if (statusesToClear.length === 0) { + toast.error("No statuses selected") + return + } try { setIsSaving(true) - const result = await api.deleteJobsByStatus("discovered") - toast.success("Discovered jobs cleared", { description: `Deleted ${result.count} jobs.` }) + let totalDeleted = 0 + const results: string[] = [] + + for (const status of statusesToClear) { + const result = await api.deleteJobsByStatus(status) + totalDeleted += result.count + if (result.count > 0) { + results.push(`${result.count} ${status}`) + } + } + + if (totalDeleted > 0) { + toast.success("Jobs cleared", { + description: `Deleted ${totalDeleted} jobs: ${results.join(', ')}` + }) + } else { + toast.info("No jobs found", { + description: `No jobs with selected statuses found` + }) + } } catch (error) { - const message = error instanceof Error ? error.message : "Failed to clear discovered jobs" + const message = error instanceof Error ? error.message : "Failed to clear jobs" toast.error(message) } finally { setIsSaving(false) } } + + const toggleStatusToClear = (status: JobStatus) => { + setStatusesToClear(prev => + prev.includes(status) + ? prev.filter(s => s !== status) + : [...prev, status] + ) + } const handleReset = async () => { try { setIsSaving(true) @@ -900,49 +944,86 @@ export const SettingsPage: React.FC = () => { - + -
+
Danger Zone
-
+
-
Clear Discovered Jobs
+
Clear Jobs by Status
- Delete all jobs with the status "discovered". Ready, applied, and skipped jobs are kept. + Select which job statuses you want to clear.
+ +
+ {ALL_JOB_STATUSES.map((status) => { + const isSelected = statusesToClear.includes(status) + return ( + + ) + })} +
+ - - Clear discovered jobs? + Clear jobs by status? - This deletes all jobs with the status "discovered". This action cannot be undone. + This will delete all jobs with the following statuses: {statusesToClear.join(', ')}. + This action cannot be undone. Cancel - - Clear discovered + + Clear {statusesToClear.length} status{statusesToClear.length !== 1 ? 'es' : ''}
+ +
-
Clear Database
+
Clear Entire Database
Delete all jobs and pipeline runs from the database.
@@ -963,7 +1044,7 @@ export const SettingsPage: React.FC = () => { Cancel - + Clear database