feat: Enhance moderation queue preview

This commit is contained in:
gpt-engineer-app[bot]
2025-10-02 14:50:49 +00:00
parent 36603cdb80
commit 17c32edd9e
2 changed files with 197 additions and 14 deletions

View File

@@ -0,0 +1,191 @@
import { useState, useEffect } from 'react';
import { Badge } from '@/components/ui/badge';
import { Card, CardContent } from '@/components/ui/card';
import { supabase } from '@/integrations/supabase/client';
import { Image as ImageIcon } from 'lucide-react';
interface EntityEditPreviewProps {
submissionId: string;
entityType: string;
entityName?: string;
}
interface ImageAssignments {
uploaded: Array<{
url: string;
cloudflare_id: string;
}>;
banner_assignment: number | null;
card_assignment: number | null;
}
interface SubmissionItemData {
id: string;
item_data: any;
original_data?: any;
}
export const EntityEditPreview = ({ submissionId, entityType, entityName }: EntityEditPreviewProps) => {
const [loading, setLoading] = useState(true);
const [itemData, setItemData] = useState<any>(null);
const [originalData, setOriginalData] = useState<any>(null);
const [changedFields, setChangedFields] = useState<string[]>([]);
const [bannerImageUrl, setBannerImageUrl] = useState<string | null>(null);
const [cardImageUrl, setCardImageUrl] = useState<string | null>(null);
useEffect(() => {
fetchSubmissionItems();
}, [submissionId]);
const fetchSubmissionItems = async () => {
try {
setLoading(true);
const { data: items, error } = await supabase
.from('submission_items')
.select('*')
.eq('submission_id', submissionId)
.order('order_index', { ascending: true });
if (error) throw error;
if (items && items.length > 0) {
const firstItem = items[0];
setItemData(firstItem.item_data);
setOriginalData(firstItem.original_data);
// Parse changed fields
const changed: string[] = [];
const data = firstItem.item_data as any;
// Check for image changes
if (data.images) {
const images: ImageAssignments = data.images;
// Extract banner image
if (images.banner_assignment !== null && images.banner_assignment !== undefined) {
const bannerImg = images.uploaded[images.banner_assignment];
if (bannerImg) {
setBannerImageUrl(bannerImg.url);
changed.push('banner_image');
}
}
// Extract card image
if (images.card_assignment !== null && images.card_assignment !== undefined) {
const cardImg = images.uploaded[images.card_assignment];
if (cardImg) {
setCardImageUrl(cardImg.url);
changed.push('card_image');
}
}
}
// Check for other field changes by comparing with original_data
if (firstItem.original_data) {
const originalData = firstItem.original_data as any;
const excludeFields = ['images', 'updated_at', 'created_at'];
Object.keys(data).forEach(key => {
if (!excludeFields.includes(key) &&
data[key] !== originalData[key]) {
changed.push(key);
}
});
}
setChangedFields(changed);
}
} catch (error) {
console.error('Error fetching submission items:', error);
} finally {
setLoading(false);
}
};
if (loading) {
return (
<div className="text-sm text-muted-foreground">
Loading preview...
</div>
);
}
if (!itemData) {
return (
<div className="text-sm text-muted-foreground">
No preview available
</div>
);
}
return (
<div className="space-y-3">
<div className="flex items-center gap-2 flex-wrap">
<Badge variant="outline" className="capitalize">
{entityType}
</Badge>
<Badge variant="secondary">
Edit
</Badge>
</div>
{entityName && (
<div className="font-medium text-base">
{entityName}
</div>
)}
{changedFields.length > 0 && (
<div className="text-sm">
<span className="font-medium">Changed fields: </span>
<span className="text-muted-foreground">
{changedFields.map(field => field.replace(/_/g, ' ')).join(', ')}
</span>
</div>
)}
{(bannerImageUrl || cardImageUrl) && (
<div className="space-y-2">
<div className="font-medium text-sm flex items-center gap-2">
<ImageIcon className="w-4 h-4" />
Image Changes:
</div>
<div className="grid grid-cols-2 gap-2">
{bannerImageUrl && (
<Card className="overflow-hidden">
<CardContent className="p-2">
<img
src={`${bannerImageUrl}/thumbnail`}
alt="New banner"
className="w-full h-24 object-cover rounded"
/>
<div className="text-xs text-center mt-1 text-muted-foreground">
Banner
</div>
</CardContent>
</Card>
)}
{cardImageUrl && (
<Card className="overflow-hidden">
<CardContent className="p-2">
<img
src={`${cardImageUrl}/thumbnail`}
alt="New card"
className="w-full h-24 object-cover rounded"
/>
<div className="text-xs text-center mt-1 text-muted-foreground">
Card
</div>
</CardContent>
</Card>
)}
</div>
</div>
)}
<div className="text-xs text-muted-foreground italic">
Click "Review Items" for full details
</div>
</div>
);
};

View File

@@ -16,6 +16,7 @@ import { PhotoModal } from './PhotoModal';
import { SubmissionReviewManager } from './SubmissionReviewManager';
import { useRealtimeSubmissions } from '@/hooks/useRealtimeSubmissions';
import { useIsMobile } from '@/hooks/use-mobile';
import { EntityEditPreview } from './EntityEditPreview';
interface ModerationItem {
id: string;
@@ -1332,20 +1333,11 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
item.submission_type === 'property_owner' ||
item.submission_type === 'park' ||
item.submission_type === 'ride') ? (
<div>
<div className="text-sm text-muted-foreground mb-3 flex items-center gap-2">
<Badge variant="outline" className="capitalize">
{item.submission_type}
</Badge>
<Badge variant={item.content.action === 'create' ? 'default' : 'secondary'}>
{item.content.action === 'create' ? 'New' : 'Edit'}
</Badge>
</div>
<div className="text-sm text-muted-foreground mb-2">
This is a complex submission with items. Click "Review Items" to see details.
</div>
</div>
<EntityEditPreview
submissionId={item.id}
entityType={item.submission_type}
entityName={item.content.name || item.entity_name}
/>
) : (
<div>
<div className="text-sm text-muted-foreground mb-2">