The tasks page now display my assignments

This commit is contained in:
Colin Dawson 2026-03-17 10:22:24 +00:00
parent 4ed3545e42
commit a456fbbdbc
5 changed files with 201 additions and 98 deletions

View File

@ -0,0 +1,65 @@
import React, { useCallback, useMemo } from "react";
import { useTranslation } from "react-i18next";
import { Namespaces } from "../../../../i18n/i18n";
import Column from "../../../../components/common/columns";
import Table, {
PublishedTableProps,
} from "../../../../components/common/Table";
import { GetMyAssignments } from "../services/tasksService";
import TaskTypeAndNameDisplayPanel from "../../workflowTemplates/components/TaskTypeAndNameDisplayPanel";
export interface TasksTableProps extends PublishedTableProps<GetMyAssignments> {}
const TasksTable: React.FC<TasksTableProps> = ({
data,
sortColumn,
onChangePage,
onSearch,
onDelete,
onSort,
}) => {
const { t } = useTranslation(Namespaces.Common);
const columns: Column<GetMyAssignments>[] = useMemo(
() => [
{
key: "taskName",
label: t("Name"),
content: (item) => {
return (
<TaskTypeAndNameDisplayPanel
taskName={item.taskName}
taskType={item.taskType}
showValidationErrorIcon={false}
allowWordWrap={false}
reserveValidationErrorIconSpace={false}
/>
);
},
order: "asc",
},
{ key: "user", label: t("User"), order: "asc" },
{ key: "role", label: t("Role"), order: "asc" },
{ key: "startDateTime", label: t("StartDateTime"), order: "asc" },
],
[t],
);
const raiseSort = (sortCol: Column<GetMyAssignments>) => {
if (onSort !== undefined) onSort(sortCol);
};
return (
<Table
data={data}
keyName="id"
columns={columns}
sortColumn={sortColumn}
onSort={raiseSort}
onChangePage={onChangePage}
onSearch={onSearch}
/>
);
};
export default TasksTable;

View File

