From 2986769f282d26480172ba74cd975720719d0020 Mon Sep 17 00:00:00 2001
From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com>
Date: Mon, 22 Jan 2024 20:50:30 +0530
Subject: [PATCH] fix: bugs in dashboard widgets (#3420)
* fix: bugs in dashboard widgets
* chore: updated view all issues button
* refactor: make use of getWidgetDetails and getWidgetStats computed functions
* fix: widgets redirection
* fix: build errors
---
packages/ui/src/dropdowns/custom-menu.tsx | 2 +-
.../dashboard/home-dashboard-widgets.tsx | 2 +-
.../dashboard/widgets/assigned-issues.tsx | 27 ++++----
.../dashboard/widgets/created-issues.tsx | 27 ++++----
.../widgets/dropdowns/duration-filter.tsx | 11 +---
.../widgets/issue-panels/issues-list.tsx | 7 ++-
.../dashboard/widgets/issues-by-priority.tsx | 27 ++++----
.../widgets/issues-by-state-group.tsx | 61 +++++++++++++------
.../dashboard/widgets/overview-stats.tsx | 17 +++---
.../dashboard/widgets/recent-activity.tsx | 17 +++---
.../widgets/recent-collaborators.tsx | 6 +-
.../dashboard/widgets/recent-projects.tsx | 22 +++----
web/store/dashboard.store.ts | 11 ++++
13 files changed, 132 insertions(+), 105 deletions(-)
diff --git a/packages/ui/src/dropdowns/custom-menu.tsx b/packages/ui/src/dropdowns/custom-menu.tsx
index 08239f16a..df2e99a3d 100644
--- a/packages/ui/src/dropdowns/custom-menu.tsx
+++ b/packages/ui/src/dropdowns/custom-menu.tsx
@@ -1,6 +1,6 @@
import * as React from "react";
import ReactDOM from "react-dom";
-// react-poppper
+// react-popper
import { usePopper } from "react-popper";
// hooks
import { useDropdownKeyDown } from "../hooks/use-dropdown-key-down";
diff --git a/web/components/dashboard/home-dashboard-widgets.tsx b/web/components/dashboard/home-dashboard-widgets.tsx
index 6402877c5..fb6055a67 100644
--- a/web/components/dashboard/home-dashboard-widgets.tsx
+++ b/web/components/dashboard/home-dashboard-widgets.tsx
@@ -50,7 +50,7 @@ export const DashboardWidgets = observer(() => {
// if the widget is full width, return it in a 2 column grid
if (widget.fullWidth)
return (
-
+
);
diff --git a/web/components/dashboard/widgets/assigned-issues.tsx b/web/components/dashboard/widgets/assigned-issues.tsx
index 5ad24ee0f..283873a5a 100644
--- a/web/components/dashboard/widgets/assigned-issues.tsx
+++ b/web/components/dashboard/widgets/assigned-issues.tsx
@@ -26,15 +26,10 @@ export const AssignedIssuesWidget: React.FC
= observer((props) => {
// states
const [fetching, setFetching] = useState(false);
// store hooks
- const {
- fetchWidgetStats,
- widgetDetails: allWidgetDetails,
- widgetStats: allWidgetStats,
- updateDashboardWidgetFilters,
- } = useDashboard();
+ const { fetchWidgetStats, getWidgetDetails, getWidgetStats, updateDashboardWidgetFilters } = useDashboard();
// derived values
- const widgetDetails = allWidgetDetails?.[workspaceSlug]?.[dashboardId]?.find((w) => w.key === WIDGET_KEY);
- const widgetStats = allWidgetStats?.[workspaceSlug]?.[dashboardId]?.[WIDGET_KEY] as TAssignedIssuesWidgetResponse;
+ const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY);
+ const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY);
const handleUpdateFilters = async (filters: Partial) => {
if (!widgetDetails) return;
@@ -48,8 +43,8 @@ export const AssignedIssuesWidget: React.FC = observer((props) => {
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
- issue_type: widgetDetails.widget_filters.tab ?? "upcoming",
- target_date: getCustomDates(widgetDetails.widget_filters.target_date ?? "this_week"),
+ issue_type: filters.tab ?? widgetDetails.widget_filters.tab ?? "upcoming",
+ target_date: getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "this_week"),
expand: "issue_relation",
}).finally(() => setFetching(false));
};
@@ -69,14 +64,18 @@ export const AssignedIssuesWidget: React.FC = observer((props) => {
}, [dashboardId, fetchWidgetStats, widgetDetails, widgetStats, workspaceSlug]);
const filterParams = getRedirectionFilters(widgetDetails?.widget_filters.tab ?? "upcoming");
- const redirectionLink = `/${workspaceSlug}/workspace-views/assigned/${filterParams}`;
if (!widgetDetails || !widgetStats) return ;
return (
-
-
All issues assigned
+
+
+ All issues assigned
+
@@ -85,7 +84,7 @@ export const AssignedIssuesWidget: React.FC = observer((props) => {
})
}
/>
-
+
t.key === widgetDetails.widget_filters.tab ?? "upcoming")}
diff --git a/web/components/dashboard/widgets/created-issues.tsx b/web/components/dashboard/widgets/created-issues.tsx
index c25623070..c17645696 100644
--- a/web/components/dashboard/widgets/created-issues.tsx
+++ b/web/components/dashboard/widgets/created-issues.tsx
@@ -26,15 +26,10 @@ export const CreatedIssuesWidget: React.FC = observer((props) => {
// states
const [fetching, setFetching] = useState(false);
// store hooks
- const {
- fetchWidgetStats,
- widgetDetails: allWidgetDetails,
- widgetStats: allWidgetStats,
- updateDashboardWidgetFilters,
- } = useDashboard();
+ const { fetchWidgetStats, getWidgetDetails, getWidgetStats, updateDashboardWidgetFilters } = useDashboard();
// derived values
- const widgetDetails = allWidgetDetails?.[workspaceSlug]?.[dashboardId]?.find((w) => w.key === WIDGET_KEY);
- const widgetStats = allWidgetStats?.[workspaceSlug]?.[dashboardId]?.[WIDGET_KEY] as TCreatedIssuesWidgetResponse;
+ const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY);
+ const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY);
const handleUpdateFilters = async (filters: Partial) => {
if (!widgetDetails) return;
@@ -48,8 +43,8 @@ export const CreatedIssuesWidget: React.FC = observer((props) => {
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
- issue_type: widgetDetails.widget_filters.tab ?? "upcoming",
- target_date: getCustomDates(widgetDetails.widget_filters.target_date ?? "this_week"),
+ issue_type: filters.tab ?? widgetDetails.widget_filters.tab ?? "upcoming",
+ target_date: getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "this_week"),
}).finally(() => setFetching(false));
};
@@ -65,14 +60,18 @@ export const CreatedIssuesWidget: React.FC = observer((props) => {
}, [dashboardId, fetchWidgetStats, widgetDetails, widgetStats, workspaceSlug]);
const filterParams = getRedirectionFilters(widgetDetails?.widget_filters.tab ?? "upcoming");
- const redirectionLink = `/${workspaceSlug}/workspace-views/created/${filterParams}`;
if (!widgetDetails || !widgetStats) return ;
return (
-
-
All issues created
+
+
+ All issues created
+
@@ -81,7 +80,7 @@ export const CreatedIssuesWidget: React.FC = observer((props) => {
})
}
/>
-
+
t.key === widgetDetails.widget_filters.tab ?? "upcoming")}
diff --git a/web/components/dashboard/widgets/dropdowns/duration-filter.tsx b/web/components/dashboard/widgets/dropdowns/duration-filter.tsx
index fedc92cbe..0db293a65 100644
--- a/web/components/dashboard/widgets/dropdowns/duration-filter.tsx
+++ b/web/components/dashboard/widgets/dropdowns/duration-filter.tsx
@@ -16,6 +16,7 @@ export const DurationFilterDropdown: React.FC = (props) => {
return (
{DURATION_FILTER_OPTIONS.find((option) => option.key === value)?.label}
@@ -23,16 +24,10 @@ export const DurationFilterDropdown: React.FC = (props) => {
}
placement="bottom-end"
+ closeOnSelect
>
{DURATION_FILTER_OPTIONS.map((option) => (
- {
- e.preventDefault();
- e.stopPropagation();
- onChange(option.key);
- }}
- >
+ onChange(option.key)}>
{option.label}
))}
diff --git a/web/components/dashboard/widgets/issue-panels/issues-list.tsx b/web/components/dashboard/widgets/issue-panels/issues-list.tsx
index d104bbd05..e1b2967bf 100644
--- a/web/components/dashboard/widgets/issue-panels/issues-list.tsx
+++ b/web/components/dashboard/widgets/issue-panels/issues-list.tsx
@@ -111,10 +111,13 @@ export const WidgetIssuesList: React.FC = (props) => {
)}
- {totalIssues > issues.length && (
+ {issues.length > 0 && (
View all issues
diff --git a/web/components/dashboard/widgets/issues-by-priority.tsx b/web/components/dashboard/widgets/issues-by-priority.tsx
index 1e0cd30da..46534a426 100644
--- a/web/components/dashboard/widgets/issues-by-priority.tsx
+++ b/web/components/dashboard/widgets/issues-by-priority.tsx
@@ -72,14 +72,9 @@ const WIDGET_KEY = "issues_by_priority";
export const IssuesByPriorityWidget: React.FC = observer((props) => {
const { dashboardId, workspaceSlug } = props;
// store hooks
- const {
- fetchWidgetStats,
- widgetDetails: allWidgetDetails,
- widgetStats: allWidgetStats,
- updateDashboardWidgetFilters,
- } = useDashboard();
- const widgetDetails = allWidgetDetails?.[workspaceSlug]?.[dashboardId]?.find((w) => w.key === WIDGET_KEY);
- const widgetStats = allWidgetStats?.[workspaceSlug]?.[dashboardId]?.[WIDGET_KEY] as TIssuesByPriorityWidgetResponse[];
+ const { fetchWidgetStats, getWidgetDetails, getWidgetStats, updateDashboardWidgetFilters } = useDashboard();
+ const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY);
+ const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY);
const handleUpdateFilters = async (filters: Partial) => {
if (!widgetDetails) return;
@@ -91,7 +86,7 @@ export const IssuesByPriorityWidget: React.FC = observer((props) =>
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
- target_date: getCustomDates(widgetDetails.widget_filters.target_date ?? "this_week"),
+ target_date: getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "this_week"),
});
};
@@ -135,12 +130,14 @@ export const IssuesByPriorityWidget: React.FC = observer((props) =>
};
return (
-
+
-
Priority of assigned issues
+
+ Priority of assigned issues
+
@@ -203,6 +200,6 @@ export const IssuesByPriorityWidget: React.FC = observer((props) =>
)}
-
+
);
});
diff --git a/web/components/dashboard/widgets/issues-by-state-group.tsx b/web/components/dashboard/widgets/issues-by-state-group.tsx
index d4478040a..113ce9613 100644
--- a/web/components/dashboard/widgets/issues-by-state-group.tsx
+++ b/web/components/dashboard/widgets/issues-by-state-group.tsx
@@ -25,21 +25,15 @@ const WIDGET_KEY = "issues_by_state_groups";
export const IssuesByStateGroupWidget: React.FC = observer((props) => {
const { dashboardId, workspaceSlug } = props;
// states
- const [activeStateGroup, setActiveStateGroup] = useState("started");
+ const [defaultStateGroup, setDefaultStateGroup] = useState(null);
+ const [activeStateGroup, setActiveStateGroup] = useState(null);
// router
const router = useRouter();
// store hooks
- const {
- fetchWidgetStats,
- widgetDetails: allWidgetDetails,
- widgetStats: allWidgetStats,
- updateDashboardWidgetFilters,
- } = useDashboard();
+ const { fetchWidgetStats, getWidgetDetails, getWidgetStats, updateDashboardWidgetFilters } = useDashboard();
// derived values
- const widgetDetails = allWidgetDetails?.[workspaceSlug]?.[dashboardId]?.find((w) => w.key === WIDGET_KEY);
- const widgetStats = allWidgetStats?.[workspaceSlug]?.[dashboardId]?.[
- WIDGET_KEY
- ] as TIssuesByStateGroupsWidgetResponse[];
+ const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY);
+ const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY);
const handleUpdateFilters = async (filters: Partial) => {
if (!widgetDetails) return;
@@ -51,10 +45,11 @@ export const IssuesByStateGroupWidget: React.FC = observer((props)
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
- target_date: getCustomDates(widgetDetails.widget_filters.target_date ?? "this_week"),
+ target_date: getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "this_week"),
});
};
+ // fetch widget stats
useEffect(() => {
if (!widgetDetails) return;
@@ -65,6 +60,33 @@ export const IssuesByStateGroupWidget: React.FC = observer((props)
});
}, [dashboardId, fetchWidgetStats, widgetDetails, widgetStats, workspaceSlug]);
+ // set active group for center metric
+ useEffect(() => {
+ if (!widgetStats) return;
+
+ const startedCount = widgetStats?.find((item) => item?.state === "started")?.count ?? 0;
+ const unStartedCount = widgetStats?.find((item) => item?.state === "unstarted")?.count ?? 0;
+ const backlogCount = widgetStats?.find((item) => item?.state === "backlog")?.count ?? 0;
+ const completedCount = widgetStats?.find((item) => item?.state === "completed")?.count ?? 0;
+ const canceledCount = widgetStats?.find((item) => item?.state === "cancelled")?.count ?? 0;
+
+ const stateGroup =
+ startedCount > 0
+ ? "started"
+ : unStartedCount > 0
+ ? "unstarted"
+ : backlogCount > 0
+ ? "backlog"
+ : completedCount > 0
+ ? "completed"
+ : canceledCount > 0
+ ? "cancelled"
+ : null;
+
+ setActiveStateGroup(stateGroup);
+ setDefaultStateGroup(stateGroup);
+ }, [widgetStats]);
+
if (!widgetDetails || !widgetStats) return ;
const totalCount = widgetStats?.reduce((acc, item) => acc + item?.count, 0);
@@ -107,12 +129,14 @@ export const IssuesByStateGroupWidget: React.FC = observer((props)
};
return (
-
+
-
State of assigned issues
+
+ State of assigned issues
+
@@ -157,6 +181,7 @@ export const IssuesByStateGroupWidget: React.FC = observer((props)
router.push(`/${workspaceSlug}/workspace-views/assigned/?state_group=${datum.id}`);
}}
onMouseEnter={(datum) => setActiveStateGroup(datum.id as TStateGroups)}
+ onMouseLeave={() => setActiveStateGroup(defaultStateGroup)}
layers={["arcs", CenteredMetric]}
/>
@@ -183,6 +208,6 @@ export const IssuesByStateGroupWidget: React.FC
= observer((props)
)}
-
+
);
});
diff --git a/web/components/dashboard/widgets/overview-stats.tsx b/web/components/dashboard/widgets/overview-stats.tsx
index 74630e1f8..a38e29eb8 100644
--- a/web/components/dashboard/widgets/overview-stats.tsx
+++ b/web/components/dashboard/widgets/overview-stats.tsx
@@ -21,9 +21,9 @@ const WIDGET_KEY = "overview_stats";
export const OverviewStatsWidget: React.FC = observer((props) => {
const { dashboardId, workspaceSlug } = props;
// store hooks
- const { fetchWidgetStats, widgetStats: allWidgetStats } = useDashboard();
+ const { fetchWidgetStats, getWidgetStats } = useDashboard();
// derived values
- const widgetStats = allWidgetStats?.[workspaceSlug]?.[dashboardId]?.[WIDGET_KEY] as TOverviewStatsWidgetResponse;
+ const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY);
const today = renderFormattedPayloadDate(new Date());
const STATS_LIST = [
@@ -76,11 +76,14 @@ export const OverviewStatsWidget: React.FC = observer((props) => {
)}
{stat.count}
{stat.title}
diff --git a/web/components/dashboard/widgets/recent-activity.tsx b/web/components/dashboard/widgets/recent-activity.tsx
index 52fca4600..2066689c3 100644
--- a/web/components/dashboard/widgets/recent-activity.tsx
+++ b/web/components/dashboard/widgets/recent-activity.tsx
@@ -21,8 +21,8 @@ export const RecentActivityWidget: React.FC = observer((props) => {
// store hooks
const { currentUser } = useUser();
// derived values
- const { fetchWidgetStats, widgetStats: allWidgetStats } = useDashboard();
- const widgetStats = allWidgetStats?.[workspaceSlug]?.[dashboardId]?.[WIDGET_KEY] as TRecentActivityWidgetResponse[];
+ const { fetchWidgetStats, getWidgetStats } = useDashboard();
+ const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY);
useEffect(() => {
if (!widgetStats)
@@ -34,13 +34,10 @@ export const RecentActivityWidget: React.FC = observer((props) => {
if (!widgetStats) return ;
return (
-
-
-
My activity
-
+
+
+ My activity
+
{widgetStats.length > 0 ? (
{widgetStats.map((activity) => (
@@ -100,6 +97,6 @@ export const RecentActivityWidget: React.FC = observer((props) => {
)}
-
+
);
});
diff --git a/web/components/dashboard/widgets/recent-collaborators.tsx b/web/components/dashboard/widgets/recent-collaborators.tsx
index 693f9808d..47d87ffa9 100644
--- a/web/components/dashboard/widgets/recent-collaborators.tsx
+++ b/web/components/dashboard/widgets/recent-collaborators.tsx
@@ -53,10 +53,8 @@ const CollaboratorListItem: React.FC = observer((prop
export const RecentCollaboratorsWidget: React.FC = observer((props) => {
const { dashboardId, workspaceSlug } = props;
// store hooks
- const { fetchWidgetStats, widgetStats: allWidgetStats } = useDashboard();
- const widgetStats = allWidgetStats?.[workspaceSlug]?.[dashboardId]?.[
- WIDGET_KEY
- ] as TRecentCollaboratorsWidgetResponse[];
+ const { fetchWidgetStats, getWidgetStats } = useDashboard();
+ const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY);
useEffect(() => {
if (!widgetStats)
diff --git a/web/components/dashboard/widgets/recent-projects.tsx b/web/components/dashboard/widgets/recent-projects.tsx
index d85cf52c8..e28886460 100644
--- a/web/components/dashboard/widgets/recent-projects.tsx
+++ b/web/components/dashboard/widgets/recent-projects.tsx
@@ -39,7 +39,7 @@ const ProjectListItem: React.FC = observer((props) => {
className={`h-[3.375rem] w-[3.375rem] grid place-items-center rounded border border-transparent flex-shrink-0 ${randomBgColor}`}
>
{projectDetails.emoji ? (
-
+
{renderEmoji(projectDetails.emoji)}
) : projectDetails.icon_prop ? (
@@ -75,9 +75,9 @@ export const RecentProjectsWidget: React.FC = observer((props) => {
const {
membership: { currentWorkspaceRole },
} = useUser();
- const { fetchWidgetStats, widgetStats: allWidgetStats } = useDashboard();
+ const { fetchWidgetStats, getWidgetStats } = useDashboard();
// derived values
- const widgetStats = allWidgetStats?.[workspaceSlug]?.[dashboardId]?.[WIDGET_KEY] as TRecentProjectsWidgetResponse;
+ const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY);
const canCreateProject = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN;
useEffect(() => {
@@ -90,13 +90,13 @@ export const RecentProjectsWidget: React.FC = observer((props) => {
if (!widgetStats) return ;
return (
-
-
-
My projects
-
+
+
+ My projects
+
{canCreateProject && (
-
+
);
});
diff --git a/web/store/dashboard.store.ts b/web/store/dashboard.store.ts
index c2f9c8545..ad0960c7b 100644
--- a/web/store/dashboard.store.ts
+++ b/web/store/dashboard.store.ts
@@ -35,6 +35,7 @@ export interface IDashboardStore {
homeDashboardWidgets: TWidget[] | undefined;
// computed actions
getWidgetDetails: (workspaceSlug: string, dashboardId: string, widgetKey: TWidgetKeys) => TWidget | undefined;
+ getWidgetStats: (workspaceSlug: string, dashboardId: string, widgetKey: TWidgetKeys) => T | undefined;
// actions
fetchHomeDashboardWidgets: (workspaceSlug: string) => Promise;
fetchWidgetStats: (
@@ -114,6 +115,16 @@ export class DashboardStore implements IDashboardStore {
return widgets.find((widget) => widget.key === widgetKey);
});
+ /**
+ * @description get widget stats
+ * @param workspaceSlug
+ * @param dashboardId
+ * @param widgetKey
+ * @returns widget stats
+ */
+ getWidgetStats = (workspaceSlug: string, dashboardId: string, widgetKey: TWidgetKeys): T | undefined =>
+ (this.widgetStats?.[workspaceSlug]?.[dashboardId]?.[widgetKey] as unknown as T) ?? undefined;
+
/**
* @description fetch home dashboard details and widgets
* @param workspaceSlug