mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 05:31:14 -05:00
feat: Enhance moderation queue preview
This commit is contained in:
191
src/components/moderation/EntityEditPreview.tsx
Normal file
191
src/components/moderation/EntityEditPreview.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -16,6 +16,7 @@ import { PhotoModal } from './PhotoModal';
|
|||||||
import { SubmissionReviewManager } from './SubmissionReviewManager';
|
import { SubmissionReviewManager } from './SubmissionReviewManager';
|
||||||
import { useRealtimeSubmissions } from '@/hooks/useRealtimeSubmissions';
|
import { useRealtimeSubmissions } from '@/hooks/useRealtimeSubmissions';
|
||||||
import { useIsMobile } from '@/hooks/use-mobile';
|
import { useIsMobile } from '@/hooks/use-mobile';
|
||||||
|
import { EntityEditPreview } from './EntityEditPreview';
|
||||||
|
|
||||||
interface ModerationItem {
|
interface ModerationItem {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -1332,20 +1333,11 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
|
|||||||
item.submission_type === 'property_owner' ||
|
item.submission_type === 'property_owner' ||
|
||||||
item.submission_type === 'park' ||
|
item.submission_type === 'park' ||
|
||||||
item.submission_type === 'ride') ? (
|
item.submission_type === 'ride') ? (
|
||||||
<div>
|
<EntityEditPreview
|
||||||
<div className="text-sm text-muted-foreground mb-3 flex items-center gap-2">
|
submissionId={item.id}
|
||||||
<Badge variant="outline" className="capitalize">
|
entityType={item.submission_type}
|
||||||
{item.submission_type}
|
entityName={item.content.name || item.entity_name}
|
||||||
</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>
|
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm text-muted-foreground mb-2">
|
<div className="text-sm text-muted-foreground mb-2">
|
||||||
|
|||||||
Reference in New Issue
Block a user