mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
fix: issue layouts bugs and ui fixes (#3012)
* fix: initial issue creation issue in the list layout * fix kanban drag n drop and updating properties * reduce z index of spreadsheet bottom row to not overlap with other elements * fix state update by using state id instead of state detail's id * fix add default use state for description * add create issue button for project views to be at par with production * save draft issues from modal * chore: added save view button in all layouts applied filters * use useEffect instead of swr for fetching issue details for peek overview * fix: resolved kanban dnd --------- Co-authored-by: rahulramesha <rahulramesham@gmail.com>
This commit is contained in:
parent
55ce748aa1
commit
e585255c4c
@ -6,7 +6,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "components/issues";
|
||||
// ui
|
||||
import { Breadcrumbs, CustomMenu, PhotoFilterIcon } from "@plane/ui";
|
||||
import { Breadcrumbs, Button, CustomMenu, PhotoFilterIcon } from "@plane/ui";
|
||||
// helpers
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
@ -15,6 +15,8 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_FILTERS_BY_LAYOUT } from "constants/issue";
|
||||
import { EFilterType } from "store/issues/types";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { Plus } from "lucide-react";
|
||||
|
||||
export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
@ -25,7 +27,6 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||
};
|
||||
|
||||
const {
|
||||
issueFilter: issueFilterStore,
|
||||
projectViewFilters: projectViewFiltersStore,
|
||||
project: { currentProjectDetails },
|
||||
projectLabel: { projectLabels },
|
||||
@ -33,6 +34,8 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||
projectState: projectStateStore,
|
||||
projectViews: projectViewsStore,
|
||||
viewIssuesFilter: { issueFilters, updateFilters },
|
||||
commandPalette: commandPaletteStore,
|
||||
trackEvent: { setTrackElement },
|
||||
} = useMobxStore();
|
||||
|
||||
const storedFilters = viewId ? projectViewFiltersStore.storedFilters[viewId.toString()] : undefined;
|
||||
@ -170,6 +173,16 @@ export const ProjectViewIssuesHeader: React.FC = observer(() => {
|
||||
handleDisplayPropertiesUpdate={handleDisplayProperties}
|
||||
/>
|
||||
</FiltersDropdown>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setTrackElement("PROJECT_VIEW_PAGE_HEADER");
|
||||
commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.PROJECT_VIEW);
|
||||
}}
|
||||
size="sm"
|
||||
prependIcon={<Plus />}
|
||||
>
|
||||
Add Issue
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -43,7 +43,6 @@ interface IssuesModalProps {
|
||||
|
||||
// services
|
||||
const issueService = new IssueService();
|
||||
const issueDraftService = new IssueDraftService();
|
||||
const moduleService = new ModuleService();
|
||||
|
||||
export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer((props) => {
|
||||
@ -65,7 +64,7 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||
|
||||
const { project: projectStore, user: userStore } = useMobxStore();
|
||||
const { project: projectStore, user: userStore, projectDraftIssues: draftIssueStore } = useMobxStore();
|
||||
|
||||
const user = userStore.currentUser;
|
||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
||||
@ -167,9 +166,10 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
||||
const createDraftIssue = async (payload: Partial<IIssue>) => {
|
||||
if (!workspaceSlug || !activeProject || !user) return;
|
||||
|
||||
await issueDraftService
|
||||
.createDraftIssue(workspaceSlug as string, activeProject ?? "", payload)
|
||||
await draftIssueStore
|
||||
.createIssue(workspaceSlug as string, activeProject ?? "", payload)
|
||||
.then(async () => {
|
||||
await draftIssueStore.fetchIssues(workspaceSlug as string, activeProject ?? "", "mutation");
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
@ -192,8 +192,8 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
||||
const updateDraftIssue = async (payload: Partial<IIssue>) => {
|
||||
if (!user) return;
|
||||
|
||||
await issueDraftService
|
||||
.updateDraftIssue(workspaceSlug as string, activeProject ?? "", data?.id ?? "", payload)
|
||||
await draftIssueStore
|
||||
.updateIssue(workspaceSlug as string, activeProject ?? "", data?.id ?? "", payload)
|
||||
.then((res) => {
|
||||
if (isUpdatingSingleIssue) {
|
||||
mutate<IIssue>(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false);
|
||||
|
@ -4,14 +4,14 @@ import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { AppliedFiltersList } from "components/issues";
|
||||
import { AppliedFiltersList, SaveFilterView } from "components/issues";
|
||||
// types
|
||||
import { IIssueFilterOptions } from "types";
|
||||
import { EFilterType } from "store/issues/types";
|
||||
|
||||
export const ArchivedIssueAppliedFiltersRoot: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||
|
||||
const {
|
||||
projectArchivedIssuesFilter: { issueFilters, updateFilters },
|
||||
@ -69,7 +69,7 @@ export const ArchivedIssueAppliedFiltersRoot: React.FC = observer(() => {
|
||||
if (Object.keys(appliedFilters).length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="p-4 flex items-center justify-between">
|
||||
<AppliedFiltersList
|
||||
appliedFilters={appliedFilters}
|
||||
handleClearAllFilters={handleClearAllFilters}
|
||||
@ -78,6 +78,8 @@ export const ArchivedIssueAppliedFiltersRoot: React.FC = observer(() => {
|
||||
members={projectMembers?.map((m) => m.member)}
|
||||
states={projectStateStore.states?.[projectId?.toString() ?? ""]}
|
||||
/>
|
||||
|
||||
<SaveFilterView workspaceSlug={workspaceSlug} projectId={projectId} filterParams={appliedFilters} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { AppliedFiltersList } from "components/issues";
|
||||
import { AppliedFiltersList, SaveFilterView } from "components/issues";
|
||||
// types
|
||||
import { IIssueFilterOptions } from "types";
|
||||
import { EFilterType } from "store/issues/types";
|
||||
@ -75,7 +76,7 @@ export const CycleAppliedFiltersRoot: React.FC = observer(() => {
|
||||
if (Object.keys(appliedFilters).length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="p-4 flex items-center justify-between">
|
||||
<AppliedFiltersList
|
||||
appliedFilters={appliedFilters}
|
||||
handleClearAllFilters={handleClearAllFilters}
|
||||
@ -84,6 +85,8 @@ export const CycleAppliedFiltersRoot: React.FC = observer(() => {
|
||||
members={projectMembers?.map((m) => m.member)}
|
||||
states={projectStateStore.states?.[cycleId ?? ""]}
|
||||
/>
|
||||
|
||||
<SaveFilterView workspaceSlug={workspaceSlug} projectId={projectId} filterParams={appliedFilters} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -4,14 +4,14 @@ import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { AppliedFiltersList } from "components/issues";
|
||||
import { AppliedFiltersList, SaveFilterView } from "components/issues";
|
||||
// types
|
||||
import { IIssueFilterOptions } from "types";
|
||||
import { EFilterType } from "store/issues/types";
|
||||
|
||||
export const DraftIssueAppliedFiltersRoot: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||
|
||||
const {
|
||||
projectDraftIssuesFilter: { issueFilters, updateFilters },
|
||||
@ -64,7 +64,7 @@ export const DraftIssueAppliedFiltersRoot: React.FC = observer(() => {
|
||||
if (Object.keys(appliedFilters).length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="p-4 flex items-center justify-between">
|
||||
<AppliedFiltersList
|
||||
appliedFilters={appliedFilters}
|
||||
handleClearAllFilters={handleClearAllFilters}
|
||||
@ -73,6 +73,8 @@ export const DraftIssueAppliedFiltersRoot: React.FC = observer(() => {
|
||||
members={projectMembers?.map((m) => m.member)}
|
||||
states={projectStateStore.states?.[projectId?.toString() ?? ""]}
|
||||
/>
|
||||
|
||||
<SaveFilterView workspaceSlug={workspaceSlug} projectId={projectId} filterParams={appliedFilters} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { AppliedFiltersList } from "components/issues";
|
||||
import { AppliedFiltersList, SaveFilterView } from "components/issues";
|
||||
// types
|
||||
import { IIssueFilterOptions } from "types";
|
||||
import { EFilterType } from "store/issues/types";
|
||||
@ -76,7 +76,7 @@ export const ModuleAppliedFiltersRoot: React.FC = observer(() => {
|
||||
if (Object.keys(appliedFilters).length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="p-4 flex items-center justify-between">
|
||||
<AppliedFiltersList
|
||||
appliedFilters={appliedFilters}
|
||||
handleClearAllFilters={handleClearAllFilters}
|
||||
@ -85,6 +85,8 @@ export const ModuleAppliedFiltersRoot: React.FC = observer(() => {
|
||||
members={projectMembers?.map((m) => m.member)}
|
||||
states={projectStateStore.states?.[moduleId ?? ""]}
|
||||
/>
|
||||
|
||||
<SaveFilterView workspaceSlug={workspaceSlug} projectId={projectId} filterParams={appliedFilters} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -4,14 +4,18 @@ import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { AppliedFiltersList } from "components/issues";
|
||||
import { AppliedFiltersList, SaveFilterView } from "components/issues";
|
||||
|
||||
// types
|
||||
import { IIssueFilterOptions } from "types";
|
||||
import { EFilterType } from "store/issues/types";
|
||||
|
||||
export const ProjectAppliedFiltersRoot: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
const { workspaceSlug, projectId } = router.query as {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
const {
|
||||
projectLabel: { projectLabels },
|
||||
@ -60,7 +64,7 @@ export const ProjectAppliedFiltersRoot: React.FC = observer(() => {
|
||||
if (Object.keys(appliedFilters).length === 0) return null;
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="p-4 flex items-center justify-between">
|
||||
<AppliedFiltersList
|
||||
appliedFilters={appliedFilters}
|
||||
handleClearAllFilters={handleClearAllFilters}
|
||||
@ -69,6 +73,8 @@ export const ProjectAppliedFiltersRoot: React.FC = observer(() => {
|
||||
members={projectMembers?.map((m) => m.member)}
|
||||
states={projectStateStore.states?.[projectId?.toString() ?? ""]}
|
||||
/>
|
||||
|
||||
<SaveFilterView workspaceSlug={workspaceSlug} projectId={projectId} filterParams={appliedFilters} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -31,7 +31,6 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
|
||||
} = useMobxStore();
|
||||
|
||||
const viewDetails = viewId ? projectViewsStore.viewDetails[viewId.toString()] : undefined;
|
||||
const storedFilters = viewId ? projectViewFiltersStore.storedFilters[viewId.toString()] : undefined;
|
||||
|
||||
const userFilters = issueFilters?.filters;
|
||||
// filters whose value not null or empty array
|
||||
@ -89,7 +88,7 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
|
||||
projectViewsStore.updateView(workspaceSlug.toString(), projectId.toString(), viewId.toString(), {
|
||||
query_data: {
|
||||
...viewDetails.query_data,
|
||||
...(storedFilters ?? {}),
|
||||
...(appliedFilters ?? {}),
|
||||
},
|
||||
});
|
||||
};
|
||||
@ -104,13 +103,16 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
|
||||
members={projectMembers?.map((m) => m.member)}
|
||||
states={projectStateStore.states?.[projectId?.toString() ?? ""]}
|
||||
/>
|
||||
{storedFilters && viewDetails && areFiltersDifferent(storedFilters, viewDetails.query_data ?? {}) && (
|
||||
<div className="flex items-center justify-center flex-shrink-0">
|
||||
<Button variant="primary" size="sm" onClick={handleUpdateView}>
|
||||
Update view
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{appliedFilters &&
|
||||
viewDetails?.query_data &&
|
||||
areFiltersDifferent(appliedFilters, viewDetails?.query_data ?? {}) && (
|
||||
<div className="flex items-center justify-center flex-shrink-0">
|
||||
<Button variant="primary" size="sm" onClick={handleUpdateView}>
|
||||
Update view
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -15,3 +15,6 @@ export * from "./spreadsheet";
|
||||
|
||||
// properties
|
||||
export * from "./properties";
|
||||
|
||||
// save view
|
||||
export * from "./save-filter-view";
|
||||
|
@ -1,16 +1,14 @@
|
||||
import { memo, useRef, useState } from "react";
|
||||
import { memo } from "react";
|
||||
import { Draggable } from "@hello-pangea/dnd";
|
||||
import isEqual from "lodash/isEqual";
|
||||
// components
|
||||
import { KanBanProperties } from "./properties";
|
||||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// hooks
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
// types
|
||||
import { IIssueDisplayProperties, IIssue } from "types";
|
||||
import { EIssueActions } from "../types";
|
||||
import { useRouter } from "next/router";
|
||||
import { MoreHorizontal } from "lucide-react";
|
||||
|
||||
interface IssueBlockProps {
|
||||
sub_group_id: string;
|
||||
@ -20,37 +18,28 @@ interface IssueBlockProps {
|
||||
isDragDisabled: boolean;
|
||||
showEmptyGroup: boolean;
|
||||
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
|
||||
quickActions: (
|
||||
sub_group_by: string | null,
|
||||
group_by: string | null,
|
||||
issue: IIssue,
|
||||
customActionButton?: React.ReactElement
|
||||
) => React.ReactNode;
|
||||
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
|
||||
displayProperties: IIssueDisplayProperties | null;
|
||||
isReadOnly: boolean;
|
||||
}
|
||||
|
||||
export const KanBanIssueMemoBlock: React.FC<IssueBlockProps> = (props) => {
|
||||
const {
|
||||
sub_group_id,
|
||||
columnId,
|
||||
index,
|
||||
issue,
|
||||
isDragDisabled,
|
||||
showEmptyGroup,
|
||||
handleIssues,
|
||||
quickActions,
|
||||
displayProperties,
|
||||
isReadOnly,
|
||||
} = props;
|
||||
// router
|
||||
interface IssueDetailsBlockProps {
|
||||
sub_group_id: string;
|
||||
columnId: string;
|
||||
issue: IIssue;
|
||||
showEmptyGroup: boolean;
|
||||
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
|
||||
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
|
||||
displayProperties: IIssueDisplayProperties | null;
|
||||
isReadOnly: boolean;
|
||||
}
|
||||
|
||||
const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = (props) => {
|
||||
const { sub_group_id, columnId, issue, showEmptyGroup, handleIssues, quickActions, displayProperties, isReadOnly } =
|
||||
props;
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// states
|
||||
const [isMenuActive, setIsMenuActive] = useState(false);
|
||||
|
||||
const menuActionRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const updateIssue = (sub_group_by: string | null, group_by: string | null, issueToUpdate: IIssue) => {
|
||||
if (issueToUpdate) handleIssues(sub_group_by, group_by, issueToUpdate, EIssueActions.UPDATE);
|
||||
};
|
||||
@ -64,24 +53,70 @@ export const KanBanIssueMemoBlock: React.FC<IssueBlockProps> = (props) => {
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{displayProperties && displayProperties?.key && (
|
||||
<div className="relative">
|
||||
<div className="text-xs line-clamp-1 text-custom-text-300">
|
||||
{issue.project_detail.identifier}-{issue.sequence_id}
|
||||
</div>
|
||||
<div className="absolute -top-1 right-0 hidden group-hover/kanban-block:block">
|
||||
{quickActions(
|
||||
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
|
||||
!columnId && columnId === "null" ? null : columnId,
|
||||
issue
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
|
||||
<div className="line-clamp-2 text-sm font-medium text-custom-text-100" onClick={handleIssuePeekOverview}>
|
||||
{issue.name}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div>
|
||||
<KanBanProperties
|
||||
sub_group_id={sub_group_id}
|
||||
columnId={columnId}
|
||||
issue={issue}
|
||||
handleIssues={updateIssue}
|
||||
displayProperties={displayProperties}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
isReadOnly={isReadOnly}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const validateMemo = (prevProps: IssueDetailsBlockProps, nextProps: IssueDetailsBlockProps) => {
|
||||
if (prevProps.issue !== nextProps.issue) return false;
|
||||
if (!isEqual(prevProps.displayProperties, nextProps.displayProperties)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const KanbanIssueMemoBlock = memo(KanbanIssueDetailsBlock, validateMemo);
|
||||
|
||||
export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
|
||||
const {
|
||||
sub_group_id,
|
||||
columnId,
|
||||
index,
|
||||
issue,
|
||||
isDragDisabled,
|
||||
showEmptyGroup,
|
||||
handleIssues,
|
||||
quickActions,
|
||||
displayProperties,
|
||||
isReadOnly,
|
||||
} = props;
|
||||
|
||||
let draggableId = issue.id;
|
||||
if (columnId) draggableId = `${draggableId}__${columnId}`;
|
||||
if (sub_group_id) draggableId = `${draggableId}__${sub_group_id}`;
|
||||
|
||||
useOutsideClickDetector(menuActionRef, () => setIsMenuActive(false));
|
||||
|
||||
const customActionButton = (
|
||||
<div
|
||||
ref={menuActionRef}
|
||||
className={`w-full cursor-pointer text-custom-sidebar-text-400 rounded p-1 hover:bg-custom-background-80 ${
|
||||
isMenuActive ? "bg-custom-background-80 text-custom-text-100" : "text-custom-text-200"
|
||||
}`}
|
||||
onClick={() => setIsMenuActive(!isMenuActive)}
|
||||
>
|
||||
<MoreHorizontal className="h-3.5 w-3.5" />
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Draggable draggableId={draggableId} index={index}>
|
||||
@ -100,44 +135,16 @@ export const KanBanIssueMemoBlock: React.FC<IssueBlockProps> = (props) => {
|
||||
isDragDisabled ? "" : "hover:cursor-grab"
|
||||
} ${snapshot.isDragging ? `border-custom-primary-100` : `border-transparent`}`}
|
||||
>
|
||||
{displayProperties && displayProperties?.key && (
|
||||
<div className="relative">
|
||||
<div className="text-xs line-clamp-1 text-custom-text-300">
|
||||
{issue.project_detail.identifier}-{issue.sequence_id}
|
||||
</div>
|
||||
<div
|
||||
className={`absolute -top-1 right-0 hidden group-hover/kanban-block:block ${
|
||||
isMenuActive ? "!block" : ""
|
||||
}`}
|
||||
>
|
||||
{quickActions(
|
||||
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
|
||||
!columnId && columnId === "null" ? null : columnId,
|
||||
issue,
|
||||
customActionButton
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
|
||||
<div
|
||||
className="line-clamp-2 text-sm font-medium text-custom-text-100"
|
||||
onClick={handleIssuePeekOverview}
|
||||
>
|
||||
{issue.name}
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div>
|
||||
<KanBanProperties
|
||||
sub_group_id={sub_group_id}
|
||||
columnId={columnId}
|
||||
issue={issue}
|
||||
handleIssues={updateIssue}
|
||||
displayProperties={displayProperties}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
isReadOnly={isReadOnly}
|
||||
/>
|
||||
</div>
|
||||
<KanbanIssueMemoBlock
|
||||
sub_group_id={sub_group_id}
|
||||
columnId={columnId}
|
||||
issue={issue}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
isReadOnly={isReadOnly}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -145,10 +152,3 @@ export const KanBanIssueMemoBlock: React.FC<IssueBlockProps> = (props) => {
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const validateMemo = (prevProps: IssueBlockProps, nextProps: IssueBlockProps) => {
|
||||
if (prevProps.issue != nextProps.issue) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
export const KanbanIssueBlock = memo(KanBanIssueMemoBlock, validateMemo);
|
||||
|
@ -22,17 +22,20 @@ export const IssueBlocksList: FC<Props> = (props) => {
|
||||
return (
|
||||
<div className="w-full h-full relative divide-y-[0.5px] divide-custom-border-200">
|
||||
{issueIds && issueIds.length > 0 ? (
|
||||
issueIds.map((issueId: string) => (
|
||||
<IssueBlock
|
||||
key={issues[issueId]?.id}
|
||||
columnId={columnId}
|
||||
issue={issues[issueId]}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
isReadonly={isReadonly}
|
||||
displayProperties={displayProperties}
|
||||
/>
|
||||
))
|
||||
issueIds.map(
|
||||
(issueId: string) =>
|
||||
issues[issueId] && (
|
||||
<IssueBlock
|
||||
key={issues[issueId].id}
|
||||
columnId={columnId}
|
||||
issue={issues[issueId]}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
isReadonly={isReadonly}
|
||||
displayProperties={displayProperties}
|
||||
/>
|
||||
)
|
||||
)
|
||||
) : (
|
||||
<div className="bg-custom-background-100 text-custom-text-400 text-sm p-3">No issues</div>
|
||||
)}
|
||||
|
33
web/components/issues/issue-layouts/save-filter-view.tsx
Normal file
33
web/components/issues/issue-layouts/save-filter-view.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { FC, useState } from "react";
|
||||
import { Plus } from "lucide-react";
|
||||
import { Button } from "@plane/ui";
|
||||
// components
|
||||
import { CreateUpdateProjectViewModal } from "components/views";
|
||||
|
||||
interface ISaveFilterView {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
filterParams: any;
|
||||
}
|
||||
|
||||
export const SaveFilterView: FC<ISaveFilterView> = (props) => {
|
||||
const { workspaceSlug, projectId, filterParams } = props;
|
||||
|
||||
const [viewModal, setViewModal] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<CreateUpdateProjectViewModal
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
preLoadedData={{ query_data: { ...filterParams } }}
|
||||
isOpen={viewModal}
|
||||
onClose={() => setViewModal(false)}
|
||||
/>
|
||||
|
||||
<Button size="sm" prependIcon={<Plus />} onClick={() => setViewModal(true)}>
|
||||
Save View
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -26,7 +26,7 @@ export const SpreadsheetStateColumn: React.FC<Props> = (props) => {
|
||||
<>
|
||||
<IssuePropertyState
|
||||
projectId={issue.project_detail?.id ?? null}
|
||||
value={issue.state_detail.id}
|
||||
value={issue.state}
|
||||
defaultOptions={issue?.state_detail ? [issue.state_detail] : []}
|
||||
onChange={(data) => onChange({ state: data.id, state_detail: data })}
|
||||
className="h-full w-full"
|
||||
|
@ -142,7 +142,7 @@ export const SpreadsheetView: React.FC<Props> = observer((props) => {
|
||||
</div>
|
||||
|
||||
<div className="border-t border-custom-border-100">
|
||||
<div className="mb-3 z-50 sticky bottom-0 left-0">
|
||||
<div className="mb-3 z-5 sticky bottom-0 left-0">
|
||||
{enableQuickCreateIssue && (
|
||||
<SpreadsheetQuickAddIssueForm formKey="name" quickAddCallback={quickAddCallback} viewId={viewId} />
|
||||
)}
|
||||
|
@ -78,8 +78,8 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
|
||||
[issue, issueUpdate]
|
||||
);
|
||||
|
||||
const [localTitleValue, setLocalTitleValue] = useState("");
|
||||
const [localIssueDescription, setLocalIssueDescription] = useState("");
|
||||
const [localTitleValue, setLocalTitleValue] = useState(issue.name);
|
||||
const [localIssueDescription, setLocalIssueDescription] = useState(issue.description_html);
|
||||
|
||||
useEffect(() => {
|
||||
if (issue.id) {
|
||||
@ -88,10 +88,6 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
|
||||
}
|
||||
}, [issue.id]);
|
||||
|
||||
useEffect(() => {
|
||||
setLocalTitleValue(issue.name);
|
||||
}, [issue.name]);
|
||||
|
||||
const debouncedFormSave = debounce(async () => {
|
||||
handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting("submitted"));
|
||||
}, 1500);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FC, Fragment, ReactNode } from "react";
|
||||
import { FC, Fragment, ReactNode, useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR from "swr";
|
||||
@ -40,17 +40,17 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && projectId && issueId && peekIssueId && issueId === peekIssueId
|
||||
? `ISSUE_DETAILS_${workspaceSlug}_${projectId}_${peekIssueId}`
|
||||
: null,
|
||||
async () => {
|
||||
if (workspaceSlug && projectId && issueId && issueId === peekIssueId) {
|
||||
if (isArchived) await archivedIssueDetailStore.fetchPeekIssueDetails(workspaceSlug, projectId, peekIssueId);
|
||||
else await issueDetailStore.fetchPeekIssueDetails(workspaceSlug, projectId, peekIssueId);
|
||||
}
|
||||
const fetchIssueDetail = async () => {
|
||||
if (workspaceSlug && projectId && peekIssueId) {
|
||||
if (isArchived)
|
||||
await archivedIssueDetailStore.fetchPeekIssueDetails(workspaceSlug, projectId, peekIssueId as string);
|
||||
else await issueDetailStore.fetchPeekIssueDetails(workspaceSlug, projectId, peekIssueId as string);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchIssueDetail();
|
||||
}, [workspaceSlug, projectId, peekIssueId]);
|
||||
|
||||
const handleCopyText = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
|
Loading…
Reference in New Issue
Block a user