forked from github/plane
fix: minor bug fixes and quality of life improvements (#3444)
* add concurrency to dev command to avaoid erroring out * add context to issue activity * minor quality of life improvement for exporter modal * show the option to save draft issue only when there is content in name and description * maintain commonality while referencing the user in activity * fix minor changes in draft save issue modal logical condition * minor change is state component for filter selection * change logic for create issue activity * change use last draft issue button to state control over previous on hover as that was inconsistent
This commit is contained in:
parent
f27efb80e1
commit
47681fe9f8
@ -15,7 +15,7 @@
|
||||
],
|
||||
"scripts": {
|
||||
"build": "turbo run build",
|
||||
"dev": "turbo run dev",
|
||||
"dev": "turbo run dev --concurrency=13",
|
||||
"start": "turbo run start",
|
||||
"lint": "turbo run lint",
|
||||
"clean": "turbo run clean",
|
||||
|
@ -27,6 +27,7 @@ export const CustomSearchSelect = (props: ICustomSearchSelectProps) => {
|
||||
onChange,
|
||||
options,
|
||||
onOpen,
|
||||
onClose,
|
||||
optionsClassName = "",
|
||||
value,
|
||||
tabIndex,
|
||||
@ -58,7 +59,10 @@ export const CustomSearchSelect = (props: ICustomSearchSelectProps) => {
|
||||
setIsOpen(true);
|
||||
if (referenceElement) referenceElement.focus();
|
||||
};
|
||||
const closeDropdown = () => setIsOpen(false);
|
||||
const closeDropdown = () => {
|
||||
setIsOpen(false);
|
||||
onClose && onClose();
|
||||
};
|
||||
const handleKeyDown = useDropdownKeyDown(openDropdown, closeDropdown, isOpen);
|
||||
useOutsideClickDetector(dropdownRef, closeDropdown);
|
||||
|
||||
|
@ -36,6 +36,7 @@ export interface ICustomSelectProps extends IDropdownProps {
|
||||
interface CustomSearchSelectProps {
|
||||
footerOption?: JSX.Element;
|
||||
onChange: any;
|
||||
onClose?: () => void;
|
||||
options:
|
||||
| {
|
||||
value: any;
|
||||
|
@ -26,7 +26,7 @@ import { capitalizeFirstLetter } from "helpers/string.helper";
|
||||
// types
|
||||
import { IIssueActivity } from "@plane/types";
|
||||
|
||||
const IssueLink = ({ activity }: { activity: IIssueActivity }) => {
|
||||
export const IssueLink = ({ activity }: { activity: IIssueActivity }) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
@ -341,7 +341,9 @@ const activityDetails: {
|
||||
if (activity.verb === "created")
|
||||
return (
|
||||
<>
|
||||
<span className="flex-shrink-0">added this issue to the cycle </span>
|
||||
<span className="flex-shrink-0">
|
||||
added {showIssue ? <IssueLink activity={activity} /> : "this issue"} to the cycle{" "}
|
||||
</span>
|
||||
<a
|
||||
href={`/${workspaceSlug}/projects/${activity.project}/cycles/${activity.new_identifier}`}
|
||||
target="_blank"
|
||||
@ -388,7 +390,7 @@ const activityDetails: {
|
||||
if (activity.verb === "created")
|
||||
return (
|
||||
<>
|
||||
added this issue to the module{" "}
|
||||
added {showIssue ? <IssueLink activity={activity} /> : "this issue"} to the module{" "}
|
||||
<a
|
||||
href={`/${workspaceSlug}/projects/${activity.project}/modules/${activity.new_identifier}`}
|
||||
target="_blank"
|
||||
@ -491,11 +493,11 @@ const activityDetails: {
|
||||
icon: <SignalMediumIcon size={12} color="#6b7280" aria-hidden="true" />,
|
||||
},
|
||||
relates_to: {
|
||||
message: (activity) => {
|
||||
message: (activity, showIssue) => {
|
||||
if (activity.old_value === "")
|
||||
return (
|
||||
<>
|
||||
marked that this issue relates to{" "}
|
||||
marked that {showIssue ? <IssueLink activity={activity} /> : "this issue"} relates to{" "}
|
||||
<span className="font-medium text-custom-text-100">{activity.new_value}</span>.
|
||||
</>
|
||||
);
|
||||
@ -509,11 +511,11 @@ const activityDetails: {
|
||||
icon: <RelatedIcon height="12" width="12" color="#6b7280" />,
|
||||
},
|
||||
blocking: {
|
||||
message: (activity) => {
|
||||
message: (activity, showIssue) => {
|
||||
if (activity.old_value === "")
|
||||
return (
|
||||
<>
|
||||
marked this issue is blocking issue{" "}
|
||||
marked {showIssue ? <IssueLink activity={activity} /> : "this issue"} is blocking issue{" "}
|
||||
<span className="font-medium text-custom-text-100">{activity.new_value}</span>.
|
||||
</>
|
||||
);
|
||||
@ -527,18 +529,18 @@ const activityDetails: {
|
||||
icon: <BlockerIcon height="12" width="12" color="#6b7280" />,
|
||||
},
|
||||
blocked_by: {
|
||||
message: (activity) => {
|
||||
message: (activity, showIssue) => {
|
||||
if (activity.old_value === "")
|
||||
return (
|
||||
<>
|
||||
marked this issue is being blocked by{" "}
|
||||
marked {showIssue ? <IssueLink activity={activity} /> : "this issue"} is being blocked by{" "}
|
||||
<span className="font-medium text-custom-text-100">{activity.new_value}</span>.
|
||||
</>
|
||||
);
|
||||
else
|
||||
return (
|
||||
<>
|
||||
removed this issue being blocked by issue{" "}
|
||||
removed {showIssue ? <IssueLink activity={activity} /> : "this issue"} being blocked by issue{" "}
|
||||
<span className="font-medium text-custom-text-100">{activity.old_value}</span>.
|
||||
</>
|
||||
);
|
||||
@ -546,18 +548,18 @@ const activityDetails: {
|
||||
icon: <BlockedIcon height="12" width="12" color="#6b7280" />,
|
||||
},
|
||||
duplicate: {
|
||||
message: (activity) => {
|
||||
message: (activity, showIssue) => {
|
||||
if (activity.old_value === "")
|
||||
return (
|
||||
<>
|
||||
marked this issue as duplicate of{" "}
|
||||
marked {showIssue ? <IssueLink activity={activity} /> : "this issue"} as duplicate of{" "}
|
||||
<span className="font-medium text-custom-text-100">{activity.new_value}</span>.
|
||||
</>
|
||||
);
|
||||
else
|
||||
return (
|
||||
<>
|
||||
removed this issue as a duplicate of{" "}
|
||||
removed {showIssue ? <IssueLink activity={activity} /> : "this issue"} as a duplicate of{" "}
|
||||
<span className="font-medium text-custom-text-100">{activity.old_value}</span>.
|
||||
</>
|
||||
);
|
||||
|
@ -5,7 +5,7 @@ import { History } from "lucide-react";
|
||||
// hooks
|
||||
import { useDashboard, useUser } from "hooks/store";
|
||||
// components
|
||||
import { ActivityIcon, ActivityMessage } from "components/core";
|
||||
import { ActivityIcon, ActivityMessage, IssueLink } from "components/core";
|
||||
import { RecentActivityEmptyState, WidgetLoader, WidgetProps } from "components/dashboard/widgets";
|
||||
// ui
|
||||
import { Avatar } from "@plane/ui";
|
||||
@ -75,15 +75,7 @@ export const RecentActivityWidget: React.FC<WidgetProps> = observer((props) => {
|
||||
<ActivityMessage activity={activity} showIssue />
|
||||
) : (
|
||||
<span>
|
||||
created this{" "}
|
||||
<a
|
||||
href={`/${workspaceSlug}/projects/${activity.project}/issues/${activity.issue}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1 font-medium text-custom-text-200 hover:underline"
|
||||
>
|
||||
Issue.
|
||||
</a>
|
||||
created <IssueLink activity={activity} />
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
|
@ -28,6 +28,7 @@ export const Exporter: React.FC<Props> = observer((props) => {
|
||||
const { isOpen, handleClose, user, provider, mutateServices } = props;
|
||||
// states
|
||||
const [exportLoading, setExportLoading] = useState(false);
|
||||
const [isSelectOpen, setIsSelectOpen] = useState(false);
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
@ -91,7 +92,13 @@ export const Exporter: React.FC<Props> = observer((props) => {
|
||||
|
||||
return (
|
||||
<Transition.Root show={isOpen} as={React.Fragment}>
|
||||
<Dialog as="div" className="relative z-20" onClose={handleClose}>
|
||||
<Dialog
|
||||
as="div"
|
||||
className="relative z-20"
|
||||
onClose={() => {
|
||||
if (!isSelectOpen) handleClose();
|
||||
}}
|
||||
>
|
||||
<Transition.Child
|
||||
as={React.Fragment}
|
||||
enter="ease-out duration-300"
|
||||
@ -142,6 +149,8 @@ export const Exporter: React.FC<Props> = observer((props) => {
|
||||
.join(", ")
|
||||
: "All projects"
|
||||
}
|
||||
onOpen={() => setIsSelectOpen(true)}
|
||||
onClose={() => setIsSelectOpen(false)}
|
||||
optionsClassName="min-w-full"
|
||||
multiple
|
||||
/>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
// components
|
||||
import { FilterHeader, FilterOption } from "components/issues";
|
||||
// ui
|
||||
@ -13,7 +14,7 @@ type Props = {
|
||||
states: IState[] | undefined;
|
||||
};
|
||||
|
||||
export const FilterState: React.FC<Props> = (props) => {
|
||||
export const FilterState: React.FC<Props> = observer((props) => {
|
||||
const { appliedFilters, handleUpdate, searchQuery, states } = props;
|
||||
|
||||
const [itemsToRender, setItemsToRender] = useState(5);
|
||||
@ -75,4 +76,4 @@ export const FilterState: React.FC<Props> = (props) => {
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -194,7 +194,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
||||
const handleFormChange = () => {
|
||||
if (!onChange) return;
|
||||
|
||||
if (isDirty) onChange(watch());
|
||||
if (isDirty && (watch("name") || watch("description_html"))) onChange(watch());
|
||||
else onChange(null);
|
||||
};
|
||||
|
||||
|
@ -1,9 +1,12 @@
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR from "swr";
|
||||
import { observer } from "mobx-react";
|
||||
//hooks
|
||||
import { useUser } from "hooks/store";
|
||||
// services
|
||||
import { UserService } from "services/user.service";
|
||||
// components
|
||||
import { ActivityMessage } from "components/core";
|
||||
import { ActivityMessage, IssueLink } from "components/core";
|
||||
// ui
|
||||
import { ProfileEmptyState } from "components/ui";
|
||||
import { Loader } from "@plane/ui";
|
||||
@ -17,9 +20,11 @@ import { USER_PROFILE_ACTIVITY } from "constants/fetch-keys";
|
||||
// services
|
||||
const userService = new UserService();
|
||||
|
||||
export const ProfileActivity = () => {
|
||||
export const ProfileActivity = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, userId } = router.query;
|
||||
// store hooks
|
||||
const { currentUser } = useUser();
|
||||
|
||||
const { data: userProfileActivity } = useSWR(
|
||||
workspaceSlug && userId ? USER_PROFILE_ACTIVITY(workspaceSlug.toString(), userId.toString()) : null,
|
||||
@ -54,20 +59,14 @@ export const ProfileActivity = () => {
|
||||
</div>
|
||||
<div className="-mt-1 w-4/5 break-words">
|
||||
<p className="text-sm text-custom-text-200">
|
||||
<span className="font-medium text-custom-text-100">{activity.actor_detail.display_name} </span>
|
||||
<span className="font-medium text-custom-text-100">
|
||||
{currentUser?.id === activity.actor_detail.id ? "You" : activity.actor_detail.display_name}{" "}
|
||||
</span>
|
||||
{activity.field ? (
|
||||
<ActivityMessage activity={activity} showIssue />
|
||||
) : (
|
||||
<span>
|
||||
created this{" "}
|
||||
<a
|
||||
href={`/${workspaceSlug}/projects/${activity.project}/issues/${activity.issue}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1 font-medium text-custom-text-200 hover:underline"
|
||||
>
|
||||
Issue.
|
||||
</a>
|
||||
created <IssueLink activity={activity} />
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
@ -95,4 +94,4 @@ export const ProfileActivity = () => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ChevronUp, PenSquare, Search } from "lucide-react";
|
||||
// hooks
|
||||
@ -25,10 +25,26 @@ export const WorkspaceSidebarQuickAction = observer(() => {
|
||||
|
||||
const { storedValue, clearValue } = useLocalStorage<any>("draftedIssue", JSON.stringify({}));
|
||||
|
||||
//useState control for displaying draft issue button instead of group hover
|
||||
const [isDraftButtonOpen, setIsDraftButtonOpen] = useState(false);
|
||||
|
||||
const timeoutRef = useRef<any>();
|
||||
|
||||
const isSidebarCollapsed = themeStore.sidebarCollapsed;
|
||||
|
||||
const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
|
||||
|
||||
const onMouseEnter = () => {
|
||||
//if renet before timout clear the timeout
|
||||
timeoutRef?.current && clearTimeout(timeoutRef.current);
|
||||
setIsDraftButtonOpen(true);
|
||||
};
|
||||
|
||||
const onMouseLeave = () => {
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
setIsDraftButtonOpen(false);
|
||||
}, 300);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<CreateUpdateDraftIssueModal
|
||||
@ -45,10 +61,12 @@ export const WorkspaceSidebarQuickAction = observer(() => {
|
||||
className={`mt-4 flex w-full cursor-pointer items-center justify-between px-4 ${
|
||||
isSidebarCollapsed ? "flex-col gap-1" : "gap-2"
|
||||
}`}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
{isAuthorizedUser && (
|
||||
<div
|
||||
className={`group relative flex w-full cursor-pointer items-center justify-between gap-1 rounded px-2 ${
|
||||
className={`relative flex w-full cursor-pointer items-center justify-between gap-1 rounded px-2 ${
|
||||
isSidebarCollapsed
|
||||
? "px-2 hover:bg-custom-sidebar-background-80"
|
||||
: "border-[0.5px] border-custom-border-200 px-3 shadow-custom-sidebar-shadow-2xs"
|
||||
@ -80,12 +98,16 @@ export const WorkspaceSidebarQuickAction = observer(() => {
|
||||
isSidebarCollapsed ? "hidden" : "block"
|
||||
}`}
|
||||
>
|
||||
<ChevronUp className="h-4 w-4 rotate-180 transform !text-custom-sidebar-text-300 transition-transform duration-300 group-hover:rotate-0" />
|
||||
<ChevronUp
|
||||
className={`h-4 w-4 rotate-180 transform !text-custom-sidebar-text-300 transition-transform duration-300 ${
|
||||
isDraftButtonOpen ? "rotate-0" : ""
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
|
||||
<div
|
||||
className={`pointer-events-none fixed left-4 mt-0 h-10 w-[203px] pt-2 opacity-0 group-hover:pointer-events-auto group-hover:opacity-100 ${
|
||||
isSidebarCollapsed ? "top-[5.5rem]" : "top-24"
|
||||
className={`fixed left-4 mt-0 h-10 w-[203px] pt-2 ${isSidebarCollapsed ? "top-[5.5rem]" : "top-24"} ${
|
||||
isDraftButtonOpen ? "block" : "hidden"
|
||||
}`}
|
||||
>
|
||||
<div className="h-full w-full">
|
||||
|
@ -1,6 +1,9 @@
|
||||
import { ReactElement } from "react";
|
||||
import useSWR from "swr";
|
||||
import Link from "next/link";
|
||||
import { observer } from "mobx-react";
|
||||
//hooks
|
||||
import { useUser } from "hooks/store";
|
||||
// services
|
||||
import { UserService } from "services/user.service";
|
||||
// layouts
|
||||
@ -21,8 +24,10 @@ import { NextPageWithLayout } from "lib/types";
|
||||
|
||||
const userService = new UserService();
|
||||
|
||||
const ProfileActivityPage: NextPageWithLayout = () => {
|
||||
const ProfileActivityPage: NextPageWithLayout = observer(() => {
|
||||
const { data: userActivity } = useSWR(USER_ACTIVITY, () => userService.getUserActivity());
|
||||
// store hooks
|
||||
const { currentUser } = useUser();
|
||||
|
||||
return (
|
||||
<section className="mx-auto mt-16 flex h-full w-full flex-col overflow-hidden px-8 pb-8 lg:w-3/5">
|
||||
@ -158,7 +163,9 @@ const ProfileActivityPage: NextPageWithLayout = () => {
|
||||
href={`/${activityItem.workspace_detail.slug}/profile/${activityItem.actor_detail.id}`}
|
||||
>
|
||||
<span className="text-gray font-medium">
|
||||
{activityItem.actor_detail.display_name}
|
||||
{currentUser?.id === activityItem.actor_detail.id
|
||||
? "You"
|
||||
: activityItem.actor_detail.display_name}
|
||||
</span>
|
||||
</Link>
|
||||
)}{" "}
|
||||
@ -189,7 +196,7 @@ const ProfileActivityPage: NextPageWithLayout = () => {
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
ProfileActivityPage.getLayout = function getLayout(page: ReactElement) {
|
||||
return <ProfileSettingsLayout>{page}</ProfileSettingsLayout>;
|
||||
|
Loading…
Reference in New Issue
Block a user