mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-28 17:26:58 -05:00
Compare commits
5 Commits
090f6aca48
...
d00ea2a3ee
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d00ea2a3ee | ||
|
|
5c24038470 | ||
|
|
93e8e98957 | ||
|
|
c8a015a15b | ||
|
|
93e48ac457 |
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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$;
|
||||||
Reference in New Issue
Block a user