import React, { useCallback, useEffect, useRef, useState } from "react"; // next import { useRouter } from "next/router"; import { KeyedMutator, mutate } from "swr"; // components import { ListInlineCreateIssueForm, SpreadsheetAssigneeColumn, SpreadsheetCreatedOnColumn, SpreadsheetDueDateColumn, SpreadsheetEstimateColumn, SpreadsheetIssuesColumn, SpreadsheetLabelColumn, SpreadsheetPriorityColumn, SpreadsheetStartDateColumn, SpreadsheetStateColumn, SpreadsheetUpdatedOnColumn, } from "components/core"; import { CustomMenu, Icon, Spinner } from "components/ui"; import { IssuePeekOverview } from "components/issues"; // hooks import useIssuesProperties from "hooks/use-issue-properties"; import useLocalStorage from "hooks/use-local-storage"; import { useWorkspaceView } from "hooks/use-workspace-view"; // types import { ICurrentUserResponse, IIssue, ISubIssueResponse, TIssueOrderByOptions, UserAuth, } from "types"; import { CYCLE_DETAILS, CYCLE_ISSUES_WITH_PARAMS, MODULE_DETAILS, MODULE_ISSUES_WITH_PARAMS, PROJECT_ISSUES_LIST_WITH_PARAMS, SUB_ISSUES, VIEW_ISSUES, WORKSPACE_VIEW_ISSUES, } from "constants/fetch-keys"; import useSpreadsheetIssuesView from "hooks/use-spreadsheet-issues-view"; import projectIssuesServices from "services/issues.service"; // icon import { CheckIcon, ChevronDownIcon, PlusIcon } from "lucide-react"; type Props = { spreadsheetIssues: IIssue[]; mutateIssues: KeyedMutator< | IIssue[] | { [key: string]: IIssue[]; } >; handleIssueAction: (issue: IIssue, action: "copy" | "delete" | "edit") => void; openIssuesListModal?: (() => void) | null; disableUserActions: boolean; user: ICurrentUserResponse | undefined; userAuth: UserAuth; }; export const SpreadsheetView: React.FC = ({ spreadsheetIssues, mutateIssues, handleIssueAction, openIssuesListModal, disableUserActions, user, userAuth, }) => { const [expandedIssues, setExpandedIssues] = useState([]); const [currentProjectId, setCurrentProjectId] = useState(null); const [isInlineCreateIssueFormOpen, setIsInlineCreateIssueFormOpen] = useState(false); const [isScrolled, setIsScrolled] = useState(false); const containerRef = useRef(null); const router = useRouter(); const { workspaceSlug, projectId, cycleId, moduleId, viewId, globalViewId } = router.query; const type = cycleId ? "cycle" : moduleId ? "module" : "issue"; const [properties] = useIssuesProperties(workspaceSlug as string, projectId as string); const { storedValue: selectedMenuItem, setValue: setSelectedMenuItem } = useLocalStorage( "spreadsheetViewSorting", "" ); const { storedValue: activeSortingProperty, setValue: setActiveSortingProperty } = useLocalStorage("spreadsheetViewActiveSortingProperty", ""); const workspaceIssuesPath = [ { params: { sub_issue: false, }, path: "workspace-views/all-issues", }, { params: { assignees: user?.id ?? undefined, sub_issue: false, }, path: "workspace-views/assigned", }, { params: { created_by: user?.id ?? undefined, sub_issue: false, }, path: "workspace-views/created", }, { params: { subscriber: user?.id ?? undefined, sub_issue: false, }, path: "workspace-views/subscribed", }, ]; const currentWorkspaceIssuePath = workspaceIssuesPath.find((path) => router.pathname.includes(path.path) ); const { params: workspaceViewParams, filters: workspaceViewFilters, handleFilters, } = useWorkspaceView(); const workspaceViewProperties = workspaceViewFilters.display_properties; const isWorkspaceView = globalViewId || currentWorkspaceIssuePath; const currentViewProperties = isWorkspaceView ? workspaceViewProperties : properties; const { params, displayFilters, setDisplayFilters } = useSpreadsheetIssuesView(); const partialUpdateIssue = useCallback( (formData: Partial, issue: IIssue) => { if (!workspaceSlug || !issue) return; const fetchKey = cycleId ? CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), params) : moduleId ? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), params) : viewId ? VIEW_ISSUES(viewId.toString(), params) : globalViewId ? WORKSPACE_VIEW_ISSUES(globalViewId.toString(), workspaceViewParams) : currentWorkspaceIssuePath ? WORKSPACE_VIEW_ISSUES(workspaceSlug.toString(), currentWorkspaceIssuePath?.params) : PROJECT_ISSUES_LIST_WITH_PARAMS(issue.project_detail.id, params); if (issue.parent) mutate( SUB_ISSUES(issue.parent.toString()), (prevData) => { if (!prevData) return prevData; return { ...prevData, sub_issues: (prevData.sub_issues ?? []).map((i) => { if (i.id === issue.id) { return { ...i, ...formData, }; } return i; }), }; }, false ); else mutate( fetchKey, (prevData) => (prevData ?? []).map((p) => { if (p.id === issue.id) { return { ...p, ...formData, }; } return p; }), false ); projectIssuesServices .patchIssue( workspaceSlug as string, issue.project_detail.id, issue.id as string, formData, user ) .then(() => { if (issue.parent) { mutate(SUB_ISSUES(issue.parent as string)); } else { mutate(fetchKey); if (cycleId) mutate(CYCLE_DETAILS(cycleId as string)); if (moduleId) mutate(MODULE_DETAILS(moduleId as string)); } }) .catch((error) => { console.log(error); }); }, [ workspaceSlug, cycleId, moduleId, viewId, globalViewId, workspaceViewParams, currentWorkspaceIssuePath, params, user, ] ); const isNotAllowed = userAuth.isGuest || userAuth.isViewer; const handleOrderBy = (order: TIssueOrderByOptions, itemKey: string) => { if (globalViewId) handleFilters("display_filters", { order_by: order }); else setDisplayFilters({ order_by: order }); setSelectedMenuItem(`${order}_${itemKey}`); setActiveSortingProperty(order === "-created_at" ? "" : itemKey); }; const renderColumn = ( header: string, propertyName: string, Component: React.ComponentType, ascendingOrder: TIssueOrderByOptions, descendingOrder: TIssueOrderByOptions ) => (
{currentWorkspaceIssuePath ? ( {header} ) : ( {activeSortingProperty === propertyName && (
)} {header}
} width="xl" > { handleOrderBy(ascendingOrder, propertyName); }} >
{propertyName === "assignee" || propertyName === "labels" ? ( <> A Z ) : propertyName === "due_date" || propertyName === "created_on" || propertyName === "updated_on" ? ( <> New Old ) : ( <> First Last )}
{ handleOrderBy(descendingOrder, propertyName); }} >
{propertyName === "assignee" || propertyName === "labels" ? ( <> Z A ) : propertyName === "due_date" ? ( <> Old New ) : ( <> Last First )}
{selectedMenuItem && selectedMenuItem !== "" && displayFilters?.order_by !== "-created_at" && selectedMenuItem.includes(propertyName) && ( { handleOrderBy("-created_at", propertyName); }} >
Clear sorting
)} )}
{spreadsheetIssues.map((issue: IIssue, index) => ( ))}
); const handleScroll = () => { if (containerRef.current) { const scrollLeft = containerRef.current.scrollLeft; setIsScrolled(scrollLeft > 0); } }; useEffect(() => { const currentContainerRef = containerRef.current; if (currentContainerRef) { currentContainerRef.addEventListener("scroll", handleScroll); } return () => { if (currentContainerRef) { currentContainerRef.removeEventListener("scroll", handleScroll); } }; }, []); return ( <> mutateIssues()} projectId={currentProjectId ?? ""} workspaceSlug={workspaceSlug?.toString() ?? ""} readOnly={disableUserActions} />
{spreadsheetIssues ? ( <>
{currentViewProperties.key && ( ID )} Issue
{spreadsheetIssues.map((issue: IIssue, index) => ( ))}
{currentViewProperties.state && renderColumn( "State", "state", SpreadsheetStateColumn, "state__name", "-state__name" )} {currentViewProperties.priority && renderColumn( "Priority", "priority", SpreadsheetPriorityColumn, "priority", "-priority" )} {currentViewProperties.assignee && renderColumn( "Assignees", "assignee", SpreadsheetAssigneeColumn, "assignees__first_name", "-assignees__first_name" )} {currentViewProperties.labels && renderColumn( "Label", "labels", SpreadsheetLabelColumn, "labels__name", "-labels__name" )} {currentViewProperties.start_date && renderColumn( "Start Date", "start_date", SpreadsheetStartDateColumn, "-start_date", "start_date" )} {currentViewProperties.due_date && renderColumn( "Due Date", "due_date", SpreadsheetDueDateColumn, "-target_date", "target_date" )} {currentViewProperties.estimate && renderColumn( "Estimate", "estimate", SpreadsheetEstimateColumn, "estimate_point", "-estimate_point" )} {currentViewProperties.created_on && renderColumn( "Created On", "created_on", SpreadsheetCreatedOnColumn, "-created_at", "created_at" )} {currentViewProperties.updated_on && renderColumn( "Updated On", "updated_on", SpreadsheetUpdatedOnColumn, "-updated_at", "updated_at" )} ) : (
)}
setIsInlineCreateIssueFormOpen(false)} prePopulatedData={{ ...(cycleId && { cycle: cycleId.toString() }), ...(moduleId && { module: moduleId.toString() }), }} />
{type === "issue" ? !disableUserActions && !isInlineCreateIssueFormOpen && ( ) : !disableUserActions && !isInlineCreateIssueFormOpen && ( New Issue
} optionsClassName="left-5 !w-36" noBorder > setIsInlineCreateIssueFormOpen(true)}> Create new {openIssuesListModal && ( Add an existing issue )} )}
); };