mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-22 15:11:13 -05:00
Refactor code structure and remove redundant changes
This commit is contained in:
338
src-old/components/moderation/SubmissionItemsList.tsx
Normal file
338
src-old/components/moderation/SubmissionItemsList.tsx
Normal file
@@ -0,0 +1,338 @@
|
||||
import { useState, useEffect, memo } from 'react';
|
||||
import { supabase } from '@/lib/supabaseClient';
|
||||
import { SubmissionChangesDisplay } from './SubmissionChangesDisplay';
|
||||
import { PhotoSubmissionDisplay } from './PhotoSubmissionDisplay';
|
||||
import { RichParkDisplay } from './displays/RichParkDisplay';
|
||||
import { RichRideDisplay } from './displays/RichRideDisplay';
|
||||
import { RichCompanyDisplay } from './displays/RichCompanyDisplay';
|
||||
import { RichRideModelDisplay } from './displays/RichRideModelDisplay';
|
||||
import { RichTimelineEventDisplay } from './displays/RichTimelineEventDisplay';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { AlertCircle, Loader2 } from 'lucide-react';
|
||||
import { format } from 'date-fns';
|
||||
import type { SubmissionItemData } from '@/types/submissions';
|
||||
import type { ParkSubmissionData, RideSubmissionData, CompanySubmissionData, RideModelSubmissionData } from '@/types/submission-data';
|
||||
import type { TimelineSubmissionData } from '@/types/timeline';
|
||||
import { getErrorMessage, handleNonCriticalError } from '@/lib/errorHandler';
|
||||
import { ModerationErrorBoundary } from '@/components/error/ModerationErrorBoundary';
|
||||
|
||||
interface SubmissionItemsListProps {
|
||||
submissionId: string;
|
||||
view?: 'summary' | 'detailed';
|
||||
showImages?: boolean;
|
||||
}
|
||||
|
||||
export const SubmissionItemsList = memo(function SubmissionItemsList({
|
||||
submissionId,
|
||||
view = 'summary',
|
||||
showImages = true
|
||||
}: SubmissionItemsListProps) {
|
||||
const [items, setItems] = useState<SubmissionItemData[]>([]);
|
||||
const [hasPhotos, setHasPhotos] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchSubmissionItems();
|
||||
}, [submissionId]);
|
||||
|
||||
const fetchSubmissionItems = async () => {
|
||||
try {
|
||||
// Only show skeleton on initial load, show refreshing indicator on refresh
|
||||
if (loading) {
|
||||
setLoading(true);
|
||||
} else {
|
||||
setRefreshing(true);
|
||||
}
|
||||
setError(null);
|
||||
|
||||
// Use database function to fetch submission items with entity data in one query
|
||||
// This eliminates N+1 query problem and properly handles RLS/AAL2 checks
|
||||
const { data: itemsData, error: itemsError } = await supabase
|
||||
.rpc('get_submission_items_with_entities', {
|
||||
p_submission_id: submissionId
|
||||
});
|
||||
|
||||
if (itemsError) throw itemsError;
|
||||
|
||||
// Transform to expected format with better null handling
|
||||
const transformedItems = (itemsData || []).map((item: any) => {
|
||||
// Ensure entity_data is at least an empty object, never null
|
||||
const safeEntityData = item.entity_data && typeof item.entity_data === 'object'
|
||||
? item.entity_data
|
||||
: {};
|
||||
|
||||
return {
|
||||
...item,
|
||||
item_data: safeEntityData,
|
||||
entity_data: item.entity_data // Keep original for debugging
|
||||
};
|
||||
});
|
||||
|
||||
// Check for photo submissions (using array query to avoid 406)
|
||||
const { data: photoData, error: photoError } = await supabase
|
||||
.from('photo_submissions')
|
||||
.select('id')
|
||||
.eq('submission_id', submissionId);
|
||||
|
||||
if (photoError) {
|
||||
handleNonCriticalError(photoError, {
|
||||
action: 'Check photo submissions',
|
||||
metadata: { submissionId }
|
||||
});
|
||||
}
|
||||
|
||||
setItems(transformedItems as SubmissionItemData[]);
|
||||
setHasPhotos(!!(photoData && photoData.length > 0));
|
||||
} catch (err) {
|
||||
handleNonCriticalError(err, {
|
||||
action: 'Fetch submission items',
|
||||
metadata: { submissionId }
|
||||
});
|
||||
setError('Failed to load submission details');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setRefreshing(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Skeleton className="h-16 w-full" />
|
||||
{view === 'detailed' && <Skeleton className="h-32 w-full" />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertDescription>{error}</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
if (items.length === 0 && !hasPhotos) {
|
||||
return (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
No items found for this submission
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Render item with appropriate display component
|
||||
const renderItem = (item: SubmissionItemData) => {
|
||||
// SubmissionItemData from submissions.ts has item_data property
|
||||
const entityData = item.item_data;
|
||||
const actionType = item.action_type || 'create';
|
||||
|
||||
// Show item metadata (order_index, depends_on, timestamps, test data flag)
|
||||
const itemMetadata = (
|
||||
<div className="flex flex-wrap items-center gap-2 mb-2 text-xs text-muted-foreground">
|
||||
<Badge variant="outline" className="font-mono">
|
||||
#{item.order_index ?? 0}
|
||||
</Badge>
|
||||
|
||||
{item.depends_on && (
|
||||
<Badge variant="outline" className="text-xs">
|
||||
Depends on: {item.depends_on.slice(0, 8)}...
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{(item as any).is_test_data && (
|
||||
<Badge variant="outline" className="bg-purple-100 dark:bg-purple-900/30 text-purple-800 dark:text-purple-300">
|
||||
Test Data
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{item.created_at && (
|
||||
<span className="font-mono">
|
||||
Created: {format(new Date(item.created_at), 'MMM d, HH:mm:ss')}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{item.updated_at && item.updated_at !== item.created_at && (
|
||||
<span className="font-mono">
|
||||
Updated: {format(new Date(item.updated_at), 'MMM d, HH:mm:ss')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
// Use summary view for compact display
|
||||
if (view === 'summary') {
|
||||
return (
|
||||
<>
|
||||
{itemMetadata}
|
||||
<SubmissionChangesDisplay
|
||||
item={item}
|
||||
view={view}
|
||||
showImages={showImages}
|
||||
submissionId={submissionId}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Use rich displays for detailed view - show BOTH rich display AND field-by-field changes
|
||||
if (item.item_type === 'park' && entityData) {
|
||||
return (
|
||||
<>
|
||||
{itemMetadata}
|
||||
<RichParkDisplay
|
||||
data={entityData as unknown as ParkSubmissionData}
|
||||
actionType={actionType}
|
||||
/>
|
||||
<div className="mt-6 pt-6 border-t">
|
||||
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-3">
|
||||
All Fields (Detailed View)
|
||||
</div>
|
||||
<SubmissionChangesDisplay
|
||||
item={item}
|
||||
view="detailed"
|
||||
showImages={showImages}
|
||||
submissionId={submissionId}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (item.item_type === 'ride' && entityData) {
|
||||
return (
|
||||
<>
|
||||
{itemMetadata}
|
||||
<RichRideDisplay
|
||||
data={entityData as unknown as RideSubmissionData}
|
||||
actionType={actionType}
|
||||
/>
|
||||
<div className="mt-6 pt-6 border-t">
|
||||
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-3">
|
||||
All Fields (Detailed View)
|
||||
</div>
|
||||
<SubmissionChangesDisplay
|
||||
item={item}
|
||||
view="detailed"
|
||||
showImages={showImages}
|
||||
submissionId={submissionId}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if ((['manufacturer', 'operator', 'designer', 'property_owner'] as const).some(type => type === item.item_type) && entityData) {
|
||||
return (
|
||||
<>
|
||||
{itemMetadata}
|
||||
<RichCompanyDisplay
|
||||
data={entityData as unknown as CompanySubmissionData}
|
||||
actionType={actionType}
|
||||
/>
|
||||
<div className="mt-6 pt-6 border-t">
|
||||
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-3">
|
||||
All Fields (Detailed View)
|
||||
</div>
|
||||
<SubmissionChangesDisplay
|
||||
item={item}
|
||||
view="detailed"
|
||||
showImages={showImages}
|
||||
submissionId={submissionId}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (item.item_type === 'ride_model' && entityData) {
|
||||
return (
|
||||
<>
|
||||
{itemMetadata}
|
||||
<RichRideModelDisplay
|
||||
data={entityData as unknown as RideModelSubmissionData}
|
||||
actionType={actionType}
|
||||
/>
|
||||
<div className="mt-6 pt-6 border-t">
|
||||
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-3">
|
||||
All Fields (Detailed View)
|
||||
</div>
|
||||
<SubmissionChangesDisplay
|
||||
item={item}
|
||||
view="detailed"
|
||||
showImages={showImages}
|
||||
submissionId={submissionId}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if ((item.item_type === 'milestone' || item.item_type === 'timeline_event') && entityData) {
|
||||
return (
|
||||
<>
|
||||
{itemMetadata}
|
||||
<RichTimelineEventDisplay
|
||||
data={entityData as unknown as TimelineSubmissionData}
|
||||
actionType={actionType}
|
||||
/>
|
||||
<div className="mt-6 pt-6 border-t">
|
||||
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wide mb-3">
|
||||
All Fields (Detailed View)
|
||||
</div>
|
||||
<SubmissionChangesDisplay
|
||||
item={item}
|
||||
view="detailed"
|
||||
showImages={showImages}
|
||||
submissionId={submissionId}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Fallback to SubmissionChangesDisplay
|
||||
return (
|
||||
<>
|
||||
{itemMetadata}
|
||||
<SubmissionChangesDisplay
|
||||
item={item}
|
||||
view={view}
|
||||
showImages={showImages}
|
||||
submissionId={submissionId}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<ModerationErrorBoundary submissionId={submissionId}>
|
||||
<div className={view === 'summary' ? 'flex flex-col gap-3' : 'flex flex-col gap-6'}>
|
||||
{refreshing && (
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<Loader2 className="h-3 w-3 animate-spin" />
|
||||
<span>Refreshing...</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Show regular submission items */}
|
||||
{items.map((item) => (
|
||||
<div key={item.id} className={view === 'summary' ? 'border-l-2 border-primary/20 pl-3' : ''}>
|
||||
{renderItem(item)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Show photo submission if exists */}
|
||||
{hasPhotos && (
|
||||
<div className={view === 'summary' ? 'border-l-2 border-primary/20 pl-3' : ''}>
|
||||
<PhotoSubmissionDisplay submissionId={submissionId} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ModerationErrorBoundary>
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user