"use client"; import { FC } from "react"; import { observer } from "mobx-react"; import Image from "next/image"; import { Tab } from "@headlessui/react"; import { IIssueFilterOptions, IIssueFilters, TModuleDistribution, TModuleEstimateDistribution, TModulePlotType, TStateGroups, } from "@plane/types"; import { Avatar, StateGroupIcon } from "@plane/ui"; // components import { SingleProgressStats } from "@/components/core"; // helpers import { cn } from "@/helpers/common.helper"; // hooks import { useProjectState } from "@/hooks/store"; import useLocalStorage from "@/hooks/use-local-storage"; // public import emptyLabel from "@/public/empty-state/empty_label.svg"; import emptyMembers from "@/public/empty-state/empty_members.svg"; // assignee types type TAssigneeData = { id: string | undefined; title: string | undefined; avatar: string | undefined; completed: number; total: number; }[]; type TAssigneeStatComponent = { distribution: TAssigneeData; isEditable?: boolean; filters?: IIssueFilters | undefined; handleFiltersUpdate: (key: keyof IIssueFilterOptions, value: string | string[]) => void; }; // labelTypes type TLabelData = { id: string | undefined; title: string | undefined; color: string | undefined; completed: number; total: number; }[]; type TLabelStatComponent = { distribution: TLabelData; isEditable?: boolean; filters?: IIssueFilters | undefined; handleFiltersUpdate: (key: keyof IIssueFilterOptions, value: string | string[]) => void; }; // stateTypes type TStateData = { state: string | undefined; completed: number; total: number; }[]; type TStateStatComponent = { distribution: TStateData; totalIssuesCount: number; isEditable?: boolean; handleFiltersUpdate: (key: keyof IIssueFilterOptions, value: string | string[]) => void; }; export const AssigneeStatComponent = observer((props: TAssigneeStatComponent) => { const { distribution, isEditable, filters, handleFiltersUpdate } = props; return (
{distribution && distribution.length > 0 ? ( distribution.map((assignee, index) => { if (assignee?.id) return ( {assignee?.title ?? ""}
} completed={assignee?.completed ?? 0} total={assignee?.total ?? 0} {...(isEditable && { onClick: () => handleFiltersUpdate("assignees", assignee.id ?? ""), selected: filters?.filters?.assignees?.includes(assignee.id ?? ""), })} /> ); else return (
User
No assignee } completed={assignee?.completed ?? 0} total={assignee?.total ?? 0} /> ); }) ) : (
empty members
No assignees yet
)} ); }); export const LabelStatComponent = observer((props: TLabelStatComponent) => { const { distribution, isEditable, filters, handleFiltersUpdate } = props; return (
{distribution && distribution.length > 0 ? ( distribution.map((label, index) => { if (label.id) { return ( {label.title ?? "No labels"}
} completed={label.completed} total={label.total} {...(isEditable && { onClick: () => handleFiltersUpdate("labels", label.id ?? ""), selected: filters?.filters?.labels?.includes(label.id ?? `no-label-${index}`), })} /> ); } else { return ( {label.title ?? "No labels"} } completed={label.completed} total={label.total} /> ); } }) ) : (
empty label
No labels yet
)} ); }); export const StateStatComponent = observer((props: TStateStatComponent) => { const { distribution, isEditable, totalIssuesCount, handleFiltersUpdate } = props; // hooks const { groupedProjectStates } = useProjectState(); // derived values const getStateGroupState = (stateGroup: string) => { const stateGroupStates = groupedProjectStates?.[stateGroup]; const stateGroupStatesId = stateGroupStates?.map((state) => state.id); return stateGroupStatesId; }; return (
{distribution.map((group, index) => ( {group.state}
} completed={group.completed} total={totalIssuesCount} {...(isEditable && { onClick: () => group.state && handleFiltersUpdate("state", getStateGroupState(group.state) ?? []), })} /> ))} ); }); const progressStats = [ { key: "stat-assignees", title: "Assignees", }, { key: "stat-labels", title: "Labels", }, { key: "stat-states", title: "States", }, ]; type TModuleProgressStats = { moduleId: string; plotType: TModulePlotType; distribution: TModuleDistribution | TModuleEstimateDistribution | undefined; groupedIssues: Record; totalIssuesCount: number; isEditable?: boolean; filters?: IIssueFilters | undefined; handleFiltersUpdate: (key: keyof IIssueFilterOptions, value: string | string[]) => void; size?: "xs" | "sm"; roundedTab?: boolean; noBackground?: boolean; }; export const ModuleProgressStats: FC = observer((props) => { const { moduleId, plotType, distribution, groupedIssues, totalIssuesCount, isEditable = false, filters, handleFiltersUpdate, size = "sm", roundedTab = false, noBackground = false, } = props; // hooks const { storedValue: currentTab, setValue: setModuleTab } = useLocalStorage( `module-analytics-tab-${moduleId}`, "stat-assignees" ); // derived values const currentTabIndex = (tab: string): number => progressStats.findIndex((stat) => stat.key === tab); const currentDistribution = distribution as TModuleDistribution; const currentEstimateDistribution = distribution as TModuleEstimateDistribution; const distributionAssigneeData: TAssigneeData = plotType === "burndown" ? (currentDistribution?.assignees || []).map((assignee) => ({ id: assignee?.assignee_id || undefined, title: assignee?.display_name || undefined, avatar: assignee?.avatar || undefined, completed: assignee.completed_issues, total: assignee.total_issues, })) : (currentEstimateDistribution?.assignees || []).map((assignee) => ({ id: assignee?.assignee_id || undefined, title: assignee?.display_name || undefined, avatar: assignee?.avatar || undefined, completed: assignee.completed_estimates, total: assignee.total_estimates, })); const distributionLabelData: TLabelData = plotType === "burndown" ? (currentDistribution?.labels || []).map((label) => ({ id: label?.label_id || undefined, title: label?.label_name || undefined, color: label?.color || undefined, completed: label.completed_issues, total: label.total_issues, })) : (currentEstimateDistribution?.labels || []).map((label) => ({ id: label?.label_id || undefined, title: label?.label_name || undefined, color: label?.color || undefined, completed: label.completed_estimates, total: label.total_estimates, })); const distributionStateData: TStateData = Object.keys(groupedIssues || {}).map((state) => ({ state: state, completed: groupedIssues?.[state] || 0, total: totalIssuesCount || 0, })); return (
{progressStats.map((stat) => ( setModuleTab(stat.key)} > {stat.title} ))}
); });