[WEB-2358] chore: optimised the recent collaborators endpoint (#5470)
* chore: optimised the recent collaborators endpoint * chore: recent collabators code refactor * chore: sorted the user's based on active issues * chore: recent collaborators sorting * chore: code refactor --------- Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
This commit is contained in:
parent
bf49ebb519
commit
3d7098855f
6 changed files with 49 additions and 320 deletions
|
|
@ -1,6 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import { observer } from "mobx-react";
|
||||
import Link from "next/link";
|
||||
import useSWR from "swr";
|
||||
|
|
@ -52,64 +52,50 @@ const CollaboratorListItem: React.FC<CollaboratorListItemProps> = observer((prop
|
|||
});
|
||||
|
||||
type CollaboratorsListProps = {
|
||||
cursor: string;
|
||||
dashboardId: string;
|
||||
perPage: number;
|
||||
searchQuery?: string;
|
||||
updateIsLoading?: (isLoading: boolean) => void;
|
||||
updateResultsCount: (count: number) => void;
|
||||
updateTotalPages: (count: number) => void;
|
||||
workspaceSlug: string;
|
||||
};
|
||||
|
||||
const WIDGET_KEY = "recent_collaborators";
|
||||
|
||||
export const CollaboratorsList: React.FC<CollaboratorsListProps> = (props) => {
|
||||
const {
|
||||
cursor,
|
||||
dashboardId,
|
||||
perPage,
|
||||
searchQuery = "",
|
||||
updateIsLoading,
|
||||
updateResultsCount,
|
||||
updateTotalPages,
|
||||
workspaceSlug,
|
||||
} = props;
|
||||
const { dashboardId, searchQuery = "", workspaceSlug } = props;
|
||||
// store hooks
|
||||
const { fetchWidgetStats } = useDashboard();
|
||||
const { getUserDetails } = useMember();
|
||||
const { data: currentUser } = useUser();
|
||||
|
||||
const { data: widgetStats } = useSWR(
|
||||
workspaceSlug && dashboardId && cursor
|
||||
? `WIDGET_STATS_${workspaceSlug}_${dashboardId}_${cursor}_${searchQuery}`
|
||||
: null,
|
||||
workspaceSlug && dashboardId && cursor
|
||||
workspaceSlug && dashboardId ? `WIDGET_STATS_${workspaceSlug}_${dashboardId}` : null,
|
||||
workspaceSlug && dashboardId
|
||||
? () =>
|
||||
fetchWidgetStats(workspaceSlug, dashboardId, {
|
||||
cursor,
|
||||
per_page: perPage,
|
||||
search: searchQuery,
|
||||
widget_key: WIDGET_KEY,
|
||||
})
|
||||
: null
|
||||
) as {
|
||||
data: TRecentCollaboratorsWidgetResponse | undefined;
|
||||
data: TRecentCollaboratorsWidgetResponse[] | undefined;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
updateIsLoading?.(true);
|
||||
if (!widgetStats) return <WidgetLoader widgetKey={WIDGET_KEY} />;
|
||||
|
||||
if (!widgetStats) return;
|
||||
const sortedStats = sortBy(widgetStats, [(user) => user.user_id !== currentUser?.id]);
|
||||
|
||||
updateIsLoading?.(false);
|
||||
updateTotalPages(widgetStats.total_pages);
|
||||
updateResultsCount(widgetStats.results?.length);
|
||||
}, [updateIsLoading, updateResultsCount, updateTotalPages, widgetStats]);
|
||||
const filteredStats = sortedStats.filter((user) => {
|
||||
const { display_name, first_name, last_name } = getUserDetails(user.user_id) || {};
|
||||
|
||||
if (!widgetStats || !widgetStats?.results) return <WidgetLoader widgetKey={WIDGET_KEY} />;
|
||||
const searchLower = searchQuery.toLowerCase();
|
||||
return (
|
||||
display_name?.toLowerCase().includes(searchLower) ||
|
||||
first_name?.toLowerCase().includes(searchLower) ||
|
||||
last_name?.toLowerCase().includes(searchLower)
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{widgetStats?.results?.map((user) => (
|
||||
<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">
|
||||
{filteredStats?.map((user) => (
|
||||
<CollaboratorListItem
|
||||
key={user.user_id}
|
||||
issueCount={user.active_issue_count}
|
||||
|
|
@ -117,6 +103,6 @@ export const CollaboratorsList: React.FC<CollaboratorsListProps> = (props) => {
|
|||
workspaceSlug={workspaceSlug}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,76 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
// components
|
||||
import { Button } from "@plane/ui";
|
||||
import { CollaboratorsList } from "./collaborators-list";
|
||||
// ui
|
||||
|
||||
type Props = {
|
||||
dashboardId: string;
|
||||
perPage: number;
|
||||
workspaceSlug: string;
|
||||
};
|
||||
|
||||
export const DefaultCollaboratorsList: React.FC<Props> = (props) => {
|
||||
const { dashboardId, perPage, workspaceSlug } = props;
|
||||
// states
|
||||
const [pageCount, setPageCount] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(0);
|
||||
const [resultsCount, setResultsCount] = useState(0);
|
||||
|
||||
const handleLoadMore = () => setPageCount((prev) => prev + 1);
|
||||
|
||||
const updateTotalPages = (count: number) => setTotalPages(count);
|
||||
|
||||
const updateResultsCount = (count: number) => setResultsCount(count);
|
||||
|
||||
const collaboratorsPages: JSX.Element[] = [];
|
||||
for (let i = 0; i < pageCount; i++)
|
||||
collaboratorsPages.push(
|
||||
<CollaboratorsList
|
||||
key={i}
|
||||
dashboardId={dashboardId}
|
||||
cursor={`${perPage}:${i}:0`}
|
||||
perPage={perPage}
|
||||
updateResultsCount={updateResultsCount}
|
||||
updateTotalPages={updateTotalPages}
|
||||
workspaceSlug={workspaceSlug}
|
||||
/>
|
||||
);
|
||||
|
||||
const showViewMoreButton = pageCount < totalPages && resultsCount !== 0;
|
||||
const showViewLessButton = pageCount > 1;
|
||||
|
||||
return (
|
||||
<>
|
||||
<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">
|
||||
{collaboratorsPages}
|
||||
</div>
|
||||
{(showViewLessButton || showViewMoreButton) && (
|
||||
<div className="flex items-center justify-center text-xs w-full">
|
||||
{showViewLessButton && (
|
||||
<Button
|
||||
variant="link-primary"
|
||||
size="sm"
|
||||
className="my-3 hover:bg-custom-primary-100/20"
|
||||
onClick={() => setPageCount(1)}
|
||||
>
|
||||
View less
|
||||
</Button>
|
||||
)}
|
||||
{showViewMoreButton && (
|
||||
<Button
|
||||
variant="link-primary"
|
||||
size="sm"
|
||||
className="my-3 hover:bg-custom-primary-100/20"
|
||||
onClick={handleLoadMore}
|
||||
>
|
||||
View more
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -4,10 +4,7 @@ import { Search } from "lucide-react";
|
|||
import { Card } from "@plane/ui";
|
||||
import { WidgetProps } from "@/components/dashboard/widgets";
|
||||
// components
|
||||
import { DefaultCollaboratorsList } from "./default-list";
|
||||
import { SearchedCollaboratorsList } from "./search-list";
|
||||
|
||||
const PER_PAGE = 8;
|
||||
import { CollaboratorsList } from "./collaborators-list";
|
||||
|
||||
export const RecentCollaboratorsWidget: React.FC<WidgetProps> = (props) => {
|
||||
const { dashboardId, workspaceSlug } = props;
|
||||
|
|
@ -33,16 +30,7 @@ export const RecentCollaboratorsWidget: React.FC<WidgetProps> = (props) => {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
{searchQuery.trim() !== "" ? (
|
||||
<SearchedCollaboratorsList
|
||||
dashboardId={dashboardId}
|
||||
perPage={PER_PAGE}
|
||||
searchQuery={searchQuery}
|
||||
workspaceSlug={workspaceSlug}
|
||||
/>
|
||||
) : (
|
||||
<DefaultCollaboratorsList dashboardId={dashboardId} perPage={PER_PAGE} workspaceSlug={workspaceSlug} />
|
||||
)}
|
||||
<CollaboratorsList dashboardId={dashboardId} searchQuery={searchQuery} workspaceSlug={workspaceSlug} />
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,97 +0,0 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useTheme } from "next-themes";
|
||||
// components
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// assets
|
||||
import DarkImage from "@/public/empty-state/dashboard/dark/recent-collaborators-1.svg";
|
||||
import LightImage from "@/public/empty-state/dashboard/light/recent-collaborators-1.svg";
|
||||
import { CollaboratorsList } from "./collaborators-list";
|
||||
|
||||
type Props = {
|
||||
dashboardId: string;
|
||||
perPage: number;
|
||||
searchQuery: string;
|
||||
workspaceSlug: string;
|
||||
};
|
||||
|
||||
export const SearchedCollaboratorsList: React.FC<Props> = (props) => {
|
||||
const { dashboardId, perPage, searchQuery, workspaceSlug } = props;
|
||||
// states
|
||||
const [pageCount, setPageCount] = useState(1);
|
||||
const [totalPages, setTotalPages] = useState(0);
|
||||
const [resultsCount, setResultsCount] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
// next-themes
|
||||
const { resolvedTheme } = useTheme();
|
||||
|
||||
const handleLoadMore = () => setPageCount((prev) => prev + 1);
|
||||
|
||||
const updateTotalPages = (count: number) => setTotalPages(count);
|
||||
|
||||
const updateResultsCount = (count: number) => setResultsCount(count);
|
||||
|
||||
const collaboratorsPages: JSX.Element[] = [];
|
||||
for (let i = 0; i < pageCount; i++)
|
||||
collaboratorsPages.push(
|
||||
<CollaboratorsList
|
||||
key={i}
|
||||
dashboardId={dashboardId}
|
||||
cursor={`${perPage}:${i}:0`}
|
||||
perPage={perPage}
|
||||
searchQuery={searchQuery}
|
||||
updateIsLoading={setIsLoading}
|
||||
updateResultsCount={updateResultsCount}
|
||||
updateTotalPages={updateTotalPages}
|
||||
workspaceSlug={workspaceSlug}
|
||||
/>
|
||||
);
|
||||
|
||||
const showViewMoreButton = pageCount < totalPages && resultsCount !== 0;
|
||||
const showViewLessButton = pageCount > 1;
|
||||
|
||||
const emptyStateImage = resolvedTheme === "dark" ? DarkImage : LightImage;
|
||||
|
||||
return (
|
||||
<>
|
||||
<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">
|
||||
{collaboratorsPages}
|
||||
</div>
|
||||
{!isLoading && totalPages === 0 && (
|
||||
<div className="flex flex-col items-center gap-6 mb-8">
|
||||
<div className="h-24 w-24 flex-shrink-0">
|
||||
<Image src={emptyStateImage} className="w-full h-full" alt="Recent collaborators" />
|
||||
</div>
|
||||
<p className="font-medium text-sm">No matching member</p>
|
||||
</div>
|
||||
)}
|
||||
{(showViewLessButton || showViewMoreButton) && (
|
||||
<div className="flex items-center justify-center text-xs w-full">
|
||||
{showViewLessButton && (
|
||||
<Button
|
||||
variant="link-primary"
|
||||
size="sm"
|
||||
className="my-3 hover:bg-custom-primary-100/20"
|
||||
onClick={() => setPageCount(1)}
|
||||
>
|
||||
View less
|
||||
</Button>
|
||||
)}
|
||||
{showViewMoreButton && (
|
||||
<Button
|
||||
variant="link-primary"
|
||||
size="sm"
|
||||
className="my-3 hover:bg-custom-primary-100/20"
|
||||
onClick={handleLoadMore}
|
||||
>
|
||||
View more
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue