fix: cycle stats empty state (#1338)

* chore: active cycle percentage fix

* fix: progress chart x-axis values
This commit is contained in:
Aaryan Khandelwal 2023-06-23 11:09:34 +05:30 committed by GitHub
parent 9c85704be3
commit d3c56c1765
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 211 additions and 165 deletions

View File

@ -3,7 +3,7 @@ import React from "react";
// ui // ui
import { LineGraph } from "components/ui"; import { LineGraph } from "components/ui";
// helpers // helpers
import { renderShortNumericDateFormat } from "helpers/date-time.helper"; import { getDatesInRange, renderShortNumericDateFormat } from "helpers/date-time.helper";
//types //types
import { TCompletionChartDistribution } from "types"; import { TCompletionChartDistribution } from "types";
@ -46,6 +46,27 @@ const ProgressChart: React.FC<Props> = ({ distribution, startDate, endDate, tota
pending: distribution[key], pending: distribution[key],
})); }));
const generateXAxisTickValues = () => {
const dates = getDatesInRange(startDate, endDate);
const maxDates = 4;
const totalDates = dates.length;
if (totalDates <= maxDates) return dates;
else {
const interval = Math.ceil(totalDates / maxDates);
const limitedDates = [];
for (let i = 0; i < totalDates; i += interval)
limitedDates.push(renderShortNumericDateFormat(dates[i]));
if (!limitedDates.includes(renderShortNumericDateFormat(dates[totalDates - 1])))
limitedDates.push(renderShortNumericDateFormat(dates[totalDates - 1]));
return limitedDates;
}
};
return ( return (
<div className="w-full flex justify-center items-center"> <div className="w-full flex justify-center items-center">
<LineGraph <LineGraph
@ -86,7 +107,7 @@ const ProgressChart: React.FC<Props> = ({ distribution, startDate, endDate, tota
]} ]}
layers={["grid", "markers", "areas", DashedLine, "slices", "points", "axes", "legends"]} layers={["grid", "markers", "areas", DashedLine, "slices", "points", "axes", "legends"]}
axisBottom={{ axisBottom={{
tickValues: chartData.map((item, index) => (index % 2 === 0 ? item.currentDate : "")), tickValues: generateXAxisTickValues(),
}} }}
enablePoints={false} enablePoints={false}
enableArea enableArea

View File

