diff --git a/apiserver/plane/app/views/dashboard.py b/apiserver/plane/app/views/dashboard.py index 47fae2c9c..1366a2886 100644 --- a/apiserver/plane/app/views/dashboard.py +++ b/apiserver/plane/app/views/dashboard.py @@ -145,6 +145,23 @@ def dashboard_assigned_issues(self, request, slug): ) ).order_by("priority_order") + if issue_type == "pending": + pending_issues_count = assigned_issues.filter( + state__group__in=["backlog", "started", "unstarted"] + ).count() + pending_issues = assigned_issues.filter( + state__group__in=["backlog", "started", "unstarted"] + )[:5] + return Response( + { + "issues": IssueSerializer( + pending_issues, many=True, expand=self.expand + ).data, + "count": pending_issues_count, + }, + status=status.HTTP_200_OK, + ) + if issue_type == "completed": completed_issues_count = assigned_issues.filter( state__group__in=["completed"] @@ -257,6 +274,23 @@ def dashboard_created_issues(self, request, slug): ) ).order_by("priority_order") + if issue_type == "pending": + pending_issues_count = created_issues.filter( + state__group__in=["backlog", "started", "unstarted"] + ).count() + pending_issues = created_issues.filter( + state__group__in=["backlog", "started", "unstarted"] + )[:5] + return Response( + { + "issues": IssueSerializer( + pending_issues, many=True, expand=self.expand + ).data, + "count": pending_issues_count, + }, + status=status.HTTP_200_OK, + ) + if issue_type == "completed": completed_issues_count = created_issues.filter( state__group__in=["completed"] diff --git a/packages/types/src/dashboard.d.ts b/packages/types/src/dashboard.d.ts index 31751c0d0..7cfa6aa85 100644 --- a/packages/types/src/dashboard.d.ts +++ b/packages/types/src/dashboard.d.ts @@ -13,9 +13,10 @@ export type TWidgetKeys = | "recent_projects" | "recent_collaborators"; -export type TIssuesListTypes = "upcoming" | "overdue" | "completed"; +export type TIssuesListTypes = "pending" | "upcoming" | "overdue" | "completed"; export type TDurationFilterOptions = + | "none" | "today" | "this_week" | "this_month" diff --git a/web/components/dashboard/widgets/assigned-issues.tsx b/web/components/dashboard/widgets/assigned-issues.tsx index d4a27afc1..e2a54c05f 100644 --- a/web/components/dashboard/widgets/assigned-issues.tsx +++ b/web/components/dashboard/widgets/assigned-issues.tsx @@ -17,7 +17,7 @@ import { getCustomDates, getRedirectionFilters } from "helpers/dashboard.helper" // types import { TAssignedIssuesWidgetFilters, TAssignedIssuesWidgetResponse } from "@plane/types"; // constants -import { ISSUES_TABS_LIST } from "constants/dashboard"; +import { FILTERED_ISSUES_TABS_LIST, UNFILTERED_ISSUES_TABS_LIST } from "constants/dashboard"; const WIDGET_KEY = "assigned_issues"; @@ -30,6 +30,8 @@ export const AssignedIssuesWidget: React.FC = observer((props) => { // derived values const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY); const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY); + const selectedTab = widgetDetails?.widget_filters.tab ?? "pending"; + const selectedDurationFilter = widgetDetails?.widget_filters.target_date ?? "none"; const handleUpdateFilters = async (filters: Partial) => { if (!widgetDetails) return; @@ -41,68 +43,79 @@ export const AssignedIssuesWidget: React.FC = observer((props) => { filters, }); + const filterDates = getCustomDates(filters.target_date ?? selectedDurationFilter); fetchWidgetStats(workspaceSlug, dashboardId, { widget_key: WIDGET_KEY, - issue_type: filters.tab ?? widgetDetails.widget_filters.tab ?? "upcoming", - target_date: getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "this_week"), + issue_type: filters.tab ?? selectedTab, + ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), expand: "issue_relation", }).finally(() => setFetching(false)); }; useEffect(() => { - const filterDates = getCustomDates(widgetDetails?.widget_filters.target_date ?? "this_week"); + const filterDates = getCustomDates(selectedDurationFilter); fetchWidgetStats(workspaceSlug, dashboardId, { widget_key: WIDGET_KEY, - issue_type: widgetDetails?.widget_filters.tab ?? "upcoming", - target_date: filterDates, + issue_type: selectedTab, + ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), expand: "issue_relation", }); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const filterParams = getRedirectionFilters(widgetDetails?.widget_filters.tab ?? "upcoming"); + const filterParams = getRedirectionFilters(selectedTab); + const tabsList = selectedDurationFilter === "none" ? UNFILTERED_ISSUES_TABS_LIST : FILTERED_ISSUES_TABS_LIST; if (!widgetDetails || !widgetStats) return ; return (
-
-
- - Assigned to you - -

- Filtered by{" "} - Due date -

-
+
+ + Assigned to you + - handleUpdateFilters({ - target_date: val, - }) - } + value={selectedDurationFilter} + onChange={(val) => { + if (val === selectedDurationFilter) return; + + // switch to pending tab if target date is changed to none + if (val === "none" && selectedTab !== "completed") { + handleUpdateFilters({ target_date: val, tab: "pending" }); + return; + } + // switch to upcoming tab if target date is changed to other than none + if (val !== "none" && selectedDurationFilter === "none" && selectedTab !== "completed") { + handleUpdateFilters({ + target_date: val, + tab: "upcoming", + }); + return; + } + + handleUpdateFilters({ target_date: val }); + }} />
t.key === widgetDetails.widget_filters.tab ?? "upcoming")} + selectedIndex={tabsList.findIndex((tab) => tab.key === selectedTab)} onChange={(i) => { - const selectedTab = ISSUES_TABS_LIST[i]; - handleUpdateFilters({ tab: selectedTab.key ?? "upcoming" }); + const selectedTab = tabsList[i]; + handleUpdateFilters({ tab: selectedTab?.key ?? "pending" }); }} className="h-full flex flex-col" >
- +
- {ISSUES_TABS_LIST.map((tab) => ( + {tabsList.map((tab) => ( = observer((props) => { // derived values const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY); const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY); + const selectedTab = widgetDetails?.widget_filters.tab ?? "pending"; + const selectedDurationFilter = widgetDetails?.widget_filters.target_date ?? "none"; const handleUpdateFilters = async (filters: Partial) => { if (!widgetDetails) return; @@ -41,64 +43,76 @@ export const CreatedIssuesWidget: React.FC = observer((props) => { filters, }); + const filterDates = getCustomDates(filters.target_date ?? selectedDurationFilter); fetchWidgetStats(workspaceSlug, dashboardId, { widget_key: WIDGET_KEY, - issue_type: filters.tab ?? widgetDetails.widget_filters.tab ?? "upcoming", - target_date: getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "this_week"), + issue_type: filters.tab ?? selectedTab, + ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), }).finally(() => setFetching(false)); }; useEffect(() => { + const filterDates = getCustomDates(selectedDurationFilter); + 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: selectedTab, + ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const filterParams = getRedirectionFilters(widgetDetails?.widget_filters.tab ?? "upcoming"); + const filterParams = getRedirectionFilters(selectedTab); + const tabsList = selectedDurationFilter === "none" ? UNFILTERED_ISSUES_TABS_LIST : FILTERED_ISSUES_TABS_LIST; if (!widgetDetails || !widgetStats) return ; return (
-
-
- - Created by you - -

- Filtered by{" "} - Due date -

-
+
+ + Created by you + - handleUpdateFilters({ - target_date: val, - }) - } + value={selectedDurationFilter} + onChange={(val) => { + if (val === selectedDurationFilter) return; + + // switch to pending tab if target date is changed to none + if (val === "none" && selectedTab !== "completed") { + handleUpdateFilters({ target_date: val, tab: "pending" }); + return; + } + // switch to upcoming tab if target date is changed to other than none + if (val !== "none" && selectedDurationFilter === "none" && selectedTab !== "completed") { + handleUpdateFilters({ + target_date: val, + tab: "upcoming", + }); + return; + } + + handleUpdateFilters({ target_date: val }); + }} />
t.key === widgetDetails.widget_filters.tab ?? "upcoming")} + selectedIndex={tabsList.findIndex((tab) => tab.key === selectedTab)} onChange={(i) => { - const selectedTab = ISSUES_TABS_LIST[i]; - handleUpdateFilters({ tab: selectedTab.key ?? "upcoming" }); + const selectedTab = tabsList[i]; + handleUpdateFilters({ tab: selectedTab.key ?? "pending" }); }} className="h-full flex flex-col" >
- +
- {ISSUES_TABS_LIST.map((tab) => ( + {tabsList.map((tab) => ( = (props) => { const filterParams = getRedirectionFilters(tab); const ISSUE_LIST_ITEM: { - [key in string]: { + [key: string]: { [key in TIssuesListTypes]: React.FC; }; } = { assigned: { + pending: AssignedUpcomingIssueListItem, upcoming: AssignedUpcomingIssueListItem, overdue: AssignedOverdueIssueListItem, completed: AssignedCompletedIssueListItem, }, created: { + pending: CreatedUpcomingIssueListItem, upcoming: CreatedUpcomingIssueListItem, overdue: CreatedOverdueIssueListItem, completed: CreatedCompletedIssueListItem, @@ -61,12 +63,7 @@ export const WidgetIssuesList: React.FC = (props) => { <>
{isLoading ? ( - - - - - - + <> ) : issues.length > 0 ? ( <>
@@ -81,7 +78,7 @@ export const WidgetIssuesList: React.FC = (props) => { {totalIssues} - {tab === "upcoming" &&
Due date
} + {["upcoming", "pending"].includes(tab) &&
Due date
} {tab === "overdue" &&
Due by
} {type === "assigned" && tab !== "completed" &&
Blocked by
} {type === "created" &&
Assigned to
} diff --git a/web/components/dashboard/widgets/issue-panels/tabs-list.tsx b/web/components/dashboard/widgets/issue-panels/tabs-list.tsx index 6ef6ec0ee..9ce00a03c 100644 --- a/web/components/dashboard/widgets/issue-panels/tabs-list.tsx +++ b/web/components/dashboard/widgets/issue-panels/tabs-list.tsx @@ -1,26 +1,63 @@ +import { observer } from "mobx-react"; import { Tab } from "@headlessui/react"; // helpers import { cn } from "helpers/common.helper"; +// types +import { TDurationFilterOptions, TIssuesListTypes } from "@plane/types"; // constants -import { ISSUES_TABS_LIST } from "constants/dashboard"; +import { FILTERED_ISSUES_TABS_LIST, UNFILTERED_ISSUES_TABS_LIST } from "constants/dashboard"; -export const TabsList = () => ( - - {ISSUES_TABS_LIST.map((tab) => ( - - cn("font-semibold text-xs rounded py-1.5 focus:outline-none", { - "bg-custom-background-100 text-custom-text-300 shadow-[2px_0_8px_rgba(167,169,174,0.15)]": selected, - "text-custom-text-400": !selected, - }) - } - > - {tab.label} - - ))} - -); +type Props = { + durationFilter: TDurationFilterOptions; + selectedTab: TIssuesListTypes; +}; + +export const TabsList: React.FC = observer((props) => { + const { durationFilter, selectedTab } = props; + + const tabsList = durationFilter === "none" ? UNFILTERED_ISSUES_TABS_LIST : FILTERED_ISSUES_TABS_LIST; + const selectedTabIndex = tabsList.findIndex((tab) => tab.key === (selectedTab ?? "pending")); + + return ( + +
+ {tabsList.map((tab) => ( + + {tab.label} + + ))} + + ); +}); diff --git a/web/components/dashboard/widgets/issues-by-priority.tsx b/web/components/dashboard/widgets/issues-by-priority.tsx index 45b71466d..97884bccc 100644 --- a/web/components/dashboard/widgets/issues-by-priority.tsx +++ b/web/components/dashboard/widgets/issues-by-priority.tsx @@ -84,16 +84,18 @@ export const IssuesByPriorityWidget: React.FC = observer((props) => filters, }); + const filterDates = getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "none"); fetchWidgetStats(workspaceSlug, dashboardId, { widget_key: WIDGET_KEY, - target_date: getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "this_week"), + ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), }); }; useEffect(() => { + const filterDates = getCustomDates(widgetDetails?.widget_filters.target_date ?? "none"); fetchWidgetStats(workspaceSlug, dashboardId, { widget_key: WIDGET_KEY, - target_date: getCustomDates(widgetDetails?.widget_filters.target_date ?? "this_week"), + ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -129,21 +131,15 @@ export const IssuesByPriorityWidget: React.FC = observer((props) => return (
-
-
- - Assigned by priority - -

- Filtered by{" "} - Due date -

-
+
+ + Assigned by priority + handleUpdateFilters({ target_date: val, diff --git a/web/components/dashboard/widgets/issues-by-state-group.tsx b/web/components/dashboard/widgets/issues-by-state-group.tsx index bd4171cfa..2f7f6ffae 100644 --- a/web/components/dashboard/widgets/issues-by-state-group.tsx +++ b/web/components/dashboard/widgets/issues-by-state-group.tsx @@ -43,17 +43,19 @@ export const IssuesByStateGroupWidget: React.FC = observer((props) filters, }); + const filterDates = getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "none"); fetchWidgetStats(workspaceSlug, dashboardId, { widget_key: WIDGET_KEY, - target_date: getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "this_week"), + ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), }); }; // fetch widget stats useEffect(() => { + const filterDates = getCustomDates(widgetDetails?.widget_filters.target_date ?? "none"); fetchWidgetStats(workspaceSlug, dashboardId, { widget_key: WIDGET_KEY, - target_date: getCustomDates(widgetDetails?.widget_filters.target_date ?? "this_week"), + ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), }); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -72,14 +74,14 @@ export const IssuesByStateGroupWidget: React.FC = observer((props) startedCount > 0 ? "started" : unStartedCount > 0 - ? "unstarted" - : backlogCount > 0 - ? "backlog" - : completedCount > 0 - ? "completed" - : canceledCount > 0 - ? "cancelled" - : null; + ? "unstarted" + : backlogCount > 0 + ? "backlog" + : completedCount > 0 + ? "completed" + : canceledCount > 0 + ? "cancelled" + : null; setActiveStateGroup(stateGroup); setDefaultStateGroup(stateGroup); @@ -128,21 +130,15 @@ export const IssuesByStateGroupWidget: React.FC = observer((props) return (
-
-
- - Assigned by state - -

- Filtered by{" "} - Due date -

-
+
+ + Assigned by state + handleUpdateFilters({ target_date: val, diff --git a/web/components/page-views/workspace-dashboard.tsx b/web/components/page-views/workspace-dashboard.tsx index dc9c4de61..93756d22a 100644 --- a/web/components/page-views/workspace-dashboard.tsx +++ b/web/components/page-views/workspace-dashboard.tsx @@ -62,16 +62,18 @@ export const WorkspaceDashboardView = observer(() => { {homeDashboardId && joinedProjectIds ? ( <> {joinedProjectIds.length > 0 ? ( -
+ <> - {currentUser && } - {currentUser && !currentUser.is_tour_completed && ( -
- -
- )} - -
+
+ {currentUser && } + {currentUser && !currentUser.is_tour_completed && ( +
+ +
+ )} + +
+ ) : ( { let firstDay, lastDay; switch (duration) { + case "none": + return ""; case "today": firstDay = renderFormattedPayloadDate(today); lastDay = renderFormattedPayloadDate(today); @@ -32,7 +34,9 @@ export const getRedirectionFilters = (type: TIssuesListTypes): string => { const today = renderFormattedPayloadDate(new Date()); const filterParams = - type === "upcoming" + type === "pending" + ? "?state_group=backlog,unstarted,started" + : type === "upcoming" ? `?target_date=${today};after` : type === "overdue" ? `?target_date=${today};before`