forked from github/plane
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:
parent
fd5326dec6
commit
2986769f28
@ -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";
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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")}
|
||||||
|
@ -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")}
|
||||||
|
@ -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>
|
||||||
))}
|
))}
|
||||||
|
@ -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>
|
||||||
|
@ -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 (
|
||||||
|
<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
|
<Link
|
||||||
href={`/${workspaceSlug}/workspace-views/assigned`}
|
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"
|
className="text-lg font-semibold text-custom-text-300 hover:underline"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between gap-2 pl-7 pr-6">
|
Priority of assigned issues
|
||||||
<h4 className="text-lg font-semibold text-custom-text-300">Priority of assigned issues</h4>
|
</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>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -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(
|
||||||
|
`py-4 hover:bg-custom-background-80 duration-300 rounded-[10px] w-full break-words flex flex-col justify-center`,
|
||||||
|
{
|
||||||
"pl-11 pr-[4.725rem] mr-0.5": isFirst,
|
"pl-11 pr-[4.725rem] mr-0.5": isFirst,
|
||||||
"px-[4.725rem] mx-0.5": isMiddle,
|
"px-[4.725rem] mx-0.5": isMiddle,
|
||||||
"px-[4.725rem] ml-0.5": isLast,
|
"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>
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -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)
|
||||||
|
@ -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 (
|
||||||
|
<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">
|
||||||
<Link
|
<Link
|
||||||
href={`/${workspaceSlug}/projects`}
|
href={`/${workspaceSlug}/projects`}
|
||||||
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"
|
className="text-lg font-semibold text-custom-text-300 mx-7 hover:underline"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between gap-2 px-7">
|
My projects
|
||||||
<h4 className="text-lg font-semibold text-custom-text-300">My projects</h4>
|
</Link>
|
||||||
</div>
|
|
||||||
<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>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user