@ -395,82 +395,87 @@ export const ActiveCycleDetails: React.FC = () => {
<div className="text-brand-primary">High Priority Issues</div> <div className="text-brand-primary">High Priority Issues</div>
<div className="my-3 flex max-h-[240px] min-h-[240px] flex-col gap-2.5 overflow-y-scroll rounded-md"> <div className="my-3 flex max-h-[240px] min-h-[240px] flex-col gap-2.5 overflow-y-scroll rounded-md">
{issues ? ( {issues ? (
issues.map((issue) => ( issues.length > 0 ? (
<div issues.map((issue) => (
key={issue.id} <div
className="flex flex-wrap rounded-md items-center justify-between gap-2 border border-brand-base bg-brand-surface-1 px-3 py-1.5" key={issue.id}
> className="flex flex-wrap rounded-md items-center justify-between gap-2 border border-brand-base bg-brand-surface-1 px-3 py-1.5"
<div className="flex flex-col gap-1"> >
<div> <div className="flex flex-col gap-1">
<div>
<Tooltip
tooltipHeading="Issue ID"
tooltipContent={`${issue.project_detail?.identifier}-${issue.sequence_id}`}
>
<span className="flex-shrink-0 text-xs text-brand-secondary">
{issue.project_detail?.identifier}-{issue.sequence_id}
</span>
</Tooltip>
</div>
<Tooltip <Tooltip
tooltipHeading="Issue ID" position="top-left"
tooltipContent={`${issue.project_detail?.identifier}-${issue.sequence_id}`} tooltipHeading="Title"
tooltipContent={issue.name}
> >
<span className="flex-shrink-0 text-xs text-brand-secondary"> <span className="text-[0.825rem] text-brand-base">
{issue.project_detail?.identifier}-{issue.sequence_id} {truncateText(issue.name, 30)}
</span> </span>
</Tooltip> </Tooltip>
</div> </div>
<Tooltip <div className="flex items-center gap-1.5">
position="top-left" <div
tooltipHeading="Title" className={`grid h-6 w-6 place-items-center items-center rounded border shadow-sm flex-shrink-0 ${
tooltipContent={issue.name} issue.priority === "urgent"
> ? "border-red-500/20 bg-red-500/20 text-red-500"
<span className="text-[0.825rem] text-brand-base"> : "border-orange-500/20 bg-orange-500/20 text-orange-500"
{truncateText(issue.name, 30)} }`}
</span> >
</Tooltip> {getPriorityIcon(issue.priority, "text-sm")}
</div>
<div className="flex items-center gap-1.5">
<div
className={`grid h-6 w-6 place-items-center items-center rounded border shadow-sm flex-shrink-0 ${
issue.priority === "urgent"
? "border-red-500/20 bg-red-500/20 text-red-500"
: "border-orange-500/20 bg-orange-500/20 text-orange-500"
}`}
>
{getPriorityIcon(issue.priority, "text-sm")}
</div>
{issue.label_details.length > 0 ? (
<div className="flex flex-wrap gap-1">
{issue.label_details.map((label) => (
<span
key={label.id}
className="group flex items-center gap-1 rounded-2xl border border-brand-base px-2 py-0.5 text-xs text-brand-secondary"
>
<span
className="h-1.5 w-1.5 rounded-full"
style={{
backgroundColor:
label?.color && label.color !== "" ? label.color : "#000",
}}
/>
{label.name}
</span>
))}
</div> </div>
) : ( {issue.label_details.length > 0 ? (
"" <div className="flex flex-wrap gap-1">
)} {issue.label_details.map((label) => (
<div className={`flex items-center gap-2 text-brand-secondary`}> <span
{issue.assignees && key={label.id}
issue.assignees.length > 0 && className="group flex items-center gap-1 rounded-2xl border border-brand-base px-2 py-0.5 text-xs text-brand-secondary"
Array.isArray(issue.assignees) ? ( >
<div className="-my-0.5 flex items-center justify-center gap-2"> <span
<AssigneesList className="h-1.5 w-1.5 rounded-full"
users={issue.assignee_details} style={{
length={3} backgroundColor:
showLength={false} label?.color && label.color !== "" ? label.color : "#000",
/> }}
/>
{label.name}
</span>
))}
</div> </div>
) : ( ) : (
"" ""
)} )}
<div className={`flex items-center gap-2 text-brand-secondary`}>
{issue.assignees &&
issue.assignees.length > 0 &&
Array.isArray(issue.assignees) ? (
<div className="-my-0.5 flex items-center justify-center gap-2">
<AssigneesList
users={issue.assignee_details}
length={3}
showLength={false}
/>
</div>
) : (
""
)}
</div>
</div> </div>
</div> </div>
))
) : (
<div className="grid place-items-center text-brand-secondary text-sm text-center">
No issues present in the cycle.
</div> </div>
)) )
) : ( ) : (
<Loader className="space-y-3"> <Loader className="space-y-3">
<Loader.Item height="50px" /> <Loader.Item height="50px" />
@ -481,27 +486,29 @@ export const ActiveCycleDetails: React.FC = () => {
</div> </div>
</div> </div>
<div className="flex items-center justify-between gap-2"> {issues && issues.length > 0 && (
<div className="h-1 w-full rounded-full bg-brand-surface-2"> <div className="flex items-center justify-between gap-2">
<div <div className="h-1 w-full rounded-full bg-brand-surface-2">
className="h-1 rounded-full bg-green-600" <div
style={{ className="h-1 rounded-full bg-green-600"
width: style={{
issues && width:
`${ issues &&
(issues.filter((issue) => issue?.state_detail?.group === "completed") `${
?.length / (issues.filter((issue) => issue?.state_detail?.group === "completed")
issues.length) * ?.length /
100 ?? 0 issues.length) *
}%`, 100 ?? 0
}} }%`,
/> }}
/>
</div>
<div className="w-16 text-end text-xs text-brand-secondary">
{issues?.filter((issue) => issue?.state_detail?.group === "completed")?.length} of{" "}
{issues?.length}
</div>
</div> </div>
<div className="w-16 text-end text-xs text-brand-secondary"> )}
{issues?.filter((issue) => issue?.state_detail?.group === "completed")?.length} of{" "}
{issues?.length}
</div>
</div>
</div> </div>
<div className="flex flex-col justify-between border-brand-base p-4"> <div className="flex flex-col justify-between border-brand-base p-4">
<div className="flex items-start justify-between gap-4 py-1.5 text-xs"> <div className="flex items-start justify-between gap-4 py-1.5 text-xs">

View File

@ -1,4 +1,4 @@
import React from "react"; import React, { Fragment } from "react";
// headless ui // headless ui
import { Tab } from "@headlessui/react"; import { Tab } from "@headlessui/react";
@ -32,6 +32,7 @@ export const ActiveCycleProgressStats: React.FC<Props> = ({ cycle }) => {
return ( return (
<Tab.Group <Tab.Group
as={Fragment}
defaultIndex={currentValue(tab)} defaultIndex={currentValue(tab)}
onChange={(i) => { onChange={(i) => {
switch (i) { switch (i) {
@ -68,81 +69,87 @@ export const ActiveCycleProgressStats: React.FC<Props> = ({ cycle }) => {
Labels Labels
</Tab> </Tab>
</Tab.List> </Tab.List>
<Tab.Panels className="flex w-full px-4 pb-4"> {cycle.total_issues > 0 ? (
<Tab.Panel <Tab.Panels as={Fragment}>
as="div" <Tab.Panel
className="flex flex-col w-full mt-2 gap-1 overflow-y-scroll items-center text-brand-secondary" as="div"
> className="w-full gap-1 overflow-y-scroll items-center text-brand-secondary p-4"
{cycle.distribution.assignees.map((assignee, index) => { >
if (assignee.assignee_id) {cycle.distribution.assignees.map((assignee, index) => {
return ( if (assignee.assignee_id)
<SingleProgressStats return (
key={assignee.assignee_id} <SingleProgressStats
title={ key={assignee.assignee_id}
<div className="flex items-center gap-2"> title={
<Avatar <div className="flex items-center gap-2">
user={{ <Avatar
id: assignee.assignee_id, user={{
avatar: assignee.avatar ?? "", id: assignee.assignee_id,
first_name: assignee.first_name ?? "", avatar: assignee.avatar ?? "",
last_name: assignee.last_name ?? "", first_name: assignee.first_name ?? "",
}} last_name: assignee.last_name ?? "",
/> }}
<span>{assignee.first_name}</span>
</div>
}
completed={assignee.completed_issues}
total={assignee.total_issues}
/>
);
else
return (
<SingleProgressStats
key={`unassigned-${index}`}
title={
<div className="flex items-center gap-2">
<div className="h-5 w-5 rounded-full border-2 border-brand-base bg-brand-surface-2">
<img
src="/user.png"
height="100%"
width="100%"
className="rounded-full"
alt="User"
/> />
<span>{assignee.first_name}</span>
</div> </div>
<span>No assignee</span> }
</div> completed={assignee.completed_issues}
} total={assignee.total_issues}
completed={assignee.completed_issues}
total={assignee.total_issues}
/>
);
})}
</Tab.Panel>
<Tab.Panel
as="div"
className="flex flex-col w-full mt-2 gap-1 overflow-y-scroll items-center text-brand-secondary"
>
{cycle.distribution.labels.map((label, index) => (
<SingleProgressStats
key={label.label_id ?? `no-label-${index}`}
title={
<div className="flex items-center gap-2">
<span
className="block h-3 w-3 rounded-full"
style={{
backgroundColor: label.color ?? "transparent",
}}
/> />
<span className="text-xs">{label.label_name ?? "No labels"}</span> );
</div> else
} return (
completed={label.completed_issues} <SingleProgressStats
total={label.total_issues} key={`unassigned-${index}`}
/> title={
))} <div className="flex items-center gap-2">
</Tab.Panel> <div className="h-5 w-5 rounded-full border-2 border-brand-base bg-brand-surface-2">
</Tab.Panels> <img
src="/user.png"
height="100%"
width="100%"
className="rounded-full"
alt="User"
/>
</div>
<span>No assignee</span>
</div>
}
completed={assignee.completed_issues}
total={assignee.total_issues}
/>
);
})}
</Tab.Panel>
<Tab.Panel
as="div"
className="w-full gap-1 overflow-y-scroll items-center text-brand-secondary p-4"
>
{cycle.distribution.labels.map((label, index) => (
<SingleProgressStats
key={label.label_id ?? `no-label-${index}`}
title={
<div className="flex items-center gap-2">
<span
className="block h-3 w-3 rounded-full"
style={{
backgroundColor: label.color ?? "transparent",
}}
/>
<span className="text-xs">{label.label_name ?? "No labels"}</span>
</div>
}
completed={label.completed_issues}
total={label.total_issues}
/>
))}
</Tab.Panel>
</Tab.Panels>
) : (
<div className="grid place-items-center text-brand-secondary text-sm text-center mt-4">
No issues present in the cycle.
</div>
)}
</Tab.Group> </Tab.Group>
); );
}; };

View File

@ -282,12 +282,18 @@ export const SingleCycleList: React.FC<TSingleStatProps> = ({
> >
{cycleStatus === "current" ? ( {cycleStatus === "current" ? (
<span className="flex gap-1"> <span className="flex gap-1">
<RadialProgressBar {cycle.total_issues > 0 ? (
progress={(cycle.completed_issues / cycle.total_issues) * 100} <>
/> <RadialProgressBar
<span> progress={(cycle.completed_issues / cycle.total_issues) * 100}
{Math.floor((cycle.completed_issues / cycle.total_issues) * 100)} % />
</span> <span>
{Math.floor((cycle.completed_issues / cycle.total_issues) * 100)} %
</span>
</>
) : (
<span className="normal-case">No issues present</span>
)}
</span> </span>
) : cycleStatus === "upcoming" ? ( ) : cycleStatus === "upcoming" ? (
<span className="flex gap-1"> <span className="flex gap-1">

View File

@ -25,13 +25,18 @@ export const findHowManyDaysLeft = (date: string | Date) => {
return Math.ceil(timeDiff / (1000 * 3600 * 24)); return Math.ceil(timeDiff / (1000 * 3600 * 24));
}; };
export const getDatesInRange = (startDate: Date, endDate: Date) => { export const getDatesInRange = (startDate: string | Date, endDate: string | Date) => {
startDate = new Date(startDate);
endDate = new Date(endDate);
const date = new Date(startDate.getTime()); const date = new Date(startDate.getTime());
const dates = []; const dates = [];
while (date <= endDate) { while (date <= endDate) {
dates.push(new Date(date)); dates.push(new Date(date));
date.setDate(date.getDate() + 1); date.setDate(date.getDate() + 1);
} }
return dates; return dates;
}; };