Skip to content

Commit ebe5fd2

Browse files
committed
refactor: Improved settings page
- Improved settings page - Moved myAccount query to useMyAccount hook - Moved search to separate component
1 parent 5ccc8d9 commit ebe5fd2

File tree

6 files changed

+110
-47
lines changed

6 files changed

+110
-47
lines changed

src/components/issues/Search.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { faSearch } from "@fortawesome/free-solid-svg-icons";
2+
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3+
import { useEffect, useRef, useState } from "react";
4+
import useHotKey from "../../hooks/useHotkey";
5+
import InputField from "../general/InputField";
6+
7+
type PropTypes = {
8+
onSearch: (search: { searching: boolean; query: string }) => void;
9+
};
10+
11+
const Search = ({ onSearch }: PropTypes) => {
12+
const searchRef = useRef<HTMLInputElement>(null);
13+
const [searching, setSearching] = useState(false);
14+
const [query, setQuery] = useState("");
15+
16+
useEffect(() => {
17+
onSearch({
18+
searching,
19+
query,
20+
});
21+
}, [searching, query]);
22+
23+
// hotkeys
24+
useHotKey(
25+
() => {
26+
setSearching(true);
27+
searchRef.current?.focus();
28+
searchRef.current?.select();
29+
},
30+
{ ctrl: true, key: "k" }
31+
);
32+
useHotKey(
33+
() => {
34+
setSearching(true);
35+
searchRef.current?.focus();
36+
searchRef.current?.select();
37+
},
38+
{ ctrl: true, key: "f" }
39+
);
40+
useHotKey(
41+
() => {
42+
setSearching(false);
43+
setQuery("");
44+
},
45+
{ key: "Escape" },
46+
searching
47+
);
48+
49+
return <>{searching && <InputField ref={searchRef} icon={<FontAwesomeIcon icon={faSearch} />} placeholder="Search..." className="select-none mb-3" value={query} onChange={(e) => setQuery(e.target.value)} autoFocus autoComplete="off" />}</>;
50+
};
51+
52+
export default Search;

src/hooks/useMyAccount.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useQuery } from "@tanstack/react-query";
2+
import { getMyAccount } from "../api/redmine";
3+
4+
const useMyAccount = () => {
5+
const myAccountQuery = useQuery({
6+
queryKey: ["myAccount"],
7+
queryFn: getMyAccount,
8+
});
9+
10+
return {
11+
data: myAccountQuery.data,
12+
isLoading: myAccountQuery.isInitialLoading,
13+
isError: myAccountQuery.isError,
14+
};
15+
};
16+
17+
export default useMyAccount;

src/hooks/useMyIssues.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
11
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
22
import { useEffect } from "react";
3-
import { getAllMyOpenIssues, getMyAccount, getOpenIssues, searchOpenIssues } from "../api/redmine";
3+
import { getAllMyOpenIssues, getOpenIssues, searchOpenIssues } from "../api/redmine";
44
import useDebounce from "./useDebounce";
55
import useSettings from "./useSettings";
66

