diff --git a/src/components/moderation/ItemEditDialog.tsx b/src/components/moderation/ItemEditDialog.tsx new file mode 100644 index 00000000..c1ddfee2 --- /dev/null +++ b/src/components/moderation/ItemEditDialog.tsx @@ -0,0 +1,215 @@ +import { useState } from 'react'; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; +import { Sheet, SheetContent, SheetHeader, SheetTitle } from '@/components/ui/sheet'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Button } from '@/components/ui/button'; +import { ParkForm } from '@/components/admin/ParkForm'; +import { RideForm } from '@/components/admin/RideForm'; +import { ManufacturerForm } from '@/components/admin/ManufacturerForm'; +import { DesignerForm } from '@/components/admin/DesignerForm'; +import { OperatorForm } from '@/components/admin/OperatorForm'; +import { PropertyOwnerForm } from '@/components/admin/PropertyOwnerForm'; +import { RideModelForm } from '@/components/admin/RideModelForm'; +import { PhotoCaptionEditor } from '@/components/upload/PhotoCaptionEditor'; +import { useIsMobile } from '@/hooks/use-mobile'; +import { editSubmissionItem } from '@/lib/submissionItemsService'; +import { useAuth } from '@/hooks/useAuth'; +import { toast } from '@/hooks/use-toast'; +import type { SubmissionItemWithDeps } from '@/lib/submissionItemsService'; + +interface ItemEditDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + item: SubmissionItemWithDeps | null; + onEditComplete: () => void; +} + +export function ItemEditDialog({ open, onOpenChange, item, onEditComplete }: ItemEditDialogProps) { + const isMobile = useIsMobile(); + const { user } = useAuth(); + const [isSubmitting, setIsSubmitting] = useState(false); + + if (!item) return null; + + const handleFormSubmit = async (data: any) => { + if (!user) { + toast({ + title: "Authentication Required", + description: "You must be logged in to edit items.", + variant: "destructive" + }); + return; + } + + setIsSubmitting(true); + try { + await editSubmissionItem(item.id, data, user.id); + + toast({ + title: "Item Updated", + description: "The submission item has been updated successfully." + }); + + onOpenChange(false); + onEditComplete(); + } catch (error: any) { + toast({ + title: "Error", + description: error.message || "Failed to update submission item.", + variant: "destructive" + }); + } finally { + setIsSubmitting(false); + } + }; + + const handleCancel = () => { + if (!isSubmitting) { + onOpenChange(false); + } + }; + + const renderForm = () => { + const itemData = item.item_data as any; + + switch (item.item_type) { + case 'park': + return ( + + ); + + case 'ride': + return ( + + ); + + case 'manufacturer': + return ( + + ); + + case 'designer': + return ( + + ); + + case 'operator': + return ( + + ); + + case 'property_owner': + return ( + + ); + + case 'ride_model': + return ( + + ); + + case 'photo': + return ( +
+
+ + { + itemData.caption = e.target.value; + }} + placeholder="Enter photo caption" + /> +
+ {itemData.image_url && ( + Preview + )} +
+ + +
+
+ ); + + default: + return ( +
+ Form not available for item type: {item.item_type} +
+ ); + } + }; + + const content = ( + <> + {isMobile ? ( + + Edit {item.item_type.replace('_', ' ')} + + ) : ( + + Edit {item.item_type.replace('_', ' ')} + + )} +
+ {renderForm()} +
+ + ); + + if (isMobile) { + return ( + + + {content} + + + ); + } + + return ( + + + {content} + + + ); +} diff --git a/src/components/moderation/ItemReviewCard.tsx b/src/components/moderation/ItemReviewCard.tsx index 0e6ce77a..9e29dd4b 100644 --- a/src/components/moderation/ItemReviewCard.tsx +++ b/src/components/moderation/ItemReviewCard.tsx @@ -141,6 +141,11 @@ export function ItemReviewCard({ item, onEdit, onStatusChange }: ItemReviewCardP )} + {item.original_data && ( + + Edited + + )} diff --git a/src/components/moderation/SubmissionReviewManager.tsx b/src/components/moderation/SubmissionReviewManager.tsx index ac8a7f0c..2f67f5b8 100644 --- a/src/components/moderation/SubmissionReviewManager.tsx +++ b/src/components/moderation/SubmissionReviewManager.tsx @@ -27,6 +27,7 @@ import { DependencyVisualizer } from './DependencyVisualizer'; import { ConflictResolutionDialog } from './ConflictResolutionDialog'; import { EscalationDialog } from './EscalationDialog'; import { RejectionDialog } from './RejectionDialog'; +import { ItemEditDialog } from './ItemEditDialog'; interface SubmissionReviewManagerProps { submissionId: string; @@ -48,6 +49,8 @@ export function SubmissionReviewManager({ const [showConflictDialog, setShowConflictDialog] = useState(false); const [showEscalationDialog, setShowEscalationDialog] = useState(false); const [showRejectionDialog, setShowRejectionDialog] = useState(false); + const [showEditDialog, setShowEditDialog] = useState(false); + const [editingItem, setEditingItem] = useState(null); const [activeTab, setActiveTab] = useState<'items' | 'dependencies'>('items'); const { toast } = useToast(); @@ -240,6 +243,12 @@ export function SubmissionReviewManager({ } }; + const handleEditComplete = async () => { + // Reload items after edit + await loadSubmissionItems(); + setEditingItem(null); + }; + const pendingCount = items.filter(item => item.status === 'pending').length; const selectedCount = selectedItemIds.size; @@ -292,6 +301,13 @@ export function SubmissionReviewManager({ )} onReject={handleReject} /> + + ); @@ -331,7 +347,10 @@ export function SubmissionReviewManager({ /> {/* TODO: Implement editing */}} + onEdit={() => { + setEditingItem(item); + setShowEditDialog(true); + }} onStatusChange={(status) => {/* TODO: Update status */}} /> diff --git a/src/lib/submissionItemsService.ts b/src/lib/submissionItemsService.ts index 416e0189..7a9b29fa 100644 --- a/src/lib/submissionItemsService.ts +++ b/src/lib/submissionItemsService.ts @@ -357,6 +357,102 @@ export async function resolveConflicts( return { updatedItems, newConflicts }; } +/** + * Edit a submission item + * Moderators can edit directly, regular users trigger auto-escalation + */ +export async function editSubmissionItem( + itemId: string, + newData: any, + userId: string +): Promise { + // Check user permissions + const { data: userRoles } = await supabase + .from('user_roles') + .select('role') + .eq('user_id', userId); + + const isModerator = userRoles?.some(r => + ['moderator', 'admin', 'superuser'].includes(r.role) + ); + + // Get current item + const { data: currentItem, error: fetchError } = await supabase + .from('submission_items') + .select('*') + .eq('id', itemId) + .single(); + + if (fetchError || !currentItem) { + throw new Error('Failed to fetch submission item'); + } + + // Preserve original data if not already saved + const originalData = currentItem.original_data || currentItem.item_data; + + if (isModerator) { + // Moderators can edit directly + const { error } = await supabase + .from('submission_items') + .update({ + item_data: newData, + original_data: originalData, + updated_at: new Date().toISOString() + }) + .eq('id', itemId); + + if (error) { + console.error('Error updating submission item:', error); + throw new Error(`Failed to update item: ${error.message}`); + } + + // Log the edit + await supabase + .from('admin_audit_log') + .insert({ + admin_user_id: userId, + target_user_id: currentItem.submission_id, + action: 'edit_submission_item', + details: { + item_id: itemId, + item_type: currentItem.item_type, + changes: newData + } + }); + } else { + // Regular users trigger auto-escalation + const { data: submission } = await supabase + .from('content_submissions') + .select('id, status') + .eq('id', currentItem.submission_id) + .single(); + + if (submission && submission.status !== 'escalated') { + await escalateSubmission( + currentItem.submission_id, + `User requested edit for ${currentItem.item_type} item`, + userId + ); + } + + // Update item with edit request + const { error } = await supabase + .from('submission_items') + .update({ + item_data: newData, + original_data: originalData, + status: 'pending', + updated_at: new Date().toISOString() + }) + .eq('id', itemId); + + if (error) { + console.error('Error updating submission item:', error); + throw new Error(`Failed to update item: ${error.message}`); + } + } +} + /** * Update individual submission item status */