diff --git a/src/components/moderation/QueueItem.tsx b/src/components/moderation/QueueItem.tsx index 94ec3499..8caa906e 100644 --- a/src/components/moderation/QueueItem.tsx +++ b/src/components/moderation/QueueItem.tsx @@ -158,15 +158,17 @@ export const QueueItem = memo(({ Claimed by You )} + {item.submission_type && ( + )}
diff --git a/src/components/moderation/ValidationSummary.tsx b/src/components/moderation/ValidationSummary.tsx index 361b18c4..ba1a6917 100644 --- a/src/components/moderation/ValidationSummary.tsx +++ b/src/components/moderation/ValidationSummary.tsx @@ -3,7 +3,6 @@ import { AlertCircle, CheckCircle, Info, AlertTriangle } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { validateEntityData, ValidationResult } from '@/lib/entityValidationSchemas'; interface ValidationSummaryProps { @@ -25,10 +24,18 @@ export function ValidationSummary({ item, onValidationChange, compact = false }: async function validate() { setIsLoading(true); try { + console.log('Validating submission:', { + type: item.item_type, + data: item.item_data, + id: item.id + }); + const result = await validateEntityData( item.item_type as any, { ...item.item_data, id: item.id } ); + + console.log('Validation result:', result); setValidationResult(result); onValidationChange?.(result); } catch (error) { @@ -73,107 +80,62 @@ export function ValidationSummary({ item, onValidationChange, compact = false }: const hasSuggestions = validationResult.suggestions.length > 0; const hasAnyIssues = hasBlockingErrors || hasWarnings || hasSuggestions; - // Compact view (for queue items) + // Compact view (for queue items) - NO HOVER, ALWAYS VISIBLE if (compact) { return ( - - - -
- {validationResult.isValid && !hasWarnings && !hasSuggestions && ( - - - Valid - - )} - {hasBlockingErrors && ( - - - {validationResult.blockingErrors.length} Error{validationResult.blockingErrors.length !== 1 ? 's' : ''} - - )} - {hasWarnings && !hasBlockingErrors && ( - - - {validationResult.warnings.length} Warning{validationResult.warnings.length !== 1 ? 's' : ''} - - )} - {hasSuggestions && !hasBlockingErrors && !hasWarnings && ( - - - {validationResult.suggestions.length} Suggestion{validationResult.suggestions.length !== 1 ? 's' : ''} - - )} -
-
- - {hasAnyIssues && ( - -
- {hasBlockingErrors && ( -
-

- Blocking Errors: -

-
    - {validationResult.blockingErrors.slice(0, 3).map((error, i) => ( -
  • - • {error.field}: {error.message} -
  • - ))} - {validationResult.blockingErrors.length > 3 && ( -
  • - ... and {validationResult.blockingErrors.length - 3} more -
  • - )} -
-
- )} - - {hasWarnings && ( -
-

- Warnings: -

-
    - {validationResult.warnings.slice(0, 3).map((warning, i) => ( -
  • - • {warning.field}: {warning.message} -
  • - ))} - {validationResult.warnings.length > 3 && ( -
  • - ... and {validationResult.warnings.length - 3} more -
  • - )} -
-
- )} - - {hasSuggestions && !hasBlockingErrors && !hasWarnings && ( -
-

- Suggestions: -

-
    - {validationResult.suggestions.slice(0, 3).map((suggestion, i) => ( -
  • - • {suggestion.field}: {suggestion.message} -
  • - ))} - {validationResult.suggestions.length > 3 && ( -
  • - ... and {validationResult.suggestions.length - 3} more -
  • - )} -
-
- )} -
-
+
+ {/* Status Badges */} +
+ {validationResult.isValid && !hasWarnings && !hasSuggestions && ( + + + Valid + )} - - + {hasBlockingErrors && ( + + + {validationResult.blockingErrors.length} Error{validationResult.blockingErrors.length !== 1 ? 's' : ''} + + )} + {hasWarnings && ( + + + {validationResult.warnings.length} Warning{validationResult.warnings.length !== 1 ? 's' : ''} + + )} +
+ + {/* ALWAYS SHOW ERROR DETAILS - NO HOVER NEEDED */} + {hasBlockingErrors && ( +
+

Blocking Errors:

+
    + {validationResult.blockingErrors.map((error, i) => ( +
  • + • {error.field}: {error.message} +
  • + ))} +
+
+ )} + + {hasWarnings && !hasBlockingErrors && ( +
+

Warnings:

+
    + {validationResult.warnings.slice(0, 3).map((warning, i) => ( +
  • + • {warning.field}: {warning.message} +
  • + ))} + {validationResult.warnings.length > 3 && ( +
  • ... and {validationResult.warnings.length - 3} more
  • + )} +
+
+ )} +
); } diff --git a/src/lib/entityValidationSchemas.ts b/src/lib/entityValidationSchemas.ts index cf0bf40c..f641e2a8 100644 --- a/src/lib/entityValidationSchemas.ts +++ b/src/lib/entityValidationSchemas.ts @@ -21,12 +21,18 @@ export const parkValidationSchema = z.object({ return date <= new Date(); }, 'Opening date cannot be in the future'), closing_date: z.string().optional(), - location_id: z.string().uuid().optional(), - website_url: z.string().url('Invalid URL format').optional().or(z.literal('')), + location_id: z.string().uuid().optional().nullable(), + website_url: z.string().optional().refine((val) => { + if (!val || val === '') return true; + return z.string().url().safeParse(val).success; + }, 'Invalid URL format'), phone: z.string().max(50, 'Phone must be less than 50 characters').optional(), - email: z.string().email('Invalid email format').optional().or(z.literal('')), - operator_id: z.string().uuid().optional(), - property_owner_id: z.string().uuid().optional(), + email: z.string().optional().refine((val) => { + if (!val || val === '') return true; + return z.string().email().safeParse(val).success; + }, 'Invalid email format'), + operator_id: z.string().uuid().optional().nullable(), + property_owner_id: z.string().uuid().optional().nullable(), banner_image_id: z.string().optional(), banner_image_url: z.string().optional(), card_image_id: z.string().optional(), @@ -54,8 +60,8 @@ export const rideValidationSchema = z.object({ category: z.string().min(1, 'Category is required'), ride_sub_type: z.string().max(100, 'Sub type must be less than 100 characters').optional(), status: z.string().min(1, 'Status is required'), - park_id: z.string().uuid().optional(), - designer_id: z.string().uuid().optional(), + park_id: z.string().uuid().optional().nullable(), + designer_id: z.string().uuid().optional().nullable(), opening_date: z.string().optional(), closing_date: z.string().optional(), height_requirement: z.number().min(0, 'Height requirement must be positive').max(300, 'Height requirement must be less than 300cm').optional(), @@ -66,8 +72,8 @@ export const rideValidationSchema = z.object({ max_height_meters: z.number().min(0, 'Height must be positive').max(200, 'Height must be less than 200 meters').optional(), length_meters: z.number().min(0, 'Length must be positive').optional(), inversions: z.number().min(0, 'Inversions must be positive').optional(), - manufacturer_id: z.string().uuid().optional(), - ride_model_id: z.string().uuid().optional(), + manufacturer_id: z.string().uuid().optional().nullable(), + ride_model_id: z.string().uuid().optional().nullable(), coaster_type: z.string().optional(), seating_type: z.string().optional(), intensity_level: z.string().optional(), @@ -93,7 +99,10 @@ export const companyValidationSchema = z.object({ person_type: z.enum(['company', 'individual', 'firm', 'organization']).optional(), founded_year: z.number().min(1800, 'Founded year must be after 1800').max(currentYear, `Founded year cannot be in the future`).optional(), headquarters_location: z.string().max(200, 'Location must be less than 200 characters').optional(), - website_url: z.string().url('Invalid URL format').optional().or(z.literal('')), + website_url: z.string().optional().refine((val) => { + if (!val || val === '') return true; + return z.string().url().safeParse(val).success; + }, 'Invalid URL format'), banner_image_id: z.string().optional(), banner_image_url: z.string().optional(), card_image_id: z.string().optional(), @@ -254,6 +263,8 @@ async function checkSlugUniqueness( const tableName = getTableNameFromEntityType(entityType); try { + console.log(`Checking slug uniqueness for "${slug}" in ${tableName}, excludeId: ${excludeId}`); + const { data, error } = await supabase .from(tableName as any) .select('id') @@ -261,19 +272,28 @@ async function checkSlugUniqueness( .limit(1); if (error) { - console.error('Error checking slug uniqueness:', error); + console.error(`Slug uniqueness check failed for ${entityType}:`, error); return true; // Assume unique on error to avoid blocking } - // Check if excludeId matches - if (excludeId && data && data.length > 0 && data[0]) { - return (data[0] as any).id === excludeId; + // If no data, slug is unique + if (!data || data.length === 0) { + console.log(`Slug "${slug}" is unique in ${tableName}`); + return true; } - return !data || data.length === 0; + // If excludeId provided and matches, it's the same entity (editing) + if (excludeId && data[0] && (data[0] as any).id === excludeId) { + console.log(`Slug "${slug}" matches current entity (editing mode)`); + return true; + } + + // Slug is in use by a different entity + console.log(`Slug "${slug}" already exists in ${tableName}`); + return false; } catch (error) { - console.error('Error checking slug uniqueness:', error); - return true; // Assume unique on error + console.error(`Exception during slug uniqueness check:`, error); + return true; // Assume unique on error to avoid false positives } }