forked from github/plane
fix: bugs in quick-add and draft issues (#2269)
* fix: 'Last Drafted Issue' making sidebar look weird on collapsed * feat: scroll to the bottom when issue is created * fix: 'Add Issue' button overlapping issue card in spreadsheet view * fix: wrong placement of quick-add in calender layout * fix: spacing for issue card in spreadsheet view
This commit is contained in:
parent
6e0999c35a
commit
b317a14983
@ -183,7 +183,10 @@ export const CalendarView: React.FC<Props> = ({
|
||||
{calendarIssues ? (
|
||||
<div className="h-full overflow-y-auto">
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<div className="h-full rounded-lg p-8 text-custom-text-200">
|
||||
<div
|
||||
id={`calendar-view-${cycleId ?? moduleId ?? viewId}`}
|
||||
className="h-full rounded-lg p-8 text-custom-text-200"
|
||||
>
|
||||
<CalendarHeader
|
||||
isMonthlyView={isMonthlyView}
|
||||
setIsMonthlyView={setIsMonthlyView}
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
// next
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// react hook form
|
||||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
@ -16,21 +19,29 @@ type Props = {
|
||||
handleClose: () => void;
|
||||
onSuccess?: (data: IIssue) => Promise<void> | void;
|
||||
prePopulatedData?: Partial<IIssue>;
|
||||
dependencies: any[];
|
||||
};
|
||||
|
||||
const useCheckIfThereIsSpaceOnRight = (ref: React.RefObject<HTMLDivElement>) => {
|
||||
const useCheckIfThereIsSpaceOnRight = (ref: React.RefObject<HTMLDivElement>, deps: any[]) => {
|
||||
const [isThereSpaceOnRight, setIsThereSpaceOnRight] = useState(true);
|
||||
|
||||
const router = useRouter();
|
||||
const { moduleId, cycleId, viewId } = router.query;
|
||||
|
||||
const container = document.getElementById(`calendar-view-${cycleId ?? moduleId ?? viewId}`);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current) return;
|
||||
|
||||
const { right } = ref.current.getBoundingClientRect();
|
||||
|
||||
const width = right + 250;
|
||||
const width = right;
|
||||
|
||||
if (width > window.innerWidth) setIsThereSpaceOnRight(false);
|
||||
const innerWidth = container?.getBoundingClientRect().width ?? window.innerWidth;
|
||||
|
||||
if (width > innerWidth) setIsThereSpaceOnRight(false);
|
||||
else setIsThereSpaceOnRight(true);
|
||||
}, [ref]);
|
||||
}, [ref, deps, container]);
|
||||
|
||||
return isThereSpaceOnRight;
|
||||
};
|
||||
@ -63,11 +74,11 @@ const InlineInput = () => {
|
||||
};
|
||||
|
||||
export const CalendarInlineCreateIssueForm: React.FC<Props> = (props) => {
|
||||
const { isOpen } = props;
|
||||
const { isOpen, dependencies } = props;
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
const isSpaceOnRight = useCheckIfThereIsSpaceOnRight(ref);
|
||||
const isSpaceOnRight = useCheckIfThereIsSpaceOnRight(ref, dependencies);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -83,6 +83,7 @@ export const SingleCalendarDate: React.FC<Props> = (props) => {
|
||||
|
||||
<CalendarInlineCreateIssueForm
|
||||
isOpen={isCreateIssueFormOpen}
|
||||
dependencies={[showWeekEnds]}
|
||||
handleClose={() => setIsCreateIssueFormOpen(false)}
|
||||
prePopulatedData={{
|
||||
target_date: date.date,
|
||||
|
@ -218,11 +218,11 @@ export const InlineCreateIssueFormWrapper: React.FC<Props> = (props) => {
|
||||
);
|
||||
|
||||
if (isDraftIssues)
|
||||
mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(projectId.toString() ?? "", params));
|
||||
if (displayFilters.layout === "calendar") mutate(calendarFetchKey);
|
||||
if (displayFilters.layout === "gantt_chart") mutate(ganttFetchKey);
|
||||
if (displayFilters.layout === "spreadsheet") mutate(spreadsheetFetchKey);
|
||||
if (groupedIssues) mutateMyIssues();
|
||||
await mutate(PROJECT_DRAFT_ISSUES_LIST_WITH_PARAMS(projectId.toString() ?? "", params));
|
||||
if (displayFilters.layout === "calendar") await mutate(calendarFetchKey);
|
||||
if (displayFilters.layout === "gantt_chart") await mutate(ganttFetchKey);
|
||||
if (displayFilters.layout === "spreadsheet") await mutate(spreadsheetFetchKey);
|
||||
if (groupedIssues) await mutateMyIssues();
|
||||
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
|
@ -68,7 +68,11 @@ export const SpreadsheetView: React.FC<Props> = ({
|
||||
workspaceSlug={workspaceSlug?.toString() ?? ""}
|
||||
readOnly={disableUserActions}
|
||||
/>
|
||||
<div className="h-full rounded-lg text-custom-text-200 overflow-x-auto whitespace-nowrap bg-custom-background-100">
|
||||
<div
|
||||
className={`h-full rounded-lg text-custom-text-200 overflow-x-auto whitespace-nowrap bg-custom-background-100 ${
|
||||
isInlineCreateIssueFormOpen ? "mb-24" : "mb-12"
|
||||
}`}
|
||||
>
|
||||
<div className="sticky z-[2] top-0 border-b border-custom-border-200 bg-custom-background-90 w-full min-w-max">
|
||||
<SpreadsheetColumns columnData={columnData} gridTemplateColumns={gridTemplateColumns} />
|
||||
</div>
|
||||
@ -89,62 +93,66 @@ export const SpreadsheetView: React.FC<Props> = ({
|
||||
userAuth={userAuth}
|
||||
/>
|
||||
))}
|
||||
|
||||
<div className="absolute bottom-0 left-0 z-10 group pb-2 hover:rounded-sm bg-custom-background-100 hover:bg-custom-background-80 border-b border-custom-border-200 w-full min-w-max">
|
||||
<ListInlineCreateIssueForm
|
||||
isOpen={isInlineCreateIssueFormOpen}
|
||||
handleClose={() => setIsInlineCreateIssueFormOpen(false)}
|
||||
prePopulatedData={{
|
||||
...(cycleId && { cycle: cycleId.toString() }),
|
||||
...(moduleId && { module: moduleId.toString() }),
|
||||
}}
|
||||
/>
|
||||
|
||||
{type === "issue"
|
||||
? !disableUserActions &&
|
||||
!isInlineCreateIssueFormOpen && (
|
||||
<button
|
||||
className="flex gap-1.5 items-center pl-7 py-2.5 text-sm sticky left-0 z-[1] text-custom-text-200 border-custom-border-200 w-full"
|
||||
onClick={() => setIsInlineCreateIssueFormOpen(true)}
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Add Issue
|
||||
</button>
|
||||
)
|
||||
: !disableUserActions &&
|
||||
!isInlineCreateIssueFormOpen && (
|
||||
<CustomMenu
|
||||
className="sticky left-0 z-[1]"
|
||||
customButton={
|
||||
<button
|
||||
className="flex gap-1.5 items-center pl-7 py-2.5 text-sm sticky left-0 z-[1] text-custom-text-200 border-custom-border-200 w-full"
|
||||
type="button"
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Add Issue
|
||||
</button>
|
||||
}
|
||||
position="left"
|
||||
verticalPosition="top"
|
||||
optionsClassName="left-5 !w-36"
|
||||
noBorder
|
||||
>
|
||||
<CustomMenu.MenuItem onClick={() => setIsInlineCreateIssueFormOpen(true)}>
|
||||
Create new
|
||||
</CustomMenu.MenuItem>
|
||||
{openIssuesListModal && (
|
||||
<CustomMenu.MenuItem onClick={openIssuesListModal}>
|
||||
Add an existing issue
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
</CustomMenu>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Spinner />
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`absolute bottom-0 left-0 z-10 group hover:rounded-sm bg-custom-background-100 hover:bg-custom-background-80 border-b border-custom-border-200 w-full min-w-max ${
|
||||
isInlineCreateIssueFormOpen ? "pb-2" : ""
|
||||
}`}
|
||||
>
|
||||
<ListInlineCreateIssueForm
|
||||
isOpen={isInlineCreateIssueFormOpen}
|
||||
handleClose={() => setIsInlineCreateIssueFormOpen(false)}
|
||||
prePopulatedData={{
|
||||
...(cycleId && { cycle: cycleId.toString() }),
|
||||
...(moduleId && { module: moduleId.toString() }),
|
||||
}}
|
||||
/>
|
||||
|
||||
{type === "issue"
|
||||
? !disableUserActions &&
|
||||
!isInlineCreateIssueFormOpen && (
|
||||
<button
|
||||
className="flex gap-1.5 items-center pl-7 py-2.5 text-sm sticky left-0 z-[1] text-custom-text-200 border-custom-border-200 w-full"
|
||||
onClick={() => setIsInlineCreateIssueFormOpen(true)}
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Add Issue
|
||||
</button>
|
||||
)
|
||||
: !disableUserActions &&
|
||||
!isInlineCreateIssueFormOpen && (
|
||||
<CustomMenu
|
||||
className="sticky left-0 z-[1] !w-full"
|
||||
customButton={
|
||||
<button
|
||||
className="flex gap-1.5 items-center pl-7 py-2.5 text-sm sticky left-0 z-[1] text-custom-text-200 border-custom-border-200 w-full"
|
||||
type="button"
|
||||
>
|
||||
<PlusIcon className="h-4 w-4" />
|
||||
Add Issue
|
||||
</button>
|
||||
}
|
||||
position="left"
|
||||
verticalPosition="top"
|
||||
optionsClassName="left-5 !w-36"
|
||||
noBorder
|
||||
>
|
||||
<CustomMenu.MenuItem onClick={() => setIsInlineCreateIssueFormOpen(true)}>
|
||||
Create new
|
||||
</CustomMenu.MenuItem>
|
||||
{openIssuesListModal && (
|
||||
<CustomMenu.MenuItem onClick={openIssuesListModal}>
|
||||
Add an existing issue
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
</CustomMenu>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -92,6 +92,7 @@ export const GanttSidebar: React.FC<Props> = (props) => {
|
||||
<StrictModeDroppable droppableId="gantt-sidebar">
|
||||
{(droppableProvided) => (
|
||||
<div
|
||||
id={`gantt-sidebar-${cycleId}`}
|
||||
className="h-full overflow-y-auto pl-2.5"
|
||||
ref={droppableProvided.innerRef}
|
||||
{...droppableProvided.droppableProps}
|
||||
@ -162,6 +163,19 @@ export const GanttSidebar: React.FC<Props> = (props) => {
|
||||
<GanttInlineCreateIssueForm
|
||||
isOpen={isCreateIssueFormOpen}
|
||||
handleClose={() => setIsCreateIssueFormOpen(false)}
|
||||
onSuccess={() => {
|
||||
const ganttSidebar = document.getElementById(`gantt-sidebar-${cycleId}`);
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (ganttSidebar)
|
||||
ganttSidebar.scrollBy({
|
||||
top: ganttSidebar.scrollHeight,
|
||||
left: 0,
|
||||
behavior: "smooth",
|
||||
});
|
||||
clearTimeout(timeoutId);
|
||||
}, 10);
|
||||
}}
|
||||
prePopulatedData={{
|
||||
start_date: new Date(Date.now()).toISOString().split("T")[0],
|
||||
target_date: new Date(Date.now() + 86400000).toISOString().split("T")[0],
|
||||
|
@ -44,7 +44,9 @@ export const WorkspaceSidebarQuickAction = () => {
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className="relative flex items-center gap-2 flex-grow rounded flex-shrink-0 py-1.5"
|
||||
className={`relative flex items-center gap-2 flex-grow rounded flex-shrink-0 py-1.5 ${
|
||||
store?.theme?.sidebarCollapsed ? "justify-center" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", { key: "c" });
|
||||
document.dispatchEvent(e);
|
||||
@ -61,11 +63,17 @@ export const WorkspaceSidebarQuickAction = () => {
|
||||
|
||||
{storedValue && Object.keys(JSON.parse(storedValue)).length > 0 && (
|
||||
<>
|
||||
<div className="h-8 w-0.5 bg-custom-sidebar-background-80" />
|
||||
<div
|
||||
className={`h-8 w-0.5 bg-custom-sidebar-background-80 ${
|
||||
store?.theme?.sidebarCollapsed ? "hidden" : "block"
|
||||
}`}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center justify-center rounded flex-shrink-0 py-1.5 ml-1.5"
|
||||
className={`flex items-center justify-center rounded flex-shrink-0 py-1.5 ml-1.5 ${
|
||||
store?.theme?.sidebarCollapsed ? "hidden" : "block"
|
||||
}`}
|
||||
>
|
||||
<ChevronDown
|
||||
size={16}
|
||||
@ -73,7 +81,11 @@ export const WorkspaceSidebarQuickAction = () => {
|
||||
/>
|
||||
</button>
|
||||
|
||||
<div className="absolute w-full h-10 pt-2 top-full left-0 opacity-0 group-hover:opacity-100 mt-0 pointer-events-none group-hover:pointer-events-auto">
|
||||
<div
|
||||
className={`fixed h-10 pt-2 w-[203px] left-4 opacity-0 group-hover:opacity-100 mt-0 pointer-events-none group-hover:pointer-events-auto ${
|
||||
store?.theme?.sidebarCollapsed ? "top-[5.5rem]" : "top-24"
|
||||
}`}
|
||||
>
|
||||
<div className="w-full h-full">
|
||||
<button
|
||||
onClick={() => setIsDraftIssueModalOpen(true)}
|
||||
|
@ -189,7 +189,7 @@ const SingleCycle: React.FC = () => {
|
||||
{cycleStatus === "completed" && (
|
||||
<TransferIssues handleClick={() => setTransferIssuesModal(true)} />
|
||||
)}
|
||||
<div className="relative overflow-y-auto">
|
||||
<div className="relative overflow-y-auto w-full h-full">
|
||||
<IssuesView
|
||||
openIssuesListModal={openIssuesListModal}
|
||||
disableUserActions={cycleStatus === "completed" ?? false}
|
||||
|
Loading…
Reference in New Issue
Block a user