fix: bugs in dashboard widgets (#3420)

* fix: bugs in dashboard widgets

* chore: updated view all issues button

* refactor: make use of getWidgetDetails and getWidgetStats computed functions

* fix: widgets redirection

* fix: build errors
This commit is contained in:
Aaryan Khandelwal 2024-01-22 20:50:30 +05:30 committed by GitHub
parent fd5326dec6
commit 2986769f28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 132 additions and 105 deletions

View File

@ -1,6 +1,6 @@
import * as React from "react"; import * as React from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom";
// react-poppper // react-popper
import { usePopper } from "react-popper"; import { usePopper } from "react-popper";
// hooks // hooks
import { useDropdownKeyDown } from "../hooks/use-dropdown-key-down"; import { useDropdownKeyDown } from "../hooks/use-dropdown-key-down";

View File

@ -50,7 +50,7 @@ export const DashboardWidgets = observer(() => {
// if the widget is full width, return it in a 2 column grid // if the widget is full width, return it in a 2 column grid
if (widget.fullWidth) if (widget.fullWidth)
return ( return (
<div className="col-span-2"> <div className="lg:col-span-2">
<WidgetComponent dashboardId={homeDashboardId} workspaceSlug={workspaceSlug} /> <WidgetComponent dashboardId={homeDashboardId} workspaceSlug={workspaceSlug} />
</div> </div>
); );

View File

@ -26,15 +26,10 @@ export const AssignedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
// states // states
const [fetching, setFetching] = useState(false); const [fetching, setFetching] = useState(false);
// store hooks // store hooks
const { const { fetchWidgetStats, getWidgetDetails, getWidgetStats, updateDashboardWidgetFilters } = useDashboard();
fetchWidgetStats,
widgetDetails: allWidgetDetails,
widgetStats: allWidgetStats,
updateDashboardWidgetFilters,
} = useDashboard();
// derived values // derived values
const widgetDetails = allWidgetDetails?.[workspaceSlug]?.[dashboardId]?.find((w) => w.key === WIDGET_KEY); const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY);
const widgetStats = allWidgetStats?.[workspaceSlug]?.[dashboardId]?.[WIDGET_KEY] as TAssignedIssuesWidgetResponse; const widgetStats = getWidgetStats<TAssignedIssuesWidgetResponse>(workspaceSlug, dashboardId, WIDGET_KEY);
const handleUpdateFilters = async (filters: Partial<TAssignedIssuesWidgetFilters>) => { const handleUpdateFilters = async (filters: Partial<TAssignedIssuesWidgetFilters>) => {
if (!widgetDetails) return; if (!widgetDetails) return;
@ -48,8 +43,8 @@ export const AssignedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
fetchWidgetStats(workspaceSlug, dashboardId, { fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY, widget_key: WIDGET_KEY,
issue_type: widgetDetails.widget_filters.tab ?? "upcoming", issue_type: filters.tab ?? widgetDetails.widget_filters.tab ?? "upcoming",
target_date: getCustomDates(widgetDetails.widget_filters.target_date ?? "this_week"), target_date: getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "this_week"),
expand: "issue_relation", expand: "issue_relation",
}).finally(() => setFetching(false)); }).finally(() => setFetching(false));
}; };
@ -69,14 +64,18 @@ export const AssignedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
}, [dashboardId, fetchWidgetStats, widgetDetails, widgetStats, workspaceSlug]); }, [dashboardId, fetchWidgetStats, widgetDetails, widgetStats, workspaceSlug]);
const filterParams = getRedirectionFilters(widgetDetails?.widget_filters.tab ?? "upcoming"); const filterParams = getRedirectionFilters(widgetDetails?.widget_filters.tab ?? "upcoming");
const redirectionLink = `/${workspaceSlug}/workspace-views/assigned/${filterParams}`;
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"> <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">
<Link href={redirectionLink} className="flex items-center justify-between gap-2 p-6 pl-7"> <div className="flex items-center justify-between gap-2 p-6 pl-7">
<h4 className="text-lg font-semibold text-custom-text-300">All issues assigned</h4> <Link
href={`/${workspaceSlug}/workspace-views/assigned/${filterParams}`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
All issues assigned
</Link>
<DurationFilterDropdown <DurationFilterDropdown
value={widgetDetails.widget_filters.target_date ?? "this_week"} value={widgetDetails.widget_filters.target_date ?? "this_week"}
onChange={(val) => onChange={(val) =>
@ -85,7 +84,7 @@ export const AssignedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
}) })
} }
/> />
</Link> </div>
<Tab.Group <Tab.Group
as="div" as="div"
defaultIndex={ISSUES_TABS_LIST.findIndex((t) => t.key === widgetDetails.widget_filters.tab ?? "upcoming")} defaultIndex={ISSUES_TABS_LIST.findIndex((t) => t.key === widgetDetails.widget_filters.tab ?? "upcoming")}

