@@ -11,6 +11,7 @@ import {
1111import { type LogEntry } from "@/lib/types" ;
1212import { useState } from 'react' ;
1313import { Loader2 } from "lucide-react" ;
14+ import { FileIcon } from "@/components/icons" ;
1415import React from 'react' ;
1516
1617interface LogTableProps {
@@ -21,9 +22,10 @@ interface LogTableProps {
2122 observerRef ?: ( node : Element | null ) => void ;
2223 isLoading ?: boolean ;
2324 hasMore ?: boolean ;
25+ isLoadingMore ?: boolean ;
2426}
2527
26- export function LogTable ( { logs = [ ] , onSort, sortColumn, sortOrder, observerRef, isLoading, hasMore } : LogTableProps ) {
28+ export function LogTable ( { logs = [ ] , onSort, sortColumn, sortOrder, observerRef, isLoading, hasMore, isLoadingMore } : LogTableProps ) {
2729 const [ hoveredColumn , setHoveredColumn ] = useState < string | null > ( null ) ;
2830
2931 const renderSortIndicator = ( column : string ) => {
@@ -35,122 +37,116 @@ export function LogTable({ logs = [], onSort, sortColumn, sortOrder, observerRef
3537
3638 return (
3739 < div className = "flex flex-col h-full" >
38- { /* Fixed header */ }
39- < div className = "sticky top-0 z-10 bg-white" >
40- < Table className = "border-collapse w-full table-fixed" >
41- < TableHeader className = "table-header" >
42- < TableRow >
43- < TableHead
44- className = "w-[10%] truncate"
45- >
46- ID
47- </ TableHead >
48- < TableHead
49- className = "w-[120px] max-w-[120px] truncate"
50- onClick = { ( ) => onSort ( 'timestamp' ) }
51- onMouseEnter = { ( ) => setHoveredColumn ( 'timestamp' ) }
52- onMouseLeave = { ( ) => setHoveredColumn ( null ) }
53- >
54- Time{ renderSortIndicator ( 'timestamp' ) }
55- </ TableHead >
56- < TableHead
57- className = "w-[80px] max-w-[80px] truncate"
58- >
59- Level
60- </ TableHead >
61- < TableHead
62- className = "w-[120px] max-w-[120px] truncate"
63- >
64- Service
65- </ TableHead >
66- < TableHead
67- className = "w-[80px] max-w-[80px] truncate"
68- >
69- Method
70- </ TableHead >
71- < TableHead
72- className = "w-[14%] truncate"
73- >
74- Path
75- </ TableHead >
76- < TableHead
77- className = "w-[80px] max-w-[80px] truncate"
78- >
79- Status
80- </ TableHead >
81- < TableHead
82- className = "w-[30%] truncate"
83- >
84- Message
85- </ TableHead >
86- </ TableRow >
87- </ TableHeader >
88- </ Table >
89- </ div >
90-
91- { /* Scrollable body */ }
92- < div className = "flex-1 overflow-auto min-h-0" >
40+ { isLoading ? (
9341 < div className = "h-full" >
94- < Table className = "border-collapse w-full table-fixed" >
95- < TableBody >
96- { logs ?. map ( ( log , index ) => (
97- < React . Fragment key = { log . request_id || index } >
98- { hasMore && index === logs . length - 6 && (
99- < tr ref = { observerRef } >
100- < td colSpan = { 8 } className = "p-0" >
101- { isLoading && (
102- < div className = "w-full flex items-center justify-center py-4" >
103- < Loader2 className = "h-6 w-6 animate-spin text-muted-foreground" />
104- </ div >
105- ) }
106- </ td >
107- </ tr >
108- ) }
109- < TableRow className = "table-row" >
110- < TableCell className = "w-[10%] truncate" > { log . request_id } </ TableCell >
111- < TableCell className = "w-[120px] max-w-[120px] truncate" >
112- { new Date ( log . timestamp ) . toLocaleDateString ( 'en-GB' , {
113- day : '2-digit' ,
114- month : '2-digit' ,
115- year : '2-digit' ,
116- hour : '2-digit' ,
117- minute : '2-digit' ,
118- second : '2-digit' ,
119- hour12 : false
120- } ) . replace ( ',' , '' ) }
121- </ TableCell >
122- < TableCell className = "w-[80px] max-w-[80px] truncate" >
123- < span className = { `inline-flex items-center rounded-sm px-2 py-1 text-xs font-medium
124- ${ log . level === 'ERROR' ? 'bg-[var(--bg-pill-error)] text-[var(--text-pill-error)]' :
125- log . level === 'WARN' ? 'bg-[var(--bg-pill-warn)] text-[var(--text-pill-warn)]' :
126- log . level === 'INFO' ? 'bg-[var(--bg-pill-info)] text-[var(--text-pill-info)]' :
127- log . level === 'DEBUG' ? 'bg-[var(--bg-pill-debug)] text-[var(--text-pill-debug)]' :
128- 'bg-[var(--bg-pill-default)] text-[var(--text-pill-default)]' } `
129- } >
130- { log . level }
131- </ span >
132- </ TableCell >
133- < TableCell className = "w-[120px] max-w-[120px] truncate" > { log . service } </ TableCell >
134- < TableCell className = "w-[80px] max-w-[80px] truncate" > { log . request_method } </ TableCell >
135- < TableCell className = "w-[14%] truncate" > { log . request_path } </ TableCell >
136- < TableCell className = "w-[80px] max-w-[80px] truncate" >
137- < span className = { `inline-flex items-center rounded-sm px-2 py-1 text-xs font-medium
138- ${ log . status_code >= 400 ? 'bg-[var(--bg-pill-error)] text-[var(--text-pill-error)]' :
139- log . status_code >= 300 ? 'bg-[var(--bg-pill-warn)] text-[var(--text-pill-warn)]' :
140- log . status_code >= 200 ? 'bg-[var(--bg-pill-success)] text-[var(--text-pill-success)]' :
141- 'bg-[var(--bg-pill-info)] text-[var(--text-pill-info)]' } `
142- } >
143- { log . status_code }
144- </ span >
145- </ TableCell >
146- < TableCell className = "w-[30%] truncate" > { log . message } </ TableCell >
147- </ TableRow >
148- </ React . Fragment >
149- ) ) }
150- </ TableBody >
151- </ Table >
42+ < div className = "space-y-[29px]" >
43+ { [ ...Array ( Math . ceil ( ( window . innerHeight - 48 ) / 67 ) ) ] . map ( ( _ , index ) => (
44+ < div
45+ key = { index }
46+ className = "h-[24px] bg-[#D9D9D9] rounded-[4px] animate-pulse"
47+ />
48+ ) ) }
49+ </ div >
15250 </ div >
153- </ div >
51+ ) : (
52+ < >
53+ < div className = "sticky top-0 z-10 bg-white" >
54+ < Table className = "border-collapse w-full table-fixed" >
55+ < TableHeader className = "table-header" >
56+ < TableRow >
57+ < TableHead className = "w-[10%] truncate" > ID</ TableHead >
58+ < TableHead
59+ className = "w-[120px] max-w-[120px] truncate"
60+ onClick = { ( ) => onSort ( 'timestamp' ) }
61+ onMouseEnter = { ( ) => setHoveredColumn ( 'timestamp' ) }
62+ onMouseLeave = { ( ) => setHoveredColumn ( null ) }
63+ >
64+ Time{ renderSortIndicator ( 'timestamp' ) }
65+ </ TableHead >
66+ < TableHead className = "w-[80px] max-w-[80px] truncate" > Level</ TableHead >
67+ < TableHead className = "w-[120px] max-w-[120px] truncate" > Service</ TableHead >
68+ < TableHead className = "w-[80px] max-w-[80px] truncate" > Method</ TableHead >
69+ < TableHead className = "w-[14%] truncate" > Path</ TableHead >
70+ < TableHead className = "w-[80px] max-w-[80px] truncate" > Status</ TableHead >
71+ < TableHead className = "w-[30%] truncate" > Message</ TableHead >
72+ </ TableRow >
73+ </ TableHeader >
74+ </ Table >
75+ </ div >
76+
77+ { logs . length === 0 ? (
78+ < div className = "h-[calc(100%-24px)] flex flex-col items-center justify-center bg-[--background] rounded-lg mt-6" >
79+ < div className = "mb-[18px]" >
80+ < FileIcon />
81+ </ div >
82+ < span className = "text-[#000000]" > No data for the selected filter</ span >
83+ </ div >
84+ ) : (
85+ < div className = "flex-1 overflow-auto min-h-0" >
86+ < div className = "h-full" >
87+ < Table className = "border-collapse w-full table-fixed" >
88+ < TableBody >
89+ { logs ?. map ( ( log , index ) => (
90+ < React . Fragment key = { log . request_id || index } >
91+ { hasMore && index === logs . length - 6 && (
92+ < tr ref = { observerRef } >
93+ < td colSpan = { 8 } className = "p-0" >
94+ { isLoadingMore && (
95+ < div className = "w-full flex items-center justify-center py-4" >
96+ < Loader2 className = "h-6 w-6 animate-spin text-muted-foreground" />
97+ </ div >
98+ ) }
99+ </ td >
100+ </ tr >
101+ ) }
102+ < TableRow className = "table-row" >
103+ < TableCell className = "w-[10%] truncate" > { log . request_id } </ TableCell >
104+ < TableCell className = "w-[120px] max-w-[120px] truncate" >
105+ { new Date ( log . timestamp ) . toLocaleDateString ( 'en-GB' , {
106+ day : '2-digit' ,
107+ month : '2-digit' ,
108+ year : '2-digit' ,
109+ hour : '2-digit' ,
110+ minute : '2-digit' ,
111+ second : '2-digit' ,
112+ hour12 : false
113+ } ) . replace ( ',' , '' ) }
114+ </ TableCell >
115+ < TableCell className = "w-[80px] max-w-[80px] truncate" >
116+ < span className = { `inline-flex items-center rounded-sm px-2 py-1 text-xs font-medium
117+ ${ log . level === 'ERROR' ? 'bg-[var(--bg-pill-error)] text-[var(--text-pill-error)]' :
118+ log . level === 'WARN' ? 'bg-[var(--bg-pill-warn)] text-[var(--text-pill-warn)]' :
119+ log . level === 'INFO' ? 'bg-[var(--bg-pill-info)] text-[var(--text-pill-info)]' :
120+ log . level === 'DEBUG' ? 'bg-[var(--bg-pill-debug)] text-[var(--text-pill-debug)]' :
121+ 'bg-[var(--bg-pill-default)] text-[var(--text-pill-default)]' } `
122+ } >
123+ { log . level }
124+ </ span >
125+ </ TableCell >
126+ < TableCell className = "w-[120px] max-w-[120px] truncate" > { log . service } </ TableCell >
127+ < TableCell className = "w-[80px] max-w-[80px] truncate" > { log . request_method } </ TableCell >
128+ < TableCell className = "w-[14%] truncate" > { log . request_path } </ TableCell >
129+ < TableCell className = "w-[80px] max-w-[80px] truncate" >
130+ < span className = { `inline-flex items-center rounded-sm px-2 py-1 text-xs font-medium
131+ ${ log . status_code >= 400 ? 'bg-[var(--bg-pill-error)] text-[var(--text-pill-error)]' :
132+ log . status_code >= 300 ? 'bg-[var(--bg-pill-warn)] text-[var(--text-pill-warn)]' :
133+ log . status_code >= 200 ? 'bg-[var(--bg-pill-success)] text-[var(--text-pill-success)]' :
134+ 'bg-[var(--bg-pill-info)] text-[var(--text-pill-info)]' } `
135+ } >
136+ { log . status_code }
137+ </ span >
138+ </ TableCell >
139+ < TableCell className = "w-[30%] truncate" > { log . message } </ TableCell >
140+ </ TableRow >
141+ </ React . Fragment >
142+ ) ) }
143+ </ TableBody >
144+ </ Table >
145+ </ div >
146+ </ div >
147+ ) }
148+ </ >
149+ ) }
154150 </ div >
155151 ) ;
156152}
0 commit comments