2024-01-18 10:19:54 +00:00
|
|
|
import Link from "next/link";
|
2024-03-19 14:38:35 +00:00
|
|
|
import { TAssignedIssuesWidgetResponse, TCreatedIssuesWidgetResponse, TIssue, TIssuesListTypes } from "@plane/types";
|
2024-01-18 10:19:54 +00:00
|
|
|
// hooks
|
|
|
|
// components
|
2024-03-06 13:09:14 +00:00
|
|
|
import { Loader, getButtonStyling } from "@plane/ui";
|
2024-01-18 10:19:54 +00:00
|
|
|
import {
|
|
|
|
AssignedCompletedIssueListItem,
|
|
|
|
AssignedIssuesEmptyState,
|
|
|
|
AssignedOverdueIssueListItem,
|
|
|
|
AssignedUpcomingIssueListItem,
|
|
|
|
CreatedCompletedIssueListItem,
|
|
|
|
CreatedIssuesEmptyState,
|
|
|
|
CreatedOverdueIssueListItem,
|
|
|
|
CreatedUpcomingIssueListItem,
|
|
|
|
IssueListItemProps,
|
2024-03-19 14:38:35 +00:00
|
|
|
} from "@/components/dashboard/widgets";
|
2024-01-18 10:19:54 +00:00
|
|
|
// ui
|
|
|
|
// helpers
|
2024-03-19 14:38:35 +00:00
|
|
|
import { cn } from "@/helpers/common.helper";
|
|
|
|
import { getRedirectionFilters } from "@/helpers/dashboard.helper";
|
|
|
|
import { useIssueDetail } from "@/hooks/store";
|
2024-01-18 10:19:54 +00:00
|
|
|
// types
|
|
|
|
|
|
|
|
export type WidgetIssuesListProps = {
|
|
|
|
isLoading: boolean;
|
|
|
|
tab: TIssuesListTypes;
|
|
|
|
type: "assigned" | "created";
|
2024-02-26 08:09:29 +00:00
|
|
|
widgetStats: TAssignedIssuesWidgetResponse | TCreatedIssuesWidgetResponse;
|
2024-01-18 10:19:54 +00:00
|
|
|
workspaceSlug: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
export const WidgetIssuesList: React.FC<WidgetIssuesListProps> = (props) => {
|
2024-02-26 08:09:29 +00:00
|
|
|
const { isLoading, tab, type, widgetStats, workspaceSlug } = props;
|
2024-01-18 10:19:54 +00:00
|
|
|
// store hooks
|
|
|
|
const { setPeekIssue } = useIssueDetail();
|
|
|
|
|
|
|
|
const handleIssuePeekOverview = (issue: TIssue) =>
|
|
|
|
setPeekIssue({ workspaceSlug, projectId: issue.project_id, issueId: issue.id });
|
|
|
|
|
|
|
|
const filterParams = getRedirectionFilters(tab);
|
|
|
|
|
|
|
|
const ISSUE_LIST_ITEM: {
|
2024-02-05 13:42:33 +00:00
|
|
|
[key: string]: {
|
2024-01-18 10:19:54 +00:00
|
|
|
[key in TIssuesListTypes]: React.FC<IssueListItemProps>;
|
|
|
|
};
|
|
|
|
} = {
|
|
|
|
assigned: {
|
2024-02-05 13:42:33 +00:00
|
|
|
pending: AssignedUpcomingIssueListItem,
|
2024-01-18 10:19:54 +00:00
|
|
|
upcoming: AssignedUpcomingIssueListItem,
|
|
|
|
overdue: AssignedOverdueIssueListItem,
|
|
|
|
completed: AssignedCompletedIssueListItem,
|
|
|
|
},
|
|
|
|
created: {
|
2024-02-05 13:42:33 +00:00
|
|
|
pending: CreatedUpcomingIssueListItem,
|
2024-01-18 10:19:54 +00:00
|
|
|
upcoming: CreatedUpcomingIssueListItem,
|
|
|
|
overdue: CreatedOverdueIssueListItem,
|
|
|
|
completed: CreatedCompletedIssueListItem,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2024-02-26 08:09:29 +00:00
|
|
|
const issuesList = widgetStats.issues;
|
|
|
|
|
2024-01-18 10:19:54 +00:00
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<div className="h-full">
|
|
|
|
{isLoading ? (
|
2024-02-23 13:40:45 +00:00
|
|
|
<Loader className="space-y-4 mx-6 mt-7">
|
|
|
|
<Loader.Item height="25px" />
|
|
|
|
<Loader.Item height="25px" />
|
|
|
|
<Loader.Item height="25px" />
|
|
|
|
<Loader.Item height="25px" />
|
|
|
|
</Loader>
|
2024-02-26 08:09:29 +00:00
|
|
|
) : issuesList.length > 0 ? (
|
2024-01-18 10:19:54 +00:00
|
|
|
<>
|
2024-01-24 14:11:02 +00:00
|
|
|
<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">
|
2024-01-18 10:19:54 +00:00
|
|
|
<h6
|
|
|
|
className={cn("pl-1 flex items-center gap-1 col-span-4", {
|
|
|
|
"col-span-6": type === "assigned" && tab === "completed",
|
|
|
|
"col-span-5": type === "created" && tab === "completed",
|
|
|
|
})}
|
|
|
|
>
|
|
|
|
Issues
|
2024-02-02 09:19:42 +00:00
|
|
|
<span className="flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-medium rounded-xl px-3 flex items-center text-center justify-center">
|
2024-02-26 08:09:29 +00:00
|
|
|
{widgetStats.count}
|
2024-01-18 10:19:54 +00:00
|
|
|
</span>
|
|
|
|
</h6>
|
2024-02-05 13:42:33 +00:00
|
|
|
{["upcoming", "pending"].includes(tab) && <h6 className="text-center">Due date</h6>}
|
2024-01-18 10:19:54 +00:00
|
|
|
{tab === "overdue" && <h6 className="text-center">Due by</h6>}
|
|
|
|
{type === "assigned" && tab !== "completed" && <h6 className="text-center">Blocked by</h6>}
|
|
|
|
{type === "created" && <h6 className="text-center">Assigned to</h6>}
|
|
|
|
</div>
|
|
|
|
<div className="px-4 pb-3 mt-2">
|
2024-02-26 08:09:29 +00:00
|
|
|
{issuesList.map((issue) => {
|
2024-01-18 10:19:54 +00:00
|
|
|
const IssueListItem = ISSUE_LIST_ITEM[type][tab];
|
|
|
|
|
|
|
|
if (!IssueListItem) return null;
|
|
|
|
|
|
|
|
return (
|
|
|
|
<IssueListItem
|
|
|
|
key={issue.id}
|
|
|
|
issueId={issue.id}
|
|
|
|
workspaceSlug={workspaceSlug}
|
|
|
|
onClick={handleIssuePeekOverview}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
) : (
|
2024-01-24 14:11:02 +00:00
|
|
|
<div className="h-full grid place-items-center">
|
|
|
|
{type === "assigned" && <AssignedIssuesEmptyState type={tab} />}
|
|
|
|
{type === "created" && <CreatedIssuesEmptyState type={tab} />}
|
2024-01-18 10:19:54 +00:00
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
2024-02-26 08:09:29 +00:00
|
|
|
{!isLoading && issuesList.length > 0 && (
|
2024-01-18 10:19:54 +00:00
|
|
|
<Link
|
|
|
|
href={`/${workspaceSlug}/workspace-views/${type}/${filterParams}`}
|
2024-01-22 15:20:30 +00:00
|
|
|
className={cn(
|
|
|
|
getButtonStyling("link-primary", "sm"),
|
|
|
|
"w-min my-3 mx-auto py-1 px-2 text-xs hover:bg-custom-primary-100/20"
|
|
|
|
)}
|
2024-01-18 10:19:54 +00:00
|
|
|
>
|
|
|
|
View all issues
|
|
|
|
</Link>
|
|
|
|
)}
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|