Skip to content

Epic 29 — Admin Job Detail Review

Admins can open any job posting in the admin panel to see its full spec, edit fields (with mandatory audit reason), and deactivate it (which creates a linked support ticket explaining why).


BE-ADMIN-JOB-001 — JobEditLog model + adminEditJob mutation

  • [x] Done

Files:

  • Edit: prisma/schema.prisma — add JobEditLog model
  • Run: pnpm db:migrate
  • Edit: src/graphql/schema/index.ts — add adminJob query, adminEditJob + adminDeactivateJob mutations, JobEditLog type
  • Edit: src/graphql/resolvers/admin.ts
  • Create: src/__tests__/resolvers/adminJobDetail.test.ts

Schema:

prisma
model JobEditLog {
  id           String   @id @default(cuid())
  jobId        String
  job          Job      @relation(fields: [jobId], references: [id], onDelete: Cascade)
  adminId      String
  admin        User     @relation("JobEditLogAdmin", fields: [adminId], references: [id])
  reason       String
  fieldsBefore Json
  fieldsAfter  Json
  createdAt    DateTime @default(now())

  @@index([jobId, createdAt])
  @@map("job_edit_logs")
}

Job model gets: editLogs JobEditLog[]User model gets: jobEditLogs JobEditLog[] @relation("JobEditLogAdmin")

GraphQL:

graphql
type JobEditLog {
  id: ID!
  jobId: ID!
  adminId: ID!
  admin: User!
  reason: String!
  fieldsBefore: JSON!
  fieldsAfter: JSON!
  createdAt: DateTime!
}
type JobEditLogPage {
  logs: [JobEditLog!]!
  page: Int!; pageSize: Int!; totalCount: Int!; totalPages: Int!
}
input AdminEditJobInput {
  title: String; description: String; status: String
  paymentType: String; paymentAmount: Float; location: String
  startDate: String; endDate: String; applicationDeadline: String
  # … all updatable job fields
}

adminJob(id: ID!): Job                              # ADMIN — full job detail
adminEditJob(id: ID!, input: AdminEditJobInput!, reason: String!): Job!    # ADMIN
adminDeactivateJob(id: ID!, reason: String!): SupportTicket!               # ADMIN — closes job + opens ticket
jobEditLogs(jobId: ID!, page: Int, pageSize: Int): JobEditLogPage!         # ADMIN

adminEditJob behaviour:

  • requireAdmin; validates reason (5–500 chars).
  • Loads current job; builds fieldsBefore snapshot of only the fields present in input.
  • Updates job with the provided fields.
  • Creates a JobEditLog row with the before/after diff.
  • Returns updated job.

adminDeactivateJob behaviour:

  • Sets job.status = CLOSED.
  • Creates a SupportTicket linked to the job (linkedEntityType: JOB, linkedEntityId: job.id) with subject "Job deactivated by admin" and the reason as body, assigned to the calling admin.
  • Returns the created SupportTicket.

FE-ADMIN-JOB-001 — Admin job detail page

  • [x] Done

Files:

  • Create: apps/app/src/pages/admin/AdminJobDetailPage.tsx
  • Create: apps/app/src/hooks/useAdminJobDetail.ts
  • Edit: apps/app/src/pages/admin/AdminJobsPage.tsx — make rows clickable → navigate to detail
  • Edit: apps/app/src/lib/queries/jobs.ts or new adminJobs.ts
  • Edit: apps/app/src/App.tsx — add route /admin/jobs/:id
  • Edit: apps/app/src/components/AdminShell.tsx — already has Jobs nav

Description:

  • Full job spec view: all fields in readable format (sections: Basic Info, Requirements, Compensation, Logistics, Casting Map link if any).
  • "Edit" button → opens edit drawer/modal: form pre-filled with current values; each session of editing requires a mandatory reason field; saves via adminEditJob.
  • "Deactivate" button → confirmation dialog with mandatory reason textarea → calls adminDeactivateJob → toast "Job deactivated, support ticket opened" → navigate back to list.
  • Edit history section at the bottom: timeline of JobEditLog entries (admin, reason, fields changed, timestamp).
  • Link to "View as producer" (opens job in main app in new tab).