fix(qc-queue): correct sidebar badge and Refresh button loading state

Sidebar My QC Queue badge was showing org-wide pending_qc job count
instead of the current user personal assigned tasks. Now uses
useMyQCQueueCount which sums the linguist and reviewer queue totals
from the same me/language-qc-queue API the queue page uses.

Refresh button now shows a spinner and Refreshing label while the
refetch is in progress so users can see the action took effect.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-05-01 14:41:37 +01:00
parent 8dee0b6ff5
commit 31d631f70d
3 changed files with 37 additions and 10 deletions

View file

@ -1,7 +1,7 @@
import { Link, useLocation, useParams } from 'react-router-dom';
import { useAuthStore } from '../../lib/auth';
import { useMyMemberships } from '../../hooks/useClients';
import { useJobs, useBriefs } from '../../hooks/useJob';
import { useJobs, useBriefs, useMyQCQueueCount } from '../../hooks/useJob';
interface SidebarItem {
label: string;
@ -25,10 +25,7 @@ export function Sidebar({ onMobileClose }: SidebarProps) {
const isPMOrAdmin = ['project_manager', 'admin'].includes(user?.role || '');
const isAdminOrProduction = ['production', 'admin'].includes(user?.role || '');
const { data: qcData } = useJobs(
{ status: 'pending_qc', size: 1 },
{ enabled: isQCRole }
);
const myQCCount = useMyQCQueueCount(isQCRole);
const { data: finalData } = useJobs(
{ status: 'pending_final_review', size: 1 },
{ enabled: isPMOrAdmin }
@ -40,7 +37,7 @@ export function Sidebar({ onMobileClose }: SidebarProps) {
const { data: allBriefs = [] } = useBriefs();
const qcBadge = isQCRole ? (qcData?.total || 0) : 0;
const qcBadge = isQCRole ? myQCCount : 0;
const finalBadge = isPMOrAdmin ? (finalData?.total || 0) : 0;
const failuresBadge = isAdminOrProduction ? (failuresData?.total || 0) : 0;
const briefsBadge = allBriefs.filter(b => b.status === 'submitted').length;

View file

@ -1,4 +1,4 @@
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useQuery, useMutation, useQueryClient, useQueries } from '@tanstack/react-query';
import { apiClient } from '../lib/api';
import type {
Job,
@ -441,4 +441,27 @@ export function useUploadFinalVtt() {
queryClient.invalidateQueries({ queryKey: ['jobs'] });
},
});
}
/** Returns the total count of QC tasks personally assigned to the current user (linguist + reviewer combined). */
export function useMyQCQueueCount(enabled = true) {
const results = useQueries({
queries: [
{
queryKey: ['linguist-queue', 'linguist', 'all'],
queryFn: () => apiClient.getMyLanguageQCQueue('linguist'),
enabled,
staleTime: 30_000,
refetchInterval: 30_000,
},
{
queryKey: ['linguist-queue', 'reviewer', 'all'],
queryFn: () => apiClient.getMyLanguageQCQueue('reviewer'),
enabled,
staleTime: 30_000,
refetchInterval: 30_000,
},
],
});
return (results[0].data?.total ?? 0) + (results[1].data?.total ?? 0);
}

View file

@ -94,7 +94,7 @@ export function LinguistQueue({ defaultRole }: LinguistQueueProps = {}) {
const [activeTab, setActiveTab] = useState<LanguageQCStatus | 'all'>('all');
const [sortDir, setSortDir] = useState<'asc' | 'desc'>('asc');
const { data, isLoading, refetch } = useQuery({
const { data, isLoading, isFetching, refetch } = useQuery({
queryKey: ['linguist-queue', activeRole, activeTab],
queryFn: () => apiClient.getMyLanguageQCQueue(
activeRole,
@ -125,9 +125,16 @@ export function LinguistQueue({ defaultRole }: LinguistQueueProps = {}) {
</div>
<button
onClick={() => refetch()}
className="text-sm text-gray-500 hover:text-gray-700 border border-gray-300 rounded px-3 py-1"
disabled={isFetching}
className="text-sm text-gray-500 hover:text-gray-700 border border-gray-300 rounded px-3 py-1 disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-1.5"
>
Refresh
{isFetching && (
<svg className="animate-spin h-3 w-3" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
</svg>
)}
{isFetching ? 'Refreshing…' : 'Refresh'}
</button>
</div>