/** * Sync Dashboard * Stage 4: WordPress sync health and management * * Displays sync status, parity indicators, and sync controls */ import React, { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import PageMeta from '../../components/common/PageMeta'; import PageHeader from '../../components/common/PageHeader'; import { Card } from '../../components/ui/card'; import Button from '../../components/ui/button/Button'; import Badge from '../../components/ui/badge/Badge'; import { useToast } from '../../components/ui/toast/ToastContainer'; import { CheckCircleIcon, ErrorIcon, AlertIcon, BoltIcon, TimeIcon, FileIcon, BoxIcon, ArrowRightIcon, ChevronDownIcon, ChevronUpIcon, PlugInIcon } from '../../icons'; import { fetchSyncStatus, runSync, fetchSyncMismatches, fetchSyncLogs, SyncStatus, SyncMismatches, SyncLog, } from '../../services/api'; export default function SyncDashboard() { const { id: siteId } = useParams<{ id: string }>(); const navigate = useNavigate(); const toast = useToast(); const [syncStatus, setSyncStatus] = useState(null); const [mismatches, setMismatches] = useState(null); const [logs, setLogs] = useState([]); const [loading, setLoading] = useState(true); const [syncing, setSyncing] = useState(false); const [showMismatches, setShowMismatches] = useState(false); const [showLogs, setShowLogs] = useState(false); useEffect(() => { if (siteId) { loadSyncData(); } }, [siteId]); const loadSyncData = async () => { if (!siteId) return; try { setLoading(true); const [statusData, mismatchesData, logsData] = await Promise.all([ fetchSyncStatus(Number(siteId)), fetchSyncMismatches(Number(siteId)), fetchSyncLogs(Number(siteId), 50), ]); setSyncStatus(statusData); setMismatches(mismatchesData); setLogs(logsData.logs || []); } catch (error: any) { toast.error(`Failed to load sync data: ${error.message}`); } finally { setLoading(false); } }; const handleSync = async (direction: 'both' | 'to_external' | 'from_external' = 'both') => { if (!siteId) return; try { setSyncing(true); const result = await runSync(Number(siteId), direction); toast.success(`Sync completed: ${result.total_integrations} integration(s) synced`); await loadSyncData(); // Refresh data } catch (error: any) { toast.error(`Sync failed: ${error.message}`); } finally { setSyncing(false); } }; const getStatusColor = (status: string) => { switch (status) { case 'healthy': case 'success': return 'success'; case 'warning': return 'warning'; case 'error': case 'failed': return 'error'; default: return 'info'; } }; const getStatusIcon = (status: string) => { switch (status) { case 'healthy': case 'success': return ; case 'warning': return ; case 'error': case 'failed': return ; default: return ; } }; if (loading) { return (
Loading sync data...
); } if (!syncStatus) { return (

No sync data available

); } const hasIntegrations = syncStatus.integrations.length > 0; const totalMismatches = (mismatches?.taxonomies.missing_in_wordpress.length || 0) + (mismatches?.taxonomies.missing_in_igny8.length || 0) + (mismatches?.products.missing_in_wordpress.length || 0) + (mismatches?.products.missing_in_igny8.length || 0) + (mismatches?.posts.missing_in_wordpress.length || 0) + (mismatches?.posts.missing_in_igny8.length || 0); return (
, color: 'blue' }} hideSiteSector />
{/* Overall Status */}

Overall Status

{syncStatus.overall_status}
{syncStatus.integrations.length}
Active Integrations
{totalMismatches}
Mismatches
{syncStatus.last_sync_at ? new Date(syncStatus.last_sync_at).toLocaleString() : 'Never'}
Last Sync
{/* Integrations List */} {hasIntegrations ? (

Integrations

{syncStatus.integrations.map((integration) => (
{getStatusIcon(integration.is_healthy ? 'healthy' : integration.status)}

{integration.platform.charAt(0).toUpperCase() + integration.platform.slice(1)}

Last sync: {integration.last_sync_at ? new Date(integration.last_sync_at).toLocaleString() : 'Never'}

{integration.status} {integration.mismatch_count > 0 && ( {integration.mismatch_count} mismatch{integration.mismatch_count !== 1 ? 'es' : ''} )}
{integration.error && (
{integration.error}
)}
))}
) : (

No active integrations

)} {/* Mismatches Section */} {totalMismatches > 0 && ( {showMismatches && mismatches && (
{/* Taxonomy Mismatches */} {(mismatches.taxonomies.missing_in_wordpress.length > 0 || mismatches.taxonomies.missing_in_igny8.length > 0) && (

Taxonomy Mismatches

{mismatches.taxonomies.missing_in_wordpress.length > 0 && (
Missing in WordPress ({mismatches.taxonomies.missing_in_wordpress.length})
    {mismatches.taxonomies.missing_in_wordpress.slice(0, 5).map((item, idx) => (
  • • {item.name} ({item.type})
  • ))}
)} {mismatches.taxonomies.missing_in_igny8.length > 0 && (
Missing in IGNY8 ({mismatches.taxonomies.missing_in_igny8.length})
    {mismatches.taxonomies.missing_in_igny8.slice(0, 5).map((item, idx) => (
  • • {item.name} ({item.type})
  • ))}
)}
)} {/* Product Mismatches */} {(mismatches.products.missing_in_wordpress.length > 0 || mismatches.products.missing_in_igny8.length > 0) && (

Product Mismatches

{mismatches.products.missing_in_wordpress.length > 0 && (
{mismatches.products.missing_in_wordpress.length} product(s) missing in WordPress
)} {mismatches.products.missing_in_igny8.length > 0 && (
{mismatches.products.missing_in_igny8.length} product(s) missing in IGNY8
)}
)} {/* Post Mismatches */} {(mismatches.posts.missing_in_wordpress.length > 0 || mismatches.posts.missing_in_igny8.length > 0) && (

Post Mismatches

{mismatches.posts.missing_in_wordpress.length > 0 && (
{mismatches.posts.missing_in_wordpress.length} post(s) missing in WordPress
)} {mismatches.posts.missing_in_igny8.length > 0 && (
{mismatches.posts.missing_in_igny8.length} post(s) missing in IGNY8
)}
)}
)}
)} {/* Sync Logs */} {showLogs && (
{logs.length > 0 ? ( logs.map((log, idx) => (
{getStatusIcon(log.status)} {log.platform} {new Date(log.timestamp).toLocaleString()}
{log.status}
{log.error && (
{log.error}
)}
)) ) : (

No sync logs available

)}
)}
); }