forked from github/plane
refactor: sidebar stats mutation (#635)
This commit is contained in:
parent
66d07e340b
commit
e2921539d0
@ -32,6 +32,7 @@ import {
|
|||||||
LinkIcon,
|
LinkIcon,
|
||||||
PencilIcon,
|
PencilIcon,
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
|
XMarkIcon,
|
||||||
} from "@heroicons/react/24/outline";
|
} from "@heroicons/react/24/outline";
|
||||||
// helpers
|
// helpers
|
||||||
import { handleIssuesMutation } from "constants/issue";
|
import { handleIssuesMutation } from "constants/issue";
|
||||||
@ -40,7 +41,9 @@ import { copyTextToClipboard, truncateText } from "helpers/string.helper";
|
|||||||
import { IIssue, Properties, TIssueGroupByOptions, UserAuth } from "types";
|
import { IIssue, Properties, TIssueGroupByOptions, UserAuth } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import {
|
||||||
|
CYCLE_DETAILS,
|
||||||
CYCLE_ISSUES_WITH_PARAMS,
|
CYCLE_ISSUES_WITH_PARAMS,
|
||||||
|
MODULE_DETAILS,
|
||||||
MODULE_ISSUES_WITH_PARAMS,
|
MODULE_ISSUES_WITH_PARAMS,
|
||||||
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
||||||
} from "constants/fetch-keys";
|
} from "constants/fetch-keys";
|
||||||
@ -135,10 +138,14 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
|||||||
|
|
||||||
issuesService
|
issuesService
|
||||||
.patchIssue(workspaceSlug as string, projectId as string, issue.id, formData)
|
.patchIssue(workspaceSlug as string, projectId as string, issue.id, formData)
|
||||||
.then((res) => {
|
.then(() => {
|
||||||
if (cycleId) mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params));
|
if (cycleId) {
|
||||||
if (moduleId) mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params));
|
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params));
|
||||||
mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string, params));
|
mutate(CYCLE_DETAILS(cycleId as string));
|
||||||
|
} else if (moduleId) {
|
||||||
|
mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params));
|
||||||
|
mutate(MODULE_DETAILS(moduleId as string));
|
||||||
|
} else mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string, params));
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@ -226,27 +233,30 @@ export const SingleBoardIssue: React.FC<Props> = ({
|
|||||||
{type && !isNotAllowed && (
|
{type && !isNotAllowed && (
|
||||||
<CustomMenu width="auto" ellipsis>
|
<CustomMenu width="auto" ellipsis>
|
||||||
<CustomMenu.MenuItem onClick={editIssue}>
|
<CustomMenu.MenuItem onClick={editIssue}>
|
||||||
<span className="flex items-center justify-start gap-2">
|
<div className="flex items-center justify-start gap-2">
|
||||||
<PencilIcon className="h-4 w-4" />
|
<PencilIcon className="h-4 w-4" />
|
||||||
<span>Edit issue</span>
|
<span>Edit issue</span>
|
||||||
</span>
|
</div>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
{type !== "issue" && removeIssue && (
|
{type !== "issue" && removeIssue && (
|
||||||
<CustomMenu.MenuItem onClick={removeIssue}>
|
<CustomMenu.MenuItem onClick={removeIssue}>
|
||||||
<>Remove from {type}</>
|
<div className="flex items-center justify-start gap-2">
|
||||||
|
<XMarkIcon className="h-4 w-4" />
|
||||||
|
<span>Remove from {type}</span>
|
||||||
|
</div>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
)}
|
)}
|
||||||
<CustomMenu.MenuItem onClick={() => handleDeleteIssue(issue)}>
|
<CustomMenu.MenuItem onClick={() => handleDeleteIssue(issue)}>
|
||||||
<span className="flex items-center justify-start gap-2">
|
<div className="flex items-center justify-start gap-2">
|
||||||
<TrashIcon className="h-4 w-4" />
|
<TrashIcon className="h-4 w-4" />
|
||||||
<span>Delete issue</span>
|
<span>Delete issue</span>
|
||||||
</span>
|
</div>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
<CustomMenu.MenuItem onClick={handleCopyText}>
|
<CustomMenu.MenuItem onClick={handleCopyText}>
|
||||||
<span className="flex items-center justify-start gap-2">
|
<div className="flex items-center justify-start gap-2">
|
||||||
<LinkIcon className="h-4 w-4" />
|
<LinkIcon className="h-4 w-4" />
|
||||||
<span>Copy issue Link</span>
|
<span>Copy issue Link</span>
|
||||||
</span>
|
</div>
|
||||||
</CustomMenu.MenuItem>
|
</CustomMenu.MenuItem>
|
||||||
</CustomMenu>
|
</CustomMenu>
|
||||||
)}
|
)}
|
||||||
|
@ -35,7 +35,9 @@ import { handleIssuesMutation } from "constants/issue";
|
|||||||
import { IIssue, Properties, UserAuth } from "types";
|
import { IIssue, Properties, UserAuth } from "types";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import {
|
import {
|
||||||
|
CYCLE_DETAILS,
|
||||||
CYCLE_ISSUES_WITH_PARAMS,
|
CYCLE_ISSUES_WITH_PARAMS,
|
||||||
|
MODULE_DETAILS,
|
||||||
MODULE_ISSUES_WITH_PARAMS,
|
MODULE_ISSUES_WITH_PARAMS,
|
||||||
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
PROJECT_ISSUES_LIST_WITH_PARAMS,
|
||||||
} from "constants/fetch-keys";
|
} from "constants/fetch-keys";
|
||||||
@ -123,10 +125,14 @@ export const SingleListIssue: React.FC<Props> = ({
|
|||||||
|
|
||||||
issuesService
|
issuesService
|
||||||
.patchIssue(workspaceSlug as string, projectId as string, issue.id, formData)
|
.patchIssue(workspaceSlug as string, projectId as string, issue.id, formData)
|
||||||
.then((res) => {
|
.then(() => {
|
||||||
if (cycleId) mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params));
|
if (cycleId) {
|
||||||
if (moduleId) mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params));
|
mutate(CYCLE_ISSUES_WITH_PARAMS(cycleId as string, params));
|
||||||
mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string, params));
|
mutate(CYCLE_DETAILS(cycleId as string));
|
||||||
|
} else if (moduleId) {
|
||||||
|
mutate(MODULE_ISSUES_WITH_PARAMS(moduleId as string, params));
|
||||||
|
mutate(MODULE_DETAILS(moduleId as string));
|
||||||
|
} else mutate(PROJECT_ISSUES_LIST_WITH_PARAMS(projectId as string, params));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[workspaceSlug, projectId, cycleId, moduleId, issue, groupTitle, index, selectedGroup, params]
|
[workspaceSlug, projectId, cycleId, moduleId, issue, groupTitle, index, selectedGroup, params]
|
||||||
|
@ -240,7 +240,7 @@ export const SidebarProgressStats: React.FC<Props> = ({
|
|||||||
<span className="text-xs capitalize">{group}</span>
|
<span className="text-xs capitalize">{group}</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
completed={groupedIssues[group].length}
|
completed={groupedIssues[group]}
|
||||||
total={issues.length}
|
total={issues.length}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
@ -34,7 +34,6 @@ import { DeleteCycleModal } from "components/cycles";
|
|||||||
import { ExclamationIcon } from "components/icons";
|
import { ExclamationIcon } from "components/icons";
|
||||||
// helpers
|
// helpers
|
||||||
import { capitalizeFirstLetter, copyTextToClipboard } from "helpers/string.helper";
|
import { capitalizeFirstLetter, copyTextToClipboard } from "helpers/string.helper";
|
||||||
import { groupBy } from "helpers/array.helper";
|
|
||||||
import { isDateRangeValid, renderDateFormat, renderShortDate } from "helpers/date-time.helper";
|
import { isDateRangeValid, renderDateFormat, renderShortDate } from "helpers/date-time.helper";
|
||||||
// types
|
// types
|
||||||
import { ICycle, IIssue } from "types";
|
import { ICycle, IIssue } from "types";
|
||||||
@ -78,15 +77,6 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
|
|||||||
: null
|
: null
|
||||||
);
|
);
|
||||||
|
|
||||||
const groupedIssues = {
|
|
||||||
backlog: [],
|
|
||||||
unstarted: [],
|
|
||||||
started: [],
|
|
||||||
cancelled: [],
|
|
||||||
completed: [],
|
|
||||||
...groupBy(issues ?? [], "state_detail.group"),
|
|
||||||
};
|
|
||||||
|
|
||||||
const { reset, watch } = useForm({
|
const { reset, watch } = useForm({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
});
|
});
|
||||||
@ -140,8 +130,8 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
|
|||||||
const isStartValid = new Date(`${cycle?.start_date}`) <= new Date();
|
const isStartValid = new Date(`${cycle?.start_date}`) <= new Date();
|
||||||
const isEndValid = new Date(`${cycle?.end_date}`) >= new Date(`${cycle?.start_date}`);
|
const isEndValid = new Date(`${cycle?.end_date}`) >= new Date(`${cycle?.start_date}`);
|
||||||
|
|
||||||
const progressPercentage = issues
|
const progressPercentage = cycle
|
||||||
? Math.round((groupedIssues.completed.length / issues?.length) * 100)
|
? Math.round((cycle.completed_issues / cycle.total_issues) * 100)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -205,7 +195,8 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
|
|||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "The date you have entered is invalid. Please check and enter a valid date.",
|
message:
|
||||||
|
"The date you have entered is invalid. Please check and enter a valid date.",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -268,7 +259,8 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
|
|||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "The date you have entered is invalid. Please check and enter a valid date.",
|
message:
|
||||||
|
"The date you have entered is invalid. Please check and enter a valid date.",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -348,12 +340,9 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
|
|||||||
|
|
||||||
<div className="flex items-center gap-2.5 text-gray-800">
|
<div className="flex items-center gap-2.5 text-gray-800">
|
||||||
<span className="h-4 w-4">
|
<span className="h-4 w-4">
|
||||||
<ProgressBar
|
<ProgressBar value={cycle.completed_issues} maxValue={cycle.total_issues} />
|
||||||
value={groupedIssues.completed.length}
|
|
||||||
maxValue={issues?.length}
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
{groupedIssues.completed.length}/{issues?.length}
|
{cycle.completed_issues}/{cycle.total_issues}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -369,7 +358,7 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
|
|||||||
<div className="flex w-full items-center justify-between gap-2 ">
|
<div className="flex w-full items-center justify-between gap-2 ">
|
||||||
<div className="flex items-center justify-start gap-2 text-sm">
|
<div className="flex items-center justify-start gap-2 text-sm">
|
||||||
<span className="font-medium text-gray-500">Progress</span>
|
<span className="font-medium text-gray-500">Progress</span>
|
||||||
{!open && issues && progressPercentage ? (
|
{!open && progressPercentage ? (
|
||||||
<span className="rounded bg-[#09A953]/10 px-1.5 py-0.5 text-xs text-[#09A953]">
|
<span className="rounded bg-[#09A953]/10 px-1.5 py-0.5 text-xs text-[#09A953]">
|
||||||
{progressPercentage ? `${progressPercentage}%` : ""}
|
{progressPercentage ? `${progressPercentage}%` : ""}
|
||||||
</span>
|
</span>
|
||||||
@ -404,9 +393,8 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
|
|||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
Pending Issues -{" "}
|
Pending Issues -{" "}
|
||||||
{issues &&
|
{cycle.total_issues -
|
||||||
groupedIssues &&
|
(cycle.completed_issues + cycle.cancelled_issues)}
|
||||||
issues?.length - groupedIssues.completed.length}
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -450,7 +438,7 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
|
|||||||
<span className="font-medium text-gray-500">Other Information</span>
|
<span className="font-medium text-gray-500">Other Information</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(issues?.length ?? 0) > 0 ? (
|
{cycle.total_issues > 0 ? (
|
||||||
<Disclosure.Button>
|
<Disclosure.Button>
|
||||||
<ChevronDownIcon
|
<ChevronDownIcon
|
||||||
className={`h-3 w-3 ${open ? "rotate-180 transform" : ""}`}
|
className={`h-3 w-3 ${open ? "rotate-180 transform" : ""}`}
|
||||||
@ -468,11 +456,17 @@ export const CycleDetailsSidebar: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
<Transition show={open}>
|
<Transition show={open}>
|
||||||
<Disclosure.Panel>
|
<Disclosure.Panel>
|
||||||
{(issues?.length ?? 0) > 0 ? (
|
{cycle.total_issues > 0 ? (
|
||||||
<div className=" h-full w-full py-4">
|
<div className=" h-full w-full py-4">
|
||||||
<SidebarProgressStats
|
<SidebarProgressStats
|
||||||
issues={issues ?? []}
|
issues={issues ?? []}
|
||||||
groupedIssues={groupedIssues}
|
groupedIssues={{
|
||||||
|
backlog: cycle.backlog_issues,
|
||||||
|
unstarted: cycle.unstarted_issues,
|
||||||
|
started: cycle.started_issues,
|
||||||
|
completed: cycle.completed_issues,
|
||||||
|
cancelled: cycle.cancelled_issues,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
@ -77,15 +77,6 @@ export const ModuleDetailsSidebar: React.FC<Props> = ({
|
|||||||
defaultValues,
|
defaultValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
const groupedIssues = {
|
|
||||||
backlog: [],
|
|
||||||
unstarted: [],
|
|
||||||
started: [],
|
|
||||||
cancelled: [],
|
|
||||||
completed: [],
|
|
||||||
...groupBy(moduleIssues ?? [], "issue_detail.state_detail.group"),
|
|
||||||
};
|
|
||||||
|
|
||||||
const submitChanges = (data: Partial<IModule>) => {
|
const submitChanges = (data: Partial<IModule>) => {
|
||||||
if (!workspaceSlug || !projectId || !moduleId) return;
|
if (!workspaceSlug || !projectId || !moduleId) return;
|
||||||
|
|
||||||
@ -179,8 +170,8 @@ export const ModuleDetailsSidebar: React.FC<Props> = ({
|
|||||||
const isStartValid = new Date(`${module?.start_date}`) <= new Date();
|
const isStartValid = new Date(`${module?.start_date}`) <= new Date();
|
||||||
const isEndValid = new Date(`${module?.target_date}`) >= new Date(`${module?.start_date}`);
|
const isEndValid = new Date(`${module?.target_date}`) >= new Date(`${module?.start_date}`);
|
||||||
|
|
||||||
const progressPercentage = moduleIssues
|
const progressPercentage = module
|
||||||
? Math.round((groupedIssues.completed.length / moduleIssues?.length) * 100)
|
? Math.round((module.completed_issues / module.total_issues) * 100)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -389,11 +380,11 @@ export const ModuleDetailsSidebar: React.FC<Props> = ({
|
|||||||
<div className="flex items-center gap-2.5 text-gray-800">
|
<div className="flex items-center gap-2.5 text-gray-800">
|
||||||
<span className="h-4 w-4">
|
<span className="h-4 w-4">
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
value={groupedIssues.completed.length}
|
value={module.completed_issues}
|
||||||
maxValue={moduleIssues?.length}
|
maxValue={module.total_issues}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
{groupedIssues.completed.length}/{moduleIssues?.length}
|
{module.completed_issues}/{module.total_issues}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -445,7 +436,8 @@ export const ModuleDetailsSidebar: React.FC<Props> = ({
|
|||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
Pending Issues -{" "}
|
Pending Issues -{" "}
|
||||||
{moduleIssues?.length - groupedIssues.completed.length}{" "}
|
{module.total_issues -
|
||||||
|
(module.completed_issues + module.cancelled_issues)}{" "}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -489,7 +481,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = ({
|
|||||||
<span className="font-medium text-gray-500">Other Information</span>
|
<span className="font-medium text-gray-500">Other Information</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{issues.length > 0 ? (
|
{module.total_issues > 0 ? (
|
||||||
<Disclosure.Button className="p-1">
|
<Disclosure.Button className="p-1">
|
||||||
<ChevronDownIcon
|
<ChevronDownIcon
|
||||||
className={`h-3 w-3 ${open ? "rotate-180 transform" : ""}`}
|
className={`h-3 w-3 ${open ? "rotate-180 transform" : ""}`}
|
||||||
@ -507,12 +499,18 @@ export const ModuleDetailsSidebar: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
<Transition show={open}>
|
<Transition show={open}>
|
||||||
<Disclosure.Panel>
|
<Disclosure.Panel>
|
||||||
{issues.length > 0 ? (
|
{module.total_issues > 0 ? (
|
||||||
<>
|
<>
|
||||||
<div className=" h-full w-full py-4">
|
<div className=" h-full w-full py-4">
|
||||||
<SidebarProgressStats
|
<SidebarProgressStats
|
||||||
issues={issues}
|
issues={issues}
|
||||||
groupedIssues={groupedIssues}
|
groupedIssues={{
|
||||||
|
backlog: module.backlog_issues,
|
||||||
|
unstarted: module.unstarted_issues,
|
||||||
|
started: module.started_issues,
|
||||||
|
completed: module.completed_issues,
|
||||||
|
cancelled: module.cancelled_issues,
|
||||||
|
}}
|
||||||
userAuth={userAuth}
|
userAuth={userAuth}
|
||||||
module={module}
|
module={module}
|
||||||
/>
|
/>
|
||||||
|
Loading…
Reference in New Issue
Block a user