11import { faSearch } from "@fortawesome/free-solid-svg-icons" ;
22import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" ;
3- import { useQuery } from "@tanstack/react-query" ;
4- import { useState } from "react" ;
3+ import { useInfiniteQuery } from "@tanstack/react-query" ;
4+ import { useEffect , useState } from "react" ;
55import { getAllMyOpenIssues } from "../api/redmine" ;
66import InputField from "../components/general/InputField" ;
77import LoadingSpinner from "../components/general/LoadingSpinner" ;
@@ -31,29 +31,40 @@ const IssuesPage = () => {
3131
3232 const [ search , setSearch ] = useState ( "" ) ;
3333
34- const issuesQuery = useQuery ( [ "issues" ] , ( ) => getAllMyOpenIssues ( ) ) ;
35- const filteredIssues = searching && search ? issuesQuery . data ?. filter ( ( issue ) => new RegExp ( search , "i" ) . test ( `#${ issue . id } ${ issue . subject } ` ) ) : issuesQuery . data ;
36- const groupedIssues = filteredIssues ?. reduce (
37- (
38- result : {
39- [ id : number ] : {
40- project : TReference ;
41- issues : TIssue [ ] ;
42- } ;
34+ const issuesQuery = useInfiniteQuery ( {
35+ queryKey : [ "issues" ] ,
36+ queryFn : ( { pageParam = 0 } ) => getAllMyOpenIssues ( pageParam * 100 , 100 ) ,
37+ getNextPageParam : ( lastPage , allPages ) => ( lastPage . length === 100 ? allPages . length : undefined ) ,
38+ } ) ;
39+ useEffect ( ( ) => {
40+ if ( issuesQuery . hasNextPage && ! issuesQuery . isFetchingNextPage ) issuesQuery . fetchNextPage ( ) ;
41+ } , [ issuesQuery . hasNextPage , issuesQuery . isFetchingNextPage , issuesQuery . fetchNextPage ] ) ;
42+ const filteredIssues = searching && search ? issuesQuery . data ?. pages ?. flat ( ) . filter ( ( issue ) => new RegExp ( search , "i" ) . test ( `#${ issue . id } ${ issue . subject } ` ) ) : issuesQuery . data ?. pages ?. flat ( ) ;
43+ const groupedIssues = Object . values (
44+ filteredIssues ?. reduce (
45+ (
46+ result : {
47+ [ id : number ] : {
48+ project : TReference ;
49+ issues : TIssue [ ] ;
50+ sort : number ;
51+ } ;
52+ } ,
53+ issue
54+ ) => {
55+ if ( ! ( issue . project . id in result ) ) {
56+ result [ issue . project . id ] = {
57+ project : issue . project ,
58+ issues : [ ] ,
59+ sort : Object . keys ( result ) . length ,
60+ } ;
61+ }
62+ result [ issue . project . id ] . issues . push ( issue ) ;
63+ return result ;
4364 } ,
44- issue
45- ) => {
46- if ( ! ( issue . project . id in result ) ) {
47- result [ issue . project . id ] = {
48- project : issue . project ,
49- issues : [ ] ,
50- } ;
51- }
52- result [ issue . project . id ] . issues . push ( issue ) ;
53- return result ;
54- } ,
55- { }
56- ) ;
65+ { }
66+ ) ?? { }
67+ ) . sort ( ( { sort : a } , { sort : b } ) => a - b ) ;
5768
5869 const { data : issues , setData : setIssues } = useStorage < IssuesData > ( "issues" , { } ) ;
5970
@@ -63,9 +74,11 @@ const IssuesPage = () => {
6374 < div className = "flex flex-col gap-y-2" >
6475 { issuesQuery . isLoading && < LoadingSpinner /> }
6576 { issuesQuery . isError && < Toast type = "error" message = "Failed to load issues" allowClose = { false } /> }
66- { Object . entries ( groupedIssues ?? { } ) . map ( ( [ _ , { project, issues : groupIssues } ] ) => (
77+ { groupedIssues . map ( ( { project, issues : groupIssues } ) => (
6778 < >
68- < h5 className = "text-xs text-slate-500 dark:text-slate-300 truncate" > { project . name } </ h5 >
79+ < a href = { `${ settings . redmineURL } /projects/${ project . id } ` } target = "_blank" tabIndex = { - 1 } >
80+ < h5 className = "text-xs text-slate-500 dark:text-slate-300 truncate" > { project . name } </ h5 >
81+ </ a >
6982 { groupIssues . map ( ( issue ) => {
7083 const issueData =
7184 issue . id in issues
0 commit comments