mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 08:11:13 -05:00
Refactor: Approve tool use
This commit is contained in:
@@ -128,39 +128,61 @@ interface PhotoDeletionPreviewProps {
|
|||||||
export function PhotoDeletionPreview({ photo, compact = false }: PhotoDeletionPreviewProps) {
|
export function PhotoDeletionPreview({ photo, compact = false }: PhotoDeletionPreviewProps) {
|
||||||
if (compact) {
|
if (compact) {
|
||||||
return (
|
return (
|
||||||
<Badge variant="outline" className="text-red-600 dark:text-red-400">
|
<div className="flex items-center gap-2 p-2 rounded-md bg-destructive/10 border border-destructive/20">
|
||||||
<Trash2 className="h-3 w-3 mr-1" />
|
<Trash2 className="h-4 w-4 text-destructive" />
|
||||||
Delete Photo
|
<span className="text-sm font-medium text-destructive">Delete Photo</span>
|
||||||
</Badge>
|
{photo.deletion_reason && (
|
||||||
|
<span className="text-xs text-muted-foreground">- {photo.deletion_reason}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2 p-3 rounded-md bg-destructive/10">
|
<div className="flex flex-col gap-3 p-4 rounded-lg bg-destructive/10 border-2 border-destructive/30">
|
||||||
<div className="text-sm font-medium text-red-600 dark:text-red-400">
|
<div className="flex items-center gap-2 text-destructive font-semibold">
|
||||||
<Trash2 className="h-4 w-4 inline mr-1" />
|
<Trash2 className="h-5 w-5" />
|
||||||
Deleting Photo
|
<span>Photo Deletion Request</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-4">
|
||||||
|
{photo.url && (
|
||||||
<img
|
<img
|
||||||
src={photo.url}
|
src={photo.url}
|
||||||
alt={photo.title || photo.caption || 'Photo to be deleted'}
|
alt={photo.title || photo.caption || 'Photo to be deleted'}
|
||||||
className="w-32 h-32 object-cover rounded opacity-75"
|
className="w-48 h-48 object-cover rounded-lg border-2 border-destructive/40 shadow-lg"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex-1 text-sm space-y-2">
|
<div className="flex-1 space-y-3">
|
||||||
{photo.title && <div className="font-medium">{photo.title}</div>}
|
{photo.title && (
|
||||||
{photo.caption && <div className="text-muted-foreground">{photo.caption}</div>}
|
<div>
|
||||||
{photo.entity_type && photo.entity_name && (
|
<span className="text-xs font-medium text-muted-foreground">Title:</span>
|
||||||
<div className="text-xs text-muted-foreground">
|
<div className="font-medium">{photo.title}</div>
|
||||||
From: <span className="capitalize">{photo.entity_type}</span> - {photo.entity_name}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{photo.caption && (
|
||||||
|
<div>
|
||||||
|
<span className="text-xs font-medium text-muted-foreground">Caption:</span>
|
||||||
|
<div className="text-sm">{photo.caption}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{photo.entity_type && photo.entity_name && (
|
||||||
|
<div>
|
||||||
|
<span className="text-xs font-medium text-muted-foreground">From Entity:</span>
|
||||||
|
<div className="text-sm">
|
||||||
|
<span className="capitalize">{photo.entity_type.replace('_', ' ')}</span> - <span className="font-medium">{photo.entity_name}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{photo.deletion_reason && (
|
{photo.deletion_reason && (
|
||||||
<div className="text-xs p-2 bg-destructive/5 rounded border border-destructive/20">
|
<div className="p-3 bg-destructive/20 rounded-md border border-destructive/30">
|
||||||
<span className="font-medium">Reason:</span> {photo.deletion_reason}
|
<span className="text-xs font-bold text-destructive uppercase">Deletion Reason:</span>
|
||||||
|
<p className="mt-1 text-sm font-medium">{photo.deletion_reason}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -85,6 +85,33 @@ export function SubmissionChangesDisplay({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (view === 'summary') {
|
if (view === 'summary') {
|
||||||
|
// Special compact display for photo deletions
|
||||||
|
if (item.item_type === 'photo_delete') {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
|
{getEntityIcon()}
|
||||||
|
<span className="font-medium">{changes.entityName}</span>
|
||||||
|
{getActionBadge()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{changes.photoChanges.length > 0 && changes.photoChanges[0].type === 'deleted' && (
|
||||||
|
<PhotoDeletionPreview
|
||||||
|
photo={{
|
||||||
|
url: changes.photoChanges[0].photo?.url || item.item_data?.cloudflare_image_url || '',
|
||||||
|
title: changes.photoChanges[0].photo?.title || item.item_data?.title,
|
||||||
|
caption: changes.photoChanges[0].photo?.caption || item.item_data?.caption,
|
||||||
|
entity_type: item.item_data?.entity_type,
|
||||||
|
entity_name: changes.entityName,
|
||||||
|
deletion_reason: changes.photoChanges[0].photo?.deletion_reason || item.item_data?.deletion_reason || item.item_data?.reason
|
||||||
|
}}
|
||||||
|
compact={true}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
@@ -146,7 +173,34 @@ export function SubmissionChangesDisplay({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detailed view
|
// Detailed view - special handling for photo deletions
|
||||||
|
if (item.item_type === 'photo_delete') {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{getEntityIcon()}
|
||||||
|
<h3 className="text-lg font-semibold">{changes.entityName}</h3>
|
||||||
|
{getActionBadge()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{changes.photoChanges.length > 0 && changes.photoChanges[0].type === 'deleted' && (
|
||||||
|
<PhotoDeletionPreview
|
||||||
|
photo={{
|
||||||
|
url: changes.photoChanges[0].photo?.url || item.item_data?.cloudflare_image_url || '',
|
||||||
|
title: changes.photoChanges[0].photo?.title || item.item_data?.title,
|
||||||
|
caption: changes.photoChanges[0].photo?.caption || item.item_data?.caption,
|
||||||
|
entity_type: item.item_data?.entity_type,
|
||||||
|
entity_name: changes.entityName,
|
||||||
|
deletion_reason: changes.photoChanges[0].photo?.deletion_reason || item.item_data?.deletion_reason || item.item_data?.reason
|
||||||
|
}}
|
||||||
|
compact={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detailed view for other items
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
|||||||
@@ -136,10 +136,13 @@ export async function detectChanges(
|
|||||||
const itemData = item.item_data || {};
|
const itemData = item.item_data || {};
|
||||||
const originalData = item.original_data || {};
|
const originalData = item.original_data || {};
|
||||||
|
|
||||||
// Determine action type
|
// Determine action type - special handling for photo_delete
|
||||||
const action: 'create' | 'edit' | 'delete' =
|
let action: 'create' | 'edit' | 'delete' = 'edit';
|
||||||
!originalData || Object.keys(originalData).length === 0 ? 'create' :
|
if (item.item_type === 'photo_delete' || itemData.action === 'delete' || itemData.deleted) {
|
||||||
itemData.deleted ? 'delete' : 'edit';
|
action = 'delete';
|
||||||
|
} else if (!originalData || Object.keys(originalData).length === 0) {
|
||||||
|
action = 'create';
|
||||||
|
}
|
||||||
|
|
||||||
const fieldChanges: FieldChange[] = [];
|
const fieldChanges: FieldChange[] = [];
|
||||||
const imageChanges: ImageChange[] = [];
|
const imageChanges: ImageChange[] = [];
|
||||||
@@ -269,22 +272,32 @@ export async function detectChanges(
|
|||||||
const entityId = itemData.entity_id;
|
const entityId = itemData.entity_id;
|
||||||
|
|
||||||
if (entityType === 'park') {
|
if (entityType === 'park') {
|
||||||
const { data } = await supabase.from('parks').select('name').eq('id', entityId).single();
|
const { data } = await supabase.from('parks').select('name').eq('id', entityId).maybeSingle();
|
||||||
if (data?.name) entityName = `${data.name} (${formatEntityType(entityType)})`;
|
if (data?.name) entityName = `${data.name} (${formatEntityType(entityType)})`;
|
||||||
} else if (entityType === 'ride') {
|
} else if (entityType === 'ride') {
|
||||||
const { data } = await supabase.from('rides').select('name').eq('id', entityId).single();
|
const { data } = await supabase.from('rides').select('name').eq('id', entityId).maybeSingle();
|
||||||
if (data?.name) entityName = `${data.name} (${formatEntityType(entityType)})`;
|
if (data?.name) entityName = `${data.name} (${formatEntityType(entityType)})`;
|
||||||
} else if (entityType === 'ride_model') {
|
} else if (entityType === 'ride_model') {
|
||||||
const { data } = await supabase.from('ride_models').select('name').eq('id', entityId).single();
|
const { data } = await supabase.from('ride_models').select('name').eq('id', entityId).maybeSingle();
|
||||||
if (data?.name) entityName = `${data.name} (${formatEntityType(entityType)})`;
|
if (data?.name) entityName = `${data.name} (${formatEntityType(entityType)})`;
|
||||||
} else if (['manufacturer', 'operator', 'designer', 'property_owner'].includes(entityType)) {
|
} else if (['manufacturer', 'operator', 'designer', 'property_owner'].includes(entityType)) {
|
||||||
const { data } = await supabase.from('companies').select('name').eq('id', entityId).single();
|
const { data } = await supabase.from('companies').select('name').eq('id', entityId).maybeSingle();
|
||||||
if (data?.name) entityName = `${data.name} (${formatEntityType(entityType)})`;
|
if (data?.name) entityName = `${data.name} (${formatEntityType(entityType)})`;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error fetching entity name:', err);
|
console.error('Error fetching entity name for photo operation:', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add debugging warning if critical data is missing
|
||||||
|
if (!itemData.entity_name && item.item_type === 'photo_delete') {
|
||||||
|
console.warn(`[Photo Delete] Missing entity_name for photo_delete item`, {
|
||||||
|
item_type: item.item_type,
|
||||||
|
has_entity_type: !!itemData.entity_type,
|
||||||
|
has_entity_id: !!itemData.entity_id,
|
||||||
|
has_cloudflare_url: !!itemData.cloudflare_image_url
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// For regular entities, use name field
|
// For regular entities, use name field
|
||||||
entityName = itemData.name || originalData?.name || 'Unknown';
|
entityName = itemData.name || originalData?.name || 'Unknown';
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
-- Phase 1: Fix existing photo_delete submissions with missing entity_name
|
||||||
|
|
||||||
|
-- Create a function to backfill missing entity names for photo_delete submissions
|
||||||
|
CREATE OR REPLACE FUNCTION backfill_photo_delete_entity_names()
|
||||||
|
RETURNS void
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SECURITY DEFINER
|
||||||
|
SET search_path TO 'public'
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
item_record RECORD;
|
||||||
|
entity_name_value TEXT;
|
||||||
|
BEGIN
|
||||||
|
-- Find all photo_delete submission_items missing entity_name
|
||||||
|
FOR item_record IN
|
||||||
|
SELECT id, item_data
|
||||||
|
FROM submission_items
|
||||||
|
WHERE item_type = 'photo_delete'
|
||||||
|
AND item_data->>'entity_name' IS NULL
|
||||||
|
AND item_data->>'entity_id' IS NOT NULL
|
||||||
|
AND item_data->>'entity_type' IS NOT NULL
|
||||||
|
LOOP
|
||||||
|
-- Fetch entity name based on entity_type
|
||||||
|
CASE item_record.item_data->>'entity_type'
|
||||||
|
WHEN 'park' THEN
|
||||||
|
SELECT name INTO entity_name_value FROM parks WHERE id = (item_record.item_data->>'entity_id')::uuid;
|
||||||
|
WHEN 'ride' THEN
|
||||||
|
SELECT name INTO entity_name_value FROM rides WHERE id = (item_record.item_data->>'entity_id')::uuid;
|
||||||
|
WHEN 'ride_model' THEN
|
||||||
|
SELECT name INTO entity_name_value FROM ride_models WHERE id = (item_record.item_data->>'entity_id')::uuid;
|
||||||
|
WHEN 'manufacturer', 'operator', 'designer', 'property_owner' THEN
|
||||||
|
SELECT name INTO entity_name_value FROM companies WHERE id = (item_record.item_data->>'entity_id')::uuid;
|
||||||
|
ELSE
|
||||||
|
entity_name_value := NULL;
|
||||||
|
END CASE;
|
||||||
|
|
||||||
|
-- Update the item_data if we found a name
|
||||||
|
IF entity_name_value IS NOT NULL THEN
|
||||||
|
UPDATE submission_items
|
||||||
|
SET item_data = item_data || jsonb_build_object('entity_name', entity_name_value)
|
||||||
|
WHERE id = item_record.id;
|
||||||
|
|
||||||
|
RAISE NOTICE 'Updated submission_item % with entity_name: %', item_record.id, entity_name_value;
|
||||||
|
END IF;
|
||||||
|
END LOOP;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- Run the backfill function
|
||||||
|
SELECT backfill_photo_delete_entity_names();
|
||||||
|
|
||||||
|
-- Drop the function after use (optional, but keeps things clean)
|
||||||
|
DROP FUNCTION IF EXISTS backfill_photo_delete_entity_names();
|
||||||
Reference in New Issue
Block a user