@ -5,7 +5,16 @@ import MapToJson from "../../../../utils/MapToJson";
const apiEndpoint = "/Tasks";
export interface GetMyTasks {}
export interface GetMyAssignments {
Id: bigint;
Guid: string;
taskType: string;
taskName: string;
user?: GeneralIdRef;
role?: GeneralIdRef;
startDateTime?: Date;
}
export async function myTasks(
page: number,
@ -13,9 +22,9 @@ export async function myTasks(
sortKey: string,
sortAscending: boolean,
filters?: Map<string, string> | undefined,
): Promise<Paginated<GetMyTasks>> {
): Promise<Paginated<GetMyAssignments>> {
const filterString = MapToJson(filters);
const response = await httpService.get<Paginated<GetMyTasks>>(
const response = await httpService.get<Paginated<GetMyAssignments>>(
apiEndpoint + "/myTasks",
{
params: {

View File

@ -7,19 +7,20 @@ import Button, { ButtonType } from "../../../components/common/Button";
import { toast } from "react-toastify";
import Loading from "../../../components/common/Loading";
import Permission from "../../../components/common/Permission";
import tasksService, { GetMyTasks } from "./services/tasksService";
import tasksService, { GetMyAssignments } from "./services/tasksService";
import TasksTable from "./components/tasksTable";
const Tasks: React.FC = () => {
const { t } = useTranslation(Namespaces.Common);
const [loaded, setLoaded] = useState(false);
const [pagedData, setPagedData] = useState<Paginated<GetMyTasks>>({
const [pagedData, setPagedData] = useState<Paginated<GetMyAssignments>>({
page: 1,
pageSize: 10,
count: 0,
totalPages: 1,
data: [],
});
const [sortColumn, setSortColumn] = useState<Column<GetMyTasks>>({
const [sortColumn, setSortColumn] = useState<Column<GetMyAssignments>>({
key: "displayName",
label: t("Name"),
order: "asc",
@ -47,7 +48,7 @@ const Tasks: React.FC = () => {
[filters, sortColumn.key, sortColumn.order],
);
const onSort = async (newSortColumn: Column<GetMyTasks>) => {
const onSort = async (newSortColumn: Column<GetMyAssignments>) => {
const { page, pageSize } = pagedData;
const data = await tasksService.myTasks(
page,
@ -114,18 +115,14 @@ const Tasks: React.FC = () => {
{t("CreateActivity")}
</Button>
</Permission>
<>My Tasks go here</>
<Loading loaded={loaded}>
My Tasks go here
{/* <UsersTable
data={pagedData}
sortColumn={sortColumn}
onChangePage={changePage}
onSort={onSort}
onSearch={onSearch}
onDelete={onDelete}
resendConfirmEmail={resentConfirmEmail}
/> */}
<TasksTable
data={pagedData}
sortColumn={sortColumn}
onChangePage={changePage}
onSort={onSort}
onSearch={onSearch}
/>
</Loading>
</>
);

View File

@ -1,99 +1,30 @@
import { useEffect } from "react";
import templateVersionsService, {
TaskMetadata,
TaskDefinition,
} from "../services/WorkflowTemplateService";
import React from "react";
import FontAwesomeStringIcon from "../../../../components/common/FontAwesomeStringIcon";
import ValidationErrorIcon from "../../../../components/validationErrorIcon";
import { TaskDefinition } from "../services/WorkflowTemplateService";
import TaskTypeAndNameDisplayPanel from "./TaskTypeAndNameDisplayPanel";
export interface TaskNameDisplayPanel {
export interface TaskNameDisplayPanelProps {
task: TaskDefinition;
showValidationErrorIcon: boolean;
allowWordWrap?: boolean;
reserveValidationErrorIconSpace?: boolean;
}
const TaskNameDisplayPanel: React.FC<TaskNameDisplayPanel> = ({
const TaskNameDisplayPanel: React.FC<TaskNameDisplayPanelProps> = ({
task,
showValidationErrorIcon = false,
allowWordWrap = false,
reserveValidationErrorIconSpace = false,
}) => {
const [tasksMetadata, setTasksMetadata] = React.useState<TaskMetadata[]>([]);
const [isNameTruncated, setIsNameTruncated] = React.useState(false);
const taskNameRef = React.useRef<HTMLSpanElement | null>(null);
const taskName = task.config.name as string;
useEffect(() => {
const fetchTaskMetadata = async () => {
const meta = await templateVersionsService.getTaskMetadata("GeneralTask");
setTasksMetadata(meta);
};
fetchTaskMetadata();
}, []);
const taskMetadataByType = React.useMemo(() => {
const map = new Map<string, TaskMetadata>();
tasksMetadata.forEach((meta) => {
map.set(meta.taskType, meta);
});
return map;
}, [tasksMetadata]);
const meta = taskMetadataByType.get(task.type);
const panelClassName = allowWordWrap
? "task-name-display-panel task-name-display-panel--wrap"
: "task-name-display-panel";
const updateTruncationState = React.useCallback(() => {
const el = taskNameRef.current;
if (!el || allowWordWrap) {
setIsNameTruncated(false);
return;
}
setIsNameTruncated(el.scrollWidth > el.clientWidth);
}, [allowWordWrap]);
useEffect(() => {
updateTruncationState();
}, [taskName, allowWordWrap, updateTruncationState]);
useEffect(() => {
const el = taskNameRef.current;
if (!el || allowWordWrap || typeof ResizeObserver === "undefined") {
return;
}
const observer = new ResizeObserver(() => {
updateTruncationState();
});
observer.observe(el);
return () => {
observer.disconnect();
};
}, [allowWordWrap, updateTruncationState]);
return (
<div className={panelClassName}>
<FontAwesomeStringIcon icon={meta?.icon} />
<span
ref={taskNameRef}
className="task-name-display-panel__name"
title={isNameTruncated ? taskName : undefined}
>
{taskName}
</span>
<ValidationErrorIcon
visible={showValidationErrorIcon}
reserveSpace={reserveValidationErrorIconSpace}
/>
</div>
<TaskTypeAndNameDisplayPanel
taskName={taskName}
taskType={task.type}
showValidationErrorIcon={showValidationErrorIcon}
allowWordWrap={allowWordWrap}
reserveValidationErrorIconSpace={reserveValidationErrorIconSpace}
/>
);
};

View File

@ -0,0 +1,101 @@
import React, { useEffect } from "react";
import templateVersionsService, {
TaskMetadata,
} from "../services/WorkflowTemplateService";
import FontAwesomeStringIcon from "../../../../components/common/FontAwesomeStringIcon";
import ValidationErrorIcon from "../../../../components/validationErrorIcon";
export interface TaskTypeAndNameDisplayPanelProps {
taskName: string;
taskType: string;
showValidationErrorIcon: boolean;
allowWordWrap?: boolean;
reserveValidationErrorIconSpace?: boolean;
}
const TaskTypeAndNameDisplayPanel: React.FC<
TaskTypeAndNameDisplayPanelProps
> = ({
taskName,
taskType,
showValidationErrorIcon = false,
allowWordWrap = false,
reserveValidationErrorIconSpace = false,
}) => {
const [tasksMetadata, setTasksMetadata] = React.useState<TaskMetadata[]>([]);
const [isNameTruncated, setIsNameTruncated] = React.useState(false);
const taskNameRef = React.useRef<HTMLSpanElement | null>(null);
useEffect(() => {
const fetchTaskMetadata = async () => {
const meta = await templateVersionsService.getTaskMetadata("GeneralTask");
setTasksMetadata(meta);
};
fetchTaskMetadata();
}, []);
const taskMetadataByType = React.useMemo(() => {
const map = new Map<string, TaskMetadata>();
tasksMetadata.forEach((meta) => {
map.set(meta.taskType, meta);
});
return map;
}, [tasksMetadata]);
const meta = taskMetadataByType.get(taskType);
const panelClassName = allowWordWrap
? "task-name-display-panel task-name-display-panel--wrap"
: "task-name-display-panel";
const updateTruncationState = React.useCallback(() => {
const el = taskNameRef.current;
if (!el || allowWordWrap) {
setIsNameTruncated(false);
return;
}
setIsNameTruncated(el.scrollWidth > el.clientWidth);
}, [allowWordWrap]);
useEffect(() => {
updateTruncationState();
}, [taskName, allowWordWrap, updateTruncationState]);
useEffect(() => {
const el = taskNameRef.current;
if (!el || allowWordWrap || typeof ResizeObserver === "undefined") {
return;
}
const observer = new ResizeObserver(() => {
updateTruncationState();
});
observer.observe(el);
return () => {
observer.disconnect();
};
}, [allowWordWrap, updateTruncationState]);
return (
<div className={panelClassName}>
<FontAwesomeStringIcon icon={meta?.icon} />
<span
ref={taskNameRef}
className="task-name-display-panel__name"
title={isNameTruncated ? taskName : undefined}
>
{taskName}
</span>
<ValidationErrorIcon
visible={showValidationErrorIcon}
reserveSpace={reserveValidationErrorIconSpace}
/>
</div>
);
};
export default TaskTypeAndNameDisplayPanel;