Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: quick add #2240

Merged
merged 3 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions web/components/core/views/board-view/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from "./all-boards";
export * from "./board-header";
export * from "./single-board";
export * from "./single-issue";
export * from "./inline-create-issue-form";
62 changes: 62 additions & 0 deletions web/components/core/views/board-view/inline-create-issue-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { useEffect } from "react";

// react hook form
import { useFormContext } from "react-hook-form";

// components
import { InlineCreateIssueFormWrapper } from "components/core";

// hooks
import useProjectDetails from "hooks/use-project-details";

// types
import { IIssue } from "types";

type Props = {
isOpen: boolean;
handleClose: () => void;
onSuccess?: (data: IIssue) => Promise<void> | void;
prePopulatedData?: Partial<IIssue>;
};

const InlineInput = () => {
const { projectDetails } = useProjectDetails();

const { register, setFocus } = useFormContext();

useEffect(() => {
setFocus("name");
}, [setFocus]);

return (
<div>
<h4 className="text-sm font-medium leading-5 text-custom-text-300">
{projectDetails?.identifier ?? "..."}
</h4>
<input
autoComplete="off"
placeholder="Issue Title"
{...register("name", {
required: "Issue title is required.",
})}
className="w-full px-2 pl-0 py-1.5 rounded-md bg-transparent text-sm font-medium leading-5 text-custom-text-200 outline-none"
/>
</div>
);
};

export const BoardInlineCreateIssueForm: React.FC<Props> = (props) => (
<>
<InlineCreateIssueFormWrapper
className="flex flex-col justify-between gap-1.5 group/card relative select-none px-3.5 py-3 h-[118px] mb-3 rounded bg-custom-background-100 shadow"
{...props}
>
<InlineInput />
</InlineCreateIssueFormWrapper>
{props.isOpen && (
<p className="text-xs ml-3 italic text-custom-text-200">
Press {"'"}Enter{"'"} to add another issue
</p>
)}
</>
);
76 changes: 56 additions & 20 deletions web/components/core/views/board-view/single-board.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useRouter } from "next/router";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { Draggable } from "react-beautiful-dnd";
// components
import { BoardHeader, SingleBoardIssue } from "components/core";
import { BoardHeader, SingleBoardIssue, BoardInlineCreateIssueForm } from "components/core";
// ui
import { CustomMenu } from "components/ui";
// icons
Expand Down Expand Up @@ -34,26 +34,30 @@ type Props = {
viewProps: IIssueViewProps;
};

