@@ -32,8 +32,11 @@ type LogsTableProps = {
3232 isLoading ?: boolean ;
3333 isLoadingMore ?: boolean ;
3434 hasMore ?: boolean ;
35+ isIncomplete ?: boolean ;
3536 onLoadMore ?: ( ) => void ;
36- onCheckForMore ?: ( ) => void ;
37+ showKeepSearching ?: boolean ;
38+ onKeepSearching ?: ( ) => void ;
39+ searchedTo ?: string ;
3740 variant ?: TableVariant ;
3841 selectedLogId ?: string ;
3942 onLogSelect ?: ( logId : string ) => void ;
@@ -62,8 +65,11 @@ export function LogsTable({
6265 isLoading = false ,
6366 isLoadingMore = false ,
6467 hasMore = false ,
68+ isIncomplete = false ,
6569 onLoadMore,
66- onCheckForMore,
70+ showKeepSearching = false ,
71+ onKeepSearching,
72+ searchedTo,
6773 selectedLogId,
6874 onLogSelect,
6975} : LogsTableProps ) {
@@ -88,30 +94,25 @@ export function LogsTable({
8894 return ( ) => clearTimeout ( timer ) ;
8995 } , [ isLoadingMore ] ) ;
9096
91- // Intersection observer for infinite scroll
9297 useEffect ( ( ) => {
93- if ( ! hasMore || isLoadingMore || ! onLoadMore ) return ;
98+ if ( isLoading || ! hasMore || isLoadingMore || showKeepSearching || ! onLoadMore ) return ;
9499
100+ const target = loadMoreRef . current ;
101+ if ( ! target ) return ;
102+
103+ let active = true ;
95104 const observer = new IntersectionObserver (
96105 ( entries ) => {
97- if ( entries [ 0 ] . isIntersecting ) {
98- onLoadMore ( ) ;
99- }
106+ if ( active && entries [ 0 ] ?. isIntersecting ) onLoadMore ( ) ;
100107 } ,
101108 { threshold : 0.1 }
102109 ) ;
103-
104- const currentRef = loadMoreRef . current ;
105- if ( currentRef ) {
106- observer . observe ( currentRef ) ;
107- }
108-
110+ observer . observe ( target ) ;
109111 return ( ) => {
110- if ( currentRef ) {
111- observer . unobserve ( currentRef ) ;
112- }
112+ active = false ;
113+ observer . disconnect ( ) ;
113114 } ;
114- } , [ hasMore , isLoadingMore , onLoadMore ] ) ;
115+ } , [ hasMore , isLoading , isLoadingMore , onLoadMore , showKeepSearching ] ) ;
115116
116117 return (
117118 < div className = "relative h-full overflow-auto border-t scrollbar-thin scrollbar-track-transparent scrollbar-thumb-surface-control" >
@@ -133,7 +134,11 @@ export function LogsTable({
133134 </ TableHeader >
134135 < TableBody >
135136 { logs . length === 0 ? (
136- < BlankState isLoading = { isLoading } onRefresh = { ( ) => window . location . reload ( ) } />
137+ < BlankState
138+ isLoading = { isLoading }
139+ isPartial = { hasMore || isIncomplete }
140+ onRefresh = { ( ) => window . location . reload ( ) }
141+ />
137142 ) : (
138143 logs . map ( ( log ) => {
139144 const isSelected = selectedLogId === log . id ;
@@ -149,7 +154,7 @@ export function LogsTable({
149154
150155 return (
151156 < TableRow
152- key = { log . id }
157+ key = { log . projectionFingerprint ?? log . id }
153158 className = { cn (
154159 "cursor-pointer transition-colors" ,
155160 isSelected ? "bg-background-hover" : "hover:bg-background-dimmed"
@@ -200,27 +205,41 @@ export function LogsTable({
200205 ) }
201206 </ TableBody >
202207 </ Table >
203- { /* Infinite scroll trigger */ }
204- { hasMore && logs . length > 0 && (
208+ { ( hasMore || logs . length > 0 || isIncomplete ) && (
205209 < div ref = { loadMoreRef } className = "flex items-center justify-center py-12" >
206- < div className = { cn ( "flex items-center gap-2" , ! showLoadMoreSpinner && "invisible" ) } >
207- < Spinner /> < span className = "text-text-dimmed" > Loading more…</ span >
208- </ div >
209- </ div >
210- ) }
211- { /* Show all logs message with check for more button */ }
212- { ! hasMore && logs . length > 0 && (
213- < div className = "flex items-center justify-center py-12" >
214210 < div className = "flex flex-col items-center gap-3" >
215- < span className = "text-text-dimmed" > Showing all { logs . length } logs</ span >
211+ { searchedTo && < span className = "text-text-dimmed" > Searched back to { searchedTo } </ span > }
212+ { hasMore ? (
213+ isLoadingMore ? (
214+ < div className = { cn ( "flex items-center gap-2" , ! showLoadMoreSpinner && "invisible" ) } >
215+ < Spinner /> < span className = "text-text-dimmed" > Searching…</ span >
216+ </ div >
217+ ) : showKeepSearching ? (
218+ < Button variant = "tertiary/medium" onClick = { onKeepSearching } >
219+ Keep searching
220+ </ Button >
221+ ) : null
222+ ) : isIncomplete ? (
223+ < span className = "text-text-dimmed" > Search incomplete</ span >
224+ ) : (
225+ < span className = "text-text-dimmed" > Showing all { logs . length } logs</ span >
226+ ) }
216227 </ div >
217228 </ div >
218229 ) }
219230 </ div >
220231 ) ;
221232}
222233
223- function BlankState ( { isLoading, onRefresh } : { isLoading ?: boolean ; onRefresh ?: ( ) => void } ) {
234+ function BlankState ( {
235+ isLoading,
236+ isPartial,
237+ onRefresh,
238+ } : {
239+ isLoading ?: boolean ;
240+ isPartial ?: boolean ;
241+ onRefresh ?: ( ) => void ;
242+ } ) {
224243 if ( isLoading ) return < TableBlankRow colSpan = { 6 } /> ;
225244
226245 const handleRefresh = onRefresh ?? ( ( ) => window . location . reload ( ) ) ;
@@ -229,13 +248,17 @@ function BlankState({ isLoading, onRefresh }: { isLoading?: boolean; onRefresh?:
229248 < TableBlankRow colSpan = { 6 } >
230249 < div className = "flex flex-col items-center justify-center gap-6" >
231250 < Paragraph className = "w-auto" variant = "base/bright" >
232- No logs match your filters. Try refreshing or modifying your filters.
251+ { isPartial
252+ ? "No matching logs found in the searched range yet."
253+ : "No logs match your filters. Try refreshing or modifying your filters." }
233254 </ Paragraph >
234- < div className = "flex items-center gap-2" >
235- < Button LeadingIcon = { ArrowPathIcon } variant = "tertiary/medium" onClick = { handleRefresh } >
236- Refresh
237- </ Button >
238- </ div >
255+ { ! isPartial && (
256+ < div className = "flex items-center gap-2" >
257+ < Button LeadingIcon = { ArrowPathIcon } variant = "tertiary/medium" onClick = { handleRefresh } >
258+ Refresh
259+ </ Button >
260+ </ div >
261+ ) }
239262 </ div >
240263 </ TableBlankRow >
241264 ) ;
0 commit comments