# Frontend Integration Guide **How to use versioning in React components** ## Overview The versioning system provides React hooks and components for displaying version history, comparing versions, and rolling back changes. All components are fully typed with TypeScript discriminated unions. ## Type System ### Core Types ```typescript // Entity type union type EntityType = 'park' | 'ride' | 'company' | 'ride_model'; // Change type enum type ChangeType = 'created' | 'updated' | 'deleted' | 'restored' | 'archived'; // Base version metadata interface BaseVersion { version_id: string; version_number: number; created_at: string; created_by: string | null; change_type: ChangeType; change_reason: string | null; submission_id: string | null; is_current: boolean; } ``` ### Entity-Specific Versions The system uses discriminated unions for type-safe version handling: ```typescript // Discriminated union based on entity type type EntityVersion = | ParkVersion | RideVersion | CompanyVersion | RideModelVersion; // Example: Park version interface ParkVersion extends BaseVersion { park_id: string; name: string; slug: string; description: string | null; park_type: string; status: string; opening_date: string | null; closing_date: string | null; location_id: string | null; operator_id: string | null; property_owner_id: string | null; // ... all park fields } ``` ### Version Diff ```typescript interface VersionDiff { [fieldName: string]: { from: any; to: any; changed: boolean; }; } ``` ## React Hooks ### useEntityVersions **Purpose:** Fetch and manage version history for an entity. #### Usage ```typescript import { useEntityVersions } from '@/hooks/useEntityVersions'; const MyComponent = ({ parkId }: { parkId: string }) => { const { versions, currentVersion, loading, fieldHistory, fetchVersions, fetchFieldHistory, compareVersions, rollbackToVersion, } = useEntityVersions('park', parkId); // versions: Array of all versions // currentVersion: Latest version with is_current = true // loading: Boolean loading state // fieldHistory: Array of field changes for selected version }; ``` #### Return Type ```typescript interface UseEntityVersionsReturn { versions: EntityVersion[]; currentVersion: EntityVersion | null; loading: boolean; fieldHistory: FieldChange[]; fetchVersions: () => Promise; fetchFieldHistory: (versionId: string) => Promise; compareVersions: (fromId: string, toId: string) => Promise; rollbackToVersion: (versionId: string, reason: string) => Promise; } ``` #### Methods **fetchVersions()** - Fetches all versions for the entity - Automatically called on mount and entity ID change - Returns: `Promise` **fetchFieldHistory(versionId)** - Fetches detailed field changes for a specific version - Populates `fieldHistory` state - Parameters: - `versionId: string` - Version to analyze - Returns: `Promise` **compareVersions(fromId, toId)** - Compares two versions using database function - Parameters: - `fromId: string` - Older version ID - `toId: string` - Newer version ID - Returns: `Promise` **rollbackToVersion(versionId, reason)** - Restores entity to a previous version - Creates new version with `change_type='restored'` - Requires moderator role - Parameters: - `versionId: string` - Target version to restore - `reason: string` - Reason for rollback - Returns: `Promise` - Success status ### useVersionComparison **Purpose:** Compare two specific versions and get diff. #### Usage ```typescript import { useVersionComparison } from '@/hooks/useVersionComparison'; const ComparisonView = () => { const { diff, loading, error } = useVersionComparison( 'park', fromVersionId, toVersionId ); if (loading) return ; if (error) return ; return (
{Object.entries(diff || {}).map(([field, change]) => ( ))}
); }; ``` #### Return Type ```typescript interface UseVersionComparisonReturn { diff: VersionDiff | null; loading: boolean; error: string | null; } ``` ## Components ### VersionIndicator **Purpose:** Display current version badge on entity pages. #### Props ```typescript interface VersionIndicatorProps { entityType: EntityType; entityId: string; entityName: string; compact?: boolean; // Compact mode shows only version number } ``` #### Usage ```typescript import { VersionIndicator } from '@/components/versioning/VersionIndicator'; // Full mode (with "Last edited" timestamp) // Compact mode (badge only) ``` #### Output - Displays: "Version 3 · Last edited 2 hours ago" - Clicking opens `EntityVersionHistory` dialog - Compact mode: Just "v3" badge ### EntityVersionHistory **Purpose:** Display complete version timeline with comparison and rollback. #### Props ```typescript interface EntityVersionHistoryProps { entityType: EntityType; entityId: string; entityName: string; } ``` #### Usage ```typescript import { EntityVersionHistory } from '@/components/versioning/EntityVersionHistory'; ``` #### Features - Timeline of all versions - Select two versions to compare - Rollback to any previous version - User attribution and timestamps - Change type badges (created, updated, restored) ### VersionComparisonDialog **Purpose:** Side-by-side diff view of two versions. #### Props ```typescript interface VersionComparisonDialogProps { open: boolean; onOpenChange: (open: boolean) => void; entityType: EntityType; fromVersionId: string | null; toVersionId: string | null; } ``` #### Usage ```typescript import { VersionComparisonDialog } from '@/components/versioning/VersionComparisonDialog'; ``` #### Output - Field-by-field comparison - Highlights added, removed, and modified fields - Color-coded changes (green=added, red=removed, yellow=modified) - Formatted values (dates, JSON, etc.) ### RollbackDialog **Purpose:** Confirm and execute version rollback. #### Props ```typescript interface RollbackDialogProps { open: boolean; onOpenChange: (open: boolean) => void; entityType: EntityType; entityId: string; targetVersion: EntityVersion; onRollback: (versionId: string, reason: string) => Promise; } ``` #### Usage ```typescript import { RollbackDialog } from '@/components/versioning/RollbackDialog'; ``` #### Features - Requires rollback reason (text input) - Shows preview of target version - Confirms action with user - Displays success/error toast ## Integration Examples ### Adding Version History to Entity Pages ```typescript // Example: ParkDetail.tsx import { VersionIndicator } from '@/components/versioning/VersionIndicator'; import { EntityHistoryTabs } from '@/components/history/EntityHistoryTabs'; const ParkDetail = () => { const { park } = usePark(); return (
{/* Header with version indicator */}

{park.name}

{/* Tabs with history */} Details History {/* Entity details */}
); }; ``` ### Custom Version List ```typescript const CustomVersionList = ({ parkId }: { parkId: string }) => { const { versions, loading } = useEntityVersions('park', parkId); if (loading) return ; return (
    {versions.map((version) => (
  • {version.change_type} Version {version.version_number} {formatDate(version.created_at)}
  • ))}
); }; ``` ### Version Comparison Widget ```typescript const VersionComparison = ({ entityType, versionIds }: { entityType: EntityType; versionIds: [string, string] }) => { const { diff, loading } = useVersionComparison( entityType, versionIds[0], versionIds[1] ); if (loading) return ; const changedFields = Object.entries(diff || {}).filter( ([_, change]) => change.changed ); return ( {changedFields.length} fields changed {changedFields.map(([field, change]) => (
{JSON.stringify(change.from, null, 2)}
{JSON.stringify(change.to, null, 2)}
))}
); }; ``` ## Real-Time Updates The `useEntityVersions` hook automatically subscribes to real-time changes via Supabase: ```typescript // Automatic subscription in useEntityVersions useEffect(() => { const channel = supabase .channel(`${tableName}:${entityId}`) .on( 'postgres_changes', { event: '*', schema: 'public', table: tableName, filter: `${entityType}_id=eq.${entityId}`, }, () => { fetchVersions(); // Refetch on change } ) .subscribe(); return () => { supabase.removeChannel(channel); }; }, [entityType, entityId]); ``` ## Type Guards Use type guards to narrow discriminated unions: ```typescript function isParkVersion(version: EntityVersion): version is ParkVersion { return 'park_id' in version; } function isRideVersion(version: EntityVersion): version is RideVersion { return 'ride_id' in version; } // Usage if (isParkVersion(version)) { console.log(version.park_type); // TypeScript knows this is a park } ``` ## Error Handling All hooks handle errors gracefully: ```typescript const { versions, loading } = useEntityVersions('park', parkId); // Loading state if (loading) return ; // Error state (versions will be empty array) if (versions.length === 0) { return ; } // Success state return ; ``` ## Best Practices ### DO ✅ Use `VersionIndicator` on all entity detail pages ✅ Add history tab to entity pages ✅ Let hooks manage state and subscriptions ✅ Use type guards for discriminated unions ✅ Handle loading and error states ### DON'T ❌ Manually query version tables (use hooks) ❌ Bypass type system with `any` ❌ Forget to cleanup subscriptions ❌ Display sensitive data (created_by IDs) to public ## Performance Optimization ### Pagination For entities with many versions, implement pagination: ```typescript const { versions } = useEntityVersions('park', parkId); const [page, setPage] = useState(1); const pageSize = 10; const paginatedVersions = versions.slice( (page - 1) * pageSize, page * pageSize ); ``` ### Lazy Loading Only load version details when needed: ```typescript const [selectedVersion, setSelectedVersion] = useState(null); const { fieldHistory, fetchFieldHistory } = useEntityVersions('park', parkId); const handleVersionClick = (versionId: string) => { setSelectedVersion(versionId); fetchFieldHistory(versionId); // Load on demand }; ``` ## Testing ### Example Unit Test ```typescript import { renderHook, waitFor } from '@testing-library/react'; import { useEntityVersions } from '@/hooks/useEntityVersions'; describe('useEntityVersions', () => { it('fetches versions on mount', async () => { const { result } = renderHook(() => useEntityVersions('park', 'test-park-id') ); expect(result.current.loading).toBe(true); await waitFor(() => { expect(result.current.loading).toBe(false); expect(result.current.versions.length).toBeGreaterThan(0); }); }); }); ``` ## Troubleshooting See [TROUBLESHOOTING.md](./TROUBLESHOOTING.md) for common issues and solutions.