Skip to content

Commit a9b90fd

Browse files
committed
Add task detail links to tasks page
1. extract base component to re-use for Analysis Details and Task Details 2. add 2 new routes: a) /tasks/:taskId b) /tasks/:taskId/attachments/:attachmentId 3. user can enter details screen by: a) task status name (link) b) "Task details" action (per row kebab actions" Signed-off-by: Radoslaw Szwajkowski <[email protected]>
1 parent 172710a commit a9b90fd

File tree

8 files changed

+321
-110
lines changed

8 files changed

+321
-110
lines changed

client/public/locales/en/translation.json

+2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"selectNone": "Select none",
5656
"selectPage": "Select page",
5757
"submitReview": "Submit review",
58+
"taskDetails": "Task details",
5859
"unlink": "Unlink",
5960
"view": "View",
6061
"viewErrorReport": "View error report",
@@ -463,6 +464,7 @@
463464
"tagCategory": "Tag category",
464465
"tagCategoryDeleted": "Tag category deleted",
465466
"tagCategories": "Tag categories",
467+
"tasks": "Tasks",
466468
"teamMember": "team member",
467469
"terminated": "Terminated",
468470
"ticket": "Ticket",

client/src/app/Paths.ts

+7
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ export const DevPaths = {
4141

4242
dependencies: "/dependencies",
4343
tasks: "/tasks",
44+
taskDetails: "/tasks/:taskId",
45+
taskDetailsAttachment: "/tasks/:taskId/attachments/:attachmentId",
4446
} as const;
4547

4648
export type DevPathValues = (typeof DevPaths)[keyof typeof DevPaths];
@@ -102,3 +104,8 @@ export interface AnalysisDetailsAttachmentRoute {
102104
taskId: string;
103105
attachmentId: string;
104106
}
107+
108+
export interface TaskDetailsAttachmentRoute {
109+
taskId: string;
110+
attachmentId: string;
111+
}

client/src/app/Routes.tsx

+11
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const AssessmentSummary = lazy(
6565
);
6666

6767
const TaskManager = lazy(() => import("./pages/tasks/tasks-page"));
68+
const TaskDetails = lazy(() => import("./pages/tasks/TaskDetails"));
6869

6970
export interface IRoute<T> {
7071
path: T;
@@ -195,6 +196,16 @@ export const devRoutes: IRoute<DevPathValues>[] = [
195196
{
196197
path: Paths.tasks,
197198
comp: TaskManager,
199+
exact: true,
200+
},
201+
{
202+
path: Paths.taskDetails,
203+
comp: TaskDetails,
204+
exact: true,
205+
},
206+
{
207+
path: Paths.taskDetailsAttachment,
208+
comp: TaskDetails,
198209
exact: false,
199210
},
200211
];
Original file line numberDiff line numberDiff line change
@@ -1,116 +1,56 @@
11
import React from "react";
2-
import { useHistory, useLocation, useParams } from "react-router-dom";
2+
import { useParams } from "react-router-dom";
33
import { useTranslation } from "react-i18next";
44

5-
import { PageSection } from "@patternfly/react-core";
6-
75
import { AnalysisDetailsAttachmentRoute, Paths } from "@app/Paths";
8-
import { PageHeader } from "@app/components/PageHeader";
96
import { formatPath } from "@app/utils/utils";
10-
import {
11-
DocumentId,
12-
SimpleDocumentViewer,
13-
} from "@app/components/simple-document-viewer";
14-
import { useFetchApplicationById } from "@app/queries/applications";
15-
import { useFetchTaskByID } from "@app/queries/tasks";
7+
168
import "@app/components/simple-document-viewer/SimpleDocumentViewer.css";
9+
import { TaskDetailsBase } from "@app/pages/tasks/TaskDetails";
10+
import { useFetchApplicationById } from "@app/queries/applications";
1711

18-
export const AnalysisDetails: React.FC = () => {
12+
export const AnalysisDetails = () => {
1913
const { t } = useTranslation();
20-
2114
const { applicationId, taskId, attachmentId } =
2215
useParams<AnalysisDetailsAttachmentRoute>();
23-
const { search } = useLocation();
24-
const hasMergedParam = new URLSearchParams(search).has("merged");
25-
26-
const history = useHistory();
27-
const onDocumentChange = (documentId: DocumentId) =>
28-
typeof documentId === "number"
29-
? history.push(
30-
formatPath(Paths.applicationsAnalysisDetailsAttachment, {
31-
applicationId: applicationId,
32-
taskId: taskId,
33-
attachmentId: documentId,
34-
})
35-
)
36-
: history.push({
37-
pathname: formatPath(Paths.applicationsAnalysisDetails, {
38-
applicationId: applicationId,
39-
taskId: taskId,
40-
}),
41-
search: documentId === "MERGED_VIEW" ? "?merged=true" : undefined,
42-
});
16+
const detailsPath = formatPath(Paths.applicationsAnalysisDetails, {
17+
applicationId: applicationId,
18+
taskId: taskId,
19+
});
4320

4421
const { application } = useFetchApplicationById(applicationId);
45-
const { task } = useFetchTaskByID(Number(taskId));
46-
47-
const taskName = task?.name ?? t("terms.unknown");
4822
const appName: string = application?.name ?? t("terms.unknown");
49-
const attachmentName = task?.attached?.find(
50-
({ id }) => String(id) === attachmentId
51-
)?.name;
52-
const resolvedAttachmentId = attachmentName
53-
? Number(attachmentId)
54-
: undefined;
55-
const resolvedLogMode = hasMergedParam ? "MERGED_VIEW" : "LOG_VIEW";
5623

5724
return (
58-
<>
59-
<PageSection variant="light">
60-
<PageHeader
61-
title={`Analysis details for ${taskName}`}
62-
breadcrumbs={[
63-
{
64-
title: t("terms.applications"),
65-
path: Paths.applications,
66-
},
67-
{
68-
title: appName,
69-
path: `${Paths.applications}/?activeItem=${applicationId}`,
70-
},
71-
{
72-
title: t("actions.analysisDetails"),
73-
path: formatPath(Paths.applicationsAnalysisDetails, {
74-
applicationId: applicationId,
75-
taskId: taskId,
76-
}),
77-
},
78-
...(attachmentName
79-
? [
80-
{
81-
title: t("terms.attachments"),
82-
},
83-
{
84-
title: attachmentName,
85-
path: formatPath(Paths.applicationsAnalysisDetails, {
86-
applicationId: applicationId,
87-
taskId: taskId,
88-
attachment: attachmentId,
89-
}),
90-
},
91-
]
92-
: []),
93-
]}
94-
/>
95-
</PageSection>
96-
<PageSection>
97-
<div
98-
style={{
99-
backgroundColor: "var(--pf-v5-global--BackgroundColor--100)",
100-
}}
101-
className="simple-task-viewer-container"
102-
>
103-
<SimpleDocumentViewer
104-
// force re-creating viewer via keys
105-
key={`${task?.id}/${task?.attached?.length}`}
106-
taskId={task ? Number(taskId) : undefined}
107-
documentId={resolvedAttachmentId || resolvedLogMode}
108-
attachments={task?.attached ?? []}
109-
onDocumentChange={onDocumentChange}
110-
height="full"
111-
/>
112-
</div>
113-
</PageSection>
114-
</>
25+
<TaskDetailsBase
26+
breadcrumbs={[
27+
{
28+
title: t("terms.applications"),
29+
path: Paths.applications,
30+
},
31+
{
32+
title: appName,
33+
path: `${Paths.applications}/?activeItem=${applicationId}`,
34+
},
35+
{
36+
title: t("actions.analysisDetails"),
37+
path: formatPath(Paths.applicationsAnalysisDetails, {
38+
applicationId,
39+
taskId,
40+
}),
41+
},
42+
]}
43+
detailsPath={detailsPath}
44+
formatTitle={(taskName) => `Analysis details for ${taskName}`}
45+
formatAttachmentPath={(attachmentId) =>
46+
formatPath(Paths.applicationsAnalysisDetailsAttachment, {
47+
applicationId,
48+
taskId,
49+
attachmentId,
50+
})
51+
}
52+
taskId={Number(taskId)}
53+
attachmentId={attachmentId}
54+
/>
11555
);
11656
};
+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import React from "react";
2+
import { useHistory, useLocation, useParams } from "react-router-dom";
3+
import { useTranslation } from "react-i18next";
4+
5+
import { PageSection } from "@patternfly/react-core";
6+
7+
import { Paths, TaskDetailsAttachmentRoute } from "@app/Paths";
8+
import { PageHeader, PageHeaderProps } from "@app/components/PageHeader";
9+
import {
10+
DocumentId,
11+
SimpleDocumentViewer,
12+
} from "@app/components/simple-document-viewer";
13+
import { useFetchTaskByID } from "@app/queries/tasks";
14+
import "@app/components/simple-document-viewer/SimpleDocumentViewer.css";
15+
import { formatPath } from "@app/utils/utils";
16+
17+
export const TaskDetailsBase: React.FC<{
18+
breadcrumbs: PageHeaderProps["breadcrumbs"];
19+
formatTitle: (taskName: string) => string;
20+
detailsPath: string;
21+
formatAttachmentPath: (attachmentId: number | string) => string;
22+
taskId: number;
23+
attachmentId?: string;
24+
}> = ({
25+
breadcrumbs,
26+
formatTitle,
27+
detailsPath,
28+
formatAttachmentPath,
29+
taskId,
30+
attachmentId,
31+
}) => {
32+
const { t } = useTranslation();
33+
34+
const { search } = useLocation();
35+
const hasMergedParam = new URLSearchParams(search).has("merged");
36+
37+
const history = useHistory();
38+
const onDocumentChange = (documentId: DocumentId) =>
39+
typeof documentId === "number"
40+
? history.push(formatAttachmentPath(documentId))
41+
: history.push({
42+
pathname: detailsPath,
43+
search: documentId === "MERGED_VIEW" ? "?merged=true" : undefined,
44+
});
45+
46+
const { task } = useFetchTaskByID(taskId);
47+
48+
const taskName: string = task?.name ?? t("terms.unknown");
49+
const attachmentName = task?.attached?.find(
50+
({ id }) => String(id) === attachmentId
51+
)?.name;
52+
const resolvedAttachmentId = attachmentName
53+
? Number(attachmentId)
54+
: undefined;
55+
const resolvedLogMode = hasMergedParam ? "MERGED_VIEW" : "LOG_VIEW";
56+
57+
return (
58+
<>
59+
<PageSection variant="light">
60+
<PageHeader
61+
title={formatTitle(taskName)}
62+
breadcrumbs={[
63+
...breadcrumbs,
64+
...(attachmentName && attachmentId
65+
? [
66+
{
67+
title: t("terms.attachments"),
68+
},
69+
{
70+
title: attachmentName,
71+
path: formatAttachmentPath(attachmentId),
72+
},
73+
]
74+
: []),
75+
]}
76+
/>
77+
</PageSection>
78+
<PageSection>
79+
<div
80+
style={{
81+
backgroundColor: "var(--pf-v5-global--BackgroundColor--100)",
82+
}}
83+
className="simple-task-viewer-container"
84+
>
85+
<SimpleDocumentViewer
86+
// force re-creating viewer via keys
87+
key={`${task?.id}/${task?.attached?.length}`}
88+
taskId={task ? taskId : undefined}
89+
documentId={resolvedAttachmentId || resolvedLogMode}
90+
attachments={task?.attached ?? []}
91+
onDocumentChange={onDocumentChange}
92+
height="full"
93+
/>
94+
</div>
95+
</PageSection>
96+
</>
97+
);
98+
};
99+
100+
export const TaskDetails = () => {
101+
const { t } = useTranslation();
102+
const { taskId, attachmentId } = useParams<TaskDetailsAttachmentRoute>();
103+
const detailsPath = formatPath(Paths.taskDetails, { taskId });
104+
return (
105+
<TaskDetailsBase
106+
breadcrumbs={[
107+
{
108+
title: t("terms.tasks"),
109+
path: Paths.tasks,
110+
},
111+
{
112+
title: t("actions.taskDetails"),
113+
path: detailsPath,
114+
},
115+
]}
116+
detailsPath={detailsPath}
117+
formatTitle={(taskName) => `Details for ${taskName}`}
118+
formatAttachmentPath={(attachmentId) =>
119+
formatPath(Paths.taskDetailsAttachment, {
120+
taskId,
121+
attachmentId,
122+
})
123+
}
124+
taskId={Number(taskId)}
125+
attachmentId={attachmentId}
126+
/>
127+
);
128+
};
129+
130+
export default TaskDetails;

0 commit comments

Comments
 (0)