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:
parent
9d9d703c62
commit
53b41481a2
48 changed files with 386 additions and 2640 deletions
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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} />;
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue