feat: F018 human review queue — 2-panel governance workspace
Replaces the old ad-hoc ReviewQueue page with a full governance-compliant workflow: filterable task list, detail panel with confidence/risk context, role-aware approve/reject/escalate/more-data actions, and persistent notes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,26 +1,77 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { reviewService } from '../services/reviewService'
|
||||
import { useSessionStore } from '../stores/sessionStore'
|
||||
import type { ReviewFilters } from '../provider/IReviewProvider'
|
||||
import type { ReviewTaskStatus } from '../domain/review'
|
||||
|
||||
const STALE_REVIEW = 30_000
|
||||
const QK = 'reviewQueue'
|
||||
|
||||
export function useReviewQueue(filters?: ReviewFilters) {
|
||||
return useQuery({
|
||||
queryKey: ['reviewQueue', filters ?? {}],
|
||||
queryFn: () => reviewService.getQueue(filters),
|
||||
queryKey: [QK, filters ?? {}],
|
||||
queryFn: () => reviewService.getTasks(filters),
|
||||
staleTime: STALE_REVIEW,
|
||||
select: (res) => res.data ?? [],
|
||||
})
|
||||
}
|
||||
|
||||
export function useReviewTask(id: string | null) {
|
||||
return useQuery({
|
||||
queryKey: [QK, 'task', id],
|
||||
queryFn: () => reviewService.getTask(id!),
|
||||
staleTime: STALE_REVIEW,
|
||||
enabled: !!id,
|
||||
select: (res) => res.data ?? null,
|
||||
})
|
||||
}
|
||||
|
||||
export function useUpdateReviewStatus() {
|
||||
const queryClient = useQueryClient()
|
||||
const { currentUser } = useSessionStore.getState()
|
||||
const userId = currentUser?.email ?? 'unknown'
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ id, status, note }: { id: string; status: ReviewTaskStatus; note?: string }) =>
|
||||
reviewService.updateStatus(id, status, userId, note),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [QK] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useAssignReviewTask() {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: ({ id, assignTo }: { id: string; assignTo: string }) =>
|
||||
reviewService.assign(id, assignTo),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [QK] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
export function useAddReviewNote() {
|
||||
const queryClient = useQueryClient()
|
||||
const { currentUser } = useSessionStore.getState()
|
||||
const userId = currentUser?.email ?? 'unknown'
|
||||
|
||||
return useMutation({
|
||||
mutationFn: ({ id, content }: { id: string; content: string }) =>
|
||||
reviewService.addNote(id, { content, createdBy: userId, createdAt: new Date().toISOString() }),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: [QK] })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Legacy exports
|
||||
export function useApproveReviewItem() {
|
||||
const queryClient = useQueryClient()
|
||||
return useMutation({
|
||||
mutationFn: ({ id, notes }: { id: string; notes?: string }) =>
|
||||
reviewService.approve(id, 'current-user', notes),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['reviewQueue'] })
|
||||
},
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: [QK] }),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -29,8 +80,6 @@ export function useRejectReviewItem() {
|
||||
return useMutation({
|
||||
mutationFn: ({ id, notes }: { id: string; notes?: string }) =>
|
||||
reviewService.reject(id, 'current-user', notes),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['reviewQueue'] })
|
||||
},
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: [QK] }),
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user