Appearance
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— addJobEditLogmodel - Run:
pnpm db:migrate - Edit:
src/graphql/schema/index.ts— addadminJobquery,adminEditJob+adminDeactivateJobmutations,JobEditLogtype - 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! # ADMINadminEditJob behaviour:
requireAdmin; validates reason (5–500 chars).- Loads current job; builds
fieldsBeforesnapshot of only the fields present ininput. - Updates job with the provided fields.
- Creates a
JobEditLogrow with the before/after diff. - Returns updated job.
adminDeactivateJob behaviour:
- Sets
job.status = CLOSED. - Creates a
SupportTicketlinked 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.tsor newadminJobs.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
JobEditLogentries (admin, reason, fields changed, timestamp). - Link to "View as producer" (opens job in main app in new tab).