ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: стабилизация контуров, досок и Voice Tasker
This commit is contained in:
@@ -10,14 +10,9 @@ import { CalendarDays } from "lucide-react";
|
||||
import { observer } from "mobx-react";
|
||||
import { EUserPermissions } from "@plane/constants";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
import { PriorityIcon, StateGroupIcon } from "@plane/propel/icons";
|
||||
import { PriorityIcon, StateGroupIcon, getStateGroupColor } from "@plane/propel/icons";
|
||||
import { TOAST_TYPE, setToast } from "@plane/propel/toast";
|
||||
import type {
|
||||
IState,
|
||||
TExternalContourBoardDirection,
|
||||
TExternalContourRequest,
|
||||
TIssue,
|
||||
} from "@plane/types";
|
||||
import type { IState, TExternalContourBoardDirection, TExternalContourRequest, TIssue } from "@plane/types";
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { cn, renderFormattedDate, renderFormattedPayloadDate } from "@plane/utils";
|
||||
import { DateDropdown } from "@/components/dropdowns/date";
|
||||
@@ -56,7 +51,7 @@ const buildSourceStateMap = (
|
||||
state.id,
|
||||
{
|
||||
id: state.id,
|
||||
color: state.color,
|
||||
color: getStateGroupColor(state.group, state.color),
|
||||
default: false,
|
||||
description: "",
|
||||
group: state.group,
|
||||
@@ -69,7 +64,10 @@ const buildSourceStateMap = (
|
||||
])
|
||||
);
|
||||
|
||||
const resolveRequestStatus = (issue: TExternalContourRequest["issue"], fallbackStatus: TExternalContourRequest["status"]) => {
|
||||
const resolveRequestStatus = (
|
||||
issue: TExternalContourRequest["issue"],
|
||||
fallbackStatus: TExternalContourRequest["status"]
|
||||
) => {
|
||||
const stateGroup = issue.state_detail?.group;
|
||||
if (!stateGroup) return fallbackStatus;
|
||||
return stateGroup === "completed" || stateGroup === "cancelled" ? "closed" : "open";
|
||||
@@ -83,23 +81,20 @@ export const ExternalContoursBoardItem = observer(function ExternalContoursBoard
|
||||
const { getUserDetails, workspace } = useMember();
|
||||
const { getProjectRoleByWorkspaceSlugAndProjectId } = useUserPermissions();
|
||||
const { getStateById, getProjectStateIds } = useProjectState();
|
||||
const {
|
||||
fetchBoard,
|
||||
upsertBoardItems,
|
||||
} = useProjectExternalContoursBoard();
|
||||
const {
|
||||
fetchTargetOptions,
|
||||
getTargetOptionsByProjectId,
|
||||
updateRequest,
|
||||
updateRequestIssue,
|
||||
} = useProjectExternalContours();
|
||||
const { fetchBoard, upsertBoardItems } = useProjectExternalContoursBoard();
|
||||
const { fetchTargetOptions, getTargetOptionsByProjectId, updateRequest, updateRequestIssue } =
|
||||
useProjectExternalContours();
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
const [isSourceOptionsLoading, setIsSourceOptionsLoading] = useState(false);
|
||||
|
||||
const issue = request.issue;
|
||||
const selectedInboxIssueId = searchParams.get("inboxIssueId");
|
||||
const isActive = selectedInboxIssueId === request.id;
|
||||
const requester = request.requested_by?.display_name || request.requested_by_name || issue.created_by_detail?.display_name || "NODE.DC";
|
||||
const requester =
|
||||
request.requested_by?.display_name ||
|
||||
request.requested_by_name ||
|
||||
issue.created_by_detail?.display_name ||
|
||||
"NODE.DC";
|
||||
const requesterAvatar = issue.created_by_detail?.avatar_url || "";
|
||||
const counterpartContourName =
|
||||
direction === "outgoing"
|
||||
@@ -110,8 +105,12 @@ export const ExternalContoursBoardItem = observer(function ExternalContoursBoard
|
||||
? getProjectRoleByWorkspaceSlugAndProjectId(workspaceSlug, targetProjectId)
|
||||
: undefined;
|
||||
const canEditTargetIssue =
|
||||
direction === "incoming" && !!targetProjectId && projectRole !== undefined && projectRole !== EUserPermissions.GUEST;
|
||||
const canEditSourceRequest = direction === "outgoing" && !!request.capabilities?.can_edit_request && !!targetProjectId;
|
||||
direction === "incoming" &&
|
||||
!!targetProjectId &&
|
||||
projectRole !== undefined &&
|
||||
projectRole !== EUserPermissions.GUEST;
|
||||
const canEditSourceRequest =
|
||||
direction === "outgoing" && !!request.capabilities?.can_edit_request && !!targetProjectId;
|
||||
const canEditCard = canEditTargetIssue || canEditSourceRequest;
|
||||
const requestLink = `/${workspaceSlug}/projects/${projectId}/external-contours?inboxIssueId=${request.id}`;
|
||||
const targetOptions = getTargetOptionsByProjectId(targetProjectId);
|
||||
@@ -124,12 +123,11 @@ export const ExternalContoursBoardItem = observer(function ExternalContoursBoard
|
||||
const projectStateIds = issue.project_id ? getProjectStateIds(issue.project_id) : [];
|
||||
const foregroundClasses = isActive ? "text-[rgb(var(--nodedc-on-card-active-rgb))]" : "text-white";
|
||||
const subtleTextClasses = isActive ? "text-[#2F4721]" : "text-[#B3B3B8]";
|
||||
const pillBackgroundClasses =
|
||||
isActive
|
||||
? "bg-black/10 text-[rgb(var(--nodedc-on-card-active-rgb))]"
|
||||
: "bg-[rgb(var(--nodedc-card-passive-rgb))] text-white";
|
||||
const pillBackgroundClasses = isActive
|
||||
? "bg-black/10 text-[rgb(var(--nodedc-on-card-active-rgb))]"
|
||||
: "bg-[rgb(var(--nodedc-card-passive-rgb))] text-white";
|
||||
const iconBubbleClasses = isActive ? "bg-black text-[rgb(var(--nodedc-card-active-rgb))]" : "bg-[#111214] text-white";
|
||||
const statusIconColor = selectedState?.color ?? (isActive ? "rgb(var(--nodedc-on-card-active-rgb))" : "var(--text-color-primary)");
|
||||
const statusIconColor = getStateGroupColor(selectedState?.group, selectedState?.color);
|
||||
const dueDateLabel = issue.target_date ? renderFormattedDate(issue.target_date, "d MMM, yyyy") : t("common.none");
|
||||
|
||||
if (!issue) return null;
|
||||
@@ -314,13 +312,13 @@ export const ExternalContoursBoardItem = observer(function ExternalContoursBoard
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cn("truncate -mt-0.5 pl-8 text-[11px] font-medium leading-4", subtleTextClasses)}>
|
||||
<div className={cn("-mt-0.5 truncate pl-8 text-[11px] leading-4 font-medium", subtleTextClasses)}>
|
||||
{counterpartContourName || t("common.none")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-1 items-center justify-center px-5 py-4 text-center">
|
||||
<div className="line-clamp-4 max-w-full text-lg font-semibold leading-6">{issue.name}</div>
|
||||
<div className="text-lg line-clamp-4 max-w-full leading-6 font-semibold">{issue.name}</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-between gap-3" onClick={stopCardPropagation}>
|
||||
@@ -333,7 +331,7 @@ export const ExternalContoursBoardItem = observer(function ExternalContoursBoard
|
||||
disabled={!canEditCard || isUpdating}
|
||||
buttonVariant="transparent-without-text"
|
||||
button={
|
||||
<div className={cn(basePillClasses, pillBackgroundClasses, "pl-1 pr-2")}>
|
||||
<div className={cn(basePillClasses, pillBackgroundClasses, "pr-2 pl-1")}>
|
||||
<ButtonAvatars showTooltip={false} userIds={issue.assignee_ids ?? []} size="sm" />
|
||||
</div>
|
||||
}
|
||||
@@ -351,7 +349,7 @@ export const ExternalContoursBoardItem = observer(function ExternalContoursBoard
|
||||
}}
|
||||
buttonVariant="transparent-without-text"
|
||||
button={
|
||||
<div className={cn(basePillClasses, pillBackgroundClasses, "pl-1 pr-2")}>
|
||||
<div className={cn(basePillClasses, pillBackgroundClasses, "pr-2 pl-1")}>
|
||||
<ButtonAvatars showTooltip={false} userIds={issue.assignee_ids ?? []} size="sm" />
|
||||
</div>
|
||||
}
|
||||
|
||||
+37
-51
@@ -17,6 +17,7 @@ import {
|
||||
StateGroupIcon,
|
||||
StatePropertyIcon,
|
||||
UserCirclePropertyIcon,
|
||||
getStateGroupColor,
|
||||
} from "@plane/propel/icons";
|
||||
import type {
|
||||
IProject,
|
||||
@@ -57,14 +58,11 @@ const buildCounterpartyProjects = (requests: TExternalContourRequest[], projectI
|
||||
|
||||
if (!project?.id || project.id === projectId || projectMap.has(project.id)) return;
|
||||
|
||||
projectMap.set(
|
||||
project.id,
|
||||
{
|
||||
id: project.id,
|
||||
name: project.name,
|
||||
logo_props: project.logo_props,
|
||||
} as IProject
|
||||
);
|
||||
projectMap.set(project.id, {
|
||||
id: project.id,
|
||||
name: project.name,
|
||||
logo_props: project.logo_props,
|
||||
} as IProject);
|
||||
});
|
||||
|
||||
return sortByName(Array.from(projectMap.values()));
|
||||
@@ -77,21 +75,18 @@ const buildStates = (requests: TExternalContourRequest[]): IState[] => {
|
||||
const state = request.issue.state_detail;
|
||||
if (!state?.id || stateMap.has(state.id)) return;
|
||||
|
||||
stateMap.set(
|
||||
state.id,
|
||||
{
|
||||
id: state.id,
|
||||
color: state.color,
|
||||
default: false,
|
||||
description: "",
|
||||
group: state.group,
|
||||
name: state.name,
|
||||
order: index + 1,
|
||||
project_id: request.issue.project_id || request.target_project?.id || request.source_project?.id || "",
|
||||
sequence: index + 1,
|
||||
workspace_id: "",
|
||||
} as IState
|
||||
);
|
||||
stateMap.set(state.id, {
|
||||
id: state.id,
|
||||
color: getStateGroupColor(state.group, state.color),
|
||||
default: false,
|
||||
description: "",
|
||||
group: state.group,
|
||||
name: state.name,
|
||||
order: index + 1,
|
||||
project_id: request.issue.project_id || request.target_project?.id || request.source_project?.id || "",
|
||||
sequence: index + 1,
|
||||
workspace_id: "",
|
||||
} as IState);
|
||||
});
|
||||
|
||||
return sortByName(Array.from(stateMap.values()));
|
||||
@@ -103,18 +98,15 @@ const buildLabels = (requests: TExternalContourRequest[]): IIssueLabel[] => {
|
||||
requests.forEach((request) => {
|
||||
request.issue.label_details?.forEach((label) => {
|
||||
if (!label.id || labelMap.has(label.id)) return;
|
||||
labelMap.set(
|
||||
label.id,
|
||||
{
|
||||
id: label.id,
|
||||
color: label.color,
|
||||
name: label.name,
|
||||
parent: null,
|
||||
project_id: request.issue.project_id || "",
|
||||
sort_order: 0,
|
||||
workspace_id: "",
|
||||
} as IIssueLabel
|
||||
);
|
||||
labelMap.set(label.id, {
|
||||
id: label.id,
|
||||
color: label.color,
|
||||
name: label.name,
|
||||
parent: null,
|
||||
project_id: request.issue.project_id || "",
|
||||
sort_order: 0,
|
||||
workspace_id: "",
|
||||
} as IIssueLabel);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -127,14 +119,11 @@ const buildAssignees = (requests: TExternalContourRequest[]): IUserLite[] => {
|
||||
requests.forEach((request) => {
|
||||
request.issue.assignee_details?.forEach((assignee) => {
|
||||
if (!assignee.id || memberMap.has(assignee.id)) return;
|
||||
memberMap.set(
|
||||
assignee.id,
|
||||
{
|
||||
id: assignee.id,
|
||||
avatar_url: assignee.avatar_url,
|
||||
display_name: assignee.display_name,
|
||||
} as IUserLite
|
||||
);
|
||||
memberMap.set(assignee.id, {
|
||||
id: assignee.id,
|
||||
avatar_url: assignee.avatar_url,
|
||||
display_name: assignee.display_name,
|
||||
} as IUserLite);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -151,14 +140,11 @@ const buildRequesters = (requests: TExternalContourRequest[]): IUserLite[] => {
|
||||
|
||||
if (!requesterId || !requesterName || memberMap.has(requesterId)) return;
|
||||
|
||||
memberMap.set(
|
||||
requesterId,
|
||||
{
|
||||
id: requesterId,
|
||||
avatar_url: request.issue.created_by_detail?.avatar_url,
|
||||
display_name: requesterName,
|
||||
} as IUserLite
|
||||
);
|
||||
memberMap.set(requesterId, {
|
||||
id: requesterId,
|
||||
avatar_url: request.issue.created_by_detail?.avatar_url,
|
||||
display_name: requesterName,
|
||||
} as IUserLite);
|
||||
});
|
||||
|
||||
return sortByName(Array.from(memberMap.values()));
|
||||
|
||||
Reference in New Issue
Block a user