fix: issue stats refactor (#6705)

* fix: issue stats refactor

* fix: refactor

* fix: ui color

* fix: translation key
This commit is contained in:
Akshita Goyal 2025-03-06 13:44:37 +05:30 committed by GitHub
parent f01d82ad1e
commit 44af90dc6c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 98 additions and 53 deletions

View file

@ -814,7 +814,8 @@
"sub_issue_count": "Sub-work item count", "sub_issue_count": "Sub-work item count",
"attachment_count": "Attachment count", "attachment_count": "Attachment count",
"created_on": "Created on", "created_on": "Created on",
"sub_issue": "Sub-work item" "sub_issue": "Sub-work item",
"work_item_count": "Work item count"
}, },
"extra": { "extra": {
"show_sub_issues": "Show sub-work items", "show_sub_issues": "Show sub-work items",

View file

@ -985,7 +985,8 @@
"sub_issue_count": "Cantidad de sub-elementos", "sub_issue_count": "Cantidad de sub-elementos",
"attachment_count": "Cantidad de archivos adjuntos", "attachment_count": "Cantidad de archivos adjuntos",
"created_on": "Creado el", "created_on": "Creado el",
"sub_issue": "Sub-elemento de trabajo" "sub_issue": "Sub-elemento de trabajo",
"work_item_count": "Recuento de elementos de trabajo"
}, },
"extra": { "extra": {
"show_sub_issues": "Mostrar sub-elementos", "show_sub_issues": "Mostrar sub-elementos",

View file

@ -983,7 +983,8 @@
"sub_issue_count": "Nombre de sous-éléments", "sub_issue_count": "Nombre de sous-éléments",
"attachment_count": "Nombre de pièces jointes", "attachment_count": "Nombre de pièces jointes",
"created_on": "Créé le", "created_on": "Créé le",
"sub_issue": "Sous-élément de travail" "sub_issue": "Sous-élément de travail",
"work_item_count": "Nombre d'éléments de travail"
}, },
"extra": { "extra": {
"show_sub_issues": "Afficher les sous-éléments", "show_sub_issues": "Afficher les sous-éléments",

View file

@ -980,8 +980,9 @@
"sub_issue_count": "Numero di sotto-elementi di lavoro", "sub_issue_count": "Numero di sotto-elementi di lavoro",
"attachment_count": "Numero di allegati", "attachment_count": "Numero di allegati",
"created_on": "Creato il", "created_on": "Creato il",
"sub_issue": "Sotto-elemento di lavoro" "sub_issue": "Sotto-elemento di lavoro",
}, "work_item_count": "Conteggio degli elementi di lavoro"
},
"extra": { "extra": {
"show_sub_issues": "Mostra sotto-elementi di lavoro", "show_sub_issues": "Mostra sotto-elementi di lavoro",
"show_empty_groups": "Mostra gruppi vuoti" "show_empty_groups": "Mostra gruppi vuoti"

View file

@ -983,8 +983,9 @@
"sub_issue_count": "サブ作業項目数", "sub_issue_count": "サブ作業項目数",
"attachment_count": "添付ファイル数", "attachment_count": "添付ファイル数",
"created_on": "作成日", "created_on": "作成日",
"sub_issue": "サブ作業項目" "sub_issue": "サブ作業項目",
}, "work_item_count": "作業項目数"
},
"extra": { "extra": {
"show_sub_issues": "サブ作業項目を表示", "show_sub_issues": "サブ作業項目を表示",
"show_empty_groups": "空のグループを表示" "show_empty_groups": "空のグループを表示"

View file

@ -982,7 +982,8 @@
"sub_issue_count": "Количество подэлементов", "sub_issue_count": "Количество подэлементов",
"attachment_count": "Количество вложений", "attachment_count": "Количество вложений",
"created_on": "Дата создания", "created_on": "Дата создания",
"sub_issue": "Подэлемент" "sub_issue": "Подэлемент",
"work_item_count": "Количество рабочих элементов"
}, },
"extra": { "extra": {
"show_sub_issues": "Показывать подэлементы", "show_sub_issues": "Показывать подэлементы",

View file

@ -983,7 +983,8 @@
"sub_issue_count": "子工作项数量", "sub_issue_count": "子工作项数量",
"attachment_count": "附件数量", "attachment_count": "附件数量",
"created_on": "创建于", "created_on": "创建于",
"sub_issue": "子工作项" "sub_issue": "子工作项",
"work_item_count": "工作项数量"
}, },
"extra": { "extra": {
"show_sub_issues": "显示子工作项", "show_sub_issues": "显示子工作项",

View file

@ -4,9 +4,10 @@ import React, { FC } from "react";
type Props = { type Props = {
issueId: string; issueId: string;
className?: string;
size?: number;
showProgressText?: boolean;
showLabel?: boolean;
}; };
export const IssueStats: FC<Props> = (props) => { export const IssueStats: FC<Props> = (props) => <></>;
const { issueId } = props;
return <></>;
};

View file

@ -53,7 +53,7 @@ export const FilterDisplayProperties: React.FC<Props> = observer((props) => {
} }
}).map((property) => { }).map((property) => {
if (isEpic && property.key === "sub_issue_count") { if (isEpic && property.key === "sub_issue_count") {
return { ...property, title: "Work item count" }; return { ...property, titleTranslationKey: "issue.display.properties.work_item_count" };
} }
return property; return property;
}); });

