11"use client" ;
22
3- import React , { useEffect , useState } from "react" ;
3+ import React , { JSX , useEffect , useState } from "react" ;
44import DatePicker from 'react-datepicker' ;
55import 'react-datepicker/dist/react-datepicker.css' ;
66import ApplicationChart from "../../components/ApplicationChart" ;
@@ -21,28 +21,45 @@ interface DataModel {
2121 status : string ;
2222 error : string ;
2323 type : string ;
24+ [ key : string ] : any ;
2425}
2526
27+ interface SummaryData {
28+ total_entries : number ;
29+ status_count : Record < string , number > ;
30+ nsapp_count : Record < string , number > ;
31+ }
2632
2733const DataFetcher : React . FC = ( ) => {
2834 const [ data , setData ] = useState < DataModel [ ] > ( [ ] ) ;
35+ const [ summary , setSummary ] = useState < SummaryData | null > ( null ) ;
2936 const [ loading , setLoading ] = useState < boolean > ( true ) ;
3037 const [ error , setError ] = useState < string | null > ( null ) ;
31- const [ searchQuery , setSearchQuery ] = useState ( '' ) ;
32- const [ startDate , setStartDate ] = useState < Date | null > ( null ) ;
33- const [ endDate , setEndDate ] = useState < Date | null > ( null ) ;
34- const [ sortConfig , setSortConfig ] = useState < { key : keyof DataModel | null , direction : 'ascending' | 'descending' } > ( { key : 'id' , direction : 'descending' } ) ;
35- const [ itemsPerPage , setItemsPerPage ] = useState ( 25 ) ;
3638 const [ currentPage , setCurrentPage ] = useState ( 1 ) ;
39+ const [ itemsPerPage , setItemsPerPage ] = useState ( 25 ) ;
40+ const [ sortConfig , setSortConfig ] = useState < { key : string ; direction : 'ascending' | 'descending' } | null > ( null ) ;
3741
38- const [ showErrorRow , setShowErrorRow ] = useState < number | null > ( null ) ;
42+ useEffect ( ( ) => {
43+ const fetchSummary = async ( ) => {
44+ try {
45+ const response = await fetch ( "https://api.htl-braunau.at/data/summary" ) ;
46+ if ( ! response . ok ) throw new Error ( `Failed to fetch summary: ${ response . statusText } ` ) ;
47+ const result : SummaryData = await response . json ( ) ;
48+ setSummary ( result ) ;
49+ } catch ( err ) {
50+ setError ( ( err as Error ) . message ) ;
51+ }
52+ } ;
3953
54+ fetchSummary ( ) ;
55+ } , [ ] ) ;
4056
4157 useEffect ( ( ) => {
42- const fetchData = async ( ) => {
58+ const fetchPaginatedData = async ( ) => {
59+ setLoading ( true ) ;
4360 try {
44- const response = await fetch ( " https://api.htl-braunau.at/data/json" ) ;
45- if ( ! response . ok ) throw new Error ( " Failed to fetch data: ${response.statusText}" ) ;
61+ const response = await fetch ( ` https://api.htl-braunau.at/data/paginated?page= ${ currentPage } &limit= ${ itemsPerPage === 0 ? '' : itemsPerPage } ` ) ;
62+ if ( ! response . ok ) throw new Error ( ` Failed to fetch data: ${ response . statusText } ` ) ;
4663 const result : DataModel [ ] = await response . json ( ) ;
4764 setData ( result ) ;
4865 } catch ( err ) {
@@ -52,52 +69,34 @@ const DataFetcher: React.FC = () => {
5269 }
5370 } ;
5471
55- fetchData ( ) ;
56- } , [ ] ) ;
57-
58-
59- const filteredData = data . filter ( item => {
60- const matchesSearchQuery = Object . values ( item ) . some ( value =>
61- value . toString ( ) . toLowerCase ( ) . includes ( searchQuery . toLowerCase ( ) )
62- ) ;
63- const itemDate = new Date ( item . created_at ) ;
64- const matchesDateRange = ( ! startDate || itemDate >= startDate ) && ( ! endDate || itemDate <= endDate ) ;
65- return matchesSearchQuery && matchesDateRange ;
66- } ) ;
72+ fetchPaginatedData ( ) ;
73+ } , [ currentPage , itemsPerPage ] ) ;
6774
6875 const sortedData = React . useMemo ( ( ) => {
69- let sortableData = [ ...filteredData ] ;
70- if ( sortConfig . key !== null ) {
71- sortableData . sort ( ( a , b ) => {
72- if ( sortConfig . key !== null && a [ sortConfig . key ] < b [ sortConfig . key ] ) {
73- return sortConfig . direction === 'ascending' ? - 1 : 1 ;
74- }
75- if ( sortConfig . key !== null && a [ sortConfig . key ] > b [ sortConfig . key ] ) {
76- return sortConfig . direction === 'ascending' ? 1 : - 1 ;
77- }
78- return 0 ;
79- } ) ;
80- }
81- return sortableData ;
82- } , [ filteredData , sortConfig ] ) ;
76+ if ( ! sortConfig ) return data ;
77+ const sorted = [ ...data ] . sort ( ( a , b ) => {
78+ if ( a [ sortConfig . key ] < b [ sortConfig . key ] ) {
79+ return sortConfig . direction === 'ascending' ? - 1 : 1 ;
80+ }
81+ if ( a [ sortConfig . key ] > b [ sortConfig . key ] ) {
82+ return sortConfig . direction === 'ascending' ? 1 : - 1 ;
83+ }
84+ return 0 ;
85+ } ) ;
86+ return sorted ;
87+ } , [ data , sortConfig ] ) ;
8388
84- const requestSort = ( key : keyof DataModel | null ) => {
89+ if ( loading ) return < p > Loading...</ p > ;
90+ if ( error ) return < p > Error: { error } </ p > ;
91+
92+ const requestSort = ( key : string ) => {
8593 let direction : 'ascending' | 'descending' = 'ascending' ;
86- if ( sortConfig . key === key && sortConfig . direction === 'ascending' ) {
87- direction = 'descending' ;
88- } else if ( sortConfig . key === key && sortConfig . direction === 'descending' ) {
89- direction = 'ascending' ;
90- } else {
94+ if ( sortConfig && sortConfig . key === key && sortConfig . direction === 'ascending' ) {
9195 direction = 'descending' ;
9296 }
9397 setSortConfig ( { key, direction } ) ;
9498 } ;
9599
96- interface SortConfig {
97- key : keyof DataModel | null ;
98- direction : 'ascending' | 'descending' ;
99- }
100-
101100 const formatDate = ( dateString : string ) : string => {
102101 const date = new Date ( dateString ) ;
103102 const year = date . getFullYear ( ) ;
@@ -109,86 +108,15 @@ const DataFetcher: React.FC = () => {
109108 return `${ day } .${ month } .${ year } ${ hours } :${ minutes } ${ timezoneOffset } GMT` ;
110109 } ;
111110
112- const handleItemsPerPageChange = ( event : React . ChangeEvent < HTMLSelectElement > ) => {
113- setItemsPerPage ( Number ( event . target . value ) ) ;
114- setCurrentPage ( 1 ) ;
115- } ;
116-
117- const paginatedData = sortedData . slice ( ( currentPage - 1 ) * itemsPerPage , currentPage * itemsPerPage ) ;
118-
119-
120- if ( loading ) return < p > Loading...</ p > ;
121- if ( error ) return < p > Error: { error } </ p > ;
122-
123- var installingCounts : number = 0 ;
124- var failedCounts : number = 0 ;
125- var doneCounts : number = 0
126- var unknownCounts : number = 0 ;
127- data . forEach ( ( item ) => {
128- if ( item . status === "installing" ) {
129- installingCounts += 1 ;
130- } else if ( item . status === "failed" ) {
131- failedCounts += 1 ;
132- }
133- else if ( item . status === "done" ) {
134- doneCounts += 1 ;
135- }
136- else {
137- unknownCounts += 1 ;
138- }
139- } ) ;
140-
141111 return (
142112 < div className = "p-6 mt-20" >
143113 < h1 className = "text-2xl font-bold mb-4 text-center" > Created LXCs</ h1 >
144- < div className = "mb-4 flex space-x-4" >
145- < div >
146- < input
147- type = "text"
148- placeholder = "Search..."
149- value = { searchQuery }
150- onChange = { e => setSearchQuery ( e . target . value ) }
151- className = "p-2 border"
152- />
153- < label className = "text-sm text-gray-600 mt-1 block" > Search by keyword</ label >
154- </ div >
155- < div >
156- < DatePicker
157- selected = { startDate }
158- onChange = { date => setStartDate ( date ) }
159- selectsStart
160- startDate = { startDate }
161- endDate = { endDate }
162- placeholderText = "Start date"
163- className = "p-2 border"
164- />
165- < label className = "text-sm text-gray-600 mt-1 block" > Set a start date</ label >
166- </ div >
167-
168- < div >
169- < DatePicker
170- selected = { endDate }
171- onChange = { date => setEndDate ( date ) }
172- selectsEnd
173- startDate = { startDate }
174- endDate = { endDate }
175- placeholderText = "End date"
176- className = "p-2 border"
177- />
178- < label className = "text-sm text-gray-600 mt-1 block" > Set a end date</ label >
179- </ div >
180- </ div >
181- < ApplicationChart data = { filteredData } />
114+ < ApplicationChart data = { summary } />
115+ < p className = "text-lg font-bold mt-4" > </ p >
182116 < div className = "mb-4 flex justify-between items-center" >
183- < p className = "text-lg font-bold" > { filteredData . length } results found</ p >
184- < p className = "text-lg font" > Status Legend: 🔄 installing { installingCounts } | ✔️ completetd { doneCounts } | ❌ failed { failedCounts } | ❓ unknown { unknownCounts } </ p >
185- < select value = { itemsPerPage } onChange = { handleItemsPerPageChange } className = "p-2 border" >
186- < option value = { 25 } > 25</ option >
187- < option value = { 50 } > 50</ option >
188- < option value = { 100 } > 100</ option >
189- < option value = { 200 } > 200</ option >
190- </ select >
191- </ div >
117+ < p className = "text-lg font-bold" > { summary ?. total_entries } results found</ p >
118+ < p className = "text-lg font" > Status Legend: 🔄 installing { summary ?. status_count [ "installing" ] ?? 0 } | ✔️ completed { summary ?. status_count [ "done" ] ?? 0 } | ❌ failed { summary ?. status_count [ "failed" ] ?? 0 } | ❓ unknown</ p >
119+ </ div >
192120 < div className = "overflow-x-auto" >
193121 < div className = "overflow-y-auto lg:overflow-y-visible" >
194122 < table className = "min-w-full table-auto border-collapse" >
@@ -209,7 +137,7 @@ const DataFetcher: React.FC = () => {
209137 </ tr >
210138 </ thead >
211139 < tbody >
212- { paginatedData . map ( ( item , index ) => (
140+ { sortedData . map ( ( item , index ) => (
213141 < tr key = { index } >
214142 < td className = "px-4 py-2 border-b" >
215143 { item . status === "done" ? (
@@ -237,20 +165,7 @@ const DataFetcher: React.FC = () => {
237165 < td className = "px-4 py-2 border-b" > { item . ram_size } </ td >
238166 < td className = "px-4 py-2 border-b" > { item . method } </ td >
239167 < td className = "px-4 py-2 border-b" > { item . pve_version } </ td >
240- < td className = "px-4 py-2 border-b" >
241- { item . error && item . error !== "none" ? (
242- showErrorRow === index ? (
243- < >
244- { item . error }
245- < button onClick = { ( ) => setShowErrorRow ( null ) } > { item . error } </ button >
246- </ >
247- ) : (
248- < button onClick = { ( ) => setShowErrorRow ( index ) } > Click to show error</ button >
249- )
250- ) : (
251- "none"
252- ) }
253- </ td >
168+ < td className = "px-4 py-2 border-b" > { item . error } </ td >
254169 < td className = "px-4 py-2 border-b" > { formatDate ( item . created_at ) } </ td >
255170 </ tr >
256171 ) ) }
@@ -259,26 +174,25 @@ const DataFetcher: React.FC = () => {
259174 </ div >
260175 </ div >
261176 < div className = "mt-4 flex justify-between items-center" >
262- < button
263- onClick = { ( ) => setCurrentPage ( prev => Math . max ( prev - 1 , 1 ) ) }
264- disabled = { currentPage === 1 }
265- className = "p-2 border"
266- >
267- Previous
268- </ button >
177+ < button onClick = { ( ) => setCurrentPage ( prev => Math . max ( prev - 1 , 1 ) ) } disabled = { currentPage === 1 } className = "p-2 border" > Previous</ button >
269178 < span > Page { currentPage } </ span >
270- < button
271- onClick = { ( ) => setCurrentPage ( prev => ( prev * itemsPerPage < sortedData . length ? prev + 1 : prev ) ) }
272- disabled = { currentPage * itemsPerPage >= sortedData . length }
179+ < button onClick = { ( ) => setCurrentPage ( prev => prev + 1 ) } className = "p-2 border" > Next</ button >
180+ < select
181+ value = { itemsPerPage }
182+ onChange = { ( e ) => setItemsPerPage ( Number ( e . target . value ) ) }
273183 className = "p-2 border"
274184 >
275- Next
276- </ button >
185+ < option value = { 10 } > 10</ option >
186+ < option value = { 20 } > 20</ option >
187+ < option value = { 50 } > 50</ option >
188+ < option value = { 100 } > 100</ option >
189+ < option value = { 250 } > 250</ option >
190+ < option value = { 500 } > 500</ option >
191+ < option value = { 5000 } > 5000</ option >
192+ </ select >
277193 </ div >
278194 </ div >
279195 ) ;
280196} ;
281197
282-
283-
284198export default DataFetcher ;
0 commit comments