wip: dropdowns

This commit is contained in:
gakshita
2024-09-17 15:41:23 +05:30
parent a1bc348a4e
commit bee30e2fc8
7 changed files with 145 additions and 28 deletions

View File

@@ -41,5 +41,8 @@
"@types/react": "18.2.48"
},
"packageManager": "yarn@1.22.22",
"name": "plane"
"name": "plane",
"dependencies": {
"recharts": "^2.12.7"
}
}

View File

@@ -1,4 +1,4 @@
import type {TIssue, IIssueFilterOptions} from "@plane/types";
import type { TIssue, IIssueFilterOptions } from "@plane/types";
export type TCycleGroups = "current" | "upcoming" | "completed" | "draft";
@@ -107,7 +107,7 @@ export interface CycleIssueResponse {
}
export type SelectCycleType =
| (ICycle & {actionType: "edit" | "delete" | "create-issue"})
| (ICycle & { actionType: "edit" | "delete" | "create-issue" })
| undefined;
export type CycleDateCheckData = {
@@ -116,4 +116,5 @@ export type CycleDateCheckData = {
cycle_id?: string;
};
export type TCyclePlotType = "burndown" | "points";
export type TCycleEstimateType = "issues" | "points";
export type TCyclePlotType = "burndown" | "burnup";

View File

@@ -4,14 +4,6 @@ import { observer } from "mobx-react";
import { Disclosure } from "@headlessui/react";
// ui
import { Row } from "@plane/ui";
// components
import {
ActiveCycleProductivity,
ActiveCycleProgress,
ActiveCycleStats,
CycleListGroupHeader,
CyclesListItem,
} from "@/components/cycles";
import { EmptyState } from "@/components/empty-state";
// constants
import { EmptyStateType } from "@/constants/empty-state";
@@ -23,6 +15,7 @@ import ActiveCycleChart from "./cycle-chart/chart";
import { useState } from "react";
import Summary from "./summary";
import Selection from "./selection";
import useActiveCycle from "./use-active-cycle";
interface IActiveCycleDetails {
workspaceSlug: string;
@@ -31,14 +24,17 @@ interface IActiveCycleDetails {
export const ActiveCycleRoot: React.FC<IActiveCycleDetails> = observer((props) => {
const { workspaceSlug, projectId } = props;
const { currentProjectActiveCycle, currentProjectActiveCycleId } = useCycle();
const {
handleFiltersUpdate,
plotType,
estimateType,
handlePlotChange,
handleEstimateChange,
cycle: activeCycle,
cycleIssueDetails,
} = useCyclesDetails({ workspaceSlug, projectId, cycleId: currentProjectActiveCycleId });
} = useActiveCycle(workspaceSlug, projectId);
const [areaToHighlight, setAreaToHighlight] = useState<string>("");
if (!activeCycle) return null;
if (!activeCycle) return;
return (
<>
<Disclosure as="div" className="flex flex-shrink-0 flex-col" defaultOpen>
@@ -48,14 +44,19 @@ export const ActiveCycleRoot: React.FC<IActiveCycleDetails> = observer((props) =
<CycleProgressHeader cycleDetails={activeCycle} />
</Disclosure.Button>
<Disclosure.Panel>
{!currentProjectActiveCycle ? (
{!activeCycle ? (
<EmptyState type={EmptyStateType.PROJECT_CYCLE_ACTIVE} size="sm" />
) : (
<div className="flex flex-col border-b border-custom-border-200">
<Row className="flex bg-custom-background-100 h-[420px] justify-between !pr-0">
<Row className="flex bg-custom-background-100 md:h-[420px] justify-between !pr-0 flex-col md:flex-row">
<Summary setAreaToHighlight={setAreaToHighlight} />
<div className="h-full w-full flex-1">
<Selection />
<Selection
plotType={plotType}
estimateType={estimateType}
handlePlotChange={handlePlotChange}
handleEstimateChange={handleEstimateChange}
/>
<ActiveCycleChart areaToHighlight={areaToHighlight} />
</div>
</Row>

View File

@@ -1,11 +1,61 @@
import { Row } from "@plane/ui";
import { useCycle } from "@/hooks/store";
import { ICycle, TCycleEstimateType, TCyclePlotType } from "@plane/types";
import { CustomSelect, Row } from "@plane/ui";
import { observer } from "mobx-react";
const Selection = () => {
type options = {
value: string;
label: string;
};
const cycleChartOptions: options[] = [
{ value: "burndown", label: "Burn-down" },
{ value: "burnup", label: "Burn-up" },
];
const cycleEstimateOptions: options[] = [
{ value: "issues", label: "issues" },
{ value: "points", label: "points" },
];
export type TSelectionProps = {
plotType: TCyclePlotType;
estimateType: TCycleEstimateType;
handlePlotChange: (value: TCyclePlotType) => Promise<void>;
handleEstimateChange: (value: TCycleEstimateType) => Promise<void>;
};
export type TDropdownProps = {
value: string;
onChange: (value: TCyclePlotType | TCycleEstimateType) => Promise<void>;
options: any[];
};
const Dropdown = ({ value, onChange, options }: TDropdownProps) => {
return (
<Row className="h-[40px] py-4">
<span className="text-xs text-custom-text-400 font-medium">Dropdowns to be inserted here</span>
</Row>
<div className="relative flex items-center gap-2">
<CustomSelect
value={value}
label={<span>{options.find((v) => v.value === value)?.label ?? "None"}</span>}
onChange={onChange}
maxHeight="lg"
buttonClassName="bg-custom-background-90 border-none rounded text-sm font-medium"
>
{options.map((item) => (
<CustomSelect.Option key={item.value} value={item.value}>
{item.label}
</CustomSelect.Option>
))}
</CustomSelect>
</div>
);
};
const Selection = observer((props: TSelectionProps) => {
const { plotType, estimateType, handlePlotChange, handleEstimateChange } = props;
return (
<Row className="h-[40px] py-4 flex text-sm items-center gap-2 font-medium">
<Dropdown value={plotType} onChange={handlePlotChange} options={cycleChartOptions} />
<span className="text-custom-text-400">for</span>
<Dropdown value={estimateType} onChange={handleEstimateChange} options={cycleEstimateOptions} />
</Row>
);
});
export default Selection;

View File

@@ -3,7 +3,7 @@ import { Info, TrendingDown } from "lucide-react";
const Summary = (props) => {
const { setAreaToHighlight } = props;
return (
<div className="w-[350px] border-r border-custom-border-200 py-4 pr-6">
<div className="md:w-[350px] md:border-r border-custom-border-200 py-4 pr-6">
<div className="text-xs text-custom-text-400 font-medium">Summary of cycle issues</div>
<div className="border-b border-custom-border-200 w-full flex text-red-500 pb-2">
<TrendingDown className="my-auto mr-2" />

View File

@@ -0,0 +1,37 @@
import { useCycle } from "@/hooks/store";
import { ICycle, TCycleEstimateType, TCyclePlotType } from "@plane/types";
import useCyclesDetails from "./use-cycles-details";
import { useState } from "react";
const useActiveCycle = (workspaceSlug: string, projectId: string) => {
const { getPlotTypeByCycleId, getEstimateTypeByCycleId, setPlotType, setEstimateType, currentProjectActiveCycleId } =
useCycle();
const { handleFiltersUpdate, cycle, cycleIssueDetails } = useCyclesDetails({
workspaceSlug,
projectId,
cycleId: currentProjectActiveCycleId,
});
// derived values
const plotType: TCyclePlotType = (cycle && getPlotTypeByCycleId(cycle.id)) || "burndown";
const estimateType: TCycleEstimateType = (cycle && getEstimateTypeByCycleId(cycle.id)) || "issues";
const handlePlotChange = async (value: TCyclePlotType) => {
if (!workspaceSlug || !projectId || !cycle || !cycle.id) return;
setPlotType(cycle.id, value);
};
const handleEstimateChange = async (value: TCycleEstimateType) => {
if (!workspaceSlug || !projectId || !cycle || !cycle.id) return;
setEstimateType(cycle.id, value);
};
return {
plotType,
estimateType,
handlePlotChange,
handleEstimateChange,
cycle,
};
};
export default useActiveCycle;

View File

@@ -11,6 +11,7 @@ import {
TProgressSnapshot,
TCycleEstimateDistribution,
TCycleDistribution,
TCycleEstimateType,
} from "@plane/types";
// helpers
import { orderCycles, shouldFilterCycle } from "@/helpers/cycle.helper";
@@ -31,6 +32,7 @@ export interface ICycleStore {
fetchedMap: Record<string, boolean>;
cycleMap: Record<string, ICycle>;
plotType: Record<string, TCyclePlotType>;
estimatedType: Record<string, TCycleEstimateType>;
activeCycleIdMap: Record<string, boolean>;
// computed
currentProjectCycleIds: string[] | null;
@@ -51,10 +53,12 @@ export interface ICycleStore {
getActiveCycleById: (cycleId: string) => ICycle | null;
getProjectCycleIds: (projectId: string) => string[] | null;
getPlotTypeByCycleId: (cycleId: string) => TCyclePlotType;
getEstimateTypeByCycleId: (cycleId: string) => TCycleEstimateType;
// actions
updateCycleDistribution: (distributionUpdates: DistributionUpdates, cycleId: string) => void;
validateDate: (workspaceSlug: string, projectId: string, payload: CycleDateCheckData) => Promise<any>;
setPlotType: (cycleId: string, plotType: TCyclePlotType) => void;
setEstimateType: (cycleId: string, estimateType: TCycleEstimateType) => void;
// fetch
fetchWorkspaceCycles: (workspaceSlug: string) => Promise<ICycle[]>;
fetchAllCycles: (workspaceSlug: string, projectId: string) => Promise<undefined | ICycle[]>;
@@ -91,6 +95,7 @@ export class CycleStore implements ICycleStore {
loader: boolean = false;
cycleMap: Record<string, ICycle> = {};
plotType: Record<string, TCyclePlotType> = {};
estimatedType: Record<string, TCycleEstimateType> = {};
activeCycleIdMap: Record<string, boolean> = {};
//loaders
fetchedMap: Record<string, boolean> = {};
@@ -108,6 +113,7 @@ export class CycleStore implements ICycleStore {
loader: observable.ref,
cycleMap: observable,
plotType: observable,
estimatedType: observable,
activeCycleIdMap: observable,
fetchedMap: observable,
// computed
@@ -122,6 +128,7 @@ export class CycleStore implements ICycleStore {
// actions
setPlotType: action,
setEstimateType: action,
fetchWorkspaceCycles: action,
fetchAllCycles: action,
fetchActiveCycle: action,
@@ -377,9 +384,19 @@ export class CycleStore implements ICycleStore {
getPlotTypeByCycleId = (cycleId: string) => {
const { projectId } = this.rootStore.router;
return this.plotType[cycleId] || "burndown";
};
/**
* @description gets the estimate type for the module store
* @param {TCycleEstimateType} estimateType
*/
getEstimateTypeByCycleId = (cycleId: string) => {
const { projectId } = this.rootStore.router;
return projectId && this.rootStore.projectEstimate.areEstimateEnabledByProjectId(projectId)
? this.plotType[cycleId] || "burndown"
: "burndown";
? this.estimatedType[cycleId] || "points"
: "points";
};
/**
@@ -390,6 +407,14 @@ export class CycleStore implements ICycleStore {
set(this.plotType, [cycleId], plotType);
};
/**
* @description updates the estimate type for the module store
* @param {TCycleEstimateType} estimateType
*/
setEstimateType = (cycleId: string, estimateType: TCycleEstimateType) => {
set(this.estimatedType, [cycleId], estimateType);
};
/**
* @description fetch all cycles
* @param workspaceSlug