Skip to content

Commit e5cbaf2

Browse files
committed
feat: Added "Auto pause" feature
- Added "Auto pause" feature - Improved InputField component - Added checkBox component - Merge defaultSettings with current settings
1 parent c230542 commit e5cbaf2

File tree

8 files changed

+101
-20
lines changed

8 files changed

+101
-20
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"@tanstack/react-query": "^4.29.5",
1919
"axios": "^1.4.0",
2020
"clsx": "^1.2.1",
21+
"deepmerge": "^4.3.1",
2122
"formik": "^2.2.9",
2223
"react": "^18.2.0",
2324
"react-dom": "^18.2.0",
@@ -31,8 +32,8 @@
3132
"@typescript-eslint/eslint-plugin": "^5.57.1",
3233
"@typescript-eslint/parser": "^5.57.1",
3334
"@vitejs/plugin-react": "^4.0.0",
34-
"autoprefixer": "^10.4.14",
3535
"adm-zip": "^0.5.10",
36+
"autoprefixer": "^10.4.14",
3637
"eslint": "^8.38.0",
3738
"eslint-plugin-react-hooks": "^4.6.0",
3839
"eslint-plugin-react-refresh": "^0.3.4",

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import clsx from "clsx";
2+
import { useId } from "react";
3+
4+
interface PropTypes extends React.ComponentProps<"input"> {
5+
title: string;
6+
description?: string;
7+
}
8+
9+
const CheckBox = ({ title, description, ...props }: PropTypes) => {
10+
const id = useId();
11+
12+
return (
13+
<div className="flex items-center pl-2 rounded-lg border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-700">
14+
<div className={clsx("flex items-center", description ? "h-12" : "h-8")}>
15+
<input
16+
{...props}
17+
id={id}
18+
type="checkbox"
19+
className={clsx("w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600", props.className)}
20+
/>
21+
</div>
22+
<div className="ml-2 text-sm">
23+
<label htmlFor={id} className="font-medium text-gray-900 dark:text-gray-300">
24+
{title}
25+
</label>
26+
{description && <p className="text-xs font-normal text-gray-500 dark:text-gray-300">{description}</p>}
27+
</div>
28+
</div>
29+
);
30+
};
31+
32+
export default CheckBox;

src/components/general/InputField.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,38 @@
11
import { faAsterisk } from "@fortawesome/free-solid-svg-icons";
22
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
33
import clsx from "clsx";
4+
import { useId } from "react";
45

56
interface PropTypes extends React.ComponentProps<"input"> {
67
error?: string;
78
}
89

9-
const InputField = (props: PropTypes) => {
10+
const InputField = ({ error, ...props }: PropTypes) => {
11+
const id = useId();
12+
1013
return (
1114
<>
1215
{props.title && (
13-
<label htmlFor={props.name} className="block text-sm font-medium text-gray-900 dark:text-white">
16+
<label htmlFor={id} className="block text-sm font-medium text-gray-900 dark:text-white">
1417
{props.title}
1518
{props.required && <FontAwesomeIcon icon={faAsterisk} size="2xs" className="text-red-600 ml-1" />}
1619
</label>
1720
)}
1821
<input
1922
{...props}
20-
id={props.name}
23+
id={id}
2124
required={false}
2225
className={clsx(
2326
"text-sm rounded-lg block w-full p-2.5",
2427
"bg-gray-50 border border-gray-300 text-gray-900 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white",
2528
"focus:ring-2 focus:ring-primary-500 focus:border-primary-500 dark:focus:ring-primary-500 dark:focus:border-primary-500",
2629
{
27-
"border-red-500 text-red-900 placeholder-red-700 dark:text-red-500 dark:placeholder-red-500 dark:border-red-500": props.error !== undefined,
28-
}
30+
"border-red-500 text-red-900 placeholder-red-700 dark:text-red-500 dark:placeholder-red-500 dark:border-red-500": error !== undefined,
31+
},
32+
props.className
2933
)}
3034
/>
31-
{props.error && <p className="text-sm text-red-600 dark:text-red-500">{props.error}</p>}
35+
{error && <p className="text-sm text-red-600 dark:text-red-500">{error}</p>}
3236
</>
3337
);
3438
};

src/components/issues/Issue.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@ type PropTypes = {
1515
onDone: (time: number) => void;
1616
};
1717

