Files
thrilltrack-explorer/src/hooks/useEntityVersions.ts
2025-10-06 15:37:10 +00:00

200 lines
5.5 KiB
TypeScript

import { useState, useEffect } from 'react';
import { supabase } from '@/integrations/supabase/client';
import { toast } from 'sonner';
interface EntityVersion {
id: string;
entity_type: string;
entity_id: string;
version_number: number;
version_data: any;
changed_by: string;
changed_at: string;
change_reason: string | null;
change_type: 'created' | 'updated' | 'deleted' | 'restored' | 'archived';
submission_id: string | null;
is_current: boolean;
ip_address_hash: string | null;
metadata: any;
changer_profile?: {
username: string;
avatar_url: string | null;
};
}
interface FieldChange {
id: string;
field_name: string;
old_value: any;
new_value: any;
change_type: 'added' | 'modified' | 'removed';
created_at: string;
}
export function useEntityVersions(entityType: string, entityId: string) {
const [versions, setVersions] = useState<EntityVersion[]>([]);
const [currentVersion, setCurrentVersion] = useState<EntityVersion | null>(null);
const [loading, setLoading] = useState(true);
const [fieldHistory, setFieldHistory] = useState<FieldChange[]>([]);
const fetchVersions = async () => {
try {
setLoading(true);
const { data, error } = await supabase
.from('entity_versions')
.select('*')
.eq('entity_type', entityType)
.eq('entity_id', entityId)
.order('version_number', { ascending: false });
if (error) throw error;
// Fetch profiles separately
const userIds = [...new Set(data?.map(v => v.changed_by).filter(Boolean) || [])];
const { data: profiles } = await supabase
.from('profiles')
.select('user_id, username, avatar_url')
.in('user_id', userIds);
const versionsWithProfiles = data?.map(v => ({
...v,
changer_profile: profiles?.find(p => p.user_id === v.changed_by)
})) as EntityVersion[];
setVersions(versionsWithProfiles || []);
setCurrentVersion(versionsWithProfiles?.find(v => v.is_current) || null);
} catch (error: any) {
console.error('Error fetching versions:', error);
toast.error('Failed to load version history');
} finally {
setLoading(false);
}
};
const fetchFieldHistory = async (versionId: string) => {
try {
const { data, error } = await supabase
.from('entity_field_history')
.select('*')
.eq('version_id', versionId)
.order('created_at', { ascending: false });
if (error) throw error;
setFieldHistory(data as FieldChange[] || []);
} catch (error: any) {
console.error('Error fetching field history:', error);
toast.error('Failed to load field history');
}
};
const compareVersions = async (fromVersionId: string, toVersionId: string) => {
try {
const { data, error } = await supabase.rpc('compare_versions', {
p_from_version_id: fromVersionId,
p_to_version_id: toVersionId
});
if (error) throw error;
return data;
} catch (error: any) {
console.error('Error comparing versions:', error);
toast.error('Failed to compare versions');
return null;
}
};
const rollbackToVersion = async (targetVersionId: string, reason: string) => {
try {
const { data: userData } = await supabase.auth.getUser();
if (!userData.user) throw new Error('Not authenticated');
const { data, error } = await supabase.rpc('rollback_to_version', {
p_entity_type: entityType,
p_entity_id: entityId,
p_target_version_id: targetVersionId,
p_changed_by: userData.user.id,
p_reason: reason
});
if (error) throw error;
toast.success('Successfully rolled back to previous version');
await fetchVersions();
return data;
} catch (error: any) {
console.error('Error rolling back version:', error);
toast.error('Failed to rollback version');
return null;
}
};
const createVersion = async (versionData: any, changeReason?: string, submissionId?: string) => {
try {
const { data: userData } = await supabase.auth.getUser();
if (!userData.user) throw new Error('Not authenticated');
const { data, error } = await supabase.rpc('create_entity_version', {
p_entity_type: entityType,
p_entity_id: entityId,
p_version_data: versionData,
p_changed_by: userData.user.id,
p_change_reason: changeReason || null,
p_submission_id: submissionId || null
});
if (error) throw error;
await fetchVersions();
return data;
} catch (error: any) {
console.error('Error creating version:', error);
toast.error('Failed to create version');
return null;
}
};
useEffect(() => {
if (entityType && entityId) {
fetchVersions();
}
}, [entityType, entityId]);
// Set up realtime subscription for version changes
useEffect(() => {
const channel = supabase
.channel('entity_versions_changes')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'entity_versions',
filter: `entity_type=eq.${entityType},entity_id=eq.${entityId}`
},
() => {
fetchVersions();
}
)
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, [entityType, entityId]);
return {
versions,
currentVersion,
loading,
fieldHistory,
fetchVersions,
fetchFieldHistory,
compareVersions,
rollbackToVersion,
createVersion
};
}