View File

@ -26,15 +26,10 @@ export const CreatedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
// states // states
const [fetching, setFetching] = useState(false); const [fetching, setFetching] = useState(false);
// store hooks // store hooks
const { const { fetchWidgetStats, getWidgetDetails, getWidgetStats, updateDashboardWidgetFilters } = useDashboard();
fetchWidgetStats,
widgetDetails: allWidgetDetails,
widgetStats: allWidgetStats,
updateDashboardWidgetFilters,
} = useDashboard();
// derived values // derived values
const widgetDetails = allWidgetDetails?.[workspaceSlug]?.[dashboardId]?.find((w) => w.key === WIDGET_KEY); const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY);
const widgetStats = allWidgetStats?.[workspaceSlug]?.[dashboardId]?.[WIDGET_KEY] as TCreatedIssuesWidgetResponse; const widgetStats = getWidgetStats<TCreatedIssuesWidgetResponse>(workspaceSlug, dashboardId, WIDGET_KEY);
const handleUpdateFilters = async (filters: Partial<TCreatedIssuesWidgetFilters>) => { const handleUpdateFilters = async (filters: Partial<TCreatedIssuesWidgetFilters>) => {
if (!widgetDetails) return; if (!widgetDetails) return;
@ -48,8 +43,8 @@ export const CreatedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
fetchWidgetStats(workspaceSlug, dashboardId, { fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY, widget_key: WIDGET_KEY,
issue_type: widgetDetails.widget_filters.tab ?? "upcoming", issue_type: filters.tab ?? widgetDetails.widget_filters.tab ?? "upcoming",
target_date: getCustomDates(widgetDetails.widget_filters.target_date ?? "this_week"), target_date: getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "this_week"),
}).finally(() => setFetching(false)); }).finally(() => setFetching(false));
}; };
@ -65,14 +60,18 @@ export const CreatedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
}, [dashboardId, fetchWidgetStats, widgetDetails, widgetStats, workspaceSlug]); }, [dashboardId, fetchWidgetStats, widgetDetails, widgetStats, workspaceSlug]);
const filterParams = getRedirectionFilters(widgetDetails?.widget_filters.tab ?? "upcoming"); const filterParams = getRedirectionFilters(widgetDetails?.widget_filters.tab ?? "upcoming");
const redirectionLink = `/${workspaceSlug}/workspace-views/created/${filterParams}`;
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"> <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">
<Link href={redirectionLink} className="flex items-center justify-between gap-2 p-6 pl-7"> <div className="flex items-center justify-between gap-2 p-6 pl-7">
<h4 className="text-lg font-semibold text-custom-text-300">All issues created</h4> <Link
href={`/${workspaceSlug}/workspace-views/created/${filterParams}`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
All issues created
</Link>
<DurationFilterDropdown <DurationFilterDropdown
value={widgetDetails.widget_filters.target_date ?? "this_week"} value={widgetDetails.widget_filters.target_date ?? "this_week"}
onChange={(val) => onChange={(val) =>
@ -81,7 +80,7 @@ export const CreatedIssuesWidget: React.FC<WidgetProps> = observer((props) => {
}) })
} }
/> />
</Link> </div>
<Tab.Group <Tab.Group
as="div" as="div"
defaultIndex={ISSUES_TABS_LIST.findIndex((t) => t.key === widgetDetails.widget_filters.tab ?? "upcoming")} defaultIndex={ISSUES_TABS_LIST.findIndex((t) => t.key === widgetDetails.widget_filters.tab ?? "upcoming")}

View File

@ -16,6 +16,7 @@ export const DurationFilterDropdown: React.FC<Props> = (props) => {
return ( return (
<CustomMenu <CustomMenu
className="flex-shrink-0"
customButton={ customButton={
<div className="px-3 py-2 border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 focus:bg-custom-background-80 text-xs font-medium whitespace-nowrap rounded-md outline-none flex items-center gap-2"> <div className="px-3 py-2 border-[0.5px] border-custom-border-300 hover:bg-custom-background-80 focus:bg-custom-background-80 text-xs font-medium whitespace-nowrap rounded-md outline-none flex items-center gap-2">
{DURATION_FILTER_OPTIONS.find((option) => option.key === value)?.label} {DURATION_FILTER_OPTIONS.find((option) => option.key === value)?.label}
@ -23,16 +24,10 @@ export const DurationFilterDropdown: React.FC<Props> = (props) => {
</div> </div>
} }
placement="bottom-end" placement="bottom-end"
closeOnSelect
> >
{DURATION_FILTER_OPTIONS.map((option) => ( {DURATION_FILTER_OPTIONS.map((option) => (
<CustomMenu.MenuItem <CustomMenu.MenuItem key={option.key} onClick={() => onChange(option.key)}>
key={option.key}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onChange(option.key);
}}
>
{option.label} {option.label}
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
))} ))}

View File

@ -111,10 +111,13 @@ export const WidgetIssuesList: React.FC<WidgetIssuesListProps> = (props) => {
</div> </div>
)} )}
</div> </div>
{totalIssues > issues.length && ( {issues.length > 0 && (
<Link <Link
href={`/${workspaceSlug}/workspace-views/${type}/${filterParams}`} href={`/${workspaceSlug}/workspace-views/${type}/${filterParams}`}
className={cn(getButtonStyling("accent-primary", "sm"), "w-min my-3 mx-auto py-1 px-2 text-xs")} className={cn(
getButtonStyling("link-primary", "sm"),
"w-min my-3 mx-auto py-1 px-2 text-xs hover:bg-custom-primary-100/20"
)}
> >
View all issues View all issues
</Link> </Link>

View File

@ -72,14 +72,9 @@ const WIDGET_KEY = "issues_by_priority";
export const IssuesByPriorityWidget: React.FC<WidgetProps> = observer((props) => { export const IssuesByPriorityWidget: React.FC<WidgetProps> = observer((props) => {
const { dashboardId, workspaceSlug } = props; const { dashboardId, workspaceSlug } = props;
// store hooks // store hooks
const { const { fetchWidgetStats, getWidgetDetails, getWidgetStats, updateDashboardWidgetFilters } = useDashboard();
fetchWidgetStats, const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY);
widgetDetails: allWidgetDetails, const widgetStats = getWidgetStats<TIssuesByPriorityWidgetResponse[]>(workspaceSlug, dashboardId, WIDGET_KEY);
widgetStats: allWidgetStats,
updateDashboardWidgetFilters,
} = useDashboard();
const widgetDetails = allWidgetDetails?.[workspaceSlug]?.[dashboardId]?.find((w) => w.key === WIDGET_KEY);
const widgetStats = allWidgetStats?.[workspaceSlug]?.[dashboardId]?.[WIDGET_KEY] as TIssuesByPriorityWidgetResponse[];
const handleUpdateFilters = async (filters: Partial<TIssuesByPriorityWidgetFilters>) => { const handleUpdateFilters = async (filters: Partial<TIssuesByPriorityWidgetFilters>) => {
if (!widgetDetails) return; if (!widgetDetails) return;
@ -91,7 +86,7 @@ export const IssuesByPriorityWidget: React.FC<WidgetProps> = observer((props) =>
fetchWidgetStats(workspaceSlug, dashboardId, { fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY, widget_key: WIDGET_KEY,
target_date: getCustomDates(widgetDetails.widget_filters.target_date ?? "this_week"), target_date: getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "this_week"),
}); });
}; };
@ -135,12 +130,14 @@ export const IssuesByPriorityWidget: React.FC<WidgetProps> = observer((props) =>
}; };
return ( return (
<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">
href={`/${workspaceSlug}/workspace-views/assigned`}
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"> <div className="flex items-center justify-between gap-2 pl-7 pr-6">
<h4 className="text-lg font-semibold text-custom-text-300">Priority of assigned issues</h4> <Link
href={`/${workspaceSlug}/workspace-views/assigned`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
Priority of assigned issues
</Link>
<DurationFilterDropdown <DurationFilterDropdown
value={widgetDetails.widget_filters.target_date ?? "this_week"} value={widgetDetails.widget_filters.target_date ?? "this_week"}
onChange={(val) => onChange={(val) =>
@ -203,6 +200,6 @@ export const IssuesByPriorityWidget: React.FC<WidgetProps> = observer((props) =>
<IssuesByPriorityEmptyState filter={widgetDetails.widget_filters.target_date ?? "this_week"} /> <IssuesByPriorityEmptyState filter={widgetDetails.widget_filters.target_date ?? "this_week"} />
</div> </div>
)} )}
</Link> </div>
); );
}); });

View File

@ -25,21 +25,15 @@ const WIDGET_KEY = "issues_by_state_groups";
export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props) => { export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props) => {
const { dashboardId, workspaceSlug } = props; const { dashboardId, workspaceSlug } = props;
// states // states
const [activeStateGroup, setActiveStateGroup] = useState<TStateGroups>("started"); const [defaultStateGroup, setDefaultStateGroup] = useState<TStateGroups | null>(null);
const [activeStateGroup, setActiveStateGroup] = useState<TStateGroups | null>(null);
// router // router
const router = useRouter(); const router = useRouter();
// store hooks // store hooks
const { const { fetchWidgetStats, getWidgetDetails, getWidgetStats, updateDashboardWidgetFilters } = useDashboard();
fetchWidgetStats,
widgetDetails: allWidgetDetails,
widgetStats: allWidgetStats,
updateDashboardWidgetFilters,
} = useDashboard();
// derived values // derived values
const widgetDetails = allWidgetDetails?.[workspaceSlug]?.[dashboardId]?.find((w) => w.key === WIDGET_KEY); const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY);
const widgetStats = allWidgetStats?.[workspaceSlug]?.[dashboardId]?.[ const widgetStats = getWidgetStats<TIssuesByStateGroupsWidgetResponse[]>(workspaceSlug, dashboardId, WIDGET_KEY);
WIDGET_KEY
] as TIssuesByStateGroupsWidgetResponse[];
const handleUpdateFilters = async (filters: Partial<TIssuesByStateGroupsWidgetFilters>) => { const handleUpdateFilters = async (filters: Partial<TIssuesByStateGroupsWidgetFilters>) => {
if (!widgetDetails) return; if (!widgetDetails) return;
@ -51,10 +45,11 @@ export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props)
fetchWidgetStats(workspaceSlug, dashboardId, { fetchWidgetStats(workspaceSlug, dashboardId, {
widget_key: WIDGET_KEY, widget_key: WIDGET_KEY,
target_date: getCustomDates(widgetDetails.widget_filters.target_date ?? "this_week"), target_date: getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "this_week"),
}); });
}; };
// fetch widget stats
useEffect(() => { useEffect(() => {
if (!widgetDetails) return; if (!widgetDetails) return;
@ -65,6 +60,33 @@ export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props)
}); });
}, [dashboardId, fetchWidgetStats, widgetDetails, widgetStats, workspaceSlug]); }, [dashboardId, fetchWidgetStats, widgetDetails, widgetStats, workspaceSlug]);
// set active group for center metric
useEffect(() => {
if (!widgetStats) return;
const startedCount = widgetStats?.find((item) => item?.state === "started")?.count ?? 0;
const unStartedCount = widgetStats?.find((item) => item?.state === "unstarted")?.count ?? 0;
const backlogCount = widgetStats?.find((item) => item?.state === "backlog")?.count ?? 0;
const completedCount = widgetStats?.find((item) => item?.state === "completed")?.count ?? 0;
const canceledCount = widgetStats?.find((item) => item?.state === "cancelled")?.count ?? 0;
const stateGroup =
startedCount > 0
? "started"
: unStartedCount > 0
? "unstarted"
: backlogCount > 0
? "backlog"
: completedCount > 0
? "completed"
: canceledCount > 0
? "cancelled"
: null;
setActiveStateGroup(stateGroup);
setDefaultStateGroup(stateGroup);
}, [widgetStats]);
if (!widgetDetails || !widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />; if (!widgetDetails || !widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;
const totalCount = widgetStats?.reduce((acc, item) => acc + item?.count, 0); const totalCount = widgetStats?.reduce((acc, item) => acc + item?.count, 0);
@ -107,12 +129,14 @@ export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props)
}; };
return ( return (
<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">
href={`/${workspaceSlug?.toString()}/workspace-views/assigned`}
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"> <div className="flex items-center justify-between gap-2 pl-7 pr-6">
<h4 className="text-lg font-semibold text-custom-text-300">State of assigned issues</h4> <Link
href={`/${workspaceSlug}/workspace-views/assigned`}
className="text-lg font-semibold text-custom-text-300 hover:underline"
>
State of assigned issues
</Link>
<DurationFilterDropdown <DurationFilterDropdown
value={widgetDetails.widget_filters.target_date ?? "this_week"} value={widgetDetails.widget_filters.target_date ?? "this_week"}
onChange={(val) => onChange={(val) =>
@ -157,6 +181,7 @@ export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props)
router.push(`/${workspaceSlug}/workspace-views/assigned/?state_group=${datum.id}`); router.push(`/${workspaceSlug}/workspace-views/assigned/?state_group=${datum.id}`);
}} }}
onMouseEnter={(datum) => setActiveStateGroup(datum.id as TStateGroups)} onMouseEnter={(datum) => setActiveStateGroup(datum.id as TStateGroups)}
onMouseLeave={() => setActiveStateGroup(defaultStateGroup)}
layers={["arcs", CenteredMetric]} layers={["arcs", CenteredMetric]}
/> />
</div> </div>
@ -183,6 +208,6 @@ export const IssuesByStateGroupWidget: React.FC<WidgetProps> = observer((props)
<IssuesByStateGroupEmptyState filter={widgetDetails.widget_filters.target_date ?? "this_week"} /> <IssuesByStateGroupEmptyState filter={widgetDetails.widget_filters.target_date ?? "this_week"} />
</div> </div>
)} )}
</Link> </div>
); );
}); });

View File

@ -21,9 +21,9 @@ const WIDGET_KEY = "overview_stats";
export const OverviewStatsWidget: React.FC<WidgetProps> = observer((props) => { export const OverviewStatsWidget: React.FC<WidgetProps> = observer((props) => {
const { dashboardId, workspaceSlug } = props; const { dashboardId, workspaceSlug } = props;
// store hooks // store hooks
const { fetchWidgetStats, widgetStats: allWidgetStats } = useDashboard(); const { fetchWidgetStats, getWidgetStats } = useDashboard();
// derived values // derived values
const widgetStats = allWidgetStats?.[workspaceSlug]?.[dashboardId]?.[WIDGET_KEY] as TOverviewStatsWidgetResponse; const widgetStats = getWidgetStats<TOverviewStatsWidgetResponse>(workspaceSlug, dashboardId, WIDGET_KEY);
const today = renderFormattedPayloadDate(new Date()); const today = renderFormattedPayloadDate(new Date());
const STATS_LIST = [ const STATS_LIST = [
@ -76,11 +76,14 @@ export const OverviewStatsWidget: React.FC<WidgetProps> = observer((props) => {
)} )}
<Link <Link
href={stat.link} href={stat.link}
className={cn(`py-4 hover:bg-custom-background-80 duration-300 rounded-[10px] w-full break-words`, { className={cn(
"pl-11 pr-[4.725rem] mr-0.5": isFirst, `py-4 hover:bg-custom-background-80 duration-300 rounded-[10px] w-full break-words flex flex-col justify-center`,
"px-[4.725rem] mx-0.5": isMiddle, {
"px-[4.725rem] ml-0.5": isLast, "pl-11 pr-[4.725rem] mr-0.5": isFirst,
})} "px-[4.725rem] mx-0.5": isMiddle,
"px-[4.725rem] ml-0.5": isLast,
}
)}
> >
<h5 className="font-semibold text-xl">{stat.count}</h5> <h5 className="font-semibold text-xl">{stat.count}</h5>
<p className="text-custom-text-300">{stat.title}</p> <p className="text-custom-text-300">{stat.title}</p>

View File

@ -21,8 +21,8 @@ export const RecentActivityWidget: React.FC<WidgetProps> = observer((props) => {
// store hooks // store hooks
const { currentUser } = useUser(); const { currentUser } = useUser();
// derived values // derived values
const { fetchWidgetStats, widgetStats: allWidgetStats } = useDashboard(); const { fetchWidgetStats, getWidgetStats } = useDashboard();
const widgetStats = allWidgetStats?.[workspaceSlug]?.[dashboardId]?.[WIDGET_KEY] as TRecentActivityWidgetResponse[]; const widgetStats = getWidgetStats<TRecentActivityWidgetResponse[]>(workspaceSlug, dashboardId, WIDGET_KEY);
useEffect(() => { useEffect(() => {
if (!widgetStats) if (!widgetStats)
@ -34,13 +34,10 @@ export const RecentActivityWidget: React.FC<WidgetProps> = observer((props) => {
if (!widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />; if (!widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;
return ( return (
<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">
href="/profile/activity" <Link href="/profile/activity" className="text-lg font-semibold text-custom-text-300 mx-7 hover:underline">
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" My activity
> </Link>
<div className="flex items-center justify-between gap-2 px-7">
<h4 className="text-lg font-semibold text-custom-text-300">My activity</h4>
</div>
{widgetStats.length > 0 ? ( {widgetStats.length > 0 ? (
<div className="space-y-6 mt-4 mx-7"> <div className="space-y-6 mt-4 mx-7">
{widgetStats.map((activity) => ( {widgetStats.map((activity) => (
@ -100,6 +97,6 @@ export const RecentActivityWidget: React.FC<WidgetProps> = observer((props) => {
<RecentActivityEmptyState /> <RecentActivityEmptyState />
</div> </div>
)} )}
</Link> </div>
); );
}); });

View File

@ -53,10 +53,8 @@ const CollaboratorListItem: React.FC<CollaboratorListItemProps> = observer((prop
export const RecentCollaboratorsWidget: React.FC<WidgetProps> = observer((props) => { export const RecentCollaboratorsWidget: React.FC<WidgetProps> = observer((props) => {
const { dashboardId, workspaceSlug } = props; const { dashboardId, workspaceSlug } = props;
// store hooks // store hooks
const { fetchWidgetStats, widgetStats: allWidgetStats } = useDashboard(); const { fetchWidgetStats, getWidgetStats } = useDashboard();
const widgetStats = allWidgetStats?.[workspaceSlug]?.[dashboardId]?.[ const widgetStats = getWidgetStats<TRecentCollaboratorsWidgetResponse[]>(workspaceSlug, dashboardId, WIDGET_KEY);
WIDGET_KEY
] as TRecentCollaboratorsWidgetResponse[];
useEffect(() => { useEffect(() => {
if (!widgetStats) if (!widgetStats)

View File

@ -39,7 +39,7 @@ const ProjectListItem: React.FC<ProjectListItemProps> = observer((props) => {
className={`h-[3.375rem] w-[3.375rem] grid place-items-center rounded border border-transparent flex-shrink-0 ${randomBgColor}`} className={`h-[3.375rem] w-[3.375rem] grid place-items-center rounded border border-transparent flex-shrink-0 ${randomBgColor}`}
> >
{projectDetails.emoji ? ( {projectDetails.emoji ? (
<span className="grid h-7 w-7 flex-shrink-0 place-items-center rounded uppercase"> <span className="grid h-7 w-7 flex-shrink-0 text-2xl place-items-center rounded uppercase">
{renderEmoji(projectDetails.emoji)} {renderEmoji(projectDetails.emoji)}
</span> </span>
) : projectDetails.icon_prop ? ( ) : projectDetails.icon_prop ? (
@ -75,9 +75,9 @@ export const RecentProjectsWidget: React.FC<WidgetProps> = observer((props) => {
const { const {
membership: { currentWorkspaceRole }, membership: { currentWorkspaceRole },
} = useUser(); } = useUser();
const { fetchWidgetStats, widgetStats: allWidgetStats } = useDashboard(); const { fetchWidgetStats, getWidgetStats } = useDashboard();
// derived values // derived values
const widgetStats = allWidgetStats?.[workspaceSlug]?.[dashboardId]?.[WIDGET_KEY] as TRecentProjectsWidgetResponse; const widgetStats = getWidgetStats<TRecentProjectsWidgetResponse>(workspaceSlug, dashboardId, WIDGET_KEY);
const canCreateProject = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN; const canCreateProject = currentWorkspaceRole === EUserWorkspaceRoles.ADMIN;
useEffect(() => { useEffect(() => {
@ -90,13 +90,13 @@ export const RecentProjectsWidget: React.FC<WidgetProps> = observer((props) => {
if (!widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />; if (!widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;
return ( return (
<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">
href={`/${workspaceSlug}/projects`} <Link
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" href={`/${workspaceSlug}/projects`}
> className="text-lg font-semibold text-custom-text-300 mx-7 hover:underline"
<div className="flex items-center justify-between gap-2 px-7"> >
<h4 className="text-lg font-semibold text-custom-text-300">My projects</h4> My projects
</div> </Link>
<div className="space-y-8 mt-4 mx-7"> <div className="space-y-8 mt-4 mx-7">
{canCreateProject && ( {canCreateProject && (
<button <button
@ -120,6 +120,6 @@ export const RecentProjectsWidget: React.FC<WidgetProps> = observer((props) => {
<ProjectListItem key={projectId} projectId={projectId} workspaceSlug={workspaceSlug} /> <ProjectListItem key={projectId} projectId={projectId} workspaceSlug={workspaceSlug} />
))} ))}
</div> </div>
</Link> </div>
); );
}); });

View File

@ -35,6 +35,7 @@ export interface IDashboardStore {
homeDashboardWidgets: TWidget[] | undefined; homeDashboardWidgets: TWidget[] | undefined;
// computed actions // computed actions
getWidgetDetails: (workspaceSlug: string, dashboardId: string, widgetKey: TWidgetKeys) => TWidget | undefined; getWidgetDetails: (workspaceSlug: string, dashboardId: string, widgetKey: TWidgetKeys) => TWidget | undefined;
getWidgetStats: <T>(workspaceSlug: string, dashboardId: string, widgetKey: TWidgetKeys) => T | undefined;
// actions // actions
fetchHomeDashboardWidgets: (workspaceSlug: string) => Promise<THomeDashboardResponse>; fetchHomeDashboardWidgets: (workspaceSlug: string) => Promise<THomeDashboardResponse>;
fetchWidgetStats: ( fetchWidgetStats: (
@ -114,6 +115,16 @@ export class DashboardStore implements IDashboardStore {
return widgets.find((widget) => widget.key === widgetKey); return widgets.find((widget) => widget.key === widgetKey);
}); });
/**
* @description get widget stats
* @param workspaceSlug
* @param dashboardId
* @param widgetKey
* @returns widget stats
*/
getWidgetStats = <T>(workspaceSlug: string, dashboardId: string, widgetKey: TWidgetKeys): T | undefined =>
(this.widgetStats?.[workspaceSlug]?.[dashboardId]?.[widgetKey] as unknown as T) ?? undefined;
/** /**
* @description fetch home dashboard details and widgets * @description fetch home dashboard details and widgets
* @param workspaceSlug * @param workspaceSlug