From 4cf4228eed2ab426b210ad671375a03daca3f5f4 Mon Sep 17 00:00:00 2001 From: Colin Dawson Date: Tue, 17 Mar 2026 16:36:52 +0000 Subject: [PATCH] The tasks page now shows the assignee --- .../tasks/components/AssigneePanel.tsx | 226 ++++++++++++++++++ .../manager/tasks/components/tasksTable.tsx | 10 +- 2 files changed, 234 insertions(+), 2 deletions(-) create mode 100644 src/modules/manager/tasks/components/AssigneePanel.tsx diff --git a/src/modules/manager/tasks/components/AssigneePanel.tsx b/src/modules/manager/tasks/components/AssigneePanel.tsx new file mode 100644 index 0000000..121859d --- /dev/null +++ b/src/modules/manager/tasks/components/AssigneePanel.tsx @@ -0,0 +1,226 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import { GeneralIdRef } from "../../../../utils/GeneralIdRef"; +import { Namespaces } from "../../../../i18n/i18n"; +import roleService from "../../domains/serrvices/rolesService"; +import userService from "../../users/services/usersService"; + +export interface AssigneePanelProps { + user?: GeneralIdRef; + role?: GeneralIdRef; +} + +const roleNameCache = new Map(); +const roleLookupInFlight = new Map>(); +const userNameCache = new Map(); +const userLookupInFlight = new Map>(); + +function getRoleCacheKey(role?: GeneralIdRef): string | null { + if (!role) { + return null; + } + + if (role.guid) { + return `guid:${role.guid}`; + } + + if (role.id !== undefined) { + return `id:${role.id.toString()}`; + } + + return null; +} + +function getUserCacheKey(user?: GeneralIdRef): string | null { + if (!user) { + return null; + } + + if (user.guid) { + return `guid:${user.guid}`; + } + + if (user.id !== undefined) { + return `id:${user.id.toString()}`; + } + + return null; +} + +async function getCachedRoleName(role: GeneralIdRef): Promise { + const key = getRoleCacheKey(role); + + if (!key) { + return null; + } + + if (roleNameCache.has(key)) { + return roleNameCache.get(key) ?? null; + } + + const inFlight = roleLookupInFlight.get(key); + if (inFlight) { + return inFlight; + } + + const request = roleService + .getRole(role.id, role.guid) + .then((roleDetails) => { + const name = roleDetails?.name ?? null; + roleNameCache.set(key, name); + return name; + }) + .catch(() => { + roleNameCache.set(key, null); + return null; + }) + .finally(() => { + roleLookupInFlight.delete(key); + }); + + roleLookupInFlight.set(key, request); + return request; +} + +async function getCachedUserName(user: GeneralIdRef): Promise { + const key = getUserCacheKey(user); + + if (!key) { + return null; + } + + if (userNameCache.has(key)) { + return userNameCache.get(key) ?? null; + } + + const inFlight = userLookupInFlight.get(key); + if (inFlight) { + return inFlight; + } + + const request = userService + .getUser(user.id, user.guid) + .then((userDetails) => { + const name = userDetails?.displayName ?? null; + userNameCache.set(key, name); + return name; + }) + .catch(() => { + userNameCache.set(key, null); + return null; + }) + .finally(() => { + userLookupInFlight.delete(key); + }); + + userLookupInFlight.set(key, request); + return request; +} + +const AssigneePanel: React.FC = ({ user, role }) => { + const { t } = useTranslation(Namespaces.Common); + const [resolvedUserName, setResolvedUserName] = React.useState( + null, + ); + const [isUserLoading, setIsUserLoading] = React.useState(false); + const [resolvedRoleName, setResolvedRoleName] = React.useState( + null, + ); + const [isRoleLoading, setIsRoleLoading] = React.useState(false); + + React.useEffect(() => { + let isActive = true; + + if (!user) { + setResolvedUserName(null); + setIsUserLoading(false); + return () => { + isActive = false; + }; + } + + setIsUserLoading(true); + + getCachedUserName(user) + .then((name) => { + if (!isActive) { + return; + } + setResolvedUserName(name); + }) + .finally(() => { + if (!isActive) { + return; + } + setIsUserLoading(false); + }); + + return () => { + isActive = false; + }; + }, [user?.id, user?.guid]); + + React.useEffect(() => { + let isActive = true; + + if (!role) { + setResolvedRoleName(null); + setIsRoleLoading(false); + return () => { + isActive = false; + }; + } + + setIsRoleLoading(true); + + getCachedRoleName(role) + .then((name) => { + if (!isActive) { + return; + } + setResolvedRoleName(name); + }) + .finally(() => { + if (!isActive) { + return; + } + setIsRoleLoading(false); + }); + + return () => { + isActive = false; + }; + }, [role?.id, role?.guid]); + + if (user) { + if (isUserLoading) { + return {t("Loading")}...; + } + + if (resolvedUserName) { + return {resolvedUserName}; + } + + const userFallback = user.guid ?? user.id?.toString(); + + return {userFallback}; + } + + if (role) { + if (isRoleLoading) { + return {t("Loading")}...; + } + + if (resolvedRoleName) { + return {resolvedRoleName}; + } + + const roleFallback = role.guid ?? role.id?.toString(); + + return {roleFallback}; + } + + return {t("Unassigned")}; +}; + +export default AssigneePanel; diff --git a/src/modules/manager/tasks/components/tasksTable.tsx b/src/modules/manager/tasks/components/tasksTable.tsx index 10e4bbf..658ae34 100644 --- a/src/modules/manager/tasks/components/tasksTable.tsx +++ b/src/modules/manager/tasks/components/tasksTable.tsx @@ -7,6 +7,7 @@ import Table, { } from "../../../../components/common/Table"; import { GetMyAssignments } from "../services/tasksService"; import TaskTypeAndNameDisplayPanel from "../../workflowTemplates/components/TaskTypeAndNameDisplayPanel"; +import AssigneePanel from "./AssigneePanel"; export interface TasksTableProps extends PublishedTableProps {} @@ -38,8 +39,13 @@ const TasksTable: React.FC = ({ }, order: "asc", }, - { key: "user", label: t("User"), order: "asc" }, - { key: "role", label: t("Role"), order: "asc" }, + { + key: "user", + label: t("Assignee"), + order: "asc", + searchable: false, + content: (item) => , + }, { key: "startDateTime", label: t("StartDateTime"), order: "asc" }, ], [t],