77
const useMyIssues = (additionalIssuesIds: number[], search: string) => {
88
const { settings } = useSettings();
99

10-
const myAccountQuery = useQuery({
11-
queryKey: ["myAccount"],
12-
queryFn: getMyAccount,
13-
});
14-
1510
const issuesQuery = useInfiniteQuery({
1611
queryKey: ["issues"],
1712
queryFn: ({ pageParam = 0 }) => getAllMyOpenIssues(pageParam * 100, 100),
@@ -60,7 +55,6 @@ const useMyIssues = (additionalIssuesIds: number[], search: string) => {
6055
let extendedSearchIssues = extendedSearchIssuesQuery.data ?? [];
6156

6257
return {
63-
account: myAccountQuery.data,
6458
data: issues,
6559
extendedSearch: extendedSearchIssues,
6660
isLoading: issuesQuery.isInitialLoading || additionalIssuesQuery.isInitialLoading,

src/pages/IssuesPage.tsx

Lines changed: 11 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import { faSearch } from "@fortawesome/free-solid-svg-icons";
2-
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3-
import { useEffect, useRef, useState } from "react";
4-
import InputField from "../components/general/InputField";
1+
import { useEffect, useState } from "react";
52
import Toast from "../components/general/Toast";
63
import IssuesList, { IssuesData } from "../components/issues/IssuesList";
74
import IssuesListSkeleton from "../components/issues/IssuesListSkeleton";
8-
import useHotKey from "../hooks/useHotkey";
5+
import Search from "../components/issues/Search";
6+
import useMyAccount from "../hooks/useMyAccount";
97
import useMyIssues from "../hooks/useMyIssues";
108
import useSettings from "../hooks/useSettings";
119
import useStorage from "../hooks/useStorage";
@@ -15,32 +13,15 @@ const IssuesPage = () => {
1513

1614
const issuesData = useStorage<IssuesData>("issues", {});
1715

18-
const searchRef = useRef<HTMLInputElement>(null);
19-
const [searching, setSearching] = useState(false);
20-
const [search, setSearch] = useState("");
16+
const [search, setSearch] = useState<{ searching: boolean; query: string }>({ searching: false, query: "" });
17+
2118
const myIssuesQuery = useMyIssues(
2219
Object.keys(issuesData.data)
2320
.map((id) => Number(id))
2421
.filter((id) => issuesData.data[id].remembered || issuesData.data[id].remember || issuesData.data[id].active || issuesData.data[id].time > 0),
25-
searching ? search : ""
26-
);
27-
28-
// hotkeys
29-
useHotKey(
30-
() => {
31-
setSearching(true);
32-
searchRef.current?.focus();
33-
},
34-
{ ctrl: true, key: "k" }
35-
);
36-
useHotKey(
37-
() => {
38-
setSearching(true);
39-
searchRef.current?.focus();
40-
},
41-
{ ctrl: true, key: "f" }
22+
search.searching ? search.query : ""
4223
);
43-
useHotKey(() => setSearching(false), { key: "Escape" }, searching);
24+
const myAccount = useMyAccount();
4425

4526
useEffect(() => {
4627
const count = Object.values(issuesData.data).reduce((count, data) => count + (data.active ? 1 : 0), 0);
@@ -53,13 +34,13 @@ const IssuesPage = () => {
5334

5435
return (
5536
<>
56-
{searching && <InputField ref={searchRef} icon={<FontAwesomeIcon icon={faSearch} />} placeholder="Search..." className="select-none mb-3" onChange={(e) => setSearch(e.target.value)} autoFocus autoComplete="off" />}
37+
<Search onSearch={setSearch} />
5738
<div className="flex flex-col gap-y-2">
5839
{myIssuesQuery.isLoading && <IssuesListSkeleton />}
5940

60-
<IssuesList account={myIssuesQuery.account} issues={myIssuesQuery.data} issuesData={issuesData} />
41+
<IssuesList account={myAccount.data} issues={myIssuesQuery.data} issuesData={issuesData} />
6142

62-
{searching && settings.options.extendedSearch && (
43+
{search.searching && settings.options.extendedSearch && (
6344
<>
6445
<div className="relative">
6546
<div className="absolute inset-0 flex items-center" aria-hidden="true">
@@ -70,7 +51,7 @@ const IssuesPage = () => {
7051
</div>
7152
</div>
7253

73-
<IssuesList account={myIssuesQuery.account} issues={myIssuesQuery.extendedSearch} issuesData={issuesData} />
54+
<IssuesList account={myAccount.data} issues={myIssuesQuery.extendedSearch} issuesData={issuesData} />
7455
</>
7556
)}
7657

src/pages/SettingsPage.tsx

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as Yup from "yup";
66
import CheckBox from "../components/general/CheckBox";
77
import InputField from "../components/general/InputField";
88
import Toast from "../components/general/Toast";
9+
import useMyAccount from "../hooks/useMyAccount";
910
import useSettings, { Settings } from "../hooks/useSettings";
1011

1112
const SettingsPage = () => {
@@ -20,6 +21,8 @@ const SettingsPage = () => {
2021

2122
const [saved, setSaved] = useState(false);
2223

24+
const myAccount = useMyAccount();
25+
2326
return (
2427
<>
2528
<Formik
@@ -42,13 +45,28 @@ const SettingsPage = () => {
4245
{({ submitForm, touched, errors }) => (
4346
<>
4447
<Form>
45-
<div className="flex flex-col gap-y-2">
46-
<Field type="text" name="redmineURL" title="Redmine URL" placeholder="Redmine URL" required as={InputField} error={touched.redmineURL && errors.redmineURL} />
47-
<Field type="password" name="redmineApiKey" title="Redmine API-Key" placeholder="Redmine API-Key" required as={InputField} error={touched.redmineApiKey && errors.redmineApiKey} />
48-
<h2 className="text-lg font-semibold">Options:</h2>
49-
<Field type="checkbox" name="options.autoPauseOnSwitch" title="Auto pause" description="Automatic pause timers when changing issue" as={CheckBox} />
50-
<Field type="checkbox" name="options.extendedSearch" title="Extended search" description="Allows to search issues that are not assigned to you" as={CheckBox} />
51-
<Field type="checkbox" name="options.roundTimeNearestQuarterHour" title="Round to nearest 15 min" description="Round timer to nearest quarter hour" as={CheckBox} />
48+
<div className="flex flex-col gap-y-1">
49+
<fieldset className="p-1.5 rounded-lg border border-gray-300 dark:border-gray-600">
50+
<legend className="px-2 text-base font-semibold">Redmine</legend>
51+
<div className="flex flex-col gap-y-2">
52+
<Field type="text" name="redmineURL" title="Redmine URL" placeholder="Redmine URL" required as={InputField} error={touched.redmineURL && errors.redmineURL} />
53+
<Field type="password" name="redmineApiKey" title="Redmine API-Key" placeholder="Redmine API-Key" required as={InputField} error={touched.redmineApiKey && errors.redmineApiKey} />
54+
{myAccount.data && (
55+
<p>
56+
User: {myAccount.data.firstname} {myAccount.data.lastname} ({myAccount.data.login})
57+
</p>
58+
)}
59+
</div>
60+
</fieldset>
61+
<fieldset className="p-1.5 rounded-lg border border-gray-300 dark:border-gray-600">
62+
<legend className="px-2 text-base font-semibold">Options</legend>
63+
<div className="flex flex-col gap-y-2">
64+
<Field type="checkbox" name="options.autoPauseOnSwitch" title="Auto pause" description="Automatic pause timers when changing issue" as={CheckBox} />
65+
<Field type="checkbox" name="options.extendedSearch" title="Extended search" description="Allows to search issues that are not assigned to you" as={CheckBox} />
66+
<Field type="checkbox" name="options.roundTimeNearestQuarterHour" title="Round to nearest 15 min" description="Round timer to nearest quarter hour" as={CheckBox} />
67+
</div>
68+
</fieldset>
69+
5270
<button
5371
type="button"
5472
className={clsx(
@@ -64,7 +82,7 @@ const SettingsPage = () => {
6482
</>
6583
)}
6684
</Formik>
67-
<div className="absolute bottom-0 w-full flex flex-col items-center p-2">
85+
<div className="w-full flex flex-col items-center p-2 mt-3">
6886
<g>{chrome.runtime.getManifest().name}</g>
6987
<p>Version: {chrome.runtime.getManifest().version}</p>
7088
</div>

src/types/redmine.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,9 @@ export type TSearchResult = {
7171

7272
export type TAccount = {
7373
id: number;
74-
fistname: string;
75-
lastname: string;
7674
login: string;
75+
firstname: string;
76+
lastname: string;
7777
mail: string;
78+
last_login_on: string;
7879
};

0 commit comments

Comments
 (0)