18-
const calcTime = (time: number, start?: number) => {
19-
return time + (start ? new Date().getTime() - start : 0);
20-
};
21-
2218
const Issue = ({ issue, isActive, time, start, onStart, onStop, onClear, onDone }: PropTypes) => {
2319
const { settings } = useSettings();
2420

@@ -59,6 +55,10 @@ const Issue = ({ issue, isActive, time, start, onStart, onStop, onClear, onDone
5955
);
6056
};
6157

58+
const calcTime = (time: number, start?: number) => {
59+
return time + (start ? new Date().getTime() - start : 0);
60+
};
61+
6262
export const formatTime = (seconds: number) => {
6363
if (isNaN(seconds) || seconds < 0) return "";
6464

src/hooks/useSettings.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
1+
import deepmerge from "deepmerge";
12
import { loadRedmineConfig } from "../api/axios.config";
23
import useStorage, { getStorage } from "./useStorage";
34

45
export type Settings = {
56
redmineURL: string;
67
redmineApiKey: string;
8+
options: {
9+
autoPauseOnSwitch: boolean;
10+
};
711
};
812

9-
const defaultSettings = { redmineURL: "", redmineApiKey: "" };
13+
const defaultSettings = {
14+
redmineURL: "",
15+
redmineApiKey: "",
16+
options: {
17+
autoPauseOnSwitch: true,
18+
},
19+
};
1020

1121
const useSettings = () => {
1222
const { data: settings, setData: setSettings } = useStorage<Settings>("settings", defaultSettings);
1323

1424
return {
15-
settings,
25+
settings: deepmerge<Settings>(defaultSettings, settings),
1626
setSettings: (data: Settings) => {
1727
setSettings(data);
1828
loadRedmineConfig();

src/pages/IssuesPage.tsx

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,39 @@ const IssuesPage = () => {
2727
{issuesQuery.isLoading && <LoadingSpinner />}
2828
{issuesQuery.isError && <Toast type="error" message="Failed to load issues" allowClose={false} />}
2929
{issuesQuery.data?.map((issue) => {
30-
const issueData = issue.id in issues ? issues[issue.id] : undefined;
30+
const issueData =
31+
issue.id in issues
32+
? issues[issue.id]
33+
: {
34+
active: false,
35+
start: undefined,
36+
time: 0,
37+
};
3138
return (
3239
<Issue
3340
issue={issue}
34-
isActive={issueData?.active || false}
35-
time={issueData?.time || 0}
36-
start={issueData?.start}
41+
isActive={issueData.active}
42+
time={issueData.time}
43+
start={issueData.start}
3744
onStart={() => {
3845
setIssues({
39-
...issues,
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),
4059
[issue.id]: {
4160
active: true,
4261
start: new Date().getTime(),
43-
time: issueData?.time || 0,
62+
time: issueData.time,
4463
},
4564
});
4665
}}
@@ -78,4 +97,8 @@ const IssuesPage = () => {
7897
);
7998
};
8099

100+
const calcTime = (time: number, start?: number) => {
101+
return time + (start ? new Date().getTime() - start : 0);
102+
};
103+
81104
export default IssuesPage;

src/pages/SettingsPage.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Field, Form, Formik, FormikProps } from "formik";
22
import { useEffect, useRef, useState } from "react";
33
import * as Yup from "yup";
4+
import CheckBox from "../components/general/CheckBox";
45
import InputField from "../components/general/InputField";
56
import Toast from "../components/general/Toast";
67
import useSettings, { Settings } from "../hooks/useSettings";
@@ -40,12 +41,14 @@ const SettingsPage = () => {
4041
<div className="flex flex-col gap-y-2">
4142
<Field type="text" name="redmineURL" title="Redmine URL" placeholder="Redmine URL" required as={InputField} error={touched.redmineURL && errors.redmineURL} />
4243
<Field type="password" name="redmineApiKey" title="Redmine API-Key" placeholder="Redmine API-Key" required as={InputField} error={touched.redmineApiKey && errors.redmineApiKey} />
44+
<h2 className="text-lg font-semibold">Options:</h2>
45+
<Field type="checkbox" name="options.autoPauseOnSwitch" title="Auto pause" description="Automatic pause timers when changing issue" as={CheckBox} />
4346
<button
4447
type="button"
4548
className="text-white bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 mt-2 dark:bg-primary-600 dark:hover:bg-primary-700 focus:outline-none dark:focus:ring-primary-800"
4649
onClick={submitForm}
4750
>
48-
Save
51+
Save Settings
4952
</button>
5053
</div>
5154
</Form>

0 commit comments

Comments
 (0)