View file

@ -5,9 +5,9 @@ import { useParams } from "next/navigation";
// ui // ui
import { Tooltip, ControlLink } from "@plane/ui"; import { Tooltip, ControlLink } from "@plane/ui";
// components // components
import { findTotalDaysInRange } from "@plane/utils";
import { SIDEBAR_WIDTH } from "@/components/gantt-chart/constants"; import { SIDEBAR_WIDTH } from "@/components/gantt-chart/constants";
// helpers // helpers
import { renderFormattedDate } from "@/helpers/date-time.helper";
import { generateWorkItemLink } from "@/helpers/issue.helper"; import { generateWorkItemLink } from "@/helpers/issue.helper";
// hooks // hooks
import { useIssueDetail, useIssues, useProject, useProjectState } from "@/hooks/store"; import { useIssueDetail, useIssues, useProject, useProjectState } from "@/hooks/store";
@ -17,6 +17,7 @@ import { usePlatformOS } from "@/hooks/use-platform-os";
// plane web components // plane web components
import { IssueIdentifier } from "@/plane-web/components/issues"; import { IssueIdentifier } from "@/plane-web/components/issues";
// //
import { IssueStats } from "@/plane-web/components/issues/issue-layouts/issue-stats";
import { getBlockViewDetails } from "../utils"; import { getBlockViewDetails } from "../utils";
import { GanttStoreType } from "./base-gantt-root"; import { GanttStoreType } from "./base-gantt-root";
@ -48,6 +49,8 @@ export const IssueGanttBlock: React.FC<Props> = observer((props) => {
const handleIssuePeekOverview = () => handleRedirection(workspaceSlug, issueDetails, isMobile); const handleIssuePeekOverview = () => handleRedirection(workspaceSlug, issueDetails, isMobile);
const duration = findTotalDaysInRange(issueDetails?.start_date, issueDetails?.target_date) || 0;
return ( return (
<Tooltip <Tooltip
isMobile={isMobile} isMobile={isMobile}
@ -62,17 +65,24 @@ export const IssueGanttBlock: React.FC<Props> = observer((props) => {
> >
<div <div
id={`issue-${issueId}`} id={`issue-${issueId}`}
className="relative flex h-full w-full cursor-pointer items-center rounded" className="relative flex h-full w-full cursor-pointer items-center rounded space-between"
style={blockStyle} style={blockStyle}
onClick={handleIssuePeekOverview} onClick={handleIssuePeekOverview}
> >
<div className="absolute left-0 top-0 h-full w-full bg-custom-background-100/50" /> <div className="absolute left-0 top-0 h-full w-full bg-custom-background-100/50 " />
<div <div
className="sticky w-auto overflow-hidden truncate px-2.5 py-1 text-sm text-custom-text-100" className="sticky w-auto overflow-hidden truncate px-2.5 py-1 text-sm text-custom-text-100 flex-1"
style={{ left: `${SIDEBAR_WIDTH}px` }} style={{ left: `${SIDEBAR_WIDTH}px` }}
> >
{issueDetails?.name} {issueDetails?.name}
</div> </div>
{isEpic && (
<IssueStats
issueId={issueId}
className="sticky mx-2 font-medium text-custom-text-100 overflow-hidden truncate w-auto justify-end flex-shrink-0"
showProgressText={duration >= 2}
/>
)}
</div> </div>
</Tooltip> </Tooltip>
); );

View file

@ -25,8 +25,10 @@ import { usePlatformOS } from "@/hooks/use-platform-os";
// plane web components // plane web components
import { IssueIdentifier } from "@/plane-web/components/issues"; import { IssueIdentifier } from "@/plane-web/components/issues";
// local components // local components
import { IssueStats } from "@/plane-web/components/issues/issue-layouts/issue-stats";
import { TRenderQuickActions } from "../list/list-view-types"; import { TRenderQuickActions } from "../list/list-view-types";
import { IssueProperties } from "../properties/all-properties"; import { IssueProperties } from "../properties/all-properties";
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
import { getIssueBlockId } from "../utils"; import { getIssueBlockId } from "../utils";
interface IssueBlockProps { interface IssueBlockProps {
@ -61,6 +63,9 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = observer((prop
// hooks // hooks
const { isMobile } = usePlatformOS(); const { isMobile } = usePlatformOS();
// derived values
const subIssueCount = issue?.sub_issues_count ?? 0;
const handleEventPropagation = (e: React.MouseEvent) => { const handleEventPropagation = (e: React.MouseEvent) => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
@ -105,6 +110,16 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = observer((prop
isReadOnly={isReadOnly} isReadOnly={isReadOnly}
isEpic={isEpic} isEpic={isEpic}
/> />
{isEpic && displayProperties && (
<WithDisplayPropertiesHOC
displayProperties={displayProperties}
displayPropertyKey="sub_issue_count"
shouldRenderProperty={(properties) => !!properties.sub_issue_count && !!subIssueCount}
>
<IssueStats issueId={issue.id} className="mt-2 font-medium text-custom-text-350" />
</WithDisplayPropertiesHOC>
)}
</> </>
); );
}); });

View file

@ -25,6 +25,7 @@ import { usePlatformOS } from "@/hooks/use-platform-os";
import { IssueIdentifier } from "@/plane-web/components/issues"; import { IssueIdentifier } from "@/plane-web/components/issues";
import { IssueStats } from "@/plane-web/components/issues/issue-layouts/issue-stats"; import { IssueStats } from "@/plane-web/components/issues/issue-layouts/issue-stats";
// types // types
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
import { TRenderQuickActions } from "./list-view-types"; import { TRenderQuickActions } from "./list-view-types";
interface IssueBlockProps { interface IssueBlockProps {
@ -269,7 +270,15 @@ export const IssueBlock = observer((props: IssueBlockProps) => {
> >
<p className="truncate cursor-pointer text-sm text-custom-text-100">{issue.name}</p> <p className="truncate cursor-pointer text-sm text-custom-text-100">{issue.name}</p>
</Tooltip> </Tooltip>
{isEpic && <IssueStats issueId={issue.id} />} {isEpic && displayProperties && (
<WithDisplayPropertiesHOC
displayProperties={displayProperties}
displayPropertyKey="sub_issue_count"
shouldRenderProperty={(properties) => !!properties.sub_issue_count}
>
<IssueStats issueId={issue.id} className="ml-2 font-medium text-custom-text-350" />
</WithDisplayPropertiesHOC>
)}
</div> </div>
{!issue?.tempId && ( {!issue?.tempId && (
<div <div

View file

@ -429,36 +429,38 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
{/* extra render properties */} {/* extra render properties */}
{/* sub-issues */} {/* sub-issues */}
<WithDisplayPropertiesHOC {!isEpic && (
displayProperties={displayProperties} <WithDisplayPropertiesHOC
displayPropertyKey="sub_issue_count" displayProperties={displayProperties}
shouldRenderProperty={(properties) => !!properties.sub_issue_count && !!subIssueCount} displayPropertyKey="sub_issue_count"
> shouldRenderProperty={(properties) => !!properties.sub_issue_count && !!subIssueCount}
<Tooltip
tooltipHeading={isEpic ? t("issues.label", { count: 2 }) : t("common.sub_work_items")}
tooltipContent={`${subIssueCount}`}
isMobile={isMobile}
renderByDefault={false}
> >
<div <Tooltip
onFocus={handleEventPropagation} tooltipHeading={t("common.sub_work_items")}
onClick={(e) => { tooltipContent={`${subIssueCount}`}
e.stopPropagation(); isMobile={isMobile}
e.preventDefault(); renderByDefault={false}
if (subIssueCount) redirectToIssueDetail();
}}
className={cn(
"flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1",
{
"hover:bg-custom-background-80 cursor-pointer": subIssueCount,
}
)}
> >
<Layers className="h-3 w-3 flex-shrink-0" strokeWidth={2} /> <div
<div className="text-xs">{subIssueCount}</div> onFocus={handleEventPropagation}
</div> onClick={(e) => {
</Tooltip> e.stopPropagation();
</WithDisplayPropertiesHOC> e.preventDefault();
if (subIssueCount) redirectToIssueDetail();
}}
className={cn(
"flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1",
{
"hover:bg-custom-background-80 cursor-pointer": subIssueCount,
}
)}
>
<Layers className="h-3 w-3 flex-shrink-0" strokeWidth={2} />
<div className="text-xs">{subIssueCount}</div>
</div>
</Tooltip>
</WithDisplayPropertiesHOC>
)}
{/* attachments */} {/* attachments */}
<WithDisplayPropertiesHOC <WithDisplayPropertiesHOC

View file

@ -8,6 +8,7 @@ import { Row } from "@plane/ui";
import { cn } from "@/helpers/common.helper"; import { cn } from "@/helpers/common.helper";
// hooks // hooks
import { useAppRouter } from "@/hooks/use-app-router"; import { useAppRouter } from "@/hooks/use-app-router";
import { IssueStats } from "@/plane-web/components/issues/issue-layouts/issue-stats";
type Props = { type Props = {
issue: TIssue; issue: TIssue;
@ -18,30 +19,30 @@ export const SpreadsheetSubIssueColumn: React.FC<Props> = observer((props: Props
// router // router
const router = useAppRouter(); const router = useAppRouter();
// hooks // hooks
const { workspaceSlug, epicId } = useParams(); const { workspaceSlug } = useParams();
// derived values // derived values
const isEpic = issue?.is_epic;
const subIssueCount = issue?.sub_issues_count ?? 0; const subIssueCount = issue?.sub_issues_count ?? 0;
const redirectToIssueDetail = () => { const redirectToIssueDetail = () => {
router.push( router.push(
`/${workspaceSlug?.toString()}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}${epicId ? "epics" : "issues"}/${issue.id}#sub-issues` `/${workspaceSlug?.toString()}/projects/${issue.project_id}/${issue.archived_at ? "archives/" : ""}${isEpic ? "epics" : "issues"}/${issue.id}#sub-issues`
); );
}; };
const issueLabel = epicId ? "work item" : "sub-work item"; const label = `${subIssueCount} sub-work item${subIssueCount !== 1 ? "s" : ""}`;
const label = `${subIssueCount} ${issueLabel}${subIssueCount !== 1 ? "s" : ""}`;
return ( return (
<Row <Row
onClick={subIssueCount ? redirectToIssueDetail : () => {}} onClick={subIssueCount ? redirectToIssueDetail : () => {}}
className={cn( className={cn(
"flex h-11 w-full items-center border-b-[0.5px] border-custom-border-200 py-1 text-xs hover:bg-custom-background-80 group-[.selected-issue-row]:bg-custom-primary-100/5 group-[.selected-issue-row]:hover:bg-custom-primary-100/10", "flex h-11 w-full items-center border-b-[0.5px] border-custom-border-200 py-1 text-xs hover:bg-custom-background-90 group-[.selected-issue-row]:bg-custom-primary-100/5 group-[.selected-issue-row]:hover:bg-custom-primary-90",
{ {
"cursor-pointer": subIssueCount, "cursor-pointer": subIssueCount,
} }
)} )}
> >
{label} {isEpic ? <IssueStats issueId={issue.id} /> : label}
</Row> </Row>
); );
}); });