chore: refactored and resolved build issues on the issues and issue detail page (#3340)
* fix: handled undefined issue_id in list layout * dev: issue detail store and optimization * dev: issue filter and list operations * fix: typo on labels update * dev: Handled all issues in the list layout in project issues * dev: handled kanban and auick add issue in swimlanes * chore: fixed peekoverview in kanban * chore: fixed peekoverview in calendar * chore: fixed peekoverview in gantt * chore: updated quick add in the gantt chart * chore: handled issue detail properties and resolved build issues --------- Co-authored-by: pablohashescobar <nikhilschacko@gmail.com>
This commit is contained in:
parent
e6b31e2550
commit
4611ec0b83
112 changed files with 3303 additions and 2560 deletions
4
web/components/issues/issue-detail/reactions/index.ts
Normal file
4
web/components/issues/issue-detail/reactions/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export * from "./reaction-selector";
|
||||
|
||||
export * from "./issue";
|
||||
// export * from "./issue-comment";
|
||||
103
web/components/issues/issue-detail/reactions/issue.tsx
Normal file
103
web/components/issues/issue-detail/reactions/issue.tsx
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
import { FC, useMemo } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { ReactionSelector } from "./reaction-selector";
|
||||
// hooks
|
||||
import { useIssueDetail } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// types
|
||||
import { IUser } from "@plane/types";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
|
||||
export type TIssueReaction = {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
issueId: string;
|
||||
currentUser: IUser;
|
||||
};
|
||||
|
||||
export const IssueReaction: FC<TIssueReaction> = observer((props) => {
|
||||
const { workspaceSlug, projectId, issueId, currentUser } = props;
|
||||
// hooks
|
||||
const {
|
||||
reaction: { getReactionsByIssueId, reactionsByUser },
|
||||
createReaction,
|
||||
removeReaction,
|
||||
} = useIssueDetail();
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const reactionIds = getReactionsByIssueId(issueId);
|
||||
const userReactions = reactionsByUser(issueId, currentUser.id).map((r) => r.reaction);
|
||||
|
||||
const issueReactionOperations = useMemo(
|
||||
() => ({
|
||||
create: async (reaction: string) => {
|
||||
try {
|
||||
if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing fields");
|
||||
await createReaction(workspaceSlug, projectId, issueId, reaction);
|
||||
setToastAlert({
|
||||
title: "Reaction created successfully",
|
||||
type: "success",
|
||||
message: "Reaction created successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
setToastAlert({
|
||||
title: "Reaction creation failed",
|
||||
type: "error",
|
||||
message: "Reaction creation failed",
|
||||
});
|
||||
}
|
||||
},
|
||||
remove: async (reaction: string) => {
|
||||
try {
|
||||
if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing fields");
|
||||
await removeReaction(workspaceSlug, projectId, issueId, reaction, currentUser.id);
|
||||
setToastAlert({
|
||||
title: "Reaction removed successfully",
|
||||
type: "success",
|
||||
message: "Reaction removed successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
setToastAlert({
|
||||
title: "Reaction remove failed",
|
||||
type: "error",
|
||||
message: "Reaction remove failed",
|
||||
});
|
||||
}
|
||||
},
|
||||
react: async (reaction: string) => {
|
||||
if (userReactions.includes(reaction)) await issueReactionOperations.remove(reaction);
|
||||
else await issueReactionOperations.create(reaction);
|
||||
},
|
||||
}),
|
||||
[workspaceSlug, projectId, issueId, currentUser, createReaction, removeReaction, setToastAlert, userReactions]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="mt-4 relative flex items-center gap-1.5">
|
||||
<ReactionSelector size="md" position="top" value={userReactions} onSelect={issueReactionOperations.react} />
|
||||
|
||||
{reactionIds &&
|
||||
Object.keys(reactionIds || {}).map(
|
||||
(reaction) =>
|
||||
reactionIds[reaction]?.length > 0 && (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => issueReactionOperations.react(reaction)}
|
||||
key={reaction}
|
||||
className={`flex h-full items-center gap-1 rounded-md px-2 py-1 text-sm text-custom-text-100 ${
|
||||
userReactions.includes(reaction) ? "bg-custom-primary-100/10" : "bg-custom-background-80"
|
||||
}`}
|
||||
>
|
||||
<span>{renderEmoji(reaction)}</span>
|
||||
<span className={userReactions.includes(reaction) ? "text-custom-primary-100" : ""}>
|
||||
{(reactionIds || {})[reaction].length}{" "}
|
||||
</span>
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
import { Fragment } from "react";
|
||||
import { Popover, Transition } from "@headlessui/react";
|
||||
// helper
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// icons
|
||||
import { SmilePlus } from "lucide-react";
|
||||
|
||||
const reactionEmojis = ["128077", "128078", "128516", "128165", "128533", "129505", "9992", "128064"];
|
||||
|
||||
interface Props {
|
||||
size?: "sm" | "md" | "lg";
|
||||
position?: "top" | "bottom";
|
||||
value?: string | string[] | null;
|
||||
onSelect: (emoji: string) => void;
|
||||
}
|
||||
|
||||
export const ReactionSelector: React.FC<Props> = (props) => {
|
||||
const { onSelect, position, size } = props;
|
||||
|
||||
return (
|
||||
<Popover className="relative">
|
||||
{({ open, close: closePopover }) => (
|
||||
<>
|
||||
<Popover.Button
|
||||
className={`${
|
||||
open ? "" : "text-opacity-90"
|
||||
} group inline-flex items-center rounded-md bg-custom-background-80 focus:outline-none`}
|
||||
>
|
||||
<span
|
||||
className={`flex items-center justify-center rounded-md px-2 ${
|
||||
size === "sm" ? "h-6 w-6" : size === "md" ? "h-7 w-7" : "h-8 w-8"
|
||||
}`}
|
||||
>
|
||||
<SmilePlus className="h-3.5 w-3.5 text-custom-text-100" />
|
||||
</span>
|
||||
</Popover.Button>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-200"
|
||||
enterFrom="opacity-0 translate-y-1"
|
||||
enterTo="opacity-100 translate-y-0"
|
||||
leave="transition ease-in duration-150"
|
||||
leaveFrom="opacity-100 translate-y-0"
|
||||
leaveTo="opacity-0 translate-y-1"
|
||||
>
|
||||
<Popover.Panel
|
||||
className={`absolute -left-2 z-10 bg-custom-sidebar-background-100 ${
|
||||
position === "top" ? "-top-12" : "-bottom-12"
|
||||
}`}
|
||||
>
|
||||
<div className="rounded-md border border-custom-border-200 bg-custom-sidebar-background-100 p-1 shadow-custom-shadow-sm">
|
||||
<div className="flex gap-x-1">
|
||||
{reactionEmojis.map((emoji) => (
|
||||
<button
|
||||
key={emoji}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
onSelect(emoji);
|
||||
closePopover();
|
||||
}}
|
||||
className="flex select-none items-center justify-between rounded-md p-1 text-sm hover:bg-custom-sidebar-background-90"
|
||||
>
|
||||
{renderEmoji(emoji)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue