chore: added None filter option to the dashboard widgets (#3556)

* chore: added tab change animation

* chore: widgets filtering logic updated

* refactor: issues list widget

* fix: tab navigation transition

* fix: extra top spacing on opening the peek overview
This commit is contained in:
Aaryan Khandelwal 2024-02-05 19:12:33 +05:30 committed by GitHub
parent ee0e3e2e25
commit 0ee93dfd8c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 270 additions and 147 deletions

View File

@ -145,6 +145,23 @@ def dashboard_assigned_issues(self, request, slug):
) )
).order_by("priority_order") ).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": if issue_type == "completed":
completed_issues_count = assigned_issues.filter( completed_issues_count = assigned_issues.filter(
state__group__in=["completed"] state__group__in=["completed"]
@ -257,6 +274,23 @@ def dashboard_created_issues(self, request, slug):
) )
).order_by("priority_order") ).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": if issue_type == "completed":
completed_issues_count = created_issues.filter( completed_issues_count = created_issues.filter(
state__group__in=["completed"] state__group__in=["completed"]

View File

@ -13,9 +13,10 @@ export type TWidgetKeys =
| "recent_projects" | "recent_projects"
| "recent_collaborators"; | "recent_collaborators";
export type TIssuesListTypes = "upcoming" | "overdue" | "completed"; export type TIssuesListTypes = "pending" | "upcoming" | "overdue" | "completed";
export type TDurationFilterOptions = export type TDurationFilterOptions =
| "none"
| "today" | "today"
| "this_week" | "this_week"
| "this_month" | "this_month"

View File

@ -17,7 +17,7 @@ import { getCustomDates, getRedirectionFilters } from "helpers/dashboard.helper"
// types // types
import { TAssignedIssuesWidgetFilters, TAssignedIssuesWidgetResponse } from "@plane/types"; import { TAssignedIssuesWidgetFilters, TAssignedIssuesWidgetResponse } from "@plane/types";
// constants // 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"; const WIDGET_KEY = "assigned_issues";
@ -30,6 +30,8 @@ export const AssignedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
// derived values // derived values
const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY); const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY);
const widgetStats = getWidgetStats<TAssignedIssuesWidgetResponse>(workspaceSlug, dashboardId, WIDGET_KEY); const widgetStats = getWidgetStats<TAssignedIssuesWidgetResponse>(workspaceSlug, dashboardId, WIDGET_KEY);
const selectedTab = widgetDetails?.widget_filters.tab ?? "pending";
const selectedDurationFilter = widgetDetails?.widget_filters.target_date ?? "none";
const handleUpdateFilters = async (filters: Partial<TAssignedIssuesWidgetFilters>) => { const handleUpdateFilters = async (filters: Partial<TAssignedIssuesWidgetFilters>) => {
if (!widgetDetails) return; if (!widgetDetails) return;
@ -41,68 +43,79 @@ export const AssignedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
filters, filters,
}); });
const filterDates = getCustomDates(filters.target_date ?? selectedDurationFilter);
fetchWidgetStats(workspaceSlug, dashboardId, { fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY, widget_key: WIDGET_KEY,
issue_type: filters.tab ?? widgetDetails.widget_filters.tab ?? "upcoming", issue_type: filters.tab ?? selectedTab,
target_date: getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "this_week"), ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}),
expand: "issue_relation", expand: "issue_relation",
}).finally(() => setFetching(false)); }).finally(() => setFetching(false));
}; };
useEffect(() => { useEffect(() => {
const filterDates = getCustomDates(widgetDetails?.widget_filters.target_date ?? "this_week"); const filterDates = getCustomDates(selectedDurationFilter);
fetchWidgetStats(workspaceSlug, dashboardId, { fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY, widget_key: WIDGET_KEY,
issue_type: widgetDetails?.widget_filters.tab ?? "upcoming", issue_type: selectedTab,
target_date: filterDates, ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}),
expand: "issue_relation", expand: "issue_relation",
}); });
// eslint-disable-next-line react-hooks/exhaustive-deps // 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 <WidgetLoader widgetKey={WIDGET_KEY} />; if (!widgetDetails || !widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;
return ( return (
<div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full hover:shadow-custom-shadow-4xl duration-300 flex flex-col min-h-96"> <div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full hover:shadow-custom-shadow-4xl duration-300 flex flex-col min-h-96">
<div className="flex items-start justify-between gap-2 p-6 pl-7"> <div className="flex items-center justify-between gap-2 p-6 pl-7">
<div> <Link
<Link href={`/${workspaceSlug}/workspace-views/assigned/${filterParams}`}
href={`/${workspaceSlug}/workspace-views/assigned/${filterParams}`} className="text-lg font-semibold text-custom-text-300 hover:underline"
className="text-lg font-semibold text-custom-text-300 hover:underline" >
> Assigned to you
Assigned to you </Link>
</Link>
<p className="mt-3 text-xs font-medium text-custom-text-300">
Filtered by{" "}
<span className="border-[0.5px] border-custom-border-300 rounded py-1 px-2 ml-0.5">Due date</span>
</p>
</div>
<DurationFilterDropdown <DurationFilterDropdown
value={widgetDetails.widget_filters.target_date ?? "this_week"} value={selectedDurationFilter}
onChange={(val) => onChange={(val) => {
handleUpdateFilters({ if (val === selectedDurationFilter) return;
target_date: val,
}) // 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 });
}}
/> />
</div> </div>
<Tab.Group <Tab.Group
as="div" as="div"
defaultIndex={ISSUES_TABS_LIST.findIndex((t) => t.key === widgetDetails.widget_filters.tab ?? "upcoming")} selectedIndex={tabsList.findIndex((tab) => tab.key === selectedTab)}
onChange={(i) => { onChange={(i) => {
const selectedTab = ISSUES_TABS_LIST[i]; const selectedTab = tabsList[i];
handleUpdateFilters({ tab: selectedTab.key ?? "upcoming" }); handleUpdateFilters({ tab: selectedTab?.key ?? "pending" });
}} }}
className="h-full flex flex-col" className="h-full flex flex-col"
> >
<div className="px-6"> <div className="px-6">
<TabsList /> <TabsList durationFilter={selectedDurationFilter} selectedTab={selectedTab} />
</div> </div>
<Tab.Panels as="div" className="h-full"> <Tab.Panels as="div" className="h-full">
{ISSUES_TABS_LIST.map((tab) => ( {tabsList.map((tab) => (
<Tab.Panel key={tab.key} as="div" className="h-full flex flex-col"> <Tab.Panel key={tab.key} as="div" className="h-full flex flex-col">
<WidgetIssuesList <WidgetIssuesList
issues={widgetStats.issues} issues={widgetStats.issues}

View File

@ -17,7 +17,7 @@ import { getCustomDates, getRedirectionFilters } from "helpers/dashboard.helper"
// types // types
import { TCreatedIssuesWidgetFilters, TCreatedIssuesWidgetResponse } from "@plane/types"; import { TCreatedIssuesWidgetFilters, TCreatedIssuesWidgetResponse } from "@plane/types";
// constants // constants
import { ISSUES_TABS_LIST } from "constants/dashboard"; import { FILTERED_ISSUES_TABS_LIST, UNFILTERED_ISSUES_TABS_LIST } from "constants/dashboard";
const WIDGET_KEY = "created_issues"; const WIDGET_KEY = "created_issues";
@ -30,6 +30,8 @@ export const CreatedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
// derived values // derived values
const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY); const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY);
const widgetStats = getWidgetStats<TCreatedIssuesWidgetResponse>(workspaceSlug, dashboardId, WIDGET_KEY); const widgetStats = getWidgetStats<TCreatedIssuesWidgetResponse>(workspaceSlug, dashboardId, WIDGET_KEY);
const selectedTab = widgetDetails?.widget_filters.tab ?? "pending";
const selectedDurationFilter = widgetDetails?.widget_filters.target_date ?? "none";
const handleUpdateFilters = async (filters: Partial<TCreatedIssuesWidgetFilters>) => { const handleUpdateFilters = async (filters: Partial<TCreatedIssuesWidgetFilters>) => {
if (!widgetDetails) return; if (!widgetDetails) return;
@ -41,64 +43,76 @@ export const CreatedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
filters, filters,
}); });
const filterDates = getCustomDates(filters.target_date ?? selectedDurationFilter);
fetchWidgetStats(workspaceSlug, dashboardId, { fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY, widget_key: WIDGET_KEY,
issue_type: filters.tab ?? widgetDetails.widget_filters.tab ?? "upcoming", issue_type: filters.tab ?? selectedTab,
target_date: getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "this_week"), ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}),
}).finally(() => setFetching(false)); }).finally(() => setFetching(false));
}; };
useEffect(() => { useEffect(() => {
const filterDates = getCustomDates(selectedDurationFilter);
fetchWidgetStats(workspaceSlug, dashboardId, { fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY, widget_key: WIDGET_KEY,
issue_type: widgetDetails?.widget_filters.tab ?? "upcoming", issue_type: selectedTab,
target_date: getCustomDates(widgetDetails?.widget_filters.target_date ?? "this_week"), ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}),
}); });
// eslint-disable-next-line react-hooks/exhaustive-deps // 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 <WidgetLoader widgetKey={WIDGET_KEY} />; if (!widgetDetails || !widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;
return ( return (
<div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full hover:shadow-custom-shadow-4xl duration-300 flex flex-col min-h-96"> <div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full hover:shadow-custom-shadow-4xl duration-300 flex flex-col min-h-96">
<div className="flex items-start justify-between gap-2 p-6 pl-7"> <div className="flex items-center justify-between gap-2 p-6 pl-7">
<div> <Link
<Link href={`/${workspaceSlug}/workspace-views/created/${filterParams}`}
href={`/${workspaceSlug}/workspace-views/created/${filterParams}`} className="text-lg font-semibold text-custom-text-300 hover:underline"
className="text-lg font-semibold text-custom-text-300 hover:underline" >
> Created by you
Created by you </Link>
</Link>
<p className="mt-3 text-xs font-medium text-custom-text-300">
Filtered by{" "}
<span className="border-[0.5px] border-custom-border-300 rounded py-1 px-2 ml-0.5">Due date</span>
</p>
</div>
<DurationFilterDropdown <DurationFilterDropdown
value={widgetDetails.widget_filters.target_date ?? "this_week"} value={selectedDurationFilter}
onChange={(val) => onChange={(val) => {
handleUpdateFilters({ if (val === selectedDurationFilter) return;
target_date: val,
}) // 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 });
}}
/> />
</div> </div>
<Tab.Group <Tab.Group
as="div" as="div"
defaultIndex={ISSUES_TABS_LIST.findIndex((t) => t.key === widgetDetails.widget_filters.tab ?? "upcoming")} selectedIndex={tabsList.findIndex((tab) => tab.key === selectedTab)}
onChange={(i) => { onChange={(i) => {
const selectedTab = ISSUES_TABS_LIST[i]; const selectedTab = tabsList[i];
handleUpdateFilters({ tab: selectedTab.key ?? "upcoming" }); handleUpdateFilters({ tab: selectedTab.key ?? "pending" });
}} }}
className="h-full flex flex-col" className="h-full flex flex-col"
> >
<div className="px-6"> <div className="px-6">
<TabsList /> <TabsList durationFilter={selectedDurationFilter} selectedTab={selectedTab} />
</div> </div>
<Tab.Panels as="div" className="h-full"> <Tab.Panels as="div" className="h-full">
{ISSUES_TABS_LIST.map((tab) => ( {tabsList.map((tab) => (
<Tab.Panel key={tab.key} as="div" className="h-full flex flex-col"> <Tab.Panel key={tab.key} as="div" className="h-full flex flex-col">
<WidgetIssuesList <WidgetIssuesList
issues={widgetStats.issues} issues={widgetStats.issues}

View File

@ -14,7 +14,7 @@ import {
IssueListItemProps, IssueListItemProps,
} from "components/dashboard/widgets"; } from "components/dashboard/widgets";
// ui // ui
import { Loader, getButtonStyling } from "@plane/ui"; import { getButtonStyling } from "@plane/ui";
// helpers // helpers
import { cn } from "helpers/common.helper"; import { cn } from "helpers/common.helper";
import { getRedirectionFilters } from "helpers/dashboard.helper"; import { getRedirectionFilters } from "helpers/dashboard.helper";
@ -41,16 +41,18 @@ export const WidgetIssuesList: React.FC<WidgetIssuesListProps> = (props) => {
const filterParams = getRedirectionFilters(tab); const filterParams = getRedirectionFilters(tab);
const ISSUE_LIST_ITEM: { const ISSUE_LIST_ITEM: {
[key in string]: { [key: string]: {
[key in TIssuesListTypes]: React.FC<IssueListItemProps>; [key in TIssuesListTypes]: React.FC<IssueListItemProps>;
}; };
} = { } = {
assigned: { assigned: {
pending: AssignedUpcomingIssueListItem,
upcoming: AssignedUpcomingIssueListItem, upcoming: AssignedUpcomingIssueListItem,
overdue: AssignedOverdueIssueListItem, overdue: AssignedOverdueIssueListItem,
completed: AssignedCompletedIssueListItem, completed: AssignedCompletedIssueListItem,
}, },
created: { created: {
pending: CreatedUpcomingIssueListItem,
upcoming: CreatedUpcomingIssueListItem, upcoming: CreatedUpcomingIssueListItem,
overdue: CreatedOverdueIssueListItem, overdue: CreatedOverdueIssueListItem,
completed: CreatedCompletedIssueListItem, completed: CreatedCompletedIssueListItem,
@ -61,12 +63,7 @@ export const WidgetIssuesList: React.FC<WidgetIssuesListProps> = (props) => {
<> <>
<div className="h-full"> <div className="h-full">
{isLoading ? ( {isLoading ? (
<Loader className="mt-7 mx-6 space-y-4"> <></>
<Loader.Item height="25px" />
<Loader.Item height="25px" />
<Loader.Item height="25px" />
<Loader.Item height="25px" />
</Loader>
) : issues.length > 0 ? ( ) : issues.length > 0 ? (
<> <>
<div className="mt-7 mx-6 border-b-[0.5px] border-custom-border-200 grid grid-cols-6 gap-1 text-xs text-custom-text-300 pb-1"> <div className="mt-7 mx-6 border-b-[0.5px] border-custom-border-200 grid grid-cols-6 gap-1 text-xs text-custom-text-300 pb-1">
@ -81,7 +78,7 @@ export const WidgetIssuesList: React.FC<WidgetIssuesListProps> = (props) => {
{totalIssues} {totalIssues}
</span> </span>
</h6> </h6>
{tab === "upcoming" && <h6 className="text-center">Due date</h6>} {["upcoming", "pending"].includes(tab) && <h6 className="text-center">Due date</h6>}
{tab === "overdue" && <h6 className="text-center">Due by</h6>} {tab === "overdue" && <h6 className="text-center">Due by</h6>}
{type === "assigned" && tab !== "completed" && <h6 className="text-center">Blocked by</h6>} {type === "assigned" && tab !== "completed" && <h6 className="text-center">Blocked by</h6>}
{type === "created" && <h6 className="text-center">Assigned to</h6>} {type === "created" && <h6 className="text-center">Assigned to</h6>}

View File

@ -1,26 +1,63 @@
import { observer } from "mobx-react";
import { Tab } from "@headlessui/react"; import { Tab } from "@headlessui/react";
// helpers // helpers
import { cn } from "helpers/common.helper"; import { cn } from "helpers/common.helper";
// types
import { TDurationFilterOptions, TIssuesListTypes } from "@plane/types";
// constants // constants
import { ISSUES_TABS_LIST } from "constants/dashboard"; import { FILTERED_ISSUES_TABS_LIST, UNFILTERED_ISSUES_TABS_LIST } from "constants/dashboard";
export const TabsList = () => ( type Props = {
<Tab.List durationFilter: TDurationFilterOptions;
as="div" selectedTab: TIssuesListTypes;
className="border-[0.5px] border-custom-border-200 rounded grid grid-cols-3 bg-custom-background-80" };
>
{ISSUES_TABS_LIST.map((tab) => ( export const TabsList: React.FC<Props> = observer((props) => {
<Tab const { durationFilter, selectedTab } = props;
key={tab.key}
className={({ selected }) => const tabsList = durationFilter === "none" ? UNFILTERED_ISSUES_TABS_LIST : FILTERED_ISSUES_TABS_LIST;
cn("font-semibold text-xs rounded py-1.5 focus:outline-none", { const selectedTabIndex = tabsList.findIndex((tab) => tab.key === (selectedTab ?? "pending"));
"bg-custom-background-100 text-custom-text-300 shadow-[2px_0_8px_rgba(167,169,174,0.15)]": selected,
"text-custom-text-400": !selected, return (
}) <Tab.List
} as="div"
> className="relative border-[0.5px] border-custom-border-200 rounded bg-custom-background-80 grid"
{tab.label} style={{
</Tab> gridTemplateColumns: `repeat(${tabsList.length}, 1fr)`,
))} }}
</Tab.List> >
); <div
className={cn("absolute bg-custom-background-100 rounded transition-all duration-500 ease-in-out", {
// right shadow
"shadow-[2px_0_8px_rgba(167,169,174,0.15)]": selectedTabIndex !== tabsList.length - 1,
// left shadow
"shadow-[-2px_0_8px_rgba(167,169,174,0.15)]": selectedTabIndex !== 0,
})}
style={{
height: "calc(100% - 1px)",
width: `${100 / tabsList.length}%`,
transform: `translateX(${selectedTabIndex * 100}%)`,
}}
/>
{tabsList.map((tab) => (
<Tab
key={tab.key}
className={cn(
"relative z-[1] font-semibold text-xs rounded py-1.5 text-custom-text-400 focus:outline-none",
"transition duration-500",
{
"text-custom-text-100 bg-custom-background-100": selectedTab === tab.key,
"hover:text-custom-text-300": selectedTab !== tab.key,
// // right shadow
// "shadow-[2px_0_8px_rgba(167,169,174,0.15)]": selectedTabIndex !== tabsList.length - 1,
// // left shadow
// "shadow-[-2px_0_8px_rgba(167,169,174,0.15)]": selectedTabIndex !== 0,
}
)}
>
<span className="scale-110">{tab.label}</span>
</Tab>
))}
</Tab.List>
);
});

View File

@ -84,16 +84,18 @@ export const IssuesByPriorityWidget: React.FC<WidgetProps> = observer((props) =>
filters, filters,
}); });
const filterDates = getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "none");
fetchWidgetStats(workspaceSlug, dashboardId, { fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY, widget_key: WIDGET_KEY,
target_date: getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "this_week"), ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}),
}); });
}; };
useEffect(() => { useEffect(() => {
const filterDates = getCustomDates(widgetDetails?.widget_filters.target_date ?? "none");
fetchWidgetStats(workspaceSlug, dashboardId, { fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY, 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 // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
@ -129,21 +131,15 @@ export const IssuesByPriorityWidget: React.FC<WidgetProps> = observer((props) =>
return ( return (
<div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full py-6 hover:shadow-custom-shadow-4xl duration-300 overflow-hidden min-h-96 flex flex-col"> <div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full py-6 hover:shadow-custom-shadow-4xl duration-300 overflow-hidden min-h-96 flex flex-col">
<div className="flex items-start justify-between gap-2 pl-7 pr-6"> <div className="flex items-center justify-between gap-2 pl-7 pr-6">
<div> <Link
<Link href={`/${workspaceSlug}/workspace-views/assigned`}
href={`/${workspaceSlug}/workspace-views/assigned`} className="text-lg font-semibold text-custom-text-300 hover:underline"
className="text-lg font-semibold text-custom-text-300 hover:underline" >
> Assigned by priority
Assigned by priority </Link>
</Link>
<p className="mt-3 text-xs font-medium text-custom-text-300">
Filtered by{" "}
<span className="border-[0.5px] border-custom-border-300 rounded py-1 px-2 ml-0.5">Due date</span>
</p>
</div>
<DurationFilterDropdown <DurationFilterDropdown
value={widgetDetails.widget_filters.target_date ?? "this_week"} value={widgetDetails.widget_filters.target_date ?? "none"}
onChange={(val) => onChange={(val) =>
handleUpdateFilters({ handleUpdateFilters({
target_date: val, target_date: val,

View File

@ -43,17 +43,19 @@ export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props)
filters, filters,
}); });
const filterDates = getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "none");
fetchWidgetStats(workspaceSlug, dashboardId, { fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY, 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 // fetch widget stats
useEffect(() => { useEffect(() => {
const filterDates = getCustomDates(widgetDetails?.widget_filters.target_date ?? "none");
fetchWidgetStats(workspaceSlug, dashboardId, { fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY, 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 // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
@ -72,14 +74,14 @@ export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props)
startedCount > 0 startedCount > 0
? "started" ? "started"
: unStartedCount > 0 : unStartedCount > 0
? "unstarted" ? "unstarted"
: backlogCount > 0 : backlogCount > 0
? "backlog" ? "backlog"
: completedCount > 0 : completedCount > 0
? "completed" ? "completed"
: canceledCount > 0 : canceledCount > 0
? "cancelled" ? "cancelled"
: null; : null;
setActiveStateGroup(stateGroup); setActiveStateGroup(stateGroup);
setDefaultStateGroup(stateGroup); setDefaultStateGroup(stateGroup);
@ -128,21 +130,15 @@ export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props)
return ( return (
<div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full py-6 hover:shadow-custom-shadow-4xl duration-300 overflow-hidden min-h-96 flex flex-col"> <div className="bg-custom-background-100 rounded-xl border-[0.5px] border-custom-border-200 w-full py-6 hover:shadow-custom-shadow-4xl duration-300 overflow-hidden min-h-96 flex flex-col">
<div className="flex items-start justify-between gap-2 pl-7 pr-6"> <div className="flex items-center justify-between gap-2 pl-7 pr-6">
<div> <Link
<Link href={`/${workspaceSlug}/workspace-views/assigned`}
href={`/${workspaceSlug}/workspace-views/assigned`} className="text-lg font-semibold text-custom-text-300 hover:underline"
className="text-lg font-semibold text-custom-text-300 hover:underline" >
> Assigned by state
Assigned by state </Link>
</Link>
<p className="mt-3 text-xs font-medium text-custom-text-300">
Filtered by{" "}
<span className="border-[0.5px] border-custom-border-300 rounded py-1 px-2 ml-0.5">Due date</span>
</p>
</div>
<DurationFilterDropdown <DurationFilterDropdown
value={widgetDetails.widget_filters.target_date ?? "this_week"} value={widgetDetails.widget_filters.target_date ?? "none"}
onChange={(val) => onChange={(val) =>
handleUpdateFilters({ handleUpdateFilters({
target_date: val, target_date: val,

View File

@ -62,16 +62,18 @@ export const WorkspaceDashboardView = observer(() => {
{homeDashboardId && joinedProjectIds ? ( {homeDashboardId && joinedProjectIds ? (
<> <>
{joinedProjectIds.length > 0 ? ( {joinedProjectIds.length > 0 ? (
<div className="flex h-full w-full flex-col space-y-7 overflow-y-auto bg-custom-background-90 p-7"> <>
<IssuePeekOverview /> <IssuePeekOverview />
{currentUser && <UserGreetingsView user={currentUser} />} <div className="space-y-7 p-7 bg-custom-background-90 h-full w-full flex flex-col overflow-y-auto">
{currentUser && !currentUser.is_tour_completed && ( {currentUser && <UserGreetingsView user={currentUser} />}
<div className="fixed left-0 top-0 z-20 grid h-full w-full place-items-center bg-custom-backdrop bg-opacity-50 transition-opacity"> {currentUser && !currentUser.is_tour_completed && (
<TourRoot onComplete={handleTourCompleted} /> <div className="fixed left-0 top-0 z-20 grid h-full w-full place-items-center bg-custom-backdrop bg-opacity-50 transition-opacity">
</div> <TourRoot onComplete={handleTourCompleted} />
)} </div>
<DashboardWidgets /> )}
</div> <DashboardWidgets />
</div>
</>
) : ( ) : (
<EmptyState <EmptyState
image={emptyStateImage} image={emptyStateImage}

View File

@ -121,21 +121,25 @@ export const DURATION_FILTER_OPTIONS: {
key: TDurationFilterOptions; key: TDurationFilterOptions;
label: string; label: string;
}[] = [ }[] = [
{
key: "none",
label: "None",
},
{ {
key: "today", key: "today",
label: "Today", label: "Due today",
}, },
{ {
key: "this_week", key: "this_week",
label: "This week", label: " Due this week",
}, },
{ {
key: "this_month", key: "this_month",
label: "This month", label: "Due this month",
}, },
{ {
key: "this_year", key: "this_year",
label: "This year", label: "Due this year",
}, },
]; ];
@ -152,7 +156,7 @@ export const PROJECT_BACKGROUND_COLORS = [
]; ];
// assigned and created issues widgets tabs list // assigned and created issues widgets tabs list
export const ISSUES_TABS_LIST: { export const FILTERED_ISSUES_TABS_LIST: {
key: TIssuesListTypes; key: TIssuesListTypes;
label: string; label: string;
}[] = [ }[] = [
@ -170,7 +174,27 @@ export const ISSUES_TABS_LIST: {
}, },
]; ];
// assigned and created issues widgets tabs list
export const UNFILTERED_ISSUES_TABS_LIST: {
key: TIssuesListTypes;
label: string;
}[] = [
{
key: "pending",
label: "Pending",
},
{
key: "completed",
label: "Marked completed",
},
];
export const ASSIGNED_ISSUES_EMPTY_STATES = { export const ASSIGNED_ISSUES_EMPTY_STATES = {
pending: {
title: "Issues assigned to you that are pending\nwill show up here.",
darkImage: UpcomingIssuesDark,
lightImage: UpcomingIssuesLight,
},
upcoming: { upcoming: {
title: "Upcoming issues assigned to\nyou will show up here.", title: "Upcoming issues assigned to\nyou will show up here.",
darkImage: UpcomingIssuesDark, darkImage: UpcomingIssuesDark,
@ -189,6 +213,11 @@ export const ASSIGNED_ISSUES_EMPTY_STATES = {
}; };
export const CREATED_ISSUES_EMPTY_STATES = { export const CREATED_ISSUES_EMPTY_STATES = {
pending: {
title: "Issues created by you that are pending\nwill show up here.",
darkImage: UpcomingIssuesDark,
lightImage: UpcomingIssuesLight,
},
upcoming: { upcoming: {
title: "Upcoming issues you created\nwill show up here.", title: "Upcoming issues you created\nwill show up here.",
darkImage: UpcomingIssuesDark, darkImage: UpcomingIssuesDark,

View File

@ -9,6 +9,8 @@ export const getCustomDates = (duration: TDurationFilterOptions): string => {
let firstDay, lastDay; let firstDay, lastDay;
switch (duration) { switch (duration) {
case "none":
return "";
case "today": case "today":
firstDay = renderFormattedPayloadDate(today); firstDay = renderFormattedPayloadDate(today);
lastDay = renderFormattedPayloadDate(today); lastDay = renderFormattedPayloadDate(today);
@ -32,7 +34,9 @@ export const getRedirectionFilters = (type: TIssuesListTypes): string => {
const today = renderFormattedPayloadDate(new Date()); const today = renderFormattedPayloadDate(new Date());
const filterParams = const filterParams =
type === "upcoming" type === "pending"
? "?state_group=backlog,unstarted,started"
: type === "upcoming"
? `?target_date=${today};after` ? `?target_date=${today};after`
: type === "overdue" : type === "overdue"
? `?target_date=${today};before` ? `?target_date=${today};before`