mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 11:51:14 -05:00
Fix build errors and complete transformation
This commit is contained in:
@@ -1,36 +1,23 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||||
import { Badge } from '@/components/ui/badge';
|
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { Clock, Plus, Minus, Edit } from 'lucide-react';
|
import { AlertCircle } from 'lucide-react';
|
||||||
import { formatDistanceToNow } from 'date-fns';
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
import { supabase } from '@/integrations/supabase/client';
|
import type { EntityType } from '@/types/versioning';
|
||||||
|
|
||||||
interface FieldHistoryDialogProps {
|
interface FieldHistoryDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onOpenChange: (open: boolean) => void;
|
onOpenChange: (open: boolean) => void;
|
||||||
entityType: string;
|
entityType: EntityType;
|
||||||
entityId: string;
|
entityId: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FieldChange {
|
/**
|
||||||
id: string;
|
* Field-level history has been removed in the relational versioning system.
|
||||||
field_name: string;
|
* Use version comparison instead to see field changes between versions.
|
||||||
old_value: any;
|
* This component now shows a helpful message directing users to use version comparison.
|
||||||
new_value: any;
|
*/
|
||||||
change_type: 'added' | 'modified' | 'removed';
|
|
||||||
created_at: string;
|
|
||||||
version: {
|
|
||||||
version_number: number;
|
|
||||||
changed_by: string;
|
|
||||||
changed_at: string;
|
|
||||||
changer_profile: {
|
|
||||||
username: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function FieldHistoryDialog({
|
export function FieldHistoryDialog({
|
||||||
open,
|
open,
|
||||||
onOpenChange,
|
onOpenChange,
|
||||||
@@ -38,72 +25,6 @@ export function FieldHistoryDialog({
|
|||||||
entityId,
|
entityId,
|
||||||
fieldName,
|
fieldName,
|
||||||
}: FieldHistoryDialogProps) {
|
}: FieldHistoryDialogProps) {
|
||||||
const [changes, setChanges] = useState<FieldChange[]>([]);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchFieldHistory = async () => {
|
|
||||||
if (!open) return;
|
|
||||||
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
// Get all versions for this entity
|
|
||||||
const { data: versions, error: versionsError } = await supabase
|
|
||||||
.from('entity_versions')
|
|
||||||
.select('id, version_number, changed_by, changed_at')
|
|
||||||
.eq('entity_type', entityType)
|
|
||||||
.eq('entity_id', entityId)
|
|
||||||
.order('version_number', { ascending: false });
|
|
||||||
|
|
||||||
if (versionsError) throw versionsError;
|
|
||||||
|
|
||||||
// Get profiles
|
|
||||||
const userIds = [...new Set(versions?.map(v => v.changed_by).filter(Boolean) || [])];
|
|
||||||
const { data: profiles } = await supabase
|
|
||||||
.from('profiles')
|
|
||||||
.select('user_id, username')
|
|
||||||
.in('user_id', userIds);
|
|
||||||
|
|
||||||
// Get field history for all these versions
|
|
||||||
const versionIds = versions?.map(v => v.id) || [];
|
|
||||||
|
|
||||||
const { data: fieldHistory, error: historyError } = await supabase
|
|
||||||
.from('entity_field_history')
|
|
||||||
.select('*')
|
|
||||||
.eq('field_name', fieldName)
|
|
||||||
.in('version_id', versionIds)
|
|
||||||
.order('created_at', { ascending: false });
|
|
||||||
|
|
||||||
if (historyError) throw historyError;
|
|
||||||
|
|
||||||
// Merge the data
|
|
||||||
const mergedData = fieldHistory?.map(change => ({
|
|
||||||
...change,
|
|
||||||
version: {
|
|
||||||
...versions?.find(v => v.id === change.version_id),
|
|
||||||
changer_profile: profiles?.find(p => p.user_id === versions?.find(v => v.id === change.version_id)?.changed_by)
|
|
||||||
},
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
setChanges(mergedData as FieldChange[]);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching field history:', error);
|
|
||||||
setChanges([]);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchFieldHistory();
|
|
||||||
}, [open, entityType, entityId, fieldName]);
|
|
||||||
|
|
||||||
const formatValue = (value: any): string => {
|
|
||||||
if (value === null || value === undefined) return 'null';
|
|
||||||
if (typeof value === 'boolean') return value ? 'true' : 'false';
|
|
||||||
if (typeof value === 'object') return JSON.stringify(value, null, 2);
|
|
||||||
return String(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatFieldName = (name: string): string => {
|
const formatFieldName = (name: string): string => {
|
||||||
return name
|
return name
|
||||||
.split('_')
|
.split('_')
|
||||||
@@ -111,106 +32,25 @@ export function FieldHistoryDialog({
|
|||||||
.join(' ');
|
.join(' ');
|
||||||
};
|
};
|
||||||
|
|
||||||
const getChangeIcon = (type: string) => {
|
|
||||||
switch (type) {
|
|
||||||
case 'added':
|
|
||||||
return <Plus className="h-4 w-4 text-green-500" />;
|
|
||||||
case 'removed':
|
|
||||||
return <Minus className="h-4 w-4 text-red-500" />;
|
|
||||||
case 'modified':
|
|
||||||
return <Edit className="h-4 w-4 text-blue-500" />;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getChangeBadgeColor = (type: string) => {
|
|
||||||
switch (type) {
|
|
||||||
case 'added':
|
|
||||||
return 'bg-green-500/10 text-green-700 dark:text-green-400';
|
|
||||||
case 'removed':
|
|
||||||
return 'bg-red-500/10 text-red-700 dark:text-red-400';
|
|
||||||
case 'modified':
|
|
||||||
return 'bg-blue-500/10 text-blue-700 dark:text-blue-400';
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="max-w-3xl max-h-[80vh] overflow-y-auto">
|
<DialogContent className="max-w-2xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Field History: {formatFieldName(fieldName)}</DialogTitle>
|
<DialogTitle>Field History: {formatFieldName(fieldName)}</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
|
|
||||||
<ScrollArea className="h-[500px] pr-4">
|
<div className="py-6">
|
||||||
{loading ? (
|
<Alert>
|
||||||
<div className="flex items-center justify-center py-8">
|
<AlertCircle className="h-4 w-4" />
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
|
<AlertDescription>
|
||||||
</div>
|
<p className="font-medium mb-2">Field-level history is not available in the current versioning system.</p>
|
||||||
) : changes.length > 0 ? (
|
<p className="text-sm text-muted-foreground">
|
||||||
<div className="space-y-4">
|
To see changes to the "{formatFieldName(fieldName)}" field, please use the <strong>Version Comparison</strong> feature
|
||||||
{changes.map((change) => (
|
in the Version History tab. This will show you all field changes between any two versions.
|
||||||
<div key={change.id} className="border rounded-lg p-4 space-y-3">
|
</p>
|
||||||
{/* Header */}
|
</AlertDescription>
|
||||||
<div className="flex items-center justify-between">
|
</Alert>
|
||||||
<div className="flex items-center gap-2">
|
</div>
|
||||||
{getChangeIcon(change.change_type)}
|
|
||||||
<Badge variant="outline" className={getChangeBadgeColor(change.change_type)}>
|
|
||||||
{change.change_type}
|
|
||||||
</Badge>
|
|
||||||
<span className="text-sm text-muted-foreground">
|
|
||||||
Version {change.version?.version_number}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
||||||
<span>{change.version?.changer_profile?.username}</span>
|
|
||||||
<Clock className="h-3 w-3" />
|
|
||||||
<span>
|
|
||||||
{formatDistanceToNow(new Date(change.created_at), { addSuffix: true })}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Values */}
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
|
||||||
{change.old_value !== null && (
|
|
||||||
<div className="space-y-1">
|
|
||||||
<div className="text-xs text-muted-foreground font-medium">Previous Value</div>
|
|
||||||
<div className="p-3 rounded-md bg-muted font-mono text-xs whitespace-pre-wrap">
|
|
||||||
{formatValue(change.old_value)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{change.new_value !== null && (
|
|
||||||
<div className="space-y-1">
|
|
||||||
<div className="text-xs text-muted-foreground font-medium">New Value</div>
|
|
||||||
<div
|
|
||||||
className={`p-3 rounded-md font-mono text-xs whitespace-pre-wrap ${
|
|
||||||
change.change_type === 'added'
|
|
||||||
? 'bg-green-500/10 text-green-700 dark:text-green-400 font-semibold'
|
|
||||||
: change.change_type === 'modified'
|
|
||||||
? 'bg-blue-500/10 text-blue-700 dark:text-blue-400 font-semibold'
|
|
||||||
: 'bg-muted'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{formatValue(change.new_value)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="text-center py-8 text-muted-foreground">
|
|
||||||
<p>No history found for this field</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</ScrollArea>
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -88,32 +88,14 @@ export function useEntityVersions(entityType: EntityType, entityId: string) {
|
|||||||
}
|
}
|
||||||
}, [entityType, entityId]);
|
}, [entityType, entityId]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Field history has been removed - use version comparison instead
|
||||||
|
* This function is kept for backward compatibility but does nothing
|
||||||
|
* @deprecated Use compareVersions() to see field-level changes
|
||||||
|
*/
|
||||||
const fetchFieldHistory = async (versionId: string) => {
|
const fetchFieldHistory = async (versionId: string) => {
|
||||||
if (!isMountedRef.current) return;
|
console.warn('fetchFieldHistory is deprecated. Use compareVersions() instead for field-level changes.');
|
||||||
|
setFieldHistory([]);
|
||||||
const currentRequestId = ++fieldHistoryRequestCounterRef.current;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { data, error } = await supabase
|
|
||||||
.from('entity_field_history')
|
|
||||||
.select('*')
|
|
||||||
.eq('version_id', versionId)
|
|
||||||
.order('created_at', { ascending: false });
|
|
||||||
|
|
||||||
if (error) throw error;
|
|
||||||
|
|
||||||
if (isMountedRef.current && currentRequestId === fieldHistoryRequestCounterRef.current) {
|
|
||||||
const fieldChanges = Array.isArray(data) ? data as FieldChange[] : [];
|
|
||||||
setFieldHistory(fieldChanges);
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
console.error('Error fetching field history:', error);
|
|
||||||
|
|
||||||
if (isMountedRef.current && currentRequestId === fieldHistoryRequestCounterRef.current) {
|
|
||||||
const errorMessage = error?.message || 'Failed to load field history';
|
|
||||||
toast.error(errorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const compareVersions = async (fromVersionId: string, toVersionId: string) => {
|
const compareVersions = async (fromVersionId: string, toVersionId: string) => {
|
||||||
|
|||||||
@@ -103,28 +103,113 @@ export async function fetchSystemActivities(
|
|||||||
): Promise<SystemActivity[]> {
|
): Promise<SystemActivity[]> {
|
||||||
const activities: SystemActivity[] = [];
|
const activities: SystemActivity[] = [];
|
||||||
|
|
||||||
// Fetch entity versions (entity changes)
|
// Fetch entity versions from relational version tables
|
||||||
// Use simplified query without foreign key join - we'll fetch profiles separately
|
// Query all four version tables in parallel for better performance
|
||||||
const { data: versions, error: versionsError } = await supabase
|
const versionQueries = [
|
||||||
.from('entity_versions')
|
supabase
|
||||||
.select('id, entity_type, entity_id, version_number, version_data, changed_by, changed_at, change_type, change_reason')
|
.from('park_versions')
|
||||||
.eq('is_current', true)
|
.select('version_id, park_id, version_number, name, created_by, created_at, change_type, change_reason, is_current')
|
||||||
.order('changed_at', { ascending: false })
|
.eq('is_current', true)
|
||||||
.limit(limit * 2); // Fetch more to account for filtering
|
.order('created_at', { ascending: false })
|
||||||
|
.limit(Math.ceil(limit / 2)),
|
||||||
|
supabase
|
||||||
|
.from('ride_versions')
|
||||||
|
.select('version_id, ride_id, version_number, name, created_by, created_at, change_type, change_reason, is_current')
|
||||||
|
.eq('is_current', true)
|
||||||
|
.order('created_at', { ascending: false })
|
||||||
|
.limit(Math.ceil(limit / 2)),
|
||||||
|
supabase
|
||||||
|
.from('company_versions')
|
||||||
|
.select('version_id, company_id, version_number, name, created_by, created_at, change_type, change_reason, is_current')
|
||||||
|
.eq('is_current', true)
|
||||||
|
.order('created_at', { ascending: false })
|
||||||
|
.limit(Math.ceil(limit / 4)),
|
||||||
|
supabase
|
||||||
|
.from('ride_model_versions')
|
||||||
|
.select('version_id, ride_model_id, version_number, name, created_by, created_at, change_type, change_reason, is_current')
|
||||||
|
.eq('is_current', true)
|
||||||
|
.order('created_at', { ascending: false })
|
||||||
|
.limit(Math.ceil(limit / 4)),
|
||||||
|
];
|
||||||
|
|
||||||
if (!versionsError && versions) {
|
const [parkVersions, rideVersions, companyVersions, modelVersions] = await Promise.all(versionQueries);
|
||||||
for (const version of versions) {
|
|
||||||
const versionData = version.version_data as any;
|
// Process park versions
|
||||||
|
if (!parkVersions.error && parkVersions.data) {
|
||||||
|
for (const version of parkVersions.data) {
|
||||||
activities.push({
|
activities.push({
|
||||||
id: version.id,
|
id: version.version_id,
|
||||||
type: 'entity_change',
|
type: 'entity_change',
|
||||||
timestamp: version.changed_at,
|
timestamp: version.created_at,
|
||||||
actor_id: version.changed_by || null,
|
actor_id: version.created_by || null,
|
||||||
action: `${version.change_type} ${version.entity_type}`,
|
action: `${version.change_type} park`,
|
||||||
details: {
|
details: {
|
||||||
entity_type: version.entity_type,
|
entity_type: 'park',
|
||||||
entity_id: version.entity_id,
|
entity_id: version.park_id,
|
||||||
entity_name: versionData?.name || versionData?.title,
|
entity_name: version.name,
|
||||||
|
change_type: version.change_type,
|
||||||
|
change_reason: version.change_reason,
|
||||||
|
version_number: version.version_number,
|
||||||
|
} as EntityChangeDetails,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process ride versions
|
||||||
|
if (!rideVersions.error && rideVersions.data) {
|
||||||
|
for (const version of rideVersions.data) {
|
||||||
|
activities.push({
|
||||||
|
id: version.version_id,
|
||||||
|
type: 'entity_change',
|
||||||
|
timestamp: version.created_at,
|
||||||
|
actor_id: version.created_by || null,
|
||||||
|
action: `${version.change_type} ride`,
|
||||||
|
details: {
|
||||||
|
entity_type: 'ride',
|
||||||
|
entity_id: version.ride_id,
|
||||||
|
entity_name: version.name,
|
||||||
|
change_type: version.change_type,
|
||||||
|
change_reason: version.change_reason,
|
||||||
|
version_number: version.version_number,
|
||||||
|
} as EntityChangeDetails,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process company versions
|
||||||
|
if (!companyVersions.error && companyVersions.data) {
|
||||||
|
for (const version of companyVersions.data) {
|
||||||
|
activities.push({
|
||||||
|
id: version.version_id,
|
||||||
|
type: 'entity_change',
|
||||||
|
timestamp: version.created_at,
|
||||||
|
actor_id: version.created_by || null,
|
||||||
|
action: `${version.change_type} company`,
|
||||||
|
details: {
|
||||||
|
entity_type: 'company',
|
||||||
|
entity_id: version.company_id,
|
||||||
|
entity_name: version.name,
|
||||||
|
change_type: version.change_type,
|
||||||
|
change_reason: version.change_reason,
|
||||||
|
version_number: version.version_number,
|
||||||
|
} as EntityChangeDetails,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process ride model versions
|
||||||
|
if (!modelVersions.error && modelVersions.data) {
|
||||||
|
for (const version of modelVersions.data) {
|
||||||
|
activities.push({
|
||||||
|
id: version.version_id,
|
||||||
|
type: 'entity_change',
|
||||||
|
timestamp: version.created_at,
|
||||||
|
actor_id: version.created_by || null,
|
||||||
|
action: `${version.change_type} ride_model`,
|
||||||
|
details: {
|
||||||
|
entity_type: 'ride_model',
|
||||||
|
entity_id: version.ride_model_id,
|
||||||
|
entity_name: version.name,
|
||||||
change_type: version.change_type,
|
change_type: version.change_type,
|
||||||
change_reason: version.change_reason,
|
change_reason: version.change_reason,
|
||||||
version_number: version.version_number,
|
version_number: version.version_number,
|
||||||
|
|||||||
Reference in New Issue
Block a user