Implement planned features

This commit is contained in:
gpt-engineer-app[bot]
2025-11-03 00:38:16 +00:00
parent ecca11a475
commit 061c06be29
10 changed files with 771 additions and 123 deletions

View File

@@ -13,8 +13,10 @@ import {
approveSubmissionItems,
rejectSubmissionItems,
escalateSubmission,
checkSubmissionConflict,
type SubmissionItemWithDeps,
type DependencyConflict
type DependencyConflict,
type ConflictCheckResult
} from '@/lib/submissionItemsService';
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from '@/components/ui/sheet';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog';
@@ -22,7 +24,7 @@ import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Badge } from '@/components/ui/badge';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { AlertCircle, CheckCircle2, XCircle, Edit, Network, ArrowUp } from 'lucide-react';
import { AlertCircle, CheckCircle2, XCircle, Edit, Network, ArrowUp, History } from 'lucide-react';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { ScrollArea } from '@/components/ui/scroll-area';
import { useIsMobile } from '@/hooks/use-mobile';
@@ -34,6 +36,8 @@ import { RejectionDialog } from './RejectionDialog';
import { ItemEditDialog } from './ItemEditDialog';
import { ValidationBlockerDialog } from './ValidationBlockerDialog';
import { WarningConfirmDialog } from './WarningConfirmDialog';
import { ConflictResolutionModal } from './ConflictResolutionModal';
import { EditHistoryAccordion } from './EditHistoryAccordion';
import { validateMultipleItems, ValidationResult } from '@/lib/entityValidationSchemas';
import { logger } from '@/lib/logger';
@@ -70,6 +74,9 @@ export function SubmissionReviewManager({
const [userConfirmedWarnings, setUserConfirmedWarnings] = useState(false);
const [hasBlockingErrors, setHasBlockingErrors] = useState(false);
const [globalValidationKey, setGlobalValidationKey] = useState(0);
const [conflictData, setConflictData] = useState<ConflictCheckResult | null>(null);
const [showConflictResolutionModal, setShowConflictResolutionModal] = useState(false);
const [lastModifiedTimestamp, setLastModifiedTimestamp] = useState<string | null>(null);
const { toast } = useToast();
const { isAdmin, isSuperuser } = useUserRole();
@@ -113,15 +120,16 @@ export function SubmissionReviewManager({
try {
const { supabase } = await import('@/integrations/supabase/client');
// Fetch submission type
// Fetch submission type and last_modified_at
const { data: submission } = await supabase
.from('content_submissions')
.select('submission_type')
.select('submission_type, last_modified_at')
.eq('id', submissionId)
.single();
if (submission) {
setSubmissionType(submission.submission_type || 'submission');
setLastModifiedTimestamp(submission.last_modified_at);
}
const fetchedItems = await fetchSubmissionItems(submissionId);
@@ -211,6 +219,18 @@ export function SubmissionReviewManager({
dispatch({ type: 'START_APPROVAL' });
try {
// Check for conflicts first (optimistic locking)
if (lastModifiedTimestamp) {
const conflictCheck = await checkSubmissionConflict(submissionId, lastModifiedTimestamp);
if (conflictCheck.hasConflict) {
setConflictData(conflictCheck);
setShowConflictResolutionModal(true);
dispatch({ type: 'RESET' }); // Return to reviewing state
return; // Block approval until conflict resolved
}
}
// Run validation on all selected items
const validationResultsMap = await validateMultipleItems(
selectedItems.map(item => ({
@@ -603,6 +623,43 @@ export function SubmissionReviewManager({
i.item_data?.name || i.item_type.replace('_', ' ')
)}
/>
<ConflictResolutionModal
open={showConflictResolutionModal}
onOpenChange={setShowConflictResolutionModal}
conflictData={conflictData}
onResolve={async (strategy) => {
if (strategy === 'keep-mine') {
// Log conflict resolution
const { supabase } = await import('@/integrations/supabase/client');
await supabase.from('conflict_resolutions').insert([{
submission_id: submissionId,
resolved_by: user?.id || null,
resolution_strategy: strategy,
conflict_details: conflictData as any,
}]);
// Force override and proceed with approval
await handleApprove();
} else if (strategy === 'keep-theirs') {
// Reload data and discard local changes
await loadSubmissionItems();
toast({
title: 'Changes Discarded',
description: 'Loaded the latest version from the server',
});
} else if (strategy === 'reload') {
// Just reload without approving
await loadSubmissionItems();
toast({
title: 'Reloaded',
description: 'Viewing the latest version',
});
}
setShowConflictResolutionModal(false);
setConflictData(null);
}}
/>
</>
);
@@ -724,13 +781,13 @@ export function SubmissionReviewManager({
<Tabs
value={activeTab}
onValueChange={(v) => {
if (v === 'items' || v === 'dependencies') {
setActiveTab(v);
if (v === 'items' || v === 'dependencies' || v === 'history') {
setActiveTab(v as 'items' | 'dependencies');
}
}}
className="flex-1 flex flex-col"
>
<TabsList className="grid w-full grid-cols-2">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="items">
<CheckCircle2 className="w-4 h-4 mr-2" />
Items ({items.length})
@@ -739,6 +796,10 @@ export function SubmissionReviewManager({
<Network className="w-4 h-4 mr-2" />
Dependencies
</TabsTrigger>
<TabsTrigger value="history">
<History className="w-4 h-4 mr-2" />
History
</TabsTrigger>
</TabsList>
<TabsContent value="items" className="flex-1 overflow-hidden">
@@ -778,6 +839,12 @@ export function SubmissionReviewManager({
<TabsContent value="dependencies" className="flex-1 overflow-hidden">
<DependencyVisualizer items={items} selectedIds={selectedItemIds} />
</TabsContent>
<TabsContent value="history" className="flex-1 overflow-hidden">
<ScrollArea className="h-full pr-4">
<EditHistoryAccordion submissionId={submissionId} />
</ScrollArea>
</TabsContent>
</Tabs>
{/* Blocking error alert */}