Skip to content

Commit 52f245e

Browse files
committed
feat: Added popout window
- Added popout window - Fixed favicon icon - Improved caching - Added auto refetch data - Fixed bug in TimeEntryList
1 parent 903082d commit 52f245e

File tree

12 files changed

+81
-34
lines changed

12 files changed

+81
-34
lines changed

index.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
<!DOCTYPE html>
1+
<!doctype html>
22
<html lang="en">
33
<head>
44
<meta charset="UTF-8" />
5-
<link rel="icon" type="image/svg+xml" href="/logo32.png" />
5+
<link rel="icon" type="image/svg+xml" href="/logo16.png" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
77
<title>Redmine Time Tracking</title>
88
</head>
9-
<body class="w-[320px] h-[500px] mx-auto bg-white dark:bg-gray-800 dark:text-white">
9+
<body class="overflow-x-hidden bg-white dark:bg-gray-800 dark:text-white">
1010
<div id="root"></div>
1111
<script type="module" src="/src/main.tsx"></script>
1212
</body>

public/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"homepage_url": "https://github.com/CrawlerCode/redmine-time-tracking",
1313
"action": {
1414
"default_title": "Redmine Time Tracking",
15-
"default_popup": "index.html"
15+
"default_popup": "index.html?location=popup"
1616
},
1717
"options_ui": {
1818
"page": "index.html#/settings",

src/App.tsx

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import { faGear, faList, faStopwatch } from "@fortawesome/free-solid-svg-icons";
1+
import { faGear, faList, faStopwatch, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
22
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3+
import clsx from "clsx";
34
import { Suspense, lazy } from "react";
45
import { useIntl } from "react-intl";
56
import { Navigate, Route, Routes } from "react-router-dom";
67
import Navbar from "./components/general/Navbar";
78
import Toast from "./components/general/Toast";
9+
import { createPopOut, getWindowLocationType } from "./utils/popout";
810

911
const IssuesPage = lazy(() => import("./pages/IssuesPage"));
1012
const SettingsPage = lazy(() => import("./pages/SettingsPage"));
@@ -13,33 +15,45 @@ const TimePage = lazy(() => import("./pages/TimePage"));
1315
function App() {
1416
const { formatMessage } = useIntl();
1517

18+
const locationType = getWindowLocationType();
19+
1620
return (
1721
<div
22+
className={clsx("mx-auto w-[320px]", {
23+
"w-full min-w-[320px]": locationType === "popout",
24+
})}
1825
// disable context menu
1926
onContextMenu={(e) => {
2027
e.preventDefault();
2128
}}
2229
>
23-
<Navbar
24-
navigation={[
25-
{
26-
href: "/issues",
27-
icon: <FontAwesomeIcon icon={faList} />,
28-
name: formatMessage({ id: "nav.tabs.issues" }),
29-
},
30-
{
31-
href: "/time",
32-
icon: <FontAwesomeIcon icon={faStopwatch} />,
33-
name: formatMessage({ id: "nav.tabs.time" }),
34-
},
35-
{
36-
href: "/settings",
37-
icon: <FontAwesomeIcon icon={faGear} />,
38-
name: formatMessage({ id: "nav.tabs.settings" }),
39-
},
40-
]}
41-
/>
42-
<main className="h-[500px] overflow-y-auto p-2">
30+
<div className="relative">
31+
<Navbar
32+
navigation={[
33+
{
34+
href: "/issues",
35+
icon: <FontAwesomeIcon icon={faList} />,
36+
name: formatMessage({ id: "nav.tabs.issues" }),
37+
},
38+
{
39+
href: "/time",
40+
icon: <FontAwesomeIcon icon={faStopwatch} />,
41+
name: formatMessage({ id: "nav.tabs.time" }),
42+
},
43+
{
44+
href: "/settings",
45+
icon: <FontAwesomeIcon icon={faGear} />,
46+
name: formatMessage({ id: "nav.tabs.settings" }),
47+
},
48+
]}
49+
/>
50+
{locationType === "popup" && <FontAwesomeIcon icon={faUpRightFromSquare} size="lg" className="absolute right-4 top-1/2 z-20 -translate-y-1/2 cursor-pointer" onClick={createPopOut} />}
51+
</div>
52+
<main
53+
className={clsx("h-[500px] overflow-y-scroll p-2", {
54+
"h-full": locationType === "popout",
55+
})}
56+
>
4357
<Routes>
4458
<Route index element={<Navigate to="/issues" replace />} />
4559

src/components/general/Navbar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const Navbar = ({ navigation }: PropTypes) => {
2727
location.pathname === item.href
2828
? "border-b-2 border-primary-600 text-primary-600 dark:border-primary-500 dark:text-primary-500"
2929
: "border-b-2 border-transparent hover:text-gray-600 dark:hover:text-gray-300",
30-
"select-none focus:outline-none focus:ring-2 focus:ring-primary-300 dark:focus:ring-primary-600"
30+
"select-none focus:outline-none"
3131
)}
3232
tabIndex={-1}
3333
>

src/components/issues/CreateTimeEntryModal.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,18 @@ const CreateTimeEntryModal = ({ issue, time, onClose, onSuccess }: PropTypes) =>
4242
const createTimeEntryMutation = useMutation({
4343
mutationFn: (entry: TCreateTimeEntry) => createTimeEntry(entry),
4444
onSuccess: () => {
45-
queryClient.invalidateQueries(["issues"]);
46-
queryClient.invalidateQueries(["additionalIssues"]);
45+
queryClient.invalidateQueries(["timeEntries"]);
4746
onSuccess();
4847
},
4948
});
5049

5150
const [doneRatio, setDoneRatio] = useState(issue.done_ratio);
5251
const updateIssueMutation = useMutation({
5352
mutationFn: (data: { done_ratio: number }) => updateIssue(issue.id, data),
53+
onSuccess: () => {
54+
queryClient.invalidateQueries(["issues"]);
55+
queryClient.invalidateQueries(["additionalIssues"]);
56+
},
5457
});
5558

5659
return (

src/components/time/TimeEntryList.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ const TimeEntryList = ({ entries }: PropTypes) => {
7575
<h4 className="col-span-1 text-sm">{format(date, "EEE")}</h4>
7676
<h3 className="col-span-2 truncate text-end text-sm font-semibold">{hours} h</h3>
7777
<div className="col-span-7">
78-
<TimeEntry entries={groupEntries} maxHours={maxHours} />
78+
<TimeEntry entries={groupEntries} maxHours={maxHours > 0 ? maxHours : undefined} />
7979
</div>
8080
</div>
8181
);

src/hooks/useIssuePriorities.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const useIssuePriorities = () => {
99
queryKey: ["issuePriorities"],
1010
queryFn: getIssuePriorities,
1111
refetchOnWindowFocus: false,
12-
staleTime: 1000 * 60 * 5,
12+
staleTime: 1000 * 60 * 60,
1313
});
1414

1515
const priorities = issuePrioritiesQuery.data?.filter((priority) => priority.active) ?? [];

src/hooks/useMyAccount.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const useMyAccount = () => {
88
const myAccountQuery = useQuery({
99
queryKey: ["myAccount", settings.redmineURL, settings.redmineApiKey],
1010
queryFn: getMyAccount,
11-
staleTime: 1000 * 60 * 5,
11+
staleTime: 1000 * 60 * 60,
1212
});
1313

1414
return {

src/hooks/useMyIssues.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import useDebounce from "./useDebounce";
66
import useSettings from "./useSettings";
77

88
const MAX_EXTENDED_SEARCH_LIMIT = 75;
9+
const AUTO_REFRESH_DATA_INTERVAL = 1000 * 60 * 5;
10+
const STALE_DATA_TIME = 1000 * 60;
911

1012
const useMyIssues = (additionalIssuesIds: number[], search: SearchQuery) => {
1113
const { settings } = useSettings();
@@ -14,13 +16,17 @@ const useMyIssues = (additionalIssuesIds: number[], search: SearchQuery) => {
1416
queryKey: ["issues"],
1517
queryFn: ({ pageParam = 0 }) => getAllMyOpenIssues(pageParam * 100, 100),
1618
getNextPageParam: (lastPage, allPages) => (lastPage.length === 100 ? allPages.length : undefined),
19+
staleTime: STALE_DATA_TIME,
20+
refetchInterval: AUTO_REFRESH_DATA_INTERVAL,
1721
});
1822
const additionalIssuesQuery = useInfiniteQuery({
1923
queryKey: ["additionalIssues", additionalIssuesIds],
2024
queryFn: ({ pageParam = 0 }) => getOpenIssuesByIds(additionalIssuesIds, pageParam * 100, 100),
2125
getNextPageParam: (lastPage, allPages) => (lastPage.length === 100 ? allPages.length : undefined),
2226
enabled: additionalIssuesIds.length > 0,
2327
keepPreviousData: additionalIssuesIds.length > 0,
28+
staleTime: STALE_DATA_TIME,
29+
refetchInterval: AUTO_REFRESH_DATA_INTERVAL,
2430
});
2531

2632
// auto fetch all pages
@@ -53,17 +59,23 @@ const useMyIssues = (additionalIssuesIds: number[], search: SearchQuery) => {
5359
const extendedSearchIssuesResultQuery = useQuery({
5460
queryKey: ["extendedSearchIssuesResult", debouncedSearch],
5561
queryFn: () => searchOpenIssues(debouncedSearch),
56-
enabled: extendedSearching && search.mode === "issue",
62+
enabled: extendedSearching && search.mode === "issue" && !debouncedSearch.includes("#"),
5763
keepPreviousData: true,
5864
});
5965
const extendedSearchIssuesResultIds = (extendedSearchIssuesResultQuery.data?.map((result) => result.id) ?? []).filter((id) => !issues.find((issue) => issue.id === id));
66+
const extendedSearchIssueIdMatch = debouncedSearch.match(/^#(\d+)$/); // search for #<issueId>
67+
if (extendedSearchIssueIdMatch) {
68+
const issueId = Number(extendedSearchIssueIdMatch[1]);
69+
if (!issues.find((issue) => issue.id === issueId)) extendedSearchIssuesResultIds.push(issueId);
70+
}
6071
const extendedSearchIssuesQuery = useQuery({
6172
queryKey: ["extendedSearchIssues", extendedSearchIssuesResultIds],
6273
queryFn: () => getOpenIssuesByIds(extendedSearchIssuesResultIds, 0, search.inProject ? 100 : MAX_EXTENDED_SEARCH_LIMIT),
6374
enabled: extendedSearchIssuesResultIds.length > 0,
6475
keepPreviousData: extendedSearchIssuesResultIds.length > 0,
6576
});
66-
const extendedSearchIssuesList = (search.inProject ? extendedSearchIssuesQuery.data?.filter((issue) => issue.project.id === search.inProject?.id) : extendedSearchIssuesQuery.data)?.slice(0, MAX_EXTENDED_SEARCH_LIMIT) ?? [];
77+
const extendedSearchIssuesList =
78+
(search.inProject ? extendedSearchIssuesQuery.data?.filter((issue) => issue.project.id === search.inProject?.id) : extendedSearchIssuesQuery.data)?.slice(0, MAX_EXTENDED_SEARCH_LIMIT) ?? [];
6779

6880
// extended search - mode: project
6981
const extendedSearchProjectsResultQuery = useQuery({

src/hooks/useMyTimeEntries.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@ import { useInfiniteQuery } from "@tanstack/react-query";
22
import { useEffect } from "react";
33
import { getAllMyTimeEntries } from "../api/redmine";
44

5+
const AUTO_REFRESH_DATA_INTERVAL = 1000 * 60 * 5;
6+
const STALE_DATA_TIME = 1000 * 60;
7+
58
const useMyTimeEntries = (from: Date, to: Date) => {
69
const entriesQuery = useInfiniteQuery({
710
queryKey: ["timeEntries", from, to],
811
queryFn: ({ pageParam = 0 }) => getAllMyTimeEntries(from, to, pageParam * 100, 100),
912
getNextPageParam: (lastPage, allPages) => (lastPage.length === 100 ? allPages.length : undefined),
13+
staleTime: STALE_DATA_TIME,
14+
refetchInterval: AUTO_REFRESH_DATA_INTERVAL,
1015
});
1116

1217
// auto fetch all pages

0 commit comments

Comments
 (0)