/* Query key convention: [domain, scope, ...params], always an array, params always last as a single object so `invalidateQueries({queryKey:['users']})` catches every scope under a domain. */ export const qk = { auth: { me: () => ['auth', 'me'] }, // --- real backend endpoints --- users: { all: () => ['users'], list: (p = {}) => ['users', 'list', p], pendingApprovals: () => ['users', 'pending-approvals'], }, roles: { all: () => ['roles'], list: () => ['roles', 'list'], permissions: () => ['roles', 'permissions'], tags: () => ['roles', 'permission-tags'], }, mailbox: { all: () => ['mailbox'], messages: () => ['mailbox', 'messages'], applications: (p = {}) => ['mailbox', 'applications', p], message: (id) => ['mailbox', 'message', id], assignments: (p = {}) => ['mailbox', 'assignments', p], counts: () => ['mailbox', 'counts'], // Under `mailbox` on purpose: every existing invalidateQueries on // qk.mailbox.all() already refreshes the intake gate's ledger, so an // override or a sync needs no extra invalidation. triage: (p = {}) => ['mailbox', 'triage', p], sync: (id) => ['mailbox', 'sync', id], // Sheet form applicants live under the same mailbox prefix so the Inbox // channel toggle can invalidate both email and form caches together. formSheets: () => ['mailbox', 'form-sheets'], formData: (p = {}) => ['mailbox', 'form-data', p], formRow: (id) => ['mailbox', 'form-row', id], formCounts: (p = {}) => ['mailbox', 'form-counts', p], applicationTotal: () => ['mailbox', 'application-total'], formTotal: (p = {}) => ['mailbox', 'form-total', p], }, assessments: { all: () => ['assessments'], list: (p = {}) => ['assessments', 'list', p], counts: () => ['assessments', 'counts'], }, cvBank: { all: () => ['cvBank'], list: () => ['cvBank', 'list'], }, notifications: { all: () => ['notifications'], list: (p = {}) => ['notifications', 'list', p], }, managers: { all: () => ['managers'], list: (p = {}) => ['managers', 'list', p], directory: () => ['managers', 'directory'], }, orgSettings: { all: () => ['orgSettings'], list: (p = {}) => ['orgSettings', 'list', p], }, savedSearches: { all: () => ['savedSearches'], list: (p = {}) => ['savedSearches', 'list', p], }, search: { query: (p = {}) => ['search', p], }, feedbackTemplates: { all: () => ['feedbackTemplates'], list: () => ['feedbackTemplates', 'list'], }, jobPosts: { all: () => ['jobPosts'], list: (p = {}) => ['jobPosts', 'list', p], departments: (p = {}) => ['jobPosts', 'departments', p], }, jobs: { all: () => ['jobs'], list: (p = {}) => ['jobs', 'list', p], requisitionStatuses: () => ['jobs', 'requisition-statuses'], statusHistory: (id) => ['jobs', 'status-history', id], }, talent: { all: () => ['talent'], account: () => ['talent', 'account'], runs: (jobId) => ['talent', 'runs', jobId], run: (runId) => ['talent', 'run', runId], profiles: (p = {}) => ['talent', 'profiles', p], profile: (id) => ['talent', 'profile', id], }, candidates: { all: () => ['candidates'], list: (p = {}) => ['candidates', 'list', p], count: (p = {}) => ['candidates', 'count', p], detail: (id) => ['candidates', 'detail', id], history: (id, p = {}) => ['candidates', 'history', id, p], matching: (p = {}) => ['candidates', 'matching', p], matchingDetail: (id) => ['candidates', 'matching', 'detail', id], }, // Board rows come from the same endpoint as qk.candidates.list but are cached // MAPPED (kanban cards, not the raw envelope), so they need their own key — // sharing one would poison whichever screen mounted first. pipeline: { all: () => ['pipeline'], board: (p = {}) => ['pipeline', 'board', p], transitions: (inboxId) => ['pipeline', 'transitions', inboxId], // One candidate's current ats_results score. Fetched on demand (a card // click), never as part of a list, so it is keyed per candidate + job. candidateScore: (p = {}) => ['pipeline', 'candidate-score', p], }, analytics: { all: () => ['analytics'], kpis: (p = {}) => ['analytics', 'kpis', p], funnel: (p = {}) => ['analytics', 'funnel', p], trend: (p = {}) => ['analytics', 'trend', p], sources: (p = {}) => ['analytics', 'sources', p], recruiters: (p = {}) => ['analytics', 'recruiters', p], jobApps: (p = {}) => ['analytics', 'job-apps', p], }, offers: { all: () => ['offers'], list: (p = {}) => ['offers', 'list', p] }, forms: { all: () => ['forms'], list: (p = {}) => ['forms', 'list', p], definitions: () => ['forms', 'definitions'], }, interviews: { all: () => ['interviews'], range: (p = {}) => ['interviews', 'range', p], byInbox: (inboxId) => ['interviews', 'inbox', inboxId], }, costs: { all: () => ['costs'], list: (p = {}) => ['costs', 'list', p], sources: () => ['costs', 'sources'], }, reports: { all: () => ['reports'], list: () => ['reports', 'list'], runs: (id) => ['reports', 'runs', id], }, assignments: { all: () => ['assignments'], job: (jobPostId) => ['assignments', 'job', jobPostId], application: (inboxId) => ['assignments', 'application', inboxId], }, activity: { all: () => ['activity'], feed: (p = {}) => ['activity', 'feed', p] }, tasks: { all: () => ['tasks'], list: (p = {}) => ['tasks', 'list', p], assignees: () => ['tasks', 'assignees'], }, // --- seed-backed buckets --- // These are not "server state" — the cache IS the store for them, so every // mutation is a setQueryData. See data/seedQueries.js. seed: { all: () => ['seed'], jobs: () => ['seed', 'jobs'], candidates: () => ['seed', 'candidates'], interviews: () => ['seed', 'interviews'], assessments: () => ['seed', 'assessments'], offers: () => ['seed', 'offers'], tasks: () => ['seed', 'tasks'], notifications: () => ['seed', 'notifications'], messages: () => ['seed', 'messages'], activity: () => ['seed', 'activity'], inbox: () => ['seed', 'inbox'], emails: () => ['seed', 'emails'], publishings: () => ['seed', 'publishings'], rbacRoles: () => ['seed', 'rbacRoles'], recruiters: () => ['seed', 'recruiters'], managers: () => ['seed', 'managers'], users: () => ['seed', 'users'], favorites: () => ['seed', 'favorites'], recentlyViewed: () => ['seed', 'recentlyViewed'], }, }