Files
thrilltrack-explorer/src/components/moderation/ConflictResolutionDialog.tsx
2025-11-03 00:58:42 +00:00

133 lines
4.7 KiB
TypeScript

import { useState } from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { Label } from '@/components/ui/label';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { AlertCircle } from 'lucide-react';
import { type DependencyConflict, type SubmissionItemWithDeps } from '@/lib/submissionItemsService';
import { useAuth } from '@/hooks/useAuth';
import { handleError, handleSuccess } from '@/lib/errorHandler';
interface ConflictResolutionDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
conflicts: DependencyConflict[];
items: SubmissionItemWithDeps[];
onResolve: () => void;
}
export function ConflictResolutionDialog({
open,
onOpenChange,
conflicts,
items,
onResolve,
}: ConflictResolutionDialogProps) {
const [resolutions, setResolutions] = useState<Record<string, string>>({});
const { user } = useAuth();
const handleResolutionChange = (itemId: string, action: string) => {
setResolutions(prev => ({ ...prev, [itemId]: action }));
};
const allConflictsResolved = conflicts.every(
conflict => resolutions[conflict.itemId]
);
const handleApply = async () => {
if (!user?.id) {
handleError(new Error('Authentication required'), {
action: 'Resolve Conflicts',
metadata: { conflictCount: conflicts.length }
});
return;
}
const { resolveConflicts } = await import('@/lib/conflictResolutionService');
try {
const result = await resolveConflicts(conflicts, resolutions, items, user.id);
if (!result.success) {
handleError(new Error(result.error || 'Failed to resolve conflicts'), {
action: 'Resolve Conflicts',
userId: user.id,
metadata: { conflictCount: conflicts.length }
});
return;
}
handleSuccess('Conflicts Resolved', 'All conflicts have been resolved successfully');
onResolve();
onOpenChange(false);
} catch (error: unknown) {
handleError(error, {
action: 'Resolve Conflicts',
userId: user.id,
metadata: { conflictCount: conflicts.length }
});
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle>Resolve Dependency Conflicts</DialogTitle>
<DialogDescription>
{conflicts.length} conflict(s) found. Choose how to resolve each one.
</DialogDescription>
</DialogHeader>
<div className="space-y-6 py-4">
{conflicts.map((conflict) => {
const item = items.find(i => i.id === conflict.itemId);
return (
<div key={conflict.itemId} className="space-y-3">
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>
<p className="font-medium">
{item?.item_type.replace('_', ' ').toUpperCase()}: {
item && typeof item.item_data === 'object' && item.item_data !== null && !Array.isArray(item.item_data) && 'name' in item.item_data
? String((item.item_data as Record<string, unknown>).name)
: 'Unnamed'
}
</p>
<p className="text-sm mt-1">{conflict.message}</p>
</AlertDescription>
</Alert>
<RadioGroup
value={resolutions[conflict.itemId] || ''}
onValueChange={(value) => handleResolutionChange(conflict.itemId, value)}
>
{conflict.suggestions.map((suggestion, idx) => (
<div key={idx} className="flex items-center space-x-2">
<RadioGroupItem value={suggestion.action} id={`${conflict.itemId}-${idx}`} />
<Label htmlFor={`${conflict.itemId}-${idx}`} className="cursor-pointer">
{suggestion.label}
</Label>
</div>
))}
</RadioGroup>
</div>
);
})}
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button onClick={handleApply} disabled={!allConflictsResolved}>
Apply & Approve
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}