[WEB-2681] fix: module progress indicator (#5842)
* fix: module progress indicator * fix: module progress indicator
This commit is contained in:
parent
b833e3b10c
commit
6f8df3279c
2 changed files with 32 additions and 56 deletions
|
|
@ -7,12 +7,21 @@ import { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||||
import { Info, SquareUser } from "lucide-react";
|
import { Info, SquareUser } from "lucide-react";
|
||||||
// ui
|
// ui
|
||||||
import { IModule } from "@plane/types";
|
import { IModule } from "@plane/types";
|
||||||
import { Card, FavoriteStar, LayersIcon, LinearProgressIndicator, TOAST_TYPE, Tooltip, setPromiseToast, setToast } from "@plane/ui";
|
import {
|
||||||
|
Card,
|
||||||
|
FavoriteStar,
|
||||||
|
LayersIcon,
|
||||||
|
LinearProgressIndicator,
|
||||||
|
TOAST_TYPE,
|
||||||
|
Tooltip,
|
||||||
|
setPromiseToast,
|
||||||
|
setToast,
|
||||||
|
} from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { DateRangeDropdown } from "@/components/dropdowns";
|
import { DateRangeDropdown } from "@/components/dropdowns";
|
||||||
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
||||||
import { ModuleQuickActions } from "@/components/modules";
|
import { ModuleQuickActions } from "@/components/modules";
|
||||||
import { ModuleStatusDropdown } from "@/components/modules/module-status-dropdown";
|
import { ModuleStatusDropdown } from "@/components/modules/module-status-dropdown";
|
||||||
// constants
|
// constants
|
||||||
import { PROGRESS_STATE_GROUPS_DETAILS } from "@/constants/common";
|
import { PROGRESS_STATE_GROUPS_DETAILS } from "@/constants/common";
|
||||||
import { MODULE_FAVORITED, MODULE_UNFAVORITED } from "@/constants/event-tracker";
|
import { MODULE_FAVORITED, MODULE_UNFAVORITED } from "@/constants/event-tracker";
|
||||||
|
|
@ -21,11 +30,10 @@ import { MODULE_STATUS } from "@/constants/module";
|
||||||
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||||
import { generateQueryParams } from "@/helpers/router.helper";
|
import { generateQueryParams } from "@/helpers/router.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useMember, useModule, useProjectEstimates, useUserPermissions } from "@/hooks/store";
|
import { useEventTracker, useMember, useModule, useUserPermissions } from "@/hooks/store";
|
||||||
import { useAppRouter } from "@/hooks/use-app-router";
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
// plane web constants
|
// plane web constants
|
||||||
import { EEstimateSystem } from "@/plane-web/constants/estimates";
|
|
||||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
|
@ -46,7 +54,6 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
||||||
const { getModuleById, addModuleToFavorites, removeModuleFromFavorites, updateModuleDetails } = useModule();
|
const { getModuleById, addModuleToFavorites, removeModuleFromFavorites, updateModuleDetails } = useModule();
|
||||||
const { getUserDetails } = useMember();
|
const { getUserDetails } = useMember();
|
||||||
const { captureEvent } = useEventTracker();
|
const { captureEvent } = useEventTracker();
|
||||||
const { currentActiveEstimateId, areEstimateEnabledByProjectId, estimateById } = useProjectEstimates();
|
|
||||||
|
|
||||||
// derived values
|
// derived values
|
||||||
const moduleDetails = getModuleById(moduleId);
|
const moduleDetails = getModuleById(moduleId);
|
||||||
|
|
@ -57,7 +64,6 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
||||||
const isDisabled = !isEditingAllowed || !!moduleDetails?.archived_at;
|
const isDisabled = !isEditingAllowed || !!moduleDetails?.archived_at;
|
||||||
const renderIcon = Boolean(moduleDetails?.start_date) || Boolean(moduleDetails?.target_date);
|
const renderIcon = Boolean(moduleDetails?.start_date) || Boolean(moduleDetails?.target_date);
|
||||||
|
|
||||||
|
|
||||||
const { isMobile } = usePlatformOS();
|
const { isMobile } = usePlatformOS();
|
||||||
const handleAddToFavorites = (e: React.MouseEvent<HTMLButtonElement>) => {
|
const handleAddToFavorites = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
@ -156,29 +162,14 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
||||||
|
|
||||||
if (!moduleDetails) return null;
|
if (!moduleDetails) return null;
|
||||||
|
|
||||||
/**
|
const moduleTotalIssues =
|
||||||
* NOTE: This completion percentage calculation is based on the total issues count.
|
moduleDetails.backlog_issues +
|
||||||
* when estimates are available and estimate type is points, we should consider the estimate point count
|
moduleDetails.unstarted_issues +
|
||||||
* when estimates are available and estimate type is not points, then by default we consider the issue count
|
moduleDetails.started_issues +
|
||||||
*/
|
moduleDetails.completed_issues +
|
||||||
const isEstimateEnabled =
|
moduleDetails.cancelled_issues;
|
||||||
projectId &&
|
|
||||||
currentActiveEstimateId &&
|
|
||||||
areEstimateEnabledByProjectId(projectId.toString()) &&
|
|
||||||
estimateById(currentActiveEstimateId)?.type === EEstimateSystem.POINTS;
|
|
||||||
|
|
||||||
const moduleTotalIssues = isEstimateEnabled
|
|
||||||
? moduleDetails?.total_estimate_points || 0
|
|
||||||
: moduleDetails.backlog_issues +
|
|
||||||
moduleDetails.unstarted_issues +
|
|
||||||
moduleDetails.started_issues +
|
|
||||||
moduleDetails.completed_issues +
|
|
||||||
moduleDetails.cancelled_issues;
|
|
||||||
|
|
||||||
const moduleCompletedIssues = isEstimateEnabled
|
|
||||||
? moduleDetails?.completed_estimate_points || 0
|
|
||||||
: moduleDetails.completed_issues;
|
|
||||||
|
|
||||||
|
const moduleCompletedIssues = moduleDetails.completed_issues;
|
||||||
|
|
||||||
// const areYearsEqual = startDate.getFullYear() === endDate.getFullYear();
|
// const areYearsEqual = startDate.getFullYear() === endDate.getFullYear();
|
||||||
|
|
||||||
|
|
@ -186,11 +177,11 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
||||||
|
|
||||||
const issueCount = module
|
const issueCount = module
|
||||||
? !moduleTotalIssues || moduleTotalIssues === 0
|
? !moduleTotalIssues || moduleTotalIssues === 0
|
||||||
? `0 ${isEstimateEnabled ? `Point` : `Issue`}`
|
? `0 Issue`
|
||||||
: moduleTotalIssues === moduleCompletedIssues
|
: moduleTotalIssues === moduleCompletedIssues
|
||||||
? `${moduleTotalIssues} Issue${moduleTotalIssues > 1 ? `s` : ``}`
|
? `${moduleTotalIssues} Issue${moduleTotalIssues > 1 ? `s` : ``}`
|
||||||
: `${moduleCompletedIssues}/${moduleTotalIssues} ${isEstimateEnabled ? `Points` : `Issues`}`
|
: `${moduleCompletedIssues}/${moduleTotalIssues} Issues`
|
||||||
: `0 ${isEstimateEnabled ? `Point` : `Issue`}`;
|
: `0 Issue`;
|
||||||
|
|
||||||
const moduleLeadDetails = moduleDetails.lead_id ? getUserDetails(moduleDetails.lead_id) : undefined;
|
const moduleLeadDetails = moduleDetails.lead_id ? getUserDetails(moduleDetails.lead_id) : undefined;
|
||||||
|
|
||||||
|
|
@ -213,9 +204,9 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
||||||
<div className="flex items-center gap-2" onClick={handleEventPropagation}>
|
<div className="flex items-center gap-2" onClick={handleEventPropagation}>
|
||||||
{moduleStatus && (
|
{moduleStatus && (
|
||||||
<ModuleStatusDropdown
|
<ModuleStatusDropdown
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
moduleDetails={moduleDetails}
|
moduleDetails={moduleDetails}
|
||||||
handleModuleDetailsChange={handleModuleDetailsChange}
|
handleModuleDetailsChange={handleModuleDetailsChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<button onClick={openModuleOverview}>
|
<button onClick={openModuleOverview}>
|
||||||
|
|
@ -252,9 +243,9 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
||||||
}}
|
}}
|
||||||
onSelect={(val) => {
|
onSelect={(val) => {
|
||||||
handleModuleDetailsChange({
|
handleModuleDetailsChange({
|
||||||
start_date: (val?.from ? renderFormattedPayloadDate(val.from) : null),
|
start_date: val?.from ? renderFormattedPayloadDate(val.from) : null,
|
||||||
target_date: (val?.to ? renderFormattedPayloadDate(val.to) : null)
|
target_date: val?.to ? renderFormattedPayloadDate(val.to) : null,
|
||||||
})
|
});
|
||||||
}}
|
}}
|
||||||
placeholder={{
|
placeholder={{
|
||||||
from: "Start date",
|
from: "Start date",
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,9 @@ import { ModuleListItemAction, ModuleQuickActions } from "@/components/modules";
|
||||||
// helpers
|
// helpers
|
||||||
import { generateQueryParams } from "@/helpers/router.helper";
|
import { generateQueryParams } from "@/helpers/router.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useModule, useProjectEstimates } from "@/hooks/store";
|
import { useModule } from "@/hooks/store";
|
||||||
import { useAppRouter } from "@/hooks/use-app-router";
|
import { useAppRouter } from "@/hooks/use-app-router";
|
||||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||||
// plane web constants
|
|
||||||
import { EEstimateSystem } from "@/plane-web/constants/estimates";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
moduleId: string;
|
moduleId: string;
|
||||||
|
|
@ -35,27 +33,14 @@ export const ModuleListItem: React.FC<Props> = observer((props) => {
|
||||||
// store hooks
|
// store hooks
|
||||||
const { getModuleById } = useModule();
|
const { getModuleById } = useModule();
|
||||||
const { isMobile } = usePlatformOS();
|
const { isMobile } = usePlatformOS();
|
||||||
const { currentActiveEstimateId, areEstimateEnabledByProjectId, estimateById } = useProjectEstimates();
|
|
||||||
|
|
||||||
// derived values
|
// derived values
|
||||||
const moduleDetails = getModuleById(moduleId);
|
const moduleDetails = getModuleById(moduleId);
|
||||||
|
|
||||||
if (!moduleDetails) return null;
|
if (!moduleDetails) return null;
|
||||||
|
|
||||||
/**
|
const completionPercentage =
|
||||||
* NOTE: This completion percentage calculation is based on the total issues count.
|
((moduleDetails.completed_issues + moduleDetails.cancelled_issues) / moduleDetails.total_issues) * 100;
|
||||||
* when estimates are available and estimate type is points, we should consider the estimate point count
|
|
||||||
* when estimates are available and estimate type is not points, then by default we consider the issue count
|
|
||||||
*/
|
|
||||||
const isEstimateEnabled =
|
|
||||||
projectId &&
|
|
||||||
currentActiveEstimateId &&
|
|
||||||
areEstimateEnabledByProjectId(projectId?.toString()) &&
|
|
||||||
estimateById(currentActiveEstimateId)?.type === EEstimateSystem.POINTS;
|
|
||||||
|
|
||||||
const completionPercentage = isEstimateEnabled
|
|
||||||
? ((moduleDetails?.completed_estimate_points || 0) / (moduleDetails?.total_estimate_points || 0)) * 100
|
|
||||||
: ((moduleDetails.completed_issues + moduleDetails.cancelled_issues) / moduleDetails.total_issues) * 100;
|
|
||||||
|
|
||||||
const progress = isNaN(completionPercentage) ? 0 : Math.floor(completionPercentage);
|
const progress = isNaN(completionPercentage) ? 0 : Math.floor(completionPercentage);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue