chore: dashboard empty states, fetching logic (#3455)

* chore: remove one-time fetching

* chore: update empty states

* chore: updated icons

* chore: empty state content
This commit is contained in:
Aaryan Khandelwal 2024-01-24 19:41:02 +05:30 committed by GitHub
parent 9d9d703c62
commit 53b41481a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
48 changed files with 386 additions and 2640 deletions

View file

@ -50,32 +50,36 @@ export const AssignedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
};
useEffect(() => {
if (!widgetDetails) return;
const filterDates = getCustomDates(widgetDetails?.widget_filters.target_date ?? "this_week");
const filterDates = getCustomDates(widgetDetails.widget_filters.target_date ?? "this_week");
if (!widgetStats)
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
issue_type: widgetDetails.widget_filters.tab ?? "upcoming",
target_date: filterDates,
expand: "issue_relation",
});
}, [dashboardId, fetchWidgetStats, widgetDetails, widgetStats, workspaceSlug]);
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
issue_type: widgetDetails?.widget_filters.tab ?? "upcoming",
target_date: filterDates,
expand: "issue_relation",
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const filterParams = getRedirectionFilters(widgetDetails?.widget_filters.tab ?? "upcoming");
if (!widgetDetails || !widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;
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">
<div className="flex items-center justify-between gap-2 p-6 pl-7">
<Link
href={`/${workspaceSlug}/workspace-views/assigned/${filterParams}`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
All issues assigned
</Link>
<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>
<Link
href={`/${workspaceSlug}/workspace-views/assigned/${filterParams}`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
Assigned to you
</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
value={widgetDetails.widget_filters.target_date ?? "this_week"}
onChange={(val) =>
@ -97,11 +101,10 @@ export const AssignedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
<div className="px-6">
<TabsList />
</div>
<Tab.Panels as="div" className="mt-7 h-full">
<Tab.Panels as="div" className="h-full">
{ISSUES_TABS_LIST.map((tab) => (
<Tab.Panel key={tab.key} as="div" className="h-full flex flex-col">
<WidgetIssuesList
filter={widgetDetails.widget_filters.target_date}
issues={widgetStats.issues}
tab={tab.key}
totalIssues={widgetStats.count}

View file

@ -49,29 +49,33 @@ export const CreatedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
};
useEffect(() => {
if (!widgetDetails) return;
if (!widgetStats)
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
issue_type: widgetDetails.widget_filters.tab ?? "upcoming",
target_date: getCustomDates(widgetDetails.widget_filters.target_date ?? "this_week"),
});
}, [dashboardId, fetchWidgetStats, widgetDetails, widgetStats, workspaceSlug]);
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
issue_type: widgetDetails?.widget_filters.tab ?? "upcoming",
target_date: getCustomDates(widgetDetails?.widget_filters.target_date ?? "this_week"),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const filterParams = getRedirectionFilters(widgetDetails?.widget_filters.tab ?? "upcoming");
if (!widgetDetails || !widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;
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">
<div className="flex items-center justify-between gap-2 p-6 pl-7">
<Link
href={`/${workspaceSlug}/workspace-views/created/${filterParams}`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
All issues created
</Link>
<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>
<Link
href={`/${workspaceSlug}/workspace-views/created/${filterParams}`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
Created by you
</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
value={widgetDetails.widget_filters.target_date ?? "this_week"}
onChange={(val) =>
@ -93,11 +97,10 @@ export const CreatedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
<div className="px-6">
<TabsList />
</div>
<Tab.Panels as="div" className="mt-7 h-full">
<Tab.Panels as="div" className="h-full">
{ISSUES_TABS_LIST.map((tab) => (
<Tab.Panel as="div" className="h-full flex flex-col">
<WidgetIssuesList
filter={widgetDetails.widget_filters.target_date}
issues={widgetStats.issues}
tab={tab.key}
totalIssues={widgetStats.count}

View file

@ -1,19 +1,16 @@
import Image from "next/image";
import { useTheme } from "next-themes";
// helpers
import { cn } from "helpers/common.helper";
// types
import { TDurationFilterOptions, TIssuesListTypes } from "@plane/types";
import { TIssuesListTypes } from "@plane/types";
// constants
import { ASSIGNED_ISSUES_EMPTY_STATES } from "constants/dashboard";
type Props = {
filter: TDurationFilterOptions;
type: TIssuesListTypes;
};
export const AssignedIssuesEmptyState: React.FC<Props> = (props) => {
const { filter, type } = props;
const { type } = props;
// next-themes
const { resolvedTheme } = useTheme();
@ -21,22 +18,13 @@ export const AssignedIssuesEmptyState: React.FC<Props> = (props) => {
const image = resolvedTheme === "dark" ? typeDetails.darkImage : typeDetails.lightImage;
// TODO: update empty state logic to use a general component
return (
<div className="text-center space-y-10 mt-16 flex flex-col items-center">
<p className="text-sm font-medium text-custom-text-300">{typeDetails.title(filter)}</p>
<div
className={cn("w-1/2 h-1/3 p-1.5 pb-0 rounded-t-md", {
"border border-custom-border-200": resolvedTheme === "dark",
})}
style={{
background:
resolvedTheme === "light"
? "linear-gradient(135deg, rgba(235, 243, 255, 0.45) 3.57%, rgba(99, 161, 255, 0.24) 94.16%)"
: "",
}}
>
<div className="text-center space-y-6 flex flex-col items-center">
<div className="h-24 w-24">
<Image src={image} className="w-full h-full" alt="Assigned issues" />
</div>
<p className="text-sm font-medium text-custom-text-300 whitespace-pre-line">{typeDetails.title}</p>
</div>
);
};

View file

@ -1,19 +1,16 @@
import Image from "next/image";
import { useTheme } from "next-themes";
// helpers
import { cn } from "helpers/common.helper";
// types
import { TDurationFilterOptions, TIssuesListTypes } from "@plane/types";
import { TIssuesListTypes } from "@plane/types";
// constants
import { CREATED_ISSUES_EMPTY_STATES } from "constants/dashboard";
type Props = {
filter: TDurationFilterOptions;
type: TIssuesListTypes;
};
export const CreatedIssuesEmptyState: React.FC<Props> = (props) => {
const { filter, type } = props;
const { type } = props;
// next-themes
const { resolvedTheme } = useTheme();
@ -22,21 +19,11 @@ export const CreatedIssuesEmptyState: React.FC<Props> = (props) => {
const image = resolvedTheme === "dark" ? typeDetails.darkImage : typeDetails.lightImage;
return (
<div className="text-center space-y-10 mt-16 flex flex-col items-center">
<p className="text-sm font-medium text-custom-text-300">{typeDetails.title(filter)}</p>
<div
className={cn("w-1/2 h-1/3 p-1.5 pb-0 rounded-t-md", {
"border border-custom-border-200": resolvedTheme === "dark",
})}
style={{
background:
resolvedTheme === "light"
? "linear-gradient(135deg, rgba(235, 243, 255, 0.45) 3.57%, rgba(99, 161, 255, 0.24) 94.16%)"
: "",
}}
>
<Image src={image} className="w-full h-full" alt="Created issues" />
<div className="text-center space-y-6 flex flex-col items-center">
<div className="h-24 w-24">
<Image src={image} className="w-full h-full" alt="Assigned issues" />
</div>
<p className="text-sm font-medium text-custom-text-300 whitespace-pre-line">{typeDetails.title}</p>
</div>
);
};

View file

@ -3,43 +3,23 @@ import { useTheme } from "next-themes";
// assets
import DarkImage from "public/empty-state/dashboard/dark/issues-by-priority.svg";
import LightImage from "public/empty-state/dashboard/light/issues-by-priority.svg";
// helpers
import { cn } from "helpers/common.helper";
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
// types
import { TDurationFilterOptions } from "@plane/types";
type Props = {
filter: TDurationFilterOptions;
};
export const IssuesByPriorityEmptyState: React.FC<Props> = (props) => {
const { filter } = props;
export const IssuesByPriorityEmptyState = () => {
// next-themes
const { resolvedTheme } = useTheme();
const image = resolvedTheme === "dark" ? DarkImage : LightImage;
return (
<div className="text-center space-y-10 mt-16 flex flex-col items-center">
<p className="text-sm font-medium text-custom-text-300">
No assigned issues {replaceUnderscoreIfSnakeCase(filter)}.
</p>
<div
className={cn("w-1/2 h-1/3 p-1.5 pb-0 rounded-t-md", {
"border border-custom-border-200": resolvedTheme === "dark",
})}
style={{
background:
resolvedTheme === "light"
? "linear-gradient(135deg, rgba(235, 243, 255, 0.45) 3.57%, rgba(99, 161, 255, 0.24) 94.16%)"
: "",
}}
>
<Image
src={resolvedTheme === "dark" ? DarkImage : LightImage}
className="w-full h-full"
alt="Issues by priority"
/>
<div className="text-center space-y-6 flex flex-col items-center">
<div className="h-24 w-24">
<Image src={image} className="w-full h-full" alt="Issues by state group" />
</div>
<p className="text-sm font-medium text-custom-text-300">
Issues assigned to you, broken down by
<br />
priority will show up here.
</p>
</div>
);
};

View file

@ -3,43 +3,23 @@ import { useTheme } from "next-themes";
// assets
import DarkImage from "public/empty-state/dashboard/dark/issues-by-state-group.svg";
import LightImage from "public/empty-state/dashboard/light/issues-by-state-group.svg";
// helpers
import { cn } from "helpers/common.helper";
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
// types
import { TDurationFilterOptions } from "@plane/types";
type Props = {
filter: TDurationFilterOptions;
};
export const IssuesByStateGroupEmptyState: React.FC<Props> = (props) => {
const { filter } = props;
export const IssuesByStateGroupEmptyState = () => {
// next-themes
const { resolvedTheme } = useTheme();
const image = resolvedTheme === "dark" ? DarkImage : LightImage;
return (
<div className="text-center space-y-10 mt-16 flex flex-col items-center">
<p className="text-sm font-medium text-custom-text-300">
No assigned issues {replaceUnderscoreIfSnakeCase(filter)}.
</p>
<div
className={cn("w-1/2 h-1/3 p-1.5 pb-0 rounded-t-md", {
"border border-custom-border-200": resolvedTheme === "dark",
})}
style={{
background:
resolvedTheme === "light"
? "linear-gradient(135deg, rgba(235, 243, 255, 0.45) 3.57%, rgba(99, 161, 255, 0.24) 94.16%)"
: "",
}}
>
<Image
src={resolvedTheme === "dark" ? DarkImage : LightImage}
className="w-full h-full"
alt="Issues by state group"
/>
<div className="text-center space-y-6 flex flex-col items-center">
<div className="h-24 w-24">
<Image src={image} className="w-full h-full" alt="Issues by state group" />
</div>
<p className="text-sm font-medium text-custom-text-300">
Issue assigned to you, broken down by state,
<br />
will show up here.
</p>
</div>
);
};

View file

@ -3,40 +3,23 @@ import { useTheme } from "next-themes";
// assets
import DarkImage from "public/empty-state/dashboard/dark/recent-activity.svg";
import LightImage from "public/empty-state/dashboard/light/recent-activity.svg";
// helpers
import { cn } from "helpers/common.helper";
type Props = {};
export const RecentActivityEmptyState: React.FC<Props> = (props) => {
const {} = props;
export const RecentActivityEmptyState = () => {
// next-themes
const { resolvedTheme } = useTheme();
const image = resolvedTheme === "dark" ? DarkImage : LightImage;
return (
<div className="text-center space-y-10 mt-16 flex flex-col items-center">
<p className="text-sm font-medium text-custom-text-300">
Feels new, go and explore our tool in depth and come back
<br />
to see your activity.
</p>
<div
className={cn("w-3/5 h-1/3 p-1.5 pb-0 rounded-t-md", {
"border border-custom-border-200": resolvedTheme === "dark",
})}
style={{
background:
resolvedTheme === "light"
? "linear-gradient(135deg, rgba(235, 243, 255, 0.45) 3.57%, rgba(99, 161, 255, 0.24) 94.16%)"
: "",
}}
>
<Image
src={resolvedTheme === "dark" ? DarkImage : LightImage}
className="w-full h-full"
alt="Issues by priority"
/>
<div className="text-center space-y-6 flex flex-col items-center">
<div className="h-24 w-24">
<Image src={image} className="w-full h-full" alt="Issues by state group" />
</div>
<p className="text-sm font-medium text-custom-text-300">
All your issue activities across
<br />
projects will show up here.
</p>
</div>
);
};

View file

@ -1,39 +1,38 @@
import Image from "next/image";
import { useTheme } from "next-themes";
// assets
import DarkImage from "public/empty-state/dashboard/dark/recent-collaborators.svg";
import LightImage from "public/empty-state/dashboard/light/recent-collaborators.svg";
// helpers
import { cn } from "helpers/common.helper";
import DarkImage1 from "public/empty-state/dashboard/dark/recent-collaborators-1.svg";
import DarkImage2 from "public/empty-state/dashboard/dark/recent-collaborators-2.svg";
import DarkImage3 from "public/empty-state/dashboard/dark/recent-collaborators-3.svg";
import LightImage1 from "public/empty-state/dashboard/light/recent-collaborators-1.svg";
import LightImage2 from "public/empty-state/dashboard/light/recent-collaborators-2.svg";
import LightImage3 from "public/empty-state/dashboard/light/recent-collaborators-3.svg";
type Props = {};
export const RecentCollaboratorsEmptyState: React.FC<Props> = (props) => {
const {} = props;
export const RecentCollaboratorsEmptyState = () => {
// next-themes
const { resolvedTheme } = useTheme();
const image1 = resolvedTheme === "dark" ? DarkImage1 : LightImage1;
const image2 = resolvedTheme === "dark" ? DarkImage2 : LightImage2;
const image3 = resolvedTheme === "dark" ? DarkImage3 : LightImage3;
return (
<div className="mt-7 px-7 flex justify-between gap-16">
<p className="text-sm font-medium text-custom-text-300">
People are excited to work with you, once they do you will find your frequent collaborators here.
<div className="mt-7 mb-16 px-36 flex flex-col lg:flex-row items-center justify-between gap-x-24 gap-y-16">
<p className="text-sm font-medium text-custom-text-300 lg:w-2/5 flex-shrink-0 text-center lg:text-left">
Compare your activities with the top
<br />
seven in your project.
</p>
<div
className={cn("w-3/5 h-1/3 p-1.5 pb-0 rounded-t-md flex-shrink-0 self-end", {
"border border-custom-border-200": resolvedTheme === "dark",
})}
style={{
background:
resolvedTheme === "light"
? "linear-gradient(135deg, rgba(235, 243, 255, 0.45) 3.57%, rgba(99, 161, 255, 0.24) 94.16%)"
: "",
}}
>
<Image
src={resolvedTheme === "dark" ? DarkImage : LightImage}
className="w-full h-full"
alt="Recent collaborators"
/>
<div className="flex items-center justify-evenly gap-20 lg:w-3/5 flex-shrink-0">
<div className="h-24 w-24 flex-shrink-0">
<Image src={image1} className="w-full h-full" alt="Recent collaborators" />
</div>
<div className="h-24 w-24 flex-shrink-0">
<Image src={image2} className="w-full h-full" alt="Recent collaborators" />
</div>
<div className="h-24 w-24 flex-shrink-0 hidden xl:block">
<Image src={image3} className="w-full h-full" alt="Recent collaborators" />
</div>
</div>
</div>
);

View file

@ -19,10 +19,9 @@ import { Loader, getButtonStyling } from "@plane/ui";
import { cn } from "helpers/common.helper";
import { getRedirectionFilters } from "helpers/dashboard.helper";
// types
import { TDurationFilterOptions, TIssue, TIssuesListTypes } from "@plane/types";
import { TIssue, TIssuesListTypes } from "@plane/types";
export type WidgetIssuesListProps = {
filter: TDurationFilterOptions | undefined;
isLoading: boolean;
issues: TIssue[];
tab: TIssuesListTypes;
@ -32,7 +31,7 @@ export type WidgetIssuesListProps = {
};
export const WidgetIssuesList: React.FC<WidgetIssuesListProps> = (props) => {
const { filter, isLoading, issues, tab, totalIssues, type, workspaceSlug } = props;
const { isLoading, issues, tab, totalIssues, type, workspaceSlug } = props;
// store hooks
const { setPeekIssue } = useIssueDetail();
@ -62,7 +61,7 @@ export const WidgetIssuesList: React.FC<WidgetIssuesListProps> = (props) => {
<>
<div className="h-full">
{isLoading ? (
<Loader className="mx-6 mt-2 space-y-4">
<Loader className="mt-7 mx-6 space-y-4">
<Loader.Item height="25px" />
<Loader.Item height="25px" />
<Loader.Item height="25px" />
@ -70,7 +69,7 @@ export const WidgetIssuesList: React.FC<WidgetIssuesListProps> = (props) => {
</Loader>
) : issues.length > 0 ? (
<>
<div className="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">
<h6
className={cn("pl-1 flex items-center gap-1 col-span-4", {
"col-span-6": type === "assigned" && tab === "completed",
@ -105,9 +104,9 @@ export const WidgetIssuesList: React.FC<WidgetIssuesListProps> = (props) => {
</div>
</>
) : (
<div className="h-full grid items-end">
{type === "assigned" && <AssignedIssuesEmptyState filter={filter ?? "this_week"} type={tab} />}
{type === "created" && <CreatedIssuesEmptyState filter={filter ?? "this_week"} type={tab} />}
<div className="h-full grid place-items-center">
{type === "assigned" && <AssignedIssuesEmptyState type={tab} />}
{type === "created" && <CreatedIssuesEmptyState type={tab} />}
</div>
)}
</div>

View file

@ -91,14 +91,12 @@ export const IssuesByPriorityWidget: React.FC<WidgetProps> = observer((props) =>
};
useEffect(() => {
if (!widgetDetails) return;
if (!widgetStats)
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
target_date: getCustomDates(widgetDetails.widget_filters.target_date ?? "this_week"),
});
}, [dashboardId, fetchWidgetStats, widgetDetails, widgetStats, workspaceSlug]);
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
target_date: getCustomDates(widgetDetails?.widget_filters.target_date ?? "this_week"),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!widgetDetails || !widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;
@ -130,14 +128,20 @@ export const IssuesByPriorityWidget: React.FC<WidgetProps> = observer((props) =>
};
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">
<div className="flex items-center justify-between gap-2 pl-7 pr-6">
<Link
href={`/${workspaceSlug}/workspace-views/assigned`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
Priority of assigned issues
</Link>
<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">
<div className="flex items-start justify-between gap-2 pl-7 pr-6">
<div>
<Link
href={`/${workspaceSlug}/workspace-views/assigned`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
Assigned by priority
</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
value={widgetDetails.widget_filters.target_date ?? "this_week"}
onChange={(val) =>
@ -196,8 +200,8 @@ export const IssuesByPriorityWidget: React.FC<WidgetProps> = observer((props) =>
</div>
</div>
) : (
<div className="h-full grid items-end">
<IssuesByPriorityEmptyState filter={widgetDetails.widget_filters.target_date ?? "this_week"} />
<div className="h-full grid place-items-center">
<IssuesByPriorityEmptyState />
</div>
)}
</div>

View file

@ -51,14 +51,12 @@ export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props)
// fetch widget stats
useEffect(() => {
if (!widgetDetails) return;
if (!widgetStats)
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
target_date: getCustomDates(widgetDetails.widget_filters.target_date ?? "this_week"),
});
}, [dashboardId, fetchWidgetStats, widgetDetails, widgetStats, workspaceSlug]);
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
target_date: getCustomDates(widgetDetails?.widget_filters.target_date ?? "this_week"),
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// set active group for center metric
useEffect(() => {
@ -129,14 +127,20 @@ export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props)
};
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">
<div className="flex items-center justify-between gap-2 pl-7 pr-6">
<Link
href={`/${workspaceSlug}/workspace-views/assigned`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
State of assigned issues
</Link>
<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">
<div className="flex items-start justify-between gap-2 pl-7 pr-6">
<div>
<Link
href={`/${workspaceSlug}/workspace-views/assigned`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
Assigned by state
</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
value={widgetDetails.widget_filters.target_date ?? "this_week"}
onChange={(val) =>
@ -204,8 +208,8 @@ export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props)
</div>
</div>
) : (
<div className="h-full grid items-end">
<IssuesByStateGroupEmptyState filter={widgetDetails.widget_filters.target_date ?? "this_week"} />
<div className="h-full grid place-items-center">
<IssuesByStateGroupEmptyState />
</div>
)}
</div>

View file

@ -54,11 +54,11 @@ export const OverviewStatsWidget: React.FC<WidgetProps> = observer((props) => {
];
useEffect(() => {
if (!widgetStats)
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
});
}, [dashboardId, fetchWidgetStats, widgetStats, workspaceSlug]);
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;

View file

@ -25,18 +25,18 @@ export const RecentActivityWidget: React.FC<WidgetProps> = observer((props) => {
const widgetStats = getWidgetStats<TRecentActivityWidgetResponse[]>(workspaceSlug, dashboardId, WIDGET_KEY);
useEffect(() => {
if (!widgetStats)
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
});
}, [dashboardId, fetchWidgetStats, widgetStats, workspaceSlug]);
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;
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">
<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 min-h-96">
<Link href="/profile/activity" className="text-lg font-semibold text-custom-text-300 mx-7 hover:underline">
My activity
Your issue activities
</Link>
{widgetStats.length > 0 ? (
<div className="space-y-6 mt-4 mx-7">
@ -85,7 +85,7 @@ export const RecentActivityWidget: React.FC<WidgetProps> = observer((props) => {
))}
</div>
) : (
<div className="h-full grid items-end">
<div className="h-full grid place-items-center">
<RecentActivityEmptyState />
</div>
)}

View file

@ -57,18 +57,21 @@ export const RecentCollaboratorsWidget: React.FC<WidgetProps> = observer((props)
const widgetStats = getWidgetStats<TRecentCollaboratorsWidgetResponse[]>(workspaceSlug, dashboardId, WIDGET_KEY);
useEffect(() => {
if (!widgetStats)
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
});
}, [dashboardId, fetchWidgetStats, widgetStats, workspaceSlug]);
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;
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">
<div className="flex items-center justify-between gap-2 px-7 pt-6">
<h4 className="text-lg font-semibold text-custom-text-300">Collaborators</h4>
<div className="px-7 pt-6">
<h4 className="text-lg font-semibold text-custom-text-300">Most active members</h4>
<p className="mt-2 text-xs font-medium text-custom-text-300">
Top eight active members in your project by last activity
</p>
</div>
{widgetStats.length > 1 ? (
<div className="mt-7 mb-6 grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-6 xl:grid-cols-8 gap-2 gap-y-8">
@ -82,7 +85,7 @@ export const RecentCollaboratorsWidget: React.FC<WidgetProps> = observer((props)
))}
</div>
) : (
<div className="h-full grid items-end">
<div className="h-full grid place-items-center">
<RecentCollaboratorsEmptyState />
</div>
)}

View file

@ -81,21 +81,21 @@ export const RecentProjectsWidget: React.FC<WidgetProps> = observer((props) => {
const canCreateProject = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN;
useEffect(() => {
if (!widgetStats)
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
});
}, [dashboardId, fetchWidgetStats, widgetStats, workspaceSlug]);
fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;
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">
<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 min-h-96">
<Link
href={`/${workspaceSlug}/projects`}
className="text-lg font-semibold text-custom-text-300 mx-7 hover:underline"
>
My projects
Your projects
</Link>
<div className="space-y-8 mt-4 mx-7">
{canCreateProject && (