diff --git a/.github/workflows/create-sync-pr.yml b/.github/workflows/create-sync-pr.yml index 28e47a0d6..d93aec13e 100644 --- a/.github/workflows/create-sync-pr.yml +++ b/.github/workflows/create-sync-pr.yml @@ -2,6 +2,8 @@ name: Create PR in Plane EE Repository to sync the changes on: pull_request: + branches: + - master types: - closed diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index f7ad735c1..8a974f868 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -57,7 +57,7 @@ from .workspace import ( LeaveWorkspaceEndpoint, ) from .state import StateViewSet -from .view import GlobalViewViewSet, GlobalViewIssuesViewSet, IssueViewViewSet, ViewIssuesEndpoint, IssueViewFavoriteViewSet +from .view import GlobalViewViewSet, GlobalViewIssuesViewSet, IssueViewViewSet, IssueViewFavoriteViewSet from .cycle import ( CycleViewSet, CycleIssueViewSet, diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index b5a62dd5d..2d13449fd 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -24,7 +24,6 @@ from django.core.serializers.json import DjangoJSONEncoder from django.utils.decorators import method_decorator from django.views.decorators.gzip import gzip_page from django.db import IntegrityError -from django.db import IntegrityError # Third Party imports from rest_framework.response import Response @@ -84,7 +83,6 @@ from plane.db.models import ( from plane.bgtasks.issue_activites_task import issue_activity from plane.utils.grouper import group_results from plane.utils.issue_filters import issue_filters -from plane.bgtasks.export_task import issue_export_task class IssueViewSet(BaseViewSet): diff --git a/apiserver/plane/api/views/view.py b/apiserver/plane/api/views/view.py index 435f8725a..938dc1382 100644 --- a/apiserver/plane/api/views/view.py +++ b/apiserver/plane/api/views/view.py @@ -243,51 +243,6 @@ class IssueViewViewSet(BaseViewSet): ) -class ViewIssuesEndpoint(BaseAPIView): - permission_classes = [ - ProjectEntityPermission, - ] - - def get(self, request, slug, project_id, view_id): - try: - view = IssueView.objects.get(pk=view_id) - queries = view.query - - filters = issue_filters(request.query_params, "GET") - - issues = ( - Issue.issue_objects.filter( - **queries, project_id=project_id, workspace__slug=slug - ) - .filter(**filters) - .select_related("project") - .select_related("workspace") - .select_related("state") - .select_related("parent") - .prefetch_related("assignees") - .prefetch_related("labels") - .prefetch_related( - Prefetch( - "issue_reactions", - queryset=IssueReaction.objects.select_related("actor"), - ) - ) - ) - - serializer = IssueLiteSerializer(issues, many=True) - return Response(serializer.data, status=status.HTTP_200_OK) - except IssueView.DoesNotExist: - return Response( - {"error": "Issue View does not exist"}, status=status.HTTP_404_NOT_FOUND - ) - except Exception as e: - capture_exception(e) - return Response( - {"error": "Something went wrong please try again later"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - class IssueViewFavoriteViewSet(BaseViewSet): serializer_class = IssueViewFavoriteSerializer model = IssueViewFavorite diff --git a/apiserver/plane/utils/issue_filters.py b/apiserver/plane/utils/issue_filters.py index dae301c38..f3f180645 100644 --- a/apiserver/plane/utils/issue_filters.py +++ b/apiserver/plane/utils/issue_filters.py @@ -1,5 +1,62 @@ -from django.utils.timezone import make_aware -from django.utils.dateparse import parse_datetime +import re +from datetime import timedelta +from django.utils import timezone + +# The date from pattern +pattern = re.compile(r"\d+_(weeks|months)$") + + +# Get the 2_weeks, 3_months +def string_date_filter(filter, duration, subsequent, term, date_filter, offset): + now = timezone.now().date() + if term == "months": + if subsequent == "after": + if offset == "fromnow": + filter[f"{date_filter}__gte"] = now + timedelta(days=duration * 30) + else: + filter[f"{date_filter}__gte"] = now - timedelta(days=duration * 30) + else: + if offset == "fromnow": + filter[f"{date_filter}__lte"] = now + timedelta(days=duration * 30) + else: + filter[f"{date_filter}__lte"] = now - timedelta(days=duration * 30) + if term == "weeks": + if subsequent == "after": + if offset == "fromnow": + filter[f"{date_filter}__gte"] = now + timedelta(weeks=duration) + else: + filter[f"{date_filter}__gte"] = now - timedelta(weeks=duration) + else: + if offset == "fromnow": + filter[f"{date_filter}__lte"] = now + timedelta(days=duration) + else: + filter[f"{date_filter}__lte"] = now - timedelta(days=duration) + + +def date_filter(filter, date_term, queries): + """ + Handle all date filters + """ + for query in queries: + date_query = query.split(";") + if len(date_query) >= 2: + match = pattern.match(date_query[0]) + if match: + if len(date_query) == 3: + digit, term = date_query[0].split("_") + string_date_filter( + filter=filter, + duration=int(digit), + subsequent=date_query[1], + term=term, + date_filter="created_at__date", + offset=date_query[2], + ) + else: + if "after" in date_query: + filter[f"{date_term}__gte"] = date_query[0] + else: + filter[f"{date_term}__lte"] = date_query[0] def filter_state(params, filter, method): @@ -97,20 +154,10 @@ def filter_created_at(params, filter, method): if method == "GET": created_ats = params.get("created_at").split(",") if len(created_ats) and "" not in created_ats: - for query in created_ats: - created_at_query = query.split(";") - if len(created_at_query) == 2 and "after" in created_at_query: - filter["created_at__date__gte"] = created_at_query[0] - else: - filter["created_at__date__lte"] = created_at_query[0] + date_filter(filter=filter, date_term="created_at__date", queries=created_ats) else: if params.get("created_at", None) and len(params.get("created_at")): - for query in params.get("created_at"): - created_at_query = query.split(";") - if len(created_at_query) == 2 and "after" in created_at_query: - filter["created_at__date__gte"] = created_at_query[0] - else: - filter["created_at__date__lte"] = created_at_query[0] + date_filter(filter=filter, date_term="created_at__date", queries=params.get("created_at", [])) return filter @@ -118,20 +165,10 @@ def filter_updated_at(params, filter, method): if method == "GET": updated_ats = params.get("updated_at").split(",") if len(updated_ats) and "" not in updated_ats: - for query in updated_ats: - updated_at_query = query.split(";") - if len(updated_at_query) == 2 and "after" in updated_at_query: - filter["updated_at__date__gte"] = updated_at_query[0] - else: - filter["updated_at__date__lte"] = updated_at_query[0] + date_filter(filter=filter, date_term="created_at__date", queries=updated_ats) else: if params.get("updated_at", None) and len(params.get("updated_at")): - for query in params.get("updated_at"): - updated_at_query = query.split(";") - if len(updated_at_query) == 2 and "after" in updated_at_query: - filter["updated_at__date__gte"] = updated_at_query[0] - else: - filter["updated_at__date__lte"] = updated_at_query[0] + date_filter(filter=filter, date_term="created_at__date", queries=params.get("updated_at", [])) return filter @@ -139,20 +176,10 @@ def filter_start_date(params, filter, method): if method == "GET": start_dates = params.get("start_date").split(",") if len(start_dates) and "" not in start_dates: - for query in start_dates: - start_date_query = query.split(";") - if len(start_date_query) == 2 and "after" in start_date_query: - filter["start_date__gte"] = start_date_query[0] - else: - filter["start_date__lte"] = start_date_query[0] + date_filter(filter=filter, date_term="start_date", queries=start_dates) else: if params.get("start_date", None) and len(params.get("start_date")): - for query in params.get("start_date"): - start_date_query = query.split(";") - if len(start_date_query) == 2 and "after" in start_date_query: - filter["start_date__gte"] = start_date_query[0] - else: - filter["start_date__lte"] = start_date_query[0] + date_filter(filter=filter, date_term="start_date", queries=params.get("start_date", [])) return filter @@ -160,21 +187,11 @@ def filter_target_date(params, filter, method): if method == "GET": target_dates = params.get("target_date").split(",") if len(target_dates) and "" not in target_dates: - for query in target_dates: - target_date_query = query.split(";") - if len(target_date_query) == 2 and "after" in target_date_query: - filter["target_date__gte"] = target_date_query[0] - else: - filter["target_date__lte"] = target_date_query[0] + + date_filter(filter=filter, date_term="target_date", queries=target_dates) else: if params.get("target_date", None) and len(params.get("target_date")): - for query in params.get("target_date"): - target_date_query = query.split(";") - if len(target_date_query) == 2 and "after" in target_date_query: - filter["target_date__gte"] = target_date_query[0] - else: - filter["target_date__lte"] = target_date_query[0] - + date_filter(filter=filter, date_term="target_date", queries=params.get("target_date", [])) return filter @@ -182,20 +199,10 @@ def filter_completed_at(params, filter, method): if method == "GET": completed_ats = params.get("completed_at").split(",") if len(completed_ats) and "" not in completed_ats: - for query in completed_ats: - completed_at_query = query.split(";") - if len(completed_at_query) == 2 and "after" in completed_at_query: - filter["completed_at__date__gte"] = completed_at_query[0] - else: - filter["completed_at__lte"] = completed_at_query[0] + date_filter(filter=filter, date_term="completed_at__date", queries=completed_ats) else: if params.get("completed_at", None) and len(params.get("completed_at")): - for query in params.get("completed_at"): - completed_at_query = query.split(";") - if len(completed_at_query) == 2 and "after" in completed_at_query: - filter["completed_at__date__gte"] = completed_at_query[0] - else: - filter["completed_at__lte"] = completed_at_query[0] + date_filter(filter=filter, date_term="completed_at__date", queries=params.get("completed_at", [])) return filter diff --git a/web/components/automation/auto-archive-automation.tsx b/web/components/automation/auto-archive-automation.tsx index d0c9a1be6..5aed7e852 100644 --- a/web/components/automation/auto-archive-automation.tsx +++ b/web/components/automation/auto-archive-automation.tsx @@ -74,7 +74,6 @@ export const AutoArchiveAutomation: React.FC = ({ handleChange({ archive_in: val }); }} input - verticalPosition="bottom" width="w-full" disabled={disabled} > diff --git a/web/components/core/theme/theme-switch.tsx b/web/components/core/theme/theme-switch.tsx index 687998c25..56d07fbad 100644 --- a/web/components/core/theme/theme-switch.tsx +++ b/web/components/core/theme/theme-switch.tsx @@ -100,7 +100,6 @@ export const ThemeSwitch: React.FC = observer( }} input width="w-full" - position="right" > {THEMES_OBJ.map(({ value, label, type, icon }) => ( diff --git a/web/components/core/views/board-view/single-board.tsx b/web/components/core/views/board-view/single-board.tsx index 6d583e772..1325f8cab 100644 --- a/web/components/core/views/board-view/single-board.tsx +++ b/web/components/core/views/board-view/single-board.tsx @@ -255,15 +255,11 @@ export const SingleBoard: React.FC = (props) => { !isDraftIssuesPage && ( +
Add Issue - +
} - position="left" noBorder > = ({ {type && !isNotAllowed && ( setIsMenuActive(!isMenuActive)} > - + } > = (props) => { } - position="right" noBorder > setIsCreateIssueFormOpen(true)}> diff --git a/web/components/core/views/spreadsheet-view/spreadsheet-view.tsx b/web/components/core/views/spreadsheet-view/spreadsheet-view.tsx index 0d9214a36..3a5108410 100644 --- a/web/components/core/views/spreadsheet-view/spreadsheet-view.tsx +++ b/web/components/core/views/spreadsheet-view/spreadsheet-view.tsx @@ -255,7 +255,6 @@ export const SpreadsheetView: React.FC = ({ = ({ +
New Issue - +
} - position="left" - verticalPosition="top" optionsClassName="left-5 !w-36" noBorder > diff --git a/web/components/integration/github/select-repository.tsx b/web/components/integration/github/select-repository.tsx index b46942e6d..ddc5e3a8a 100644 --- a/web/components/integration/github/select-repository.tsx +++ b/web/components/integration/github/select-repository.tsx @@ -92,7 +92,6 @@ export const SelectRepository: React.FC = ({ )} } - position="right" optionsClassName="w-full" /> ); diff --git a/web/components/integration/jira/give-details.tsx b/web/components/integration/jira/give-details.tsx index 3fc5d9641..fa04c3404 100644 --- a/web/components/integration/jira/give-details.tsx +++ b/web/components/integration/jira/give-details.tsx @@ -143,7 +143,6 @@ export const JiraGetImportDetail: React.FC = () => { )} } - verticalPosition="top" > {projects && projects.length > 0 ? ( projects.map((project) => ( diff --git a/web/components/issues/comment/comment-reaction.tsx b/web/components/issues/comment/comment-reaction.tsx index b6ce3bbbc..d84e2b9ff 100644 --- a/web/components/issues/comment/comment-reaction.tsx +++ b/web/components/issues/comment/comment-reaction.tsx @@ -13,10 +13,11 @@ import { renderEmoji } from "helpers/emoji.helper"; type Props = { projectId?: string | string[]; commentId: string; + readonly?: boolean; }; export const CommentReaction: React.FC = (props) => { - const { projectId, commentId } = props; + const { projectId, commentId, readonly = false } = props; const router = useRouter(); const { workspaceSlug } = router.query; @@ -47,16 +48,18 @@ export const CommentReaction: React.FC = (props) => { return (
- reaction.actor === user?.id) - .map((r) => r.reaction) || [] - } - onSelect={handleReactionClick} - /> + {!readonly && ( + reaction.actor === user?.id) + .map((r) => r.reaction) || [] + } + onSelect={handleReactionClick} + /> + )} {Object.keys(groupedReactions || {}).map( (reaction) => @@ -64,6 +67,7 @@ export const CommentReaction: React.FC = (props) => { groupedReactions[reaction].length > 0 && ( } - position="left" > {peekModes.map((mode) => ( diff --git a/web/components/issues/select/estimate.tsx b/web/components/issues/select/estimate.tsx index 0d910013b..7ac86f6b9 100644 --- a/web/components/issues/select/estimate.tsx +++ b/web/components/issues/select/estimate.tsx @@ -33,7 +33,6 @@ export const IssueEstimateSelect: React.FC = ({ value, onChange }) => {
} onChange={onChange} - position="right" width="w-full min-w-[8rem]" noChevron > diff --git a/web/components/issues/sidebar-select/cycle.tsx b/web/components/issues/sidebar-select/cycle.tsx index 8fbfcf707..c5bc84861 100644 --- a/web/components/issues/sidebar-select/cycle.tsx +++ b/web/components/issues/sidebar-select/cycle.tsx @@ -47,7 +47,7 @@ export const SidebarCycleSelect: React.FC = ({ issuesService .removeIssueFromCycle(workspaceSlug as string, projectId as string, cycleId, bridgeId) - .then((res) => { + .then(() => { mutate(ISSUE_DETAILS(issueId as string)); mutate(CYCLE_ISSUES(cycleId)); @@ -91,7 +91,6 @@ export const SidebarCycleSelect: React.FC = ({ : handleCycleChange(incompleteCycles?.find((c) => c.id === value) as ICycle); }} width="w-full" - position="right" maxHeight="rg" disabled={disabled} > diff --git a/web/components/issues/sidebar-select/estimate.tsx b/web/components/issues/sidebar-select/estimate.tsx index 7ebdfe2b9..75c168853 100644 --- a/web/components/issues/sidebar-select/estimate.tsx +++ b/web/components/issues/sidebar-select/estimate.tsx @@ -20,17 +20,14 @@ export const SidebarEstimateSelect: React.FC = ({ value, onChange, disabl +
{estimatePoints?.find((e) => e.key === value)?.value ?? "No estimate"} - +
} onChange={onChange} disabled={disabled} diff --git a/web/components/issues/sidebar-select/module.tsx b/web/components/issues/sidebar-select/module.tsx index c9599bdf4..edb95b983 100644 --- a/web/components/issues/sidebar-select/module.tsx +++ b/web/components/issues/sidebar-select/module.tsx @@ -87,7 +87,6 @@ export const SidebarModuleSelect: React.FC = ({ : handleModuleChange(modules?.find((m) => m.id === value) as IModule); }} width="w-full" - position="right" maxHeight="rg" disabled={disabled} > diff --git a/web/components/issues/sidebar-select/priority.tsx b/web/components/issues/sidebar-select/priority.tsx index 543f79707..512aba5c6 100644 --- a/web/components/issues/sidebar-select/priority.tsx +++ b/web/components/issues/sidebar-select/priority.tsx @@ -18,8 +18,7 @@ type Props = { export const SidebarPrioritySelect: React.FC = ({ value, onChange, disabled = false }) => ( = ({ value, onChange, disabl {value ?? "None"} - + } value={value} onChange={onChange} diff --git a/web/components/issues/sidebar-select/state.tsx b/web/components/issues/sidebar-select/state.tsx index cf2cfe3b2..7ff4f13b8 100644 --- a/web/components/issues/sidebar-select/state.tsx +++ b/web/components/issues/sidebar-select/state.tsx @@ -39,7 +39,7 @@ export const SidebarStateSelect: React.FC = ({ value, onChange, disabled return ( +
{selectedState ? (
@@ -53,12 +53,11 @@ export const SidebarStateSelect: React.FC = ({ value, onChange, disabled ) : ( "None" )} - +
} value={value} onChange={onChange} optionsClassName="w-min" - position="left" disabled={disabled} > {states ? ( diff --git a/web/components/issues/sub-issues/root.tsx b/web/components/issues/sub-issues/root.tsx index 4b29b97c9..a2c6c81a4 100644 --- a/web/components/issues/sub-issues/root.tsx +++ b/web/components/issues/sub-issues/root.tsx @@ -247,7 +247,6 @@ export const SubIssuesRoot: React.FC = ({ parentIssue, user }) = } buttonClassName="whitespace-nowrap" - position="left" noBorder noChevron > @@ -283,7 +282,6 @@ export const SubIssuesRoot: React.FC = ({ parentIssue, user }) = } buttonClassName="whitespace-nowrap" - position="left" noBorder noChevron > diff --git a/web/components/issues/view-select/assignee.tsx b/web/components/issues/view-select/assignee.tsx index 1026f240d..807d343e2 100644 --- a/web/components/issues/view-select/assignee.tsx +++ b/web/components/issues/view-select/assignee.tsx @@ -113,10 +113,8 @@ export const ViewAssigneeSelect: React.FC = ({ {...(customButton ? { customButton: assigneeLabel } : { label: assigneeLabel })} multiple noChevron - position={position} disabled={isNotAllowed} onOpen={() => setFetchAssignees(true)} - selfPositioned={selfPositioned} width="w-full min-w-[12rem]" /> ); diff --git a/web/components/issues/view-select/estimate.tsx b/web/components/issues/view-select/estimate.tsx index bef060e77..0edf5e33f 100644 --- a/web/components/issues/view-select/estimate.tsx +++ b/web/components/issues/view-select/estimate.tsx @@ -74,8 +74,6 @@ export const ViewEstimateSelect: React.FC = ({ maxHeight="md" noChevron disabled={isNotAllowed} - position={position} - selfPositioned={selfPositioned} width="w-full min-w-[8rem]" > diff --git a/web/components/issues/view-select/label.tsx b/web/components/issues/view-select/label.tsx index 3a75763b2..ae3fe1f50 100644 --- a/web/components/issues/view-select/label.tsx +++ b/web/components/issues/view-select/label.tsx @@ -146,9 +146,7 @@ export const ViewLabelSelect: React.FC = ({ {...(customButton ? { customButton: labelsLabel } : { label: labelsLabel })} multiple noChevron - position={position} disabled={isNotAllowed} - selfPositioned={selfPositioned} footerOption={footerOption} width="w-full min-w-[12rem]" /> diff --git a/web/components/issues/view-select/priority.tsx b/web/components/issues/view-select/priority.tsx index 1bdbf3428..f19b682fc 100644 --- a/web/components/issues/view-select/priority.tsx +++ b/web/components/issues/view-select/priority.tsx @@ -59,8 +59,7 @@ export const ViewPrioritySelect: React.FC = ({ }} maxHeight="md" customButton={ - +
} noChevron disabled={isNotAllowed} - position={position} - selfPositioned={selfPositioned} > {PRIORITIES?.map((priority) => ( diff --git a/web/components/modules/sidebar-select/select-lead.tsx b/web/components/modules/sidebar-select/select-lead.tsx index e00a5db25..46e08af0b 100644 --- a/web/components/modules/sidebar-select/select-lead.tsx +++ b/web/components/modules/sidebar-select/select-lead.tsx @@ -65,7 +65,6 @@ export const SidebarLeadSelect: React.FC = ({ value, onChange }) => { } options={options} maxHeight="md" - position="right" onChange={onChange} /> diff --git a/web/components/modules/sidebar-select/select-members.tsx b/web/components/modules/sidebar-select/select-members.tsx index 185f70cec..7aa1e8e5e 100644 --- a/web/components/modules/sidebar-select/select-members.tsx +++ b/web/components/modules/sidebar-select/select-members.tsx @@ -64,7 +64,6 @@ export const SidebarMembersSelect: React.FC = ({ value, onChange }) => { options={options} onChange={onChange} maxHeight="md" - position="right" multiple /> diff --git a/web/components/notifications/notification-card.tsx b/web/components/notifications/notification-card.tsx index 29fe579a8..acbd1c0c9 100644 --- a/web/components/notifications/notification-card.tsx +++ b/web/components/notifications/notification-card.tsx @@ -214,12 +214,9 @@ export const NotificationCard: React.FC = (props) => { e.stopPropagation(); }} customButton={ - + } optionsClassName="!z-20" > diff --git a/web/components/onboarding/user-details.tsx b/web/components/onboarding/user-details.tsx index 2911bbc80..8ac8b2070 100644 --- a/web/components/onboarding/user-details.tsx +++ b/web/components/onboarding/user-details.tsx @@ -167,7 +167,6 @@ export const UserDetails: React.FC = ({ user }) => { } input width="w-full" - verticalPosition="top" > {USER_ROLES.map((item) => ( @@ -197,7 +196,6 @@ export const UserDetails: React.FC = ({ user }) => { } options={timeZoneOptions} onChange={onChange} - verticalPosition="top" optionsClassName="w-full" input /> diff --git a/web/components/pages/single-page-block.tsx b/web/components/pages/single-page-block.tsx index e4c1d94ac..18fba83bc 100644 --- a/web/components/pages/single-page-block.tsx +++ b/web/components/pages/single-page-block.tsx @@ -384,12 +384,12 @@ export const SinglePageBlock: React.FC = ({ setIsMenuActive(!isMenuActive)} > - + } > {block.issue ? ( diff --git a/web/components/project/create-project-modal.tsx b/web/components/project/create-project-modal.tsx index 5593d8c7c..e0afea1ce 100644 --- a/web/components/project/create-project-modal.tsx +++ b/web/components/project/create-project-modal.tsx @@ -418,7 +418,6 @@ export const CreateProjectModal: React.FC = ({ )} } - verticalPosition="top" noChevron /> ); diff --git a/web/components/project/label-select.tsx b/web/components/project/label-select.tsx index c155dea14..a34d778f9 100644 --- a/web/components/project/label-select.tsx +++ b/web/components/project/label-select.tsx @@ -1,13 +1,13 @@ -import React, { useRef, useState } from "react"; +import React, { useState } from "react"; import useSWR from "swr"; import { useRouter } from "next/router"; +// react-popper +import { usePopper } from "react-popper"; // services import issuesService from "services/issues.service"; -// hooks -import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown"; // headless ui import { Combobox } from "@headlessui/react"; // component @@ -19,6 +19,7 @@ import { PlusIcon } from "lucide-react"; // types import { Tooltip } from "components/ui"; import { ICurrentUserResponse, IIssueLabels } from "types"; +import { Placement } from "@popperjs/core"; // constants import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys"; @@ -31,6 +32,7 @@ type Props = { buttonClassName?: string; optionsClassName?: string; maxRender?: number; + placement?: Placement; hideDropdownArrow?: boolean; disabled?: boolean; user: ICurrentUserResponse | undefined; @@ -45,21 +47,25 @@ export const LabelSelect: React.FC = ({ buttonClassName = "", optionsClassName = "", maxRender = 2, + placement, hideDropdownArrow = false, disabled = false, user, }) => { const [query, setQuery] = useState(""); - const [isOpen, setIsOpen] = useState(false); const [fetchStates, setFetchStates] = useState(false); + const [referenceElement, setReferenceElement] = useState(null); + const [popperElement, setPopperElement] = useState(null); + const [labelModal, setLabelModal] = useState(false); const router = useRouter(); const { workspaceSlug } = router.query; - const dropdownBtn = useRef(null); - const dropdownOptions = useRef(null); + const { styles, attributes } = usePopper(referenceElement, popperElement, { + placement: placement ?? "bottom-start", + }); const { data: issueLabels } = useSWR( projectId && fetchStates ? PROJECT_ISSUE_LABELS(projectId) : null, @@ -131,8 +137,6 @@ export const LabelSelect: React.FC = ({ ); - useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions); - const footerOption = ( -
- +
@@ -234,8 +240,8 @@ export const LabelSelect: React.FC = ({ )}
{footerOption} - -
+
+ ); }} diff --git a/web/components/project/member-select.tsx b/web/components/project/member-select.tsx index 4fcb04268..0d29e8e8d 100644 --- a/web/components/project/member-select.tsx +++ b/web/components/project/member-select.tsx @@ -77,7 +77,6 @@ export const MemberSelect: React.FC = ({ value, onChange, isDisabled = fa ] } maxHeight="md" - position="right" width="w-full" onChange={onChange} disabled={isDisabled} diff --git a/web/components/project/members-select.tsx b/web/components/project/members-select.tsx index 57523df8d..42dd89ef4 100644 --- a/web/components/project/members-select.tsx +++ b/web/components/project/members-select.tsx @@ -1,9 +1,10 @@ -import React, { useRef, useState } from "react"; +import React, { useState } from "react"; import { useRouter } from "next/router"; +// react-popper +import { usePopper } from "react-popper"; // hooks -import useDynamicDropdownPosition from "hooks/use-dynamic-dropdown"; import useProjectMembers from "hooks/use-project-members"; import useWorkspaceMembers from "hooks/use-workspace-members"; // headless ui @@ -15,6 +16,7 @@ import { ChevronDownIcon } from "@heroicons/react/20/solid"; import { CheckIcon, MagnifyingGlassIcon } from "@heroicons/react/24/outline"; // types import { IUser } from "types"; +import { Placement } from "@popperjs/core"; type Props = { value: string | string[]; @@ -25,6 +27,7 @@ type Props = { className?: string; buttonClassName?: string; optionsClassName?: string; + placement?: Placement; hideDropdownArrow?: boolean; disabled?: boolean; }; @@ -38,18 +41,22 @@ export const MembersSelect: React.FC = ({ className = "", buttonClassName = "", optionsClassName = "", + placement, hideDropdownArrow = false, disabled = false, }) => { const [query, setQuery] = useState(""); - const [isOpen, setIsOpen] = useState(false); const [fetchStates, setFetchStates] = useState(false); + const [referenceElement, setReferenceElement] = useState(null); + const [popperElement, setPopperElement] = useState(null); + const router = useRouter(); const { workspaceSlug } = router.query; - const dropdownBtn = useRef(null); - const dropdownOptions = useRef(null); + const { styles, attributes } = usePopper(referenceElement, popperElement, { + placement: placement ?? "bottom-start", + }); const { members } = useProjectMembers( workspaceSlug?.toString(), @@ -105,8 +112,6 @@ export const MembersSelect: React.FC = ({ ); - useDynamicDropdownPosition(isOpen, () => setIsOpen(false), dropdownBtn, dropdownOptions); - return ( = ({ multiple > {({ open }: { open: boolean }) => { - if (open) { - if (!isOpen) setIsOpen(true); - setFetchStates(true); - } else if (isOpen) setIsOpen(false); + if (open) setFetchStates(true); return ( <> - - {label} - {!hideDropdownArrow && !disabled && ( -