import { useCallback, useRef, useState } from "react"; import { observer } from "mobx-react"; import { Search, Briefcase, X, ListFilter } from "lucide-react"; // types import { TProjectFilters } from "@plane/types"; // ui import { Breadcrumbs, Button } from "@plane/ui"; // components import { BreadcrumbLink } from "@/components/common"; import { FiltersDropdown } from "@/components/issues"; import { ProjectFiltersSelection, ProjectOrderByDropdown } from "@/components/project"; // constants import { EUserWorkspaceRoles } from "@/constants/workspace"; // helpers import { cn } from "@/helpers/common.helper"; import { calculateTotalFilters } from "@/helpers/filter.helper"; // hooks import { useAppRouter, useCommandPalette, useEventTracker, useMember, useProjectFilter, useUser } from "@/hooks/store"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; export const ProjectsHeader = observer(() => { // states const [isSearchOpen, setIsSearchOpen] = useState(false); // refs const inputRef = useRef(null); // store hooks const { toggleCreateProjectModal } = useCommandPalette(); const { workspaceSlug } = useAppRouter(); const { setTrackElement } = useEventTracker(); const { membership: { currentWorkspaceRole }, } = useUser(); const { currentWorkspaceDisplayFilters: displayFilters, currentWorkspaceFilters: filters, updateFilters, updateDisplayFilters, searchQuery, updateSearchQuery, } = useProjectFilter(); const { workspace: { workspaceMemberIds }, } = useMember(); // outside click detector hook useOutsideClickDetector(inputRef, () => { if (isSearchOpen && searchQuery.trim() === "") setIsSearchOpen(false); }); // auth const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER; const handleFilters = useCallback( (key: keyof TProjectFilters, value: string | string[]) => { if (!workspaceSlug) return; let newValues = filters?.[key] ?? []; if (Array.isArray(value)) { if (key === "created_at" && newValues.find((v) => v.includes("custom"))) newValues = []; value.forEach((val) => { if (!newValues.includes(val)) newValues.push(val); else newValues.splice(newValues.indexOf(val), 1); }); } else { if (filters?.[key]?.includes(value)) newValues.splice(newValues.indexOf(value), 1); else { if (key === "created_at") newValues = [value]; else newValues.push(value); } } updateFilters(workspaceSlug, { [key]: newValues }); }, [filters, updateFilters, workspaceSlug] ); const handleInputKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Escape") { if (searchQuery && searchQuery.trim() !== "") updateSearchQuery(""); else setIsSearchOpen(false); } }; const isFiltersApplied = calculateTotalFilters(filters ?? {}) !== 0; return (
} />} />
{!isSearchOpen && ( )}
updateSearchQuery(e.target.value)} onKeyDown={handleInputKeyDown} /> {isSearchOpen && ( )}
{ if (!workspaceSlug || val === displayFilters?.order_by) return; updateDisplayFilters(workspaceSlug, { order_by: val, }); }} /> } title="Filters" placement="bottom-end" isFiltersApplied={isFiltersApplied} > { if (!workspaceSlug) return; updateDisplayFilters(workspaceSlug, val); }} memberIds={workspaceMemberIds ?? undefined} />
{isAuthorizedUser && ( )}
); });