11"use client" ;
22
33import { useRouter } from "next/navigation" ;
4- import { useEffect , useRef } from "react" ;
4+ import { useEffect , useRef , useState } from "react" ;
55import { useAppToast } from "@/components/providers" ;
66import { clearSyncWatch , getSyncWatchStartedAt , isSyncWatchActive } from "@/lib/sync-watch" ;
77
@@ -15,6 +15,9 @@ type SyncEventPayload = {
1515
1616type SyncStatusPayload = {
1717 activeJobCount ?: number ;
18+ totalJobCount ?: number ;
19+ finishedJobCount ?: number ;
20+ progressPercent ?: number | null ;
1821 latestJobStatus ?: "COMPLETED" | "FAILED" | null ;
1922 latestJobFinishedAt ?: string | null ;
2023} ;
@@ -27,6 +30,7 @@ export function DashboardLiveUpdater() {
2730 const latestTerminalRef = useRef < string | null > ( null ) ;
2831 const latestRefreshedTerminalRef = useRef < string | null > ( null ) ;
2932 const NO_ACTIVITY_TIMEOUT_MS = 3 * 60 * 1000 ;
33+ const [ progress , setProgress ] = useState < { percent : number ; finished : number ; total : number } | null > ( null ) ;
3034
3135 useEffect ( ( ) => {
3236 const source = new EventSource ( "/api/sync/events" ) ;
@@ -79,11 +83,26 @@ export function DashboardLiveUpdater() {
7983 const pollId = setInterval ( async ( ) => {
8084 if ( ! isSyncWatchActive ( ) ) return ;
8185 try {
82- const response = await fetch ( "/api/sync/status" , { cache : "no-store" } ) ;
86+ const watchStartedAt = getSyncWatchStartedAt ( ) ;
87+ const query = watchStartedAt ? `?since=${ encodeURIComponent ( new Date ( watchStartedAt ) . toISOString ( ) ) } ` : "" ;
88+ const response = await fetch ( `/api/sync/status${ query } ` , { cache : "no-store" } ) ;
8389 if ( ! response . ok ) return ;
8490 const status = ( await response . json ( ) ) as SyncStatusPayload ;
85- const watchStartedAt = getSyncWatchStartedAt ( ) ;
8691 const activeJobCount = Number ( status . activeJobCount ?? 0 ) ;
92+ const total = Number ( status . totalJobCount ?? 0 ) ;
93+ const finished = Number ( status . finishedJobCount ?? 0 ) ;
94+ const percent = Number . isFinite ( Number ( status . progressPercent ) )
95+ ? Number ( status . progressPercent ?? 0 )
96+ : total > 0
97+ ? Math . round ( ( finished / total ) * 100 )
98+ : 0 ;
99+ if ( isSyncWatchActive ( ) ) {
100+ setProgress ( {
101+ percent : Math . max ( 0 , Math . min ( 100 , percent ) ) ,
102+ finished : Math . max ( 0 , finished ) ,
103+ total : Math . max ( 0 , total ) ,
104+ } ) ;
105+ }
87106 const terminalTime = status . latestJobFinishedAt ? Date . parse ( status . latestJobFinishedAt ) : NaN ;
88107 const terminalKey = status . latestJobFinishedAt ? `${ status . latestJobStatus ?? "UNKNOWN" } :${ status . latestJobFinishedAt } ` : null ;
89108 const isFromCurrentWatch =
@@ -134,6 +153,7 @@ export function DashboardLiveUpdater() {
134153 clearSyncWatch ( ) ;
135154 hasAnnouncedStart . current = false ;
136155 latestRefreshedTerminalRef . current = null ;
156+ setProgress ( null ) ;
137157 router . refresh ( ) ;
138158 return ;
139159 }
@@ -144,6 +164,7 @@ export function DashboardLiveUpdater() {
144164 clearSyncWatch ( ) ;
145165 hasAnnouncedStart . current = false ;
146166 latestRefreshedTerminalRef . current = null ;
167+ setProgress ( null ) ;
147168 }
148169 } catch {
149170 // ignore polling errors
@@ -156,5 +177,19 @@ export function DashboardLiveUpdater() {
156177 } ;
157178 } , [ pushToast , router ] ) ;
158179
159- return null ;
180+ if ( ! progress || ! isSyncWatchActive ( ) ) return null ;
181+
182+ return (
183+ < div className = "animate-in fade-in slide-in-from-top-1 rounded-lg border border-border/60 bg-card/70 p-3" >
184+ < div className = "mb-2 flex items-center justify-between gap-2 text-sm" >
185+ < p className = "font-medium" > Sync progress</ p >
186+ < p className = "text-muted-foreground" >
187+ { progress . total > 0 ? `${ progress . finished } /${ progress . total } ` : "Preparing..." } • { progress . percent } %
188+ </ p >
189+ </ div >
190+ < div className = "h-2 w-full overflow-hidden rounded-full bg-muted" >
191+ < div className = "h-full bg-primary transition-all duration-500" style = { { width : `${ progress . percent } %` } } />
192+ </ div >
193+ </ div >
194+ ) ;
160195}
0 commit comments