-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: moved dropdowns to chart component + added pending icon #5824
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,27 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import * as React from "react"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { ISvgIcons } from "./type"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export const PendingState: React.FC<ISvgIcons> = ({ width = "10", height = "11", className, color }) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<svg | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
width={width} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
height={height} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
viewBox="0 0 24 24" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fill="none" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
xmlns="http://www.w3.org/2000/svg" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
className={className} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<path | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fill-rule="evenodd" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
clip-rule="evenodd" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
d="M12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3ZM1 12C1 5.92487 5.92487 1 12 1C18.0751 1 23 5.92487 23 12C23 18.0751 18.0751 23 12 23C5.92487 23 1 18.0751 1 12Z" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fill="#455068" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
<path | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fill-rule="evenodd" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
clip-rule="evenodd" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
d="M12 5C12.5523 5 13 5.44772 13 6V11.382L16.4472 13.1056C16.9412 13.3526 17.1414 13.9532 16.8944 14.4472C16.6474 14.9412 16.0468 15.1414 15.5528 14.8944L11.5528 12.8944C11.214 12.725 11 12.3788 11 12V6C11 5.44772 11.4477 5 12 5Z" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
fill="#455068" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
/> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider making the icon color customizable. The SVG implementation looks good and should correctly render a pending state icon. However, the fill color is currently hardcoded as "#455068". To improve the icon's reusability across different color schemes, consider using the Here's a suggested modification: - fill="#455068"
+ fill={color || "#455068"} Apply this change to both path elements (lines 18 and 24). 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
</svg> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -2,14 +2,16 @@ | |||||
import { FC, Fragment } from "react"; | ||||||
import { observer } from "mobx-react"; | ||||||
// plane ui | ||||||
import { Loader } from "@plane/ui"; | ||||||
import { TCycleEstimateType } from "@plane/types"; | ||||||
import { EEstimateSystem } from "@plane/types/src/enums"; | ||||||
import { CustomSelect, Loader } from "@plane/ui"; | ||||||
// components | ||||||
import ProgressChart from "@/components/core/sidebar/progress-chart"; | ||||||
import { validateCycleSnapshot } from "@/components/cycles"; | ||||||
import { cycleEstimateOptions, validateCycleSnapshot } from "@/components/cycles"; | ||||||
// helpers | ||||||
import { getDate } from "@/helpers/date-time.helper"; | ||||||
// hooks | ||||||
import { useCycle } from "@/hooks/store"; | ||||||
import { useCycle, useProjectEstimates } from "@/hooks/store"; | ||||||
|
||||||
type ProgressChartProps = { | ||||||
workspaceSlug: string; | ||||||
|
@@ -20,7 +22,9 @@ export const SidebarChart: FC<ProgressChartProps> = observer((props) => { | |||||
const { workspaceSlug, projectId, cycleId } = props; | ||||||
|
||||||
// hooks | ||||||
const { getEstimateTypeByCycleId, getCycleById } = useCycle(); | ||||||
const { getEstimateTypeByCycleId, getCycleById, fetchCycleDetails, fetchArchivedCycleDetails, setEstimateType } = | ||||||
useCycle(); | ||||||
const { currentActiveEstimateId, areEstimateEnabledByProjectId, estimateById } = useProjectEstimates(); | ||||||
|
||||||
// derived data | ||||||
const cycleDetails = validateCycleSnapshot(getCycleById(cycleId)); | ||||||
|
@@ -29,41 +33,82 @@ export const SidebarChart: FC<ProgressChartProps> = observer((props) => { | |||||
const totalEstimatePoints = cycleDetails?.total_estimate_points || 0; | ||||||
const totalIssues = cycleDetails?.total_issues || 0; | ||||||
const estimateType = getEstimateTypeByCycleId(cycleId); | ||||||
|
||||||
const isCurrentProjectEstimateEnabled = projectId && areEstimateEnabledByProjectId(projectId) ? true : false; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Simplify unnecessary boolean ternary operator The ternary operator in line 36 is unnecessary because the expression already evaluates to a boolean value. You can simplify the code by removing the ternary operator. Apply this diff to fix: - const isCurrentProjectEstimateEnabled = projectId && areEstimateEnabledByProjectId(projectId) ? true : false;
+ const isCurrentProjectEstimateEnabled = Boolean(projectId && areEstimateEnabledByProjectId(projectId)); 📝 Committable suggestion
Suggested change
🧰 Tools🪛 Biome
|
||||||
const estimateDetails = | ||||||
isCurrentProjectEstimateEnabled && currentActiveEstimateId && estimateById(currentActiveEstimateId); | ||||||
const isCurrentEstimateTypeIsPoints = estimateDetails && estimateDetails?.type === EEstimateSystem.POINTS; | ||||||
const chartDistributionData = | ||||||
estimateType === "points" ? cycleDetails?.estimate_distribution : cycleDetails?.distribution || undefined; | ||||||
|
||||||
const completionChartDistributionData = chartDistributionData?.completion_chart || undefined; | ||||||
|
||||||
if (!workspaceSlug || !projectId || !cycleId) return null; | ||||||
|
||||||
const isArchived = !!cycleDetails?.archived_at; | ||||||
|
||||||
// handlers | ||||||
const onChange = async (value: TCycleEstimateType) => { | ||||||
setEstimateType(cycleId, value); | ||||||
if (!workspaceSlug || !projectId || !cycleId) return; | ||||||
try { | ||||||
if (isArchived) { | ||||||
await fetchArchivedCycleDetails(workspaceSlug, projectId, cycleId); | ||||||
} else { | ||||||
await fetchCycleDetails(workspaceSlug, projectId, cycleId); | ||||||
} | ||||||
} catch (err) { | ||||||
console.error(err); | ||||||
setEstimateType(cycleId, estimateType); | ||||||
} | ||||||
}; | ||||||
return ( | ||||||
<div> | ||||||
<div className="relative flex items-center gap-2"> | ||||||
<div className="flex items-center justify-center gap-1 text-xs"> | ||||||
<span className="h-2.5 w-2.5 rounded-full bg-[#A9BBD0]" /> | ||||||
<span>Ideal</span> | ||||||
<> | ||||||
{isCurrentEstimateTypeIsPoints && ( | ||||||
<div className="relative flex items-center justify-between gap-2 pt-4"> | ||||||
<CustomSelect | ||||||
value={estimateType} | ||||||
label={<span>{cycleEstimateOptions.find((v) => v.value === estimateType)?.label ?? "None"}</span>} | ||||||
onChange={onChange} | ||||||
maxHeight="lg" | ||||||
buttonClassName="border-none rounded text-sm font-medium capitalize" | ||||||
> | ||||||
{cycleEstimateOptions.map((item) => ( | ||||||
<CustomSelect.Option key={item.value} value={item.value} className="capitalize"> | ||||||
{item.label} | ||||||
</CustomSelect.Option> | ||||||
))} | ||||||
</CustomSelect> | ||||||
</div> | ||||||
<div className="flex items-center justify-center gap-1 text-xs"> | ||||||
<span className="h-2.5 w-2.5 rounded-full bg-[#4C8FFF]" /> | ||||||
<span>Current</span> | ||||||
)} | ||||||
<div className="py-4"> | ||||||
<div> | ||||||
<div className="relative flex items-center gap-2"> | ||||||
<div className="flex items-center justify-center gap-1 text-xs"> | ||||||
<span className="h-2.5 w-2.5 rounded-full bg-[#A9BBD0]" /> | ||||||
<span>Ideal</span> | ||||||
</div> | ||||||
<div className="flex items-center justify-center gap-1 text-xs"> | ||||||
<span className="h-2.5 w-2.5 rounded-full bg-[#4C8FFF]" /> | ||||||
<span>Current</span> | ||||||
</div> | ||||||
</div> | ||||||
{cycleStartDate && cycleEndDate && completionChartDistributionData ? ( | ||||||
<Fragment> | ||||||
<ProgressChart | ||||||
distribution={completionChartDistributionData} | ||||||
startDate={cycleStartDate} | ||||||
endDate={cycleEndDate} | ||||||
totalIssues={estimateType === "points" ? totalEstimatePoints : totalIssues} | ||||||
plotTitle={estimateType === "points" ? "points" : "issues"} | ||||||
/> | ||||||
</Fragment> | ||||||
) : ( | ||||||
<Loader className="w-full h-[160px] mt-4"> | ||||||
<Loader.Item width="100%" height="100%" /> | ||||||
</Loader> | ||||||
)} | ||||||
</div> | ||||||
</div> | ||||||
{cycleStartDate && cycleEndDate && completionChartDistributionData ? ( | ||||||
<Fragment> | ||||||
<ProgressChart | ||||||
distribution={completionChartDistributionData} | ||||||
startDate={cycleStartDate} | ||||||
endDate={cycleEndDate} | ||||||
totalIssues={estimateType === "points" ? totalEstimatePoints : totalIssues} | ||||||
plotTitle={estimateType === "points" ? "points" : "issues"} | ||||||
/> | ||||||
</Fragment> | ||||||
) : ( | ||||||
<Loader className="w-full h-[160px] mt-4"> | ||||||
<Loader.Item width="100%" height="100%" /> | ||||||
</Loader> | ||||||
)} | ||||||
</div> | ||||||
</> | ||||||
); | ||||||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,8 +7,7 @@ import { observer } from "mobx-react"; | |
import { useSearchParams } from "next/navigation"; | ||
import { ChevronUp, ChevronDown } from "lucide-react"; | ||
import { Disclosure, Transition } from "@headlessui/react"; | ||
import { ICycle, IIssueFilterOptions, TCycleEstimateType, TCyclePlotType, TProgressSnapshot } from "@plane/types"; | ||
import { CustomSelect } from "@plane/ui"; | ||
import { ICycle, IIssueFilterOptions, TCyclePlotType, TProgressSnapshot } from "@plane/types"; | ||
// components | ||
import { CycleProgressStats } from "@/components/cycles"; | ||
// constants | ||
|
@@ -26,6 +25,11 @@ type TCycleAnalyticsProgress = { | |
cycleId: string; | ||
}; | ||
|
||
export const cycleEstimateOptions: options[] = [ | ||
{ value: "issues", label: "issues" }, | ||
{ value: "points", label: "points" }, | ||
]; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider renaming The type |
||
export const validateCycleSnapshot = (cycleDetails: ICycle | null): ICycle | null => { | ||
if (!cycleDetails || cycleDetails === null) return cycleDetails; | ||
|
||
|
@@ -49,24 +53,14 @@ export const cycleChartOptions: options[] = [ | |
{ value: "burndown", label: "Burn-down" }, | ||
{ value: "burnup", label: "Burn-up" }, | ||
]; | ||
export const cycleEstimateOptions: options[] = [ | ||
{ value: "issues", label: "issues" }, | ||
{ value: "points", label: "points" }, | ||
]; | ||
|
||
export const CycleAnalyticsProgress: FC<TCycleAnalyticsProgress> = observer((props) => { | ||
// props | ||
const { workspaceSlug, projectId, cycleId } = props; | ||
// router | ||
const searchParams = useSearchParams(); | ||
const peekCycle = searchParams.get("peekCycle") || undefined; | ||
const { | ||
getPlotTypeByCycleId, | ||
getEstimateTypeByCycleId, | ||
getCycleById, | ||
fetchCycleDetails, | ||
fetchArchivedCycleDetails, | ||
setEstimateType, | ||
} = useCycle(); | ||
const { getPlotTypeByCycleId, getEstimateTypeByCycleId, getCycleById } = useCycle(); | ||
const { | ||
issuesFilter: { issueFilters, updateFilters }, | ||
} = useIssues(EIssuesStoreType.CYCLE); | ||
|
@@ -76,21 +70,9 @@ export const CycleAnalyticsProgress: FC<TCycleAnalyticsProgress> = observer((pro | |
const plotType: TCyclePlotType = getPlotTypeByCycleId(cycleId); | ||
const estimateType = getEstimateTypeByCycleId(cycleId); | ||
|
||
const completedIssues = cycleDetails?.completed_issues || 0; | ||
const totalIssues = cycleDetails?.total_issues || 0; | ||
const completedEstimatePoints = cycleDetails?.completed_estimate_points || 0; | ||
const totalEstimatePoints = cycleDetails?.total_estimate_points || 0; | ||
|
||
const progressHeaderPercentage = cycleDetails | ||
? estimateType === "points" | ||
? completedEstimatePoints != 0 && totalEstimatePoints != 0 | ||
? Math.round((completedEstimatePoints / totalEstimatePoints) * 100) | ||
: 0 | ||
: completedIssues != 0 && completedIssues != 0 | ||
? Math.round((completedIssues / totalIssues) * 100) | ||
: 0 | ||
: 0; | ||
|
||
const chartDistributionData = | ||
estimateType === "points" ? cycleDetails?.estimate_distribution : cycleDetails?.distribution || undefined; | ||
|
||
|
@@ -115,23 +97,6 @@ export const CycleAnalyticsProgress: FC<TCycleAnalyticsProgress> = observer((pro | |
const isCycleStartDateValid = cycleStartDate && cycleStartDate <= new Date(); | ||
const isCycleEndDateValid = cycleStartDate && cycleEndDate && cycleEndDate >= cycleStartDate; | ||
const isCycleDateValid = isCycleStartDateValid && isCycleEndDateValid; | ||
const isArchived = !!cycleDetails?.archived_at; | ||
|
||
// handlers | ||
const onChange = async (value: TCycleEstimateType) => { | ||
setEstimateType(cycleId, value); | ||
if (!workspaceSlug || !projectId || !cycleId) return; | ||
try { | ||
if (isArchived) { | ||
await fetchArchivedCycleDetails(workspaceSlug, projectId, cycleId); | ||
} else { | ||
await fetchCycleDetails(workspaceSlug, projectId, cycleId); | ||
} | ||
} catch (err) { | ||
console.error(err); | ||
setEstimateType(cycleId, estimateType); | ||
} | ||
}; | ||
|
||
const handleFiltersUpdate = useCallback( | ||
(key: keyof IIssueFilterOptions, value: string | string[]) => { | ||
|
@@ -192,30 +157,7 @@ export const CycleAnalyticsProgress: FC<TCycleAnalyticsProgress> = observer((pro | |
|
||
<Transition show={open}> | ||
<Disclosure.Panel className="flex flex-col"> | ||
<div className="relative flex items-center justify-between gap-2 pt-4"> | ||
<CustomSelect | ||
value={estimateType} | ||
label={<span>{cycleEstimateOptions.find((v) => v.value === estimateType)?.label ?? "None"}</span>} | ||
onChange={onChange} | ||
maxHeight="lg" | ||
buttonClassName="border-none rounded text-sm font-medium" | ||
> | ||
{cycleEstimateOptions.map((item) => ( | ||
<CustomSelect.Option key={item.value} value={item.value}> | ||
{item.label} | ||
</CustomSelect.Option> | ||
))} | ||
</CustomSelect> | ||
<div className="flex items-center justify-center gap-2"> | ||
<div className="flex items-center gap-1 text-xs"> | ||
<span className="text-custom-text-300">Done</span> | ||
<span className="font-semibold text-custom-text-400">{progressHeaderPercentage}%</span> | ||
</div> | ||
</div> | ||
</div> | ||
<div className="py-4"> | ||
<SidebarChartRoot workspaceSlug={workspaceSlug} projectId={projectId} cycleId={cycleId} /> | ||
</div> | ||
<SidebarChartRoot workspaceSlug={workspaceSlug} projectId={projectId} cycleId={cycleId} /> | ||
{/* progress detailed view */} | ||
{chartDistributionData && ( | ||
<div className="w-full border-t border-custom-border-200 py-4"> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Set #455068 as the default color instead of hardcoding, so that we can override if required.