Implement Phase 6: Lock Management

This commit is contained in:
gpt-engineer-app[bot]
2025-10-12 22:57:37 +00:00
parent 7e6b99a68b
commit 0d0e352a1e
6 changed files with 281 additions and 70 deletions

View File

@@ -33,6 +33,8 @@ import { smartMergeArray } from '@/lib/smartStateUpdate';
import { useDebounce } from '@/hooks/useDebounce';
import { QueueItem } from './QueueItem';
import { QueueSkeleton } from './QueueSkeleton';
import { LockStatusDisplay } from './LockStatusDisplay';
import { getLockStatus } from '@/lib/moderation/lockHelpers';
import type {
ModerationItem,
EntityFilter,
@@ -60,7 +62,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
const [selectedPhotoIndex, setSelectedPhotoIndex] = useState(0);
const [reviewManagerOpen, setReviewManagerOpen] = useState(false);
const [selectedSubmissionId, setSelectedSubmissionId] = useState<string | null>(null);
const [lockedSubmissions, setLockedSubmissions] = useState<Set<string>>(new Set());
const [escalationDialogOpen, setEscalationDialogOpen] = useState(false);
const [reassignDialogOpen, setReassignDialogOpen] = useState(false);
const [selectedItemForAction, setSelectedItemForAction] = useState<ModerationItem | null>(null);
@@ -1969,7 +1970,9 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
item={item}
isMobile={isMobile}
actionLoading={actionLoading}
lockedSubmissions={lockedSubmissions}
isLockedByMe={queue.isLockedByMe(item.id)}
isLockedByOther={queue.isLockedByOther(item.id, item.assigned_to, item.locked_until)}
lockStatus={getLockStatus({ assigned_to: item.assigned_to, locked_until: item.locked_until }, user?.id || '')}
currentLockSubmissionId={queue.currentLock?.submissionId}
notes={notes}
isAdmin={isAdmin()}
@@ -2008,13 +2011,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
}
};
// Helper to format lock timer
const formatLockTimer = (ms: number): string => {
const minutes = Math.floor(ms / 60000);
const seconds = Math.floor((ms % 60000) / 1000);
return `${minutes}:${seconds.toString().padStart(2, '0')}`;
};
// Handle claim next action
const handleClaimNext = async () => {
await queue.claimNext();
@@ -2047,56 +2043,16 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
</div>
{/* Claim/Lock Status */}
<div className="flex flex-col gap-2 min-w-[200px]">
{queue.currentLock ? (
<>
{/* Lock Timer */}
<div className="flex items-center gap-2 text-sm">
<Lock className="w-4 h-4 text-amber-500" />
<span className="font-medium">
Lock: {formatLockTimer(queue.getTimeRemaining() || 0)}
</span>
</div>
<Progress
value={((queue.getTimeRemaining() || 0) / (15 * 60 * 1000)) * 100}
className="h-2"
/>
{/* Extend Lock Button (show when < 5 min left) */}
{(queue.getTimeRemaining() || 0) < 5 * 60 * 1000 && (
<Button
size="sm"
variant="outline"
onClick={() => queue.extendLock(queue.currentLock!.submissionId)}
disabled={queue.isLoading}
className="w-full"
>
<Clock className="w-4 h-4 mr-2" />
Extend Lock
</Button>
)}
<Button
size="sm"
variant="ghost"
onClick={() => queue.releaseLock(queue.currentLock!.submissionId)}
disabled={queue.isLoading}
className="w-full"
>
<Unlock className="w-4 h-4 mr-2" />
Release Lock
</Button>
</>
) : (
<Button
size="lg"
onClick={handleClaimNext}
disabled={queue.isLoading || queue.queueStats.pendingCount === 0}
className="w-full"
>
<Lock className="w-4 h-4 mr-2" />
Claim Next Submission
</Button>
)}
</div>
<LockStatusDisplay
currentLock={queue.currentLock}
queueStats={queue.queueStats}
isLoading={queue.isLoading}
onClaimNext={handleClaimNext}
onExtendLock={queue.extendLock}
onReleaseLock={queue.releaseLock}
getTimeRemaining={queue.getTimeRemaining}
getLockProgress={queue.getLockProgress}
/>
</div>
</CardContent>
</Card>