mirror of
https://github.com/makeplane/plane.git
synced 2026-07-12 21:40:18 +02:00
[WEB-2232] fix: project grouping UI changes (#856)
* fix: project grouping bugs * feat: join btn change + access filters * fix: handled states api call + made attributes common * fix: archived projects handling * fix: removed log * fix: copy changes * fix: UI based changes
This commit is contained in:
@@ -15,7 +15,7 @@ import { getRandomEmoji } from "@/helpers/emoji.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useMember, useProject, useUser, useWorkspace } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
import { useWorkspaceFeatures } from "@/plane-web/hooks/store";
|
||||
import { E_FEATURE_FLAGS, useFlag, useWorkspaceFeatures } from "@/plane-web/hooks/store";
|
||||
import { TProject } from "@/plane-web/types/projects";
|
||||
import { EWorkspaceFeatures } from "@/plane-web/types/workspace-feature";
|
||||
import ProjectAttributes from "./attributes";
|
||||
@@ -58,7 +58,9 @@ export const CreateProjectForm: FC<Props> = observer((props) => {
|
||||
const { data: currentUser } = useUser();
|
||||
const { isWorkspaceFeatureEnabled } = useWorkspaceFeatures();
|
||||
|
||||
const isProjectGroupingEnabled = isWorkspaceFeatureEnabled(EWorkspaceFeatures.IS_PROJECT_GROUPING_ENABLED);
|
||||
const isProjectGroupingEnabled =
|
||||
isWorkspaceFeatureEnabled(EWorkspaceFeatures.IS_PROJECT_GROUPING_ENABLED) &&
|
||||
useFlag(workspaceSlug.toString(), E_FEATURE_FLAGS.PROJECT_GROUPING);
|
||||
|
||||
// form info
|
||||
const methods = useForm<TProject>({
|
||||
|
||||
@@ -10,7 +10,7 @@ import { PROJECT_SCOPES } from "@/plane-web/constants/project";
|
||||
// plane web hooks
|
||||
import { useProjectFilter } from "@/plane-web/hooks/store";
|
||||
// plane web types
|
||||
import { EProjectScope } from "@/plane-web/types/workspace-project-filters";
|
||||
import { EProjectLayouts, EProjectScope } from "@/plane-web/types/workspace-project-filters";
|
||||
|
||||
export type TProjectScopeDropdown = {
|
||||
workspaceSlug: string;
|
||||
@@ -20,7 +20,7 @@ export type TProjectScopeDropdown = {
|
||||
export const ProjectScopeDropdown: FC<TProjectScopeDropdown> = observer((props) => {
|
||||
const { workspaceSlug, className } = props;
|
||||
// hooks
|
||||
const { scopeProjectsCount, filters, updateScope } = useProjectFilter();
|
||||
const { scopeProjectsCount, filters, updateScope, updateLayout } = useProjectFilter();
|
||||
|
||||
// derived values
|
||||
const selectedScope = filters?.scope || EProjectScope.ALL_PROJECTS;
|
||||
@@ -28,13 +28,17 @@ export const ProjectScopeDropdown: FC<TProjectScopeDropdown> = observer((props)
|
||||
|
||||
const DropdownLabel = () => (
|
||||
<>
|
||||
<div className="hidden md:flex relative items-center gap-2">
|
||||
<div className="whitespace-nowrap font-medium">
|
||||
{(PROJECT_SCOPES || []).find((scope) => selectedScope === scope.key)?.label}
|
||||
</div>
|
||||
<div className="px-2 py-0.5 flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-semibold rounded-xl">
|
||||
{selectedScopeCount}
|
||||
<div className="hidden md:flex relative items-center gap-2 w-[160px]">
|
||||
<div className="flex gap-2 flex-1 my-auto">
|
||||
{" "}
|
||||
<div className="whitespace-nowrap font-medium my-auto">
|
||||
{(PROJECT_SCOPES || []).find((scope) => selectedScope === scope.key)?.label}
|
||||
</div>
|
||||
<div className="px-2 py-0.5 flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-semibold rounded-xl">
|
||||
{selectedScopeCount}
|
||||
</div>
|
||||
</div>
|
||||
<ChevronDown className="h-3 w-3" strokeWidth={2} />
|
||||
</div>
|
||||
<div className="flex md:hidden text-sm items-center gap-2 neutral-primary text-custom-text-200 justify-center w-full">
|
||||
<span>{(PROJECT_SCOPES || []).find((scope) => selectedScope === scope.key)?.label}</span>
|
||||
@@ -48,7 +52,15 @@ export const ProjectScopeDropdown: FC<TProjectScopeDropdown> = observer((props)
|
||||
<CustomMenu.MenuItem
|
||||
key={scope.key}
|
||||
className="flex items-center gap-2 truncate"
|
||||
onClick={() => updateScope(workspaceSlug, scope.key)}
|
||||
onClick={() => {
|
||||
updateScope(workspaceSlug, scope.key);
|
||||
if (
|
||||
scope.key === EProjectScope.ALL_PROJECTS &&
|
||||
filters?.layout &&
|
||||
![EProjectLayouts.GALLERY, EProjectLayouts.TABLE].includes(filters?.layout)
|
||||
)
|
||||
updateLayout(workspaceSlug, EProjectLayouts.GALLERY);
|
||||
}}
|
||||
>
|
||||
<div className="truncate font-medium text-xs">{scope?.label}</div>
|
||||
<div className="px-2 py-0.5 flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-semibold rounded-xl">
|
||||
|
||||
@@ -10,7 +10,7 @@ import { PROJECT_LAYOUTS } from "@/plane-web/constants/project";
|
||||
// plane web hooks
|
||||
import { useProjectFilter } from "@/plane-web/hooks/store";
|
||||
// plane web types
|
||||
import { EProjectLayouts } from "@/plane-web/types/workspace-project-filters";
|
||||
import { EProjectLayouts, EProjectScope } from "@/plane-web/types/workspace-project-filters";
|
||||
|
||||
type TProjectLayoutSelection = {
|
||||
workspaceSlug: string;
|
||||
@@ -48,7 +48,9 @@ export const ProjectLayoutSelection: FC<TProjectLayoutSelection> = observer((pro
|
||||
))}
|
||||
</CustomMenu>
|
||||
<div className="hidden md:flex items-center gap-1 rounded bg-custom-background-80 p-1">
|
||||
{PROJECT_LAYOUTS.map((layout) => (
|
||||
{PROJECT_LAYOUTS.filter(
|
||||
(layout) => !layout.selectivelyHide || filters?.scope === EProjectScope.MY_PROJECTS
|
||||
).map((layout) => (
|
||||
<Tooltip key={layout.key} tooltipContent={layout.title} isMobile={isMobile}>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
ProjectScopeDropdown,
|
||||
ProjectSearch,
|
||||
} from "@/plane-web/components/projects/";
|
||||
import { useWorkspaceFeatures } from "@/plane-web/hooks/store";
|
||||
import { useFlag, useWorkspaceFeatures, E_FEATURE_FLAGS } from "@/plane-web/hooks/store";
|
||||
import { EWorkspaceFeatures } from "@/plane-web/types/workspace-feature";
|
||||
|
||||
export const ProjectsListHeader = observer(() => {
|
||||
@@ -30,7 +30,10 @@ export const ProjectsListHeader = observer(() => {
|
||||
|
||||
// derived values
|
||||
const workspaceId = currentWorkspace?.id || undefined;
|
||||
const isProjectGroupingEnabled = isWorkspaceFeatureEnabled(EWorkspaceFeatures.IS_PROJECT_GROUPING_ENABLED);
|
||||
const isProjectGroupingEnabled =
|
||||
isWorkspaceFeatureEnabled(EWorkspaceFeatures.IS_PROJECT_GROUPING_ENABLED) &&
|
||||
useFlag(workspaceSlug.toString(), E_FEATURE_FLAGS.PROJECT_GROUPING);
|
||||
|
||||
const isArchived = pathname.includes("/archives");
|
||||
|
||||
if (!workspaceSlug || !workspaceId) return <></>;
|
||||
|
||||
@@ -9,7 +9,9 @@ import { ArchiveRestoreProjectModal, DeleteProjectModal, JoinProjectModal } from
|
||||
// hooks
|
||||
import { useProject, useWorkspace } from "@/hooks/store";
|
||||
// types
|
||||
import { useProjectFilter } from "@/plane-web/hooks/store";
|
||||
import { TProject } from "@/plane-web/types/projects";
|
||||
import { EProjectScope } from "@/plane-web/types/workspace-project-filters";
|
||||
import JoinButton from "../../common/join-button";
|
||||
import Attributes from "../attributes";
|
||||
import Details from "./details";
|
||||
@@ -29,6 +31,7 @@ export const ProjectCard: React.FC<Props> = observer((props) => {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const { updateProject } = useProject();
|
||||
const { filters } = useProjectFilter();
|
||||
|
||||
const isArchived = pathname.includes("/archives");
|
||||
const handleUpdateProject = (data: Partial<TProject>) => {
|
||||
@@ -108,7 +111,7 @@ export const ProjectCard: React.FC<Props> = observer((props) => {
|
||||
</>
|
||||
</Link>
|
||||
|
||||
{!project.archived_at && <JoinButton project={project} />}
|
||||
{!project.archived_at && filters?.scope === EProjectScope.ALL_PROJECTS && <JoinButton project={project} />}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -11,8 +11,9 @@ import { IGanttBlock } from "@/components/gantt-chart";
|
||||
import { findTotalDaysInRange, renderFormattedDate } from "@/helpers/date-time.helper";
|
||||
import { useProject } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
import { useWorkspaceProjectStates } from "@/plane-web/hooks/store";
|
||||
import { useProjectFilter, useWorkspaceProjectStates } from "@/plane-web/hooks/store";
|
||||
import { TProject } from "@/plane-web/types/projects";
|
||||
import { EProjectScope } from "@/plane-web/types/workspace-project-filters";
|
||||
import JoinButton from "../../common/join-button";
|
||||
|
||||
type Props = {
|
||||
@@ -68,6 +69,7 @@ export const ProjectGanttBlock: React.FC<Props> = observer((props) => {
|
||||
// rendering projects on gantt sidebar
|
||||
export const ProjectGanttSidebarBlock: React.FC<SidebarProps> = observer((props) => {
|
||||
const { block } = props;
|
||||
const { filters } = useProjectFilter();
|
||||
|
||||
// router
|
||||
const { workspaceSlug: routerWorkspaceSlug } = useParams();
|
||||
@@ -89,7 +91,7 @@ export const ProjectGanttSidebarBlock: React.FC<SidebarProps> = observer((props)
|
||||
<span className="flex-grow text-sm font-medium max-w-[150px] truncate">{projectDetails?.name}</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<JoinButton project={projectDetails} />
|
||||
{filters?.scope === EProjectScope.ALL_PROJECTS && <JoinButton project={projectDetails} />}
|
||||
<div className="flex-shrink-0 h-full text-sm font-medium py-3 ml-2">
|
||||
{duration} day{duration > 1 ? "s" : ""}
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,9 @@ import { cn } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
import { useAppTheme, useProject, useWorkspace } from "@/hooks/store";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
import { useProjectFilter } from "@/plane-web/hooks/store";
|
||||
import { TProject } from "@/plane-web/types/projects";
|
||||
import { EProjectScope } from "@/plane-web/types/workspace-project-filters";
|
||||
import JoinButton from "../../common/join-button";
|
||||
import QuickActions from "../../quick-actions";
|
||||
import Attributes from "../attributes";
|
||||
@@ -35,7 +37,7 @@ export const ProjectBlock = observer((props: ProjectBlockProps) => {
|
||||
const { sidebarCollapsed: isSidebarCollapsed } = useAppTheme();
|
||||
const { getProjectById, updateProject } = useProject();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const { filters } = useProjectFilter();
|
||||
const { isMobile } = usePlatformOS();
|
||||
|
||||
const projectDetails = getProjectById(projectId);
|
||||
@@ -112,7 +114,7 @@ export const ProjectBlock = observer((props: ProjectBlockProps) => {
|
||||
handleUpdateProject={(data) => updateProject(workspaceSlug.toString(), projectDetails.id, data)}
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
currentWorkspace={currentWorkspace}
|
||||
cta={<JoinButton project={projectDetails as TProject} />}
|
||||
cta={filters?.scope === EProjectScope.ALL_PROJECTS && <JoinButton project={projectDetails as TProject} />}
|
||||
/>
|
||||
<div
|
||||
className={cn("hidden", {
|
||||
|
||||
@@ -11,6 +11,7 @@ import { useWorkspace } from "@/hooks/store";
|
||||
// plane web components
|
||||
import { WorkspaceProjectsRoot } from "@/plane-web/components/projects";
|
||||
import { useProjectFilter, useWorkspaceFeatures } from "@/plane-web/hooks/store";
|
||||
import { E_FEATURE_FLAGS, useFlag } from "@/plane-web/hooks/store/use-flag";
|
||||
import { EWorkspaceFeatures } from "@/plane-web/types/workspace-feature";
|
||||
import { EProjectLayouts } from "@/plane-web/types/workspace-project-filters";
|
||||
|
||||
@@ -25,7 +26,9 @@ export const ProjectPageRoot = observer(() => {
|
||||
// derived values
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Projects` : undefined;
|
||||
const currentWorkspaceId = currentWorkspace?.id;
|
||||
const isProjectGroupingEnabled = isWorkspaceFeatureEnabled(EWorkspaceFeatures.IS_PROJECT_GROUPING_ENABLED);
|
||||
const isProjectGroupingEnabled =
|
||||
isWorkspaceFeatureEnabled(EWorkspaceFeatures.IS_PROJECT_GROUPING_ENABLED) &&
|
||||
useFlag(workspaceSlug.toString(), E_FEATURE_FLAGS.PROJECT_GROUPING);
|
||||
|
||||
useEffect(() => {
|
||||
if (pathname.includes("/archives")) {
|
||||
|
||||
@@ -27,7 +27,7 @@ export const PROJECT_SCOPE_MAP: TProjectScopeMap = {
|
||||
export const PROJECT_SCOPES = Object.values(PROJECT_SCOPE_MAP);
|
||||
|
||||
// layout constants
|
||||
type TProjectLayoutMapObject<T> = { key: T; title: string; label: string; icon: LucideIcon };
|
||||
type TProjectLayoutMapObject<T> = { key: T; title: string; label: string; icon: LucideIcon; selectivelyHide: boolean };
|
||||
type TProjectLayoutMap = {
|
||||
[key in EProjectLayouts]: TProjectLayoutMapObject<key>;
|
||||
};
|
||||
@@ -37,14 +37,28 @@ export const PROJECT_LAYOUT_MAP: TProjectLayoutMap = {
|
||||
title: "Gallery Layout",
|
||||
label: "Gallery",
|
||||
icon: GalleryVertical,
|
||||
selectivelyHide: false,
|
||||
},
|
||||
[EProjectLayouts.BOARD]: {
|
||||
key: EProjectLayouts.BOARD,
|
||||
title: "Board Layout",
|
||||
label: "Board",
|
||||
icon: Kanban,
|
||||
selectivelyHide: true,
|
||||
},
|
||||
[EProjectLayouts.TABLE]: {
|
||||
key: EProjectLayouts.TABLE,
|
||||
title: "Table Layout",
|
||||
label: "Table",
|
||||
icon: List,
|
||||
selectivelyHide: false,
|
||||
},
|
||||
[EProjectLayouts.BOARD]: { key: EProjectLayouts.BOARD, title: "Board Layout", label: "Board", icon: Kanban },
|
||||
[EProjectLayouts.TABLE]: { key: EProjectLayouts.TABLE, title: "Table Layout", label: "Table", icon: List },
|
||||
[EProjectLayouts.TIMELINE]: {
|
||||
key: EProjectLayouts.TIMELINE,
|
||||
title: "Timeline Layout",
|
||||
label: "Timeline",
|
||||
icon: GanttChartSquare,
|
||||
selectivelyHide: true,
|
||||
},
|
||||
};
|
||||
export const PROJECT_LAYOUTS = Object.values(PROJECT_LAYOUT_MAP);
|
||||
|
||||
@@ -207,10 +207,11 @@ export class ProjectFilterStore extends ProjectFilterHelper implements IProjectF
|
||||
if (!this.scopeMap[workspaceSlug]) {
|
||||
this.updateScope(workspaceSlug, EProjectScope.ALL_PROJECTS);
|
||||
}
|
||||
const savedLayout = this.handleProjectLocalFilters.get(workspaceSlug)?.layout;
|
||||
if (!this.layoutMap[workspaceSlug]) {
|
||||
this.updateLayout(
|
||||
workspaceSlug,
|
||||
this.handleProjectLocalFilters.get(workspaceSlug)?.layout || EProjectLayouts.GALLERY
|
||||
[EProjectLayouts.GALLERY, EProjectLayouts.TABLE].includes(savedLayout) ? savedLayout : EProjectLayouts.GALLERY
|
||||
);
|
||||
}
|
||||
if (!this.attributesMap[workspaceSlug]) {
|
||||
|
||||
Reference in New Issue
Block a user