Skip to content

Commit 4e070bf

Browse files
committed
feat: Group issues by project
- Group issues list by project - Renamed types
1 parent fea607a commit 4e070bf

File tree

4 files changed

+107
-79
lines changed

4 files changed

+107
-79
lines changed

src/api/redmine.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { Issue } from "../types/redmine";
1+
import { TIssue } from "../types/redmine";
22
import instance from "./axios.config";
33

4-
export const getAllMyOpenIssues = async (offset = 0): Promise<Issue[]> => {
4+
export const getAllMyOpenIssues = async (offset = 0): Promise<TIssue[]> => {
55
return instance.get(`/issues.json?limit=100&offset=${offset}&status_id=open&assigned_to_id=me`).then((res) => res.data.issues);
66
};

src/components/issues/Issue.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import { faCheck, faPause, faPlay, faStop } from "@fortawesome/free-solid-svg-ic
22
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
33
import { useEffect, useState } from "react";
44
import useSettings from "../../hooks/useSettings";
5-
import { Issue } from "../../types/redmine";
5+
import { TIssue } from "../../types/redmine";
66

77
type PropTypes = {
8-
issue: Issue;
8+
issue: TIssue;
99
isActive: boolean;
1010
time: number;
1111
start?: number;

src/pages/IssuesPage.tsx

Lines changed: 94 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Toast from "../components/general/Toast";
55
import Issue from "../components/issues/Issue";
66
import useSettings from "../hooks/useSettings";
77
import useStorage from "../hooks/useStorage";
8+
import { TIssue, TReference } from "../types/redmine";
89

910
type 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
</>

src/types/redmine.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
1-
export type Reference = {
1+
export type TReference = {
22
id: number;
33
name: string;
44
};
55

6-
export type Status = {
6+
export type TStatus = {
77
id: number;
88
name: string;
99
is_closed: boolean;
1010
};
1111

12-
export type Issue = {
12+
export type TIssue = {
1313
id: number;
14-
project: Reference;
15-
tracker: Reference;
16-
status: Status;
17-
priority: Reference;
18-
author: Reference;
19-
assigned_to: Reference;
14+
project: TReference;
15+
tracker: TReference;
16+
status: TStatus;
17+
priority: TReference;
18+
author: TReference;
19+
assigned_to: TReference;
2020
subject: string;
2121
description: string;
2222
done_ratio: number;

0 commit comments

Comments
 (0)