"rejected" isn't a thing. it's "skipped"
This commit is contained in:
parent
b5eebfcf49
commit
717987c5cc
@ -89,7 +89,7 @@ job-ops/
|
|||||||
|
|
||||||
- `jobs`
|
- `jobs`
|
||||||
- from crawl: `title`, `employer`, `jobUrl`, `applicationLink`, `deadline`, `salary`, `location`, `jobDescription`, `source` (gradcracker/indeed/linkedin/ukvisajobs), etc.
|
- from crawl: `title`, `employer`, `jobUrl`, `applicationLink`, `deadline`, `salary`, `location`, `jobDescription`, `source` (gradcracker/indeed/linkedin/ukvisajobs), etc.
|
||||||
- enrichments: `status` (`discovered` -> `processing` -> `ready` -> `applied`/`rejected`), `suitabilityScore`, `suitabilityReason`, `tailoredSummary`, `pdfPath`, `notionPageId`
|
- enrichments: `status` (`discovered` -> `processing` -> `ready` -> `applied`/`skipped`), `suitabilityScore`, `suitabilityReason`, `tailoredSummary`, `pdfPath`, `notionPageId`
|
||||||
- `pipeline_runs`: audit log of runs (`running`/`completed`/`failed`, counts, error)
|
- `pipeline_runs`: audit log of runs (`running`/`completed`/`failed`, counts, error)
|
||||||
|
|
||||||
## Running (Docker)
|
## Running (Docker)
|
||||||
@ -148,7 +148,7 @@ Dev URLs:
|
|||||||
|
|
||||||
## Key endpoints
|
## Key endpoints
|
||||||
|
|
||||||
- Jobs: `GET /api/jobs`, `POST /api/jobs/:id/process`, `POST /api/jobs/:id/apply`, `POST /api/jobs/:id/reject`, `POST /api/jobs/process-discovered`
|
- Jobs: `GET /api/jobs`, `POST /api/jobs/:id/process`, `POST /api/jobs/:id/apply`, `POST /api/jobs/:id/skip`, `POST /api/jobs/process-discovered`
|
||||||
- Pipeline: `POST /api/pipeline/run`, `GET /api/pipeline/status`, `GET /api/pipeline/progress` (SSE)
|
- Pipeline: `POST /api/pipeline/run`, `GET /api/pipeline/status`, `GET /api/pipeline/progress` (SSE)
|
||||||
- Webhook: `POST /api/webhook/trigger` (optional auth via `WEBHOOK_SECRET`)
|
- Webhook: `POST /api/webhook/trigger` (optional auth via `WEBHOOK_SECRET`)
|
||||||
- Ops: `DELETE /api/database` (wipes DB)
|
- Ops: `DELETE /api/database` (wipes DB)
|
||||||
|
|||||||
@ -61,7 +61,7 @@ orchestrator/
|
|||||||
| PATCH | `/api/jobs/:id` | Update job |
|
| PATCH | `/api/jobs/:id` | Update job |
|
||||||
| POST | `/api/jobs/:id/process` | Generate resume for job |
|
| POST | `/api/jobs/:id/process` | Generate resume for job |
|
||||||
| POST | `/api/jobs/:id/apply` | Mark as applied + sync to Notion |
|
| POST | `/api/jobs/:id/apply` | Mark as applied + sync to Notion |
|
||||||
| POST | `/api/jobs/:id/reject` | Mark as rejected |
|
| POST | `/api/jobs/:id/skip` | Mark as skipped |
|
||||||
|
|
||||||
### Pipeline
|
### Pipeline
|
||||||
|
|
||||||
|
|||||||
@ -89,8 +89,8 @@ export async function markAsApplied(id: string): Promise<Job> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function rejectJob(id: string): Promise<Job> {
|
export async function skipJob(id: string): Promise<Job> {
|
||||||
return fetchApi<Job>(`/jobs/${id}/reject`, {
|
return fetchApi<Job>(`/jobs/${id}/skip`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ import { StatusBadge } from "./StatusBadge";
|
|||||||
interface JobCardProps {
|
interface JobCardProps {
|
||||||
job: Job;
|
job: Job;
|
||||||
onApply: (id: string) => void | Promise<void>;
|
onApply: (id: string) => void | Promise<void>;
|
||||||
onReject: (id: string) => void | Promise<void>;
|
onSkip: (id: string) => void | Promise<void>;
|
||||||
onProcess: (id: string) => void | Promise<void>;
|
onProcess: (id: string) => void | Promise<void>;
|
||||||
onEditDescription?: (id: string) => void;
|
onEditDescription?: (id: string) => void;
|
||||||
isProcessing: boolean;
|
isProcessing: boolean;
|
||||||
@ -78,7 +78,7 @@ const safeFilenamePart = (value: string) => value.replace(/[^a-z0-9]/gi, "_");
|
|||||||
export const JobCard: React.FC<JobCardProps> = ({
|
export const JobCard: React.FC<JobCardProps> = ({
|
||||||
job,
|
job,
|
||||||
onApply,
|
onApply,
|
||||||
onReject,
|
onSkip,
|
||||||
onProcess,
|
onProcess,
|
||||||
onEditDescription,
|
onEditDescription,
|
||||||
isProcessing,
|
isProcessing,
|
||||||
@ -95,7 +95,7 @@ export const JobCard: React.FC<JobCardProps> = ({
|
|||||||
const hasPdf = !!job.pdfPath;
|
const hasPdf = !!job.pdfPath;
|
||||||
const canApply = job.status === "ready";
|
const canApply = job.status === "ready";
|
||||||
const canProcess = ["discovered", "ready"].includes(job.status);
|
const canProcess = ["discovered", "ready"].includes(job.status);
|
||||||
const canReject = ["discovered", "ready"].includes(job.status);
|
const canSkip = ["discovered", "ready"].includes(job.status);
|
||||||
|
|
||||||
const jobLink = job.applicationLink || job.jobUrl;
|
const jobLink = job.applicationLink || job.jobUrl;
|
||||||
const pdfHref = `/pdfs/resume_${job.id}.pdf?v=${encodeURIComponent(job.updatedAt)}`;
|
const pdfHref = `/pdfs/resume_${job.id}.pdf?v=${encodeURIComponent(job.updatedAt)}`;
|
||||||
@ -164,7 +164,7 @@ export const JobCard: React.FC<JobCardProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
{(job.suitabilityReason || canApply || canReject || canProcess || hasPdf) && (
|
{(job.suitabilityReason || canApply || canSkip || canProcess || hasPdf) && (
|
||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
{job.suitabilityReason && (
|
{job.suitabilityReason && (
|
||||||
<p className="text-sm italic text-muted-foreground">
|
<p className="text-sm italic text-muted-foreground">
|
||||||
@ -246,8 +246,8 @@ export const JobCard: React.FC<JobCardProps> = ({
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{canReject && (
|
{canSkip && (
|
||||||
<Button variant="destructive" size="sm" onClick={() => onReject(job.id)}>
|
<Button variant="destructive" size="sm" onClick={() => onSkip(job.id)}>
|
||||||
<XCircle className="mr-2 h-4 w-4" />
|
<XCircle className="mr-2 h-4 w-4" />
|
||||||
Skip
|
Skip
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -39,7 +39,7 @@ import { TailoringEditor } from "./TailoringEditor";
|
|||||||
interface JobListProps {
|
interface JobListProps {
|
||||||
jobs: Job[];
|
jobs: Job[];
|
||||||
onApply: (id: string) => void | Promise<void>;
|
onApply: (id: string) => void | Promise<void>;
|
||||||
onReject: (id: string) => void | Promise<void>;
|
onSkip: (id: string) => void | Promise<void>;
|
||||||
onProcess: (id: string) => void | Promise<void>;
|
onProcess: (id: string) => void | Promise<void>;
|
||||||
onUpdate: () => void | Promise<void>;
|
onUpdate: () => void | Promise<void>;
|
||||||
processingJobId: string | null;
|
processingJobId: string | null;
|
||||||
@ -87,7 +87,7 @@ const statusRank: Record<JobStatus, number> = {
|
|||||||
processing: 1,
|
processing: 1,
|
||||||
ready: 2,
|
ready: 2,
|
||||||
applied: 3,
|
applied: 3,
|
||||||
rejected: 4,
|
skipped: 4,
|
||||||
expired: 5,
|
expired: 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -194,7 +194,7 @@ const stripHtml = (value: string) => value.replace(/<[^>]*>/g, " ").replace(/\s+
|
|||||||
export const JobList: React.FC<JobListProps> = ({
|
export const JobList: React.FC<JobListProps> = ({
|
||||||
jobs,
|
jobs,
|
||||||
onApply,
|
onApply,
|
||||||
onReject,
|
onSkip,
|
||||||
onProcess,
|
onProcess,
|
||||||
onUpdate,
|
onUpdate,
|
||||||
processingJobId,
|
processingJobId,
|
||||||
@ -204,7 +204,7 @@ export const JobList: React.FC<JobListProps> = ({
|
|||||||
const [sourceFilter, setSourceFilter] = useState<JobSource | "all">("all");
|
const [sourceFilter, setSourceFilter] = useState<JobSource | "all">("all");
|
||||||
const [sort, setSort] = useState<JobSort>(DEFAULT_SORT);
|
const [sort, setSort] = useState<JobSort>(DEFAULT_SORT);
|
||||||
const [selectedJobIds, setSelectedJobIds] = useState<Set<string>>(() => new Set());
|
const [selectedJobIds, setSelectedJobIds] = useState<Set<string>>(() => new Set());
|
||||||
const [batchAction, setBatchAction] = useState<null | "process" | "reject" | "apply">(null);
|
const [batchAction, setBatchAction] = useState<null | "process" | "skip" | "apply">(null);
|
||||||
const [highlightedJobId, setHighlightedJobId] = useState<string | null>(null);
|
const [highlightedJobId, setHighlightedJobId] = useState<string | null>(null);
|
||||||
const [isHighlightVisible, setIsHighlightVisible] = useState(false);
|
const [isHighlightVisible, setIsHighlightVisible] = useState(false);
|
||||||
const [isEditingDescription, setIsEditingDescription] = useState(false);
|
const [isEditingDescription, setIsEditingDescription] = useState(false);
|
||||||
@ -369,7 +369,7 @@ export const JobList: React.FC<JobListProps> = ({
|
|||||||
|
|
||||||
const selectedCount = selectedJobIds.size;
|
const selectedCount = selectedJobIds.size;
|
||||||
|
|
||||||
const runBatch = async (action: "process" | "reject" | "apply") => {
|
const runBatch = async (action: "process" | "skip" | "apply") => {
|
||||||
if (selectedJobs.length === 0) return;
|
if (selectedJobs.length === 0) return;
|
||||||
|
|
||||||
const eligible = selectedJobs.filter((job) => {
|
const eligible = selectedJobs.filter((job) => {
|
||||||
@ -389,7 +389,7 @@ export const JobList: React.FC<JobListProps> = ({
|
|||||||
for (const job of eligible) {
|
for (const job of eligible) {
|
||||||
if (action === "process") await Promise.resolve(onProcess(job.id));
|
if (action === "process") await Promise.resolve(onProcess(job.id));
|
||||||
if (action === "apply") await Promise.resolve(onApply(job.id));
|
if (action === "apply") await Promise.resolve(onApply(job.id));
|
||||||
if (action === "reject") await Promise.resolve(onReject(job.id));
|
if (action === "skip") await Promise.resolve(onSkip(job.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelectedJobIds(new Set());
|
setSelectedJobIds(new Set());
|
||||||
@ -440,7 +440,7 @@ export const JobList: React.FC<JobListProps> = ({
|
|||||||
<JobCard
|
<JobCard
|
||||||
job={highlightedJob}
|
job={highlightedJob}
|
||||||
onApply={onApply}
|
onApply={onApply}
|
||||||
onReject={onReject}
|
onSkip={onSkip}
|
||||||
onProcess={onProcess}
|
onProcess={onProcess}
|
||||||
isProcessing={processingJobId === highlightedJob.id}
|
isProcessing={processingJobId === highlightedJob.id}
|
||||||
highlightedJobId={highlightedJobId}
|
highlightedJobId={highlightedJobId}
|
||||||
@ -713,7 +713,7 @@ export const JobList: React.FC<JobListProps> = ({
|
|||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
onClick={() => runBatch("reject")}
|
onClick={() => runBatch("skip")}
|
||||||
disabled={batchAction !== null}
|
disabled={batchAction !== null}
|
||||||
>
|
>
|
||||||
Skip
|
Skip
|
||||||
@ -746,7 +746,7 @@ export const JobList: React.FC<JobListProps> = ({
|
|||||||
selectedJobIds={selectedJobIds}
|
selectedJobIds={selectedJobIds}
|
||||||
onSelectedJobIdsChange={setSelectedJobIds}
|
onSelectedJobIdsChange={setSelectedJobIds}
|
||||||
onApply={onApply}
|
onApply={onApply}
|
||||||
onReject={onReject}
|
onSkip={onSkip}
|
||||||
onProcess={onProcess}
|
onProcess={onProcess}
|
||||||
processingJobId={processingJobId}
|
processingJobId={processingJobId}
|
||||||
highlightedJobId={highlightedJobId}
|
highlightedJobId={highlightedJobId}
|
||||||
@ -762,7 +762,7 @@ export const JobList: React.FC<JobListProps> = ({
|
|||||||
key={job.id}
|
key={job.id}
|
||||||
job={job}
|
job={job}
|
||||||
onApply={onApply}
|
onApply={onApply}
|
||||||
onReject={onReject}
|
onSkip={onSkip}
|
||||||
onProcess={onProcess}
|
onProcess={onProcess}
|
||||||
isProcessing={processingJobId === job.id}
|
isProcessing={processingJobId === job.id}
|
||||||
highlightedJobId={highlightedJobId}
|
highlightedJobId={highlightedJobId}
|
||||||
|
|||||||
@ -57,7 +57,7 @@ export interface JobTableProps {
|
|||||||
selectedJobIds: Set<string>;
|
selectedJobIds: Set<string>;
|
||||||
onSelectedJobIdsChange: (ids: Set<string>) => void;
|
onSelectedJobIdsChange: (ids: Set<string>) => void;
|
||||||
onApply: (id: string) => void | Promise<void>;
|
onApply: (id: string) => void | Promise<void>;
|
||||||
onReject: (id: string) => void | Promise<void>;
|
onSkip: (id: string) => void | Promise<void>;
|
||||||
onProcess: (id: string) => void | Promise<void>;
|
onProcess: (id: string) => void | Promise<void>;
|
||||||
onEditDescription?: (id: string) => void;
|
onEditDescription?: (id: string) => void;
|
||||||
processingJobId: string | null;
|
processingJobId: string | null;
|
||||||
@ -145,7 +145,7 @@ export const JobTable: React.FC<JobTableProps> = ({
|
|||||||
selectedJobIds,
|
selectedJobIds,
|
||||||
onSelectedJobIdsChange,
|
onSelectedJobIdsChange,
|
||||||
onApply,
|
onApply,
|
||||||
onReject,
|
onSkip,
|
||||||
onProcess,
|
onProcess,
|
||||||
onEditDescription,
|
onEditDescription,
|
||||||
processingJobId,
|
processingJobId,
|
||||||
@ -228,7 +228,7 @@ export const JobTable: React.FC<JobTableProps> = ({
|
|||||||
|
|
||||||
const canApply = job.status === "ready";
|
const canApply = job.status === "ready";
|
||||||
const canProcess = ["discovered", "ready"].includes(job.status);
|
const canProcess = ["discovered", "ready"].includes(job.status);
|
||||||
const canReject = ["discovered", "ready"].includes(job.status);
|
const canSkip = ["discovered", "ready"].includes(job.status);
|
||||||
const isProcessing = processingJobId === job.id;
|
const isProcessing = processingJobId === job.id;
|
||||||
const isSelected = selectedJobIds.has(job.id);
|
const isSelected = selectedJobIds.has(job.id);
|
||||||
const isHighlighted = highlightedJobId === job.id;
|
const isHighlighted = highlightedJobId === job.id;
|
||||||
@ -342,7 +342,7 @@ export const JobTable: React.FC<JobTableProps> = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(canProcess || canReject || canApply) && <DropdownMenuSeparator />}
|
{(canProcess || canSkip || canApply) && <DropdownMenuSeparator />}
|
||||||
|
|
||||||
{canProcess && (
|
{canProcess && (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
@ -358,9 +358,9 @@ export const JobTable: React.FC<JobTableProps> = ({
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{canReject && (
|
{canSkip && (
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onSelect={() => onReject(job.id)}
|
onSelect={() => onSkip(job.id)}
|
||||||
>
|
>
|
||||||
<XCircle className="mr-2 h-4 w-4" />
|
<XCircle className="mr-2 h-4 w-4" />
|
||||||
Skip
|
Skip
|
||||||
|
|||||||
@ -28,7 +28,7 @@ const statConfig: Array<{
|
|||||||
{ key: "processing", label: "Processing", Icon: Loader2 },
|
{ key: "processing", label: "Processing", Icon: Loader2 },
|
||||||
{ key: "ready", label: "Ready", Icon: Sparkles },
|
{ key: "ready", label: "Ready", Icon: Sparkles },
|
||||||
{ key: "applied", label: "Applied", Icon: CheckCircle2 },
|
{ key: "applied", label: "Applied", Icon: CheckCircle2 },
|
||||||
{ key: "rejected", label: "Rejected", Icon: XCircle },
|
{ key: "skipped", label: "Skipped", Icon: XCircle },
|
||||||
{ key: "expired", label: "Expired", Icon: Clock },
|
{ key: "expired", label: "Expired", Icon: Clock },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,7 @@ const statusLabels: Record<JobStatus, string> = {
|
|||||||
processing: "Processing",
|
processing: "Processing",
|
||||||
ready: "Ready",
|
ready: "Ready",
|
||||||
applied: "Applied",
|
applied: "Applied",
|
||||||
rejected: "Rejected",
|
skipped: "Skipped",
|
||||||
expired: "Expired",
|
expired: "Expired",
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ const statusStyles: Record<
|
|||||||
processing: { variant: "secondary" },
|
processing: { variant: "secondary" },
|
||||||
ready: { variant: "default" },
|
ready: { variant: "default" },
|
||||||
applied: { variant: "outline", className: "text-emerald-400 border-emerald-500/30" },
|
applied: { variant: "outline", className: "text-emerald-400 border-emerald-500/30" },
|
||||||
rejected: { variant: "destructive" },
|
skipped: { variant: "destructive" },
|
||||||
expired: { variant: "outline", className: "text-muted-foreground" },
|
expired: { variant: "outline", className: "text-muted-foreground" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -93,8 +93,8 @@ const statusTokens: Record<
|
|||||||
badge: "border-emerald-500/30 bg-emerald-500/10 text-emerald-200",
|
badge: "border-emerald-500/30 bg-emerald-500/10 text-emerald-200",
|
||||||
dot: "bg-emerald-400",
|
dot: "bg-emerald-400",
|
||||||
},
|
},
|
||||||
rejected: {
|
skipped: {
|
||||||
label: "Rejected",
|
label: "Skipped",
|
||||||
badge: "border-rose-500/30 bg-rose-500/10 text-rose-200",
|
badge: "border-rose-500/30 bg-rose-500/10 text-rose-200",
|
||||||
dot: "bg-rose-400",
|
dot: "bg-rose-400",
|
||||||
},
|
},
|
||||||
@ -292,7 +292,7 @@ export const OrchestratorPage: React.FC = () => {
|
|||||||
processing: 0,
|
processing: 0,
|
||||||
ready: 0,
|
ready: 0,
|
||||||
applied: 0,
|
applied: 0,
|
||||||
rejected: 0,
|
skipped: 0,
|
||||||
expired: 0,
|
expired: 0,
|
||||||
});
|
});
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
@ -430,13 +430,13 @@ export const OrchestratorPage: React.FC = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReject = async (jobId: string) => {
|
const handleSkip = async (jobId: string) => {
|
||||||
try {
|
try {
|
||||||
await api.rejectJob(jobId);
|
await api.skipJob(jobId);
|
||||||
toast.message("Job skipped");
|
toast.message("Job skipped");
|
||||||
await loadJobs();
|
await loadJobs();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : "Failed to reject job";
|
const message = error instanceof Error ? error.message : "Failed to skip job";
|
||||||
toast.error(message);
|
toast.error(message);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -586,7 +586,7 @@ export const OrchestratorPage: React.FC = () => {
|
|||||||
const selectedDiscoveredAt = selectedJob ? formatDateTime(selectedJob.discoveredAt) : null;
|
const selectedDiscoveredAt = selectedJob ? formatDateTime(selectedJob.discoveredAt) : null;
|
||||||
const canApply = selectedJob?.status === "ready";
|
const canApply = selectedJob?.status === "ready";
|
||||||
const canProcess = selectedJob ? ["discovered", "ready"].includes(selectedJob.status) : false;
|
const canProcess = selectedJob ? ["discovered", "ready"].includes(selectedJob.status) : false;
|
||||||
const canReject = selectedJob ? ["discovered", "ready"].includes(selectedJob.status) : false;
|
const canSkip = selectedJob ? ["discovered", "ready"].includes(selectedJob.status) : false;
|
||||||
const showReadyPdf = activeTab === "ready";
|
const showReadyPdf = activeTab === "ready";
|
||||||
const showGeneratePdf = activeTab === "discovered";
|
const showGeneratePdf = activeTab === "discovered";
|
||||||
const isProcessingSelected =
|
const isProcessingSelected =
|
||||||
@ -716,7 +716,7 @@ export const OrchestratorPage: React.FC = () => {
|
|||||||
{ label: "Processing", value: stats.processing },
|
{ label: "Processing", value: stats.processing },
|
||||||
{ label: "Ready", value: stats.ready },
|
{ label: "Ready", value: stats.ready },
|
||||||
{ label: "Applied", value: stats.applied },
|
{ label: "Applied", value: stats.applied },
|
||||||
{ label: "Rejected", value: stats.rejected },
|
{ label: "Skipped", value: stats.skipped },
|
||||||
{ label: "Expired", value: stats.expired },
|
{ label: "Expired", value: stats.expired },
|
||||||
].map((item, index) => (
|
].map((item, index) => (
|
||||||
<div
|
<div
|
||||||
@ -1060,11 +1060,11 @@ export const OrchestratorPage: React.FC = () => {
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{canReject && (
|
{canSkip && (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onSelect={() => handleReject(selectedJob.id)}
|
onSelect={() => handleSkip(selectedJob.id)}
|
||||||
className="text-destructive focus:text-destructive"
|
className="text-destructive focus:text-destructive"
|
||||||
>
|
>
|
||||||
<XCircle className="mr-2 h-4 w-4" />
|
<XCircle className="mr-2 h-4 w-4" />
|
||||||
|
|||||||
@ -906,7 +906,7 @@ export const SettingsPage: React.FC = () => {
|
|||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<div className="text-sm font-medium">Clear Discovered Jobs</div>
|
<div className="text-sm font-medium">Clear Discovered Jobs</div>
|
||||||
<div className="text-xs text-muted-foreground">
|
<div className="text-xs text-muted-foreground">
|
||||||
Delete all jobs with the status "discovered". Ready, applied, and rejected jobs are kept.
|
Delete all jobs with the status "discovered". Ready, applied, and skipped jobs are kept.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AlertDialog>
|
<AlertDialog>
|
||||||
|
|||||||
@ -105,7 +105,7 @@ apiRouter.get('/jobs/:id', async (req: Request, res: Response) => {
|
|||||||
* PATCH /api/jobs/:id - Update a job
|
* PATCH /api/jobs/:id - Update a job
|
||||||
*/
|
*/
|
||||||
const updateJobSchema = z.object({
|
const updateJobSchema = z.object({
|
||||||
status: z.enum(['discovered', 'processing', 'ready', 'applied', 'rejected', 'expired']).optional(),
|
status: z.enum(['discovered', 'processing', 'ready', 'applied', 'skipped', 'expired']).optional(),
|
||||||
jobDescription: z.string().optional(),
|
jobDescription: z.string().optional(),
|
||||||
suitabilityScore: z.number().min(0).max(100).optional(),
|
suitabilityScore: z.number().min(0).max(100).optional(),
|
||||||
suitabilityReason: z.string().optional(),
|
suitabilityReason: z.string().optional(),
|
||||||
@ -241,11 +241,11 @@ apiRouter.post('/jobs/:id/apply', async (req: Request, res: Response) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /api/jobs/:id/reject - Mark a job as rejected
|
* POST /api/jobs/:id/skip - Mark a job as skipped
|
||||||
*/
|
*/
|
||||||
apiRouter.post('/jobs/:id/reject', async (req: Request, res: Response) => {
|
apiRouter.post('/jobs/:id/skip', async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
const job = await jobsRepo.updateJob(req.params.id, { status: 'rejected' });
|
const job = await jobsRepo.updateJob(req.params.id, { status: 'skipped' });
|
||||||
|
|
||||||
if (!job) {
|
if (!job) {
|
||||||
return res.status(404).json({ success: false, error: 'Job not found' });
|
return res.status(404).json({ success: false, error: 'Job not found' });
|
||||||
|
|||||||
@ -65,7 +65,7 @@ const migrations = [
|
|||||||
degree_required TEXT,
|
degree_required TEXT,
|
||||||
starting TEXT,
|
starting TEXT,
|
||||||
job_description TEXT,
|
job_description TEXT,
|
||||||
status TEXT NOT NULL DEFAULT 'discovered' CHECK(status IN ('discovered', 'processing', 'ready', 'applied', 'rejected', 'expired')),
|
status TEXT NOT NULL DEFAULT 'discovered' CHECK(status IN ('discovered', 'processing', 'ready', 'applied', 'skipped', 'expired')),
|
||||||
suitability_score REAL,
|
suitability_score REAL,
|
||||||
suitability_reason TEXT,
|
suitability_reason TEXT,
|
||||||
tailored_summary TEXT,
|
tailored_summary TEXT,
|
||||||
|
|||||||
@ -54,7 +54,7 @@ export const jobs = sqliteTable('jobs', {
|
|||||||
|
|
||||||
// Orchestrator enrichments
|
// Orchestrator enrichments
|
||||||
status: text('status', {
|
status: text('status', {
|
||||||
enum: ['discovered', 'processing', 'ready', 'applied', 'rejected', 'expired']
|
enum: ['discovered', 'processing', 'ready', 'applied', 'skipped', 'expired']
|
||||||
}).notNull().default('discovered'),
|
}).notNull().default('discovered'),
|
||||||
suitabilityScore: real('suitability_score'),
|
suitabilityScore: real('suitability_score'),
|
||||||
suitabilityReason: text('suitability_reason'),
|
suitabilityReason: text('suitability_reason'),
|
||||||
|
|||||||
@ -165,7 +165,7 @@ export async function getJobStats(): Promise<Record<JobStatus, number>> {
|
|||||||
processing: 0,
|
processing: 0,
|
||||||
ready: 0,
|
ready: 0,
|
||||||
applied: 0,
|
applied: 0,
|
||||||
rejected: 0,
|
skipped: 0,
|
||||||
expired: 0,
|
expired: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ export type JobStatus =
|
|||||||
| 'processing' // Currently generating resume
|
| 'processing' // Currently generating resume
|
||||||
| 'ready' // PDF generated, waiting for user to apply
|
| 'ready' // PDF generated, waiting for user to apply
|
||||||
| 'applied' // User marked as applied (added to Notion)
|
| 'applied' // User marked as applied (added to Notion)
|
||||||
| 'rejected' // User rejected this job
|
| 'skipped' // User skipped this job
|
||||||
| 'expired'; // Deadline passed
|
| 'expired'; // Deadline passed
|
||||||
|
|
||||||
export type JobSource =
|
export type JobSource =
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user