feat: Implement comprehensive validation error handling

This commit is contained in:
gpt-engineer-app[bot]
2025-11-05 19:00:28 +00:00
parent 882959bce6
commit d29e873e14
4 changed files with 290 additions and 105 deletions

View File

@@ -247,28 +247,68 @@ export function SubmissionReviewManager({
}
// Run validation on all selected items
const validationResultsMap = await validateMultipleItems(
selectedItems.map(item => ({
item_type: item.item_type,
item_data: item.item_data,
id: item.id
}))
);
let validationResultsMap: Map<string, any>;
setValidationResults(validationResultsMap);
// Check for blocking errors
const itemsWithBlockingErrors = selectedItems.filter(item => {
const result = validationResultsMap.get(item.id);
return result && result.blockingErrors.length > 0;
});
// CRITICAL: Blocking errors can NEVER be bypassed, regardless of warnings
if (itemsWithBlockingErrors.length > 0) {
setHasBlockingErrors(true);
setShowValidationBlockerDialog(true);
dispatch({ type: 'ERROR', payload: { error: 'Validation failed' } });
return; // Block approval
try {
validationResultsMap = await validateMultipleItems(
selectedItems.map(item => ({
item_type: item.item_type,
item_data: item.item_data,
id: item.id
}))
);
setValidationResults(validationResultsMap);
// Check for blocking errors
const itemsWithBlockingErrors = selectedItems.filter(item => {
const result = validationResultsMap.get(item.id);
return result && result.blockingErrors.length > 0;
});
// CRITICAL: Blocking errors can NEVER be bypassed, regardless of warnings
if (itemsWithBlockingErrors.length > 0) {
// Log which items have blocking errors
itemsWithBlockingErrors.forEach(item => {
const result = validationResultsMap.get(item.id);
logger.error('Blocking validation errors prevent approval', {
submissionId,
itemId: item.id,
itemType: item.item_type,
errors: result?.blockingErrors
});
});
setHasBlockingErrors(true);
setShowValidationBlockerDialog(true);
dispatch({ type: 'ERROR', payload: { error: 'Validation failed' } });
return; // Block approval
}
} catch (error) {
// Validation itself failed (network error, bug, etc.)
const errorId = handleError(error, {
action: 'Validation System Error',
userId: user?.id,
metadata: {
submissionId,
selectedItemCount: selectedItems.length,
itemTypes: selectedItems.map(i => i.item_type)
}
});
toast({
title: 'Validation System Error',
description: (
<div className="space-y-2">
<p>Unable to validate submission. Please try again.</p>
<p className="text-xs font-mono">Ref: {errorId.slice(0, 8)}</p>
</div>
),
variant: 'destructive'
});
dispatch({ type: 'ERROR', payload: { error: 'Validation system error' } });
return;
}
// Check for warnings

View File

@@ -1,4 +1,5 @@
import { AlertCircle } from 'lucide-react';
import { useState } from 'react';
import { AlertCircle, ChevronDown } from 'lucide-react';
import {
AlertDialog,
AlertDialogAction,
@@ -9,6 +10,9 @@ import {
AlertDialogTitle,
} from '@/components/ui/alert-dialog';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
import { ValidationError } from '@/lib/entityValidationSchemas';
interface ValidationBlockerDialogProps {
@@ -24,9 +28,11 @@ export function ValidationBlockerDialog({
blockingErrors,
itemNames,
}: ValidationBlockerDialogProps) {
const [showDetails, setShowDetails] = useState(false);
return (
<AlertDialog open={open} onOpenChange={onClose}>
<AlertDialogContent>
<AlertDialogContent className="max-w-2xl">
<AlertDialogHeader>
<AlertDialogTitle className="flex items-center gap-2 text-destructive">
<AlertCircle className="w-5 h-5" />
@@ -34,28 +40,51 @@ export function ValidationBlockerDialog({
</AlertDialogTitle>
<AlertDialogDescription>
The following items have blocking validation errors that MUST be fixed before approval.
These items cannot be approved until the errors are resolved. Please edit or reject them.
Edit the items to fix the errors, or reject them.
</AlertDialogDescription>
</AlertDialogHeader>
<div className="space-y-3 my-4">
{itemNames.map((name, index) => (
<div key={index} className="space-y-2">
<div className="font-medium text-sm">{name}</div>
<Alert variant="destructive">
<AlertDescription className="space-y-1">
{blockingErrors
.filter((_, i) => i === index || itemNames.length === 1)
.map((error, errIndex) => (
{itemNames.map((name, index) => {
const itemErrors = blockingErrors.filter((_, i) =>
itemNames.length === 1 || i === index
);
return (
<div key={index} className="space-y-2">
<div className="font-medium text-sm flex items-center justify-between">
<span>{name}</span>
<Badge variant="destructive">
{itemErrors.length} error{itemErrors.length > 1 ? 's' : ''}
</Badge>
</div>
<Alert variant="destructive">
<AlertDescription className="space-y-1">
{itemErrors.map((error, errIndex) => (
<div key={errIndex} className="text-sm">
<span className="font-medium">{error.field}:</span> {error.message}
</div>
))}
</AlertDescription>
</Alert>
</div>
))}
</AlertDescription>
</Alert>
</div>
);
})}
</div>
<Collapsible open={showDetails} onOpenChange={setShowDetails}>
<CollapsibleTrigger asChild>
<Button variant="ghost" size="sm" className="w-full">
{showDetails ? 'Hide' : 'Show'} Technical Details
<ChevronDown className={`ml-2 h-4 w-4 transition-transform ${showDetails ? 'rotate-180' : ''}`} />
</Button>
</CollapsibleTrigger>
<CollapsibleContent className="mt-2">
<div className="bg-muted p-3 rounded text-xs font-mono max-h-60 overflow-auto">
<pre>{JSON.stringify(blockingErrors, null, 2)}</pre>
</div>
</CollapsibleContent>
</Collapsible>
<AlertDialogFooter>
<AlertDialogAction onClick={onClose}>