norhan12 commited on
Commit
443ae6f
·
verified ·
1 Parent(s): 9ea9ab6

Recruiters can delete a job

Browse files
frontend/src/lib/i18n.tsx CHANGED
@@ -141,7 +141,8 @@ const en = {
141
  skills_pool: "Skills across all answers", jobs_table: "Jobs at a glance", export_csv: "Export CSV", c_job: "Job",
142
  empty_company: "Post your first job to start receiving interviews. Public jobs appear on the EvalBot job board, and every job gets a link you can share anywhere.",
143
  job_stats: "This job in numbers", search_candidates: "Search candidates", min_score: "Minimum score",
144
- co_new_job_cta: "Post a job", out_of_100: "out of 100",
 
145
  };
146
 
147
  type Dict = typeof en;
@@ -287,7 +288,8 @@ const ar: Dict = {
287
  skills_pool: "المهارات عبر كل الإجابات", jobs_table: "الوظائف باختصار", export_csv: "تصدير CSV", c_job: "الوظيفة",
288
  empty_company: "انشر أول وظيفة لتبدأ في استقبال المقابلات. الوظائف العامة تظهر في لوحة وظائف EvalBot، ولكل وظيفة رابط تشاركه في أي مكان.",
289
  job_stats: "الوظيفة بالأرقام", search_candidates: "ابحث في المرشحين", min_score: "أقل درجة",
290
- co_new_job_cta: "انشر وظيفة", out_of_100: "من 100",
 
291
  };
292
 
293
  export type Lang = "en" | "ar";
 
141
  skills_pool: "Skills across all answers", jobs_table: "Jobs at a glance", export_csv: "Export CSV", c_job: "Job",
142
  empty_company: "Post your first job to start receiving interviews. Public jobs appear on the EvalBot job board, and every job gets a link you can share anywhere.",
143
  job_stats: "This job in numbers", search_candidates: "Search candidates", min_score: "Minimum score",
144
+ co_new_job_cta: "Post a job", out_of_100: "out of 100", delete_job: "Delete job",
145
+ delete_job_confirm: "Delete this job and all of its interviews? This can't be undone. To stop new applications but keep the results, set the status to Closed instead.",
146
  };
147
 
148
  type Dict = typeof en;
 
288
  skills_pool: "المهارات عبر كل الإجابات", jobs_table: "الوظائف باختصار", export_csv: "تصدير CSV", c_job: "الوظيفة",
289
  empty_company: "انشر أول وظيفة لتبدأ في استقبال المقابلات. الوظائف العامة تظهر في لوحة وظائف EvalBot، ولكل وظيفة رابط تشاركه في أي مكان.",
290
  job_stats: "الوظيفة بالأرقام", search_candidates: "ابحث في المرشحين", min_score: "أقل درجة",
291
+ co_new_job_cta: "انشر وظيفة", out_of_100: "من 100", delete_job: "حذف الوظيفة",
292
+ delete_job_confirm: "حذف هذه الوظيفة وكل مقابلاتها؟ لا يمكن التراجع عن ذلك. لإيقاف التقديم مع الاحتفاظ بالنتائج، اجعل الحالة «مغلقة» بدلاً من الحذف.",
293
  };
294
 
295
  export type Lang = "en" | "ar";
frontend/src/pages/JobEditor.tsx CHANGED
@@ -144,7 +144,13 @@ export default function JobEditor() {
144
  </Panel>
145
 
146
  {error && <ErrorNote>{error}</ErrorNote>}
147
- <div className="flex justify-end gap-3">
 
 
 
 
 
 
148
  <Button variant="quiet" onClick={() => nav(-1)}>{t("cancel")}</Button>
149
  <Button size="lg" onClick={save} busy={busy} disabled={title.trim().length < 2 || !qs.some((x) => x.text.trim())}>{t("save_job")}</Button>
150
  </div>
 
144
  </Panel>
145
 
146
  {error && <ErrorNote>{error}</ErrorNote>}
147
+ <div className="flex flex-wrap justify-end gap-3">
148
+ {id && (
149
+ <Button variant="quiet" className="me-auto text-danger hover:bg-danger-soft" onClick={async () => {
150
+ if (!window.confirm(t("delete_job_confirm"))) return;
151
+ try { await api.del(`/jobs/${id}`); nav("/app/jobs"); } catch (e: any) { setError(e.message); }
152
+ }}><Trash2 size={16} /> {t("delete_job")}</Button>
153
+ )}
154
  <Button variant="quiet" onClick={() => nav(-1)}>{t("cancel")}</Button>
155
  <Button size="lg" onClick={save} busy={busy} disabled={title.trim().length < 2 || !qs.some((x) => x.text.trim())}>{t("save_job")}</Button>
156
  </div>
platform_app/routes.py CHANGED
@@ -541,6 +541,17 @@ def update_job(jid: str, body: JobIn, user: Dict = Depends(recruiter)):
541
  return q1(f"update jobs set {sets} where id = %(jid)s returning *", {**d, "jid": jid})
542
 
543
 
 
 
 
 
 
 
 
 
 
 
 
544
  @router.get("/jobs/{jid}/candidates")
545
  def job_candidates(jid: str, user: Dict = Depends(recruiter)):
546
  _job(jid, user)
 
541
  return q1(f"update jobs set {sets} where id = %(jid)s returning *", {**d, "jid": jid})
542
 
543
 
544
+ @router.delete("/jobs/{jid}")
545
+ def delete_job(jid: str, user: Dict = Depends(recruiter)):
546
+ """Delete a job together with its interviews (only possible while nobody has been hired for it)."""
547
+ _job(jid, user)
548
+ if q1("select 1 from sessions s join reviews r on r.session_id = s.id where s.job_id = %s and r.status = 'hired'", [jid]):
549
+ raise HTTPException(400, "Someone was hired for this job. Close it instead of deleting it.")
550
+ q("delete from sessions where job_id = %s", [jid])
551
+ q("delete from jobs where id = %s", [jid])
552
+ return {"ok": True}
553
+
554
+
555
  @router.get("/jobs/{jid}/candidates")
556
  def job_candidates(jid: str, user: Dict = Depends(recruiter)):
557
  _job(jid, user)