Fix real-time stats and submission refresh

This commit is contained in:
gpt-engineer-app[bot]
2025-10-03 17:06:44 +00:00
parent 1cf13efc97
commit 2d0a45b5b0
2 changed files with 243 additions and 13 deletions

View File

@@ -348,12 +348,89 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
// Set up realtime subscriptions // Set up realtime subscriptions
const { connectionState: submissionsConnectionState, reconnect: reconnectSubmissions } = useRealtimeSubmissions({ const { connectionState: submissionsConnectionState, reconnect: reconnectSubmissions } = useRealtimeSubmissions({
onInsert: (payload) => { onInsert: async (payload) => {
const newSubmission = payload.new;
// Only add if it matches current filters
const matchesStatusFilter =
activeStatusFilter === 'all' ||
(activeStatusFilter === 'pending' && (newSubmission.status === 'pending' || newSubmission.status === 'partially_approved')) ||
activeStatusFilter === newSubmission.status;
const matchesEntityFilter =
activeEntityFilter === 'all' ||
(activeEntityFilter === 'submissions' && newSubmission.submission_type !== 'photo') ||
(activeEntityFilter === 'photos' && newSubmission.submission_type === 'photo');
if (!matchesStatusFilter || !matchesEntityFilter) return;
// Fetch minimal data for the new submission
try {
const { data: profile } = await supabase
.from('profiles')
.select('user_id, username, display_name, avatar_url')
.eq('user_id', newSubmission.user_id)
.single();
// Fetch entity name if photo submission
let entity_name, park_name;
if (newSubmission.submission_type === 'photo' && newSubmission.content) {
const contentObj = newSubmission.content as any;
const contextType = typeof contentObj.context === 'string' ? contentObj.context : null;
const entityId = contentObj.entity_id || contentObj.ride_id || contentObj.park_id || contentObj.company_id;
if (contextType === 'ride' && entityId) {
const { data: rideData } = await supabase
.from('rides')
.select('name, parks:park_id(name)')
.eq('id', entityId)
.single();
if (rideData) {
entity_name = rideData.name;
park_name = rideData.parks?.name;
}
} else if (contextType === 'park' && entityId) {
const { data: parkData } = await supabase
.from('parks')
.select('name')
.eq('id', entityId)
.single();
if (parkData) entity_name = parkData.name;
} else if (['manufacturer', 'operator', 'designer', 'property_owner'].includes(contextType) && entityId) {
const { data: companyData } = await supabase
.from('companies')
.select('name')
.eq('id', entityId)
.single();
if (companyData) entity_name = companyData.name;
}
}
// Create new item and prepend to list
const newItem: ModerationItem = {
id: newSubmission.id,
type: 'content_submission',
content: newSubmission.submission_type === 'photo' ? newSubmission.content : newSubmission,
created_at: newSubmission.created_at,
user_id: newSubmission.user_id,
status: newSubmission.status,
submission_type: newSubmission.submission_type,
user_profile: profile || undefined,
entity_name,
park_name,
};
setItems(prevItems => [newItem, ...prevItems]);
toast({ toast({
title: 'New Submission', title: 'New Submission',
description: 'A new content submission has been added', description: 'A new content submission has been added',
}); });
} catch (error) {
console.error('Error adding new submission to queue:', error);
// Fallback to full refresh on error
fetchItems(activeEntityFilter, activeStatusFilter); fetchItems(activeEntityFilter, activeStatusFilter);
}
}, },
onUpdate: (payload) => { onUpdate: (payload) => {
// Update items state directly for better UX // Update items state directly for better UX

View File

@@ -108,36 +108,189 @@ export const useRealtimeModerationStats = (options: UseRealtimeModerationStatsOp
.on( .on(
'postgres_changes', 'postgres_changes',
{ {
event: '*', event: 'INSERT',
schema: 'public', schema: 'public',
table: 'content_submissions', table: 'content_submissions',
}, },
() => { (payload) => {
console.log('Content submissions changed'); console.log('Content submission inserted');
// Optimistic update: increment pending submissions
if (payload.new.status === 'pending') {
setStats(prev => ({
...prev,
pendingSubmissions: prev.pendingSubmissions + 1
}));
}
// Debounced sync as backup
debouncedFetchStats(); debouncedFetchStats();
} }
) )
.on( .on(
'postgres_changes', 'postgres_changes',
{ {
event: '*', event: 'UPDATE',
schema: 'public',
table: 'content_submissions',
},
(payload) => {
console.log('Content submission updated');
// Optimistic update: adjust counter based on status change
const oldStatus = payload.old.status;
const newStatus = payload.new.status;
if (oldStatus === 'pending' && newStatus !== 'pending') {
setStats(prev => ({
...prev,
pendingSubmissions: Math.max(0, prev.pendingSubmissions - 1)
}));
} else if (oldStatus !== 'pending' && newStatus === 'pending') {
setStats(prev => ({
...prev,
pendingSubmissions: prev.pendingSubmissions + 1
}));
}
debouncedFetchStats();
}
)
.on(
'postgres_changes',
{
event: 'DELETE',
schema: 'public',
table: 'content_submissions',
},
(payload) => {
console.log('Content submission deleted');
// Optimistic update: decrement if was pending
if (payload.old.status === 'pending') {
setStats(prev => ({
...prev,
pendingSubmissions: Math.max(0, prev.pendingSubmissions - 1)
}));
}
debouncedFetchStats();
}
)
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public', schema: 'public',
table: 'reports', table: 'reports',
}, },
() => { () => {
console.log('Reports changed'); console.log('Report inserted');
setStats(prev => ({
...prev,
openReports: prev.openReports + 1
}));
debouncedFetchStats(); debouncedFetchStats();
} }
) )
.on( .on(
'postgres_changes', 'postgres_changes',
{ {
event: '*', event: 'UPDATE',
schema: 'public',
table: 'reports',
},
(payload) => {
console.log('Report updated');
const oldStatus = payload.old.status;
const newStatus = payload.new.status;
if (oldStatus === 'pending' && newStatus !== 'pending') {
setStats(prev => ({
...prev,
openReports: Math.max(0, prev.openReports - 1)
}));
} else if (oldStatus !== 'pending' && newStatus === 'pending') {
setStats(prev => ({
...prev,
openReports: prev.openReports + 1
}));
}
debouncedFetchStats();
}
)
.on(
'postgres_changes',
{
event: 'DELETE',
schema: 'public',
table: 'reports',
},
(payload) => {
console.log('Report deleted');
if (payload.old.status === 'pending') {
setStats(prev => ({
...prev,
openReports: Math.max(0, prev.openReports - 1)
}));
}
debouncedFetchStats();
}
)
.on(
'postgres_changes',
{
event: 'INSERT',
schema: 'public', schema: 'public',
table: 'reviews', table: 'reviews',
}, },
() => { () => {
console.log('Reviews changed'); console.log('Review inserted');
setStats(prev => ({
...prev,
flaggedContent: prev.flaggedContent + 1
}));
debouncedFetchStats();
}
)
.on(
'postgres_changes',
{
event: 'UPDATE',
schema: 'public',
table: 'reviews',
},
(payload) => {
console.log('Review updated');
const oldStatus = payload.old.moderation_status;
const newStatus = payload.new.moderation_status;
if (oldStatus === 'flagged' && newStatus !== 'flagged') {
setStats(prev => ({
...prev,
flaggedContent: Math.max(0, prev.flaggedContent - 1)
}));
} else if (oldStatus !== 'flagged' && newStatus === 'flagged') {
setStats(prev => ({
...prev,
flaggedContent: prev.flaggedContent + 1
}));
}
debouncedFetchStats();
}
)
.on(
'postgres_changes',
{
event: 'DELETE',
schema: 'public',
table: 'reviews',
},
(payload) => {
console.log('Review deleted');
if (payload.old.moderation_status === 'flagged') {
setStats(prev => ({
...prev,
flaggedContent: Math.max(0, prev.flaggedContent - 1)
}));
}
debouncedFetchStats(); debouncedFetchStats();
} }
); );