Compare commits

...

5 Commits

Author SHA1 Message Date
gpt-engineer-app[bot]
d00ea2a3ee Fix 406 errors in validation 2025-11-06 04:47:35 +00:00
gpt-engineer-app[bot]
5c24038470 Refactor moderation queue display 2025-11-06 04:42:00 +00:00
gpt-engineer-app[bot]
93e8e98957 Fix: Display temp location data 2025-11-06 04:37:48 +00:00
gpt-engineer-app[bot]
c8a015a15b Fix park type and moderator ID 2025-11-06 04:33:26 +00:00
gpt-engineer-app[bot]
93e48ac457 Fix park type and moderator ID 2025-11-06 04:31:58 +00:00
7 changed files with 165 additions and 21 deletions

View File

@@ -93,14 +93,14 @@ interface ParkFormProps {
} }
const parkTypes = [ const parkTypes = [
'Theme Park', { value: 'theme_park', label: 'Theme Park' },
'Amusement Park', { value: 'amusement_park', label: 'Amusement Park' },
'Water Park', { value: 'water_park', label: 'Water Park' },
'Family Entertainment Center', { value: 'family_entertainment', label: 'Family Entertainment Center' },
'Adventure Park', { value: 'adventure_park', label: 'Adventure Park' },
'Safari Park', { value: 'safari_park', label: 'Safari Park' },
'Carnival', { value: 'carnival', label: 'Carnival' },
'Fair' { value: 'fair', label: 'Fair' }
]; ];
const statusOptions = [ const statusOptions = [
@@ -363,8 +363,8 @@ export function ParkForm({ onSubmit, onCancel, initialData, isEditing = false }:
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{parkTypes.map((type) => ( {parkTypes.map((type) => (
<SelectItem key={type} value={type}> <SelectItem key={type.value} value={type.value}>
{type} {type.label}
</SelectItem> </SelectItem>
))} ))}
</SelectContent> </SelectContent>

View File

@@ -177,7 +177,7 @@ export const SubmissionItemsList = memo(function SubmissionItemsList({
); );
} }
// Use rich displays for detailed view // Use rich displays for detailed view - show BOTH rich display AND field-by-field changes
if (item.item_type === 'park' && entityData) { if (item.item_type === 'park' && entityData) {
return ( return (
<> <>
@@ -186,6 +186,17 @@ export const SubmissionItemsList = memo(function SubmissionItemsList({
data={entityData as unknown as ParkSubmissionData} data={entityData as unknown as ParkSubmissionData}
actionType={actionType} 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>
</> </>
); );
} }
@@ -198,6 +209,17 @@ export const SubmissionItemsList = memo(function SubmissionItemsList({
data={entityData as unknown as RideSubmissionData} data={entityData as unknown as RideSubmissionData}
actionType={actionType} 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>
</> </>
); );
} }
@@ -210,6 +232,17 @@ export const SubmissionItemsList = memo(function SubmissionItemsList({
data={entityData as unknown as CompanySubmissionData} data={entityData as unknown as CompanySubmissionData}
actionType={actionType} 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>
</> </>
); );
} }
@@ -222,6 +255,17 @@ export const SubmissionItemsList = memo(function SubmissionItemsList({
data={entityData as unknown as RideModelSubmissionData} data={entityData as unknown as RideModelSubmissionData}
actionType={actionType} 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>
</> </>
); );
} }

View File

@@ -21,7 +21,7 @@ export function RichParkDisplay({ data, actionType, showAllFields = true }: Rich
if (!data) return; if (!data) return;
const fetchRelatedData = async () => { const fetchRelatedData = async () => {
// Fetch location // Fetch location if location_id exists (for edits)
if (data.location_id) { if (data.location_id) {
const { data: locationData } = await supabase const { data: locationData } = await supabase
.from('locations') .from('locations')
@@ -30,6 +30,10 @@ export function RichParkDisplay({ data, actionType, showAllFields = true }: Rich
.single(); .single();
setLocation(locationData); setLocation(locationData);
} }
// Otherwise use temp_location_data (for new submissions)
else if (data.temp_location_data) {
setLocation(data.temp_location_data);
}
// Fetch operator // Fetch operator
if (data.operator_id) { if (data.operator_id) {
@@ -53,7 +57,7 @@ export function RichParkDisplay({ data, actionType, showAllFields = true }: Rich
}; };
fetchRelatedData(); fetchRelatedData();
}, [data.location_id, data.operator_id, data.property_owner_id]); }, [data.location_id, data.temp_location_data, data.operator_id, data.property_owner_id]);
const getStatusColor = (status: string | undefined) => { const getStatusColor = (status: string | undefined) => {
if (!status) return 'bg-gray-500'; if (!status) return 'bg-gray-500';

View File

@@ -602,23 +602,39 @@ export async function validateEntityData(
try { try {
switch (tableName) { switch (tableName) {
case 'parks': { case 'parks': {
const { data } = await supabase.from('parks').select('slug').eq('id', entityId).single(); const { data, error } = await supabase.from('parks').select('slug').eq('id', entityId).maybeSingle();
originalSlug = data?.slug || null; if (error || !data) {
originalSlug = null;
break;
}
originalSlug = data.slug || null;
break; break;
} }
case 'rides': { case 'rides': {
const { data } = await supabase.from('rides').select('slug').eq('id', entityId).single(); const { data, error } = await supabase.from('rides').select('slug').eq('id', entityId).maybeSingle();
originalSlug = data?.slug || null; if (error || !data) {
originalSlug = null;
break;
}
originalSlug = data.slug || null;
break; break;
} }
case 'companies': { case 'companies': {
const { data } = await supabase.from('companies').select('slug').eq('id', entityId).single(); const { data, error } = await supabase.from('companies').select('slug').eq('id', entityId).maybeSingle();
originalSlug = data?.slug || null; if (error || !data) {
originalSlug = null;
break;
}
originalSlug = data.slug || null;
break; break;
} }
case 'ride_models': { case 'ride_models': {
const { data } = await supabase.from('ride_models').select('slug').eq('id', entityId).single(); const { data, error } = await supabase.from('ride_models').select('slug').eq('id', entityId).maybeSingle();
originalSlug = data?.slug || null; if (error || !data) {
originalSlug = null;
break;
}
originalSlug = data.slug || null;
break; break;
} }
} }

View File

@@ -3,6 +3,8 @@
* These replace the `any` types in entityTransformers.ts * These replace the `any` types in entityTransformers.ts
*/ */
import type { LocationData } from './location';
export interface ParkSubmissionData { export interface ParkSubmissionData {
name: string; name: string;
slug: string; slug: string;
@@ -19,6 +21,7 @@ export interface ParkSubmissionData {
operator_id?: string | null; operator_id?: string | null;
property_owner_id?: string | null; property_owner_id?: string | null;
location_id?: string | null; location_id?: string | null;
temp_location_data?: LocationData | null;
banner_image_url?: string | null; banner_image_url?: string | null;
banner_image_id?: string | null; banner_image_id?: string | null;
card_image_url?: string | null; card_image_url?: string | null;

View File

@@ -1179,6 +1179,13 @@ serve(withRateLimit(async (req) => {
? 'Submission has unresolved dependencies. Escalation required.' ? 'Submission has unresolved dependencies. Escalation required.'
: undefined; : undefined;
// Set moderator_id session variable for audit logging
await supabase.rpc('set_config', {
setting: 'app.moderator_id',
value: authenticatedUserId,
is_local: true
});
const { error: updateError } = await supabase const { error: updateError } = await supabase
.from('content_submissions') .from('content_submissions')
.update({ .update({

View File

@@ -0,0 +1,70 @@
-- Update log_moderation_action to use session variable for moderator_id
-- This allows edge functions using service role to pass the actual moderator ID
CREATE OR REPLACE FUNCTION public.log_moderation_action(
_submission_id uuid,
_action text,
_previous_status text DEFAULT NULL::text,
_new_status text DEFAULT NULL::text,
_notes text DEFAULT NULL::text,
_metadata jsonb DEFAULT '{}'::jsonb
)
RETURNS uuid
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path TO 'public'
AS $function$
DECLARE
_log_id UUID;
_metadata_record record;
_moderator_id UUID;
BEGIN
-- Get moderator ID from session variable (set by edge function) or auth.uid()
BEGIN
_moderator_id := COALESCE(
current_setting('app.moderator_id', true)::uuid,
auth.uid()
);
EXCEPTION WHEN OTHERS THEN
_moderator_id := auth.uid();
END;
-- Insert into moderation_audit_log (without metadata JSONB column)
INSERT INTO public.moderation_audit_log (
submission_id,
moderator_id,
action,
previous_status,
new_status,
notes
) VALUES (
_submission_id,
_moderator_id,
_action,
_previous_status,
_new_status,
_notes
)
RETURNING id INTO _log_id;
-- Write metadata to relational moderation_audit_metadata table
IF _metadata IS NOT NULL AND jsonb_typeof(_metadata) = 'object' THEN
FOR _metadata_record IN
SELECT key, value::text as text_value
FROM jsonb_each_text(_metadata)
LOOP
INSERT INTO public.moderation_audit_metadata (
audit_log_id,
metadata_key,
metadata_value
) VALUES (
_log_id,
_metadata_record.key,
_metadata_record.text_value
);
END LOOP;
END IF;
RETURN _log_id;
END;
$function$;