mirror of
https://github.com/makeplane/plane.git
synced 2026-07-11 21:10:13 +02:00
wip: dropdowns
This commit is contained in:
@@ -41,5 +41,8 @@
|
||||
"@types/react": "18.2.48"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22",
|
||||
"name": "plane"
|
||||
"name": "plane",
|
||||
"dependencies": {
|
||||
"recharts": "^2.12.7"
|
||||
}
|
||||
}
|
||||
|
||||
7
packages/types/src/cycle/cycle.d.ts
vendored
7
packages/types/src/cycle/cycle.d.ts
vendored
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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" />
|
||||
|
||||
37
web/core/components/cycles/active-cycle/use-active-cycle.tsx
Normal file
37
web/core/components/cycles/active-cycle/use-active-cycle.tsx
Normal 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;
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user