22 fetchDbInfo ,
33 loadPaginatedData ,
44} from "../redux/neurojson/neurojson.action" ;
5+ import { Row } from "../redux/neurojson/types/neurojson.interface" ;
56import {
67 Box ,
78 Typography ,
@@ -33,51 +34,89 @@ const DatasetPage: React.FC = () => {
3334 const { loading, error, data, limit, hasMore } = useAppSelector (
3435 ( state : { neurojson : any } ) => state . neurojson
3536 ) ;
36- const [ currentOffset , setCurrentOffset ] = useState ( 0 ) ;
37+ const [ currentPage , setCurrentPage ] = useState ( 1 ) ;
3738 const [ pageSize , setPageSize ] = useState ( 10 ) ;
39+ const totalPages = Math . ceil ( limit / pageSize ) ;
40+ const [ searchQuery , setSearchQuery ] = useState ( "" ) ;
3841
3942 useEffect ( ( ) => {
4043 if ( dbName ) {
4144 dispatch ( fetchDbInfo ( dbName . toLowerCase ( ) ) ) ;
4245 dispatch (
4346 loadPaginatedData ( {
4447 dbName : dbName . toLowerCase ( ) ,
45- offset : 0 ,
48+ offset : ( currentPage - 1 ) * pageSize ,
4649 limit : pageSize ,
4750 } )
4851 ) ;
4952 }
50- } , [ dbName , dispatch , pageSize ] ) ;
53+ } , [ dbName , dispatch , currentPage , pageSize ] ) ;
5154
52- const loadMoreData = ( ) => {
53- if ( dbName && ! loading ) {
54- const nextOffset = currentOffset + pageSize ;
55- setCurrentOffset ( nextOffset ) ;
56- dispatch (
57- loadPaginatedData ( {
58- dbName : dbName . toLowerCase ( ) ,
59- offset : nextOffset ,
60- limit : pageSize ,
61- } )
62- ) ;
63- }
55+ const handlePageChange = ( page : number ) => {
56+ if ( ! dbName || loading ) return ;
57+
58+ setCurrentPage ( page ) ;
59+ dispatch ( loadPaginatedData ( {
60+ dbName : dbName . toLowerCase ( ) ,
61+ offset : ( page - 1 ) * pageSize ,
62+ limit : pageSize ,
63+ } ) ) ;
6464 } ;
6565
6666 const handlePageSizeChange = ( event : any ) => {
6767 setPageSize ( event . target . value ) ;
68- setCurrentOffset ( 0 ) ; // Reset offset when changing page size
68+ setCurrentPage ( 1 ) ; // Reset offset when changing page size
69+ } ;
70+
71+ const getVisiblePageNumbers = ( ) => {
72+ const visiblePages : ( number | string ) [ ] = [ ] ;
73+ const maxVisible = 6 ;
74+
75+ if ( totalPages <= maxVisible + 2 ) {
76+ for ( let i = 1 ; i <= totalPages ; i ++ ) visiblePages . push ( i ) ;
77+ } else {
78+ const start = Math . max ( 2 , currentPage - 2 ) ;
79+ const end = Math . min ( totalPages - 1 , currentPage + 2 ) ;
80+
81+ visiblePages . push ( 1 ) ;
82+ if ( start > 2 ) visiblePages . push ( "..." ) ;
83+
84+ for ( let i = start ; i <= end ; i ++ ) visiblePages . push ( i ) ;
85+ if ( end < totalPages - 1 ) visiblePages . push ( "..." ) ;
86+
87+ visiblePages . push ( totalPages ) ;
88+ }
89+
90+ return visiblePages ;
91+ } ;
92+
93+ const handlePrevNextPage = ( direction : "prev" | "next" ) => {
94+ if ( direction === "prev" && currentPage > 1 ) {
95+ handlePageChange ( currentPage - 1 ) ;
96+ } else if ( direction === "next" && currentPage < totalPages ) {
97+ handlePageChange ( currentPage + 1 ) ;
98+ }
6999 } ;
70100
101+ const filteredData = data . filter ( ( doc : Row ) =>
102+ ( doc . value . name || "" )
103+ . toLowerCase ( )
104+ . includes ( searchQuery . toLowerCase ( ) )
105+ ) ;
106+
71107 return (
72108 < Box sx = { { padding : { xs : 2 , md : 4 } } } >
73109 < Box
74110 sx = { {
75111 display : "flex" ,
112+ flexWrap : "wrap" ,
113+ justifyContent : "space-between" ,
76114 alignItems : "center" ,
77115 gap : 2 ,
78- justifyContent : "space-between" ,
116+ mb : 3 ,
79117 } }
80118 >
119+ { /* Left: Title */ }
81120 < Typography
82121 variant = "h1"
83122 gutterBottom
@@ -90,67 +129,146 @@ const DatasetPage: React.FC = () => {
90129 Database: { dbName || "N/A" }
91130 </ Typography >
92131
93- < Box sx = { { mb : 3 , display : "flex" , alignItems : "center" } } >
132+ { /* Right: Total + Dropdown + Pagination */ }
133+ < Box sx = { { display : "flex" , flexWrap : "wrap" , alignItems : "center" , gap : 3 } } >
134+ { /* Left: Total datasets */ }
135+ < Typography
136+ sx = { {
137+ fontWeight : 600 ,
138+ fontSize : "1.2rem" ,
139+ color : Colors . white ,
140+ } }
141+ >
142+ Total datasets: { limit }
143+ </ Typography >
144+
145+ { /* Search in page input */ }
146+ < Box sx = { { display : "flex" , alignItems : "center" , gap : 1.5 } } >
147+ < Typography sx = { { fontWeight : 500 , fontSize : "1rem" , color : Colors . white } } >
148+ Search in page:
149+ </ Typography >
150+ < input
151+ type = "text"
152+ placeholder = "Filter results in this page"
153+ value = { searchQuery }
154+ onChange = { ( e ) => setSearchQuery ( e . target . value ) }
155+ style = { {
156+ padding : "6px 10px" ,
157+ borderRadius : "4px" ,
158+ border : `2px solid ${ Colors . primary . main } ` ,
159+ fontSize : "0.95rem" ,
160+ minWidth : "200px" ,
161+ } }
162+ />
163+ </ Box >
164+
165+ { /* Right: Label + Select in one line */ }
166+ < Box
167+ sx = { {
168+ display : "flex" ,
169+ alignItems : "center" ,
170+ gap : 1.5 ,
171+ } }
172+ >
173+ </ Box >
174+ < Typography sx = { { fontWeight : 500 , fontSize : "1rem" , color : Colors . white , minWidth : "150px" , } } >
175+ Dataset per page:
176+ </ Typography >
177+ { /* Dataset per page dropdown */ }
94178 < FormControl
95179 size = "small"
96180 sx = { {
97- minWidth : 150 ,
181+ minWidth : 160 ,
98182 backgroundColor : Colors . white ,
99183 borderRadius : 1 ,
100184 boxShadow : "0 2px 4px rgba(0,0,0,0.05)" ,
101185 "& .MuiInputLabel-root" : {
102186 color : Colors . textSecondary ,
103187 fontWeight : 500 ,
188+ zIndex : 1 , // ✅ ensures label is above the select box
104189 } ,
105190 "& .MuiOutlinedInput-root" : {
106- transition : "all 0.2s ease-in-out" ,
107191 "& fieldset" : {
108192 borderColor : Colors . primary . main ,
109- borderWidth : 2 ,
110- } ,
111- "&:hover fieldset" : {
112- borderColor : Colors . primary . dark ,
113- borderWidth : 2 ,
114193 } ,
115- "&.Mui-focused fieldset" : {
116- borderColor : Colors . primary . dark ,
117- borderWidth : 2 ,
118- } ,
119- } ,
194+ } ,
120195 } }
121- >
122- < InputLabel > Items per page</ InputLabel >
196+ >
123197 < Select
124198 value = { pageSize }
125- label = "Items per page"
199+ label = "Dataset per page"
126200 onChange = { handlePageSizeChange }
127201 sx = { {
128- color : Colors . textPrimary ,
129202 fontWeight : 500 ,
130203 "& .MuiSelect-icon" : {
131204 color : Colors . primary . main ,
132- transition : "transform 0.2s ease-in-out" ,
133- } ,
134- "&:hover .MuiSelect-icon" : {
135- transform : "rotate(180deg)" ,
136- color : Colors . primary . dark ,
137205 } ,
138206 } }
139207 >
140- < MenuItem value = { 10 } sx = { { fontWeight : 500 } } >
141- 10 items
142- </ MenuItem >
143- < MenuItem value = { 25 } sx = { { fontWeight : 500 } } >
144- 25 items
145- </ MenuItem >
146- < MenuItem value = { 50 } sx = { { fontWeight : 500 } } >
147- 50 items
148- </ MenuItem >
149- < MenuItem value = { 100 } sx = { { fontWeight : 500 } } >
150- 100 items
151- </ MenuItem >
208+ < MenuItem value = { 10 } > 10</ MenuItem >
209+ < MenuItem value = { 25 } > 25</ MenuItem >
210+ < MenuItem value = { 50 } > 50</ MenuItem >
211+ < MenuItem value = { 100 } > 100</ MenuItem >
152212 </ Select >
153213 </ FormControl >
214+
215+ { /* Pagination buttons */ }
216+ { ! loading && (
217+ < Box sx = { { display : "flex" , flexWrap : "wrap" , alignItems : "center" , gap : 1 } } >
218+ < Button
219+ onClick = { ( ) => handlePrevNextPage ( "prev" ) }
220+ disabled = { currentPage === 1 }
221+ sx = { {
222+ minWidth : "36px" ,
223+ backgroundColor : Colors . primary . main ,
224+ color : "white" ,
225+ "&:disabled" : { backgroundColor : "#ccc" } ,
226+ } }
227+ >
228+ <
229+ </ Button >
230+
231+ { getVisiblePageNumbers ( ) . map ( ( item , idx ) =>
232+ item === "..." ? (
233+ < Typography
234+ key = { idx }
235+ sx = { { px : 1.5 , fontSize : "1rem" , color : Colors . textSecondary } }
236+ >
237+ ...
238+ </ Typography >
239+ ) : (
240+ < Button
241+ key = { item }
242+ variant = { item === currentPage ? "contained" : "outlined" }
243+ onClick = { ( ) => handlePageChange ( Number ( item ) ) }
244+ sx = { {
245+ minWidth : "36px" ,
246+ padding : "4px 8px" ,
247+ fontWeight : item === currentPage ? "bold" : "normal" ,
248+ backgroundColor : item === currentPage ? Colors . primary . main : "white" ,
249+ color : item === currentPage ? "white" : Colors . primary . main ,
250+ borderColor : Colors . primary . main ,
251+ } }
252+ >
253+ { item }
254+ </ Button >
255+ )
256+ ) }
257+
258+ < Button
259+ onClick = { ( ) => handlePrevNextPage ( "next" ) }
260+ disabled = { currentPage === totalPages }
261+ sx = { {
262+ minWidth : "36px" ,
263+ backgroundColor : Colors . primary . main ,
264+ color : "white" ,
265+ "&:disabled" : { backgroundColor : "#ccc" } ,
266+ } }
267+ >
268+ >
269+ </ Button >
270+ </ Box >
271+ ) }
154272 </ Box >
155273 </ Box >
156274
@@ -178,21 +296,36 @@ const DatasetPage: React.FC = () => {
178296
179297 { ! loading && ! error && data . length > 0 && (
180298 < Grid container spacing = { 3 } >
181- { data . map ( ( doc : any ) => (
299+ { filteredData . map ( ( doc : any , index : number ) => {
300+ const datasetIndex = ( currentPage - 1 ) * pageSize + index + 1 ;
301+ return (
182302 < Grid item xs = { 12 } sm = { 6 } key = { doc . id } >
183303 < Card
184304 sx = { {
305+ position : "relative" , // ✅ allows absolute positioning of the number
185306 backgroundColor : Colors . white ,
186307 boxShadow : "0 2px 4px rgba(0,0,0,0.1)" ,
187308 height : "100%" ,
188309 display : "flex" ,
189310 flexDirection : "column" ,
190311 } }
191312 >
313+ { /* Dataset index number on top-right corner */ }
314+ < Box
315+ sx = { {
316+ position : "absolute" ,
317+ top : 8 ,
318+ right : 12 ,
319+ fontSize : "2rem" ,
320+ fontWeight : "bold" ,
321+ color : "rgba(0, 0, 0, 0.36)" ,
322+ } }
323+ >
324+ { datasetIndex }
325+ </ Box >
192326 < CardContent sx = { { flex : 1 } } >
193327 < Button
194328 onClick = { ( ) =>
195- // navigate(`${RoutesEnum.DATABASES}/${dbName}/${doc.id}`)
196329 navigate ( `${ RoutesEnum . DATABASES } /${ encodeURIComponent ( dbName ?? '' ) } /${ encodeURIComponent ( doc . id ?? '' ) } ` )
197330 }
198331 sx = { {
@@ -282,7 +415,8 @@ const DatasetPage: React.FC = () => {
282415 </ CardContent >
283416 </ Card >
284417 </ Grid >
285- ) ) }
418+ )
419+ } ) }
286420 </ Grid >
287421 ) }
288422
@@ -296,27 +430,6 @@ const DatasetPage: React.FC = () => {
296430 No database information available.
297431 </ Typography >
298432 ) }
299-
300- { ! loading && (
301- < Box sx = { { textAlign : "center" , mt : 3 } } >
302- < Button
303- variant = "contained"
304- onClick = { loadMoreData }
305- // disabled={data.length >= limit}
306- disabled = { ! hasMore }
307- sx = { {
308- backgroundColor : Colors . primary . main ,
309- color : Colors . white ,
310- "&:hover" : {
311- backgroundColor : Colors . primary . dark ,
312- } ,
313- } }
314- >
315- Load More ({ data . length } of { limit } items)
316- { data . length >= limit && " - Limit Reached" }
317- </ Button >
318- </ Box >
319- ) }
320433 </ Box >
321434 ) ;
322435} ;
0 commit comments