export const SingleBoard: React.FC<Props> = ({
addIssueToGroup,
currentState,
groupTitle,
disableUserActions,
disableAddIssueOption = false,
dragDisabled,
handleIssueAction,
handleDraftIssueAction,
handleTrashBox,
openIssuesListModal,
handleMyIssueOpen,
removeIssue,
user,
userAuth,
viewProps,
}) => {
export const SingleBoard: React.FC<Props> = (props) => {
const {
addIssueToGroup,
currentState,
groupTitle,
disableUserActions,
disableAddIssueOption = false,
dragDisabled,
handleIssueAction,
handleDraftIssueAction,
handleTrashBox,
openIssuesListModal,
handleMyIssueOpen,
removeIssue,
user,
userAuth,
viewProps,
} = props;

// collapse/expand
const [isCollapsed, setIsCollapsed] = useState(true);

const [isInlineCreateIssueFormOpen, setIsInlineCreateIssueFormOpen] = useState(false);

const { displayFilters, groupedIssues } = viewProps;

const router = useRouter();
Expand All @@ -67,6 +71,24 @@ export const SingleBoard: React.FC<Props> = ({

const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disableUserActions;

const onCreateClick = () => {
setIsInlineCreateIssueFormOpen(true);

const boardListElement = document.getElementById(`board-list-${groupTitle}`);

// timeout is needed because the animation
// takes time to complete & we can scroll only after that
const timeoutId = setTimeout(() => {
if (boardListElement)
boardListElement.scrollBy({
top: boardListElement.scrollHeight,
left: 0,
behavior: "smooth",
});
clearTimeout(timeoutId);
}, 10);
};

return (
<div className={`flex-shrink-0 ${!isCollapsed ? "" : "flex h-full flex-col w-96"}`}>
<BoardHeader
Expand Down Expand Up @@ -115,6 +137,7 @@ export const SingleBoard: React.FC<Props> = ({
</>
)}
<div
id={`board-list-${groupTitle}`}
className={`pt-3 ${
hasMinimumNumberOfCards ? "overflow-hidden overflow-y-scroll" : ""
} `}
Expand Down Expand Up @@ -169,6 +192,19 @@ export const SingleBoard: React.FC<Props> = ({
>
<>{provided.placeholder}</>
</span>

<BoardInlineCreateIssueForm
isOpen={isInlineCreateIssueFormOpen}
handleClose={() => setIsInlineCreateIssueFormOpen(false)}
prePopulatedData={{
...(cycleId && { cycle: cycleId.toString() }),
...(moduleId && { module: moduleId.toString() }),
[displayFilters?.group_by! === "labels"
? "labels_list"
: displayFilters?.group_by!]:
displayFilters?.group_by === "labels" ? [groupTitle] : groupTitle,
}}
/>
</div>
{displayFilters?.group_by !== "created_by" && (
<div>
Expand All @@ -177,7 +213,7 @@ export const SingleBoard: React.FC<Props> = ({
<button
type="button"
className="flex items-center gap-2 font-medium text-custom-primary outline-none p-1"
onClick={addIssueToGroup}
onClick={() => onCreateClick()}
>
<PlusIcon className="h-4 w-4" />
Add Issue
Expand All @@ -197,7 +233,7 @@ export const SingleBoard: React.FC<Props> = ({
position="left"
noBorder
>
<CustomMenu.MenuItem onClick={addIssueToGroup}>
<CustomMenu.MenuItem onClick={() => onCreateClick()}>
Create new
</CustomMenu.MenuItem>
{openIssuesListModal && (
Expand Down
1 change: 1 addition & 0 deletions web/components/core/views/calendar-view/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from "./calendar-header";
export * from "./calendar";
export * from "./single-date";
export * from "./single-issue";
export * from "./inline-create-issue-form";
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { useEffect, useRef, useState } from "react";

// react hook form
import { useFormContext } from "react-hook-form";

import { InlineCreateIssueFormWrapper } from "components/core";

// hooks
import useProjectDetails from "hooks/use-project-details";

// types
import { IIssue } from "types";

type Props = {
isOpen: boolean;
handleClose: () => void;
onSuccess?: (data: IIssue) => Promise<void> | void;
prePopulatedData?: Partial<IIssue>;
};

const useCheckIfThereIsSpaceOnRight = (ref: React.RefObject<HTMLDivElement>) => {
const [isThereSpaceOnRight, setIsThereSpaceOnRight] = useState(true);

useEffect(() => {
if (!ref.current) return;

const { right } = ref.current.getBoundingClientRect();

const width = right + 250;

if (width > window.innerWidth) setIsThereSpaceOnRight(false);
else setIsThereSpaceOnRight(true);
}, [ref]);

return isThereSpaceOnRight;
};

const InlineInput = () => {
const { projectDetails } = useProjectDetails();

const { register, setFocus } = useFormContext();

useEffect(() => {
setFocus("name");
}, [setFocus]);

return (
<>
<h4 className="text-sm font-medium leading-5 text-custom-text-400">
{projectDetails?.identifier ?? "..."}
</h4>
<input
type="text"
autoComplete="off"
placeholder="Issue Title"
{...register("name", {
required: "Issue title is required.",
})}
className="w-full px-2 py-1.5 rounded-md bg-transparent text-sm font-medium leading-5 text-custom-text-200 outline-none"
/>
</>
);
};

export const CalendarInlineCreateIssueForm: React.FC<Props> = (props) => {
const { isOpen } = props;

const ref = useRef<HTMLDivElement>(null);

const isSpaceOnRight = useCheckIfThereIsSpaceOnRight(ref);

return (
<>
<div
ref={ref}
className={`absolute -translate-x-1 top-5 transition-all z-20 ${
isOpen ? "opacity-100 scale-100" : "opacity-0 pointer-events-none scale-95"
} ${isSpaceOnRight ? "left-full" : "right-0"}`}
>
<InlineCreateIssueFormWrapper
{...props}
className="flex w-60 p-1 px-1.5 rounded items-center gap-x-3 bg-custom-background-100 shadow-custom-shadow-md transition-opacity"
>
<InlineInput />
</InlineCreateIssueFormWrapper>
</div>
{/* Added to make any other element as outside click. This will make input also to be outside. */}
{isOpen && <div className="w-screen h-screen fixed inset-0 z-10" />}
</>
);
};
34 changes: 23 additions & 11 deletions web/components/core/views/calendar-view/single-date.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import React, { useState } from "react";

// next
import { useRouter } from "next/router";

// react-beautiful-dnd
import { Draggable } from "react-beautiful-dnd";
// component
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { SingleCalendarIssue } from "./single-issue";
import { CalendarInlineCreateIssueForm } from "./inline-create-issue-form";
// icons
import { PlusSmallIcon } from "@heroicons/react/24/outline";
// helper
Expand All @@ -26,17 +30,14 @@ type Props = {
isNotAllowed: boolean;
};

export const SingleCalendarDate: React.FC<Props> = ({
handleIssueAction,
date,
index,
addIssueToDate,
isMonthlyView,
showWeekEnds,
user,
isNotAllowed,
}) => {
export const SingleCalendarDate: React.FC<Props> = (props) => {
const { handleIssueAction, date, index, isMonthlyView, showWeekEnds, user, isNotAllowed } = props;

const router = useRouter();
const { cycleId, moduleId } = router.query;

const [showAllIssues, setShowAllIssues] = useState(false);
const [isCreateIssueFormOpen, setIsCreateIssueFormOpen] = useState(false);

const totalIssues = date.issues.length;

Expand Down Expand Up @@ -78,6 +79,17 @@ export const SingleCalendarDate: React.FC<Props> = ({
)}
</Draggable>
))}

<CalendarInlineCreateIssueForm
isOpen={isCreateIssueFormOpen}
handleClose={() => setIsCreateIssueFormOpen(false)}
prePopulatedData={{
target_date: date.date,
...(cycleId && { cycle: cycleId.toString() }),
...(moduleId && { module: moduleId.toString() }),
}}
/>

{totalIssues > 4 && (
<button
type="button"
Expand All @@ -93,7 +105,7 @@ export const SingleCalendarDate: React.FC<Props> = ({
>
<button
className="flex items-center justify-center gap-1 text-center"
onClick={() => addIssueToDate(date.date)}
onClick={() => setIsCreateIssueFormOpen(true)}
>
<PlusSmallIcon className="h-4 w-4 text-custom-text-200" />
Add issue
Expand Down
Loading
Loading