@@ -5,6 +5,7 @@ import Toast from "../components/general/Toast";
55import Issue from "../components/issues/Issue" ;
66import useSettings from "../hooks/useSettings" ;
77import useStorage from "../hooks/useStorage" ;
8+ import { TIssue , TReference } from "../types/redmine" ;
89
910type IssuesData = {
1011 [ id : number ] : {
@@ -18,79 +19,106 @@ const IssuesPage = () => {
1819 const { settings } = useSettings ( ) ;
1920
2021 const issuesQuery = useQuery ( [ "issues" ] , ( ) => getAllMyOpenIssues ( ) ) ;
22+ const groupedIssues = issuesQuery . data ?. reduce (
23+ (
24+ result : {
25+ [ id : number ] : {
26+ project : TReference ;
27+ issues : TIssue [ ] ;
28+ } ;
29+ } ,
30+ issue
31+ ) => {
32+ if ( ! ( issue . project . id in result ) ) {
33+ result [ issue . project . id ] = {
34+ project : issue . project ,
35+ issues : [ ] ,
36+ } ;
37+ }
38+ result [ issue . project . id ] . issues . push ( issue ) ;
39+ return result ;
40+ } ,
41+ { }
42+ ) ;
2143
2244 const { data : issues , setData : setIssues } = useStorage < IssuesData > ( "issues" , { } ) ;
2345
2446 return (
2547 < >
26- < div className = "flex flex-col items-center gap-y-2" >
48+ < div className = "flex flex-col gap-y-2" >
2749 { issuesQuery . isLoading && < LoadingSpinner /> }
2850 { issuesQuery . isError && < Toast type = "error" message = "Failed to load issues" allowClose = { false } /> }
29- { issuesQuery . data ?. map ( ( issue ) => {
30- const issueData =
31- issue . id in issues
32- ? issues [ issue . id ]
33- : {
34- active : false ,
35- start : undefined ,
36- time : 0 ,
37- } ;
38- return (
39- < Issue
40- issue = { issue }
41- isActive = { issueData . active }
42- time = { issueData . time }
43- start = { issueData . start }
44- onStart = { ( ) => {
45- setIssues ( {
46- ...( settings . options . autoPauseOnSwitch
47- ? Object . entries ( issues ) . reduce ( ( res , [ id , val ] ) => {
48- // @ts -ignore
49- res [ id ] = val . active
50- ? {
51- active : false ,
52- start : undefined ,
53- time : calcTime ( val . time , val . start ) ,
54- }
55- : val ;
56- return res ;
57- } , { } )
58- : issues ) ,
59- [ issue . id ] : {
60- active : true ,
61- start : new Date ( ) . getTime ( ) ,
62- time : issueData . time ,
63- } ,
64- } ) ;
65- } }
66- onStop = { ( time ) => {
67- setIssues ( {
68- ...issues ,
69- [ issue . id ] : {
70- active : false ,
71- start : undefined ,
72- time : time ,
73- } ,
74- } ) ;
75- } }
76- onClear = { ( ) => {
77- setIssues ( {
78- ...issues ,
79- [ issue . id ] : {
80- active : false ,
81- start : undefined ,
82- time : 0 ,
83- } ,
84- } ) ;
85- } }
86- onDone = { ( time ) => {
87- const h = time / 1000 / 60 / 60 ;
88- window . open ( `${ settings . redmineURL } /issues/${ issue . id } /time_entries/new?time_entry[hours]=${ h } ` ) ;
89- } }
90- key = { issue . id }
91- />
92- ) ;
93- } ) }
51+ { Object . entries ( groupedIssues ?? { } ) . map ( ( [ _ , { project, issues : groupIssues } ] ) => (
52+ < >
53+ < h5 className = "text-xs text-slate-500 dark:text-slate-300 truncate" > { project . name } </ h5 >
54+ { groupIssues . map ( ( issue ) => {
55+ const issueData =
56+ issue . id in issues
57+ ? issues [ issue . id ]
58+ : {
59+ active : false ,
60+ start : undefined ,
61+ time : 0 ,
62+ } ;
63+ return (
64+ < Issue
65+ issue = { issue }
66+ isActive = { issueData . active }
67+ time = { issueData . time }
68+ start = { issueData . start }
69+ onStart = { ( ) => {
70+ setIssues ( {
71+ ...( settings . options . autoPauseOnSwitch
72+ ? Object . entries ( issues ) . reduce ( ( res , [ id , val ] ) => {
73+ // @ts -ignore
74+ res [ id ] = val . active
75+ ? {
76+ active : false ,
77+ start : undefined ,
78+ time : calcTime ( val . time , val . start ) ,
79+ }
80+ : val ;
81+ return res ;
82+ } , { } )
83+ : issues ) ,
84+ [ issue . id ] : {
85+ active : true ,
86+ start : new Date ( ) . getTime ( ) ,
87+ time : issueData . time ,
88+ } ,
89+ } ) ;
90+ } }
91+ onStop = { ( time ) => {
92+ setIssues ( {
93+ ...issues ,
94+ [ issue . id ] : {
95+ active : false ,
96+ start : undefined ,
97+ time : time ,
98+ } ,
99+ } ) ;
100+ } }
101+ onClear = { ( ) => {
102+ setIssues ( {
103+ ...issues ,
104+ [ issue . id ] : {
105+ active : false ,
106+ start : undefined ,
107+ time : 0 ,
108+ } ,
109+ } ) ;
110+ } }
111+ onDone = { ( time ) => {
112+ const h = time / 1000 / 60 / 60 ;
113+ window . open ( `${ settings . redmineURL } /issues/${ issue . id } /time_entries/new?time_entry[hours]=${ h } ` ) ;
114+ } }
115+ key = { issue . id }
116+ />
117+ ) ;
118+ } ) }
119+ </ >
120+ ) ) }
121+
94122 { issuesQuery . data ?. length === 0 && < p > No issues</ p > }
95123 </ div >
96124 </ >
0 commit comments