diff --git a/apiserver/plane/app/views/issue.py b/apiserver/plane/app/views/issue.py
index 0b5c612d39..34bce8a0a9 100644
--- a/apiserver/plane/app/views/issue.py
+++ b/apiserver/plane/app/views/issue.py
@@ -1668,15 +1668,9 @@ class IssueDraftViewSet(BaseViewSet):
def get_queryset(self):
return (
- Issue.objects.annotate(
- sub_issues_count=Issue.issue_objects.filter(
- parent=OuterRef("id")
- )
- .order_by()
- .annotate(count=Func(F("id"), function="Count"))
- .values("count")
+ Issue.objects.filter(
+ project_id=self.kwargs.get("project_id")
)
- .filter(project_id=self.kwargs.get("project_id"))
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(is_draft=True)
.select_related("workspace", "project", "state", "parent")
@@ -1710,7 +1704,7 @@ class IssueDraftViewSet(BaseViewSet):
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
- )
+ ).distinct()
@method_decorator(gzip_page)
def list(self, request, slug, project_id):
@@ -1832,7 +1826,10 @@ class IssueDraftViewSet(BaseViewSet):
notification=True,
origin=request.META.get("HTTP_ORIGIN"),
)
- return Response(serializer.data, status=status.HTTP_201_CREATED)
+ issue = (
+ self.get_queryset().filter(pk=serializer.data["id"]).first()
+ )
+ return Response(IssueSerializer(issue).data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def partial_update(self, request, slug, project_id, pk):
@@ -1868,10 +1865,13 @@ class IssueDraftViewSet(BaseViewSet):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def retrieve(self, request, slug, project_id, pk=None):
- issue = Issue.objects.get(
- workspace__slug=slug, project_id=project_id, pk=pk, is_draft=True
+ issue = self.get_queryset().filter(pk=pk).first()
+ return Response(
+ IssueSerializer(
+ issue, fields=self.fields, expand=self.expand
+ ).data,
+ status=status.HTTP_200_OK,
)
- return Response(IssueSerializer(issue).data, status=status.HTTP_200_OK)
def destroy(self, request, slug, project_id, pk=None):
issue = Issue.objects.get(
diff --git a/apiserver/plane/db/migrations/0059_auto_20240208_0957.py b/apiserver/plane/db/migrations/0059_auto_20240208_0957.py
new file mode 100644
index 0000000000..c4c43fa4bf
--- /dev/null
+++ b/apiserver/plane/db/migrations/0059_auto_20240208_0957.py
@@ -0,0 +1,33 @@
+# Generated by Django 4.2.7 on 2024-02-08 09:57
+
+from django.db import migrations
+
+
+def widgets_filter_change(apps, schema_editor):
+ Widget = apps.get_model("db", "Widget")
+ widgets_to_update = []
+
+ # Define the filter dictionaries for each widget key
+ filters_mapping = {
+ "assigned_issues": {"duration": "none", "tab": "pending"},
+ "created_issues": {"duration": "none", "tab": "pending"},
+ "issues_by_state_groups": {"duration": "none"},
+ "issues_by_priority": {"duration": "none"},
+ }
+
+ # Iterate over widgets and update filters if applicable
+ for widget in Widget.objects.all():
+ if widget.key in filters_mapping:
+ widget.filters = filters_mapping[widget.key]
+ widgets_to_update.append(widget)
+
+ # Bulk update the widgets
+ Widget.objects.bulk_update(widgets_to_update, ["filters"], batch_size=10)
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('db', '0058_alter_moduleissue_issue_and_more'),
+ ]
+ operations = [
+ migrations.RunPython(widgets_filter_change)
+ ]
diff --git a/apiserver/templates/emails/notifications/issue-updates.html b/apiserver/templates/emails/notifications/issue-updates.html
index a7990562d4..3c561f37ac 100644
--- a/apiserver/templates/emails/notifications/issue-updates.html
+++ b/apiserver/templates/emails/notifications/issue-updates.html
@@ -66,7 +66,7 @@
style="margin-left: 30px; margin-bottom: 20px; margin-top: 20px"
>
|
|
|
|
|
-
+ {% if update.changes.state.old_value.0 == 'Backlog' or update.changes.state.old_value.0 == 'In Progress' or update.changes.state.old_value.0 == 'Done' or update.changes.state.old_value.0 == 'Cancelled' %}
+ |
|
+ {% endif %}
|
-
+ {% if update.changes.state.new_value|last == 'Backlog' or update.changes.state.new_value|last == 'In Progress' or update.changes.state.new_value|last == 'Done' or update.changes.state.new_value|last == 'Cancelled' %}
+ |
|
+ {% endif %}
|
|
|
(
{React.Children.map(children, (child, index) => (
-
+
{child}
{index !== React.Children.count(children) - 1 && (
diff --git a/packages/ui/src/dropdowns/custom-menu.tsx b/packages/ui/src/dropdowns/custom-menu.tsx
index 7ef99370f5..a2b5ebe3db 100644
--- a/packages/ui/src/dropdowns/custom-menu.tsx
+++ b/packages/ui/src/dropdowns/custom-menu.tsx
@@ -48,7 +48,13 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
if (referenceElement) referenceElement.focus();
};
const closeDropdown = () => setIsOpen(false);
+
const handleKeyDown = useDropdownKeyDown(openDropdown, closeDropdown, isOpen);
+
+ const handleOnClick = () => {
+ if (closeOnSelect) closeDropdown();
+ };
+
useOutsideClickDetector(dropdownRef, closeDropdown);
let menuItems = (
@@ -90,6 +96,7 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
tabIndex={tabIndex}
className={cn("relative w-min text-left", className)}
onKeyDown={handleKeyDown}
+ onClick={handleOnClick}
>
{({ open }) => (
<>
@@ -98,7 +105,8 @@ const CustomMenu = (props: ICustomMenuDropdownProps) => {
diff --git a/web/components/gantt-chart/sidebar/module-sidebar.tsx b/web/components/gantt-chart/sidebar/module-sidebar.tsx
index 30f146dc55..8f8788787c 100644
--- a/web/components/gantt-chart/sidebar/module-sidebar.tsx
+++ b/web/components/gantt-chart/sidebar/module-sidebar.tsx
@@ -93,7 +93,7 @@ export const ModuleGanttSidebar: React.FC = (props) => {
<>
{blocks ? (
blocks.map((block, index) => {
- const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? "");
+ const duration = findTotalDaysInRange(block.start_date, block.target_date);
return (
= (props) => {
-
- {duration} day{duration > 1 ? "s" : ""}
-
+ {duration !== undefined && (
+
+ {duration} day{duration > 1 ? "s" : ""}
+
+ )}
diff --git a/web/components/gantt-chart/sidebar/project-view-sidebar.tsx b/web/components/gantt-chart/sidebar/project-view-sidebar.tsx
index da7382859f..6e31215c1c 100644
--- a/web/components/gantt-chart/sidebar/project-view-sidebar.tsx
+++ b/web/components/gantt-chart/sidebar/project-view-sidebar.tsx
@@ -94,7 +94,7 @@ export const ProjectViewGanttSidebar: React.FC = (props) => {
<>
{blocks ? (
blocks.map((block, index) => {
- const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? "");
+ const duration = findTotalDaysInRange(block.start_date, block.target_date);
return (
= (props) => {
-
- {duration} day{duration > 1 ? "s" : ""}
-
+ {duration !== undefined && (
+
+ {duration} day{duration > 1 ? "s" : ""}
+
+ )}
diff --git a/web/components/gantt-chart/sidebar/sidebar.tsx b/web/components/gantt-chart/sidebar/sidebar.tsx
index bca39a0bd6..12de8e127f 100644
--- a/web/components/gantt-chart/sidebar/sidebar.tsx
+++ b/web/components/gantt-chart/sidebar/sidebar.tsx
@@ -119,10 +119,7 @@ export const IssueGanttSidebar: React.FC = (props) => {
// hide the block if it doesn't have start and target dates and showAllBlocks is false
if (!showAllBlocks && !isBlockVisibleOnSidebar) return;
- const duration =
- !block.start_date || !block.target_date
- ? null
- : findTotalDaysInRange(block.start_date, block.target_date);
+ const duration = findTotalDaysInRange(block.start_date, block.target_date);
return (
= (props) => {
-
- {duration && (
+ {duration !== undefined && (
+
{duration} day{duration > 1 ? "s" : ""}
- )}
-
+
+ )}
diff --git a/web/components/headers/project-draft-issues.tsx b/web/components/headers/project-draft-issues.tsx
index 0fe6a74c5b..139ec02579 100644
--- a/web/components/headers/project-draft-issues.tsx
+++ b/web/components/headers/project-draft-issues.tsx
@@ -103,7 +103,7 @@ export const ProjectDraftIssueHeader: FC = observer(() => {
} />
+ } />
}
/>
diff --git a/web/components/headers/project-issues.tsx b/web/components/headers/project-issues.tsx
index c819b25c34..81e2d2d76b 100644
--- a/web/components/headers/project-issues.tsx
+++ b/web/components/headers/project-issues.tsx
@@ -200,8 +200,8 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
handleDisplayPropertiesUpdate={handleDisplayProperties}
/>
-
- {currentProjectDetails?.inbox_view && inboxDetails && (
+
+ {currentProjectDetails?.inbox_view && inboxDetails && (
@@ -214,9 +214,7 @@ export const ProjectIssuesHeader: React.FC = observer(() => {
- )}
-
-
+ )}
{canUserCreateIssue && (
<>
setAnalyticsModal(true)} variant="neutral-primary" size="sm">
diff --git a/web/components/headers/user-profile.tsx b/web/components/headers/user-profile.tsx
index d54f73009e..f933d4dfac 100644
--- a/web/components/headers/user-profile.tsx
+++ b/web/components/headers/user-profile.tsx
@@ -1,18 +1,78 @@
// ui
-import { Breadcrumbs } from "@plane/ui";
+import { Breadcrumbs, CustomMenu } from "@plane/ui";
import { BreadcrumbLink } from "components/common";
// components
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
+import { cn } from "helpers/common.helper";
+import { FC } from "react";
+import { useApplication, useUser } from "hooks/store";
+import { ChevronDown, PanelRight } from "lucide-react";
+import { observer } from "mobx-react-lite";
+import { PROFILE_ADMINS_TAB, PROFILE_VIEWER_TAB } from "constants/profile";
+import Link from "next/link";
+import { useRouter } from "next/router";
-export const UserProfileHeader = () => (
-
+type TUserProfileHeader = {
+ type?: string | undefined
+}
+
+export const UserProfileHeader: FC = observer((props) => {
+ const { type = undefined } = props
+
+ const router = useRouter();
+ const { workspaceSlug, userId } = router.query;
+
+ const AUTHORIZED_ROLES = [20, 15, 10];
+ const {
+ membership: { currentWorkspaceRole },
+ } = useUser();
+
+ if (!currentWorkspaceRole) return null;
+
+ const isAuthorized = AUTHORIZED_ROLES.includes(currentWorkspaceRole);
+ const tabsList = isAuthorized ? [...PROFILE_VIEWER_TAB, ...PROFILE_ADMINS_TAB] : PROFILE_VIEWER_TAB;
+
+ const { theme: themStore } = useApplication();
+
+ return (
-
+
} />
+
+
+ {type}
+
+
+ }
+ customButtonClassName="flex flex-grow justify-center text-custom-text-200 text-sm"
+ closeOnSelect
+ >
+ <>>
+ {tabsList.map((tab) => (
+
+ {tab.label}
+
+ ))}
+
+ { themStore.toggleProfileSidebar(); console.log(themStore.profileSidebarCollapsed) }}>
+
+
+
-
-);
+ )
+});
+
+
diff --git a/web/components/headers/workspace-dashboard.tsx b/web/components/headers/workspace-dashboard.tsx
index d074132e26..d8306ab40e 100644
--- a/web/components/headers/workspace-dashboard.tsx
+++ b/web/components/headers/workspace-dashboard.tsx
@@ -1,4 +1,3 @@
-import { useState } from "react";
import { LayoutGrid, Zap } from "lucide-react";
import Image from "next/image";
import { useTheme } from "next-themes";
@@ -6,18 +5,16 @@ import { useTheme } from "next-themes";
import githubBlackImage from "/public/logos/github-black.png";
import githubWhiteImage from "/public/logos/github-white.png";
// components
-import { BreadcrumbLink, ProductUpdatesModal } from "components/common";
+import { BreadcrumbLink } from "components/common";
import { Breadcrumbs } from "@plane/ui";
import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
export const WorkspaceDashboardHeader = () => {
- const [isProductUpdatesModalOpen, setIsProductUpdatesModalOpen] = useState(false);
// hooks
const { resolvedTheme } = useTheme();
return (
<>
-
diff --git a/web/components/issues/index.ts b/web/components/issues/index.ts
index 3cf88cb7c4..3904049e9c 100644
--- a/web/components/issues/index.ts
+++ b/web/components/issues/index.ts
@@ -1,6 +1,5 @@
export * from "./attachment";
export * from "./issue-modal";
-export * from "./view-select";
export * from "./delete-issue-modal";
export * from "./description-form";
export * from "./issue-layouts";
diff --git a/web/components/issues/issue-layouts/kanban/block.tsx b/web/components/issues/issue-layouts/kanban/block.tsx
index 99d774bd57..203ac4938b 100644
--- a/web/components/issues/issue-layouts/kanban/block.tsx
+++ b/web/components/issues/issue-layouts/kanban/block.tsx
@@ -66,16 +66,22 @@ const KanbanIssueDetailsBlock: React.FC = observer((prop
- handleIssuePeekOverview(issue)}
- className="w-full line-clamp-1 cursor-pointer text-sm text-custom-text-100"
- >
+ {issue?.is_draft ? (
{issue.name}
-
+ ) : (
+ handleIssuePeekOverview(issue)}
+ className="w-full line-clamp-1 cursor-pointer text-sm text-custom-text-100"
+ >
+
+ {issue.name}
+
+
+ )}
= observer((props) => {
return (
<>
- {isDraftIssue ? (
- setIsOpen(false)}
- prePopulateData={issuePayload}
- fieldsToShow={["all"]}
- />
- ) : (
- setIsOpen(false)}
- data={issuePayload}
- storeType={storeType}
- />
- )}
+ setIsOpen(false)}
+ data={issuePayload}
+ storeType={storeType}
+ isDraft={isDraftIssue}
+ />
+
{renderExistingIssueModal && (
= observer((props: IssueBlock
)}
- handleIssuePeekOverview(issue)}
- className="w-full line-clamp-1 cursor-pointer text-sm text-custom-text-100"
- >
+ {issue?.is_draft ? (
{issue.name}
-
+ ) : (
+ handleIssuePeekOverview(issue)}
+ className="w-full line-clamp-1 cursor-pointer text-sm text-custom-text-100"
+ >
+
+ {issue.name}
+
+
+ )}
{!issue?.tempId ? (
diff --git a/web/components/issues/issue-layouts/list/headers/group-by-card.tsx b/web/components/issues/issue-layouts/list/headers/group-by-card.tsx
index 49c9f7e406..90270e1a15 100644
--- a/web/components/issues/issue-layouts/list/headers/group-by-card.tsx
+++ b/web/components/issues/issue-layouts/list/headers/group-by-card.tsx
@@ -109,21 +109,13 @@ export const HeaderGroupByCard = observer(
))}
- {isDraftIssue ? (
- setIsOpen(false)}
- prePopulateData={issuePayload}
- fieldsToShow={["all"]}
- />
- ) : (
- setIsOpen(false)}
- data={issuePayload}
- storeType={storeType}
- />
- )}
+ setIsOpen(false)}
+ data={issuePayload}
+ storeType={storeType}
+ isDraft={isDraftIssue}
+ />
{renderExistingIssueModal && (
= (props) =>
};
delete duplicateIssuePayload.id;
+ const isDraftIssue = router?.asPath?.includes("draft-issues") || false;
+
return (
<>
= (props) =>
handleClose={() => setDeleteIssueModal(false)}
onSubmit={handleDelete}
/>
+
{
@@ -73,7 +76,9 @@ export const ProjectIssueQuickActions: React.FC = (props) =>
if (issueToEdit && handleUpdate) await handleUpdate({ ...issueToEdit, ...data });
}}
storeType={EIssuesStoreType.PROJECT}
+ isDraft={isDraftIssue}
/>
+
void;
onSubmit: (formData: Partial) => Promise;
projectId: string;
+ isDraft: boolean;
}
const issueDraftService = new IssueDraftService();
@@ -35,6 +36,7 @@ export const DraftIssueLayout: React.FC = observer((props) => {
projectId,
isCreateMoreToggleEnabled,
onCreateMoreToggleChange,
+ isDraft,
} = props;
// states
const [issueDiscardModal, setIssueDiscardModal] = useState(false);
@@ -107,6 +109,7 @@ export const DraftIssueLayout: React.FC = observer((props) => {
onClose={handleClose}
onSubmit={onSubmit}
projectId={projectId}
+ isDraft={isDraft}
/>
>
);
diff --git a/web/components/issues/issue-modal/form.tsx b/web/components/issues/issue-modal/form.tsx
index 31cb9dd669..544ebeb15d 100644
--- a/web/components/issues/issue-modal/form.tsx
+++ b/web/components/issues/issue-modal/form.tsx
@@ -1,4 +1,4 @@
-import React, { FC, useState, useRef, useEffect } from "react";
+import React, { FC, useState, useRef, useEffect, Fragment } from "react";
import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
import { Controller, useForm } from "react-hook-form";
@@ -55,8 +55,9 @@ export interface IssueFormProps {
onCreateMoreToggleChange: (value: boolean) => void;
onChange?: (formData: Partial | null) => void;
onClose: () => void;
- onSubmit: (values: Partial) => Promise;
+ onSubmit: (values: Partial, is_draft_issue?: boolean) => Promise;
projectId: string;
+ isDraft: boolean;
}
// services
@@ -72,6 +73,7 @@ export const IssueFormRoot: FC = observer((props) => {
projectId: defaultProjectId,
isCreateMoreToggleEnabled,
onCreateMoreToggleChange,
+ isDraft,
} = props;
// states
const [labelModal, setLabelModal] = useState(false);
@@ -137,8 +139,8 @@ export const IssueFormRoot: FC = observer((props) => {
const issueName = watch("name");
- const handleFormSubmit = async (formData: Partial) => {
- await onSubmit(formData);
+ const handleFormSubmit = async (formData: Partial, is_draft_issue = false) => {
+ await onSubmit(formData, is_draft_issue);
setGptAssistantModal(false);
@@ -248,7 +250,7 @@ export const IssueFormRoot: FC = observer((props) => {
}}
/>
)}
-
-
- );
-};
diff --git a/web/components/modules/gantt-chart/modules-list-layout.tsx b/web/components/modules/gantt-chart/modules-list-layout.tsx
index d1cbd0dfab..53948f71db 100644
--- a/web/components/modules/gantt-chart/modules-list-layout.tsx
+++ b/web/components/modules/gantt-chart/modules-list-layout.tsx
@@ -13,37 +13,32 @@ export const ModulesListGanttChartView: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug } = router.query;
// store
- const { projectModuleIds, moduleMap } = useModule();
const { currentProjectDetails } = useProject();
+ const { projectModuleIds, moduleMap, updateModuleDetails } = useModule();
- const handleModuleUpdate = (module: IModule, payload: IBlockUpdateData) => {
- if (!workspaceSlug) return;
- // FIXME
- //updateModuleGanttStructure(workspaceSlug.toString(), module.project, module, payload);
+ const handleModuleUpdate = async (module: IModule, data: IBlockUpdateData) => {
+ if (!workspaceSlug || !module) return;
+
+ const payload: any = { ...data };
+ if (data.sort_order) payload.sort_order = data.sort_order.newSortOrder;
+
+ await updateModuleDetails(workspaceSlug.toString(), module.project, module.id, payload);
};
const blockFormat = (blocks: string[]) =>
- blocks && blocks.length > 0
- ? blocks
- .filter((blockId) => {
- const block = moduleMap[blockId];
- return block.start_date && block.target_date && new Date(block.start_date) <= new Date(block.target_date);
- })
- .map((blockId) => {
- const block = moduleMap[blockId];
- return {
- data: block,
- id: block.id,
- sort_order: block.sort_order,
- start_date: new Date(block.start_date ?? ""),
- target_date: new Date(block.target_date ?? ""),
- };
- })
- : [];
+ blocks?.map((blockId) => {
+ const block = moduleMap[blockId];
+ return {
+ data: block,
+ id: block.id,
+ sort_order: block.sort_order,
+ start_date: block.start_date ? new Date(block.start_date) : null,
+ target_date: block.target_date ? new Date(block.target_date) : null,
+ };
+ });
const isAllowed = currentProjectDetails?.member_role === 20 || currentProjectDetails?.member_role === 15;
- const modules = projectModuleIds;
return (
{
enableBlockRightResize={isAllowed}
enableBlockMove={isAllowed}
enableReorder={isAllowed}
+ showAllBlocks
/>
);
diff --git a/web/components/modules/module-mobile-header.tsx b/web/components/modules/module-mobile-header.tsx
index b08c443d2c..e9ed56a8de 100644
--- a/web/components/modules/module-mobile-header.tsx
+++ b/web/components/modules/module-mobile-header.tsx
@@ -77,7 +77,7 @@ export const ModuleMobileHeader = () => {
);
return (
- <>
+
setAnalyticsModal(false)}
@@ -157,6 +157,6 @@ export const ModuleMobileHeader = () => {
Analytics
- >
+
);
};
diff --git a/web/components/notifications/notification-card.tsx b/web/components/notifications/notification-card.tsx
index 58b79ac3eb..7a372c5d8d 100644
--- a/web/components/notifications/notification-card.tsx
+++ b/web/components/notifications/notification-card.tsx
@@ -208,9 +208,6 @@ export const NotificationCard: React.FC = (props) => {
void }) => {
- e.stopPropagation();
- }}
customButton={
diff --git a/web/components/profile/navbar.tsx b/web/components/profile/navbar.tsx
index 44dfe57d18..4361b7a9d2 100644
--- a/web/components/profile/navbar.tsx
+++ b/web/components/profile/navbar.tsx
@@ -22,16 +22,15 @@ export const ProfileNavbar: React.FC = (props) => {
const tabsList = isAuthorized ? [...PROFILE_VIEWER_TAB, ...PROFILE_ADMINS_TAB] : PROFILE_VIEWER_TAB;
return (
-
+
{tabsList.map((tab) => (
{tab.label}
diff --git a/web/components/profile/sidebar.tsx b/web/components/profile/sidebar.tsx
index 3ce7747c9b..b356b5adb3 100644
--- a/web/components/profile/sidebar.tsx
+++ b/web/components/profile/sidebar.tsx
@@ -4,7 +4,7 @@ import useSWR from "swr";
import { Disclosure, Transition } from "@headlessui/react";
import { observer } from "mobx-react-lite";
// hooks
-import { useUser } from "hooks/store";
+import { useApplication, useUser } from "hooks/store";
// services
import { UserService } from "services/user.service";
// components
@@ -18,6 +18,8 @@ import { renderFormattedDate } from "helpers/date-time.helper";
import { renderEmoji } from "helpers/emoji.helper";
// fetch-keys
import { USER_PROFILE_PROJECT_SEGREGATION } from "constants/fetch-keys";
+import useOutsideClickDetector from "hooks/use-outside-click-detector";
+import { useEffect, useRef } from "react";
// services
const userService = new UserService();
@@ -28,6 +30,8 @@ export const ProfileSidebar = observer(() => {
const { workspaceSlug, userId } = router.query;
// store hooks
const { currentUser } = useUser();
+ const { theme: themStore } = useApplication();
+ const ref = useRef (null);
const { data: userProjectsData } = useSWR(
workspaceSlug && userId ? USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug.toString(), userId.toString()) : null,
@@ -36,6 +40,14 @@ export const ProfileSidebar = observer(() => {
: null
);
+ useOutsideClickDetector(ref, () => {
+ if (themStore.profileSidebarCollapsed === false) {
+ if (window.innerWidth < 768) {
+ themStore.toggleProfileSidebar();
+ }
+ }
+ });
+
const userDetails = [
{
label: "Joined on",
@@ -47,8 +59,26 @@ export const ProfileSidebar = observer(() => {
},
];
+ useEffect(() => {
+ const handleToggleProfileSidebar = () => {
+ if (window && window.innerWidth < 768) {
+ themStore.toggleProfileSidebar(true);
+ }
+ if (window && themStore.profileSidebarCollapsed && window.innerWidth >= 768) {
+ themStore.toggleProfileSidebar(false);
+ }
+ };
+
+ window.addEventListener("resize", handleToggleProfileSidebar);
+ handleToggleProfileSidebar();
+ return () => window.removeEventListener("resize", handleToggleProfileSidebar);
+ }, [themStore]);
+
return (
-
+
{userProjectsData ? (
<>
@@ -132,13 +162,12 @@ export const ProfileSidebar = observer(() => {
{project.assigned_issues > 0 && (
{completedIssuePercentage}%
diff --git a/web/helpers/dashboard.helper.ts b/web/helpers/dashboard.helper.ts
index 8003f15e32..90319a90b9 100644
--- a/web/helpers/dashboard.helper.ts
+++ b/web/helpers/dashboard.helper.ts
@@ -4,6 +4,10 @@ import { renderFormattedPayloadDate } from "./date-time.helper";
// types
import { TDurationFilterOptions, TIssuesListTypes } from "@plane/types";
+/**
+ * @description returns date range based on the duration filter
+ * @param duration
+ */
export const getCustomDates = (duration: TDurationFilterOptions): string => {
const today = new Date();
let firstDay, lastDay;
@@ -30,6 +34,10 @@ export const getCustomDates = (duration: TDurationFilterOptions): string => {
}
};
+/**
+ * @description returns redirection filters for the issues list
+ * @param type
+ */
export const getRedirectionFilters = (type: TIssuesListTypes): string => {
const today = renderFormattedPayloadDate(new Date());
@@ -44,3 +52,20 @@ export const getRedirectionFilters = (type: TIssuesListTypes): string => {
return filterParams;
};
+
+/**
+ * @description returns the tab key based on the duration filter
+ * @param duration
+ * @param tab
+ */
+export const getTabKey = (duration: TDurationFilterOptions, tab: TIssuesListTypes | undefined): TIssuesListTypes => {
+ if (!tab) return "completed";
+
+ if (tab === "completed") return tab;
+
+ if (duration === "none") return "pending";
+ else {
+ if (["upcoming", "overdue"].includes(tab)) return tab;
+ else return "upcoming";
+ }
+};
diff --git a/web/helpers/date-time.helper.ts b/web/helpers/date-time.helper.ts
index bc5daa2a35..b629e60ec3 100644
--- a/web/helpers/date-time.helper.ts
+++ b/web/helpers/date-time.helper.ts
@@ -87,11 +87,11 @@ export const renderFormattedTime = (date: string | Date, timeFormat: "12-hour" |
* @example checkIfStringIsDate("2021-01-01", "2021-01-08") // 8
*/
export const findTotalDaysInRange = (
- startDate: Date | string,
- endDate: Date | string,
+ startDate: Date | string | undefined | null,
+ endDate: Date | string | undefined | null,
inclusive: boolean = true
-): number => {
- if (!startDate || !endDate) return 0;
+): number | undefined => {
+ if (!startDate || !endDate) return undefined;
// Parse the dates to check if they are valid
const parsedStartDate = new Date(startDate);
const parsedEndDate = new Date(endDate);
@@ -110,8 +110,11 @@ export const findTotalDaysInRange = (
* @param {boolean} inclusive (optional) // default true
* @example findHowManyDaysLeft("2024-01-01") // 3
*/
-export const findHowManyDaysLeft = (date: string | Date, inclusive: boolean = true): number => {
- if (!date) return 0;
+export const findHowManyDaysLeft = (
+ date: Date | string | undefined | null,
+ inclusive: boolean = true
+): number | undefined => {
+ if (!date) return undefined;
// Pass the date to findTotalDaysInRange function to find the total number of days in range from today
return findTotalDaysInRange(new Date(), date, inclusive);
};
diff --git a/web/hooks/use-reload-confirmation.tsx b/web/hooks/use-reload-confirmation.tsx
index cdaff73652..8343ea78df 100644
--- a/web/hooks/use-reload-confirmation.tsx
+++ b/web/hooks/use-reload-confirmation.tsx
@@ -1,26 +1,41 @@
import { useCallback, useEffect, useState } from "react";
+import { useRouter } from "next/router";
-const useReloadConfirmations = (message?: string) => {
+//TODO: remove temp flag isActive later and use showAlert as the source of truth
+const useReloadConfirmations = (isActive = true) => {
const [showAlert, setShowAlert] = useState(false);
+ const router = useRouter();
const handleBeforeUnload = useCallback(
(event: BeforeUnloadEvent) => {
+ if (!isActive || !showAlert) return;
event.preventDefault();
event.returnValue = "";
- return message ?? "Are you sure you want to leave?";
},
- [message]
+ [isActive, showAlert]
+ );
+
+ const handleRouteChangeStart = useCallback(
+ (url: string) => {
+ if (!isActive || !showAlert) return;
+ const leave = confirm("Are you sure you want to leave? Changes you made may not be saved.");
+ if (!leave) {
+ router.events.emit("routeChangeError");
+ throw `Route change to "${url}" was aborted (this error can be safely ignored).`;
+ }
+ },
+ [isActive, showAlert, router.events]
);
useEffect(() => {
- if (!showAlert) {
- window.removeEventListener("beforeunload", handleBeforeUnload);
- return;
- }
-
window.addEventListener("beforeunload", handleBeforeUnload);
- return () => window.removeEventListener("beforeunload", handleBeforeUnload);
- }, [handleBeforeUnload, showAlert]);
+ router.events.on("routeChangeStart", handleRouteChangeStart);
+
+ return () => {
+ window.removeEventListener("beforeunload", handleBeforeUnload);
+ router.events.off("routeChangeStart", handleRouteChangeStart);
+ };
+ }, [handleBeforeUnload, handleRouteChangeStart, router.events]);
return { setShowAlert };
};
diff --git a/web/layouts/settings-layout/profile/preferences/layout.tsx b/web/layouts/settings-layout/profile/preferences/layout.tsx
index 9d17350a98..b25935f4e2 100644
--- a/web/layouts/settings-layout/profile/preferences/layout.tsx
+++ b/web/layouts/settings-layout/profile/preferences/layout.tsx
@@ -2,6 +2,11 @@ import { FC, ReactNode } from "react";
// layout
import { ProfileSettingsLayout } from "layouts/settings-layout";
import { ProfilePreferenceSettingsSidebar } from "./sidebar";
+import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle";
+import { CustomMenu } from "@plane/ui";
+import { ChevronDown } from "lucide-react";
+import Link from "next/link";
+import { useRouter } from "next/router";
interface IProfilePreferenceSettingsLayout {
children: ReactNode;
@@ -10,9 +15,57 @@ interface IProfilePreferenceSettingsLayout {
export const ProfilePreferenceSettingsLayout: FC = (props) => {
const { children, header } = props;
+ const router = useRouter();
+
+ const showMenuItem = () => {
+ const item = router.asPath.split('/');
+ let splittedItem = item[item.length - 1];
+ splittedItem = splittedItem.replace(splittedItem[0], splittedItem[0].toUpperCase());
+ console.log(splittedItem);
+ return splittedItem;
+ }
+
+ const profilePreferenceLinks: Array<{
+ label: string;
+ href: string;
+ }> = [
+ {
+ label: "Theme",
+ href: `/profile/preferences/theme`,
+ },
+ {
+ label: "Email",
+ href: `/profile/preferences/email`,
+ },
+ ];
return (
-
+
+
+
+ {showMenuItem()}
+
+
+ }
+ customButtonClassName="flex flex-grow justify-start text-custom-text-200 text-sm"
+ >
+ <>>
+ {profilePreferenceLinks.map((link) => (
+
+ {link.label}
+
+ ))}
+
+
+ }>
diff --git a/web/layouts/settings-layout/profile/preferences/sidebar.tsx b/web/layouts/settings-layout/profile/preferences/sidebar.tsx
index d1eec12331..7f43f3cad1 100644
--- a/web/layouts/settings-layout/profile/preferences/sidebar.tsx
+++ b/web/layouts/settings-layout/profile/preferences/sidebar.tsx
@@ -9,28 +9,27 @@ export const ProfilePreferenceSettingsSidebar = () => {
label: string;
href: string;
}> = [
- {
- label: "Theme",
- href: `/profile/preferences/theme`,
- },
- {
- label: "Email",
- href: `/profile/preferences/email`,
- },
- ];
+ {
+ label: "Theme",
+ href: `/profile/preferences/theme`,
+ },
+ {
+ label: "Email",
+ href: `/profile/preferences/email`,
+ },
+ ];
return (
-
+
Preference
{profilePreferenceLinks.map((link) => (
{link.label}
diff --git a/web/layouts/settings-layout/profile/sidebar.tsx b/web/layouts/settings-layout/profile/sidebar.tsx
index 0a97b33648..4b8a1b8543 100644
--- a/web/layouts/settings-layout/profile/sidebar.tsx
+++ b/web/layouts/settings-layout/profile/sidebar.tsx
@@ -1,4 +1,4 @@
-import { useState } from "react";
+import { useEffect, useRef, useState } from "react";
import { mutate } from "swr";
import Link from "next/link";
import { useRouter } from "next/router";
@@ -12,6 +12,7 @@ import useToast from "hooks/use-toast";
import { Tooltip } from "@plane/ui";
// constants
import { PROFILE_ACTION_LINKS } from "constants/profile";
+import useOutsideClickDetector from "hooks/use-outside-click-detector";
const WORKSPACE_ACTION_LINKS = [
{
@@ -52,6 +53,35 @@ export const ProfileLayoutSidebar = observer(() => {
currentUserSettings?.workspace?.fallback_workspace_slug ||
"";
+ const ref = useRef (null);
+
+ useOutsideClickDetector(ref, () => {
+ if (sidebarCollapsed === false) {
+ if (window.innerWidth < 768) {
+ toggleSidebar();
+ }
+ }
+ });
+
+ useEffect(() => {
+ const handleResize = () => {
+ if (window.innerWidth <= 768) {
+ toggleSidebar(true);
+ }
+ };
+ handleResize();
+ window.addEventListener("resize", handleResize);
+ return () => {
+ window.removeEventListener("resize", handleResize);
+ };
+ }, [toggleSidebar]);
+
+ const handleItemClick = () => {
+ if (window.innerWidth < 768) {
+ toggleSidebar();
+ }
+ };
+
const handleSignOut = async () => {
setIsSigningOut(true);
@@ -73,16 +103,18 @@ export const ProfileLayoutSidebar = observer(() => {
return (
-
+
@@ -101,14 +133,13 @@ export const ProfileLayoutSidebar = observer(() => {
if (link.key === "change-password" && currentUser?.is_password_autoset) return null;
return (
-
+
{ }
{!sidebarCollapsed && link.label}
@@ -129,19 +160,17 @@ export const ProfileLayoutSidebar = observer(() => {
{workspace?.logo && workspace.logo !== "" ? (
{
)}
{WORKSPACE_ACTION_LINKS.map((link) => (
-
+
{}
{!sidebarCollapsed && link.label}
@@ -180,9 +208,8 @@ export const ProfileLayoutSidebar = observer(() => {
{
toggleSidebar()}
>
diff --git a/web/layouts/user-profile-layout/layout.tsx b/web/layouts/user-profile-layout/layout.tsx
index 60c17d8d4e..5ff3891e02 100644
--- a/web/layouts/user-profile-layout/layout.tsx
+++ b/web/layouts/user-profile-layout/layout.tsx
@@ -28,9 +28,8 @@ export const ProfileAuthWrapper: React.FC = observer((props) => {
const isAuthorizedPath = router.pathname.includes("assigned" || "created" || "subscribed");
return (
-
-
-
+
+
{isAuthorized || !isAuthorizedPath ? (
{children}
@@ -40,6 +39,8 @@ export const ProfileAuthWrapper: React.FC = observer((props) => {
)}
+
+
);
});
diff --git a/web/pages/[workspaceSlug]/profile/[userId]/assigned.tsx b/web/pages/[workspaceSlug]/profile/[userId]/assigned.tsx
index fe5de04541..0808b9503e 100644
--- a/web/pages/[workspaceSlug]/profile/[userId]/assigned.tsx
+++ b/web/pages/[workspaceSlug]/profile/[userId]/assigned.tsx
@@ -12,7 +12,7 @@ const ProfileAssignedIssuesPage: NextPageWithLayout = () => }>
+ }>
{page}
);
diff --git a/web/pages/[workspaceSlug]/profile/[userId]/created.tsx b/web/pages/[workspaceSlug]/profile/[userId]/created.tsx
index d91dd8d658..76f4c1fbac 100644
--- a/web/pages/[workspaceSlug]/profile/[userId]/created.tsx
+++ b/web/pages/[workspaceSlug]/profile/[userId]/created.tsx
@@ -14,7 +14,7 @@ const ProfileCreatedIssuesPage: NextPageWithLayout = () => }>
+ }>
{page}
);
diff --git a/web/pages/[workspaceSlug]/profile/[userId]/index.tsx b/web/pages/[workspaceSlug]/profile/[userId]/index.tsx
index f56a8c14f3..486d8d7e3f 100644
--- a/web/pages/[workspaceSlug]/profile/[userId]/index.tsx
+++ b/web/pages/[workspaceSlug]/profile/[userId]/index.tsx
@@ -56,7 +56,7 @@ const ProfileOverviewPage: NextPageWithLayout = () => {
ProfileOverviewPage.getLayout = function getLayout(page: ReactElement) {
return (
- }>
+ }>
{page}
);
diff --git a/web/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx b/web/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx
index 44d802a1bb..257c236558 100644
--- a/web/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx
+++ b/web/pages/[workspaceSlug]/profile/[userId]/subscribed.tsx
@@ -14,7 +14,7 @@ const ProfileSubscribedIssuesPage: NextPageWithLayout = () => }>
+ }>
{page}
);
diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx
index 81f3c8e4ea..7860688fd5 100644
--- a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx
+++ b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx
@@ -57,9 +57,6 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
// toast alert
const { setToastAlert } = useToast();
- //TODO:fix reload confirmations, with mobx
- const { setShowAlert } = useReloadConfirmations();
-
const { handleSubmit, setValue, watch, getValues, control, reset } = useForm ({
defaultValues: { name: "", description_html: "" },
});
@@ -92,6 +89,8 @@ const PageDetailsPage: NextPageWithLayout = observer(() => {
const pageStore = usePage(pageId as string);
+ const { setShowAlert } = useReloadConfirmations(pageStore?.isSubmitting === "submitting");
+
useEffect(
() => () => {
if (pageStore) {
diff --git a/web/pages/_document.tsx b/web/pages/_document.tsx
index 54ad4782b2..cc0411068c 100644
--- a/web/pages/_document.tsx
+++ b/web/pages/_document.tsx
@@ -50,16 +50,6 @@ class MyDocument extends Document {
src="https://plausible.io/js/script.js"
/>
)}
- {process.env.NEXT_PUBLIC_POSTHOG_KEY && process.env.NEXT_PUBLIC_POSTHOG_HOST && (
-
- )}
-
|