import { useCallback, useEffect, useMemo, useState } from 'react'; import { AlertTriangle, CheckCircle2, CloudDownload, Database, Link2, Loader2, RefreshCw, ScrollText, Square, Timer, X, XCircle } from 'lucide-react'; import { fetchOlistRunDetails, fetchOlistStatus, getOlistAuthorizationUrl, startOlistCompositionSync, stopOlistCompositionSync } from '../dataService'; import type { OlistRunDetails, OlistStatus, OlistSyncRun } from '../types'; const formatDateTime = (value: string | null) => { if (!value) return '—'; const date = new Date(value); return Number.isNaN(date.getTime()) ? '—' : new Intl.DateTimeFormat('pt-BR', { dateStyle: 'short', timeStyle: 'medium' }).format(date); }; const formatDuration = (startedAt: string | null, endedAt: string | null, now: number) => { if (!startedAt) return '—'; const start = new Date(startedAt).getTime(); const end = endedAt ? new Date(endedAt).getTime() : now; if (Number.isNaN(start) || Number.isNaN(end)) return '—'; const totalSeconds = Math.max(0, Math.floor((end - start) / 1000)); const hours = Math.floor(totalSeconds / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; return hours ? `${hours}h ${minutes}m` : `${minutes}m ${seconds}s`; }; const runTone = (run: OlistSyncRun) => ( run.status === 'failed' ? 'text-red-300 border-red-400/30 bg-red-400/10' : run.status === 'running' ? 'text-sky-300 border-sky-400/30 bg-sky-400/10' : run.status === 'cancelled' ? 'text-amber-200 border-amber-400/30 bg-amber-400/10' : 'text-emerald-300 border-emerald-400/30 bg-emerald-400/10' ); const runStatusLabel = (run: OlistSyncRun) => ( run.status === 'running' ? (run.cancelRequestedAt ? 'Parando' : 'Em andamento') : run.status === 'failed' ? 'Falhou' : run.status === 'cancelled' ? 'Interrompida' : 'Concluída' ); const OlistSync = () => { const [status, setStatus] = useState(null); const [isLoading, setIsLoading] = useState(true); const [isActionLoading, setIsActionLoading] = useState(false); const [error, setError] = useState(''); const [now, setNow] = useState(0); const [selectedRunId, setSelectedRunId] = useState(null); const [runDetails, setRunDetails] = useState(null); const [detailsPages, setDetailsPages] = useState({ events: 1, products: 1 }); const [isDetailsLoading, setIsDetailsLoading] = useState(false); const loadStatus = useCallback(async (initial = false) => { if (initial) setIsLoading(true); try { setStatus(await fetchOlistStatus()); setError(''); } catch (loadError) { setError(loadError instanceof Error ? loadError.message : 'Não foi possível consultar a Olist.'); } finally { if (initial) setIsLoading(false); } }, []); useEffect(() => { const loadTimeout = window.setTimeout(() => void loadStatus(true), 0); const pollInterval = window.setInterval(() => void loadStatus(), 5_000); const clockInterval = window.setInterval(() => setNow(Date.now()), 1_000); return () => { window.clearTimeout(loadTimeout); window.clearInterval(pollInterval); window.clearInterval(clockInterval); }; }, [loadStatus]); const loadRunDetails = useCallback(async (runId: number, pages: { events: number; products: number }) => { setIsDetailsLoading(true); try { setRunDetails(await fetchOlistRunDetails(runId, pages.events, pages.products)); } catch (loadError) { setError(loadError instanceof Error ? loadError.message : 'Não foi possível consultar os detalhes da execução.'); } finally { setIsDetailsLoading(false); } }, []); useEffect(() => { if (!selectedRunId || runDetails?.run.status !== 'running') return; const intervalId = window.setInterval(() => void loadRunDetails(selectedRunId, detailsPages), 5_000); return () => window.clearInterval(intervalId); }, [selectedRunId, runDetails?.run.status, detailsPages, loadRunDetails]); const latestRun = status?.runs[0] || null; const completionPercent = useMemo(() => { const cache = status?.productCache; if (!cache?.total) return 0; return Math.round((cache.checked / cache.total) * 100); }, [status?.productCache]); const connect = async () => { setIsActionLoading(true); try { window.location.assign(await getOlistAuthorizationUrl()); } catch (actionError) { setError(actionError instanceof Error ? actionError.message : 'Não foi possível iniciar a conexão Olist.'); setIsActionLoading(false); } }; const startSync = async () => { setIsActionLoading(true); try { await startOlistCompositionSync(!status?.runs.some(run => run.status === 'completed')); await loadStatus(); } catch (actionError) { setError(actionError instanceof Error ? actionError.message : 'Não foi possível iniciar a sincronização.'); } finally { setIsActionLoading(false); } }; const stopSync = async () => { if (!window.confirm('Interromper a sincronização atual? Os produtos já concluídos serão preservados e a próxima sincronização continuará de onde parou.')) return; setIsActionLoading(true); try { await stopOlistCompositionSync(); await loadStatus(); } catch (actionError) { setError(actionError instanceof Error ? actionError.message : 'Não foi possível interromper a sincronização.'); } finally { setIsActionLoading(false); } }; const openRunDetails = (runId: number) => { const initialPages = { events: 1, products: 1 }; setDetailsPages(initialPages); setSelectedRunId(runId); void loadRunDetails(runId, initialPages); }; if (isLoading && !status) { return
; } return (

Olist

Monitoramento da conexão e da sincronização de composições.

{!status?.connected ? ( ) : ( <> {status.syncInProgress && ( )} )}
{error &&
{error}
} {!status?.configured &&
Configure as credenciais Olist V3 no ambiente antes de conectar.
}
Conexão

{status?.connected ? 'Conectada' : 'Não conectada'}

Token expira: {formatDateTime(status?.tokenExpiresAt || null)}

Execução atual

{latestRun?.status === 'running' ? (latestRun.cancelRequestedAt ? 'Parando' : 'Em andamento') : 'Em espera'}

{latestRun?.status === 'running' ? `Duração: ${formatDuration(latestRun.startedAt, null, now)}` : `Próxima automática: ${formatDateTime(status?.nextScheduledSyncAt || null)}`}

Estruturas

{status?.productCache.checked.toLocaleString('pt-BR') || 0} / {status?.productCache.total.toLocaleString('pt-BR') || 0}

{completionPercent}% verificadas · {status?.productCache.pending || 0} pendentes

{latestRun && (

Última execução

Início: {formatDateTime(latestRun.startedAt)} · {latestRun.mode === 'full' ? 'Carga completa' : 'Atualização incremental'}

{latestRun.status === 'failed' ? : latestRun.status === 'running' ? : latestRun.status === 'cancelled' ? : } {runStatusLabel(latestRun)}
{[ ['Produtos verificados', latestRun.productsScanned], ['Fabricados', latestRun.manufacturedProducts], ['Composições', latestRun.imported], ['Componentes', latestRun.componentCount], ['Referências', latestRun.referenceCount], ['Falhas', latestRun.failed] ].map(([label, value]) =>

{label}

{Number(value).toLocaleString('pt-BR')}

)}
{latestRun.errorMessage &&

{latestRun.errorMessage}

} {latestRun.details.length > 0 &&
Produtos com falha
{latestRun.details.slice(0, 5).map((detail, index) =>

{detail.sku || detail.productId}: {detail.error || 'Falha sem detalhe'}

)}
}
)}

Histórico de sincronizações

{!status?.runs.length &&
Nenhuma sincronização iniciada.
} {status?.runs.map(run =>

{run.mode === 'full' ? 'Carga completa' : 'Atualização incremental'} · {run.trigger === 'scheduled' ? 'Automática' : 'Manual'}

{formatDateTime(run.startedAt)} · duração {formatDuration(run.startedAt, run.status === 'running' ? null : run.completedAt, now)}

{run.imported} comp.{run.referenceCount} refs.{run.failed} falhas{runStatusLabel(run)}
)}
{selectedRunId && (

Logs e produtos afetados

{runDetails ? `${runDetails.run.mode === 'full' ? 'Carga completa' : 'Atualização incremental'} · ${formatDateTime(runDetails.run.startedAt)}` : 'Carregando execução...'}

{isDetailsLoading && !runDetails ? (
) : runDetails ? (

Logs

{runDetails.events.total.toLocaleString('pt-BR')} eventos

{isDetailsLoading && }
{!runDetails.events.items.length &&

Nenhum evento registrado ainda.

} {runDetails.events.items.map(event =>

{event.message}

{event.sku &&

{event.sku} · {event.productDescription}

}

{formatDateTime(event.createdAt)}

)}
Página {runDetails.events.page}

Produtos afetados

{runDetails.affectedProducts.total.toLocaleString('pt-BR')} composições importadas nesta execução

{!runDetails.affectedProducts.items.length &&

Nenhuma composição importada nesta execução.

} {runDetails.affectedProducts.items.map(product =>

{product.productDescription || product.sku || product.productId}

{product.sku || `ID ${product.productId}`}

{Number(product.metadata.componentCount || 0).toLocaleString('pt-BR')} componentes · {Number(product.metadata.referenceCount || 0).toLocaleString('pt-BR')} referências

)}
Página {runDetails.affectedProducts.page}
) :
Não foi possível carregar os detalhes desta execução.
}
)}
); }; export default OlistSync;