refactor: MobX store structure (#3228)
* query params from router as computed * chore: setup workspace store and sub-stores * chore: update router query store * chore: update store types * fix: pages store changes * change observables and retain object reference * fix build errors * chore: changed the structure of workspace, project, cycle, module and pages * fix: pages fixes * fix: merge conflicts resolved * chore: fixed workspace list * chore: update workspace store accroding to the new response * fix: adding page details to store * fix: adding new contexts and providers * dev: issues store and filters in new store * dev: optimised the issue fetching in issue base store * chore: project views id mapped * update lodash set to directly run inside runInaction since it mutates the object * fix: context changes * code refactor kanban for better mainatinability * optimize Kanban for performance * chore: implemented hooks for all the created stores * chore: removed bridge id * css change and refactor * chore: update cycle store structure * chore: implement the new label root store * chore: removed object structure * chore: implement project view hook * Kanban new store implementation for project issues * fix project root for kanban * feat: workspace and project members endpoint (#3092) * fix: merge conflicts resolved * issue properties optimization * chore: user stores * chore: create new store context and update hooks * chore: setup inbox store and implement router store * chore: initialize and implement project estimate store * chore: initialize global view store * kanban and list view optimization * chore: use new cycle and module store. (#3172) * chore: use new cycle and module store. * chore: minor improvements. * Revert "chore: merge develop" This reverts commit 9d2e0e29e7370b55b48fc2fee4fd126093a6cc48, reversing changes made to 9595493c42be3ea0ddd17b23a0b124555075c062. * chore: implement useGlobalView hook * refactor: projects & inbox store instances (#3179) * refactor: projects & inbox store instances * fix: formatting * fix: action usage * chore: implement useProjectState hook. (#3185) * dev: issue, cycle store optimiation * fix build for code * dev: removed dummy variables * dev: issue store * fix: adding todos * chore: removing legacy store * dev: issues store types and typos * chore: cycle module user properties * fix legacy store deletion issues * chore: change POST to PATCH * fix issues rendering for project root * chore: removed workspace details in workpsaceinvite * chore: created models for display properties * chore: setup member store and implement it everywhere * refactor: module store (#3202) * refactor: cycle store (#3192) * refator: cycle store * some more improvements. * chore: implement useLabel hook. (#3190) * refactor: inbox & project related stores. (#3193) * refactor: inbox -> filter, issues, inoxes & project -> publish, projects store * refactor: workspace-project-id name * fix kanban dropdown overlapping issue * fix kanban layout minor re rendering * chore: implement useMember store everywhere * chore: create and implement editor mention store * chore: removed the issue view user property * chore: created at id changed * dev: segway intgegration (#3132) * feat: implemented rabbitmq * dev: initialize segway with queue setup * dev: import refactors * dev: create communication with the segway server * dev: create new workers * dev: create celery node queue for consuming messages from django * dev: node to celery connection * dev: setup segway and django connection * dev: refactor the structure and add database integration to the app * dev: add external id and source added --------- Co-authored-by: NarayanBavisetti <narayan3119@gmail.com> * dev: github importer (#3205) * dev: initiate github import * dev: github importer all issues import * dev: github comments and links for the imported issues * dev: update controller to use logger and spread the resultData in getAllEntities * dev: removed console log * dev: update code structure and sync functions * dev: updated retry logic when exception * dev: add imported data as well * dev: update logger and repo fetch * dev: update jira integration to new structure * dev: update migrations * dev: update the reason field * chore: workspace object id removed * chore: view's creation fixed * refactor: mobx store improvements. (#3213) * fix: state and label errors * chore: remove legacy code * fix: branch build fix (#3214) * branch build fix for release-* in case of space,backend,proxy * fixes * chore: update store names and types * fix - file size limit not work on plane.settings.production (#3160) * fix - file size limit not work on plane.settings.production * fix - file size limit not work on plane.settings.production * fix - file size limit not work on plane.settings.production, move to common.py --------- Co-authored-by: luanduongtel4vn <hoangluan@tel4vn.com> Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com> * style: instance admin email settings ui & ux update. (#3186) * refactor: use-user-auth hook (#3215) * refactor: use-user-auth hook * fix: user store currentUserLoader * refactor: project-view & application related stores (#3207) * refactor: project-view & application related stores * rename: projectViews -> projectViewIds * fix: project-view favourite state in store * chore: remove unnecessary hooks and contexts (#3217) * chore: update issue assignee property component * chore: bug fixes & improvement (#3218) * chore: draft issue validation added to prevent saving empty or whitespace title * chore: resolve scrolling issue in page empty state * chore: kanban layout quick add issue improvement * fix: bugs & improvements (#3189) * fix: workspace invitation modal form values reset * fix: profile sidebar avatar letter * [refactor] Editor code refactoring (#3194) * removed relative imports from editor core * Update issue widget file paths and imports to use kebab case instead of camel case, to align with coding conventions and improve consistency. * Update Tiptap core and extensions versions to 2.1.13 and Tiptap React version to 2.1.13. Update Tiptap table imports to use the new location in package @tiptap/pm/tables. Update AlertLabel component to use the new type definition for LucideIcon. * updated lock file * removed default exports from editor/core * fixed injecting css into the core package itself * seperated css code to have single source of origin wrt to the package * removed default imports from document editor * all instances using index as key while mapping fixed * Update Lite Text Editor package.json to remove @plane/editor-types as a dependency. Update Lite Text Editor index.ts to update the import of IMentionSuggestion and IMentionHighlight from @plane/editor-types to @plane/editor-core. Update Lite Text Editor ui/index.tsx to update the import of UploadImage, DeleteImage, IMentionSuggestion, and RestoreImage from @plane/editor-types to @plane/editor-core. Update Lite Text Editor ui/menus/fixed-menu/index.tsx to update the import of UploadImage from @plane/editor-types to @plane/editor-core. Update turbo.json to remove @plane/editor-types#build as a dependency for @plane/lite-text-editor#build, @plane/rich-text-editor#build, and @plane/document-editor#build. * Remove deprecated import and adjust tippy.js usage in the slash-commands.tsx file of the editor extensions package. * Update dependencies in `rich-text-editor/package.json`, remove `@plane/editor-types` and add `@plane/editor-core` in `rich-text-editor/src/index.ts`, and update imports in `rich-text-editor/src/ui/extensions/index.tsx` and `rich-text-editor/src/ui/index.tsx` to use `@plane/editor-core` instead of `@plane/editor-types`. * Update package.json dependencies and add new types for image deletion, upload, restore, mention highlight, mention suggestion, and slash command item. * Update import statements in various files to use the new package "@plane/editor-core" instead of "@plane/editor-types". * fixed document editor to follow conventions * Refactor imports in the Rich Text Editor package to use relative paths instead of absolute paths. - Updated imports in `index.ts`, `ui/index.tsx`, and `ui/menus/bubble-menu/index.tsx` to use relative paths. - Updated `tsconfig.json` to include the `baseUrl` compiler option and adjust the `include` and `exclude` paths. * Refactor Lite Text Editor code to use relative import paths instead of absolute import paths. * Added LucideIconType to the exports in index.ts for use in other files. Created a new file lucide-icon.ts which contains the type LucideIconType. Updated the icon type in HeadingOneItem in menu-items/index.tsx to use LucideIconType. Updated the Icon type in AlertLabel in alert-label.tsx to use LucideIconType. Updated the Icon type in VerticalDropdownItemProps in vertical-dropdown-menu.tsx to use LucideIconType. Updated the Icon type in BubbleMenuItem in fixed-menu/index.tsx to use LucideIconType. Deleted the file tooltip.tsx since it is no longer used. Updated the Icon type in BubbleMenuItem in bubble-menu/index.tsx to use LucideIconType. * ♻️ refactor: simplify rendering logic in slash-commands.tsx The rendering logic in the file "slash-commands.tsx" has been simplified. Previously, the code used inline positioning for the popup, but it has now been removed. Instead of appending the popup to the document body, it is now appended to the element with the ID "tiptap-container". The "flip" option has also been removed. These changes have improved the readability and maintainability of the code. * fixed build errors caused due to core's internal imports * regression: fixed pages not saving issue and not duplicating with proper content issue * build: Update @tiptap dependencies Updated the @tiptap dependencies in the package.json files of `document-editor`, `extensions`, and `rich-text-editor` packages to version 2.1.13. * 🚑 fix: Correct appendTo selector in slash-commands.tsx Update the `appendTo` function call in `slash-commands.tsx` to use the correct selector `#editor-container` instead of `#tiptap-container`. This ensures that the component is appended to the appropriate container in the editor extension. Note: The commit message assumes that the change is a fix for an issue or error. If it's not a fix, please provide more context so that an appropriate commit type can be determined. * style: email placeholder changed across the platform (#3206) * style: email placeholder changed across the platform * fix: placeholder text * dev: updated new filter endpoints and restructured issue and issue filters store * implement issues and replace useMobxStore * remove all store legacy references * dev: updated the orderby and subgroupby filters data * dev:added projectId in issue filters for consistency * fix more build errors * dev: updated profile issues * dev: removed store legacy * dev: active cycle issues in the cycle issue store * fix additional build errors and memoize issueActions in each layout component * change store enums * remove all useMobxStore references * fix more build errors * dev: reverted workspace invitation * fix: build errors and warnings * fix: optimistic update for instant operations (#3221) * fix: update functions failed case * fix: typo * chore: revert back to optimistic update approach for all `update related actions` (#3219) * fix: merge conflicts resolved * chore: update memberMap logic in components * add assignees to kanban groups and properties * dev: migration fixes * final bit of optimization on list view * change all TODOs that are to be done before this release to FIXME * change base Kanban TODOs that are to be done before this release to FIXME * dev: add fields and expand for app serializers * dev: issue detail store * dev: update issue serializer to return object ids * fix: Instance key added in settings and converted issues list api to arry instead of dict * fix: removing segway files * dev: control expand through query parameters * revert: github importer * Revert "dev: segway intgegration (#3132)" This reverts commit 1cc18a09156d1790d114061dbac8c901e0f2754c. * dev: remove migrations for segway * dev: issue structure change and created workspacebasemodel * dev: issue detail serializer * fix: changed workspace dict * dev: updated new issue structure * chore: build fix * dev: issue detail store refactor * dev: created list endpoint for issue-relation * dev: added issue attachments in issue detail store * dev: added issue activity computed * fix: build error * chore: peek overview modal context added * chore: build error fix * dev: added sub_issues in issue details store * dev: added complete issue serializer for sub issues * dev: resolved type errors in issue root store * dev: changed the issue relation structure * chore: new global dropdowns * chore: build error fix * chore: cycle and module selection if disabled * dev: removed unnecessary code from the workspace root * chore: build error fix * chore: issue relation remove endpoint * fix: build error * dev: typos and implemented issue relation store * fix: yarn lock updated * style: update the UI of all the dropdowns * fix: state store fixes * fix: key issue * fix: state store console logs removed * refactor: member dropdowns * fix: moving types to packages * fix: dropdown arrow positioning * dev: removed logs * style: label dropdown * chore: restrict description notifications * chore: description changes * chore: update spreadsheet layout dropdowns * fix: build errors * chore: duplicate key change * fix: ui bugs * chore: relation activity change * chore: comment activity changes * chore: blocking issue removal * chore: added project_id for relation * chore: issue relation store and component * chore: issue redirection issue in the issue realtion in detail page * chore: created activity changed * chore: issue links new store implementation on the issue detail * chore: issue relation deletion acitivity changed * chore: issue attachments new store implementation on the issue detail * chore: workspace level issues * fix: build errors --------- Co-authored-by: rahulramesha <rahulramesham@gmail.com> Co-authored-by: gurusainath <gurusainath007@gmail.com> Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com> Co-authored-by: NarayanBavisetti <narayan3119@gmail.com> Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com> Co-authored-by: Lakhan Baheti <94619783+1akhanBaheti@users.noreply.github.com> Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Co-authored-by: Manish Gupta <59428681+mguptahub@users.noreply.github.com> Co-authored-by: Hoang Luan <luandnh98@gmail.com> Co-authored-by: luanduongtel4vn <hoangluan@tel4vn.com> Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Co-authored-by: M. Palanikannan <73993394+Palanikannan1437@users.noreply.github.com> Co-authored-by: pablohashescobar <nikhilschacko@gmail.com> Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
This commit is contained in:
parent
1539340113
commit
804b7d8663
940 changed files with 26378 additions and 34411 deletions
|
|
@ -11,7 +11,7 @@ import { Loader, Tooltip } from "@plane/ui";
|
|||
// helpers
|
||||
import { renderFormattedTime, renderFormattedDate, calculateTimeAgo } from "helpers/date-time.helper";
|
||||
// types
|
||||
import { IIssueActivity } from "types";
|
||||
import { IIssueActivity } from "@plane/types";
|
||||
import { History } from "lucide-react";
|
||||
|
||||
type Props = {
|
||||
|
|
|
|||
88
web/components/issues/attachment/attachment-detail.tsx
Normal file
88
web/components/issues/attachment/attachment-detail.tsx
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
import { FC, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { AlertCircle, X } from "lucide-react";
|
||||
// hooks
|
||||
import { useIssueDetail, useMember } from "hooks/store";
|
||||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// components
|
||||
import { IssueAttachmentDeleteModal } from "./delete-attachment-confirmation-modal";
|
||||
// icons
|
||||
import { getFileIcon } from "components/icons";
|
||||
// helper
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
import { renderFormattedDate } from "helpers/date-time.helper";
|
||||
import { convertBytesToSize, getFileExtension, getFileName } from "helpers/attachment.helper";
|
||||
// type
|
||||
import { TIssueAttachmentsList } from "./attachments-list";
|
||||
|
||||
export type TIssueAttachmentsDetail = TIssueAttachmentsList & {
|
||||
attachmentId: string;
|
||||
};
|
||||
|
||||
export const IssueAttachmentsDetail: FC<TIssueAttachmentsDetail> = (props) => {
|
||||
// props
|
||||
const { attachmentId, handleAttachmentOperations } = props;
|
||||
// store hooks
|
||||
const { getUserDetails } = useMember();
|
||||
const {
|
||||
attachment: { getAttachmentById },
|
||||
} = useIssueDetail();
|
||||
// states
|
||||
const [attachmentDeleteModal, setAttachmentDeleteModal] = useState<boolean>(false);
|
||||
|
||||
const attachment = attachmentId && getAttachmentById(attachmentId);
|
||||
|
||||
if (!attachment) return <></>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<IssueAttachmentDeleteModal
|
||||
isOpen={attachmentDeleteModal}
|
||||
setIsOpen={setAttachmentDeleteModal}
|
||||
handleAttachmentOperations={handleAttachmentOperations}
|
||||
data={attachment}
|
||||
/>
|
||||
|
||||
<div
|
||||
key={attachmentId}
|
||||
className="flex h-[60px] items-center justify-between gap-1 rounded-md border-[2px] border-custom-border-200 bg-custom-background-100 px-4 py-2 text-sm"
|
||||
>
|
||||
<Link href={attachment.asset} target="_blank" rel="noopener noreferrer">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="h-7 w-7">{getFileIcon(getFileExtension(attachment.asset))}</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Tooltip tooltipContent={getFileName(attachment.attributes.name)}>
|
||||
<span className="text-sm">{truncateText(`${getFileName(attachment.attributes.name)}`, 10)}</span>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
tooltipContent={`${
|
||||
getUserDetails(attachment.updated_by)?.display_name ?? ""
|
||||
} uploaded on ${renderFormattedDate(attachment.updated_at)}`}
|
||||
>
|
||||
<span>
|
||||
<AlertCircle className="h-3 w-3" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 text-xs text-custom-text-200">
|
||||
<span>{getFileExtension(attachment.asset).toUpperCase()}</span>
|
||||
<span>{convertBytesToSize(attachment.attributes.size)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
setAttachmentDeleteModal(true);
|
||||
}}
|
||||
>
|
||||
<X className="h-4 w-4 text-custom-text-200 hover:text-custom-text-100" />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,40 +1,29 @@
|
|||
import { useCallback, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { mutate } from "swr";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// services
|
||||
import { IssueAttachmentService } from "services/issue";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// types
|
||||
import { IIssueAttachment } from "types";
|
||||
// fetch-keys
|
||||
import { ISSUE_ATTACHMENTS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
|
||||
import { useApplication } from "hooks/store";
|
||||
// constants
|
||||
import { MAX_FILE_SIZE } from "constants/common";
|
||||
// types
|
||||
import { TAttachmentOperations } from "./root";
|
||||
|
||||
type TAttachmentOperationsModal = Exclude<TAttachmentOperations, "remove">;
|
||||
|
||||
type Props = {
|
||||
disabled?: boolean;
|
||||
handleAttachmentOperations: TAttachmentOperationsModal;
|
||||
};
|
||||
|
||||
const issueAttachmentService = new IssueAttachmentService();
|
||||
|
||||
export const IssueAttachmentUpload: React.FC<Props> = observer((props) => {
|
||||
const { disabled = false } = props;
|
||||
const { disabled = false, handleAttachmentOperations } = props;
|
||||
// store hooks
|
||||
const {
|
||||
router: { workspaceSlug },
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
// states
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, issueId } = router.query;
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
|
||||
const onDrop = useCallback((acceptedFiles: File[]) => {
|
||||
if (!acceptedFiles[0] || !workspaceSlug) return;
|
||||
|
|
@ -49,31 +38,7 @@ export const IssueAttachmentUpload: React.FC<Props> = observer((props) => {
|
|||
})
|
||||
);
|
||||
setIsLoading(true);
|
||||
|
||||
issueAttachmentService
|
||||
.uploadIssueAttachment(workspaceSlug as string, projectId as string, issueId as string, formData)
|
||||
.then((res) => {
|
||||
mutate<IIssueAttachment[]>(
|
||||
ISSUE_ATTACHMENTS(issueId as string),
|
||||
(prevData) => [res, ...(prevData ?? [])],
|
||||
false
|
||||
);
|
||||
mutate(PROJECT_ISSUES_ACTIVITY(issueId as string));
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "File added successfully.",
|
||||
});
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
setIsLoading(false);
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "error!",
|
||||
message: "Something went wrong. please check file type & size (max 5 MB)",
|
||||
});
|
||||
});
|
||||
handleAttachmentOperations.create(formData).finally(() => setIsLoading(false));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
|
|
|
|||
32
web/components/issues/attachment/attachments-list.tsx
Normal file
32
web/components/issues/attachment/attachments-list.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useIssueDetail } from "hooks/store";
|
||||
// components
|
||||
import { IssueAttachmentsDetail } from "./attachment-detail";
|
||||
// types
|
||||
import { TAttachmentOperations } from "./root";
|
||||
|
||||
export type TAttachmentOperationsRemoveModal = Exclude<TAttachmentOperations, "create">;
|
||||
|
||||
export type TIssueAttachmentsList = {
|
||||
handleAttachmentOperations: TAttachmentOperationsRemoveModal;
|
||||
};
|
||||
|
||||
export const IssueAttachmentsList: FC<TIssueAttachmentsList> = observer((props) => {
|
||||
const { handleAttachmentOperations } = props;
|
||||
// store hooks
|
||||
const {
|
||||
attachment: { issueAttachments },
|
||||
} = useIssueDetail();
|
||||
|
||||
return (
|
||||
<>
|
||||
{issueAttachments &&
|
||||
issueAttachments.length > 0 &&
|
||||
issueAttachments.map((attachmentId) => (
|
||||
<IssueAttachmentsDetail attachmentId={attachmentId} handleAttachmentOperations={handleAttachmentOperations} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
import React, { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR from "swr";
|
||||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
import { DeleteAttachmentModal } from "./delete-attachment-modal";
|
||||
// icons
|
||||
import { getFileIcon } from "components/icons";
|
||||
import { AlertCircle, X } from "lucide-react";
|
||||
// services
|
||||
import { IssueAttachmentService } from "services/issue";
|
||||
import { ProjectMemberService } from "services/project";
|
||||
// fetch-key
|
||||
import { ISSUE_ATTACHMENTS, PROJECT_MEMBERS } from "constants/fetch-keys";
|
||||
// helper
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
import { renderFormattedDate } from "helpers/date-time.helper";
|
||||
import { convertBytesToSize, getFileExtension, getFileName } from "helpers/attachment.helper";
|
||||
// type
|
||||
import { IIssueAttachment } from "types";
|
||||
|
||||
// services
|
||||
const issueAttachmentService = new IssueAttachmentService();
|
||||
const projectMemberService = new ProjectMemberService();
|
||||
|
||||
type Props = {
|
||||
editable: boolean;
|
||||
};
|
||||
|
||||
export const IssueAttachments: React.FC<Props> = (props) => {
|
||||
const { editable } = props;
|
||||
|
||||
// states
|
||||
const [deleteAttachment, setDeleteAttachment] = useState<IIssueAttachment | null>(null);
|
||||
const [attachmentDeleteModal, setAttachmentDeleteModal] = useState<boolean>(false);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, issueId } = router.query;
|
||||
|
||||
const { data: attachments } = useSWR<IIssueAttachment[]>(
|
||||
workspaceSlug && projectId && issueId ? ISSUE_ATTACHMENTS(issueId as string) : null,
|
||||
workspaceSlug && projectId && issueId
|
||||
? () => issueAttachmentService.getIssueAttachment(workspaceSlug as string, projectId as string, issueId as string)
|
||||
: null
|
||||
);
|
||||
|
||||
const { data: people } = useSWR(
|
||||
workspaceSlug && projectId ? PROJECT_MEMBERS(projectId as string) : null,
|
||||
workspaceSlug && projectId
|
||||
? () => projectMemberService.fetchProjectMembers(workspaceSlug as string, projectId as string)
|
||||
: null
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeleteAttachmentModal
|
||||
isOpen={attachmentDeleteModal}
|
||||
setIsOpen={setAttachmentDeleteModal}
|
||||
data={deleteAttachment}
|
||||
/>
|
||||
{attachments &&
|
||||
attachments.length > 0 &&
|
||||
attachments.map((file) => (
|
||||
<div
|
||||
key={file.id}
|
||||
className="flex h-[60px] items-center justify-between gap-1 rounded-md border-[2px] border-custom-border-200 bg-custom-background-100 px-4 py-2 text-sm"
|
||||
>
|
||||
<Link href={file.asset} target="_blank" rel="noopener noreferrer">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="h-7 w-7">{getFileIcon(getFileExtension(file.asset))}</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Tooltip tooltipContent={getFileName(file.attributes.name)}>
|
||||
<span className="text-sm">{truncateText(`${getFileName(file.attributes.name)}`, 10)}</span>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
tooltipContent={`${
|
||||
people?.find((person) => person.member.id === file.updated_by)?.member.display_name ?? ""
|
||||
} uploaded on ${renderFormattedDate(file.updated_at)}`}
|
||||
>
|
||||
<span>
|
||||
<AlertCircle className="h-3 w-3" />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 text-xs text-custom-text-200">
|
||||
<span>{getFileExtension(file.asset).toUpperCase()}</span>
|
||||
<span>{convertBytesToSize(file.attributes.size)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
|
||||
{editable && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setDeleteAttachment(file);
|
||||
setAttachmentDeleteModal(true);
|
||||
}}
|
||||
>
|
||||
<X className="h-4 w-4 text-custom-text-200 hover:text-custom-text-100" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,72 +1,42 @@
|
|||
import React from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { mutate } from "swr";
|
||||
|
||||
import { FC, Fragment, Dispatch, SetStateAction, useState } from "react";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
// headless ui
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// services
|
||||
import { IssueAttachmentService } from "services/issue";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// icons
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
// helper
|
||||
import { getFileName } from "helpers/attachment.helper";
|
||||
// types
|
||||
import type { IIssueAttachment } from "types";
|
||||
// fetch-keys
|
||||
import { ISSUE_ATTACHMENTS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys";
|
||||
import type { TIssueAttachment } from "@plane/types";
|
||||
import { TIssueAttachmentsList } from "./attachments-list";
|
||||
|
||||
type Props = {
|
||||
type Props = TIssueAttachmentsList & {
|
||||
isOpen: boolean;
|
||||
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
data: IIssueAttachment | null;
|
||||
setIsOpen: Dispatch<SetStateAction<boolean>>;
|
||||
data: TIssueAttachment;
|
||||
};
|
||||
|
||||
// services
|
||||
const issueAttachmentService = new IssueAttachmentService();
|
||||
|
||||
export const DeleteAttachmentModal: React.FC<Props> = ({ isOpen, setIsOpen, data }) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, issueId } = router.query;
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
export const IssueAttachmentDeleteModal: FC<Props> = (props) => {
|
||||
const { isOpen, setIsOpen, data, handleAttachmentOperations } = props;
|
||||
// state
|
||||
const [loader, setLoader] = useState(false);
|
||||
|
||||
const handleClose = () => {
|
||||
setIsOpen(false);
|
||||
setLoader(false);
|
||||
};
|
||||
|
||||
const handleDeletion = async (assetId: string) => {
|
||||
if (!workspaceSlug || !projectId || !data) return;
|
||||
|
||||
mutate<IIssueAttachment[]>(
|
||||
ISSUE_ATTACHMENTS(issueId as string),
|
||||
(prevData) => (prevData ?? [])?.filter((p) => p.id !== assetId),
|
||||
false
|
||||
);
|
||||
|
||||
await issueAttachmentService
|
||||
.deleteIssueAttachment(workspaceSlug as string, projectId as string, issueId as string, assetId as string)
|
||||
.then(() => mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)))
|
||||
.catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "error!",
|
||||
message: "Something went wrong please try again.",
|
||||
});
|
||||
});
|
||||
setLoader(true);
|
||||
handleAttachmentOperations.remove(assetId).finally(() => handleClose());
|
||||
};
|
||||
|
||||
return (
|
||||
data && (
|
||||
<Transition.Root show={isOpen} as={React.Fragment}>
|
||||
<Transition.Root show={isOpen} as={Fragment}>
|
||||
<Dialog as="div" className="relative z-20" onClose={handleClose}>
|
||||
<Transition.Child
|
||||
as={React.Fragment}
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
|
|
@ -80,7 +50,7 @@ export const DeleteAttachmentModal: React.FC<Props> = ({ isOpen, setIsOpen, data
|
|||
<div className="fixed inset-0 z-20 overflow-y-auto">
|
||||
<div className="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
|
||||
<Transition.Child
|
||||
as={React.Fragment}
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
enterTo="opacity-100 translate-y-0 sm:scale-100"
|
||||
|
|
@ -118,10 +88,10 @@ export const DeleteAttachmentModal: React.FC<Props> = ({ isOpen, setIsOpen, data
|
|||
tabIndex={1}
|
||||
onClick={() => {
|
||||
handleDeletion(data.id);
|
||||
handleClose();
|
||||
}}
|
||||
disabled={loader}
|
||||
>
|
||||
Delete
|
||||
{loader ? "Deleting..." : "Delete"}
|
||||
</Button>
|
||||
</div>
|
||||
</Dialog.Panel>
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
export * from "./root";
|
||||
|
||||
export * from "./attachment-upload";
|
||||
export * from "./attachments";
|
||||
export * from "./delete-attachment-modal";
|
||||
export * from "./delete-attachment-confirmation-modal";
|
||||
|
||||
export * from "./attachments-list";
|
||||
export * from "./attachment-detail";
|
||||
|
|
|
|||
77
web/components/issues/attachment/root.tsx
Normal file
77
web/components/issues/attachment/root.tsx
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { FC, useMemo } from "react";
|
||||
// hooks
|
||||
import { useApplication, useIssueDetail } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import { IssueAttachmentUpload } from "./attachment-upload";
|
||||
import { IssueAttachmentsList } from "./attachments-list";
|
||||
|
||||
export type TIssueAttachmentRoot = {
|
||||
isEditable: boolean;
|
||||
};
|
||||
|
||||
export type TAttachmentOperations = {
|
||||
create: (data: FormData) => Promise<void>;
|
||||
remove: (linkId: string) => Promise<void>;
|
||||
};
|
||||
|
||||
export const IssueAttachmentRoot: FC<TIssueAttachmentRoot> = (props) => {
|
||||
// props
|
||||
const { isEditable } = props;
|
||||
// hooks
|
||||
const {
|
||||
router: { workspaceSlug, projectId },
|
||||
} = useApplication();
|
||||
const { issueId, createAttachment, removeAttachment } = useIssueDetail();
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const handleAttachmentOperations: TAttachmentOperations = useMemo(
|
||||
() => ({
|
||||
create: async (data: FormData) => {
|
||||
try {
|
||||
if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing required fields");
|
||||
await createAttachment(workspaceSlug, projectId, issueId, data);
|
||||
setToastAlert({
|
||||
message: "The attachment has been successfully uploaded",
|
||||
type: "success",
|
||||
title: "Attachment uploaded",
|
||||
});
|
||||
} catch (error) {
|
||||
setToastAlert({
|
||||
message: "The attachment could not be uploaded",
|
||||
type: "error",
|
||||
title: "Attachment not uploaded",
|
||||
});
|
||||
}
|
||||
},
|
||||
remove: async (attachmentId: string) => {
|
||||
try {
|
||||
if (!workspaceSlug || !projectId || !issueId) throw new Error("Missing required fields");
|
||||
await removeAttachment(workspaceSlug, projectId, issueId, attachmentId);
|
||||
setToastAlert({
|
||||
message: "The attachment has been successfully removed",
|
||||
type: "success",
|
||||
title: "Attachment removed",
|
||||
});
|
||||
} catch (error) {
|
||||
setToastAlert({
|
||||
message: "The Attachment could not be removed",
|
||||
type: "error",
|
||||
title: "Attachment not removed",
|
||||
});
|
||||
}
|
||||
},
|
||||
}),
|
||||
[workspaceSlug, projectId, issueId, createAttachment, removeAttachment, setToastAlert]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="relative py-3 space-y-3">
|
||||
<h3 className="text-lg">Attachments</h3>
|
||||
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
|
||||
<IssueAttachmentUpload disabled={isEditable} handleAttachmentOperations={handleAttachmentOperations} />
|
||||
<IssueAttachmentsList handleAttachmentOperations={handleAttachmentOperations} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
import React from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
|
||||
// hooks
|
||||
import { useMention } from "hooks/store";
|
||||
// services
|
||||
import { FileService } from "services/file.service";
|
||||
// components
|
||||
|
|
@ -9,10 +10,8 @@ import { LiteTextEditorWithRef } from "@plane/lite-text-editor";
|
|||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
import { Globe2, Lock } from "lucide-react";
|
||||
|
||||
// types
|
||||
import type { IIssueActivity } from "types";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
import type { IIssueActivity } from "@plane/types";
|
||||
|
||||
const defaultValues: Partial<IIssueActivity> = {
|
||||
access: "INTERNAL",
|
||||
|
|
@ -47,13 +46,14 @@ const commentAccess: commentAccessType[] = [
|
|||
const fileService = new FileService();
|
||||
|
||||
export const AddComment: React.FC<Props> = ({ disabled = false, onSubmit, showAccessSpecifier = false }) => {
|
||||
// refs
|
||||
const editorRef = React.useRef<any>(null);
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const editorSuggestions = useEditorSuggestions();
|
||||
|
||||
// store hooks
|
||||
const { mentionHighlights, mentionSuggestions } = useMention();
|
||||
// form info
|
||||
const {
|
||||
control,
|
||||
formState: { isSubmitting },
|
||||
|
|
@ -99,8 +99,8 @@ export const AddComment: React.FC<Props> = ({ disabled = false, onSubmit, showAc
|
|||
? { accessValue: accessValue ?? "INTERNAL", onAccessChange, showAccessSpecifier, commentAccess }
|
||||
: undefined
|
||||
}
|
||||
mentionSuggestions={editorSuggestions.mentionSuggestions}
|
||||
mentionHighlights={editorSuggestions.mentionHighlights}
|
||||
mentionSuggestions={mentionSuggestions}
|
||||
mentionHighlights={mentionHighlights}
|
||||
submitButton={
|
||||
<Button
|
||||
variant="primary"
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useMention, useUser } from "hooks/store";
|
||||
// services
|
||||
import { FileService } from "services/file.service";
|
||||
// icons
|
||||
import { Check, Globe2, Lock, MessageSquare, Pencil, Trash2, X } from "lucide-react";
|
||||
// hooks
|
||||
import useUser from "hooks/use-user";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
// ui
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
import { CommentReaction } from "components/issues";
|
||||
|
|
@ -14,7 +14,7 @@ import { LiteTextEditorWithRef, LiteReadOnlyEditorWithRef } from "@plane/lite-te
|
|||
// helpers
|
||||
import { calculateTimeAgo } from "helpers/date-time.helper";
|
||||
// types
|
||||
import type { IIssueActivity } from "types";
|
||||
import type { IIssueActivity } from "@plane/types";
|
||||
|
||||
// services
|
||||
const fileService = new FileService();
|
||||
|
|
@ -27,22 +27,17 @@ type Props = {
|
|||
workspaceSlug: string;
|
||||
};
|
||||
|
||||
export const CommentCard: React.FC<Props> = ({
|
||||
comment,
|
||||
handleCommentDeletion,
|
||||
onSubmit,
|
||||
showAccessSpecifier = false,
|
||||
workspaceSlug,
|
||||
}) => {
|
||||
const { user } = useUser();
|
||||
|
||||
export const CommentCard: React.FC<Props> = observer((props) => {
|
||||
const { comment, handleCommentDeletion, onSubmit, showAccessSpecifier = false, workspaceSlug } = props;
|
||||
// states
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
// refs
|
||||
const editorRef = React.useRef<any>(null);
|
||||
const showEditorRef = React.useRef<any>(null);
|
||||
|
||||
const editorSuggestions = useEditorSuggestions();
|
||||
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
// store hooks
|
||||
const { currentUser } = useUser();
|
||||
const { mentionHighlights, mentionSuggestions } = useMention();
|
||||
// form info
|
||||
const {
|
||||
formState: { isSubmitting },
|
||||
handleSubmit,
|
||||
|
|
@ -113,8 +108,8 @@ export const CommentCard: React.FC<Props> = ({
|
|||
debouncedUpdatesEnabled={false}
|
||||
customClassName="min-h-[50px] p-3 shadow-sm"
|
||||
onChange={(comment_json: Object, comment_html: string) => setValue("comment_html", comment_html)}
|
||||
mentionSuggestions={editorSuggestions.mentionSuggestions}
|
||||
mentionHighlights={editorSuggestions.mentionHighlights}
|
||||
mentionSuggestions={mentionSuggestions}
|
||||
mentionHighlights={mentionHighlights}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-1 self-end">
|
||||
|
|
@ -145,13 +140,13 @@ export const CommentCard: React.FC<Props> = ({
|
|||
ref={showEditorRef}
|
||||
value={comment.comment_html ?? ""}
|
||||
customClassName="text-xs border border-custom-border-200 bg-custom-background-100"
|
||||
mentionHighlights={editorSuggestions.mentionHighlights}
|
||||
mentionHighlights={mentionHighlights}
|
||||
/>
|
||||
<CommentReaction projectId={comment.project} commentId={comment.id} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{user?.id === comment.actor && (
|
||||
{currentUser?.id === comment.actor && (
|
||||
<CustomMenu ellipsis>
|
||||
<CustomMenu.MenuItem onClick={() => setIsEditing(true)} className="flex items-center gap-1">
|
||||
<Pencil className="h-3 w-3" />
|
||||
|
|
@ -191,4 +186,4 @@ export const CommentCard: React.FC<Props> = ({
|
|||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
import { FC } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import useUser from "hooks/use-user";
|
||||
import { useUser } from "hooks/store";
|
||||
import useCommentReaction from "hooks/use-comment-reaction";
|
||||
// ui
|
||||
import { ReactionSelector } from "components/core";
|
||||
// helper
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { IssueCommentReaction } from "types";
|
||||
// types
|
||||
import { IssueCommentReaction } from "@plane/types";
|
||||
|
||||
type Props = {
|
||||
projectId?: string | string[];
|
||||
|
|
@ -15,13 +17,13 @@ type Props = {
|
|||
readonly?: boolean;
|
||||
};
|
||||
|
||||
export const CommentReaction: FC<Props> = (props) => {
|
||||
export const CommentReaction: FC<Props> = observer((props) => {
|
||||
const { projectId, commentId, readonly = false } = props;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { user } = useUser();
|
||||
// store hooks
|
||||
const { currentUser } = useUser();
|
||||
|
||||
const { commentReactions, groupedReactions, handleReactionCreate, handleReactionDelete } = useCommentReaction(
|
||||
workspaceSlug,
|
||||
|
|
@ -33,7 +35,7 @@ export const CommentReaction: FC<Props> = (props) => {
|
|||
if (!workspaceSlug || !projectId || !commentId) return;
|
||||
|
||||
const isSelected = commentReactions?.some(
|
||||
(r: IssueCommentReaction) => r.actor === user?.id && r.reaction === reaction
|
||||
(r: IssueCommentReaction) => r.actor === currentUser?.id && r.reaction === reaction
|
||||
);
|
||||
|
||||
if (isSelected) {
|
||||
|
|
@ -51,7 +53,7 @@ export const CommentReaction: FC<Props> = (props) => {
|
|||
position="top"
|
||||
value={
|
||||
commentReactions
|
||||
?.filter((reaction: IssueCommentReaction) => reaction.actor === user?.id)
|
||||
?.filter((reaction: IssueCommentReaction) => reaction.actor === currentUser?.id)
|
||||
.map((r: IssueCommentReaction) => r.reaction) || []
|
||||
}
|
||||
onSelect={handleReactionClick}
|
||||
|
|
@ -70,7 +72,9 @@ export const CommentReaction: FC<Props> = (props) => {
|
|||
}}
|
||||
key={reaction}
|
||||
className={`flex h-full items-center gap-1 rounded-md px-2 py-1 text-sm text-custom-text-100 ${
|
||||
commentReactions?.some((r: IssueCommentReaction) => r.actor === user?.id && r.reaction === reaction)
|
||||
commentReactions?.some(
|
||||
(r: IssueCommentReaction) => r.actor === currentUser?.id && r.reaction === reaction
|
||||
)
|
||||
? "bg-custom-primary-100/10"
|
||||
: "bg-custom-background-80"
|
||||
}`}
|
||||
|
|
@ -78,7 +82,9 @@ export const CommentReaction: FC<Props> = (props) => {
|
|||
<span>{renderEmoji(reaction)}</span>
|
||||
<span
|
||||
className={
|
||||
commentReactions?.some((r: IssueCommentReaction) => r.actor === user?.id && r.reaction === reaction)
|
||||
commentReactions?.some(
|
||||
(r: IssueCommentReaction) => r.actor === currentUser?.id && r.reaction === reaction
|
||||
)
|
||||
? "text-custom-primary-100"
|
||||
: ""
|
||||
}
|
||||
|
|
@ -90,4 +96,4 @@ export const CommentReaction: FC<Props> = (props) => {
|
|||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,19 +3,19 @@ import { useRouter } from "next/router";
|
|||
import { observer } from "mobx-react-lite";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import { useIssues, useProject } from "hooks/store";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// types
|
||||
import type { IIssue } from "types";
|
||||
import type { TIssue } from "@plane/types";
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
handleClose: () => void;
|
||||
data: IIssue;
|
||||
data: TIssue;
|
||||
onSubmit?: () => Promise<void>;
|
||||
};
|
||||
|
||||
|
|
@ -26,8 +26,11 @@ export const DeleteArchivedIssueModal: React.FC<Props> = observer((props) => {
|
|||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
const { getProjectById } = useProject();
|
||||
|
||||
const { archivedIssueDetail: archivedIssueDetailStore } = useMobxStore();
|
||||
const {
|
||||
issues: { removeIssue },
|
||||
} = useIssues(EIssuesStoreType.ARCHIVED);
|
||||
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
|
||||
|
|
@ -45,8 +48,7 @@ export const DeleteArchivedIssueModal: React.FC<Props> = observer((props) => {
|
|||
|
||||
setIsDeleteLoading(true);
|
||||
|
||||
await archivedIssueDetailStore
|
||||
.deleteArchivedIssue(workspaceSlug.toString(), data.project, data.id)
|
||||
await removeIssue(workspaceSlug.toString(), data.project_id, data.id)
|
||||
.then(() => {
|
||||
if (onSubmit) onSubmit();
|
||||
})
|
||||
|
|
@ -106,7 +108,7 @@ export const DeleteArchivedIssueModal: React.FC<Props> = observer((props) => {
|
|||
<p className="text-sm text-custom-text-200">
|
||||
Are you sure you want to delete issue{" "}
|
||||
<span className="break-words font-medium text-custom-text-100">
|
||||
{data?.project_detail.identifier}-{data?.sequence_id}
|
||||
{getProjectById(data?.project_id)?.identifier}-{data?.sequence_id}
|
||||
</span>
|
||||
{""}? All of the data related to the archived issue will be permanently removed. This action
|
||||
cannot be undone.
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// services
|
||||
import { IssueDraftService } from "services/issue";
|
||||
// hooks
|
||||
|
|
@ -13,29 +10,29 @@ import { AlertTriangle } from "lucide-react";
|
|||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// types
|
||||
import type { IIssue } from "types";
|
||||
import type { TIssue } from "@plane/types";
|
||||
import { useProject } from "hooks/store";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
handleClose: () => void;
|
||||
data: IIssue | null;
|
||||
data: TIssue | null;
|
||||
onSubmit?: () => Promise<void> | void;
|
||||
};
|
||||
|
||||
const issueDraftService = new IssueDraftService();
|
||||
|
||||
export const DeleteDraftIssueModal: React.FC<Props> = observer((props) => {
|
||||
export const DeleteDraftIssueModal: React.FC<Props> = (props) => {
|
||||
const { isOpen, handleClose, data, onSubmit } = props;
|
||||
|
||||
// states
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
|
||||
const { user: userStore } = useMobxStore();
|
||||
const user = userStore.currentUser;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
// hooks
|
||||
const { getProjectById } = useProject();
|
||||
|
||||
useEffect(() => {
|
||||
setIsDeleteLoading(false);
|
||||
|
|
@ -47,12 +44,12 @@ export const DeleteDraftIssueModal: React.FC<Props> = observer((props) => {
|
|||
};
|
||||
|
||||
const handleDeletion = async () => {
|
||||
if (!workspaceSlug || !data || !user) return;
|
||||
if (!workspaceSlug || !data) return;
|
||||
|
||||
setIsDeleteLoading(true);
|
||||
|
||||
await issueDraftService
|
||||
.deleteDraftIssue(workspaceSlug as string, data.project, data.id)
|
||||
.deleteDraftIssue(workspaceSlug.toString(), data.project_id, data.id)
|
||||
.then(() => {
|
||||
setIsDeleteLoading(false);
|
||||
handleClose();
|
||||
|
|
@ -64,7 +61,7 @@ export const DeleteDraftIssueModal: React.FC<Props> = observer((props) => {
|
|||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
console.error(error);
|
||||
handleClose();
|
||||
setToastAlert({
|
||||
title: "Error",
|
||||
|
|
@ -116,7 +113,7 @@ export const DeleteDraftIssueModal: React.FC<Props> = observer((props) => {
|
|||
<p className="text-sm text-custom-text-200">
|
||||
Are you sure you want to delete issue{" "}
|
||||
<span className="break-words font-medium text-custom-text-100">
|
||||
{data?.project_detail.identifier}-{data?.sequence_id}
|
||||
{data && getProjectById(data?.project_id)?.identifier}-{data?.sequence_id}
|
||||
</span>
|
||||
{""}? All of the data related to the draft issue will be permanently removed. This action cannot
|
||||
be undone.
|
||||
|
|
@ -138,4 +135,4 @@ export const DeleteDraftIssueModal: React.FC<Props> = observer((props) => {
|
|||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,26 +6,37 @@ import { Button } from "@plane/ui";
|
|||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// types
|
||||
import type { IIssue } from "types";
|
||||
import { useIssues } from "hooks/store/use-issues";
|
||||
import { TIssue } from "@plane/types";
|
||||
import { useProject } from "hooks/store";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
handleClose: () => void;
|
||||
data: IIssue;
|
||||
dataId?: string | null | undefined;
|
||||
data?: TIssue;
|
||||
onSubmit?: () => Promise<void>;
|
||||
};
|
||||
|
||||
export const DeleteIssueModal: React.FC<Props> = (props) => {
|
||||
const { data, isOpen, handleClose, onSubmit } = props;
|
||||
const { dataId, data, isOpen, handleClose, onSubmit } = props;
|
||||
|
||||
const { issueMap } = useIssues();
|
||||
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
// hooks
|
||||
const { getProjectById } = useProject();
|
||||
|
||||
useEffect(() => {
|
||||
setIsDeleteLoading(false);
|
||||
}, [isOpen]);
|
||||
|
||||
if (!dataId && !data) return null;
|
||||
|
||||
const issue = data ? data : issueMap[dataId!];
|
||||
|
||||
const onClose = () => {
|
||||
setIsDeleteLoading(false);
|
||||
handleClose();
|
||||
|
|
@ -93,7 +104,7 @@ export const DeleteIssueModal: React.FC<Props> = (props) => {
|
|||
<p className="text-sm text-custom-text-200">
|
||||
Are you sure you want to delete issue{" "}
|
||||
<span className="break-words font-medium text-custom-text-100">
|
||||
{data?.project_detail?.identifier}-{data?.sequence_id}
|
||||
{getProjectById(issue?.project_id)?.identifier}-{issue?.sequence_id}
|
||||
</span>
|
||||
{""}? All of the data related to the issue will be permanently removed. This action cannot be
|
||||
undone.
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ import debounce from "lodash/debounce";
|
|||
import { TextArea } from "@plane/ui";
|
||||
import { RichTextEditor } from "@plane/rich-text-editor";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
import { TIssue } from "@plane/types";
|
||||
// services
|
||||
import { FileService } from "services/file.service";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
import { useMention } from "hooks/store";
|
||||
|
||||
export interface IssueDescriptionFormValues {
|
||||
name: string;
|
||||
|
|
@ -39,16 +39,16 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
|
|||
const [characterLimit, setCharacterLimit] = useState(false);
|
||||
|
||||
const { setShowAlert } = useReloadConfirmations();
|
||||
|
||||
const editorSuggestion = useEditorSuggestions();
|
||||
|
||||
// store hooks
|
||||
const { mentionHighlights, mentionSuggestions } = useMention();
|
||||
// form info
|
||||
const {
|
||||
handleSubmit,
|
||||
watch,
|
||||
reset,
|
||||
control,
|
||||
formState: { errors },
|
||||
} = useForm<IIssue>({
|
||||
} = useForm<TIssue>({
|
||||
defaultValues: {
|
||||
name: "",
|
||||
description_html: "",
|
||||
|
|
@ -72,7 +72,7 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
|
|||
}, [issue.id]); // TODO: verify the exhaustive-deps warning
|
||||
|
||||
const handleDescriptionFormSubmit = useCallback(
|
||||
async (formData: Partial<IIssue>) => {
|
||||
async (formData: Partial<TIssue>) => {
|
||||
if (!formData?.name || formData?.name.length === 0 || formData?.name.length > 255) return;
|
||||
|
||||
await handleFormSubmit({
|
||||
|
|
@ -135,10 +135,8 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
|
|||
debouncedFormSave();
|
||||
}}
|
||||
required
|
||||
className={`min-h-min block w-full resize-none overflow-hidden rounded border-none bg-transparent px-3 py-2 text-2xl font-medium outline-none ring-0 focus:ring-1 focus:ring-custom-primary ${
|
||||
!isAllowed ? "hover:cursor-not-allowed" : ""
|
||||
}`}
|
||||
hasError={Boolean(errors?.description)}
|
||||
className="min-h-min block w-full resize-none overflow-hidden rounded border-none bg-transparent px-3 py-2 text-2xl font-medium outline-none ring-0 focus:ring-1 focus:ring-custom-primary"
|
||||
hasError={Boolean(errors?.name)}
|
||||
role="textbox"
|
||||
disabled={!isAllowed}
|
||||
/>
|
||||
|
|
@ -172,9 +170,7 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
|
|||
setShouldShowAlert={setShowAlert}
|
||||
setIsSubmitting={setIsSubmitting}
|
||||
dragDropEnabled
|
||||
customClassName={
|
||||
isAllowed ? "min-h-[150px] shadow-sm" : "!p-0 !pt-2 text-custom-text-200 pointer-events-none"
|
||||
}
|
||||
customClassName={isAllowed ? "min-h-[150px] shadow-sm" : "!p-0 !pt-2 text-custom-text-200"}
|
||||
noBorder={!isAllowed}
|
||||
onChange={(description: Object, description_html: string) => {
|
||||
setShowAlert(true);
|
||||
|
|
@ -182,8 +178,8 @@ export const IssueDescriptionForm: FC<IssueDetailsProps> = (props) => {
|
|||
onChange(description_html);
|
||||
debouncedFormSave();
|
||||
}}
|
||||
mentionSuggestions={editorSuggestion.mentionSuggestions}
|
||||
mentionHighlights={editorSuggestion.mentionHighlights}
|
||||
mentionSuggestions={mentionSuggestions}
|
||||
mentionHighlights={mentionHighlights}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,72 +1,62 @@
|
|||
import React, { FC, useState, useEffect, useRef } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Sparkle, X } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication, useEstimate, useMention } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import useLocalStorage from "hooks/use-local-storage";
|
||||
// services
|
||||
import { AIService } from "services/ai.service";
|
||||
import { FileService } from "services/file.service";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import useLocalStorage from "hooks/use-local-storage";
|
||||
// components
|
||||
import { GptAssistantPopover } from "components/core";
|
||||
import { ParentIssuesListModal } from "components/issues";
|
||||
import {
|
||||
IssueAssigneeSelect,
|
||||
IssueDateSelect,
|
||||
IssueEstimateSelect,
|
||||
IssueLabelSelect,
|
||||
IssuePrioritySelect,
|
||||
IssueProjectSelect,
|
||||
IssueStateSelect,
|
||||
} from "components/issues/select";
|
||||
import { IssueLabelSelect } from "components/issues/select";
|
||||
import { CreateStateModal } from "components/states";
|
||||
import { CreateLabelModal } from "components/labels";
|
||||
// ui
|
||||
import {} from "components/ui";
|
||||
import { Button, CustomMenu, Input, ToggleSwitch } from "@plane/ui";
|
||||
// icons
|
||||
import { Sparkle, X } from "lucide-react";
|
||||
// types
|
||||
import type { IUser, IIssue, ISearchIssueResponse } from "types";
|
||||
// components
|
||||
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import {
|
||||
DateDropdown,
|
||||
EstimateDropdown,
|
||||
PriorityDropdown,
|
||||
ProjectDropdown,
|
||||
ProjectMemberDropdown,
|
||||
StateDropdown,
|
||||
} from "components/dropdowns";
|
||||
// ui
|
||||
import { Button, CustomMenu, Input, ToggleSwitch } from "@plane/ui";
|
||||
// helpers
|
||||
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
||||
// types
|
||||
import type { IUser, TIssue, ISearchIssueResponse } from "@plane/types";
|
||||
|
||||
const aiService = new AIService();
|
||||
const fileService = new FileService();
|
||||
|
||||
const defaultValues: Partial<IIssue> = {
|
||||
project: "",
|
||||
const defaultValues: Partial<TIssue> = {
|
||||
project_id: "",
|
||||
name: "",
|
||||
description: {
|
||||
type: "doc",
|
||||
content: [
|
||||
{
|
||||
type: "paragraph",
|
||||
},
|
||||
],
|
||||
},
|
||||
description_html: "<p></p>",
|
||||
estimate_point: null,
|
||||
state: "",
|
||||
parent: null,
|
||||
state_id: "",
|
||||
parent_id: null,
|
||||
priority: "none",
|
||||
assignees: [],
|
||||
labels: [],
|
||||
start_date: null,
|
||||
target_date: null,
|
||||
assignee_ids: [],
|
||||
label_ids: [],
|
||||
start_date: undefined,
|
||||
target_date: undefined,
|
||||
};
|
||||
|
||||
interface IssueFormProps {
|
||||
handleFormSubmit: (
|
||||
formData: Partial<IIssue>,
|
||||
formData: Partial<TIssue>,
|
||||
action?: "createDraft" | "createNewIssue" | "updateDraft" | "convertToNewIssue"
|
||||
) => Promise<void>;
|
||||
data?: Partial<IIssue> | null;
|
||||
data?: Partial<TIssue> | null;
|
||||
isOpen: boolean;
|
||||
prePopulatedData?: Partial<IIssue> | null;
|
||||
prePopulatedData?: Partial<TIssue> | null;
|
||||
projectId: string;
|
||||
setActiveProject: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
createMore: boolean;
|
||||
|
|
@ -112,10 +102,12 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
const [selectedParentIssue, setSelectedParentIssue] = useState<ISearchIssueResponse | null>(null);
|
||||
const [gptAssistantModal, setGptAssistantModal] = useState(false);
|
||||
const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false);
|
||||
// store hooks
|
||||
const { areEstimatesActiveForProject } = useEstimate();
|
||||
const { mentionHighlights, mentionSuggestions } = useMention();
|
||||
// hooks
|
||||
const { setValue: setLocalStorageValue } = useLocalStorage("draftedIssue", {});
|
||||
const { setToastAlert } = useToast();
|
||||
const editorSuggestions = useEditorSuggestions();
|
||||
// refs
|
||||
const editorRef = useRef<any>(null);
|
||||
// router
|
||||
|
|
@ -123,8 +115,8 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
const { workspaceSlug } = router.query;
|
||||
// store
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
// form info
|
||||
const {
|
||||
formState: { errors, isSubmitting },
|
||||
|
|
@ -135,27 +127,26 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
getValues,
|
||||
setValue,
|
||||
setFocus,
|
||||
} = useForm<IIssue>({
|
||||
} = useForm<TIssue>({
|
||||
defaultValues: prePopulatedData ?? defaultValues,
|
||||
reValidateMode: "onChange",
|
||||
});
|
||||
|
||||
const issueName = watch("name");
|
||||
|
||||
const payload: Partial<IIssue> = {
|
||||
const payload: Partial<TIssue> = {
|
||||
name: watch("name"),
|
||||
description: watch("description"),
|
||||
description_html: watch("description_html"),
|
||||
state: watch("state"),
|
||||
state_id: watch("state_id"),
|
||||
priority: watch("priority"),
|
||||
assignees: watch("assignees"),
|
||||
labels: watch("labels"),
|
||||
assignee_ids: watch("assignee_ids"),
|
||||
label_ids: watch("label_ids"),
|
||||
start_date: watch("start_date"),
|
||||
target_date: watch("target_date"),
|
||||
project: watch("project"),
|
||||
parent: watch("parent"),
|
||||
cycle: watch("cycle"),
|
||||
module: watch("module"),
|
||||
project_id: watch("project_id"),
|
||||
parent_id: watch("parent_id"),
|
||||
cycle_id: watch("cycle_id"),
|
||||
module_id: watch("module_id"),
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -189,31 +180,24 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
// };
|
||||
|
||||
const handleCreateUpdateIssue = async (
|
||||
formData: Partial<IIssue>,
|
||||
formData: Partial<TIssue>,
|
||||
action: "createDraft" | "createNewIssue" | "updateDraft" | "convertToNewIssue" = "createDraft"
|
||||
) => {
|
||||
await handleFormSubmit(
|
||||
{
|
||||
...(data ?? {}),
|
||||
...formData,
|
||||
is_draft: action === "createDraft" || action === "updateDraft",
|
||||
// is_draft: action === "createDraft" || action === "updateDraft",
|
||||
},
|
||||
action
|
||||
);
|
||||
// TODO: check_with_backend
|
||||
|
||||
setGptAssistantModal(false);
|
||||
|
||||
reset({
|
||||
...defaultValues,
|
||||
project: projectId,
|
||||
description: {
|
||||
type: "doc",
|
||||
content: [
|
||||
{
|
||||
type: "paragraph",
|
||||
},
|
||||
],
|
||||
},
|
||||
project_id: projectId,
|
||||
description_html: "<p></p>",
|
||||
});
|
||||
editorRef?.current?.clearEditor();
|
||||
|
|
@ -222,7 +206,7 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
const handleAiAssistance = async (response: string) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
setValue("description", {});
|
||||
// setValue("description", {});
|
||||
setValue("description_html", `${watch("description_html")}<p>${response}</p>`);
|
||||
editorRef.current?.setEditorValue(`${watch("description_html")}`);
|
||||
};
|
||||
|
|
@ -280,7 +264,7 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
useEffect(() => {
|
||||
reset({
|
||||
...getValues(),
|
||||
project: projectId,
|
||||
project_id: projectId,
|
||||
});
|
||||
}, [getValues, projectId, reset]);
|
||||
|
||||
|
|
@ -302,7 +286,7 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
isOpen={labelModal}
|
||||
handleClose={() => setLabelModal(false)}
|
||||
projectId={projectId}
|
||||
onSuccess={(response) => setValue("labels", [...watch("labels"), response.id])}
|
||||
onSuccess={(response) => setValue("label_ids", [...watch("label_ids"), response.id])}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -316,14 +300,15 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
{(fieldsToShow.includes("all") || fieldsToShow.includes("project")) && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="project"
|
||||
name="project_id"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<IssueProjectSelect
|
||||
<ProjectDropdown
|
||||
value={value}
|
||||
onChange={(val: string) => {
|
||||
onChange={(val) => {
|
||||
onChange(val);
|
||||
setActiveProject(val);
|
||||
}}
|
||||
buttonVariant="background-with-text"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -332,7 +317,7 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
{status ? "Update" : "Create"} Issue
|
||||
</h3>
|
||||
</div>
|
||||
{watch("parent") &&
|
||||
{watch("parent_id") &&
|
||||
(fieldsToShow.includes("all") || fieldsToShow.includes("parent")) &&
|
||||
selectedParentIssue && (
|
||||
<div className="flex w-min items-center gap-2 whitespace-nowrap rounded bg-custom-background-80 p-2 text-xs">
|
||||
|
|
@ -350,7 +335,7 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
<X
|
||||
className="h-3 w-3 cursor-pointer"
|
||||
onClick={() => {
|
||||
setValue("parent", null);
|
||||
setValue("parent_id", null);
|
||||
setSelectedParentIssue(null);
|
||||
}}
|
||||
/>
|
||||
|
|
@ -454,10 +439,9 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
customClassName="min-h-[150px]"
|
||||
onChange={(description: Object, description_html: string) => {
|
||||
onChange(description_html);
|
||||
setValue("description", description);
|
||||
}}
|
||||
mentionHighlights={editorSuggestions.mentionHighlights}
|
||||
mentionSuggestions={editorSuggestions.mentionSuggestions}
|
||||
mentionHighlights={mentionHighlights}
|
||||
mentionSuggestions={mentionSuggestions}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -467,14 +451,16 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
{(fieldsToShow.includes("all") || fieldsToShow.includes("state")) && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="state"
|
||||
name="state_id"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<IssueStateSelect
|
||||
setIsOpen={setStateModal}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
projectId={projectId}
|
||||
/>
|
||||
<div className="h-7">
|
||||
<StateDropdown
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
projectId={projectId}
|
||||
buttonVariant="border-with-text"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -483,80 +469,100 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
control={control}
|
||||
name="priority"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<IssuePrioritySelect value={value} onChange={onChange} />
|
||||
<div className="h-7">
|
||||
<PriorityDropdown value={value} onChange={onChange} buttonVariant="background-with-text" />
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("assignee")) && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="assignees"
|
||||
name="assignee_ids"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<IssueAssigneeSelect projectId={projectId} value={value} onChange={onChange} />
|
||||
<div className="h-7">
|
||||
<ProjectMemberDropdown
|
||||
projectId={projectId}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
multiple
|
||||
buttonVariant="background-with-text"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("label")) && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="labels"
|
||||
name="label_ids"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<IssueLabelSelect
|
||||
setIsOpen={setLabelModal}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
projectId={projectId}
|
||||
/>
|
||||
<div className="h-7">
|
||||
<IssueLabelSelect
|
||||
setIsOpen={setLabelModal}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
projectId={projectId}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("startDate")) && (
|
||||
<div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="start_date"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<IssueDateSelect
|
||||
label="Start date"
|
||||
maxDate={maxDate ?? undefined}
|
||||
onChange={onChange}
|
||||
<Controller
|
||||
control={control}
|
||||
name="start_date"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<div className="h-7">
|
||||
<DateDropdown
|
||||
value={value}
|
||||
onChange={(date) => onChange(date ? renderFormattedPayloadDate(date) : null)}
|
||||
buttonVariant="border-with-text"
|
||||
placeholder="Start date"
|
||||
maxDate={maxDate ?? undefined}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("dueDate")) && (
|
||||
<div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="target_date"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<IssueDateSelect
|
||||
label="Due date"
|
||||
minDate={minDate ?? undefined}
|
||||
onChange={onChange}
|
||||
<Controller
|
||||
control={control}
|
||||
name="target_date"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<div className="h-7">
|
||||
<DateDropdown
|
||||
value={value}
|
||||
onChange={(date) => onChange(date ? renderFormattedPayloadDate(date) : null)}
|
||||
buttonVariant="border-with-text"
|
||||
placeholder="Due date"
|
||||
minDate={minDate ?? undefined}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("estimate")) && (
|
||||
<div>
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("estimate")) &&
|
||||
areEstimatesActiveForProject(projectId) && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="estimate_point"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<IssueEstimateSelect value={value} onChange={onChange} />
|
||||
<div className="h-7">
|
||||
<EstimateDropdown
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
projectId={projectId}
|
||||
buttonVariant="background-with-text"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("parent")) && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="parent"
|
||||
name="parent_id"
|
||||
render={({ field: { onChange } }) => (
|
||||
<ParentIssuesListModal
|
||||
isOpen={parentIssueListModalOpen}
|
||||
|
|
@ -572,12 +578,12 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
)}
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("parent")) && (
|
||||
<CustomMenu ellipsis>
|
||||
{watch("parent") ? (
|
||||
{watch("parent_id") ? (
|
||||
<>
|
||||
<CustomMenu.MenuItem onClick={() => setParentIssueListModalOpen(true)}>
|
||||
Change parent issue
|
||||
</CustomMenu.MenuItem>
|
||||
<CustomMenu.MenuItem onClick={() => setValue("parent", null)}>
|
||||
<CustomMenu.MenuItem onClick={() => setValue("parent_id", null)}>
|
||||
Remove parent issue
|
||||
</CustomMenu.MenuItem>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -3,27 +3,27 @@ import { useRouter } from "next/router";
|
|||
import { observer } from "mobx-react-lite";
|
||||
import { mutate } from "swr";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// services
|
||||
import { IssueService } from "services/issue";
|
||||
import { ModuleService } from "services/module.service";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import useLocalStorage from "hooks/use-local-storage";
|
||||
import { useIssues, useProject, useUser } from "hooks/store";
|
||||
// components
|
||||
import { DraftIssueForm } from "components/issues";
|
||||
// types
|
||||
import type { IIssue } from "types";
|
||||
import type { TIssue } from "@plane/types";
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
// fetch-keys
|
||||
import { PROJECT_ISSUES_DETAILS, USER_ISSUE, SUB_ISSUES } from "constants/fetch-keys";
|
||||
|
||||
interface IssuesModalProps {
|
||||
data?: IIssue | null;
|
||||
data?: TIssue | null;
|
||||
handleClose: () => void;
|
||||
isOpen: boolean;
|
||||
isUpdatingSingleIssue?: boolean;
|
||||
prePopulateData?: Partial<IIssue>;
|
||||
prePopulateData?: Partial<TIssue>;
|
||||
fieldsToShow?: (
|
||||
| "project"
|
||||
| "name"
|
||||
|
|
@ -38,7 +38,7 @@ interface IssuesModalProps {
|
|||
| "parent"
|
||||
| "all"
|
||||
)[];
|
||||
onSubmit?: (data: Partial<IIssue>) => Promise<void> | void;
|
||||
onSubmit?: (data: Partial<TIssue>) => Promise<void> | void;
|
||||
}
|
||||
|
||||
// services
|
||||
|
|
@ -59,15 +59,16 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||
// states
|
||||
const [createMore, setCreateMore] = useState(false);
|
||||
const [activeProject, setActiveProject] = useState<string | null>(null);
|
||||
const [prePopulateData, setPreloadedData] = useState<Partial<IIssue> | undefined>(undefined);
|
||||
|
||||
const [prePopulateData, setPreloadedData] = useState<Partial<TIssue> | undefined>(undefined);
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||
|
||||
const { project: projectStore, user: userStore, projectDraftIssues: draftIssueStore } = useMobxStore();
|
||||
|
||||
const user = userStore.currentUser;
|
||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
||||
// store
|
||||
const { issues: draftIssues } = useIssues(EIssuesStoreType.DRAFT);
|
||||
const { currentUser } = useUser();
|
||||
const { workspaceProjectIds: workspaceProjects } = useProject();
|
||||
// derived values
|
||||
const projects = workspaceProjects;
|
||||
|
||||
const { clearValue: clearDraftIssueLocalStorage } = useLocalStorage("draftedIssue", {});
|
||||
|
||||
|
|
@ -86,14 +87,14 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||
useEffect(() => {
|
||||
setPreloadedData(prePopulateDataProps ?? {});
|
||||
|
||||
if (cycleId && !prePopulateDataProps?.cycle) {
|
||||
if (cycleId && !prePopulateDataProps?.cycle_id) {
|
||||
setPreloadedData((prevData) => ({
|
||||
...(prevData ?? {}),
|
||||
...prePopulateDataProps,
|
||||
cycle: cycleId.toString(),
|
||||
}));
|
||||
}
|
||||
if (moduleId && !prePopulateDataProps?.module) {
|
||||
if (moduleId && !prePopulateDataProps?.module_id) {
|
||||
setPreloadedData((prevData) => ({
|
||||
...(prevData ?? {}),
|
||||
...prePopulateDataProps,
|
||||
|
|
@ -102,27 +103,27 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||
}
|
||||
if (
|
||||
(router.asPath.includes("my-issues") || router.asPath.includes("assigned")) &&
|
||||
!prePopulateDataProps?.assignees
|
||||
!prePopulateDataProps?.assignee_ids
|
||||
) {
|
||||
setPreloadedData((prevData) => ({
|
||||
...(prevData ?? {}),
|
||||
...prePopulateDataProps,
|
||||
assignees: prePopulateDataProps?.assignees ?? [user?.id ?? ""],
|
||||
assignees: prePopulateDataProps?.assignee_ids ?? [currentUser?.id ?? ""],
|
||||
}));
|
||||
}
|
||||
}, [prePopulateDataProps, cycleId, moduleId, router.asPath, user?.id]);
|
||||
}, [prePopulateDataProps, cycleId, moduleId, router.asPath, currentUser?.id]);
|
||||
|
||||
useEffect(() => {
|
||||
setPreloadedData(prePopulateDataProps ?? {});
|
||||
|
||||
if (cycleId && !prePopulateDataProps?.cycle) {
|
||||
if (cycleId && !prePopulateDataProps?.cycle_id) {
|
||||
setPreloadedData((prevData) => ({
|
||||
...(prevData ?? {}),
|
||||
...prePopulateDataProps,
|
||||
cycle: cycleId.toString(),
|
||||
}));
|
||||
}
|
||||
if (moduleId && !prePopulateDataProps?.module) {
|
||||
if (moduleId && !prePopulateDataProps?.module_id) {
|
||||
setPreloadedData((prevData) => ({
|
||||
...(prevData ?? {}),
|
||||
...prePopulateDataProps,
|
||||
|
|
@ -131,15 +132,15 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||
}
|
||||
if (
|
||||
(router.asPath.includes("my-issues") || router.asPath.includes("assigned")) &&
|
||||
!prePopulateDataProps?.assignees
|
||||
!prePopulateDataProps?.assignee_ids
|
||||
) {
|
||||
setPreloadedData((prevData) => ({
|
||||
...(prevData ?? {}),
|
||||
...prePopulateDataProps,
|
||||
assignees: prePopulateDataProps?.assignees ?? [user?.id ?? ""],
|
||||
assignees: prePopulateDataProps?.assignee_ids ?? [currentUser?.id ?? ""],
|
||||
}));
|
||||
}
|
||||
}, [prePopulateDataProps, cycleId, moduleId, router.asPath, user?.id]);
|
||||
}, [prePopulateDataProps, cycleId, moduleId, router.asPath, currentUser?.id]);
|
||||
|
||||
useEffect(() => {
|
||||
// if modal is closed, reset active project to null
|
||||
|
|
@ -151,32 +152,35 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||
|
||||
// if data is present, set active project to the project of the
|
||||
// issue. This has more priority than the project in the url.
|
||||
if (data && data.project) return setActiveProject(data.project);
|
||||
if (data && data.project_id) return setActiveProject(data.project_id);
|
||||
|
||||
if (prePopulateData && prePopulateData.project && !activeProject) return setActiveProject(prePopulateData.project);
|
||||
if (prePopulateData && prePopulateData.project_id && !activeProject)
|
||||
return setActiveProject(prePopulateData.project_id);
|
||||
|
||||
if (prePopulateData && prePopulateData.project && !activeProject) return setActiveProject(prePopulateData.project);
|
||||
if (prePopulateData && prePopulateData.project_id && !activeProject)
|
||||
return setActiveProject(prePopulateData.project_id);
|
||||
|
||||
// if data is not present, set active project to the project
|
||||
// in the url. This has the least priority.
|
||||
if (projects && projects.length > 0 && !activeProject)
|
||||
setActiveProject(projects?.find((p) => p.id === projectId)?.id ?? projects?.[0].id ?? null);
|
||||
setActiveProject(projects?.find((id) => id === projectId) ?? projects?.[0] ?? null);
|
||||
}, [activeProject, data, projectId, projects, isOpen, prePopulateData]);
|
||||
|
||||
const createDraftIssue = async (payload: Partial<IIssue>) => {
|
||||
if (!workspaceSlug || !activeProject || !user) return;
|
||||
const createDraftIssue = async (payload: Partial<TIssue>) => {
|
||||
if (!workspaceSlug || !activeProject || !currentUser) return;
|
||||
|
||||
await draftIssueStore
|
||||
await draftIssues
|
||||
.createIssue(workspaceSlug as string, activeProject ?? "", payload)
|
||||
.then(async () => {
|
||||
await draftIssueStore.fetchIssues(workspaceSlug as string, activeProject ?? "", "mutation");
|
||||
await draftIssues.fetchIssues(workspaceSlug as string, activeProject ?? "", "mutation");
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: "Issue created successfully.",
|
||||
});
|
||||
|
||||
if (payload.assignees?.some((assignee) => assignee === user?.id)) mutate(USER_ISSUE(workspaceSlug.toString()));
|
||||
if (payload.assignee_ids?.some((assignee) => assignee === currentUser?.id))
|
||||
mutate(USER_ISSUE(workspaceSlug.toString()));
|
||||
})
|
||||
.catch(() => {
|
||||
setToastAlert({
|
||||
|
|
@ -189,22 +193,20 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||
if (!createMore) onClose();
|
||||
};
|
||||
|
||||
const updateDraftIssue = async (payload: Partial<IIssue>) => {
|
||||
if (!user) return;
|
||||
|
||||
await draftIssueStore
|
||||
const updateDraftIssue = async (payload: Partial<TIssue>) => {
|
||||
await draftIssues
|
||||
.updateIssue(workspaceSlug as string, activeProject ?? "", data?.id ?? "", payload)
|
||||
.then((res) => {
|
||||
if (isUpdatingSingleIssue) {
|
||||
mutate<IIssue>(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false);
|
||||
mutate<TIssue>(PROJECT_ISSUES_DETAILS, (prevData) => ({ ...prevData, ...res }), false);
|
||||
} else {
|
||||
if (payload.parent) mutate(SUB_ISSUES(payload.parent.toString()));
|
||||
if (payload.parent_id) mutate(SUB_ISSUES(payload.parent_id.toString()));
|
||||
}
|
||||
|
||||
if (!payload.is_draft) {
|
||||
if (payload.cycle && payload.cycle !== "") addIssueToCycle(res.id, payload.cycle);
|
||||
if (payload.module && payload.module !== "") addIssueToModule(res.id, payload.module);
|
||||
}
|
||||
// if (!payload.is_draft) { // TODO: check_with_backend
|
||||
// if (payload.cycle_id && payload.cycle_id !== "") addIssueToCycle(res.id, payload.cycle_id);
|
||||
// if (payload.module_id && payload.module_id !== "") addIssueToModule(res.id, payload.module_id);
|
||||
// }
|
||||
|
||||
if (!createMore) onClose();
|
||||
|
||||
|
|
@ -224,7 +226,7 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||
};
|
||||
|
||||
const addIssueToCycle = async (issueId: string, cycleId: string) => {
|
||||
if (!workspaceSlug || !activeProject || !user) return;
|
||||
if (!workspaceSlug || !activeProject) return;
|
||||
|
||||
await issueService.addIssueToCycle(workspaceSlug as string, activeProject ?? "", cycleId, {
|
||||
issues: [issueId],
|
||||
|
|
@ -232,21 +234,21 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||
};
|
||||
|
||||
const addIssueToModule = async (issueId: string, moduleId: string) => {
|
||||
if (!workspaceSlug || !activeProject || !user) return;
|
||||
if (!workspaceSlug || !activeProject) return;
|
||||
|
||||
await moduleService.addIssuesToModule(workspaceSlug as string, activeProject ?? "", moduleId as string, {
|
||||
issues: [issueId],
|
||||
});
|
||||
};
|
||||
|
||||
const createIssue = async (payload: Partial<IIssue>) => {
|
||||
if (!workspaceSlug || !activeProject || !user) return;
|
||||
const createIssue = async (payload: Partial<TIssue>) => {
|
||||
if (!workspaceSlug || !activeProject) return;
|
||||
|
||||
await issueService
|
||||
.createIssue(workspaceSlug.toString(), activeProject, payload)
|
||||
.then(async (res) => {
|
||||
if (payload.cycle && payload.cycle !== "") await addIssueToCycle(res.id, payload.cycle);
|
||||
if (payload.module && payload.module !== "") await addIssueToModule(res.id, payload.module);
|
||||
if (payload.cycle_id && payload.cycle_id !== "") await addIssueToCycle(res.id, payload.cycle_id);
|
||||
if (payload.module_id && payload.module_id !== "") await addIssueToModule(res.id, payload.module_id);
|
||||
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
|
|
@ -256,9 +258,10 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||
|
||||
if (!createMore) onClose();
|
||||
|
||||
if (payload.assignees?.some((assignee) => assignee === user?.id)) mutate(USER_ISSUE(workspaceSlug as string));
|
||||
if (payload.assignee_ids?.some((assignee) => assignee === currentUser?.id))
|
||||
mutate(USER_ISSUE(workspaceSlug as string));
|
||||
|
||||
if (payload.parent && payload.parent !== "") mutate(SUB_ISSUES(payload.parent));
|
||||
if (payload.parent_id && payload.parent_id !== "") mutate(SUB_ISSUES(payload.parent_id));
|
||||
})
|
||||
.catch(() => {
|
||||
setToastAlert({
|
||||
|
|
@ -270,14 +273,14 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||
};
|
||||
|
||||
const handleFormSubmit = async (
|
||||
formData: Partial<IIssue>,
|
||||
formData: Partial<TIssue>,
|
||||
action: "createDraft" | "createNewIssue" | "updateDraft" | "convertToNewIssue" = "createDraft"
|
||||
) => {
|
||||
if (!workspaceSlug || !activeProject) return;
|
||||
|
||||
const payload: Partial<IIssue> = {
|
||||
const payload: Partial<TIssue> = {
|
||||
...formData,
|
||||
description: formData.description ?? "",
|
||||
// description: formData.description ?? "",
|
||||
description_html: formData.description_html ?? "<p></p>",
|
||||
};
|
||||
|
||||
|
|
@ -332,7 +335,7 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = observer(
|
|||
projectId={activeProject ?? ""}
|
||||
setActiveProject={setActiveProject}
|
||||
status={data ? true : false}
|
||||
user={user ?? undefined}
|
||||
user={currentUser ?? undefined}
|
||||
fieldsToShow={fieldsToShow}
|
||||
/>
|
||||
</Dialog.Panel>
|
||||
|
|
|
|||
|
|
@ -2,63 +2,61 @@ import React, { FC, useState, useEffect, useRef } from "react";
|
|||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { LayoutPanelTop, Sparkle, X } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication, useEstimate, useMention, useProject } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// services
|
||||
import { AIService } from "services/ai.service";
|
||||
import { FileService } from "services/file.service";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import { GptAssistantPopover } from "components/core";
|
||||
import { ParentIssuesListModal } from "components/issues";
|
||||
import {
|
||||
IssueAssigneeSelect,
|
||||
IssueDateSelect,
|
||||
IssueEstimateSelect,
|
||||
IssueLabelSelect,
|
||||
IssuePrioritySelect,
|
||||
IssueProjectSelect,
|
||||
IssueStateSelect,
|
||||
IssueModuleSelect,
|
||||
IssueCycleSelect,
|
||||
} from "components/issues/select";
|
||||
import { IssueLabelSelect } from "components/issues/select";
|
||||
import { CreateStateModal } from "components/states";
|
||||
import { CreateLabelModal } from "components/labels";
|
||||
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
|
||||
import {
|
||||
CycleDropdown,
|
||||
DateDropdown,
|
||||
EstimateDropdown,
|
||||
ModuleDropdown,
|
||||
PriorityDropdown,
|
||||
ProjectDropdown,
|
||||
ProjectMemberDropdown,
|
||||
StateDropdown,
|
||||
} from "components/dropdowns";
|
||||
// ui
|
||||
import { Button, CustomMenu, Input, ToggleSwitch } from "@plane/ui";
|
||||
// icons
|
||||
import { LayoutPanelTop, Sparkle, X } from "lucide-react";
|
||||
// helpers
|
||||
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
||||
// types
|
||||
import type { IIssue, ISearchIssueResponse } from "types";
|
||||
// components
|
||||
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
import type { TIssue, ISearchIssueResponse } from "@plane/types";
|
||||
|
||||
const defaultValues: Partial<IIssue> = {
|
||||
project: "",
|
||||
const defaultValues: Partial<TIssue> = {
|
||||
project_id: "",
|
||||
name: "",
|
||||
description_html: "<p></p>",
|
||||
estimate_point: null,
|
||||
state: "",
|
||||
parent: null,
|
||||
state_id: "",
|
||||
parent_id: null,
|
||||
priority: "none",
|
||||
assignees: [],
|
||||
labels: [],
|
||||
start_date: null,
|
||||
target_date: null,
|
||||
assignee_ids: [],
|
||||
label_ids: [],
|
||||
start_date: undefined,
|
||||
target_date: undefined,
|
||||
};
|
||||
|
||||
export interface IssueFormProps {
|
||||
handleFormSubmit: (values: Partial<IIssue>) => Promise<void>;
|
||||
initialData?: Partial<IIssue>;
|
||||
handleFormSubmit: (values: Partial<TIssue>) => Promise<void>;
|
||||
initialData?: Partial<TIssue>;
|
||||
projectId: string;
|
||||
setActiveProject: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
createMore: boolean;
|
||||
setCreateMore: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
handleDiscardClose: () => void;
|
||||
status: boolean;
|
||||
handleFormDirty: (payload: Partial<IIssue> | null) => void;
|
||||
handleFormDirty: (payload: Partial<TIssue> | null) => void;
|
||||
fieldsToShow: (
|
||||
| "project"
|
||||
| "name"
|
||||
|
|
@ -106,14 +104,14 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// store
|
||||
// store hooks
|
||||
const {
|
||||
user: userStore,
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
const user = userStore.currentUser;
|
||||
// hooks
|
||||
const editorSuggestion = useEditorSuggestions();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
const { getProjectById } = useProject();
|
||||
const { areEstimatesActiveForProject } = useEstimate();
|
||||
const { mentionHighlights, mentionSuggestions } = useMention();
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
// form info
|
||||
const {
|
||||
|
|
@ -125,50 +123,44 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
getValues,
|
||||
setValue,
|
||||
setFocus,
|
||||
} = useForm<IIssue>({
|
||||
} = useForm<TIssue>({
|
||||
defaultValues: initialData ?? defaultValues,
|
||||
reValidateMode: "onChange",
|
||||
});
|
||||
|
||||
const issueName = watch("name");
|
||||
|
||||
const payload: Partial<IIssue> = {
|
||||
const payload: Partial<TIssue> = {
|
||||
name: getValues("name"),
|
||||
description: getValues("description"),
|
||||
state: getValues("state"),
|
||||
state_id: getValues("state_id"),
|
||||
priority: getValues("priority"),
|
||||
assignees: getValues("assignees"),
|
||||
labels: getValues("labels"),
|
||||
assignee_ids: getValues("assignee_ids"),
|
||||
label_ids: getValues("label_ids"),
|
||||
start_date: getValues("start_date"),
|
||||
target_date: getValues("target_date"),
|
||||
project: getValues("project"),
|
||||
parent: getValues("parent"),
|
||||
cycle: getValues("cycle"),
|
||||
module: getValues("module"),
|
||||
project_id: getValues("project_id"),
|
||||
parent_id: getValues("parent_id"),
|
||||
cycle_id: getValues("cycle_id"),
|
||||
module_id: getValues("module_id"),
|
||||
};
|
||||
|
||||
// derived values
|
||||
const projectDetails = getProjectById(projectId);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDirty) handleFormDirty(payload);
|
||||
else handleFormDirty(null);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [JSON.stringify(payload), isDirty]);
|
||||
|
||||
const handleCreateUpdateIssue = async (formData: Partial<IIssue>) => {
|
||||
const handleCreateUpdateIssue = async (formData: Partial<TIssue>) => {
|
||||
await handleFormSubmit(formData);
|
||||
|
||||
setGptAssistantModal(false);
|
||||
|
||||
reset({
|
||||
...defaultValues,
|
||||
project: projectId,
|
||||
description: {
|
||||
type: "doc",
|
||||
content: [
|
||||
{
|
||||
type: "paragraph",
|
||||
},
|
||||
],
|
||||
},
|
||||
project_id: projectId,
|
||||
description_html: "<p></p>",
|
||||
});
|
||||
editorRef?.current?.clearEditor();
|
||||
|
|
@ -177,18 +169,17 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
const handleAiAssistance = async (response: string) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
setValue("description", {});
|
||||
setValue("description_html", `${watch("description_html")}<p>${response}</p>`);
|
||||
editorRef.current?.setEditorValue(`${watch("description_html")}`);
|
||||
};
|
||||
|
||||
const handleAutoGenerateDescription = async () => {
|
||||
if (!workspaceSlug || !projectId || !user) return;
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
setIAmFeelingLucky(true);
|
||||
|
||||
aiService
|
||||
.createGptTask(workspaceSlug as string, projectId as string, {
|
||||
.createGptTask(workspaceSlug.toString(), projectId.toString(), {
|
||||
prompt: issueName,
|
||||
task: "Generate a proper description for this issue.",
|
||||
})
|
||||
|
|
@ -227,7 +218,6 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
reset({
|
||||
...defaultValues,
|
||||
...initialData,
|
||||
project: projectId,
|
||||
});
|
||||
}, [setFocus, initialData, reset]);
|
||||
|
||||
|
|
@ -235,7 +225,7 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
useEffect(() => {
|
||||
reset({
|
||||
...getValues(),
|
||||
project: projectId,
|
||||
project_id: projectId,
|
||||
});
|
||||
}, [getValues, projectId, reset]);
|
||||
|
||||
|
|
@ -257,29 +247,31 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
isOpen={labelModal}
|
||||
handleClose={() => setLabelModal(false)}
|
||||
projectId={projectId}
|
||||
onSuccess={(response) => setValue("labels", [...watch("labels"), response.id])}
|
||||
onSuccess={(response) => setValue("label_ids", [...watch("label_ids"), response.id])}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<form onSubmit={handleSubmit(handleCreateUpdateIssue)}>
|
||||
<div className="space-y-5">
|
||||
<div className="flex items-center gap-x-2">
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("project")) && (
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("project")) && !status && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="project"
|
||||
name="project_id"
|
||||
rules={{
|
||||
required: true,
|
||||
}}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<IssueProjectSelect
|
||||
value={value}
|
||||
error={error}
|
||||
onChange={(val: string) => {
|
||||
onChange(val);
|
||||
setActiveProject(val);
|
||||
}}
|
||||
/>
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<div className="h-7">
|
||||
<ProjectDropdown
|
||||
value={value}
|
||||
onChange={(val) => {
|
||||
onChange(val);
|
||||
setActiveProject(val);
|
||||
}}
|
||||
buttonVariant="border-with-text"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -287,7 +279,7 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
{status ? "Update" : "Create"} Issue
|
||||
</h3>
|
||||
</div>
|
||||
{watch("parent") &&
|
||||
{watch("parent_id") &&
|
||||
(fieldsToShow.includes("all") || fieldsToShow.includes("parent")) &&
|
||||
selectedParentIssue && (
|
||||
<div className="flex w-min items-center gap-2 whitespace-nowrap rounded bg-custom-background-80 p-2 text-xs">
|
||||
|
|
@ -305,7 +297,7 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
<X
|
||||
className="h-3 w-3 cursor-pointer"
|
||||
onClick={() => {
|
||||
setValue("parent", null);
|
||||
setValue("parent_id", null);
|
||||
setSelectedParentIssue(null);
|
||||
}}
|
||||
/>
|
||||
|
|
@ -408,10 +400,9 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
customClassName="min-h-[7rem] border-custom-border-100"
|
||||
onChange={(description: Object, description_html: string) => {
|
||||
onChange(description_html);
|
||||
setValue("description", description);
|
||||
}}
|
||||
mentionHighlights={editorSuggestion.mentionHighlights}
|
||||
mentionSuggestions={editorSuggestion.mentionSuggestions}
|
||||
mentionHighlights={mentionHighlights}
|
||||
mentionSuggestions={mentionSuggestions}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
@ -421,14 +412,16 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
{(fieldsToShow.includes("all") || fieldsToShow.includes("state")) && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="state"
|
||||
name="state_id"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<IssueStateSelect
|
||||
setIsOpen={setStateModal}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
projectId={projectId}
|
||||
/>
|
||||
<div className="h-7">
|
||||
<StateDropdown
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
projectId={projectId}
|
||||
buttonVariant="border-with-text"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -437,48 +430,63 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
control={control}
|
||||
name="priority"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<IssuePrioritySelect value={value} onChange={onChange} />
|
||||
<div className="h-7">
|
||||
<PriorityDropdown value={value} onChange={onChange} buttonVariant="border-with-text" />
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("assignee")) && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="assignees"
|
||||
name="assignee_ids"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<IssueAssigneeSelect projectId={projectId} value={value} onChange={onChange} />
|
||||
<div className="h-7">
|
||||
<ProjectMemberDropdown
|
||||
projectId={projectId}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
buttonVariant={value?.length > 0 ? "transparent-without-text" : "border-with-text"}
|
||||
buttonClassName={value?.length > 0 ? "hover:bg-transparent px-0" : ""}
|
||||
placeholder="Assignees"
|
||||
multiple
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("label")) && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="labels"
|
||||
name="label_ids"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<IssueLabelSelect
|
||||
setIsOpen={setLabelModal}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
projectId={projectId}
|
||||
/>
|
||||
<div className="h-7">
|
||||
<IssueLabelSelect
|
||||
setIsOpen={setLabelModal}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
projectId={projectId}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("startDate")) && (
|
||||
<div>
|
||||
<Controller
|
||||
control={control}
|
||||
name="start_date"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<IssueDateSelect
|
||||
label="Start date"
|
||||
maxDate={maxDate ?? undefined}
|
||||
onChange={onChange}
|
||||
<Controller
|
||||
control={control}
|
||||
name="start_date"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<div className="h-7">
|
||||
<DateDropdown
|
||||
value={value}
|
||||
onChange={(date) => onChange(date ? renderFormattedPayloadDate(date) : null)}
|
||||
buttonVariant="border-with-text"
|
||||
placeholder="Start date"
|
||||
maxDate={maxDate ?? undefined}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("dueDate")) && (
|
||||
<div>
|
||||
|
|
@ -486,70 +494,79 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
control={control}
|
||||
name="target_date"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<IssueDateSelect
|
||||
label="Due date"
|
||||
minDate={minDate ?? undefined}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
/>
|
||||
<div className="h-7">
|
||||
<DateDropdown
|
||||
value={value}
|
||||
onChange={(date) => onChange(date ? renderFormattedPayloadDate(date) : null)}
|
||||
buttonVariant="border-with-text"
|
||||
placeholder="Due date"
|
||||
minDate={minDate ?? undefined}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("module")) && (
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("cycle")) && projectDetails?.cycle_view && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="module"
|
||||
name="cycle_id"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<IssueModuleSelect
|
||||
workspaceSlug={workspaceSlug as string}
|
||||
projectId={projectId}
|
||||
value={value}
|
||||
onChange={(val: string) => {
|
||||
onChange(val);
|
||||
}}
|
||||
/>
|
||||
<div className="h-7">
|
||||
<CycleDropdown
|
||||
projectId={projectId}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
buttonVariant="border-with-text"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("cycle")) && (
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("module")) && projectDetails?.module_view && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="cycle"
|
||||
name="module_id"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<IssueCycleSelect
|
||||
workspaceSlug={workspaceSlug as string}
|
||||
projectId={projectId}
|
||||
value={value}
|
||||
onChange={(val: string) => {
|
||||
onChange(val);
|
||||
}}
|
||||
/>
|
||||
<div className="h-7">
|
||||
<ModuleDropdown
|
||||
projectId={projectId}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
buttonVariant="border-with-text"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("estimate")) && (
|
||||
<>
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("estimate")) &&
|
||||
areEstimatesActiveForProject(projectId) && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="estimate_point"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<IssueEstimateSelect value={value} onChange={onChange} />
|
||||
<div className="h-7">
|
||||
<EstimateDropdown
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
projectId={projectId}
|
||||
buttonVariant="border-with-text"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
)}
|
||||
{(fieldsToShow.includes("all") || fieldsToShow.includes("parent")) && (
|
||||
<>
|
||||
{watch("parent") ? (
|
||||
{watch("parent_id") ? (
|
||||
<CustomMenu
|
||||
customButton={
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-full cursor-pointer items-center justify-between gap-1 rounded border-[0.5px] border-custom-border-300 px-2 py-1 text-xs text-custom-text-200 hover:bg-custom-background-80"
|
||||
className="h-7 flex items-center justify-between gap-1 rounded border-[0.5px] border-custom-border-300 px-2 py-1 text-xs text-custom-text-200 hover:bg-custom-background-80"
|
||||
>
|
||||
<div className="flex items-center gap-1 text-custom-text-200">
|
||||
<LayoutPanelTop className="h-3 w-3 flex-shrink-0" />
|
||||
<LayoutPanelTop className="h-2.5 w-2.5 flex-shrink-0" />
|
||||
<span className="whitespace-nowrap">
|
||||
{selectedParentIssue &&
|
||||
`${selectedParentIssue.project__identifier}-
|
||||
|
|
@ -563,26 +580,24 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
|||
<CustomMenu.MenuItem className="!p-1" onClick={() => setParentIssueListModalOpen(true)}>
|
||||
Change parent issue
|
||||
</CustomMenu.MenuItem>
|
||||
<CustomMenu.MenuItem className="!p-1" onClick={() => setValue("parent", null)}>
|
||||
<CustomMenu.MenuItem className="!p-1" onClick={() => setValue("parent_id", null)}>
|
||||
Remove parent issue
|
||||
</CustomMenu.MenuItem>
|
||||
</CustomMenu>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="flex w-min cursor-pointer items-center justify-between gap-1 rounded border-[0.5px] border-custom-border-300 px-2 py-1 text-xs text-custom-text-200 hover:bg-custom-background-80"
|
||||
className="h-7 flex items-center justify-between gap-1 rounded border-[0.5px] border-custom-border-300 px-2 py-1 text-xs hover:bg-custom-background-80"
|
||||
onClick={() => setParentIssueListModalOpen(true)}
|
||||
>
|
||||
<div className="flex items-center gap-1 text-custom-text-300">
|
||||
<LayoutPanelTop className="h-3 w-3 flex-shrink-0" />
|
||||
<span className="whitespace-nowrap">Add Parent</span>
|
||||
</div>
|
||||
<LayoutPanelTop className="h-3 w-3 flex-shrink-0" />
|
||||
<span className="whitespace-nowrap">Add parent</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
<Controller
|
||||
control={control}
|
||||
name="parent"
|
||||
name="parent_id"
|
||||
render={({ field: { onChange } }) => (
|
||||
<ParentIssuesListModal
|
||||
isOpen={parentIssueListModalOpen}
|
||||
|
|
|
|||
|
|
@ -24,3 +24,6 @@ export * from "./delete-draft-issue-modal";
|
|||
|
||||
// archived issue
|
||||
export * from "./delete-archived-issue-modal";
|
||||
|
||||
// issue links
|
||||
export * from "./issue-links";
|
||||
|
|
|
|||
|
|
@ -7,52 +7,42 @@ import { CalendarChart, IssuePeekOverview } from "components/issues";
|
|||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
import {
|
||||
ICycleIssuesFilterStore,
|
||||
ICycleIssuesStore,
|
||||
IModuleIssuesFilterStore,
|
||||
IModuleIssuesStore,
|
||||
IProjectIssuesFilterStore,
|
||||
IProjectIssuesStore,
|
||||
IViewIssuesFilterStore,
|
||||
IViewIssuesStore,
|
||||
} from "store/issues";
|
||||
import { TGroupedIssues, TIssue } from "@plane/types";
|
||||
import { IQuickActionProps } from "../list/list-view-types";
|
||||
import { EIssueActions } from "../types";
|
||||
import { IGroupedIssues } from "store/issues/types";
|
||||
import { handleDragDrop } from "./utils";
|
||||
import { useIssues } from "hooks/store";
|
||||
import { ICycleIssues, ICycleIssuesFilter } from "store/issue/cycle";
|
||||
import { IModuleIssues, IModuleIssuesFilter } from "store/issue/module";
|
||||
import { IProjectIssues, IProjectIssuesFilter } from "store/issue/project";
|
||||
import { IProjectViewIssues, IProjectViewIssuesFilter } from "store/issue/project-views";
|
||||
|
||||
interface IBaseCalendarRoot {
|
||||
issueStore: IProjectIssuesStore | IModuleIssuesStore | ICycleIssuesStore | IViewIssuesStore;
|
||||
issuesFilterStore:
|
||||
| IProjectIssuesFilterStore
|
||||
| IModuleIssuesFilterStore
|
||||
| ICycleIssuesFilterStore
|
||||
| IViewIssuesFilterStore;
|
||||
issueStore: IProjectIssues | IModuleIssues | ICycleIssues | IProjectViewIssues;
|
||||
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
|
||||
QuickActions: FC<IQuickActionProps>;
|
||||
issueActions: {
|
||||
[EIssueActions.DELETE]: (issue: IIssue) => Promise<void>;
|
||||
[EIssueActions.UPDATE]?: (issue: IIssue) => Promise<void>;
|
||||
[EIssueActions.REMOVE]?: (issue: IIssue) => Promise<void>;
|
||||
[EIssueActions.DELETE]: (issue: TIssue) => Promise<void>;
|
||||
[EIssueActions.UPDATE]?: (issue: TIssue) => Promise<void>;
|
||||
[EIssueActions.REMOVE]?: (issue: TIssue) => Promise<void>;
|
||||
};
|
||||
viewId?: string;
|
||||
handleDragDrop: (source: any, destination: any, issues: any, issueWithIds: any) => Promise<void>;
|
||||
}
|
||||
|
||||
export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
||||
const { issueStore, issuesFilterStore, QuickActions, issueActions, viewId, handleDragDrop } = props;
|
||||
const { issueStore, issuesFilterStore, QuickActions, issueActions, viewId } = props;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, peekIssueId, peekProjectId } = router.query;
|
||||
const { workspaceSlug, projectId, peekIssueId, peekProjectId } = router.query;
|
||||
|
||||
// hooks
|
||||
const { setToastAlert } = useToast();
|
||||
const { issueMap } = useIssues();
|
||||
|
||||
const displayFilters = issuesFilterStore.issueFilters?.displayFilters;
|
||||
|
||||
const issues = issueStore.getIssues;
|
||||
const groupedIssueIds = (issueStore.getIssuesIds ?? {}) as IGroupedIssues;
|
||||
const groupedIssueIds = (issueStore.groupedIssueIds ?? {}) as TGroupedIssues;
|
||||
|
||||
const onDragEnd = async (result: DropResult) => {
|
||||
if (!result) return;
|
||||
|
|
@ -64,7 +54,15 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
|||
if (result.destination.droppableId === result.source.droppableId) return;
|
||||
|
||||
if (handleDragDrop) {
|
||||
await handleDragDrop(result.source, result.destination, issues, groupedIssueIds).catch((err) => {
|
||||
await handleDragDrop(
|
||||
result.source,
|
||||
result.destination,
|
||||
workspaceSlug?.toString(),
|
||||
projectId?.toString(),
|
||||
issueStore,
|
||||
issueMap,
|
||||
groupedIssueIds
|
||||
).catch((err) => {
|
||||
setToastAlert({
|
||||
title: "Error",
|
||||
type: "error",
|
||||
|
|
@ -75,7 +73,7 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
|||
};
|
||||
|
||||
const handleIssues = useCallback(
|
||||
async (date: string, issue: IIssue, action: EIssueActions) => {
|
||||
async (date: string, issue: TIssue, action: EIssueActions) => {
|
||||
if (issueActions[action]) {
|
||||
await issueActions[action]!(issue);
|
||||
}
|
||||
|
|
@ -89,7 +87,7 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
|||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<CalendarChart
|
||||
issuesFilterStore={issuesFilterStore}
|
||||
issues={issues}
|
||||
issues={issueMap}
|
||||
groupedIssueIds={groupedIssueIds}
|
||||
layout={displayFilters?.calendar?.layout}
|
||||
showWeekends={displayFilters?.calendar?.show_weekends ?? false}
|
||||
|
|
@ -120,8 +118,8 @@ export const BaseCalendarRoot = observer((props: IBaseCalendarRoot) => {
|
|||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={peekProjectId.toString()}
|
||||
issueId={peekIssueId.toString()}
|
||||
handleIssue={async (issueToUpdate, action: EIssueActions) =>
|
||||
await handleIssues(issueToUpdate.target_date ?? "", issueToUpdate as IIssue, action)
|
||||
handleIssue={async (issueToUpdate) =>
|
||||
await handleIssues(issueToUpdate.target_date ?? "", issueToUpdate as TIssue, EIssueActions.UPDATE)
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,60 +1,56 @@
|
|||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useIssues, useUser } from "hooks/store";
|
||||
// components
|
||||
import { CalendarHeader, CalendarWeekDays, CalendarWeekHeader } from "components/issues";
|
||||
// ui
|
||||
import { Spinner } from "@plane/ui";
|
||||
// types
|
||||
import { ICalendarWeek } from "./types";
|
||||
import { IIssue } from "types";
|
||||
import { IGroupedIssues, IIssueResponse } from "store/issues/types";
|
||||
import {
|
||||
ICycleIssuesFilterStore,
|
||||
IModuleIssuesFilterStore,
|
||||
IProjectIssuesFilterStore,
|
||||
IViewIssuesFilterStore,
|
||||
} from "store/issues";
|
||||
import { TGroupedIssues, TIssue, TIssueMap } from "@plane/types";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { useCalendarView } from "hooks/store/use-calendar-view";
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
import { ICycleIssuesFilter } from "store/issue/cycle";
|
||||
import { IModuleIssuesFilter } from "store/issue/module";
|
||||
import { IProjectIssuesFilter } from "store/issue/project";
|
||||
import { IProjectViewIssuesFilter } from "store/issue/project-views";
|
||||
|
||||
type Props = {
|
||||
issuesFilterStore:
|
||||
| IProjectIssuesFilterStore
|
||||
| IModuleIssuesFilterStore
|
||||
| ICycleIssuesFilterStore
|
||||
| IViewIssuesFilterStore;
|
||||
issues: IIssueResponse | undefined;
|
||||
groupedIssueIds: IGroupedIssues;
|
||||
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
|
||||
issues: TIssueMap | undefined;
|
||||
groupedIssueIds: TGroupedIssues;
|
||||
layout: "month" | "week" | undefined;
|
||||
showWeekends: boolean;
|
||||
quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
quickAddCallback?: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: IIssue,
|
||||
data: TIssue,
|
||||
viewId?: string
|
||||
) => Promise<IIssue | undefined>;
|
||||
) => Promise<TIssue | undefined>;
|
||||
viewId?: string;
|
||||
};
|
||||
|
||||
export const CalendarChart: React.FC<Props> = observer((props) => {
|
||||
const { issuesFilterStore, issues, groupedIssueIds, layout, showWeekends, quickActions, quickAddCallback, viewId } =
|
||||
props;
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
calendar: calendarStore,
|
||||
projectIssues: issueStore,
|
||||
user: { currentProjectRole },
|
||||
} = useMobxStore();
|
||||
issues: { viewFlags },
|
||||
} = useIssues(EIssuesStoreType.PROJECT);
|
||||
const issueCalendarView = useCalendarView();
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
|
||||
const { enableIssueCreation } = issueStore?.viewFlags || {};
|
||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||
const { enableIssueCreation } = viewFlags || {};
|
||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
||||
|
||||
const calendarPayload = calendarStore.calendarPayload;
|
||||
const calendarPayload = issueCalendarView.calendarPayload;
|
||||
|
||||
const allWeeksOfActiveMonth = calendarStore.allWeeksOfActiveMonth;
|
||||
const allWeeksOfActiveMonth = issueCalendarView.allWeeksOfActiveMonth;
|
||||
|
||||
if (!calendarPayload)
|
||||
return (
|
||||
|
|
@ -66,7 +62,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
|||
return (
|
||||
<>
|
||||
<div className="flex h-full w-full flex-col overflow-hidden">
|
||||
<CalendarHeader issuesFilterStore={issuesFilterStore} />
|
||||
<CalendarHeader issuesFilterStore={issuesFilterStore} viewId={viewId} />
|
||||
<CalendarWeekHeader isLoading={!issues} showWeekends={showWeekends} />
|
||||
<div className="h-full w-full overflow-y-auto">
|
||||
{layout === "month" && (
|
||||
|
|
@ -91,7 +87,7 @@ export const CalendarChart: React.FC<Props> = observer((props) => {
|
|||
{layout === "week" && (
|
||||
<CalendarWeekDays
|
||||
issuesFilterStore={issuesFilterStore}
|
||||
week={calendarStore.allDaysOfActiveWeek}
|
||||
week={issueCalendarView.allDaysOfActiveWeek}
|
||||
issues={issues}
|
||||
groupedIssueIds={groupedIssueIds}
|
||||
enableQuickIssueCreate
|
||||
|
|
|
|||
|
|
@ -7,33 +7,26 @@ import { CalendarIssueBlocks, ICalendarDate, CalendarQuickAddIssueForm } from "c
|
|||
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
||||
// constants
|
||||
import { MONTHS_LIST } from "constants/calendar";
|
||||
import { IIssue } from "types";
|
||||
import { IGroupedIssues, IIssueResponse } from "store/issues/types";
|
||||
import {
|
||||
ICycleIssuesFilterStore,
|
||||
IModuleIssuesFilterStore,
|
||||
IProjectIssuesFilterStore,
|
||||
IViewIssuesFilterStore,
|
||||
} from "store/issues";
|
||||
import { TGroupedIssues, TIssue, TIssueMap } from "@plane/types";
|
||||
import { ICycleIssuesFilter } from "store/issue/cycle";
|
||||
import { IModuleIssuesFilter } from "store/issue/module";
|
||||
import { IProjectIssuesFilter } from "store/issue/project";
|
||||
import { IProjectViewIssuesFilter } from "store/issue/project-views";
|
||||
|
||||
type Props = {
|
||||
issuesFilterStore:
|
||||
| IProjectIssuesFilterStore
|
||||
| IModuleIssuesFilterStore
|
||||
| ICycleIssuesFilterStore
|
||||
| IViewIssuesFilterStore;
|
||||
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
|
||||
date: ICalendarDate;
|
||||
issues: IIssueResponse | undefined;
|
||||
groupedIssueIds: IGroupedIssues;
|
||||
quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
issues: TIssueMap | undefined;
|
||||
groupedIssueIds: TGroupedIssues;
|
||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
enableQuickIssueCreate?: boolean;
|
||||
disableIssueCreation?: boolean;
|
||||
quickAddCallback?: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: IIssue,
|
||||
data: TIssue,
|
||||
viewId?: string
|
||||
) => Promise<IIssue | undefined>;
|
||||
) => Promise<TIssue | undefined>;
|
||||
viewId?: string;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,17 +2,26 @@ import React, { useState } from "react";
|
|||
import { Popover, Transition } from "@headlessui/react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { usePopper } from "react-popper";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
//hooks
|
||||
import { useCalendarView } from "hooks/store";
|
||||
// icons
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
// constants
|
||||
import { MONTHS_LIST } from "constants/calendar";
|
||||
import { ICycleIssuesFilter } from "store/issue/cycle";
|
||||
import { IModuleIssuesFilter } from "store/issue/module";
|
||||
import { IProjectIssuesFilter } from "store/issue/project";
|
||||
import { IProjectViewIssuesFilter } from "store/issue/project-views";
|
||||
|
||||
export const CalendarMonthsDropdown: React.FC = observer(() => {
|
||||
const { calendar: calendarStore, issueFilter: issueFilterStore } = useMobxStore();
|
||||
interface Props {
|
||||
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
|
||||
}
|
||||
export const CalendarMonthsDropdown: React.FC<Props> = observer((props: Props) => {
|
||||
const { issuesFilterStore } = props;
|
||||
|
||||
const calendarLayout = issueFilterStore.userDisplayFilters.calendar?.layout ?? "month";
|
||||
const issueCalendarView = useCalendarView();
|
||||
|
||||
const calendarLayout = issuesFilterStore.issueFilters?.displayFilters?.calendar?.layout ?? "month";
|
||||
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
|
|
@ -29,10 +38,10 @@ export const CalendarMonthsDropdown: React.FC = observer(() => {
|
|||
],
|
||||
});
|
||||
|
||||
const { activeMonthDate } = calendarStore.calendarFilters;
|
||||
const { activeMonthDate } = issueCalendarView.calendarFilters;
|
||||
|
||||
const getWeekLayoutHeader = (): string => {
|
||||
const allDaysOfActiveWeek = calendarStore.allDaysOfActiveWeek;
|
||||
const allDaysOfActiveWeek = issueCalendarView.allDaysOfActiveWeek;
|
||||
|
||||
if (!allDaysOfActiveWeek) return "Week view";
|
||||
|
||||
|
|
@ -55,7 +64,7 @@ export const CalendarMonthsDropdown: React.FC = observer(() => {
|
|||
};
|
||||
|
||||
const handleDateChange = (date: Date) => {
|
||||
calendarStore.updateCalendarFilters({
|
||||
issueCalendarView.updateCalendarFilters({
|
||||
activeMonthDate: date,
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,39 +3,34 @@ import { useRouter } from "next/router";
|
|||
import { Popover, Transition } from "@headlessui/react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { usePopper } from "react-popper";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useCalendarView } from "hooks/store";
|
||||
// ui
|
||||
import { ToggleSwitch } from "@plane/ui";
|
||||
// icons
|
||||
import { Check, ChevronUp } from "lucide-react";
|
||||
// types
|
||||
import { TCalendarLayouts } from "types";
|
||||
import { TCalendarLayouts } from "@plane/types";
|
||||
// constants
|
||||
import { CALENDAR_LAYOUTS } from "constants/calendar";
|
||||
import { EFilterType } from "store/issues/types";
|
||||
import {
|
||||
ICycleIssuesFilterStore,
|
||||
IModuleIssuesFilterStore,
|
||||
IProjectIssuesFilterStore,
|
||||
IViewIssuesFilterStore,
|
||||
} from "store/issues";
|
||||
import { EIssueFilterType } from "constants/issue";
|
||||
import { ICycleIssuesFilter } from "store/issue/cycle";
|
||||
import { IModuleIssuesFilter } from "store/issue/module";
|
||||
import { IProjectIssuesFilter } from "store/issue/project";
|
||||
import { IProjectViewIssuesFilter } from "store/issue/project-views";
|
||||
|
||||
interface ICalendarHeader {
|
||||
issuesFilterStore:
|
||||
| IProjectIssuesFilterStore
|
||||
| IModuleIssuesFilterStore
|
||||
| ICycleIssuesFilterStore
|
||||
| IViewIssuesFilterStore;
|
||||
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
|
||||
viewId?: string;
|
||||
}
|
||||
|
||||
export const CalendarOptionsDropdown: React.FC<ICalendarHeader> = observer((props) => {
|
||||
const { issuesFilterStore } = props;
|
||||
const { issuesFilterStore, viewId } = props;
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
const { calendar: calendarStore } = useMobxStore();
|
||||
const issueCalendarView = useCalendarView();
|
||||
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
|
|
@ -58,15 +53,17 @@ export const CalendarOptionsDropdown: React.FC<ICalendarHeader> = observer((prop
|
|||
const handleLayoutChange = (layout: TCalendarLayouts) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
issuesFilterStore.updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.DISPLAY_FILTERS, {
|
||||
issuesFilterStore.updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.DISPLAY_FILTERS, {
|
||||
calendar: {
|
||||
...issuesFilterStore.issueFilters?.displayFilters?.calendar,
|
||||
layout,
|
||||
},
|
||||
});
|
||||
|
||||
calendarStore.updateCalendarPayload(
|
||||
layout === "month" ? calendarStore.calendarFilters.activeMonthDate : calendarStore.calendarFilters.activeWeekDate
|
||||
issueCalendarView.updateCalendarPayload(
|
||||
layout === "month"
|
||||
? issueCalendarView.calendarFilters.activeMonthDate
|
||||
: issueCalendarView.calendarFilters.activeWeekDate
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -75,12 +72,18 @@ export const CalendarOptionsDropdown: React.FC<ICalendarHeader> = observer((prop
|
|||
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
issuesFilterStore.updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.DISPLAY_FILTERS, {
|
||||
calendar: {
|
||||
...issuesFilterStore.issueFilters?.displayFilters?.calendar,
|
||||
show_weekends: !showWeekends,
|
||||
issuesFilterStore.updateFilters(
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
EIssueFilterType.DISPLAY_FILTERS,
|
||||
{
|
||||
calendar: {
|
||||
...issuesFilterStore.issueFilters?.displayFilters?.calendar,
|
||||
show_weekends: !showWeekends,
|
||||
},
|
||||
},
|
||||
});
|
||||
viewId
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,34 +1,28 @@
|
|||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { CalendarMonthsDropdown, CalendarOptionsDropdown } from "components/issues";
|
||||
// icons
|
||||
import { ChevronLeft, ChevronRight } from "lucide-react";
|
||||
import {
|
||||
ICycleIssuesFilterStore,
|
||||
IModuleIssuesFilterStore,
|
||||
IProjectIssuesFilterStore,
|
||||
IViewIssuesFilterStore,
|
||||
} from "store/issues";
|
||||
import { useCalendarView } from "hooks/store/use-calendar-view";
|
||||
import { ICycleIssuesFilter } from "store/issue/cycle";
|
||||
import { IModuleIssuesFilter } from "store/issue/module";
|
||||
import { IProjectIssuesFilter } from "store/issue/project";
|
||||
import { IProjectViewIssuesFilter } from "store/issue/project-views";
|
||||
|
||||
interface ICalendarHeader {
|
||||
issuesFilterStore:
|
||||
| IProjectIssuesFilterStore
|
||||
| IModuleIssuesFilterStore
|
||||
| ICycleIssuesFilterStore
|
||||
| IViewIssuesFilterStore;
|
||||
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
|
||||
viewId?: string;
|
||||
}
|
||||
|
||||
export const CalendarHeader: React.FC<ICalendarHeader> = observer((props) => {
|
||||
const { issuesFilterStore } = props;
|
||||
const { issuesFilterStore, viewId } = props;
|
||||
|
||||
const { calendar: calendarStore } = useMobxStore();
|
||||
const issueCalendarView = useCalendarView();
|
||||
|
||||
const calendarLayout = issuesFilterStore.issueFilters?.displayFilters?.calendar?.layout ?? "month";
|
||||
|
||||
const { activeMonthDate, activeWeekDate } = calendarStore.calendarFilters;
|
||||
const { activeMonthDate, activeWeekDate } = issueCalendarView.calendarFilters;
|
||||
|
||||
const handlePrevious = () => {
|
||||
if (calendarLayout === "month") {
|
||||
|
|
@ -38,7 +32,7 @@ export const CalendarHeader: React.FC<ICalendarHeader> = observer((props) => {
|
|||
|
||||
const previousMonthFirstDate = new Date(previousMonthYear, previousMonthMonth, 1);
|
||||
|
||||
calendarStore.updateCalendarFilters({
|
||||
issueCalendarView.updateCalendarFilters({
|
||||
activeMonthDate: previousMonthFirstDate,
|
||||
});
|
||||
} else {
|
||||
|
|
@ -48,7 +42,7 @@ export const CalendarHeader: React.FC<ICalendarHeader> = observer((props) => {
|
|||
activeWeekDate.getDate() - 7
|
||||
);
|
||||
|
||||
calendarStore.updateCalendarFilters({
|
||||
issueCalendarView.updateCalendarFilters({
|
||||
activeWeekDate: previousWeekDate,
|
||||
});
|
||||
}
|
||||
|
|
@ -62,7 +56,7 @@ export const CalendarHeader: React.FC<ICalendarHeader> = observer((props) => {
|
|||
|
||||
const nextMonthFirstDate = new Date(nextMonthYear, nextMonthMonth, 1);
|
||||
|
||||
calendarStore.updateCalendarFilters({
|
||||
issueCalendarView.updateCalendarFilters({
|
||||
activeMonthDate: nextMonthFirstDate,
|
||||
});
|
||||
} else {
|
||||
|
|
@ -72,7 +66,7 @@ export const CalendarHeader: React.FC<ICalendarHeader> = observer((props) => {
|
|||
activeWeekDate.getDate() + 7
|
||||
);
|
||||
|
||||
calendarStore.updateCalendarFilters({
|
||||
issueCalendarView.updateCalendarFilters({
|
||||
activeWeekDate: nextWeekDate,
|
||||
});
|
||||
}
|
||||
|
|
@ -82,7 +76,7 @@ export const CalendarHeader: React.FC<ICalendarHeader> = observer((props) => {
|
|||
const today = new Date();
|
||||
const firstDayOfCurrentMonth = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||
|
||||
calendarStore.updateCalendarFilters({
|
||||
issueCalendarView.updateCalendarFilters({
|
||||
activeMonthDate: firstDayOfCurrentMonth,
|
||||
activeWeekDate: today,
|
||||
});
|
||||
|
|
@ -97,7 +91,7 @@ export const CalendarHeader: React.FC<ICalendarHeader> = observer((props) => {
|
|||
<button type="button" className="grid place-items-center" onClick={handleNext}>
|
||||
<ChevronRight size={16} strokeWidth={2} />
|
||||
</button>
|
||||
<CalendarMonthsDropdown />
|
||||
<CalendarMonthsDropdown issuesFilterStore={issuesFilterStore} />
|
||||
</div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<button
|
||||
|
|
@ -107,7 +101,7 @@ export const CalendarHeader: React.FC<ICalendarHeader> = observer((props) => {
|
|||
>
|
||||
Today
|
||||
</button>
|
||||
<CalendarOptionsDropdown issuesFilterStore={issuesFilterStore} />
|
||||
<CalendarOptionsDropdown issuesFilterStore={issuesFilterStore} viewId={viewId} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,16 +8,13 @@ import { Tooltip } from "@plane/ui";
|
|||
// hooks
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
import { IIssueResponse } from "store/issues/types";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
import { TIssue, TIssueMap } from "@plane/types";
|
||||
import { useProject, useProjectState } from "hooks/store";
|
||||
|
||||
type Props = {
|
||||
issues: IIssueResponse | undefined;
|
||||
issues: TIssueMap | undefined;
|
||||
issueIdList: string[] | null;
|
||||
quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
showAllIssues?: boolean;
|
||||
};
|
||||
|
||||
|
|
@ -25,28 +22,21 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
|||
const { issues, issueIdList, quickActions, showAllIssues = false } = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
|
||||
// hooks
|
||||
const { getProjectById } = useProject();
|
||||
const { getProjectStates } = useProjectState();
|
||||
// states
|
||||
const [isMenuActive, setIsMenuActive] = useState(false);
|
||||
|
||||
// mobx store
|
||||
const {
|
||||
user: { currentProjectRole },
|
||||
} = useMobxStore();
|
||||
|
||||
const menuActionRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
const handleIssuePeekOverview = (issue: IIssue, event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
const handleIssuePeekOverview = (issue: TIssue) => {
|
||||
const { query } = router;
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
const issueUrl = `/${issue.workspace_detail.slug}/projects/${issue.project_detail.id}/issues/${issue?.id}`;
|
||||
window.open(issueUrl, "_blank"); // Open link in a new tab
|
||||
} else {
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: { ...query, peekIssueId: issue?.id, peekProjectId: issue?.project },
|
||||
});
|
||||
}
|
||||
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: { ...query, peekIssueId: issue?.id, peekProjectId: issue?.project_id },
|
||||
});
|
||||
};
|
||||
|
||||
useOutsideClickDetector(menuActionRef, () => setIsMenuActive(false));
|
||||
|
|
@ -63,8 +53,6 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
|||
</div>
|
||||
);
|
||||
|
||||
const isEditable = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||
|
||||
return (
|
||||
<>
|
||||
{issueIdList?.slice(0, showAllIssues ? issueIdList.length : 4).map((issueId, index) => {
|
||||
|
|
@ -72,14 +60,14 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
|||
|
||||
const issue = issues?.[issueId];
|
||||
return (
|
||||
<Draggable key={issue.id} draggableId={issue.id} index={index} isDragDisabled={!isEditable}>
|
||||
<Draggable key={issue.id} draggableId={issue.id} index={index}>
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
className="relative cursor-pointer p-1 px-2"
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
ref={provided.innerRef}
|
||||
onClick={(e) => handleIssuePeekOverview(issue, e)}
|
||||
onClick={() => handleIssuePeekOverview(issue)}
|
||||
>
|
||||
{issue?.tempId !== undefined && (
|
||||
<div className="absolute left-0 top-0 z-[99999] h-full w-full animate-pulse bg-custom-background-100/20" />
|
||||
|
|
@ -96,11 +84,13 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
|||
<span
|
||||
className="h-full w-0.5 flex-shrink-0 rounded"
|
||||
style={{
|
||||
backgroundColor: issue.state_detail.color,
|
||||
backgroundColor: getProjectStates(issue?.project_id).find(
|
||||
(state) => state?.id == issue?.state_id
|
||||
)?.color,
|
||||
}}
|
||||
/>
|
||||
<div className="flex-shrink-0 text-xs text-custom-text-300">
|
||||
{issue.project_detail.identifier}-{issue.sequence_id}
|
||||
{getProjectById(issue?.project_id)?.identifier}-{issue.sequence_id}
|
||||
</div>
|
||||
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
|
||||
<div className="truncate text-xs">{issue.name}</div>
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@ import { useEffect, useRef, useState } from "react";
|
|||
import { useRouter } from "next/router";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useProject, useWorkspace } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import useKeypress from "hooks/use-keypress";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
|
|
@ -13,24 +12,24 @@ import { createIssuePayload } from "helpers/issue.helper";
|
|||
// icons
|
||||
import { PlusIcon } from "lucide-react";
|
||||
// types
|
||||
import { IIssue, IProject } from "types";
|
||||
import { TIssue } from "@plane/types";
|
||||
|
||||
type Props = {
|
||||
formKey: keyof IIssue;
|
||||
formKey: keyof TIssue;
|
||||
groupId?: string;
|
||||
subGroupId?: string | null;
|
||||
prePopulatedData?: Partial<IIssue>;
|
||||
prePopulatedData?: Partial<TIssue>;
|
||||
quickAddCallback?: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: IIssue,
|
||||
data: TIssue,
|
||||
viewId?: string
|
||||
) => Promise<IIssue | undefined>;
|
||||
) => Promise<TIssue | undefined>;
|
||||
viewId?: string;
|
||||
onOpen?: () => void;
|
||||
};
|
||||
|
||||
const defaultValues: Partial<IIssue> = {
|
||||
const defaultValues: Partial<TIssue> = {
|
||||
name: "",
|
||||
};
|
||||
|
||||
|
|
@ -62,22 +61,20 @@ export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
|
|||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||
|
||||
const { workspace: workspaceStore, project: projectStore } = useMobxStore();
|
||||
|
||||
// ref
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// store hooks
|
||||
const { getProjectById } = useProject();
|
||||
const { getWorkspaceBySlug } = useWorkspace();
|
||||
// refs
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
// states
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
// derived values
|
||||
const workspaceDetail = (workspaceSlug && workspaceStore.getWorkspaceBySlug(workspaceSlug)) || null;
|
||||
const projectDetail: IProject | null =
|
||||
(workspaceSlug && projectId && projectStore.getProjectById(workspaceSlug, projectId)) || null;
|
||||
const workspaceDetail = (workspaceSlug && getWorkspaceBySlug(workspaceSlug.toString())) || null;
|
||||
const projectDetail = projectId ? getProjectById(projectId.toString()) : null;
|
||||
|
||||
const {
|
||||
reset,
|
||||
|
|
@ -85,7 +82,7 @@ export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
|
|||
register,
|
||||
setFocus,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<IIssue>({ defaultValues });
|
||||
} = useForm<TIssue>({ defaultValues });
|
||||
|
||||
const handleClose = () => {
|
||||
setIsOpen(false);
|
||||
|
|
@ -102,7 +99,7 @@ export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
|
|||
if (!errors) return;
|
||||
|
||||
Object.keys(errors).forEach((key) => {
|
||||
const error = errors[key as keyof IIssue];
|
||||
const error = errors[key as keyof TIssue];
|
||||
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
|
|
@ -112,8 +109,8 @@ export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
|
|||
});
|
||||
}, [errors, setToastAlert]);
|
||||
|
||||
const onSubmitHandler = async (formData: IIssue) => {
|
||||
if (isSubmitting || !groupId || !workspaceDetail || !projectDetail) return;
|
||||
const onSubmitHandler = async (formData: TIssue) => {
|
||||
if (isSubmitting || !groupId || !workspaceDetail || !projectDetail || !workspaceSlug || !projectId) return;
|
||||
|
||||
reset({ ...defaultValues });
|
||||
|
||||
|
|
@ -125,8 +122,8 @@ export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
|
|||
try {
|
||||
quickAddCallback &&
|
||||
(await quickAddCallback(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
{
|
||||
...payload,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,74 +1,50 @@
|
|||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
//hooks
|
||||
import { useIssues } from "hooks/store";
|
||||
// components
|
||||
import { CycleIssueQuickActions } from "components/issues";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
import { TIssue } from "@plane/types";
|
||||
import { EIssueActions } from "../../types";
|
||||
import { BaseCalendarRoot } from "../base-calendar-root";
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export const CycleCalendarLayout: React.FC = observer(() => {
|
||||
const {
|
||||
cycleIssues: cycleIssueStore,
|
||||
cycleIssuesFilter: cycleIssueFilterStore,
|
||||
calendarHelpers: { handleDragDrop: handleCalenderDragDrop },
|
||||
cycle: { fetchCycleWithId },
|
||||
} = useMobxStore();
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId } = router.query;
|
||||
|
||||
const issueActions = {
|
||||
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug || !cycleId) return;
|
||||
const issueActions = useMemo(
|
||||
() => ({
|
||||
[EIssueActions.UPDATE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !cycleId) return;
|
||||
|
||||
await cycleIssueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue, cycleId.toString());
|
||||
fetchCycleWithId(workspaceSlug.toString(), issue.project, cycleId.toString());
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug || !cycleId) return;
|
||||
await cycleIssueStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id, cycleId.toString());
|
||||
fetchCycleWithId(workspaceSlug.toString(), issue.project, cycleId.toString());
|
||||
},
|
||||
[EIssueActions.REMOVE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug || !cycleId || !projectId || !issue.bridge_id) return;
|
||||
await cycleIssueStore.removeIssueFromCycle(
|
||||
workspaceSlug.toString(),
|
||||
issue.project,
|
||||
cycleId.toString(),
|
||||
issue.id,
|
||||
issue.bridge_id
|
||||
);
|
||||
fetchCycleWithId(workspaceSlug.toString(), issue.project, cycleId.toString());
|
||||
},
|
||||
};
|
||||
|
||||
const handleDragDrop = async (source: any, destination: any, issues: IIssue[], issueWithIds: any) => {
|
||||
if (workspaceSlug && projectId && cycleId)
|
||||
await handleCalenderDragDrop(
|
||||
source,
|
||||
destination,
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
cycleIssueStore,
|
||||
issues,
|
||||
issueWithIds,
|
||||
cycleId.toString()
|
||||
);
|
||||
};
|
||||
await issues.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, issue, cycleId.toString());
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !cycleId) return;
|
||||
await issues.removeIssue(workspaceSlug.toString(), issue.project_id, issue.id, cycleId.toString());
|
||||
},
|
||||
[EIssueActions.REMOVE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !cycleId || !projectId) return;
|
||||
await issues.removeIssueFromCycle(workspaceSlug.toString(), issue.project_id, cycleId.toString(), issue.id);
|
||||
},
|
||||
}),
|
||||
[issues, workspaceSlug, cycleId, projectId]
|
||||
);
|
||||
|
||||
if (!cycleId) return null;
|
||||
|
||||
return (
|
||||
<BaseCalendarRoot
|
||||
issueStore={cycleIssueStore}
|
||||
issuesFilterStore={cycleIssueFilterStore}
|
||||
issueStore={issues}
|
||||
issuesFilterStore={issuesFilter}
|
||||
QuickActions={CycleIssueQuickActions}
|
||||
issueActions={issueActions}
|
||||
viewId={cycleId.toString()}
|
||||
handleDragDrop={handleDragDrop}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,68 +1,50 @@
|
|||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hoks
|
||||
import { useIssues } from "hooks/store";
|
||||
// components
|
||||
import { ModuleIssueQuickActions } from "components/issues";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
import { TIssue } from "@plane/types";
|
||||
import { EIssueActions } from "../../types";
|
||||
import { BaseCalendarRoot } from "../base-calendar-root";
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export const ModuleCalendarLayout: React.FC = observer(() => {
|
||||
const {
|
||||
moduleIssues: moduleIssueStore,
|
||||
moduleIssuesFilter: moduleIssueFilterStore,
|
||||
calendarHelpers: { handleDragDrop: handleCalenderDragDrop },
|
||||
module: { fetchModuleDetails },
|
||||
} = useMobxStore();
|
||||
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.MODULE);
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, moduleId } = router.query as {
|
||||
const { workspaceSlug, moduleId } = router.query as {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
moduleId: string;
|
||||
};
|
||||
|
||||
const issueActions = {
|
||||
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug || !moduleId) return;
|
||||
await moduleIssueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue, moduleId);
|
||||
fetchModuleDetails(workspaceSlug, issue.project, moduleId);
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug || !moduleId) return;
|
||||
await moduleIssueStore.removeIssue(workspaceSlug, issue.project, issue.id, moduleId);
|
||||
fetchModuleDetails(workspaceSlug, issue.project, moduleId);
|
||||
},
|
||||
[EIssueActions.REMOVE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug || !moduleId || !issue.bridge_id) return;
|
||||
await moduleIssueStore.removeIssueFromModule(workspaceSlug, issue.project, moduleId, issue.id, issue.bridge_id);
|
||||
fetchModuleDetails(workspaceSlug, issue.project, moduleId);
|
||||
},
|
||||
};
|
||||
|
||||
const handleDragDrop = async (source: any, destination: any, issues: IIssue[], issueWithIds: any) => {
|
||||
await handleCalenderDragDrop(
|
||||
source,
|
||||
destination,
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
moduleIssueStore,
|
||||
issues,
|
||||
issueWithIds,
|
||||
moduleId
|
||||
);
|
||||
};
|
||||
const issueActions = useMemo(
|
||||
() => ({
|
||||
[EIssueActions.UPDATE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !moduleId) return;
|
||||
await issues.updateIssue(workspaceSlug, issue.project_id, issue.id, issue, moduleId);
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !moduleId) return;
|
||||
await issues.removeIssue(workspaceSlug, issue.project_id, issue.id, moduleId);
|
||||
},
|
||||
[EIssueActions.REMOVE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !moduleId) return;
|
||||
await issues.removeIssueFromModule(workspaceSlug, issue.project_id, moduleId, issue.id);
|
||||
},
|
||||
}),
|
||||
[issues, workspaceSlug, moduleId]
|
||||
);
|
||||
|
||||
return (
|
||||
<BaseCalendarRoot
|
||||
issueStore={moduleIssueStore}
|
||||
issuesFilterStore={moduleIssueFilterStore}
|
||||
issueStore={issues}
|
||||
issuesFilterStore={issuesFilter}
|
||||
QuickActions={ModuleIssueQuickActions}
|
||||
issueActions={issueActions}
|
||||
viewId={moduleId}
|
||||
handleDragDrop={handleDragDrop}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,56 +1,43 @@
|
|||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { useRouter } from "next/router";
|
||||
// hooks
|
||||
import { useIssues } from "hooks/store";
|
||||
// components
|
||||
import { ProjectIssueQuickActions } from "components/issues";
|
||||
import { BaseCalendarRoot } from "../base-calendar-root";
|
||||
import { EIssueActions } from "../../types";
|
||||
import { IIssue } from "types";
|
||||
import { useRouter } from "next/router";
|
||||
import { TIssue } from "@plane/types";
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export const CalendarLayout: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const {
|
||||
projectIssues: issueStore,
|
||||
projectIssuesFilter: projectIssueFiltersStore,
|
||||
calendarHelpers: { handleDragDrop: handleCalenderDragDrop },
|
||||
} = useMobxStore();
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT);
|
||||
|
||||
const issueActions = {
|
||||
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
const issueActions = useMemo(
|
||||
() => ({
|
||||
[EIssueActions.UPDATE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
await issueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
await issues.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, issue);
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
await issueStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id);
|
||||
},
|
||||
};
|
||||
|
||||
const handleDragDrop = async (source: any, destination: any, issues: IIssue[], issueWithIds: any) => {
|
||||
if (workspaceSlug && projectId)
|
||||
await handleCalenderDragDrop(
|
||||
source,
|
||||
destination,
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
issueStore,
|
||||
issues,
|
||||
issueWithIds
|
||||
);
|
||||
};
|
||||
await issues.removeIssue(workspaceSlug.toString(), issue.project_id, issue.id);
|
||||
},
|
||||
}),
|
||||
[issues, workspaceSlug]
|
||||
);
|
||||
|
||||
return (
|
||||
<BaseCalendarRoot
|
||||
issueStore={issueStore}
|
||||
issuesFilterStore={projectIssueFiltersStore}
|
||||
issueStore={issues}
|
||||
issuesFilterStore={issuesFilter}
|
||||
QuickActions={ProjectIssueQuickActions}
|
||||
issueActions={issueActions}
|
||||
handleDragDrop={handleDragDrop}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,57 +1,44 @@
|
|||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useIssues } from "hooks/store";
|
||||
// components
|
||||
import { ProjectIssueQuickActions } from "components/issues";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
import { TIssue } from "@plane/types";
|
||||
import { EIssueActions } from "../../types";
|
||||
import { BaseCalendarRoot } from "../base-calendar-root";
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export const ProjectViewCalendarLayout: React.FC = observer(() => {
|
||||
const {
|
||||
viewIssues: projectViewIssuesStore,
|
||||
viewIssuesFilter: projectIssueViewFiltersStore,
|
||||
calendarHelpers: { handleDragDrop: handleCalenderDragDrop },
|
||||
} = useMobxStore();
|
||||
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT_VIEW);
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
const { workspaceSlug, projectId, viewId } = router.query;
|
||||
|
||||
const issueActions = {
|
||||
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
const issueActions = useMemo(
|
||||
() => ({
|
||||
[EIssueActions.UPDATE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
await projectViewIssuesStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
await issues.updateIssue(workspaceSlug.toString(), projectId.toString(), issue.id, issue);
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
await projectViewIssuesStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id);
|
||||
},
|
||||
};
|
||||
|
||||
const handleDragDrop = async (source: any, destination: any, issues: IIssue[], issueWithIds: any) => {
|
||||
if (workspaceSlug && projectId)
|
||||
await handleCalenderDragDrop(
|
||||
source,
|
||||
destination,
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
projectViewIssuesStore,
|
||||
issues,
|
||||
issueWithIds
|
||||
);
|
||||
};
|
||||
await issues.removeIssue(workspaceSlug.toString(), projectId.toString(), issue.id);
|
||||
},
|
||||
}),
|
||||
[issues, workspaceSlug, projectId]
|
||||
);
|
||||
|
||||
return (
|
||||
<BaseCalendarRoot
|
||||
issueStore={projectViewIssuesStore}
|
||||
issuesFilterStore={projectIssueViewFiltersStore}
|
||||
issueStore={issues}
|
||||
issuesFilterStore={issuesFilter}
|
||||
QuickActions={ProjectIssueQuickActions}
|
||||
issueActions={issueActions}
|
||||
handleDragDrop={handleDragDrop}
|
||||
viewId={viewId?.toString()}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
42
web/components/issues/issue-layouts/calendar/utils.ts
Normal file
42
web/components/issues/issue-layouts/calendar/utils.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { DraggableLocation } from "@hello-pangea/dnd";
|
||||
import { ICycleIssues } from "store/issue/cycle";
|
||||
import { IModuleIssues } from "store/issue/module";
|
||||
import { IProjectIssues } from "store/issue/project";
|
||||
import { IProjectViewIssues } from "store/issue/project-views";
|
||||
import { TGroupedIssues, IIssueMap } from "@plane/types";
|
||||
|
||||
export const handleDragDrop = async (
|
||||
source: DraggableLocation,
|
||||
destination: DraggableLocation,
|
||||
workspaceSlug: string | undefined,
|
||||
projectId: string | undefined,
|
||||
store: IProjectIssues | IModuleIssues | ICycleIssues | IProjectViewIssues,
|
||||
issueMap: IIssueMap,
|
||||
issueWithIds: TGroupedIssues,
|
||||
viewId: string | null = null // it can be moduleId, cycleId
|
||||
) => {
|
||||
if (!issueMap || !issueWithIds || !workspaceSlug || !projectId) return;
|
||||
|
||||
const sourceColumnId = source?.droppableId || null;
|
||||
const destinationColumnId = destination?.droppableId || null;
|
||||
|
||||
if (!workspaceSlug || !projectId || !sourceColumnId || !destinationColumnId) return;
|
||||
|
||||
if (sourceColumnId === destinationColumnId) return;
|
||||
|
||||
// horizontal
|
||||
if (sourceColumnId != destinationColumnId) {
|
||||
const sourceIssues = issueWithIds[sourceColumnId] || [];
|
||||
|
||||
const [removed] = sourceIssues.splice(source.index, 1);
|
||||
const removedIssueDetail = issueMap[removed];
|
||||
|
||||
const updateIssue = {
|
||||
id: removedIssueDetail?.id,
|
||||
target_date: destinationColumnId,
|
||||
};
|
||||
|
||||
if (viewId) return await store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue, viewId);
|
||||
else return await store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue);
|
||||
}
|
||||
};
|
||||
|
|
@ -5,33 +5,26 @@ import { CalendarDayTile } from "components/issues";
|
|||
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
||||
// types
|
||||
import { ICalendarDate, ICalendarWeek } from "./types";
|
||||
import { IIssue } from "types";
|
||||
import { IGroupedIssues, IIssueResponse } from "store/issues/types";
|
||||
import {
|
||||
ICycleIssuesFilterStore,
|
||||
IModuleIssuesFilterStore,
|
||||
IProjectIssuesFilterStore,
|
||||
IViewIssuesFilterStore,
|
||||
} from "store/issues";
|
||||
import { TGroupedIssues, TIssue, TIssueMap } from "@plane/types";
|
||||
import { ICycleIssuesFilter } from "store/issue/cycle";
|
||||
import { IModuleIssuesFilter } from "store/issue/module";
|
||||
import { IProjectIssuesFilter } from "store/issue/project";
|
||||
import { IProjectViewIssuesFilter } from "store/issue/project-views";
|
||||
|
||||
type Props = {
|
||||
issuesFilterStore:
|
||||
| IProjectIssuesFilterStore
|
||||
| IModuleIssuesFilterStore
|
||||
| ICycleIssuesFilterStore
|
||||
| IViewIssuesFilterStore;
|
||||
issues: IIssueResponse | undefined;
|
||||
groupedIssueIds: IGroupedIssues;
|
||||
issuesFilterStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
|
||||
issues: TIssueMap | undefined;
|
||||
groupedIssueIds: TGroupedIssues;
|
||||
week: ICalendarWeek | undefined;
|
||||
quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
enableQuickIssueCreate?: boolean;
|
||||
disableIssueCreation?: boolean;
|
||||
quickAddCallback?: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: IIssue,
|
||||
data: TIssue,
|
||||
viewId?: string
|
||||
) => Promise<IIssue | undefined>;
|
||||
) => Promise<TIssue | undefined>;
|
||||
viewId?: string;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication, useIssues, useUser } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import { EmptyState } from "components/common";
|
||||
|
|
@ -13,10 +12,10 @@ import { Button } from "@plane/ui";
|
|||
// assets
|
||||
import emptyIssue from "public/empty-state/issue.svg";
|
||||
// types
|
||||
import { ISearchIssueResponse } from "types";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { ISearchIssueResponse } from "@plane/types";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
|
||||
type Props = {
|
||||
workspaceSlug: string | undefined;
|
||||
|
|
@ -28,13 +27,15 @@ export const CycleEmptyState: React.FC<Props> = observer((props) => {
|
|||
const { workspaceSlug, projectId, cycleId } = props;
|
||||
// states
|
||||
const [cycleIssuesListModal, setCycleIssuesListModal] = useState(false);
|
||||
|
||||
// store hooks
|
||||
const { issues } = useIssues(EIssuesStoreType.CYCLE);
|
||||
const {
|
||||
cycleIssues: cycleIssueStore,
|
||||
commandPalette: commandPaletteStore,
|
||||
trackEvent: { setTrackElement },
|
||||
user: { currentProjectRole: userRole },
|
||||
} = useMobxStore();
|
||||
commandPalette: { toggleCreateIssueModal },
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
const {
|
||||
membership: { currentProjectRole: userRole },
|
||||
} = useUser();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
|
|
@ -43,7 +44,7 @@ export const CycleEmptyState: React.FC<Props> = observer((props) => {
|
|||
|
||||
const issueIds = data.map((i) => i.id);
|
||||
|
||||
await cycleIssueStore.addIssueToCycle(workspaceSlug.toString(), cycleId.toString(), issueIds).catch(() => {
|
||||
await issues.addIssueToCycle(workspaceSlug.toString(), projectId, cycleId.toString(), issueIds).catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
|
|
@ -52,7 +53,7 @@ export const CycleEmptyState: React.FC<Props> = observer((props) => {
|
|||
});
|
||||
};
|
||||
|
||||
const isEditingAllowed = !!userRole && userRole >= EUserWorkspaceRoles.MEMBER;
|
||||
const isEditingAllowed = !!userRole && userRole >= EUserProjectRoles.MEMBER;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -72,7 +73,7 @@ export const CycleEmptyState: React.FC<Props> = observer((props) => {
|
|||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => {
|
||||
setTrackElement("CYCLE_EMPTY_STATE");
|
||||
commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.CYCLE);
|
||||
toggleCreateIssueModal(true, EIssuesStoreType.CYCLE);
|
||||
},
|
||||
}}
|
||||
secondaryButton={
|
||||
|
|
|
|||
|
|
@ -1,31 +1,24 @@
|
|||
// next
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { Plus, PlusIcon } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication, useProject } from "hooks/store";
|
||||
// components
|
||||
import { EmptyState } from "components/common";
|
||||
// assets
|
||||
import emptyIssue from "public/empty-state/issue.svg";
|
||||
import emptyProject from "public/empty-state/project.svg";
|
||||
// icons
|
||||
import { Plus, PlusIcon } from "lucide-react";
|
||||
|
||||
export const GlobalViewEmptyState: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
commandPalette: commandPaletteStore,
|
||||
project: projectStore,
|
||||
trackEvent: { setTrackElement },
|
||||
} = useMobxStore();
|
||||
|
||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null;
|
||||
commandPalette: { toggleCreateIssueModal, toggleCreateProjectModal },
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
const { workspaceProjectIds } = useProject();
|
||||
|
||||
return (
|
||||
<div className="grid h-full w-full place-items-center">
|
||||
{!projects || projects?.length === 0 ? (
|
||||
{!workspaceProjectIds || workspaceProjectIds?.length === 0 ? (
|
||||
<EmptyState
|
||||
image={emptyProject}
|
||||
title="No projects yet"
|
||||
|
|
@ -35,7 +28,7 @@ export const GlobalViewEmptyState: React.FC = observer(() => {
|
|||
text: "New Project",
|
||||
onClick: () => {
|
||||
setTrackElement("ALL_ISSUES_EMPTY_STATE");
|
||||
commandPaletteStore.toggleCreateProjectModal(true);
|
||||
toggleCreateProjectModal(true);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
|
@ -49,7 +42,7 @@ export const GlobalViewEmptyState: React.FC = observer(() => {
|
|||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => {
|
||||
setTrackElement("ALL_ISSUES_EMPTY_STATE");
|
||||
commandPaletteStore.toggleCreateIssueModal(true);
|
||||
toggleCreateIssueModal(true);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,21 @@
|
|||
import { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication, useIssues, useUser } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import { EmptyState } from "components/common";
|
||||
import { ExistingIssuesListModal } from "components/core";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// assets
|
||||
import emptyIssue from "public/empty-state/issue.svg";
|
||||
import { ExistingIssuesListModal } from "components/core";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { ISearchIssueResponse } from "types";
|
||||
import useToast from "hooks/use-toast";
|
||||
import { useState } from "react";
|
||||
// types
|
||||
import { ISearchIssueResponse } from "@plane/types";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
|
||||
type Props = {
|
||||
workspaceSlug: string | undefined;
|
||||
|
|
@ -23,14 +27,16 @@ export const ModuleEmptyState: React.FC<Props> = observer((props) => {
|
|||
const { workspaceSlug, projectId, moduleId } = props;
|
||||
// states
|
||||
const [moduleIssuesListModal, setModuleIssuesListModal] = useState(false);
|
||||
|
||||
// store hooks
|
||||
const { issues } = useIssues(EIssuesStoreType.MODULE);
|
||||
const {
|
||||
moduleIssues: moduleIssueStore,
|
||||
commandPalette: commandPaletteStore,
|
||||
trackEvent: { setTrackElement },
|
||||
user: { currentProjectRole: userRole },
|
||||
} = useMobxStore();
|
||||
|
||||
commandPalette: { toggleCreateIssueModal },
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
const {
|
||||
membership: { currentProjectRole: userRole },
|
||||
} = useUser();
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const handleAddIssuesToModule = async (data: ISearchIssueResponse[]) => {
|
||||
|
|
@ -38,16 +44,18 @@ export const ModuleEmptyState: React.FC<Props> = observer((props) => {
|
|||
|
||||
const issueIds = data.map((i) => i.id);
|
||||
|
||||
await moduleIssueStore.addIssueToModule(workspaceSlug.toString(), moduleId.toString(), issueIds).catch(() =>
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Selected issues could not be added to the module. Please try again.",
|
||||
})
|
||||
);
|
||||
await issues
|
||||
.addIssueToModule(workspaceSlug.toString(), projectId?.toString(), moduleId.toString(), issueIds)
|
||||
.catch(() =>
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Selected issues could not be added to the module. Please try again.",
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const isEditingAllowed = !!userRole && userRole >= EUserWorkspaceRoles.MEMBER;
|
||||
const isEditingAllowed = !!userRole && userRole >= EUserProjectRoles.MEMBER;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -67,7 +75,7 @@ export const ModuleEmptyState: React.FC<Props> = observer((props) => {
|
|||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => {
|
||||
setTrackElement("MODULE_EMPTY_STATE");
|
||||
commandPaletteStore.toggleCreateIssueModal(true);
|
||||
toggleCreateIssueModal(true);
|
||||
},
|
||||
}}
|
||||
secondaryButton={
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
import { observer } from "mobx-react-lite";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// components
|
||||
import { EmptyState } from "components/common";
|
||||
// assets
|
||||
import emptyIssue from "public/empty-state/issue.svg";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
|
||||
export const ProjectViewEmptyState: React.FC = observer(() => {
|
||||
// store hooks
|
||||
const {
|
||||
commandPalette: commandPaletteStore,
|
||||
trackEvent: { setTrackElement },
|
||||
} = useMobxStore();
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
|
||||
return (
|
||||
<div className="grid h-full w-full place-items-center">
|
||||
|
|
@ -25,7 +26,7 @@ export const ProjectViewEmptyState: React.FC = observer(() => {
|
|||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => {
|
||||
setTrackElement("VIEW_EMPTY_STATE");
|
||||
commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.PROJECT_VIEW);
|
||||
commandPaletteStore.toggleCreateIssueModal(true, EIssuesStoreType.PROJECT_VIEW);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,23 +1,26 @@
|
|||
import { observer } from "mobx-react-lite";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication, useUser } from "hooks/store";
|
||||
// components
|
||||
import { NewEmptyState } from "components/common/new-empty-state";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
// assets
|
||||
import emptyIssue from "public/empty-state/empty_issues.webp";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
|
||||
export const ProjectEmptyState: React.FC = observer(() => {
|
||||
// store hooks
|
||||
const {
|
||||
commandPalette: commandPaletteStore,
|
||||
trackEvent: { setTrackElement },
|
||||
user: { currentProjectRole },
|
||||
} = useMobxStore();
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
|
||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
||||
|
||||
return (
|
||||
<div className="grid h-full w-full place-items-center">
|
||||
|
|
@ -31,18 +34,14 @@ export const ProjectEmptyState: React.FC = observer(() => {
|
|||
description:
|
||||
"Redesign the Plane UI, Rebrand the company, or Launch the new fuel injection system are examples of issues that likely have sub-issues.",
|
||||
}}
|
||||
primaryButton={
|
||||
isEditingAllowed
|
||||
? {
|
||||
text: "Create your first issue",
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => {
|
||||
setTrackElement("PROJECT_EMPTY_STATE");
|
||||
commandPaletteStore.toggleCreateIssueModal(true, EProjectStore.PROJECT);
|
||||
},
|
||||
}
|
||||
: null
|
||||
}
|
||||
primaryButton={{
|
||||
text: "Create your first issue",
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => {
|
||||
setTrackElement("PROJECT_EMPTY_STATE");
|
||||
commandPaletteStore.toggleCreateIssueModal(true, EIssuesStoreType.PROJECT);
|
||||
},
|
||||
}}
|
||||
disabled={!isEditingAllowed}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { X } from "lucide-react";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
// components
|
||||
import {
|
||||
AppliedDateFilters,
|
||||
|
|
@ -10,22 +12,18 @@ import {
|
|||
AppliedStateFilters,
|
||||
AppliedStateGroupFilters,
|
||||
} from "components/issues";
|
||||
// icons
|
||||
import { X } from "lucide-react";
|
||||
// helpers
|
||||
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
|
||||
// types
|
||||
import { IIssueFilterOptions, IIssueLabel, IProject, IState, IUserLite } from "types";
|
||||
import { IIssueFilterOptions, IIssueLabel, IState } from "@plane/types";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
|
||||
type Props = {
|
||||
appliedFilters: IIssueFilterOptions;
|
||||
handleClearAllFilters: () => void;
|
||||
handleRemoveFilter: (key: keyof IIssueFilterOptions, value: string | null) => void;
|
||||
labels?: IIssueLabel[] | undefined;
|
||||
members?: IUserLite[] | undefined;
|
||||
projects?: IProject[] | undefined;
|
||||
states?: IState[] | undefined;
|
||||
};
|
||||
|
||||
|
|
@ -33,17 +31,17 @@ const membersFilters = ["assignees", "mentions", "created_by", "subscriber"];
|
|||
const dateFilters = ["start_date", "target_date"];
|
||||
|
||||
export const AppliedFiltersList: React.FC<Props> = observer((props) => {
|
||||
const { appliedFilters, handleClearAllFilters, handleRemoveFilter, labels, members, projects, states } = props;
|
||||
|
||||
const { appliedFilters, handleClearAllFilters, handleRemoveFilter, labels, states } = props;
|
||||
// store hooks
|
||||
const {
|
||||
user: { currentProjectRole },
|
||||
} = useMobxStore();
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
|
||||
if (!appliedFilters) return null;
|
||||
|
||||
if (Object.keys(appliedFilters).length === 0) return null;
|
||||
|
||||
const isEditingAllowed = currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||
const isEditingAllowed = currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-stretch gap-2 bg-custom-background-100">
|
||||
|
|
@ -63,7 +61,6 @@ export const AppliedFiltersList: React.FC<Props> = observer((props) => {
|
|||
<AppliedMembersFilters
|
||||
editable={isEditingAllowed}
|
||||
handleRemove={(val) => handleRemoveFilter(filterKey, val)}
|
||||
members={members}
|
||||
values={value}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -103,7 +100,6 @@ export const AppliedFiltersList: React.FC<Props> = observer((props) => {
|
|||
<AppliedProjectFilters
|
||||
editable={isEditingAllowed}
|
||||
handleRemove={(val) => handleRemoveFilter("project", val)}
|
||||
projects={projects}
|
||||
values={value}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { observer } from "mobx-react-lite";
|
|||
// icons
|
||||
import { X } from "lucide-react";
|
||||
// types
|
||||
import { IIssueLabel } from "types";
|
||||
import { IIssueLabel } from "@plane/types";
|
||||
|
||||
type Props = {
|
||||
handleRemove: (val: string) => void;
|
||||
|
|
|
|||
|
|
@ -3,22 +3,25 @@ import { X } from "lucide-react";
|
|||
// ui
|
||||
import { Avatar } from "@plane/ui";
|
||||
// types
|
||||
import { IUserLite } from "types";
|
||||
import { useMember } from "hooks/store";
|
||||
|
||||
type Props = {
|
||||
handleRemove: (val: string) => void;
|
||||
members: IUserLite[] | undefined;
|
||||
values: string[];
|
||||
editable: boolean | undefined;
|
||||
};
|
||||
|
||||
export const AppliedMembersFilters: React.FC<Props> = observer((props) => {
|
||||
const { handleRemove, members, values, editable } = props;
|
||||
const { handleRemove, values, editable } = props;
|
||||
|
||||
const {
|
||||
project: { getProjectMemberDetails },
|
||||
} = useMember();
|
||||
|
||||
return (
|
||||
<>
|
||||
{values.map((memberId) => {
|
||||
const memberDetails = members?.find((m) => m.id === memberId);
|
||||
const memberDetails = getProjectMemberDetails(memberId)?.member;
|
||||
|
||||
if (!memberDetails) return null;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
|
|||
import { PriorityIcon } from "@plane/ui";
|
||||
import { X } from "lucide-react";
|
||||
// types
|
||||
import { TIssuePriorities } from "types";
|
||||
import { TIssuePriorities } from "@plane/types";
|
||||
|
||||
type Props = {
|
||||
handleRemove: (val: string) => void;
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// icons
|
||||
import { X } from "lucide-react";
|
||||
// types
|
||||
import { IProject } from "types";
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
// helpers
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
|
||||
type Props = {
|
||||
handleRemove: (val: string) => void;
|
||||
projects: IProject[] | undefined;
|
||||
values: string[];
|
||||
editable: boolean | undefined;
|
||||
};
|
||||
|
||||
export const AppliedProjectFilters: React.FC<Props> = observer((props) => {
|
||||
const { handleRemove, projects, values, editable } = props;
|
||||
const { handleRemove, values, editable } = props;
|
||||
// store hooks
|
||||
const { projectMap } = useProject();
|
||||
|
||||
return (
|
||||
<>
|
||||
{values.map((projectId) => {
|
||||
const projectDetails = projects?.find((p) => p.id === projectId);
|
||||
const projectDetails = projectMap?.[projectId] ?? null;
|
||||
|
||||
if (!projectDetails) return null;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,28 @@
|
|||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useIssues, useLabel, useProjectState } from "hooks/store";
|
||||
// components
|
||||
import { AppliedFiltersList, SaveFilterView } from "components/issues";
|
||||
// types
|
||||
import { IIssueFilterOptions } from "types";
|
||||
import { EFilterType } from "store/issues/types";
|
||||
import { IIssueFilterOptions } from "@plane/types";
|
||||
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
|
||||
|
||||
export const ArchivedIssueAppliedFiltersRoot: React.FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||
// store hooks
|
||||
|
||||
const {
|
||||
projectArchivedIssuesFilter: { issueFilters, updateFilters },
|
||||
projectLabel: { projectLabels },
|
||||
projectMember: { projectMembers },
|
||||
projectState: projectStateStore,
|
||||
} = useMobxStore();
|
||||
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
} = useIssues(EIssuesStoreType.ARCHIVED);
|
||||
const {
|
||||
project: { projectLabels },
|
||||
} = useLabel();
|
||||
const { projectStates } = useProjectState();
|
||||
// derived values
|
||||
const userFilters = issueFilters?.filters;
|
||||
|
||||
// filters whose value not null or empty array
|
||||
const appliedFilters: IIssueFilterOptions = {};
|
||||
Object.entries(userFilters ?? {}).forEach(([key, value]) => {
|
||||
|
|
@ -37,7 +38,7 @@ export const ArchivedIssueAppliedFiltersRoot: React.FC = observer(() => {
|
|||
|
||||
// remove all values of the key if value is null
|
||||
if (!value) {
|
||||
updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, {
|
||||
updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, {
|
||||
[key]: null,
|
||||
});
|
||||
return;
|
||||
|
|
@ -47,7 +48,7 @@ export const ArchivedIssueAppliedFiltersRoot: React.FC = observer(() => {
|
|||
let newValues = issueFilters?.filters?.[key] ?? [];
|
||||
newValues = newValues.filter((val) => val !== value);
|
||||
|
||||
updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, {
|
||||
updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, {
|
||||
[key]: newValues,
|
||||
});
|
||||
};
|
||||
|
|
@ -60,7 +61,7 @@ export const ArchivedIssueAppliedFiltersRoot: React.FC = observer(() => {
|
|||
newFilters[key as keyof IIssueFilterOptions] = null;
|
||||
});
|
||||
|
||||
updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, {
|
||||
updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, {
|
||||
...newFilters,
|
||||
});
|
||||
};
|
||||
|
|
@ -75,8 +76,7 @@ export const ArchivedIssueAppliedFiltersRoot: React.FC = observer(() => {
|
|||
handleClearAllFilters={handleClearAllFilters}
|
||||
handleRemoveFilter={handleRemoveFilter}
|
||||
labels={projectLabels ?? []}
|
||||
members={projectMembers?.map((m) => m.member)}
|
||||
states={projectStateStore.states?.[projectId?.toString() ?? ""]}
|
||||
states={projectStates}
|
||||
/>
|
||||
|
||||
<SaveFilterView workspaceSlug={workspaceSlug} projectId={projectId} filterParams={appliedFilters} />
|
||||
|
|
|
|||
|
|
@ -1,31 +1,32 @@
|
|||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useIssues, useLabel, useProjectState } from "hooks/store";
|
||||
// components
|
||||
import { AppliedFiltersList, SaveFilterView } from "components/issues";
|
||||
// types
|
||||
import { IIssueFilterOptions } from "types";
|
||||
import { EFilterType } from "store/issues/types";
|
||||
import { IIssueFilterOptions } from "@plane/types";
|
||||
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
|
||||
|
||||
export const CycleAppliedFiltersRoot: React.FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId } = router.query as {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
cycleId: string;
|
||||
};
|
||||
// store hooks
|
||||
const {
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
} = useIssues(EIssuesStoreType.CYCLE);
|
||||
|
||||
const {
|
||||
projectLabel: { projectLabels },
|
||||
projectState: projectStateStore,
|
||||
projectMember: { projectMembers },
|
||||
cycleIssuesFilter: { issueFilters, updateFilters },
|
||||
} = useMobxStore();
|
||||
|
||||
project: { projectLabels },
|
||||
} = useLabel();
|
||||
const { projectStates } = useProjectState();
|
||||
// derived values
|
||||
const userFilters = issueFilters?.filters;
|
||||
|
||||
// filters whose value not null or empty array
|
||||
const appliedFilters: IIssueFilterOptions = {};
|
||||
Object.entries(userFilters ?? {}).forEach(([key, value]) => {
|
||||
|
|
@ -35,32 +36,20 @@ export const CycleAppliedFiltersRoot: React.FC = observer(() => {
|
|||
});
|
||||
|
||||
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
if (!workspaceSlug || !projectId || !cycleId) return;
|
||||
if (!value) {
|
||||
updateFilters(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
EFilterType.FILTERS,
|
||||
{
|
||||
[key]: null,
|
||||
},
|
||||
cycleId
|
||||
);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, {
|
||||
[key]: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let newValues = issueFilters?.filters?.[key] ?? [];
|
||||
newValues = newValues.filter((val) => val !== value);
|
||||
|
||||
updateFilters(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
EFilterType.FILTERS,
|
||||
{
|
||||
[key]: newValues,
|
||||
},
|
||||
cycleId
|
||||
);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, {
|
||||
[key]: newValues,
|
||||
});
|
||||
};
|
||||
|
||||
const handleClearAllFilters = () => {
|
||||
|
|
@ -69,7 +58,7 @@ export const CycleAppliedFiltersRoot: React.FC = observer(() => {
|
|||
Object.keys(userFilters ?? {}).forEach((key) => {
|
||||
newFilters[key as keyof IIssueFilterOptions] = null;
|
||||
});
|
||||
updateFilters(workspaceSlug, projectId, EFilterType.FILTERS, { ...newFilters }, cycleId);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { ...newFilters }, cycleId);
|
||||
};
|
||||
|
||||
// return if no filters are applied
|
||||
|
|
@ -82,8 +71,7 @@ export const CycleAppliedFiltersRoot: React.FC = observer(() => {
|
|||
handleClearAllFilters={handleClearAllFilters}
|
||||
handleRemoveFilter={handleRemoveFilter}
|
||||
labels={projectLabels ?? []}
|
||||
members={projectMembers?.map((m) => m.member)}
|
||||
states={projectStateStore.states?.[cycleId ?? ""]}
|
||||
states={projectStates}
|
||||
/>
|
||||
|
||||
<SaveFilterView workspaceSlug={workspaceSlug} projectId={projectId} filterParams={appliedFilters} />
|
||||
|
|
|
|||
|
|
@ -1,25 +1,26 @@
|
|||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useIssues, useLabel, useProjectState } from "hooks/store";
|
||||
// components
|
||||
import { AppliedFiltersList, SaveFilterView } from "components/issues";
|
||||
// types
|
||||
import { IIssueFilterOptions } from "types";
|
||||
import { EFilterType } from "store/issues/types";
|
||||
import { IIssueFilterOptions } from "@plane/types";
|
||||
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
|
||||
|
||||
export const DraftIssueAppliedFiltersRoot: React.FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
projectDraftIssuesFilter: { issueFilters, updateFilters },
|
||||
projectLabel: { projectLabels },
|
||||
projectMember: { projectMembers },
|
||||
projectState: projectStateStore,
|
||||
} = useMobxStore();
|
||||
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
} = useIssues(EIssuesStoreType.DRAFT);
|
||||
const {
|
||||
project: { projectLabels },
|
||||
} = useLabel();
|
||||
const { projectStates } = useProjectState();
|
||||
// derived values
|
||||
const userFilters = issueFilters?.filters;
|
||||
// filters whose value not null or empty array
|
||||
const appliedFilters: IIssueFilterOptions = {};
|
||||
|
|
@ -34,7 +35,7 @@ export const DraftIssueAppliedFiltersRoot: React.FC = observer(() => {
|
|||
|
||||
// remove all values of the key if value is null
|
||||
if (!value) {
|
||||
updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, {
|
||||
updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, {
|
||||
[key]: null,
|
||||
});
|
||||
return;
|
||||
|
|
@ -44,7 +45,7 @@ export const DraftIssueAppliedFiltersRoot: React.FC = observer(() => {
|
|||
let newValues = issueFilters?.filters?.[key] ?? [];
|
||||
newValues = newValues.filter((val) => val !== value);
|
||||
|
||||
updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, {
|
||||
updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, {
|
||||
[key]: newValues,
|
||||
});
|
||||
};
|
||||
|
|
@ -57,7 +58,7 @@ export const DraftIssueAppliedFiltersRoot: React.FC = observer(() => {
|
|||
newFilters[key as keyof IIssueFilterOptions] = null;
|
||||
});
|
||||
|
||||
updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, { ...newFilters });
|
||||
updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, { ...newFilters });
|
||||
};
|
||||
|
||||
// return if no filters are applied
|
||||
|
|
@ -70,8 +71,7 @@ export const DraftIssueAppliedFiltersRoot: React.FC = observer(() => {
|
|||
handleClearAllFilters={handleClearAllFilters}
|
||||
handleRemoveFilter={handleRemoveFilter}
|
||||
labels={projectLabels ?? []}
|
||||
members={projectMembers?.map((m) => m.member)}
|
||||
states={projectStateStore.states?.[projectId?.toString() ?? ""]}
|
||||
states={projectStates}
|
||||
/>
|
||||
|
||||
<SaveFilterView workspaceSlug={workspaceSlug} projectId={projectId} filterParams={appliedFilters} />
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useIssues, useLabel } from "hooks/store";
|
||||
// components
|
||||
import { AppliedFiltersList } from "components/issues";
|
||||
// types
|
||||
import { IIssueFilterOptions } from "types";
|
||||
import { EFilterType } from "store/issues/types";
|
||||
import { IIssueFilterOptions } from "@plane/types";
|
||||
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
|
||||
|
||||
export const GlobalViewsAppliedFiltersRoot = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query as { workspaceSlug: string; globalViewId: string };
|
||||
|
||||
const { workspaceSlug, globalViewId } = router.query;
|
||||
// store hooks
|
||||
const {
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
} = useIssues(EIssuesStoreType.GLOBAL);
|
||||
const {
|
||||
project: { workspaceProjects },
|
||||
workspace: { workspaceLabels },
|
||||
workspaceMember: { workspaceMembers },
|
||||
workspaceGlobalIssuesFilter: { issueFilters, updateFilters },
|
||||
} = useMobxStore();
|
||||
|
||||
} = useLabel();
|
||||
// derived values
|
||||
const userFilters = issueFilters?.filters;
|
||||
|
||||
// filters whose value not null or empty array
|
||||
|
|
@ -31,23 +31,43 @@ export const GlobalViewsAppliedFiltersRoot = observer(() => {
|
|||
});
|
||||
|
||||
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
|
||||
if (!workspaceSlug || !globalViewId) return;
|
||||
|
||||
if (!value) {
|
||||
updateFilters(workspaceSlug, EFilterType.FILTERS, { [key]: null });
|
||||
updateFilters(
|
||||
workspaceSlug.toString(),
|
||||
undefined,
|
||||
EIssueFilterType.FILTERS,
|
||||
{ [key]: null },
|
||||
globalViewId.toString()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let newValues = userFilters?.[key] ?? [];
|
||||
newValues = newValues.filter((val) => val !== value);
|
||||
updateFilters(workspaceSlug, EFilterType.FILTERS, { [key]: newValues });
|
||||
updateFilters(
|
||||
workspaceSlug.toString(),
|
||||
undefined,
|
||||
EIssueFilterType.FILTERS,
|
||||
{ [key]: newValues },
|
||||
globalViewId.toString()
|
||||
);
|
||||
};
|
||||
|
||||
const handleClearAllFilters = () => {
|
||||
if (!workspaceSlug) return;
|
||||
if (!workspaceSlug || !globalViewId) return;
|
||||
const newFilters: IIssueFilterOptions = {};
|
||||
Object.keys(userFilters ?? {}).forEach((key) => {
|
||||
newFilters[key as keyof IIssueFilterOptions] = null;
|
||||
});
|
||||
updateFilters(workspaceSlug, EFilterType.FILTERS, { ...newFilters });
|
||||
updateFilters(
|
||||
workspaceSlug.toString(),
|
||||
undefined,
|
||||
EIssueFilterType.FILTERS,
|
||||
{ ...newFilters },
|
||||
globalViewId.toString()
|
||||
);
|
||||
};
|
||||
|
||||
// const handleUpdateView = () => {
|
||||
|
|
@ -78,8 +98,6 @@ export const GlobalViewsAppliedFiltersRoot = observer(() => {
|
|||
<div className="flex items-start justify-between gap-4 p-4">
|
||||
<AppliedFiltersList
|
||||
labels={workspaceLabels ?? undefined}
|
||||
members={workspaceMembers?.map((m) => m.member)}
|
||||
projects={workspaceProjects ?? undefined}
|
||||
appliedFilters={appliedFilters ?? {}}
|
||||
handleClearAllFilters={handleClearAllFilters}
|
||||
handleRemoveFilter={handleRemoveFilter}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,31 @@
|
|||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useIssues, useLabel, useProjectState } from "hooks/store";
|
||||
// components
|
||||
import { AppliedFiltersList, SaveFilterView } from "components/issues";
|
||||
// types
|
||||
import { IIssueFilterOptions } from "types";
|
||||
import { EFilterType } from "store/issues/types";
|
||||
import { IIssueFilterOptions } from "@plane/types";
|
||||
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
|
||||
|
||||
export const ModuleAppliedFiltersRoot: React.FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, moduleId } = router.query as {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
moduleId: string;
|
||||
};
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
projectLabel: { projectLabels },
|
||||
projectState: projectStateStore,
|
||||
projectMember: { projectMembers },
|
||||
moduleIssuesFilter: { issueFilters, updateFilters },
|
||||
} = useMobxStore();
|
||||
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
} = useIssues(EIssuesStoreType.MODULE);
|
||||
const {
|
||||
project: { projectLabels },
|
||||
} = useLabel();
|
||||
const { projectStates } = useProjectState();
|
||||
// derived values
|
||||
const userFilters = issueFilters?.filters;
|
||||
|
||||
// filters whose value not null or empty array
|
||||
const appliedFilters: IIssueFilterOptions = {};
|
||||
Object.entries(userFilters ?? {}).forEach(([key, value]) => {
|
||||
|
|
@ -37,30 +37,18 @@ export const ModuleAppliedFiltersRoot: React.FC = observer(() => {
|
|||
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
if (!value) {
|
||||
updateFilters(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
EFilterType.FILTERS,
|
||||
{
|
||||
[key]: null,
|
||||
},
|
||||
moduleId
|
||||
);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, {
|
||||
[key]: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let newValues = issueFilters?.filters?.[key] ?? [];
|
||||
newValues = newValues.filter((val) => val !== value);
|
||||
|
||||
updateFilters(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
EFilterType.FILTERS,
|
||||
{
|
||||
[key]: newValues,
|
||||
},
|
||||
moduleId
|
||||
);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, {
|
||||
[key]: newValues,
|
||||
});
|
||||
};
|
||||
|
||||
const handleClearAllFilters = () => {
|
||||
|
|
@ -69,7 +57,7 @@ export const ModuleAppliedFiltersRoot: React.FC = observer(() => {
|
|||
Object.keys(userFilters ?? {}).forEach((key) => {
|
||||
newFilters[key as keyof IIssueFilterOptions] = null;
|
||||
});
|
||||
updateFilters(workspaceSlug, projectId, EFilterType.FILTERS, { ...newFilters }, moduleId);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { ...newFilters }, moduleId);
|
||||
};
|
||||
|
||||
// return if no filters are applied
|
||||
|
|
@ -82,8 +70,7 @@ export const ModuleAppliedFiltersRoot: React.FC = observer(() => {
|
|||
handleClearAllFilters={handleClearAllFilters}
|
||||
handleRemoveFilter={handleRemoveFilter}
|
||||
labels={projectLabels ?? []}
|
||||
members={projectMembers?.map((m) => m.member)}
|
||||
states={projectStateStore.states?.[moduleId ?? ""]}
|
||||
states={projectStates}
|
||||
/>
|
||||
|
||||
<SaveFilterView workspaceSlug={workspaceSlug} projectId={projectId} filterParams={appliedFilters} />
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useIssues, useLabel } from "hooks/store";
|
||||
// components
|
||||
import { AppliedFiltersList } from "components/issues";
|
||||
// types
|
||||
import { IIssueFilterOptions } from "types";
|
||||
import { EFilterType } from "store/issues/types";
|
||||
import { IIssueFilterOptions } from "@plane/types";
|
||||
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
|
||||
|
||||
export const ProfileIssuesAppliedFiltersRoot: React.FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query as {
|
||||
workspaceSlug: string;
|
||||
};
|
||||
const { workspaceSlug, userId } = router.query;
|
||||
// store hooks
|
||||
const {
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
} = useIssues(EIssuesStoreType.PROFILE);
|
||||
|
||||
const {
|
||||
workspace: { workspaceLabels },
|
||||
workspaceProfileIssuesFilter: { issueFilters, updateFilters },
|
||||
projectMember: { projectMembers },
|
||||
} = useMobxStore();
|
||||
|
||||
} = useLabel();
|
||||
// derived values
|
||||
const userFilters = issueFilters?.filters;
|
||||
|
||||
// filters whose value not null or empty array
|
||||
|
|
@ -32,27 +32,33 @@ export const ProfileIssuesAppliedFiltersRoot: React.FC = observer(() => {
|
|||
});
|
||||
|
||||
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
|
||||
if (!workspaceSlug) return;
|
||||
if (!workspaceSlug || !userId) return;
|
||||
if (!value) {
|
||||
updateFilters(workspaceSlug, EFilterType.FILTERS, { [key]: null });
|
||||
updateFilters(workspaceSlug.toString(), undefined, EIssueFilterType.FILTERS, { [key]: null }, userId.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
let newValues = issueFilters?.filters?.[key] ?? [];
|
||||
newValues = newValues.filter((val) => val !== value);
|
||||
|
||||
updateFilters(workspaceSlug, EFilterType.FILTERS, {
|
||||
[key]: newValues,
|
||||
});
|
||||
updateFilters(
|
||||
workspaceSlug.toString(),
|
||||
undefined,
|
||||
EIssueFilterType.FILTERS,
|
||||
{
|
||||
[key]: newValues,
|
||||
},
|
||||
userId.toString()
|
||||
);
|
||||
};
|
||||
|
||||
const handleClearAllFilters = () => {
|
||||
if (!workspaceSlug) return;
|
||||
if (!workspaceSlug || !userId) return;
|
||||
const newFilters: IIssueFilterOptions = {};
|
||||
Object.keys(userFilters ?? {}).forEach((key) => {
|
||||
newFilters[key as keyof IIssueFilterOptions] = null;
|
||||
});
|
||||
updateFilters(workspaceSlug, EFilterType.FILTERS, { ...newFilters });
|
||||
updateFilters(workspaceSlug.toString(), undefined, EIssueFilterType.FILTERS, { ...newFilters }, userId.toString());
|
||||
};
|
||||
|
||||
// return if no filters are applied
|
||||
|
|
@ -65,7 +71,6 @@ export const ProfileIssuesAppliedFiltersRoot: React.FC = observer(() => {
|
|||
handleClearAllFilters={handleClearAllFilters}
|
||||
handleRemoveFilter={handleRemoveFilter}
|
||||
labels={workspaceLabels ?? []}
|
||||
members={projectMembers?.map((m) => m.member)}
|
||||
states={[]}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useLabel, useProjectState, useUser } from "hooks/store";
|
||||
import { useIssues } from "hooks/store/use-issues";
|
||||
// components
|
||||
import { AppliedFiltersList, SaveFilterView } from "components/issues";
|
||||
// types
|
||||
import { IIssueFilterOptions } from "types";
|
||||
import { EFilterType } from "store/issues/types";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
// types
|
||||
import { IIssueFilterOptions } from "@plane/types";
|
||||
|
||||
export const ProjectAppliedFiltersRoot: React.FC = observer(() => {
|
||||
// router
|
||||
|
|
@ -17,18 +18,20 @@ export const ProjectAppliedFiltersRoot: React.FC = observer(() => {
|
|||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
};
|
||||
// mobx stores
|
||||
// store hooks
|
||||
const {
|
||||
projectLabel: { projectLabels },
|
||||
projectState: projectStateStore,
|
||||
projectMember: { projectMembers },
|
||||
projectIssuesFilter: { issueFilters, updateFilters },
|
||||
user: { currentProjectRole },
|
||||
} = useMobxStore();
|
||||
project: { projectLabels },
|
||||
} = useLabel();
|
||||
const {
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
} = useIssues(EIssuesStoreType.PROJECT);
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { projectStates } = useProjectState();
|
||||
// derived values
|
||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
||||
const userFilters = issueFilters?.filters;
|
||||
|
||||
// filters whose value not null or empty array
|
||||
const appliedFilters: IIssueFilterOptions = {};
|
||||
Object.entries(userFilters ?? {}).forEach(([key, value]) => {
|
||||
|
|
@ -40,7 +43,7 @@ export const ProjectAppliedFiltersRoot: React.FC = observer(() => {
|
|||
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
if (!value) {
|
||||
updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, {
|
||||
updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, {
|
||||
[key]: null,
|
||||
});
|
||||
return;
|
||||
|
|
@ -49,7 +52,7 @@ export const ProjectAppliedFiltersRoot: React.FC = observer(() => {
|
|||
let newValues = issueFilters?.filters?.[key] ?? [];
|
||||
newValues = newValues.filter((val) => val !== value);
|
||||
|
||||
updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, {
|
||||
updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, {
|
||||
[key]: newValues,
|
||||
});
|
||||
};
|
||||
|
|
@ -60,7 +63,7 @@ export const ProjectAppliedFiltersRoot: React.FC = observer(() => {
|
|||
Object.keys(userFilters ?? {}).forEach((key) => {
|
||||
newFilters[key as keyof IIssueFilterOptions] = null;
|
||||
});
|
||||
updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, { ...newFilters });
|
||||
updateFilters(workspaceSlug.toString(), projectId.toString(), EIssueFilterType.FILTERS, { ...newFilters });
|
||||
};
|
||||
|
||||
// return if no filters are applied
|
||||
|
|
@ -73,8 +76,7 @@ export const ProjectAppliedFiltersRoot: React.FC = observer(() => {
|
|||
handleClearAllFilters={handleClearAllFilters}
|
||||
handleRemoveFilter={handleRemoveFilter}
|
||||
labels={projectLabels ?? []}
|
||||
members={projectMembers?.map((m) => m.member)}
|
||||
states={projectStateStore.states?.[projectId?.toString() ?? ""]}
|
||||
states={projectStates}
|
||||
/>
|
||||
{isEditingAllowed && (
|
||||
<SaveFilterView workspaceSlug={workspaceSlug} projectId={projectId} filterParams={appliedFilters} />
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useIssues, useLabel, useProjectState, useProjectView } from "hooks/store";
|
||||
// components
|
||||
import { AppliedFiltersList } from "components/issues";
|
||||
// ui
|
||||
|
|
@ -9,27 +9,28 @@ import { Button } from "@plane/ui";
|
|||
// helpers
|
||||
import { areFiltersDifferent } from "helpers/filter.helper";
|
||||
// types
|
||||
import { IIssueFilterOptions } from "types";
|
||||
import { EFilterType } from "store/issues/types";
|
||||
import { IIssueFilterOptions } from "@plane/types";
|
||||
import { EIssueFilterType, EIssuesStoreType } from "constants/issue";
|
||||
|
||||
export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, viewId } = router.query as {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
viewId: string;
|
||||
};
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
projectLabel: { projectLabels },
|
||||
projectState: projectStateStore,
|
||||
projectMember: { projectMembers },
|
||||
projectViews: projectViewsStore,
|
||||
viewIssuesFilter: { issueFilters, updateFilters },
|
||||
} = useMobxStore();
|
||||
|
||||
const viewDetails = viewId ? projectViewsStore.viewDetails[viewId.toString()] : undefined;
|
||||
|
||||
issuesFilter: { issueFilters, updateFilters },
|
||||
} = useIssues(EIssuesStoreType.PROJECT_VIEW);
|
||||
const {
|
||||
project: { projectLabels },
|
||||
} = useLabel();
|
||||
const { projectStates } = useProjectState();
|
||||
const { getViewById, updateView } = useProjectView();
|
||||
// derived values
|
||||
const viewDetails = viewId ? getViewById(viewId.toString()) : null;
|
||||
const userFilters = issueFilters?.filters;
|
||||
// filters whose value not null or empty array
|
||||
const appliedFilters: IIssueFilterOptions = {};
|
||||
|
|
@ -42,30 +43,18 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
|
|||
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
if (!value) {
|
||||
updateFilters(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
EFilterType.FILTERS,
|
||||
{
|
||||
[key]: null,
|
||||
},
|
||||
viewId
|
||||
);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, {
|
||||
[key]: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let newValues = issueFilters?.filters?.[key] ?? [];
|
||||
newValues = newValues.filter((val) => val !== value);
|
||||
|
||||
updateFilters(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
EFilterType.FILTERS,
|
||||
{
|
||||
[key]: newValues,
|
||||
},
|
||||
viewId
|
||||
);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, {
|
||||
[key]: newValues,
|
||||
});
|
||||
};
|
||||
|
||||
const handleClearAllFilters = () => {
|
||||
|
|
@ -74,7 +63,7 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
|
|||
Object.keys(userFilters ?? {}).forEach((key) => {
|
||||
newFilters[key as keyof IIssueFilterOptions] = null;
|
||||
});
|
||||
updateFilters(workspaceSlug, projectId, EFilterType.FILTERS, { ...newFilters }, viewId);
|
||||
updateFilters(workspaceSlug, projectId, EIssueFilterType.FILTERS, { ...newFilters }, viewId);
|
||||
};
|
||||
|
||||
// return if no filters are applied
|
||||
|
|
@ -83,7 +72,7 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
|
|||
const handleUpdateView = () => {
|
||||
if (!workspaceSlug || !projectId || !viewId || !viewDetails) return;
|
||||
|
||||
projectViewsStore.updateView(workspaceSlug.toString(), projectId.toString(), viewId.toString(), {
|
||||
updateView(workspaceSlug.toString(), projectId.toString(), viewId.toString(), {
|
||||
query_data: {
|
||||
...viewDetails.query_data,
|
||||
...(appliedFilters ?? {}),
|
||||
|
|
@ -98,8 +87,7 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
|
|||
handleClearAllFilters={handleClearAllFilters}
|
||||
handleRemoveFilter={handleRemoveFilter}
|
||||
labels={projectLabels ?? []}
|
||||
members={projectMembers?.map((m) => m.member)}
|
||||
states={projectStateStore.states?.[projectId?.toString() ?? ""]}
|
||||
states={projectStates}
|
||||
/>
|
||||
|
||||
{appliedFilters &&
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { observer } from "mobx-react-lite";
|
|||
// icons
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
import { X } from "lucide-react";
|
||||
import { TStateGroups } from "types";
|
||||
import { TStateGroups } from "@plane/types";
|
||||
|
||||
type Props = {
|
||||
handleRemove: (val: string) => void;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
|
|||
import { StateGroupIcon } from "@plane/ui";
|
||||
import { X } from "lucide-react";
|
||||
// types
|
||||
import { IState } from "types";
|
||||
import { IState } from "@plane/types";
|
||||
|
||||
type Props = {
|
||||
handleRemove: (val: string) => void;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
FilterSubGroupBy,
|
||||
} from "components/issues";
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties } from "types";
|
||||
import { IIssueDisplayFilterOptions, IIssueDisplayProperties } from "@plane/types";
|
||||
import { ILayoutDisplayFiltersOptions } from "constants/issue";
|
||||
|
||||
type Props = {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
|
|||
// components
|
||||
import { FilterHeader } from "../helpers/filter-header";
|
||||
// types
|
||||
import { IIssueDisplayProperties } from "types";
|
||||
import { IIssueDisplayProperties } from "@plane/types";
|
||||
// constants
|
||||
import { ISSUE_DISPLAY_PROPERTIES } from "constants/issue";
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
|
|||
// components
|
||||
import { FilterOption } from "components/issues";
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, TIssueExtraOptions } from "types";
|
||||
import { IIssueDisplayFilterOptions, TIssueExtraOptions } from "@plane/types";
|
||||
// constants
|
||||
import { ISSUE_EXTRA_OPTIONS } from "constants/issue";
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
|
|||
// components
|
||||
import { FilterHeader, FilterOption } from "components/issues";
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, TIssueGroupByOptions } from "types";
|
||||
import { IIssueDisplayFilterOptions, TIssueGroupByOptions } from "@plane/types";
|
||||
// constants
|
||||
import { ISSUE_GROUP_BY_OPTIONS } from "constants/issue";
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
|
|||
// components
|
||||
import { FilterHeader, FilterOption } from "components/issues";
|
||||
// types
|
||||
import { TIssueTypeFilters } from "types";
|
||||
import { TIssueTypeFilters } from "@plane/types";
|
||||
// constants
|
||||
import { ISSUE_FILTER_OPTIONS } from "constants/issue";
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
|
|||
// components
|
||||
import { FilterHeader, FilterOption } from "components/issues";
|
||||
// types
|
||||
import { TIssueOrderByOptions } from "types";
|
||||
import { TIssueOrderByOptions } from "@plane/types";
|
||||
// constants
|
||||
import { ISSUE_ORDER_BY_OPTIONS } from "constants/issue";
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
|
|||
// components
|
||||
import { FilterHeader, FilterOption } from "components/issues";
|
||||
// types
|
||||
import { IIssueDisplayFilterOptions, TIssueGroupByOptions } from "types";
|
||||
import { IIssueDisplayFilterOptions, TIssueGroupByOptions } from "@plane/types";
|
||||
// constants
|
||||
import { ISSUE_GROUP_BY_OPTIONS } from "constants/issue";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +1,31 @@
|
|||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useMember } from "hooks/store";
|
||||
// components
|
||||
import { FilterHeader, FilterOption } from "components/issues";
|
||||
// ui
|
||||
import { Avatar, Loader } from "@plane/ui";
|
||||
// types
|
||||
import { IUserLite } from "types";
|
||||
|
||||
type Props = {
|
||||
appliedFilters: string[] | null;
|
||||
handleUpdate: (val: string) => void;
|
||||
members: IUserLite[] | undefined;
|
||||
memberIds: string[] | undefined;
|
||||
searchQuery: string;
|
||||
};
|
||||
|
||||
export const FilterAssignees: React.FC<Props> = (props) => {
|
||||
const { appliedFilters, handleUpdate, members, searchQuery } = props;
|
||||
|
||||
export const FilterAssignees: React.FC<Props> = observer((props: Props) => {
|
||||
const { appliedFilters, handleUpdate, memberIds, searchQuery } = props;
|
||||
// states
|
||||
const [itemsToRender, setItemsToRender] = useState(5);
|
||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||
// store hooks
|
||||
const { getUserDetails } = useMember();
|
||||
|
||||
const appliedFiltersCount = appliedFilters?.length ?? 0;
|
||||
|
||||
const filteredOptions = members?.filter((member) =>
|
||||
member.display_name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
const filteredOptions = memberIds?.filter((memberId) =>
|
||||
getUserDetails(memberId)?.display_name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
const handleViewToggle = () => {
|
||||
|
|
@ -44,15 +47,20 @@ export const FilterAssignees: React.FC<Props> = (props) => {
|
|||
{filteredOptions ? (
|
||||
filteredOptions.length > 0 ? (
|
||||
<>
|
||||
{filteredOptions.slice(0, itemsToRender).map((member) => (
|
||||
<FilterOption
|
||||
key={`assignees-${member.id}`}
|
||||
isChecked={appliedFilters?.includes(member.id) ? true : false}
|
||||
onClick={() => handleUpdate(member.id)}
|
||||
icon={<Avatar name={member.display_name} src={member.avatar} showTooltip={false} size="md" />}
|
||||
title={member.display_name}
|
||||
/>
|
||||
))}
|
||||
{filteredOptions.slice(0, itemsToRender).map((memberId) => {
|
||||
const member = getUserDetails(memberId);
|
||||
|
||||
if (!member) return null;
|
||||
return (
|
||||
<FilterOption
|
||||
key={`assignees-${member.id}`}
|
||||
isChecked={appliedFilters?.includes(member.id) ? true : false}
|
||||
onClick={() => handleUpdate(member.id)}
|
||||
icon={<Avatar name={member.display_name} src={member.avatar} showTooltip={false} size="md" />}
|
||||
title={member.display_name}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{filteredOptions.length > 5 && (
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -77,4 +85,4 @@ export const FilterAssignees: React.FC<Props> = (props) => {
|
|||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,29 +1,31 @@
|
|||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useMember } from "hooks/store";
|
||||
// components
|
||||
import { FilterHeader, FilterOption } from "components/issues";
|
||||
// ui
|
||||
import { Avatar, Loader } from "@plane/ui";
|
||||
// types
|
||||
import { IUserLite } from "types";
|
||||
|
||||
type Props = {
|
||||
appliedFilters: string[] | null;
|
||||
handleUpdate: (val: string) => void;
|
||||
members: IUserLite[] | undefined;
|
||||
memberIds: string[] | undefined;
|
||||
searchQuery: string;
|
||||
};
|
||||
|
||||
export const FilterCreatedBy: React.FC<Props> = (props) => {
|
||||
const { appliedFilters, handleUpdate, members, searchQuery } = props;
|
||||
|
||||
export const FilterCreatedBy: React.FC<Props> = observer((props: Props) => {
|
||||
const { appliedFilters, handleUpdate, memberIds, searchQuery } = props;
|
||||
// states
|
||||
const [itemsToRender, setItemsToRender] = useState(5);
|
||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||
// store hooks
|
||||
const { getUserDetails } = useMember();
|
||||
|
||||
const appliedFiltersCount = appliedFilters?.length ?? 0;
|
||||
|
||||
const filteredOptions = members?.filter((member) =>
|
||||
member.display_name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
const filteredOptions = memberIds?.filter((memberId) =>
|
||||
getUserDetails(memberId)?.display_name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
const appliedFiltersCount = appliedFilters?.length ?? 0;
|
||||
|
||||
const handleViewToggle = () => {
|
||||
if (!filteredOptions) return;
|
||||
|
|
@ -44,15 +46,20 @@ export const FilterCreatedBy: React.FC<Props> = (props) => {
|
|||
{filteredOptions ? (
|
||||
filteredOptions.length > 0 ? (
|
||||
<>
|
||||
{filteredOptions.slice(0, itemsToRender).map((member) => (
|
||||
<FilterOption
|
||||
key={`created-by-${member.id}`}
|
||||
isChecked={appliedFilters?.includes(member.id) ? true : false}
|
||||
onClick={() => handleUpdate(member.id)}
|
||||
icon={<Avatar name={member.display_name} src={member.avatar} size="md" />}
|
||||
title={member.display_name}
|
||||
/>
|
||||
))}
|
||||
{filteredOptions.slice(0, itemsToRender).map((memberId) => {
|
||||
const member = getUserDetails(memberId);
|
||||
|
||||
if (!member) return null;
|
||||
return (
|
||||
<FilterOption
|
||||
key={`created-by-${member.id}`}
|
||||
isChecked={appliedFilters?.includes(member.id) ? true : false}
|
||||
onClick={() => handleUpdate(member.id)}
|
||||
icon={<Avatar name={member.display_name} src={member.avatar} size="md" />}
|
||||
title={member.display_name}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{filteredOptions.length > 5 && (
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -77,4 +84,4 @@ export const FilterCreatedBy: React.FC<Props> = (props) => {
|
|||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Search, X } from "lucide-react";
|
||||
// components
|
||||
|
|
@ -15,7 +15,7 @@ import {
|
|||
FilterTargetDate,
|
||||
} from "components/issues";
|
||||
// types
|
||||
import { IIssueFilterOptions, IIssueLabel, IProject, IState, IUserLite } from "types";
|
||||
import { IIssueFilterOptions, IIssueLabel, IState } from "@plane/types";
|
||||
// constants
|
||||
import { ILayoutDisplayFiltersOptions } from "constants/issue";
|
||||
|
||||
|
|
@ -24,14 +24,13 @@ type Props = {
|
|||
handleFiltersUpdate: (key: keyof IIssueFilterOptions, value: string | string[]) => void;
|
||||
layoutDisplayFiltersOptions: ILayoutDisplayFiltersOptions | undefined;
|
||||
labels?: IIssueLabel[] | undefined;
|
||||
members?: IUserLite[] | undefined;
|
||||
projects?: IProject[] | undefined;
|
||||
memberIds?: string[] | undefined;
|
||||
states?: IState[] | undefined;
|
||||
};
|
||||
|
||||
export const FilterSelection: React.FC<Props> = observer((props) => {
|
||||
const { filters, handleFiltersUpdate, layoutDisplayFiltersOptions, labels, members, projects, states } = props;
|
||||
|
||||
const { filters, handleFiltersUpdate, layoutDisplayFiltersOptions, labels, memberIds, states } = props;
|
||||
// states
|
||||
const [filtersSearchQuery, setFiltersSearchQuery] = useState("");
|
||||
|
||||
const isFilterEnabled = (filter: keyof IIssueFilterOptions) => layoutDisplayFiltersOptions?.filters.includes(filter);
|
||||
|
|
@ -97,7 +96,7 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
|
|||
<FilterAssignees
|
||||
appliedFilters={filters.assignees ?? null}
|
||||
handleUpdate={(val) => handleFiltersUpdate("assignees", val)}
|
||||
members={members}
|
||||
memberIds={memberIds}
|
||||
searchQuery={filtersSearchQuery}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -109,7 +108,7 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
|
|||
<FilterMentions
|
||||
appliedFilters={filters.mentions ?? null}
|
||||
handleUpdate={(val) => handleFiltersUpdate("mentions", val)}
|
||||
members={members}
|
||||
memberIds={memberIds}
|
||||
searchQuery={filtersSearchQuery}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -121,7 +120,7 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
|
|||
<FilterCreatedBy
|
||||
appliedFilters={filters.created_by ?? null}
|
||||
handleUpdate={(val) => handleFiltersUpdate("created_by", val)}
|
||||
members={members}
|
||||
memberIds={memberIds}
|
||||
searchQuery={filtersSearchQuery}
|
||||
/>
|
||||
</div>
|
||||
|
|
@ -144,7 +143,6 @@ export const FilterSelection: React.FC<Props> = observer((props) => {
|
|||
<div className="py-2">
|
||||
<FilterProjects
|
||||
appliedFilters={filters.project ?? null}
|
||||
projects={projects}
|
||||
handleUpdate={(val) => handleFiltersUpdate("project", val)}
|
||||
searchQuery={filtersSearchQuery}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { FilterHeader, FilterOption } from "components/issues";
|
|||
// ui
|
||||
import { Loader } from "@plane/ui";
|
||||
// types
|
||||
import { IIssueLabel } from "types";
|
||||
import { IIssueLabel } from "@plane/types";
|
||||
|
||||
const LabelIcons = ({ color }: { color: string }) => (
|
||||
<span className="h-2.5 w-2.5 rounded-full" style={{ backgroundColor: color }} />
|
||||
|
|
|
|||
|
|
@ -1,28 +1,31 @@
|
|||
import React, { useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useMember } from "hooks/store";
|
||||
// components
|
||||
import { FilterHeader, FilterOption } from "components/issues";
|
||||
// ui
|
||||
import { Loader, Avatar } from "@plane/ui";
|
||||
// types
|
||||
import { IUserLite } from "types";
|
||||
|
||||
type Props = {
|
||||
appliedFilters: string[] | null;
|
||||
handleUpdate: (val: string) => void;
|
||||
members: IUserLite[] | undefined;
|
||||
memberIds: string[] | undefined;
|
||||
searchQuery: string;
|
||||
};
|
||||
|
||||
export const FilterMentions: React.FC<Props> = (props) => {
|
||||
const { appliedFilters, handleUpdate, members, searchQuery } = props;
|
||||
|
||||
export const FilterMentions: React.FC<Props> = observer((props: Props) => {
|
||||
const { appliedFilters, handleUpdate, memberIds, searchQuery } = props;
|
||||
// states
|
||||
const [itemsToRender, setItemsToRender] = useState(5);
|
||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||
// store hooks
|
||||
const { getUserDetails } = useMember();
|
||||
|
||||
const appliedFiltersCount = appliedFilters?.length ?? 0;
|
||||
|
||||
const filteredOptions = members?.filter((member) =>
|
||||
member.display_name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
const filteredOptions = memberIds?.filter((memberId) =>
|
||||
getUserDetails(memberId)?.display_name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
const handleViewToggle = () => {
|
||||
|
|
@ -44,15 +47,20 @@ export const FilterMentions: React.FC<Props> = (props) => {
|
|||
{filteredOptions ? (
|
||||
filteredOptions.length > 0 ? (
|
||||
<>
|
||||
{filteredOptions.slice(0, itemsToRender).map((member) => (
|
||||
<FilterOption
|
||||
key={`mentions-${member.id}`}
|
||||
isChecked={appliedFilters?.includes(member.id) ? true : false}
|
||||
onClick={() => handleUpdate(member.id)}
|
||||
icon={<Avatar name={member?.display_name} src={member?.avatar} showTooltip={false} size={"md"} />}
|
||||
title={member.display_name}
|
||||
/>
|
||||
))}
|
||||
{filteredOptions.slice(0, itemsToRender).map((memberId) => {
|
||||
const member = getUserDetails(memberId);
|
||||
|
||||
if (!member) return null;
|
||||
return (
|
||||
<FilterOption
|
||||
key={`mentions-${member.id}`}
|
||||
isChecked={appliedFilters?.includes(member.id) ? true : false}
|
||||
onClick={() => handleUpdate(member.id)}
|
||||
icon={<Avatar name={member?.display_name} src={member?.avatar} showTooltip={false} size={"md"} />}
|
||||
title={member.display_name}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{filteredOptions.length > 5 && (
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -77,4 +85,4 @@ export const FilterMentions: React.FC<Props> = (props) => {
|
|||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,28 +2,29 @@ import React, { useState } from "react";
|
|||
|
||||
// components
|
||||
import { FilterHeader, FilterOption } from "components/issues";
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
// ui
|
||||
import { Loader } from "@plane/ui";
|
||||
// helpers
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// types
|
||||
import { IProject } from "types";
|
||||
|
||||
type Props = {
|
||||
appliedFilters: string[] | null;
|
||||
handleUpdate: (val: string) => void;
|
||||
projects: IProject[] | undefined;
|
||||
searchQuery: string;
|
||||
};
|
||||
|
||||
export const FilterProjects: React.FC<Props> = (props) => {
|
||||
const { appliedFilters, handleUpdate, projects, searchQuery } = props;
|
||||
|
||||
const { appliedFilters, handleUpdate, searchQuery } = props;
|
||||
// states
|
||||
const [itemsToRender, setItemsToRender] = useState(5);
|
||||
const [previewEnabled, setPreviewEnabled] = useState(true);
|
||||
|
||||
// store
|
||||
const { getProjectById, workspaceProjectIds } = useProject();
|
||||
// derived values
|
||||
const projects = workspaceProjectIds?.map((projectId) => getProjectById(projectId)!) ?? null;
|
||||
const appliedFiltersCount = appliedFilters?.length ?? 0;
|
||||
|
||||
const filteredOptions = projects?.filter((project) => project.name.toLowerCase().includes(searchQuery.toLowerCase()));
|
||||
|
||||
const handleViewToggle = () => {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { FilterHeader, FilterOption } from "components/issues";
|
|||
// ui
|
||||
import { Loader, StateGroupIcon } from "@plane/ui";
|
||||
// types
|
||||
import { IState } from "types";
|
||||
import { IState } from "@plane/types";
|
||||
|
||||
type Props = {
|
||||
appliedFilters: string[] | null;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import React from "react";
|
|||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// types
|
||||
import { TIssueLayouts } from "types";
|
||||
import { TIssueLayouts } from "@plane/types";
|
||||
// constants
|
||||
import { ISSUE_LAYOUTS } from "constants/issue";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import React, { useCallback } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useIssues, useUser } from "hooks/store";
|
||||
// components
|
||||
import { IssueGanttBlock, IssuePeekOverview } from "components/issues";
|
||||
import {
|
||||
|
|
@ -12,75 +12,60 @@ import {
|
|||
IssueGanttSidebar,
|
||||
} from "components/gantt-chart";
|
||||
// types
|
||||
import { IIssueUnGroupedStructure } from "store/issue";
|
||||
import { IIssue } from "types";
|
||||
import {
|
||||
ICycleIssuesFilterStore,
|
||||
ICycleIssuesStore,
|
||||
IModuleIssuesFilterStore,
|
||||
IModuleIssuesStore,
|
||||
IProjectIssuesFilterStore,
|
||||
IProjectIssuesStore,
|
||||
IViewIssuesFilterStore,
|
||||
IViewIssuesStore,
|
||||
} from "store/issues";
|
||||
import { TUnGroupedIssues } from "store/issues/types";
|
||||
import { TIssue, TUnGroupedIssues } from "@plane/types";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { ICycleIssues, ICycleIssuesFilter } from "store/issue/cycle";
|
||||
import { IModuleIssues, IModuleIssuesFilter } from "store/issue/module";
|
||||
import { IProjectIssues, IProjectIssuesFilter } from "store/issue/project";
|
||||
import { IProjectViewIssues, IProjectViewIssuesFilter } from "store/issue/project-views";
|
||||
import { EIssueActions } from "../types";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
|
||||
interface IBaseGanttRoot {
|
||||
issueFiltersStore:
|
||||
| IProjectIssuesFilterStore
|
||||
| IModuleIssuesFilterStore
|
||||
| ICycleIssuesFilterStore
|
||||
| IViewIssuesFilterStore;
|
||||
issueStore: IProjectIssuesStore | IModuleIssuesStore | ICycleIssuesStore | IViewIssuesStore;
|
||||
issueFiltersStore: IProjectIssuesFilter | IModuleIssuesFilter | ICycleIssuesFilter | IProjectViewIssuesFilter;
|
||||
issueStore: IProjectIssues | IModuleIssues | ICycleIssues | IProjectViewIssues;
|
||||
viewId?: string;
|
||||
issueActions: {
|
||||
[EIssueActions.DELETE]: (issue: IIssue) => Promise<void>;
|
||||
[EIssueActions.UPDATE]?: (issue: IIssue) => Promise<void>;
|
||||
[EIssueActions.REMOVE]?: (issue: IIssue) => Promise<void>;
|
||||
[EIssueActions.DELETE]: (issue: TIssue) => Promise<void>;
|
||||
[EIssueActions.UPDATE]?: (issue: TIssue) => Promise<void>;
|
||||
[EIssueActions.REMOVE]?: (issue: TIssue) => Promise<void>;
|
||||
};
|
||||
}
|
||||
|
||||
export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGanttRoot) => {
|
||||
const { issueFiltersStore, issueStore, viewId, issueActions } = props;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, peekIssueId, peekProjectId } = router.query;
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
user: { currentProjectRole },
|
||||
} = useMobxStore();
|
||||
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { issueMap } = useIssues();
|
||||
const appliedDisplayFilters = issueFiltersStore.issueFilters?.displayFilters;
|
||||
|
||||
const issuesResponse = issueStore.getIssues;
|
||||
const issueIds = (issueStore.getIssuesIds ?? []) as TUnGroupedIssues;
|
||||
const issueIds = (issueStore.groupedIssueIds ?? []) as TUnGroupedIssues;
|
||||
const { enableIssueCreation } = issueStore?.viewFlags || {};
|
||||
|
||||
const issues = issueIds.map((id) => issuesResponse?.[id]);
|
||||
const issues = issueIds.map((id) => issueMap?.[id]);
|
||||
|
||||
const updateIssueBlockStructure = async (issue: IIssue, data: IBlockUpdateData) => {
|
||||
const updateIssueBlockStructure = async (issue: TIssue, data: IBlockUpdateData) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
const payload: any = { ...data };
|
||||
if (data.sort_order) payload.sort_order = data.sort_order.newSortOrder;
|
||||
|
||||
await issueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, payload, viewId);
|
||||
await issueStore.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, payload, viewId);
|
||||
};
|
||||
|
||||
const handleIssues = useCallback(
|
||||
async (issue: IIssue, action: EIssueActions) => {
|
||||
async (issue: TIssue, action: EIssueActions) => {
|
||||
if (issueActions[action]) {
|
||||
await issueActions[action]!(issue);
|
||||
}
|
||||
},
|
||||
[issueActions]
|
||||
);
|
||||
|
||||
const isAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||
const isAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -89,9 +74,9 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
|||
border={false}
|
||||
title="Issues"
|
||||
loaderTitle="Issues"
|
||||
blocks={issues ? renderIssueBlocksStructure(issues as IIssueUnGroupedStructure) : null}
|
||||
blocks={issues ? renderIssueBlocksStructure(issues as TIssue[]) : null}
|
||||
blockUpdateHandler={updateIssueBlockStructure}
|
||||
blockToRender={(data: IIssue) => <IssueGanttBlock data={data} />}
|
||||
blockToRender={(data: TIssue) => <IssueGanttBlock data={data} />}
|
||||
sidebarToRender={(props) => (
|
||||
<IssueGanttSidebar
|
||||
{...props}
|
||||
|
|
@ -113,7 +98,7 @@ export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGan
|
|||
projectId={peekProjectId.toString()}
|
||||
issueId={peekIssueId.toString()}
|
||||
handleIssue={async (issueToUpdate, action) => {
|
||||
await handleIssues(issueToUpdate as IIssue, action);
|
||||
await handleIssues(issueToUpdate as TIssue, action);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -4,28 +4,29 @@ import { Tooltip, StateGroupIcon } from "@plane/ui";
|
|||
// helpers
|
||||
import { renderFormattedDate } from "helpers/date-time.helper";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
import { TIssue } from "@plane/types";
|
||||
import { useProject, useProjectState } from "hooks/store";
|
||||
|
||||
export const IssueGanttBlock = ({ data }: { data: IIssue }) => {
|
||||
export const IssueGanttBlock = ({ data }: { data: TIssue }) => {
|
||||
const router = useRouter();
|
||||
// hooks
|
||||
const { getProjectStates } = useProjectState();
|
||||
|
||||
const handleIssuePeekOverview = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
const handleIssuePeekOverview = () => {
|
||||
const { query } = router;
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
const issueUrl = `/${data?.workspace_detail.slug}/projects/${data?.project_detail.id}/issues/${data?.id}`;
|
||||
window.open(issueUrl, "_blank"); // Open link in a new tab
|
||||
} else {
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: { ...query, peekIssueId: data?.id, peekProjectId: data?.project },
|
||||
});
|
||||
}
|
||||
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: { ...query, peekIssueId: data?.id, peekProjectId: data?.project_id },
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative flex h-full w-full cursor-pointer items-center rounded"
|
||||
style={{ backgroundColor: data?.state_detail?.color }}
|
||||
style={{
|
||||
backgroundColor: getProjectStates(data?.project_id)?.find((state) => state?.id == data?.state_id)?.color,
|
||||
}}
|
||||
onClick={handleIssuePeekOverview}
|
||||
>
|
||||
<div className="absolute left-0 top-0 h-full w-full bg-custom-background-100/50" />
|
||||
|
|
@ -47,23 +48,31 @@ export const IssueGanttBlock = ({ data }: { data: IIssue }) => {
|
|||
};
|
||||
|
||||
// rendering issues on gantt sidebar
|
||||
export const IssueGanttSidebarBlock = ({ data }: { data: IIssue }) => {
|
||||
export const IssueGanttSidebarBlock = ({ data }: { data: TIssue }) => {
|
||||
const router = useRouter();
|
||||
// hooks
|
||||
const { getProjectStates } = useProjectState();
|
||||
const { getProjectById } = useProject();
|
||||
|
||||
const handleIssuePeekOverview = () => {
|
||||
const { query } = router;
|
||||
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: { ...query, peekIssueId: data?.id, peekProjectId: data?.project },
|
||||
query: { ...query, peekIssueId: data?.id, peekProjectId: data?.project_id },
|
||||
});
|
||||
};
|
||||
|
||||
const currentStateDetails =
|
||||
getProjectStates(data?.project_id)?.find((state) => state?.id == data?.state_id) || undefined;
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full w-full cursor-pointer items-center gap-2" onClick={handleIssuePeekOverview}>
|
||||
<StateGroupIcon stateGroup={data?.state_detail?.group} color={data?.state_detail?.color} />
|
||||
{currentStateDetails != undefined && (
|
||||
<StateGroupIcon stateGroup={currentStateDetails?.group} color={currentStateDetails?.color} />
|
||||
)}
|
||||
<div className="flex-shrink-0 text-xs text-custom-text-300">
|
||||
{data?.project_detail?.identifier} {data?.sequence_id}
|
||||
{getProjectById(data?.project_id)?.identifier} {data?.sequence_id}
|
||||
</div>
|
||||
<Tooltip tooltipHeading="Title" tooltipContent={data.name}>
|
||||
<span className="flex-grow truncate text-sm font-medium">{data?.name}</span>
|
||||
|
|
|
|||
|
|
@ -1,55 +1,47 @@
|
|||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/router";
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { useCycle, useIssues } from "hooks/store";
|
||||
// components
|
||||
import { BaseGanttRoot } from "./base-gantt-root";
|
||||
import { useRouter } from "next/router";
|
||||
// types
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
import { EIssueActions } from "../types";
|
||||
import { IIssue } from "types";
|
||||
import { TIssue } from "@plane/types";
|
||||
|
||||
export const CycleGanttLayout: React.FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { cycleId, workspaceSlug } = router.query;
|
||||
|
||||
const {
|
||||
cycleIssues: cycleIssueStore,
|
||||
cycleIssuesFilter: cycleIssueFilterStore,
|
||||
cycle: { fetchCycleWithId },
|
||||
} = useMobxStore();
|
||||
const { workspaceSlug, cycleId } = router.query;
|
||||
// store hooks
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
|
||||
const { fetchCycleDetails } = useCycle();
|
||||
|
||||
const issueActions = {
|
||||
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||
[EIssueActions.UPDATE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !cycleId) return;
|
||||
|
||||
await cycleIssueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue, cycleId.toString());
|
||||
fetchCycleWithId(workspaceSlug.toString(), issue.project, cycleId.toString());
|
||||
await issues.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, issue, cycleId.toString());
|
||||
fetchCycleDetails(workspaceSlug.toString(), issue.project_id, cycleId.toString());
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||
[EIssueActions.DELETE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !cycleId) return;
|
||||
|
||||
await cycleIssueStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id, cycleId.toString());
|
||||
fetchCycleWithId(workspaceSlug.toString(), issue.project, cycleId.toString());
|
||||
await issues.removeIssue(workspaceSlug.toString(), issue.project_id, issue.id, cycleId.toString());
|
||||
fetchCycleDetails(workspaceSlug.toString(), issue.project_id, cycleId.toString());
|
||||
},
|
||||
[EIssueActions.REMOVE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug || !cycleId || !issue.bridge_id) return;
|
||||
[EIssueActions.REMOVE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !cycleId || !issue.id) return;
|
||||
|
||||
await cycleIssueStore.removeIssueFromCycle(
|
||||
workspaceSlug.toString(),
|
||||
issue.project,
|
||||
cycleId.toString(),
|
||||
issue.id,
|
||||
issue.bridge_id
|
||||
);
|
||||
fetchCycleWithId(workspaceSlug.toString(), issue.project, cycleId.toString());
|
||||
await issues.removeIssueFromCycle(workspaceSlug.toString(), issue.project_id, cycleId.toString(), issue.id);
|
||||
fetchCycleDetails(workspaceSlug.toString(), issue.project_id, cycleId.toString());
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseGanttRoot
|
||||
issueActions={issueActions}
|
||||
issueFiltersStore={cycleIssueFilterStore}
|
||||
issueStore={cycleIssueStore}
|
||||
issueFiltersStore={issuesFilter}
|
||||
issueStore={issues}
|
||||
viewId={cycleId?.toString()}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,55 +1,47 @@
|
|||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/router";
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { useIssues, useModule } from "hooks/store";
|
||||
// components
|
||||
import { BaseGanttRoot } from "./base-gantt-root";
|
||||
import { useRouter } from "next/router";
|
||||
// types
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
import { EIssueActions } from "../types";
|
||||
import { IIssue } from "types";
|
||||
import { TIssue } from "@plane/types";
|
||||
|
||||
export const ModuleGanttLayout: React.FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { moduleId, workspaceSlug } = router.query;
|
||||
|
||||
const {
|
||||
moduleIssues: moduleIssueStore,
|
||||
moduleIssuesFilter: moduleIssueFilterStore,
|
||||
module: { fetchModuleDetails },
|
||||
} = useMobxStore();
|
||||
const { workspaceSlug, moduleId } = router.query;
|
||||
// store hooks
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.MODULE);
|
||||
const { fetchModuleDetails } = useModule();
|
||||
|
||||
const issueActions = {
|
||||
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||
[EIssueActions.UPDATE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !moduleId) return;
|
||||
|
||||
await moduleIssueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue, moduleId.toString());
|
||||
fetchModuleDetails(workspaceSlug.toString(), issue.project, moduleId.toString());
|
||||
await issues.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, issue, moduleId.toString());
|
||||
fetchModuleDetails(workspaceSlug.toString(), issue.project_id, moduleId.toString());
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||
[EIssueActions.DELETE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !moduleId) return;
|
||||
|
||||
await moduleIssueStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id, moduleId.toString());
|
||||
fetchModuleDetails(workspaceSlug.toString(), issue.project, moduleId.toString());
|
||||
await issues.removeIssue(workspaceSlug.toString(), issue.project_id, issue.id, moduleId.toString());
|
||||
fetchModuleDetails(workspaceSlug.toString(), issue.project_id, moduleId.toString());
|
||||
},
|
||||
[EIssueActions.REMOVE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug || !moduleId || !issue.bridge_id) return;
|
||||
[EIssueActions.REMOVE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !moduleId || !issue.id) return;
|
||||
|
||||
await moduleIssueStore.removeIssueFromModule(
|
||||
workspaceSlug.toString(),
|
||||
issue.project,
|
||||
moduleId.toString(),
|
||||
issue.id,
|
||||
issue.bridge_id
|
||||
);
|
||||
fetchModuleDetails(workspaceSlug.toString(), issue.project, moduleId.toString());
|
||||
await issues.removeIssueFromModule(workspaceSlug.toString(), issue.project_id, moduleId.toString(), issue.id);
|
||||
fetchModuleDetails(workspaceSlug.toString(), issue.project_id, moduleId.toString());
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseGanttRoot
|
||||
issueActions={issueActions}
|
||||
issueFiltersStore={moduleIssueFilterStore}
|
||||
issueStore={moduleIssueStore}
|
||||
issueFiltersStore={issuesFilter}
|
||||
issueStore={issues}
|
||||
viewId={moduleId?.toString()}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,35 +2,32 @@ import React from "react";
|
|||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { useIssues } from "hooks/store";
|
||||
// components
|
||||
import { BaseGanttRoot } from "./base-gantt-root";
|
||||
// types
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
import { EIssueActions } from "../types";
|
||||
import { IIssue } from "types";
|
||||
import { TIssue } from "@plane/types";
|
||||
|
||||
export const GanttLayout: React.FC = observer(() => {
|
||||
const { projectIssues: projectIssuesStore, projectIssuesFilter: projectIssueFiltersStore } = useMobxStore();
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// store hooks
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT);
|
||||
|
||||
const issueActions = {
|
||||
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||
[EIssueActions.UPDATE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
await projectIssuesStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
||||
await issues.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, issue);
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||
[EIssueActions.DELETE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
await projectIssuesStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id);
|
||||
await issues.removeIssue(workspaceSlug.toString(), issue.project_id, issue.id);
|
||||
},
|
||||
};
|
||||
return (
|
||||
<BaseGanttRoot
|
||||
issueActions={issueActions}
|
||||
issueFiltersStore={projectIssueFiltersStore}
|
||||
issueStore={projectIssuesStore}
|
||||
/>
|
||||
);
|
||||
|
||||
return <BaseGanttRoot issueFiltersStore={issuesFilter} issueStore={issues} issueActions={issueActions} />;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,35 +1,31 @@
|
|||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/router";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useIssues } from "hooks/store";
|
||||
// components
|
||||
import { BaseGanttRoot } from "./base-gantt-root";
|
||||
// types
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
import { EIssueActions } from "../types";
|
||||
import { IIssue } from "types";
|
||||
import { TIssue } from "@plane/types";
|
||||
|
||||
export const ProjectViewGanttLayout: React.FC = observer(() => {
|
||||
const { viewIssues: projectIssueViewStore, viewIssuesFilter: projectIssueViewFiltersStore } = useMobxStore();
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT_VIEW);
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const issueActions = {
|
||||
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||
[EIssueActions.UPDATE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
await projectIssueViewStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
||||
await issues.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, issue);
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||
[EIssueActions.DELETE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
await projectIssueViewStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id);
|
||||
await issues.removeIssue(workspaceSlug.toString(), issue.project_id, issue.id);
|
||||
},
|
||||
};
|
||||
return (
|
||||
<BaseGanttRoot
|
||||
issueActions={issueActions}
|
||||
issueFiltersStore={projectIssueViewFiltersStore}
|
||||
issueStore={projectIssueViewStore}
|
||||
/>
|
||||
);
|
||||
|
||||
return <BaseGanttRoot issueFiltersStore={issuesFilter} issueStore={issues} issueActions={issueActions} />;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,32 +3,30 @@ import { useRouter } from "next/router";
|
|||
import { useForm } from "react-hook-form";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useProject, useWorkspace } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import useKeypress from "hooks/use-keypress";
|
||||
import useProjectDetails from "hooks/use-project-details";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
// helpers
|
||||
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
||||
import { createIssuePayload } from "helpers/issue.helper";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
import { TIssue } from "@plane/types";
|
||||
|
||||
type Props = {
|
||||
prePopulatedData?: Partial<IIssue>;
|
||||
onSuccess?: (data: IIssue) => Promise<void> | void;
|
||||
prePopulatedData?: Partial<TIssue>;
|
||||
onSuccess?: (data: TIssue) => Promise<void> | void;
|
||||
quickAddCallback?: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: IIssue,
|
||||
data: TIssue,
|
||||
viewId?: string
|
||||
) => Promise<IIssue | undefined>;
|
||||
) => Promise<TIssue | undefined>;
|
||||
viewId?: string;
|
||||
};
|
||||
|
||||
const defaultValues: Partial<IIssue> = {
|
||||
const defaultValues: Partial<TIssue> = {
|
||||
name: "",
|
||||
};
|
||||
|
||||
|
|
@ -54,23 +52,20 @@ const Inputs = (props: any) => {
|
|||
|
||||
export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
||||
const { prePopulatedData, quickAddCallback, viewId } = props;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||
|
||||
// store
|
||||
const { workspace: workspaceStore } = useMobxStore();
|
||||
|
||||
const { projectDetails } = useProjectDetails();
|
||||
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// store hooks
|
||||
const { getWorkspaceBySlug } = useWorkspace();
|
||||
const { currentProjectDetails } = useProject();
|
||||
// form info
|
||||
const {
|
||||
reset,
|
||||
handleSubmit,
|
||||
setFocus,
|
||||
register,
|
||||
formState: { errors, isSubmitting },
|
||||
} = useForm<IIssue>({ defaultValues });
|
||||
} = useForm<TIssue>({ defaultValues });
|
||||
|
||||
// ref
|
||||
const ref = useRef<HTMLFormElement>(null);
|
||||
|
|
@ -86,7 +81,7 @@ export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
|||
const { setToastAlert } = useToast();
|
||||
|
||||
// derived values
|
||||
const workspaceDetail = workspaceStore.getWorkspaceBySlug(workspaceSlug?.toString()!);
|
||||
const workspaceDetail = getWorkspaceBySlug(workspaceSlug?.toString()!);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) reset({ ...defaultValues });
|
||||
|
|
@ -96,7 +91,7 @@ export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
|||
if (!errors) return;
|
||||
|
||||
Object.keys(errors).forEach((key) => {
|
||||
const error = errors[key as keyof IIssue];
|
||||
const error = errors[key as keyof TIssue];
|
||||
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
|
|
@ -106,13 +101,13 @@ export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
|||
});
|
||||
}, [errors, setToastAlert]);
|
||||
|
||||
const onSubmitHandler = async (formData: IIssue) => {
|
||||
const onSubmitHandler = async (formData: TIssue) => {
|
||||
if (isSubmitting || !workspaceSlug || !projectId) return;
|
||||
|
||||
// resetting the form so that user can add another issue quickly
|
||||
reset({ ...defaultValues, ...(prePopulatedData ?? {}) });
|
||||
|
||||
const payload = createIssuePayload(workspaceDetail!, projectDetails!, {
|
||||
const payload = createIssuePayload(workspaceDetail!, currentProjectDetails!, {
|
||||
...(prePopulatedData ?? {}),
|
||||
...formData,
|
||||
start_date: renderFormattedPayloadDate(new Date()),
|
||||
|
|
@ -121,7 +116,7 @@ export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
|||
|
||||
try {
|
||||
if (quickAddCallback) {
|
||||
await quickAddCallback(workspaceSlug, projectId, payload, viewId);
|
||||
await quickAddCallback(workspaceSlug.toString(), projectId.toString(), payload, viewId);
|
||||
}
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
|
|
@ -151,7 +146,7 @@ export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
|||
onSubmit={handleSubmit(onSubmitHandler)}
|
||||
>
|
||||
<div className="h-3 w-3 flex-shrink-0 rounded-full border border-custom-border-1000" />
|
||||
<h4 className="text-xs text-custom-text-400">{projectDetails?.identifier ?? "..."}</h4>
|
||||
<h4 className="text-xs text-custom-text-400">{currentProjectDetails?.identifier ?? "..."}</h4>
|
||||
<Inputs register={register} setFocus={setFocus} />
|
||||
</form>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -2,72 +2,50 @@ import { FC, useCallback, useState } from "react";
|
|||
import { DragDropContext, DragStart, DraggableLocation, DropResult, Droppable } from "@hello-pangea/dnd";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { Spinner } from "@plane/ui";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
import { TIssue } from "@plane/types";
|
||||
import { EIssueActions } from "../types";
|
||||
import {
|
||||
ICycleIssuesFilterStore,
|
||||
ICycleIssuesStore,
|
||||
IModuleIssuesFilterStore,
|
||||
IModuleIssuesStore,
|
||||
IProfileIssuesFilterStore,
|
||||
IProfileIssuesStore,
|
||||
IProjectDraftIssuesStore,
|
||||
IProjectIssuesFilterStore,
|
||||
IProjectIssuesStore,
|
||||
IViewIssuesFilterStore,
|
||||
IViewIssuesStore,
|
||||
} from "store/issues";
|
||||
import { IQuickActionProps } from "../list/list-view-types";
|
||||
import { IIssueKanBanViewStore } from "store/issue";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// constants
|
||||
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
|
||||
import { IProjectIssues, IProjectIssuesFilter } from "store/issue/project";
|
||||
//components
|
||||
import { KanBan } from "./default";
|
||||
import { KanBanSwimLanes } from "./swimlanes";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { DeleteIssueModal, IssuePeekOverview } from "components/issues";
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { useIssues } from "hooks/store/use-issues";
|
||||
import { handleDragDrop } from "./utils";
|
||||
import { IssueKanBanViewStore } from "store/issue/issue_kanban_view.store";
|
||||
import { ICycleIssues, ICycleIssuesFilter } from "store/issue/cycle";
|
||||
import { IDraftIssues, IDraftIssuesFilter } from "store/issue/draft";
|
||||
import { IProfileIssues, IProfileIssuesFilter } from "store/issue/profile";
|
||||
import { IModuleIssues, IModuleIssuesFilter } from "store/issue/module";
|
||||
import { IProjectViewIssues, IProjectViewIssuesFilter } from "store/issue/project-views";
|
||||
import { TCreateModalStoreTypes } from "constants/issue";
|
||||
|
||||
export interface IBaseKanBanLayout {
|
||||
issueStore:
|
||||
| IProjectIssuesStore
|
||||
| IModuleIssuesStore
|
||||
| ICycleIssuesStore
|
||||
| IViewIssuesStore
|
||||
| IProjectDraftIssuesStore
|
||||
| IProfileIssuesStore;
|
||||
issuesFilterStore:
|
||||
| IProjectIssuesFilterStore
|
||||
| IModuleIssuesFilterStore
|
||||
| ICycleIssuesFilterStore
|
||||
| IViewIssuesFilterStore
|
||||
| IProfileIssuesFilterStore;
|
||||
kanbanViewStore: IIssueKanBanViewStore;
|
||||
issues: IProjectIssues | ICycleIssues | IDraftIssues | IModuleIssues | IProjectViewIssues | IProfileIssues;
|
||||
issuesFilter:
|
||||
| IProjectIssuesFilter
|
||||
| IModuleIssuesFilter
|
||||
| ICycleIssuesFilter
|
||||
| IDraftIssuesFilter
|
||||
| IProjectViewIssuesFilter
|
||||
| IProfileIssuesFilter;
|
||||
QuickActions: FC<IQuickActionProps>;
|
||||
issueActions: {
|
||||
[EIssueActions.DELETE]: (issue: IIssue) => Promise<void>;
|
||||
[EIssueActions.UPDATE]?: (issue: IIssue) => Promise<void>;
|
||||
[EIssueActions.REMOVE]?: (issue: IIssue) => Promise<void>;
|
||||
[EIssueActions.DELETE]: (issue: TIssue) => Promise<void>;
|
||||
[EIssueActions.UPDATE]?: (issue: TIssue) => Promise<void>;
|
||||
[EIssueActions.REMOVE]?: (issue: TIssue) => Promise<void>;
|
||||
};
|
||||
showLoader?: boolean;
|
||||
viewId?: string;
|
||||
currentStore?: EProjectStore;
|
||||
handleDragDrop?: (
|
||||
source: any,
|
||||
destination: any,
|
||||
subGroupBy: string | null,
|
||||
groupBy: string | null,
|
||||
issues: any,
|
||||
issueWithIds: any
|
||||
) => Promise<IIssue | undefined>;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
currentStore?: TCreateModalStoreTypes;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
||||
canEditPropertiesBasedOnProject?: (projectId: string) => boolean;
|
||||
}
|
||||
|
||||
|
|
@ -79,66 +57,60 @@ type KanbanDragState = {
|
|||
|
||||
export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBaseKanBanLayout) => {
|
||||
const {
|
||||
issueStore,
|
||||
issuesFilterStore,
|
||||
kanbanViewStore,
|
||||
issues,
|
||||
issuesFilter,
|
||||
QuickActions,
|
||||
issueActions,
|
||||
showLoader,
|
||||
viewId,
|
||||
currentStore,
|
||||
handleDragDrop,
|
||||
addIssuesToView,
|
||||
canEditPropertiesBasedOnProject,
|
||||
} = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, peekIssueId, peekProjectId } = router.query;
|
||||
// mobx store
|
||||
const { workspaceSlug, projectId, peekIssueId, peekProjectId } = router.query;
|
||||
// store hooks
|
||||
const {
|
||||
project: { workspaceProjects },
|
||||
projectLabel: { projectLabels },
|
||||
projectMember: { projectMembers },
|
||||
projectState: projectStateStore,
|
||||
user: userStore,
|
||||
} = useMobxStore();
|
||||
|
||||
// hooks
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { issueMap } = useIssues();
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const { currentProjectRole } = userStore;
|
||||
// FIXME get from filters
|
||||
const kanbanViewStore: IssueKanBanViewStore = {} as IssueKanBanViewStore;
|
||||
|
||||
const issues = issueStore?.getIssues || {};
|
||||
const issueIds = issueStore?.getIssuesIds || [];
|
||||
const issueIds = issues?.groupedIssueIds || [];
|
||||
|
||||
const displayFilters = issuesFilterStore?.issueFilters?.displayFilters;
|
||||
const displayProperties = issuesFilterStore?.issueFilters?.displayProperties || null;
|
||||
const displayFilters = issuesFilter?.issueFilters?.displayFilters;
|
||||
const displayProperties = issuesFilter?.issueFilters?.displayProperties;
|
||||
|
||||
const sub_group_by: string | null = displayFilters?.sub_group_by || null;
|
||||
|
||||
const group_by: string | null = displayFilters?.group_by || null;
|
||||
|
||||
const order_by: string | null = displayFilters?.order_by || null;
|
||||
|
||||
const userDisplayFilters = displayFilters || null;
|
||||
|
||||
const currentKanBanView: "swimlanes" | "default" = sub_group_by ? "swimlanes" : "default";
|
||||
const KanBanView = sub_group_by ? KanBanSwimLanes : KanBan;
|
||||
|
||||
const { enableInlineEditing, enableQuickAdd, enableIssueCreation } = issueStore?.viewFlags || {};
|
||||
const { enableInlineEditing, enableQuickAdd, enableIssueCreation } = issues?.viewFlags || {};
|
||||
|
||||
// states
|
||||
const [isDragStarted, setIsDragStarted] = useState<boolean>(false);
|
||||
const [dragState, setDragState] = useState<KanbanDragState>({});
|
||||
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
||||
|
||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
||||
|
||||
const canEditProperties = (projectId: string | undefined) => {
|
||||
const isEditingAllowedBasedOnProject =
|
||||
canEditPropertiesBasedOnProject && projectId ? canEditPropertiesBasedOnProject(projectId) : isEditingAllowed;
|
||||
const canEditProperties = useCallback(
|
||||
(projectId: string | undefined) => {
|
||||
const isEditingAllowedBasedOnProject =
|
||||
canEditPropertiesBasedOnProject && projectId ? canEditPropertiesBasedOnProject(projectId) : isEditingAllowed;
|
||||
|
||||
return enableInlineEditing && isEditingAllowedBasedOnProject;
|
||||
};
|
||||
return enableInlineEditing && isEditingAllowedBasedOnProject;
|
||||
},
|
||||
[canEditPropertiesBasedOnProject, enableInlineEditing, isEditingAllowed]
|
||||
);
|
||||
|
||||
const onDragStart = (dragStart: DragStart) => {
|
||||
setDragState({
|
||||
|
|
@ -171,21 +143,30 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||
});
|
||||
setDeleteIssueModal(true);
|
||||
} else {
|
||||
await handleDragDrop(result.source, result.destination, sub_group_by, group_by, issues, issueIds).catch(
|
||||
(err) => {
|
||||
setToastAlert({
|
||||
title: "Error",
|
||||
type: "error",
|
||||
message: err.detail ?? "Failed to perform this action",
|
||||
});
|
||||
}
|
||||
);
|
||||
await handleDragDrop(
|
||||
result.source,
|
||||
result.destination,
|
||||
workspaceSlug?.toString(),
|
||||
projectId?.toString(),
|
||||
issues,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
issueMap,
|
||||
issueIds,
|
||||
viewId
|
||||
).catch((err) => {
|
||||
setToastAlert({
|
||||
title: "Error",
|
||||
type: "error",
|
||||
message: err.detail ?? "Failed to perform this action",
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleIssues = useCallback(
|
||||
async (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => {
|
||||
async (issue: TIssue, action: EIssueActions) => {
|
||||
if (issueActions[action]) {
|
||||
await issueActions[action]!(issue);
|
||||
}
|
||||
|
|
@ -193,34 +174,57 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||
[issueActions]
|
||||
);
|
||||
|
||||
const renderQuickActions = useCallback(
|
||||
(issue: TIssue, customActionButton?: React.ReactElement) => (
|
||||
<QuickActions
|
||||
customActionButton={customActionButton}
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(issue, EIssueActions.DELETE)}
|
||||
handleUpdate={
|
||||
issueActions[EIssueActions.UPDATE] ? async (data) => handleIssues(data, EIssueActions.UPDATE) : undefined
|
||||
}
|
||||
handleRemoveFromView={
|
||||
issueActions[EIssueActions.REMOVE] ? async () => handleIssues(issue, EIssueActions.REMOVE) : undefined
|
||||
}
|
||||
/>
|
||||
),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[issueActions, handleIssues]
|
||||
);
|
||||
|
||||
const handleDeleteIssue = async () => {
|
||||
if (!handleDragDrop) return;
|
||||
await handleDragDrop(dragState.source, dragState.destination, sub_group_by, group_by, issues, issueIds).finally(
|
||||
() => {
|
||||
setDeleteIssueModal(false);
|
||||
setDragState({});
|
||||
}
|
||||
);
|
||||
await handleDragDrop(
|
||||
dragState.source,
|
||||
dragState.destination,
|
||||
workspaceSlug?.toString(),
|
||||
projectId?.toString(),
|
||||
issues,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
issueMap,
|
||||
issueIds,
|
||||
viewId
|
||||
).finally(() => {
|
||||
setDeleteIssueModal(false);
|
||||
setDragState({});
|
||||
});
|
||||
};
|
||||
|
||||
const handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => {
|
||||
kanbanViewStore.handleKanBanToggle(toggle, value);
|
||||
};
|
||||
|
||||
const states = projectStateStore?.projectStates || null;
|
||||
const priorities = ISSUE_PRIORITIES || null;
|
||||
const stateGroups = ISSUE_STATE_GROUPS || null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeleteIssueModal
|
||||
data={dragState.draggedIssueId ? issues[dragState.draggedIssueId] : ({} as IIssue)}
|
||||
dataId={dragState.draggedIssueId}
|
||||
isOpen={deleteIssueModal}
|
||||
handleClose={() => setDeleteIssueModal(false)}
|
||||
onSubmit={handleDeleteIssue}
|
||||
/>
|
||||
|
||||
{showLoader && issueStore?.loader === "init-loader" && (
|
||||
{showLoader && issues?.loader === "init-loader" && (
|
||||
<div className="fixed right-2 top-16 z-30 flex h-10 w-10 items-center justify-center rounded bg-custom-background-80 shadow-custom-shadow-sm">
|
||||
<Spinner className="h-5 w-5" />
|
||||
</div>
|
||||
|
|
@ -250,94 +254,25 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||
</Droppable>
|
||||
</div>
|
||||
|
||||
{currentKanBanView === "default" ? (
|
||||
<KanBan
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(sub_group_by, group_by, issue, customActionButton) => (
|
||||
<QuickActions
|
||||
customActionButton={customActionButton}
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, EIssueActions.DELETE)}
|
||||
handleUpdate={
|
||||
issueActions[EIssueActions.UPDATE]
|
||||
? async (data) => handleIssues(sub_group_by, group_by, data, EIssueActions.UPDATE)
|
||||
: undefined
|
||||
}
|
||||
handleRemoveFromView={
|
||||
issueActions[EIssueActions.REMOVE]
|
||||
? async () => handleIssues(sub_group_by, group_by, issue, EIssueActions.REMOVE)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanbanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={projectLabels}
|
||||
members={projectMembers?.map((m) => m.member) ?? null}
|
||||
projects={workspaceProjects}
|
||||
enableQuickIssueCreate={enableQuickAdd}
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
isDragStarted={isDragStarted}
|
||||
quickAddCallback={issueStore?.quickAddIssue}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
|
||||
canEditProperties={canEditProperties}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
) : (
|
||||
<KanBanSwimLanes
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(sub_group_by, group_by, issue, customActionButton) => (
|
||||
<QuickActions
|
||||
customActionButton={customActionButton}
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, EIssueActions.DELETE)}
|
||||
handleUpdate={
|
||||
issueActions[EIssueActions.UPDATE]
|
||||
? async (data) => handleIssues(sub_group_by, group_by, data, EIssueActions.UPDATE)
|
||||
: undefined
|
||||
}
|
||||
handleRemoveFromView={
|
||||
issueActions[EIssueActions.REMOVE]
|
||||
? async () => handleIssues(sub_group_by, group_by, issue, EIssueActions.REMOVE)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanbanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={projectLabels}
|
||||
members={projectMembers?.map((m) => m.member) ?? null}
|
||||
projects={workspaceProjects}
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
|
||||
enableQuickIssueCreate={enableQuickAdd}
|
||||
currentStore={currentStore}
|
||||
quickAddCallback={issueStore?.quickAddIssue}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
<KanBanView
|
||||
issuesMap={issueMap}
|
||||
issueIds={issueIds}
|
||||
displayProperties={displayProperties}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={renderQuickActions}
|
||||
kanBanToggle={kanbanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickAdd}
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
quickAddCallback={issues?.quickAddIssue}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
|
||||
canEditProperties={canEditProperties}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
</DragDropContext>
|
||||
</div>
|
||||
|
||||
|
|
@ -346,9 +281,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
|||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={peekProjectId.toString()}
|
||||
issueId={peekIssueId.toString()}
|
||||
handleIssue={async (issueToUpdate, action: EIssueActions) =>
|
||||
await handleIssues(sub_group_by, group_by, issueToUpdate as IIssue, action)
|
||||
}
|
||||
handleIssue={async (issueToUpdate) => await handleIssues(issueToUpdate as TIssue, EIssueActions.UPDATE)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -1,171 +1,128 @@
|
|||
import { memo } from "react";
|
||||
import { Draggable, DraggableStateSnapshot } from "@hello-pangea/dnd";
|
||||
import isEqual from "lodash/isEqual";
|
||||
import { DraggableProvided, DraggableStateSnapshot } from "@hello-pangea/dnd";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { KanBanProperties } from "./properties";
|
||||
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
|
||||
import { IssueProperties } from "../properties/all-properties";
|
||||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// types
|
||||
import { IIssueDisplayProperties, IIssue } from "types";
|
||||
import { TIssue, IIssueDisplayProperties, IIssueMap } from "@plane/types";
|
||||
import { EIssueActions } from "../types";
|
||||
import { useRouter } from "next/router";
|
||||
import { useProject } from "hooks/store";
|
||||
|
||||
interface IssueBlockProps {
|
||||
sub_group_id: string;
|
||||
columnId: string;
|
||||
index: number;
|
||||
issue: IIssue;
|
||||
issueId: string;
|
||||
issuesMap: IIssueMap;
|
||||
displayProperties: IIssueDisplayProperties | undefined;
|
||||
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) => React.ReactNode;
|
||||
displayProperties: IIssueDisplayProperties | null;
|
||||
handleIssues: (issue: TIssue, action: EIssueActions) => void;
|
||||
quickActions: (issue: TIssue) => React.ReactNode;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
provided: DraggableProvided;
|
||||
snapshot: DraggableStateSnapshot;
|
||||
}
|
||||
|
||||
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;
|
||||
issue: TIssue;
|
||||
displayProperties: IIssueDisplayProperties | undefined;
|
||||
handleIssues: (issue: TIssue, action: EIssueActions) => void;
|
||||
quickActions: (issue: TIssue) => React.ReactNode;
|
||||
isReadOnly: boolean;
|
||||
snapshot: DraggableStateSnapshot;
|
||||
isDragDisabled: boolean;
|
||||
}
|
||||
|
||||
const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = (props) => {
|
||||
const {
|
||||
sub_group_id,
|
||||
columnId,
|
||||
issue,
|
||||
showEmptyGroup,
|
||||
handleIssues,
|
||||
quickActions,
|
||||
displayProperties,
|
||||
isReadOnly,
|
||||
snapshot,
|
||||
isDragDisabled,
|
||||
} = props;
|
||||
const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = observer((props: IssueDetailsBlockProps) => {
|
||||
const { issue, handleIssues, quickActions, isReadOnly, displayProperties } = props;
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const updateIssue = (sub_group_by: string | null, group_by: string | null, issueToUpdate: IIssue) => {
|
||||
if (issueToUpdate) handleIssues(sub_group_by, group_by, issueToUpdate, EIssueActions.UPDATE);
|
||||
// hooks
|
||||
const { getProjectById } = useProject();
|
||||
|
||||
const updateIssue = (issueToUpdate: TIssue) => {
|
||||
if (issueToUpdate) handleIssues(issueToUpdate, EIssueActions.UPDATE);
|
||||
};
|
||||
|
||||
const handleIssuePeekOverview = (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
const handleIssuePeekOverview = () => {
|
||||
const { query } = router;
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
const issueUrl = `/${issue.workspace_detail.slug}/projects/${issue.project_detail.id}/issues/${issue?.id}`;
|
||||
window.open(issueUrl, "_blank"); // Open link in a new tab
|
||||
} else {
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: { ...query, peekIssueId: issue?.id, peekProjectId: issue?.project },
|
||||
});
|
||||
}
|
||||
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: { ...query, peekIssueId: issue?.id, peekProjectId: issue?.project_id },
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col space-y-2 cursor-pointer rounded border-[0.5px] border-custom-border-200 bg-custom-background-100 px-3 py-2 text-sm shadow-custom-shadow-2xs transition-all w-full ${
|
||||
isDragDisabled ? "" : "hover:cursor-grab"
|
||||
} ${snapshot.isDragging ? `border-custom-primary-100` : `border-transparent`}`}
|
||||
onClick={handleIssuePeekOverview}
|
||||
>
|
||||
{displayProperties && displayProperties?.key && (
|
||||
<div className="relative w-full ">
|
||||
<div className="line-clamp-1 text-xs text-left 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
|
||||
)}
|
||||
<>
|
||||
<WithDisplayPropertiesHOC displayProperties={displayProperties || {}} displayPropertyKey="key">
|
||||
<div className="relative">
|
||||
<div className="line-clamp-1 text-xs text-custom-text-300">
|
||||
{getProjectById(issue.project_id)?.identifier}-{issue.sequence_id}
|
||||
</div>
|
||||
<div className="absolute -top-1 right-0 hidden group-hover/kanban-block:block">{quickActions(issue)}</div>
|
||||
</div>
|
||||
)}
|
||||
</WithDisplayPropertiesHOC>
|
||||
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
|
||||
<div className="line-clamp-2 text-sm font-medium text-custom-text-100">{issue.name}</div>
|
||||
<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}
|
||||
<IssueProperties
|
||||
className="flex flex-wrap items-center gap-2 whitespace-nowrap"
|
||||
issue={issue}
|
||||
displayProperties={displayProperties}
|
||||
handleIssues={updateIssue}
|
||||
isReadOnly={isReadOnly}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export const KanbanIssueBlock: React.FC<IssueBlockProps> = memo((props) => {
|
||||
const {
|
||||
issueId,
|
||||
issuesMap,
|
||||
displayProperties,
|
||||
isDragDisabled,
|
||||
handleIssues,
|
||||
quickActions,
|
||||
canEditProperties,
|
||||
provided,
|
||||
snapshot,
|
||||
} = props;
|
||||
|
||||
const issue = issuesMap[issueId];
|
||||
|
||||
if (!issue) return null;
|
||||
|
||||
const canEditIssueProperties = canEditProperties(issue.project_id);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="group/kanban-block relative p-1.5 hover:cursor-default"
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
{issue.tempId !== undefined && (
|
||||
<div className="absolute left-0 top-0 z-[99999] h-full w-full animate-pulse bg-custom-background-100/20" />
|
||||
)}
|
||||
<div
|
||||
className={`space-y-2 rounded border-[0.5px] border-custom-border-200 bg-custom-background-100 px-3 py-2 text-sm shadow-custom-shadow-2xs transition-all ${
|
||||
isDragDisabled ? "" : "hover:cursor-grab"
|
||||
} ${snapshot.isDragging ? `border-custom-primary-100` : `border-transparent`}`}
|
||||
>
|
||||
<KanbanIssueDetailsBlock
|
||||
issue={issue}
|
||||
handleIssues={updateIssue}
|
||||
displayProperties={displayProperties}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
isReadOnly={isReadOnly}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
isReadOnly={!canEditIssueProperties}
|
||||
/>
|
||||
</div>
|
||||
</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,
|
||||
canEditProperties,
|
||||
} = props;
|
||||
|
||||
let draggableId = issue.id;
|
||||
if (columnId) draggableId = `${draggableId}__${columnId}`;
|
||||
if (sub_group_id) draggableId = `${draggableId}__${sub_group_id}`;
|
||||
|
||||
const canEditIssueProperties = canEditProperties(issue.project);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Draggable draggableId={draggableId} index={index} isDragDisabled={!canEditIssueProperties}>
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
className="group/kanban-block relative p-1.5"
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
{issue.tempId !== undefined && (
|
||||
<div className="absolute left-0 top-0 z-[99999] h-full w-full animate-pulse bg-custom-background-100/20" />
|
||||
)}
|
||||
<KanbanIssueMemoBlock
|
||||
sub_group_id={sub_group_id}
|
||||
columnId={columnId}
|
||||
issue={issue}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
isReadOnly={!canEditIssueProperties}
|
||||
snapshot={snapshot}
|
||||
isDragDisabled={isDragDisabled}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
</>
|
||||
);
|
||||
};
|
||||
KanbanIssueBlock.displayName = "KanbanIssueBlock";
|
||||
|
|
|
|||
|
|
@ -1,38 +1,33 @@
|
|||
import { memo } from "react";
|
||||
//types
|
||||
import { TIssue, IIssueDisplayProperties, IIssueMap } from "@plane/types";
|
||||
import { EIssueActions } from "../types";
|
||||
// components
|
||||
import { KanbanIssueBlock } from "components/issues";
|
||||
import { IIssueDisplayProperties, IIssue } from "types";
|
||||
import { EIssueActions } from "../types";
|
||||
import { IIssueResponse } from "store/issues/types";
|
||||
import { Draggable } from "@hello-pangea/dnd";
|
||||
|
||||
interface IssueBlocksListProps {
|
||||
sub_group_id: string;
|
||||
columnId: string;
|
||||
issues: IIssueResponse;
|
||||
issuesMap: IIssueMap;
|
||||
issueIds: string[];
|
||||
displayProperties: IIssueDisplayProperties | undefined;
|
||||
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;
|
||||
displayProperties: IIssueDisplayProperties | null;
|
||||
handleIssues: (issue: TIssue, action: EIssueActions) => void;
|
||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
}
|
||||
|
||||
export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = (props) => {
|
||||
const KanbanIssueBlocksListMemo: React.FC<IssueBlocksListProps> = (props) => {
|
||||
const {
|
||||
sub_group_id,
|
||||
columnId,
|
||||
issues,
|
||||
issuesMap,
|
||||
issueIds,
|
||||
showEmptyGroup,
|
||||
displayProperties,
|
||||
isDragDisabled,
|
||||
handleIssues,
|
||||
quickActions,
|
||||
displayProperties,
|
||||
canEditProperties,
|
||||
} = props;
|
||||
|
||||
|
|
@ -41,34 +36,35 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = (props) =>
|
|||
{issueIds && issueIds.length > 0 ? (
|
||||
<>
|
||||
{issueIds.map((issueId, index) => {
|
||||
if (!issues[issueId]) return null;
|
||||
if (!issueId) return null;
|
||||
|
||||
const issue = issues[issueId];
|
||||
let draggableId = issueId;
|
||||
if (columnId) draggableId = `${draggableId}__${columnId}`;
|
||||
if (sub_group_id) draggableId = `${draggableId}__${sub_group_id}`;
|
||||
|
||||
return (
|
||||
<KanbanIssueBlock
|
||||
key={`kanban-issue-block-${issue.id}`}
|
||||
index={index}
|
||||
issue={issue}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
columnId={columnId}
|
||||
sub_group_id={sub_group_id}
|
||||
isDragDisabled={isDragDisabled}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
<Draggable key={draggableId} draggableId={draggableId} index={index}>
|
||||
{(provided, snapshot) => (
|
||||
<KanbanIssueBlock
|
||||
key={`kanban-issue-block-${issueId}`}
|
||||
issueId={issueId}
|
||||
issuesMap={issuesMap}
|
||||
displayProperties={displayProperties}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
provided={provided}
|
||||
snapshot={snapshot}
|
||||
isDragDisabled={isDragDisabled}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
!isDragDisabled && (
|
||||
<div className="absolute left-0 top-0 flex h-full w-full items-center justify-center">
|
||||
{/* <div className="text-custom-text-300 text-sm">Drop here</div> */}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const KanbanIssueBlocksList = memo(KanbanIssueBlocksListMemo);
|
||||
|
|
|
|||
|
|
@ -1,77 +1,65 @@
|
|||
import React from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Droppable } from "@hello-pangea/dnd";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useKanbanView, useLabel, useMember, useProject, useProjectState } from "hooks/store";
|
||||
// components
|
||||
import { KanBanGroupByHeaderRoot } from "./headers/group-by-root";
|
||||
import { KanbanIssueBlocksList, KanBanQuickAddIssueForm } from "components/issues";
|
||||
import { HeaderGroupByCard } from "./headers/group-by-card";
|
||||
import { KanbanGroup } from "./kanban-group";
|
||||
// types
|
||||
import { IIssueDisplayProperties, IIssue, IState } from "types";
|
||||
import {
|
||||
GroupByColumnTypes,
|
||||
IGroupByColumn,
|
||||
TGroupedIssues,
|
||||
TIssue,
|
||||
IIssueDisplayProperties,
|
||||
IIssueMap,
|
||||
TSubGroupedIssues,
|
||||
TUnGroupedIssues,
|
||||
} from "@plane/types";
|
||||
// constants
|
||||
import { getValueFromObject } from "constants/issue";
|
||||
import { EIssueActions } from "../types";
|
||||
import { IIssueResponse, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { getGroupByColumns } from "../utils";
|
||||
import { TCreateModalStoreTypes } from "constants/issue";
|
||||
|
||||
export interface IGroupByKanBan {
|
||||
issues: IIssueResponse;
|
||||
issueIds: any;
|
||||
issuesMap: IIssueMap;
|
||||
issueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues;
|
||||
displayProperties: IIssueDisplayProperties | undefined;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
order_by: string | null;
|
||||
sub_group_id: string;
|
||||
list: any;
|
||||
listKey: string;
|
||||
states: IState[] | null;
|
||||
isDragDisabled: boolean;
|
||||
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
|
||||
showEmptyGroup: boolean;
|
||||
quickActions: (
|
||||
sub_group_by: string | null,
|
||||
group_by: string | null,
|
||||
issue: IIssue,
|
||||
customActionButton?: React.ReactElement
|
||||
) => React.ReactNode;
|
||||
displayProperties: IIssueDisplayProperties | null;
|
||||
handleIssues: (issue: TIssue, action: EIssueActions) => void;
|
||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
enableQuickIssueCreate?: boolean;
|
||||
isDragStarted?: boolean;
|
||||
quickAddCallback?: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: IIssue,
|
||||
data: TIssue,
|
||||
viewId?: string
|
||||
) => Promise<IIssue | undefined>;
|
||||
) => Promise<TIssue | undefined>;
|
||||
viewId?: string;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
currentStore?: TCreateModalStoreTypes;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
}
|
||||
|
||||
const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
||||
const {
|
||||
issues,
|
||||
issuesMap,
|
||||
issueIds,
|
||||
displayProperties,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
order_by,
|
||||
sub_group_id = "null",
|
||||
list,
|
||||
listKey,
|
||||
isDragDisabled,
|
||||
handleIssues,
|
||||
showEmptyGroup,
|
||||
quickActions,
|
||||
displayProperties,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
enableQuickIssueCreate,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
isDragStarted,
|
||||
quickAddCallback,
|
||||
viewId,
|
||||
disableIssueCreation,
|
||||
|
|
@ -80,167 +68,108 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
|||
canEditProperties,
|
||||
} = props;
|
||||
|
||||
const verticalAlignPosition = (_list: any) =>
|
||||
kanBanToggle?.groupByHeaderMinMax.includes(getValueFromObject(_list, listKey) as string);
|
||||
const member = useMember();
|
||||
const project = useProject();
|
||||
const projectLabel = useLabel();
|
||||
const projectState = useProjectState();
|
||||
|
||||
const list = getGroupByColumns(group_by as GroupByColumnTypes, project, projectLabel, projectState, member);
|
||||
|
||||
if (!list) return null;
|
||||
|
||||
const verticalAlignPosition = (_list: IGroupByColumn) => kanBanToggle?.groupByHeaderMinMax.includes(_list.id);
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full w-full gap-3">
|
||||
{list &&
|
||||
list.length > 0 &&
|
||||
list.map((_list: any) => (
|
||||
<div
|
||||
className={`relative flex flex-shrink-0 flex-col ${!verticalAlignPosition(_list) ? `w-[340px]` : ``} group`}
|
||||
>
|
||||
{sub_group_by === null && (
|
||||
<div className="sticky top-0 z-[2] w-full flex-shrink-0 bg-custom-background-90 py-1">
|
||||
<KanBanGroupByHeaderRoot
|
||||
column_id={getValueFromObject(_list, listKey) as string}
|
||||
column_value={_list}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
issues_count={issueIds?.[getValueFromObject(_list, listKey) as string]?.length || 0}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
list.map((_list: IGroupByColumn) => {
|
||||
const verticalPosition = verticalAlignPosition(_list);
|
||||
|
||||
<div
|
||||
className={`${
|
||||
verticalAlignPosition(_list) ? `min-h-[150px] w-[0px] overflow-hidden` : `w-full transition-all`
|
||||
}`}
|
||||
>
|
||||
<Droppable droppableId={`${getValueFromObject(_list, listKey) as string}__${sub_group_id}`}>
|
||||
{(provided: any, snapshot: any) => (
|
||||
<div
|
||||
className={`relative h-full w-full transition-all ${
|
||||
snapshot.isDraggingOver ? `bg-custom-background-80` : ``
|
||||
}`}
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
{issues && !verticalAlignPosition(_list) ? (
|
||||
<KanbanIssueBlocksList
|
||||
sub_group_id={sub_group_id}
|
||||
columnId={getValueFromObject(_list, listKey) as string}
|
||||
issues={issues}
|
||||
issueIds={issueIds?.[getValueFromObject(_list, listKey) as string] || []}
|
||||
isDragDisabled={isDragDisabled}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
) : (
|
||||
isDragDisabled && (
|
||||
<div className="absolute left-0 top-0 flex h-full w-full items-center justify-center text-sm">
|
||||
{/* <div className="text-custom-text-300 text-sm">Drop here</div> */}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
|
||||
<div className="sticky bottom-0 z-[0] w-full flex-shrink-0 bg-custom-background-90 py-1">
|
||||
{enableQuickIssueCreate && !disableIssueCreation && (
|
||||
<KanBanQuickAddIssueForm
|
||||
formKey="name"
|
||||
groupId={getValueFromObject(_list, listKey) as string}
|
||||
subGroupId={sub_group_id}
|
||||
prePopulatedData={{
|
||||
...(group_by && { [group_by]: getValueFromObject(_list, listKey) }),
|
||||
...(sub_group_by && sub_group_id !== "null" && { [sub_group_by]: sub_group_id }),
|
||||
}}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
return (
|
||||
<div className={`relative flex flex-shrink-0 flex-col ${!verticalPosition ? `w-[340px]` : ``} group`}>
|
||||
{sub_group_by === null && (
|
||||
<div className="sticky top-0 z-[2] w-full flex-shrink-0 bg-custom-background-90 py-1">
|
||||
<HeaderGroupByCard
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
column_id={_list.id}
|
||||
icon={_list.Icon}
|
||||
title={_list.name}
|
||||
count={(issueIds as TGroupedIssues)?.[_list.id]?.length || 0}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
issuePayload={_list.payload}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* {isDragStarted && isDragDisabled && (
|
||||
<div className="invisible group-hover:visible transition-all text-sm absolute top-12 bottom-10 left-0 right-0 bg-custom-background-100/40 text-center">
|
||||
<div className="rounded inline-flex mt-80 h-8 px-3 justify-center items-center bg-custom-background-80 text-custom-text-100 font-medium">
|
||||
{`This board is ordered by "${replaceUnderscoreIfSnakeCase(
|
||||
order_by ? (order_by[0] === "-" ? order_by.slice(1) : order_by) : "created_at"
|
||||
)}"`}
|
||||
</div>
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
))}
|
||||
)}
|
||||
<KanbanGroup
|
||||
groupId={_list.id}
|
||||
issuesMap={issuesMap}
|
||||
issueIds={issueIds}
|
||||
displayProperties={displayProperties}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
isDragDisabled={isDragDisabled}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
canEditProperties={canEditProperties}
|
||||
verticalPosition={verticalPosition}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export interface IKanBan {
|
||||
issues: IIssueResponse;
|
||||
issueIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues;
|
||||
issuesMap: IIssueMap;
|
||||
issueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues;
|
||||
displayProperties: IIssueDisplayProperties | undefined;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
order_by: string | null;
|
||||
sub_group_id?: string;
|
||||
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;
|
||||
displayProperties: IIssueDisplayProperties | null;
|
||||
handleIssues: (issue: TIssue, action: EIssueActions) => void;
|
||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
showEmptyGroup: boolean;
|
||||
states: any;
|
||||
stateGroups: any;
|
||||
priorities: any;
|
||||
labels: any;
|
||||
members: any;
|
||||
projects: any;
|
||||
enableQuickIssueCreate?: boolean;
|
||||
isDragStarted?: boolean;
|
||||
quickAddCallback?: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: IIssue,
|
||||
data: TIssue,
|
||||
viewId?: string
|
||||
) => Promise<IIssue | undefined>;
|
||||
) => Promise<TIssue | undefined>;
|
||||
viewId?: string;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
currentStore?: TCreateModalStoreTypes;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
}
|
||||
|
||||
export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
const {
|
||||
issues,
|
||||
issuesMap,
|
||||
issueIds,
|
||||
displayProperties,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
order_by,
|
||||
sub_group_id = "null",
|
||||
handleIssues,
|
||||
quickActions,
|
||||
displayProperties,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
showEmptyGroup,
|
||||
states,
|
||||
stateGroups,
|
||||
priorities,
|
||||
labels,
|
||||
members,
|
||||
projects,
|
||||
enableQuickIssueCreate,
|
||||
isDragStarted,
|
||||
quickAddCallback,
|
||||
viewId,
|
||||
disableIssueCreation,
|
||||
|
|
@ -249,212 +178,30 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
|||
canEditProperties,
|
||||
} = props;
|
||||
|
||||
const { issueKanBanView: issueKanBanViewStore } = useMobxStore();
|
||||
const issueKanBanView = useKanbanView();
|
||||
|
||||
return (
|
||||
<div className="relative h-full w-full">
|
||||
{group_by && group_by === "project" && (
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
list={projects}
|
||||
listKey={`id`}
|
||||
states={states}
|
||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "state" && (
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
list={states}
|
||||
listKey={`id`}
|
||||
states={states}
|
||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "state_detail.group" && (
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
list={stateGroups}
|
||||
listKey={`key`}
|
||||
states={states}
|
||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "priority" && (
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
list={priorities}
|
||||
listKey={`key`}
|
||||
states={states}
|
||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "labels" && (
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
list={labels ? [...labels, { id: "None", name: "None" }] : labels}
|
||||
listKey={`id`}
|
||||
states={states}
|
||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "assignees" && (
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
list={members ? [...members, { id: "None", display_name: "None" }] : members}
|
||||
listKey={`id`}
|
||||
states={states}
|
||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "created_by" && (
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
list={members}
|
||||
listKey={`id`}
|
||||
states={states}
|
||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
<GroupByKanBan
|
||||
issuesMap={issuesMap}
|
||||
issueIds={issueIds}
|
||||
displayProperties={displayProperties}
|
||||
group_by={group_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
isDragDisabled={!issueKanBanView?.canUserDragDrop}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,74 +0,0 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { HeaderGroupByCard } from "./group-by-card";
|
||||
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
||||
// ui
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { IIssue } from "types";
|
||||
|
||||
export interface IAssigneesHeader {
|
||||
column_id: string;
|
||||
column_value: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
header_type: "group_by" | "sub_group_by";
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
}
|
||||
|
||||
export const Icon = ({ user }: any) => <Avatar name={user.display_name} src={user.avatar} size="md" />;
|
||||
|
||||
export const AssigneesHeader: FC<IAssigneesHeader> = observer((props) => {
|
||||
const {
|
||||
column_id,
|
||||
column_value,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
header_type,
|
||||
issues_count,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
disableIssueCreation,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
} = props;
|
||||
|
||||
const assignee = column_value ?? null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{assignee &&
|
||||
(sub_group_by && header_type === "sub_group_by" ? (
|
||||
<HeaderSubGroupByCard
|
||||
column_id={column_id}
|
||||
icon={<Icon user={assignee} />}
|
||||
title={assignee?.display_name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<HeaderGroupByCard
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
column_id={column_id}
|
||||
icon={<Icon user={assignee} />}
|
||||
title={assignee?.display_name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
issuePayload={{ assignees: [assignee?.id] }}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { HeaderGroupByCard } from "./group-by-card";
|
||||
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
||||
import { Icon } from "./assignee";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { IIssue } from "types";
|
||||
|
||||
export interface ICreatedByHeader {
|
||||
column_id: string;
|
||||
column_value: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
header_type: "group_by" | "sub_group_by";
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
}
|
||||
|
||||
export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
|
||||
const {
|
||||
column_id,
|
||||
column_value,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
header_type,
|
||||
issues_count,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
disableIssueCreation,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
} = props;
|
||||
|
||||
const createdBy = column_value ?? null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{createdBy &&
|
||||
(sub_group_by && header_type === "sub_group_by" ? (
|
||||
<HeaderSubGroupByCard
|
||||
column_id={column_id}
|
||||
icon={<Icon user={createdBy} />}
|
||||
title={createdBy?.display_name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<HeaderGroupByCard
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
column_id={column_id}
|
||||
icon={<Icon user={createdBy} />}
|
||||
title={createdBy?.display_name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
issuePayload={{ created_by: createdBy?.id }}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
@ -12,8 +12,8 @@ import useToast from "hooks/use-toast";
|
|||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// types
|
||||
import { IIssue, ISearchIssueResponse } from "types";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { TIssue, ISearchIssueResponse } from "@plane/types";
|
||||
import { TCreateModalStoreTypes } from "constants/issue";
|
||||
|
||||
interface IHeaderGroupByCard {
|
||||
sub_group_by: string | null;
|
||||
|
|
@ -24,10 +24,10 @@ interface IHeaderGroupByCard {
|
|||
count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
issuePayload: Partial<IIssue>;
|
||||
issuePayload: Partial<TIssue>;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
currentStore?: TCreateModalStoreTypes;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
||||
}
|
||||
|
||||
export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
||||
|
|
@ -64,14 +64,15 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
|
|||
|
||||
const issues = data.map((i) => i.id);
|
||||
|
||||
addIssuesToView &&
|
||||
addIssuesToView(issues)?.catch(() => {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Selected issues could not be added to the cycle. Please try again.",
|
||||
});
|
||||
try {
|
||||
addIssuesToView && addIssuesToView(issues);
|
||||
} catch (error) {
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "Selected issues could not be added to the cycle. Please try again.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -1,149 +0,0 @@
|
|||
// components
|
||||
import { ProjectHeader } from "./project";
|
||||
import { StateHeader } from "./state";
|
||||
import { StateGroupHeader } from "./state-group";
|
||||
import { AssigneesHeader } from "./assignee";
|
||||
import { PriorityHeader } from "./priority";
|
||||
import { LabelHeader } from "./label";
|
||||
import { CreatedByHeader } from "./created_by";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { IIssue } from "types";
|
||||
|
||||
export interface IKanBanGroupByHeaderRoot {
|
||||
column_id: string;
|
||||
column_value: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
}
|
||||
|
||||
export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = observer(
|
||||
({
|
||||
column_id,
|
||||
column_value,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
issues_count,
|
||||
kanBanToggle,
|
||||
disableIssueCreation,
|
||||
handleKanBanToggle,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
}) => (
|
||||
<>
|
||||
{group_by && group_by === "project" && (
|
||||
<ProjectHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "state" && (
|
||||
<StateHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
{group_by && group_by === "state_detail.group" && (
|
||||
<StateGroupHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
{group_by && group_by === "priority" && (
|
||||
<PriorityHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
{group_by && group_by === "labels" && (
|
||||
<LabelHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
{group_by && group_by === "assignees" && (
|
||||
<AssigneesHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
{group_by && group_by === "created_by" && (
|
||||
<CreatedByHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
);
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { HeaderGroupByCard } from "./group-by-card";
|
||||
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { IIssue } from "types";
|
||||
|
||||
export interface ILabelHeader {
|
||||
column_id: string;
|
||||
column_value: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
header_type: "group_by" | "sub_group_by";
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
}
|
||||
|
||||
const Icon = ({ color }: any) => (
|
||||
<div className="h-[12px] w-[12px] rounded-full" style={{ backgroundColor: color ? color : "#666" }} />
|
||||
);
|
||||
|
||||
export const LabelHeader: FC<ILabelHeader> = observer((props) => {
|
||||
const {
|
||||
column_id,
|
||||
column_value,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
header_type,
|
||||
issues_count,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
disableIssueCreation,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
} = props;
|
||||
|
||||
const label = column_value ?? null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{label &&
|
||||
(sub_group_by && header_type === "sub_group_by" ? (
|
||||
<HeaderSubGroupByCard
|
||||
column_id={column_id}
|
||||
icon={<Icon color={label?.color} />}
|
||||
title={label?.name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<HeaderGroupByCard
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
column_id={column_id}
|
||||
icon={<Icon />}
|
||||
title={label?.name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
issuePayload={{ labels: [label?.id] }}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { HeaderGroupByCard } from "./group-by-card";
|
||||
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
||||
|
||||
// Icons
|
||||
import { PriorityIcon } from "@plane/ui";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { IIssue } from "types";
|
||||
|
||||
export interface IPriorityHeader {
|
||||
column_id: string;
|
||||
column_value: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
header_type: "group_by" | "sub_group_by";
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
}
|
||||
|
||||
export const PriorityHeader: FC<IPriorityHeader> = observer((props) => {
|
||||
const {
|
||||
column_id,
|
||||
column_value,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
header_type,
|
||||
issues_count,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
disableIssueCreation,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
} = props;
|
||||
|
||||
const priority = column_value || null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{priority &&
|
||||
(sub_group_by && header_type === "sub_group_by" ? (
|
||||
<HeaderSubGroupByCard
|
||||
column_id={column_id}
|
||||
icon={<PriorityIcon priority={priority?.key} />}
|
||||
title={priority?.title || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<HeaderGroupByCard
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
column_id={column_id}
|
||||
icon={<PriorityIcon priority={priority?.key} />}
|
||||
title={priority?.title || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
issuePayload={{ priority: priority?.key }}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { HeaderGroupByCard } from "./group-by-card";
|
||||
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
||||
// emoji helper
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { IIssue } from "types";
|
||||
|
||||
export interface IProjectHeader {
|
||||
column_id: string;
|
||||
column_value: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
header_type: "group_by" | "sub_group_by";
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
}
|
||||
|
||||
const Icon = ({ emoji }: any) => <div className="h-6 w-6">{renderEmoji(emoji)}</div>;
|
||||
|
||||
export const ProjectHeader: FC<IProjectHeader> = observer((props) => {
|
||||
const {
|
||||
column_id,
|
||||
column_value,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
header_type,
|
||||
issues_count,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
disableIssueCreation,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
} = props;
|
||||
|
||||
const project = column_value ?? null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{project &&
|
||||
(sub_group_by && header_type === "sub_group_by" ? (
|
||||
<HeaderSubGroupByCard
|
||||
column_id={column_id}
|
||||
icon={<Icon emoji={project?.emoji} />}
|
||||
title={project?.name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<HeaderGroupByCard
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
column_id={column_id}
|
||||
icon={<Icon emoji={project?.emoji} />}
|
||||
title={project?.name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
issuePayload={{ project: project?.id }}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { HeaderGroupByCard } from "./group-by-card";
|
||||
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { IIssue } from "types";
|
||||
|
||||
export interface IStateGroupHeader {
|
||||
column_id: string;
|
||||
column_value: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
header_type: "group_by" | "sub_group_by";
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
}
|
||||
|
||||
export const Icon = ({ stateGroup, color }: { stateGroup: any; color?: any }) => (
|
||||
<div className="h-3.5 w-3.5 rounded-full">
|
||||
<StateGroupIcon stateGroup={stateGroup} color={color || null} width="14" height="14" />
|
||||
</div>
|
||||
);
|
||||
|
||||
export const StateGroupHeader: FC<IStateGroupHeader> = observer((props) => {
|
||||
const {
|
||||
column_id,
|
||||
column_value,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
header_type,
|
||||
issues_count,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
disableIssueCreation,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
} = props;
|
||||
|
||||
const stateGroup = column_value || null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{stateGroup &&
|
||||
(sub_group_by && header_type === "sub_group_by" ? (
|
||||
<HeaderSubGroupByCard
|
||||
column_id={column_id}
|
||||
icon={<Icon stateGroup={stateGroup?.key} />}
|
||||
title={stateGroup?.key || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<HeaderGroupByCard
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
column_id={column_id}
|
||||
icon={<Icon stateGroup={stateGroup?.key} />}
|
||||
title={stateGroup?.key || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
issuePayload={{}}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { HeaderGroupByCard } from "./group-by-card";
|
||||
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
||||
import { Icon } from "./state-group";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { IIssue } from "types";
|
||||
|
||||
export interface IStateHeader {
|
||||
column_id: string;
|
||||
column_value: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
header_type: "group_by" | "sub_group_by";
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
}
|
||||
|
||||
export const StateHeader: FC<IStateHeader> = observer((props) => {
|
||||
const {
|
||||
column_id,
|
||||
column_value,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
header_type,
|
||||
issues_count,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
disableIssueCreation,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
} = props;
|
||||
|
||||
const state = column_value ?? null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{state &&
|
||||
(sub_group_by && header_type === "sub_group_by" ? (
|
||||
<HeaderSubGroupByCard
|
||||
column_id={column_id}
|
||||
icon={<Icon stateGroup={state?.group} color={state?.color} />}
|
||||
title={state?.name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<HeaderGroupByCard
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
column_id={column_id}
|
||||
icon={<Icon stateGroup={state?.group} color={state?.color} />}
|
||||
title={state?.name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
issuePayload={{ state: state?.id }}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { StateHeader } from "./state";
|
||||
import { StateGroupHeader } from "./state-group";
|
||||
import { AssigneesHeader } from "./assignee";
|
||||
import { PriorityHeader } from "./priority";
|
||||
import { LabelHeader } from "./label";
|
||||
import { CreatedByHeader } from "./created_by";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { IIssue } from "types";
|
||||
|
||||
export interface IKanBanSubGroupByHeaderRoot {
|
||||
column_id: string;
|
||||
column_value: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
}
|
||||
|
||||
export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> = observer((props) => {
|
||||
const {
|
||||
column_id,
|
||||
column_value,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
issues_count,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
disableIssueCreation,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
{sub_group_by && sub_group_by === "state" && (
|
||||
<StateHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`sub_group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
{sub_group_by && sub_group_by === "state_detail.group" && (
|
||||
<StateGroupHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`sub_group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
{sub_group_by && sub_group_by === "priority" && (
|
||||
<PriorityHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`sub_group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
{sub_group_by && sub_group_by === "labels" && (
|
||||
<LabelHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`sub_group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
{sub_group_by && sub_group_by === "assignees" && (
|
||||
<AssigneesHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`sub_group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
{sub_group_by && sub_group_by === "created_by" && (
|
||||
<CreatedByHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`sub_group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
||||
106
web/components/issues/issue-layouts/kanban/kanban-group.tsx
Normal file
106
web/components/issues/issue-layouts/kanban/kanban-group.tsx
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import { Droppable } from "@hello-pangea/dnd";
|
||||
//types
|
||||
import {
|
||||
TGroupedIssues,
|
||||
TIssue,
|
||||
IIssueDisplayProperties,
|
||||
IIssueMap,
|
||||
TSubGroupedIssues,
|
||||
TUnGroupedIssues,
|
||||
} from "@plane/types";
|
||||
import { EIssueActions } from "../types";
|
||||
//components
|
||||
import { KanBanQuickAddIssueForm, KanbanIssueBlocksList } from ".";
|
||||
|
||||
interface IKanbanGroup {
|
||||
groupId: string;
|
||||
issuesMap: IIssueMap;
|
||||
issueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues;
|
||||
displayProperties: IIssueDisplayProperties | undefined;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
sub_group_id: string;
|
||||
isDragDisabled: boolean;
|
||||
handleIssues: (issue: TIssue, action: EIssueActions) => void;
|
||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
enableQuickIssueCreate?: boolean;
|
||||
quickAddCallback?: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: TIssue,
|
||||
viewId?: string
|
||||
) => Promise<TIssue | undefined>;
|
||||
viewId?: string;
|
||||
disableIssueCreation?: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
verticalPosition: any;
|
||||
}
|
||||
|
||||
export const KanbanGroup = (props: IKanbanGroup) => {
|
||||
const {
|
||||
groupId,
|
||||
sub_group_id,
|
||||
group_by,
|
||||
sub_group_by,
|
||||
issuesMap,
|
||||
displayProperties,
|
||||
verticalPosition,
|
||||
issueIds,
|
||||
isDragDisabled,
|
||||
handleIssues,
|
||||
quickActions,
|
||||
canEditProperties,
|
||||
enableQuickIssueCreate,
|
||||
disableIssueCreation,
|
||||
quickAddCallback,
|
||||
viewId,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className={`${verticalPosition ? `min-h-[150px] w-[0px] overflow-hidden` : `w-full transition-all`}`}>
|
||||
<Droppable droppableId={`${groupId}__${sub_group_id}`}>
|
||||
{(provided: any, snapshot: any) => (
|
||||
<div
|
||||
className={`relative h-full w-full transition-all ${
|
||||
snapshot.isDraggingOver ? `bg-custom-background-80` : ``
|
||||
}`}
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
{!verticalPosition ? (
|
||||
<KanbanIssueBlocksList
|
||||
sub_group_id={sub_group_id}
|
||||
columnId={groupId}
|
||||
issuesMap={issuesMap}
|
||||
issueIds={(issueIds as TGroupedIssues)?.[groupId] || []}
|
||||
displayProperties={displayProperties}
|
||||
isDragDisabled={isDragDisabled}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
|
||||
<div className="sticky bottom-0 z-[0] w-full flex-shrink-0 bg-custom-background-90 py-1">
|
||||
{enableQuickIssueCreate && !disableIssueCreation && (
|
||||
<KanBanQuickAddIssueForm
|
||||
formKey="name"
|
||||
groupId={groupId}
|
||||
subGroupId={sub_group_id}
|
||||
prePopulatedData={{
|
||||
...(group_by && { [group_by]: groupId }),
|
||||
...(sub_group_by && sub_group_id !== "null" && { [sub_group_by]: sub_group_id }),
|
||||
}}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,197 +0,0 @@
|
|||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// lucide icons
|
||||
import { Layers, Link, Paperclip } from "lucide-react";
|
||||
// components
|
||||
import { IssuePropertyState } from "../properties/state";
|
||||
import { IssuePropertyPriority } from "../properties/priority";
|
||||
import { IssuePropertyLabels } from "../properties/labels";
|
||||
import { IssuePropertyAssignee } from "../properties/assignee";
|
||||
import { IssuePropertyEstimates } from "../properties/estimates";
|
||||
import { IssuePropertyDate } from "../properties/date";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
import { IIssue, IIssueDisplayProperties, IState, TIssuePriorities } from "types";
|
||||
|
||||
export interface IKanBanProperties {
|
||||
sub_group_id: string;
|
||||
columnId: string;
|
||||
issue: IIssue;
|
||||
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => void;
|
||||
displayProperties: IIssueDisplayProperties | null;
|
||||
showEmptyGroup: boolean;
|
||||
isReadOnly: boolean;
|
||||
}
|
||||
|
||||
export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) => {
|
||||
const { sub_group_id, columnId: group_id, issue, handleIssues, displayProperties, isReadOnly } = props;
|
||||
|
||||
const handleState = (state: IState) => {
|
||||
handleIssues(
|
||||
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
|
||||
!group_id && group_id === "null" ? null : group_id,
|
||||
{ ...issue, state: state.id }
|
||||
);
|
||||
};
|
||||
|
||||
const handlePriority = (value: TIssuePriorities) => {
|
||||
handleIssues(
|
||||
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
|
||||
!group_id && group_id === "null" ? null : group_id,
|
||||
{ ...issue, priority: value }
|
||||
);
|
||||
};
|
||||
|
||||
const handleLabel = (ids: string[]) => {
|
||||
handleIssues(
|
||||
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
|
||||
!group_id && group_id === "null" ? null : group_id,
|
||||
{ ...issue, labels: ids }
|
||||
);
|
||||
};
|
||||
|
||||
const handleAssignee = (ids: string[]) => {
|
||||
handleIssues(
|
||||
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
|
||||
!group_id && group_id === "null" ? null : group_id,
|
||||
{ ...issue, assignees: ids }
|
||||
);
|
||||
};
|
||||
|
||||
const handleStartDate = (date: string | null) => {
|
||||
handleIssues(
|
||||
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
|
||||
!group_id && group_id === "null" ? null : group_id,
|
||||
{ ...issue, start_date: date }
|
||||
);
|
||||
};
|
||||
|
||||
const handleTargetDate = (date: string | null) => {
|
||||
handleIssues(
|
||||
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
|
||||
!group_id && group_id === "null" ? null : group_id,
|
||||
{ ...issue, target_date: date }
|
||||
);
|
||||
};
|
||||
|
||||
const handleEstimate = (value: number | null) => {
|
||||
handleIssues(
|
||||
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
|
||||
!group_id && group_id === "null" ? null : group_id,
|
||||
{ ...issue, estimate_point: value }
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap items-center gap-2 whitespace-nowrap">
|
||||
{/* basic properties */}
|
||||
{/* state */}
|
||||
{displayProperties && displayProperties?.state && (
|
||||
<IssuePropertyState
|
||||
projectId={issue?.project_detail?.id || null}
|
||||
value={issue?.state || null}
|
||||
defaultOptions={issue?.state_detail ? [issue.state_detail] : []}
|
||||
onChange={handleState}
|
||||
disabled={isReadOnly}
|
||||
hideDropdownArrow
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* priority */}
|
||||
{displayProperties && displayProperties?.priority && (
|
||||
<IssuePropertyPriority
|
||||
value={issue?.priority || null}
|
||||
onChange={handlePriority}
|
||||
disabled={isReadOnly}
|
||||
hideDropdownArrow
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* label */}
|
||||
{displayProperties && displayProperties?.labels && (
|
||||
<IssuePropertyLabels
|
||||
projectId={issue?.project_detail?.id || null}
|
||||
value={issue?.labels || null}
|
||||
defaultOptions={issue?.label_details ? issue.label_details : []}
|
||||
onChange={handleLabel}
|
||||
disabled={isReadOnly}
|
||||
hideDropdownArrow
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* start date */}
|
||||
{displayProperties && displayProperties?.start_date && (
|
||||
<IssuePropertyDate
|
||||
value={issue?.start_date || null}
|
||||
onChange={(date) => handleStartDate(date)}
|
||||
disabled={isReadOnly}
|
||||
type="start_date"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* target/due date */}
|
||||
{displayProperties && displayProperties?.due_date && (
|
||||
<IssuePropertyDate
|
||||
value={issue?.target_date || null}
|
||||
onChange={(date) => handleTargetDate(date)}
|
||||
disabled={isReadOnly}
|
||||
type="target_date"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* assignee */}
|
||||
{displayProperties && displayProperties?.assignee && (
|
||||
<IssuePropertyAssignee
|
||||
projectId={issue?.project_detail?.id || null}
|
||||
value={issue?.assignees || null}
|
||||
defaultOptions={issue?.assignee_details ? issue.assignee_details : []}
|
||||
hideDropdownArrow
|
||||
onChange={handleAssignee}
|
||||
disabled={isReadOnly}
|
||||
multiple
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* estimates */}
|
||||
{displayProperties && displayProperties?.estimate && (
|
||||
<IssuePropertyEstimates
|
||||
projectId={issue?.project_detail?.id || null}
|
||||
value={issue?.estimate_point || null}
|
||||
onChange={handleEstimate}
|
||||
disabled={isReadOnly}
|
||||
hideDropdownArrow
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* extra render properties */}
|
||||
{/* sub-issues */}
|
||||
{displayProperties && displayProperties?.sub_issue_count && !!issue?.sub_issues_count && (
|
||||
<Tooltip tooltipHeading="Sub-issues" tooltipContent={`${issue.sub_issues_count}`}>
|
||||
<div className="flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1 cursor-default">
|
||||
<Layers className="h-3 w-3 flex-shrink-0" strokeWidth={2} />
|
||||
<div className="text-xs">{issue.sub_issues_count}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{/* attachments */}
|
||||
{displayProperties && displayProperties?.attachment_count && !!issue?.attachment_count && (
|
||||
<Tooltip tooltipHeading="Attachments" tooltipContent={`${issue.attachment_count}`}>
|
||||
<div className="flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1 cursor-default">
|
||||
<Paperclip className="h-3 w-3 flex-shrink-0" strokeWidth={2} />
|
||||
<div className="text-xs">{issue.attachment_count}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{/* link */}
|
||||
{displayProperties && displayProperties?.link && !!issue?.link_count && (
|
||||
<Tooltip tooltipHeading="Links" tooltipContent={`${issue.link_count}`}>
|
||||
<div className="flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1 cursor-default">
|
||||
<Link className="h-3 w-3 flex-shrink-0" strokeWidth={2} />
|
||||
<div className="text-xs">{issue.link_count}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
@ -1,18 +1,17 @@
|
|||
import { useEffect, useState, useRef } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
// hooks
|
||||
import { useProject, useWorkspace } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import useKeypress from "hooks/use-keypress";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
// helpers
|
||||
import { createIssuePayload } from "helpers/issue.helper";
|
||||
// types
|
||||
import { IIssue, IProject } from "types";
|
||||
import { TIssue } from "@plane/types";
|
||||
|
||||
const Inputs = (props: any) => {
|
||||
const { register, setFocus, projectDetail } = props;
|
||||
|
|
@ -37,35 +36,34 @@ const Inputs = (props: any) => {
|
|||
};
|
||||
|
||||
interface IKanBanQuickAddIssueForm {
|
||||
formKey: keyof IIssue;
|
||||
formKey: keyof TIssue;
|
||||
groupId?: string;
|
||||
subGroupId?: string | null;
|
||||
prePopulatedData?: Partial<IIssue>;
|
||||
prePopulatedData?: Partial<TIssue>;
|
||||
quickAddCallback?: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: IIssue,
|
||||
data: TIssue,
|
||||
viewId?: string
|
||||
) => Promise<IIssue | undefined>;
|
||||
) => Promise<TIssue | undefined>;
|
||||
viewId?: string;
|
||||
}
|
||||
|
||||
const defaultValues: Partial<IIssue> = {
|
||||
const defaultValues: Partial<TIssue> = {
|
||||
name: "",
|
||||
};
|
||||
|
||||
export const KanBanQuickAddIssueForm: React.FC<IKanBanQuickAddIssueForm> = observer((props) => {
|
||||
const { formKey, groupId, prePopulatedData, quickAddCallback, viewId } = props;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// store hooks
|
||||
const { getWorkspaceBySlug } = useWorkspace();
|
||||
const { getProjectById } = useProject();
|
||||
|
||||
const { workspace: workspaceStore, project: projectStore } = useMobxStore();
|
||||
|
||||
const workspaceDetail = (workspaceSlug && workspaceStore.getWorkspaceBySlug(workspaceSlug)) || null;
|
||||
const projectDetail: IProject | null =
|
||||
(workspaceSlug && projectId && projectStore.getProjectById(workspaceSlug, projectId)) || null;
|
||||
const workspaceDetail = workspaceSlug ? getWorkspaceBySlug(workspaceSlug.toString()) : null;
|
||||
const projectDetail = projectId ? getProjectById(projectId.toString()) : null;
|
||||
|
||||
const ref = useRef<HTMLFormElement>(null);
|
||||
|
||||
|
|
@ -82,14 +80,14 @@ export const KanBanQuickAddIssueForm: React.FC<IKanBanQuickAddIssueForm> = obser
|
|||
setFocus,
|
||||
register,
|
||||
formState: { isSubmitting },
|
||||
} = useForm<IIssue>({ defaultValues });
|
||||
} = useForm<TIssue>({ defaultValues });
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) reset({ ...defaultValues });
|
||||
}, [isOpen, reset]);
|
||||
|
||||
const onSubmitHandler = async (formData: IIssue) => {
|
||||
if (isSubmitting || !groupId || !workspaceDetail || !projectDetail) return;
|
||||
const onSubmitHandler = async (formData: TIssue) => {
|
||||
if (isSubmitting || !groupId || !workspaceDetail || !projectDetail || !workspaceSlug || !projectId) return;
|
||||
|
||||
reset({ ...defaultValues });
|
||||
|
||||
|
|
@ -101,8 +99,8 @@ export const KanBanQuickAddIssueForm: React.FC<IKanBanQuickAddIssueForm> = obser
|
|||
try {
|
||||
quickAddCallback &&
|
||||
(await quickAddCallback(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
{
|
||||
...payload,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
import React from "react";
|
||||
import React, { useMemo } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useIssues } from "hooks/store";
|
||||
// ui
|
||||
import { CycleIssueQuickActions } from "components/issues";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
import { TIssue } from "@plane/types";
|
||||
import { EIssueActions } from "../../types";
|
||||
// components
|
||||
import { BaseKanBanRoot } from "../base-kanban-root";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { IGroupedIssues, IIssueResponse, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
|
||||
export interface ICycleKanBanLayout {}
|
||||
|
||||
|
|
@ -20,78 +19,42 @@ export const CycleKanBanLayout: React.FC = observer(() => {
|
|||
const { workspaceSlug, projectId, cycleId } = router.query;
|
||||
|
||||
// store
|
||||
const {
|
||||
cycleIssues: cycleIssueStore,
|
||||
cycleIssuesFilter: cycleIssueFilterStore,
|
||||
cycleIssueKanBanView: cycleIssueKanBanViewStore,
|
||||
kanBanHelpers: kanBanHelperStore,
|
||||
cycle: { fetchCycleWithId },
|
||||
} = useMobxStore();
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.CYCLE);
|
||||
|
||||
const issueActions = {
|
||||
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug || !cycleId) return;
|
||||
const issueActions = useMemo(
|
||||
() => ({
|
||||
[EIssueActions.UPDATE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !cycleId) return;
|
||||
|
||||
await cycleIssueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue, cycleId.toString());
|
||||
fetchCycleWithId(workspaceSlug.toString(), issue.project, cycleId.toString());
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug || !cycleId) return;
|
||||
await issues.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, issue, cycleId.toString());
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !cycleId) return;
|
||||
|
||||
await cycleIssueStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id, cycleId.toString());
|
||||
fetchCycleWithId(workspaceSlug.toString(), issue.project, cycleId.toString());
|
||||
},
|
||||
[EIssueActions.REMOVE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug || !cycleId || !issue.bridge_id) return;
|
||||
await issues.removeIssue(workspaceSlug.toString(), issue.project_id, issue.id, cycleId.toString());
|
||||
},
|
||||
[EIssueActions.REMOVE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !cycleId) return;
|
||||
|
||||
await cycleIssueStore.removeIssueFromCycle(
|
||||
workspaceSlug.toString(),
|
||||
issue.project,
|
||||
cycleId.toString(),
|
||||
issue.id,
|
||||
issue.bridge_id
|
||||
);
|
||||
fetchCycleWithId(workspaceSlug.toString(), issue.project, cycleId.toString());
|
||||
},
|
||||
};
|
||||
|
||||
const handleDragDrop = async (
|
||||
source: any,
|
||||
destination: any,
|
||||
subGroupBy: string | null,
|
||||
groupBy: string | null,
|
||||
issues: IIssueResponse | undefined,
|
||||
issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined
|
||||
) => {
|
||||
if (workspaceSlug && projectId && cycleId)
|
||||
return await kanBanHelperStore.handleDragDrop(
|
||||
source,
|
||||
destination,
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
cycleIssueStore,
|
||||
subGroupBy,
|
||||
groupBy,
|
||||
issues,
|
||||
issueWithIds,
|
||||
cycleId.toString()
|
||||
);
|
||||
};
|
||||
await issues.removeIssueFromCycle(workspaceSlug.toString(), issue.project_id, cycleId.toString(), issue.id);
|
||||
},
|
||||
}),
|
||||
[issues, workspaceSlug, cycleId]
|
||||
);
|
||||
|
||||
return (
|
||||
<BaseKanBanRoot
|
||||
issueActions={issueActions}
|
||||
issueStore={cycleIssueStore}
|
||||
issuesFilterStore={cycleIssueFilterStore}
|
||||
kanbanViewStore={cycleIssueKanBanViewStore}
|
||||
issues={issues}
|
||||
issuesFilter={issuesFilter}
|
||||
showLoader={true}
|
||||
QuickActions={CycleIssueQuickActions}
|
||||
viewId={cycleId?.toString() ?? ""}
|
||||
currentStore={EProjectStore.CYCLE}
|
||||
handleDragDrop={handleDragDrop}
|
||||
addIssuesToView={(issues: string[]) =>
|
||||
cycleIssueStore.addIssueToCycle(workspaceSlug?.toString() ?? "", cycleId?.toString() ?? "", issues)
|
||||
}
|
||||
currentStore={EIssuesStoreType.CYCLE}
|
||||
addIssuesToView={(issueIds: string[]) => {
|
||||
if (!workspaceSlug || !projectId || !cycleId) throw new Error();
|
||||
return issues.addIssueToCycle(workspaceSlug.toString(), projectId.toString(), cycleId.toString(), issueIds);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useIssues } from "hooks/store";
|
||||
// components
|
||||
import { ProjectIssueQuickActions } from "components/issues";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
import { TIssue } from "@plane/types";
|
||||
// constants
|
||||
import { EIssueActions } from "../../types";
|
||||
import { BaseKanBanRoot } from "../base-kanban-root";
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export interface IKanBanLayout {}
|
||||
|
||||
|
|
@ -16,31 +18,30 @@ export const DraftKanBanLayout: React.FC = observer(() => {
|
|||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const {
|
||||
projectDraftIssues: issueStore,
|
||||
projectDraftIssuesFilter: projectIssuesFilterStore,
|
||||
issueKanBanView: issueKanBanViewStore,
|
||||
} = useMobxStore();
|
||||
// store
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.DRAFT);
|
||||
|
||||
const issueActions = {
|
||||
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
const issueActions = useMemo(
|
||||
() => ({
|
||||
[EIssueActions.UPDATE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
await issueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
await issues.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, issue);
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
await issueStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id);
|
||||
},
|
||||
};
|
||||
await issues.removeIssue(workspaceSlug.toString(), issue.project_id, issue.id);
|
||||
},
|
||||
}),
|
||||
[issues, workspaceSlug]
|
||||
);
|
||||
|
||||
return (
|
||||
<BaseKanBanRoot
|
||||
issueActions={issueActions}
|
||||
issuesFilterStore={projectIssuesFilterStore}
|
||||
issueStore={issueStore}
|
||||
kanbanViewStore={issueKanBanViewStore}
|
||||
issuesFilter={issuesFilter}
|
||||
issues={issues}
|
||||
showLoader={true}
|
||||
QuickActions={ProjectIssueQuickActions}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
import React from "react";
|
||||
import React, { useMemo } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hook
|
||||
import { useIssues } from "hooks/store";
|
||||
// components
|
||||
import { ModuleIssueQuickActions } from "components/issues";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
import { TIssue } from "@plane/types";
|
||||
// constants
|
||||
import { EIssueActions } from "../../types";
|
||||
import { BaseKanBanRoot } from "../base-kanban-root";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { IGroupedIssues, IIssueResponse, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
|
||||
export interface IModuleKanBanLayout {}
|
||||
|
||||
|
|
@ -20,77 +19,42 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
|
|||
const { workspaceSlug, projectId, moduleId } = router.query;
|
||||
|
||||
// store
|
||||
const {
|
||||
moduleIssues: moduleIssueStore,
|
||||
moduleIssuesFilter: moduleIssueFilterStore,
|
||||
moduleIssueKanBanView: moduleIssueKanBanViewStore,
|
||||
kanBanHelpers: kanBanHelperStore,
|
||||
module: { fetchModuleDetails },
|
||||
} = useMobxStore();
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.MODULE);
|
||||
|
||||
const issueActions = {
|
||||
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug || !moduleId) return;
|
||||
const issueActions = useMemo(
|
||||
() => ({
|
||||
[EIssueActions.UPDATE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !moduleId) return;
|
||||
|
||||
await moduleIssueStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue, moduleId.toString());
|
||||
fetchModuleDetails(workspaceSlug.toString(), issue.project, moduleId.toString());
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug || !moduleId) return;
|
||||
await issues.updateIssue(workspaceSlug.toString(), issue.project_id, issue.id, issue, moduleId.toString());
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !moduleId) return;
|
||||
|
||||
await moduleIssueStore.removeIssue(workspaceSlug.toString(), issue.project, issue.id, moduleId.toString());
|
||||
fetchModuleDetails(workspaceSlug.toString(), issue.project, moduleId.toString());
|
||||
},
|
||||
[EIssueActions.REMOVE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug || !moduleId || !issue.bridge_id) return;
|
||||
await issues.removeIssue(workspaceSlug.toString(), issue.project_id, issue.id, moduleId.toString());
|
||||
},
|
||||
[EIssueActions.REMOVE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !moduleId) return;
|
||||
|
||||
await moduleIssueStore.removeIssueFromModule(
|
||||
workspaceSlug.toString(),
|
||||
issue.project,
|
||||
moduleId.toString(),
|
||||
issue.id,
|
||||
issue.bridge_id
|
||||
);
|
||||
fetchModuleDetails(workspaceSlug.toString(), issue.project, moduleId.toString());
|
||||
},
|
||||
};
|
||||
await issues.removeIssueFromModule(workspaceSlug.toString(), issue.project_id, moduleId.toString(), issue.id);
|
||||
},
|
||||
}),
|
||||
[issues, workspaceSlug, moduleId]
|
||||
);
|
||||
|
||||
const handleDragDrop = async (
|
||||
source: any,
|
||||
destination: any,
|
||||
subGroupBy: string | null,
|
||||
groupBy: string | null,
|
||||
issues: IIssueResponse | undefined,
|
||||
issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined
|
||||
) => {
|
||||
if (workspaceSlug && projectId && moduleId)
|
||||
return await kanBanHelperStore.handleDragDrop(
|
||||
source,
|
||||
destination,
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
moduleIssueStore,
|
||||
subGroupBy,
|
||||
groupBy,
|
||||
issues,
|
||||
issueWithIds,
|
||||
moduleId.toString()
|
||||
);
|
||||
};
|
||||
return (
|
||||
<BaseKanBanRoot
|
||||
issueActions={issueActions}
|
||||
issueStore={moduleIssueStore}
|
||||
issuesFilterStore={moduleIssueFilterStore}
|
||||
kanbanViewStore={moduleIssueKanBanViewStore}
|
||||
issues={issues}
|
||||
issuesFilter={issuesFilter}
|
||||
showLoader={true}
|
||||
QuickActions={ModuleIssueQuickActions}
|
||||
viewId={moduleId?.toString() ?? ""}
|
||||
currentStore={EProjectStore.MODULE}
|
||||
handleDragDrop={handleDragDrop}
|
||||
addIssuesToView={(issues: string[]) =>
|
||||
moduleIssueStore.addIssueToModule(workspaceSlug?.toString() ?? "", moduleId?.toString() ?? "", issues)
|
||||
}
|
||||
viewId={moduleId?.toString()}
|
||||
currentStore={EIssuesStoreType.MODULE}
|
||||
addIssuesToView={(issueIds: string[]) => {
|
||||
if (!workspaceSlug || !projectId || !moduleId) throw new Error();
|
||||
return issues.addIssueToModule(workspaceSlug.toString(), projectId.toString(), moduleId.toString(), issueIds);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,56 +1,58 @@
|
|||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useIssues, useUser } from "hooks/store";
|
||||
// components
|
||||
import { ProjectIssueQuickActions } from "components/issues";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
import { TIssue } from "@plane/types";
|
||||
// constants
|
||||
import { EIssueActions } from "../../types";
|
||||
import { BaseKanBanRoot } from "../base-kanban-root";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
import { EUserProjectRoles } from "constants/project";
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
import { useMemo } from "react";
|
||||
|
||||
export const ProfileIssuesKanBanLayout: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, userId } = router.query as { workspaceSlug: string; userId: string };
|
||||
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROFILE);
|
||||
|
||||
const {
|
||||
workspaceProfileIssues: profileIssuesStore,
|
||||
workspaceProfileIssuesFilter: profileIssueFiltersStore,
|
||||
workspaceMember: { currentWorkspaceUserProjectsRole },
|
||||
issueKanBanView: issueKanBanViewStore,
|
||||
} = useMobxStore();
|
||||
membership: { currentWorkspaceAllProjectsRole },
|
||||
} = useUser();
|
||||
|
||||
const issueActions = {
|
||||
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug || !userId) return;
|
||||
const issueActions = useMemo(
|
||||
() => ({
|
||||
[EIssueActions.UPDATE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !userId) return;
|
||||
|
||||
await profileIssuesStore.updateIssue(workspaceSlug, userId, issue.id, issue);
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug || !userId) return;
|
||||
await issues.updateIssue(workspaceSlug, userId, issue.id, issue);
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug || !userId) return;
|
||||
|
||||
await profileIssuesStore.removeIssue(workspaceSlug, issue.project, issue.id, userId);
|
||||
},
|
||||
};
|
||||
await issues.removeIssue(workspaceSlug, issue.project_id, issue.id, userId);
|
||||
},
|
||||
}),
|
||||
[issues, workspaceSlug, userId]
|
||||
);
|
||||
|
||||
const canEditPropertiesBasedOnProject = (projectId: string) => {
|
||||
const currentProjectRole = currentWorkspaceUserProjectsRole && currentWorkspaceUserProjectsRole[projectId];
|
||||
const currentProjectRole = currentWorkspaceAllProjectsRole && currentWorkspaceAllProjectsRole[projectId];
|
||||
|
||||
return !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||
return !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseKanBanRoot
|
||||
issueActions={issueActions}
|
||||
issuesFilterStore={profileIssueFiltersStore}
|
||||
issueStore={profileIssuesStore}
|
||||
kanbanViewStore={issueKanBanViewStore}
|
||||
issuesFilter={issuesFilter}
|
||||
issues={issues}
|
||||
showLoader={true}
|
||||
QuickActions={ProjectIssueQuickActions}
|
||||
currentStore={EProjectStore.PROFILE}
|
||||
currentStore={EIssuesStoreType.PROFILE}
|
||||
canEditPropertiesBasedOnProject={canEditPropertiesBasedOnProject}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,73 +1,49 @@
|
|||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMemo } from "react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { useIssues } from "hooks/store/use-issues";
|
||||
// components
|
||||
import { ProjectIssueQuickActions } from "components/issues";
|
||||
import { BaseKanBanRoot } from "../base-kanban-root";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
import { TIssue } from "@plane/types";
|
||||
// constants
|
||||
import { EIssueActions } from "../../types";
|
||||
import { BaseKanBanRoot } from "../base-kanban-root";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { IGroupedIssues, IIssueResponse, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
|
||||
export interface IKanBanLayout {}
|
||||
|
||||
export const KanBanLayout: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||
const { workspaceSlug } = router.query as { workspaceSlug: string; projectId: string };
|
||||
|
||||
const {
|
||||
projectIssues: issueStore,
|
||||
projectIssuesFilter: issuesFilterStore,
|
||||
issueKanBanView: issueKanBanViewStore,
|
||||
kanBanHelpers: kanBanHelperStore,
|
||||
} = useMobxStore();
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT);
|
||||
|
||||
const issueActions = {
|
||||
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
const issueActions = useMemo(
|
||||
() => ({
|
||||
[EIssueActions.UPDATE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
await issueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue);
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
await issues.updateIssue(workspaceSlug, issue.project_id, issue.id, issue);
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
await issueStore.removeIssue(workspaceSlug, issue.project, issue.id);
|
||||
},
|
||||
};
|
||||
|
||||
const handleDragDrop = async (
|
||||
source: any,
|
||||
destination: any,
|
||||
subGroupBy: string | null,
|
||||
groupBy: string | null,
|
||||
issues: IIssueResponse | undefined,
|
||||
issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined
|
||||
) =>
|
||||
await kanBanHelperStore.handleDragDrop(
|
||||
source,
|
||||
destination,
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
issueStore,
|
||||
subGroupBy,
|
||||
groupBy,
|
||||
issues,
|
||||
issueWithIds
|
||||
);
|
||||
await issues.removeIssue(workspaceSlug, issue.project_id, issue.id);
|
||||
},
|
||||
}),
|
||||
[issues, workspaceSlug]
|
||||
);
|
||||
|
||||
return (
|
||||
<BaseKanBanRoot
|
||||
issueActions={issueActions}
|
||||
issuesFilterStore={issuesFilterStore}
|
||||
issueStore={issueStore}
|
||||
kanbanViewStore={issueKanBanViewStore}
|
||||
issues={issues}
|
||||
issuesFilter={issuesFilter}
|
||||
showLoader={true}
|
||||
QuickActions={ProjectIssueQuickActions}
|
||||
currentStore={EProjectStore.PROJECT}
|
||||
handleDragDrop={handleDragDrop}
|
||||
currentStore={EIssuesStoreType.PROJECT}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,73 +1,47 @@
|
|||
import React from "react";
|
||||
import React, { useMemo } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useRouter } from "next/router";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useIssues } from "hooks/store";
|
||||
// constant
|
||||
import { IIssue } from "types";
|
||||
import { TIssue } from "@plane/types";
|
||||
import { EIssueActions } from "../../types";
|
||||
import { ProjectIssueQuickActions } from "../../quick-action-dropdowns";
|
||||
// components
|
||||
import { BaseKanBanRoot } from "../base-kanban-root";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { IGroupedIssues, IIssueResponse, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
|
||||
import { EIssuesStoreType } from "constants/issue";
|
||||
|
||||
export interface IViewKanBanLayout {}
|
||||
|
||||
export const ProjectViewKanBanLayout: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||
const { workspaceSlug } = router.query as { workspaceSlug: string; projectId: string };
|
||||
|
||||
const {
|
||||
viewIssues: projectViewIssuesStore,
|
||||
viewIssuesFilter: projectIssueViewFiltersStore,
|
||||
issueKanBanView: projectViewIssueKanBanViewStore,
|
||||
kanBanHelpers: kanBanHelperStore,
|
||||
} = useMobxStore();
|
||||
const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT_VIEW);
|
||||
const issueActions = useMemo(
|
||||
() => ({
|
||||
[EIssueActions.UPDATE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
const issueActions = {
|
||||
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
await issues.updateIssue(workspaceSlug, issue.project_id, issue.id, issue);
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: TIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
await projectViewIssuesStore.updateIssue(workspaceSlug, issue.project, issue.id, issue);
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
await projectViewIssuesStore.removeIssue(workspaceSlug, issue.project, issue.id);
|
||||
},
|
||||
};
|
||||
|
||||
const handleDragDrop = async (
|
||||
source: any,
|
||||
destination: any,
|
||||
subGroupBy: string | null,
|
||||
groupBy: string | null,
|
||||
issues: IIssueResponse | undefined,
|
||||
issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined
|
||||
) =>
|
||||
await kanBanHelperStore.handleDragDrop(
|
||||
source,
|
||||
destination,
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
projectViewIssuesStore,
|
||||
subGroupBy,
|
||||
groupBy,
|
||||
issues,
|
||||
issueWithIds
|
||||
);
|
||||
await issues.removeIssue(workspaceSlug, issue.project_id, issue.id);
|
||||
},
|
||||
}),
|
||||
[issues, workspaceSlug]
|
||||
);
|
||||
|
||||
return (
|
||||
<BaseKanBanRoot
|
||||
issueActions={issueActions}
|
||||
issuesFilterStore={projectIssueViewFiltersStore}
|
||||
issueStore={projectViewIssuesStore}
|
||||
kanbanViewStore={projectViewIssueKanBanViewStore}
|
||||
issuesFilter={issuesFilter}
|
||||
issues={issues}
|
||||
showLoader={true}
|
||||
QuickActions={ProjectIssueQuickActions}
|
||||
currentStore={EProjectStore.PROJECT_VIEW}
|
||||
handleDragDrop={handleDragDrop}
|
||||
currentStore={EIssuesStoreType.PROJECT_VIEW}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,142 +1,111 @@
|
|||
import React from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { KanBanGroupByHeaderRoot } from "./headers/group-by-root";
|
||||
import { KanBanSubGroupByHeaderRoot } from "./headers/sub-group-by-root";
|
||||
import { KanBan } from "./default";
|
||||
import { HeaderSubGroupByCard } from "./headers/sub-group-by-card";
|
||||
import { HeaderGroupByCard } from "./headers/group-by-card";
|
||||
// types
|
||||
import { IIssue, IIssueDisplayProperties, IIssueLabel, IProject, IState, IUserLite } from "types";
|
||||
import { IIssueResponse, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
|
||||
import {
|
||||
GroupByColumnTypes,
|
||||
IGroupByColumn,
|
||||
TGroupedIssues,
|
||||
TIssue,
|
||||
IIssueDisplayProperties,
|
||||
IIssueMap,
|
||||
TSubGroupedIssues,
|
||||
TUnGroupedIssues,
|
||||
} from "@plane/types";
|
||||
// constants
|
||||
import { getValueFromObject } from "constants/issue";
|
||||
import { EIssueActions } from "../types";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { useLabel, useMember, useProject, useProjectState } from "hooks/store";
|
||||
import { getGroupByColumns } from "../utils";
|
||||
import { TCreateModalStoreTypes } from "constants/issue";
|
||||
|
||||
interface ISubGroupSwimlaneHeader {
|
||||
issues: IIssueResponse;
|
||||
issueIds: any;
|
||||
issueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
list: any;
|
||||
listKey: string;
|
||||
list: IGroupByColumn[];
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
}
|
||||
const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
|
||||
issueIds,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
list,
|
||||
listKey,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
disableIssueCreation,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
}) => {
|
||||
const calculateIssueCount = (column_id: string) => {
|
||||
let issueCount = 0;
|
||||
issueIds &&
|
||||
Object.keys(issueIds)?.forEach((_issueKey: any) => {
|
||||
issueCount += issueIds?.[_issueKey]?.[column_id]?.length || 0;
|
||||
});
|
||||
return issueCount;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex h-max min-h-full w-full items-center">
|
||||
{list &&
|
||||
list.length > 0 &&
|
||||
list.map((_list: any) => (
|
||||
<div className="flex w-[340px] flex-shrink-0 flex-col">
|
||||
<KanBanGroupByHeaderRoot
|
||||
column_id={getValueFromObject(_list, listKey) as string}
|
||||
column_value={_list}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
issues_count={calculateIssueCount(getValueFromObject(_list, listKey) as string)}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}) => (
|
||||
<div className="relative flex h-max min-h-full w-full items-center">
|
||||
{list &&
|
||||
list.length > 0 &&
|
||||
list.map((_list: IGroupByColumn) => (
|
||||
<div key={`${sub_group_by}_${_list.id}`} className="flex w-[340px] flex-shrink-0 flex-col">
|
||||
<HeaderGroupByCard
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
column_id={_list.id}
|
||||
icon={_list.Icon}
|
||||
title={_list.name}
|
||||
count={(issueIds as TGroupedIssues)?.[_list.id]?.length || 0}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
issuePayload={_list.payload}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
||||
issues: IIssueResponse;
|
||||
issueIds: any;
|
||||
order_by: string | null;
|
||||
issuesMap: IIssueMap;
|
||||
issueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues;
|
||||
showEmptyGroup: boolean;
|
||||
states: IState[] | null;
|
||||
stateGroups: any;
|
||||
priorities: any;
|
||||
labels: IIssueLabel[] | null;
|
||||
members: IUserLite[] | null;
|
||||
projects: IProject[] | null;
|
||||
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;
|
||||
displayProperties: IIssueDisplayProperties | null;
|
||||
displayProperties: IIssueDisplayProperties | undefined;
|
||||
handleIssues: (issue: TIssue, action: EIssueActions) => void;
|
||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
isDragStarted?: boolean;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
currentStore?: TCreateModalStoreTypes;
|
||||
enableQuickIssueCreate: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
||||
quickAddCallback?: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: IIssue,
|
||||
data: TIssue,
|
||||
viewId?: string
|
||||
) => Promise<IIssue | undefined>;
|
||||
) => Promise<TIssue | undefined>;
|
||||
viewId?: string;
|
||||
}
|
||||
const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||
const {
|
||||
issues,
|
||||
issuesMap,
|
||||
issueIds,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
order_by,
|
||||
list,
|
||||
listKey,
|
||||
handleIssues,
|
||||
quickActions,
|
||||
displayProperties,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
showEmptyGroup,
|
||||
states,
|
||||
stateGroups,
|
||||
priorities,
|
||||
labels,
|
||||
members,
|
||||
projects,
|
||||
isDragStarted,
|
||||
disableIssueCreation,
|
||||
enableQuickIssueCreate,
|
||||
canEditProperties,
|
||||
addIssuesToView,
|
||||
quickAddCallback,
|
||||
viewId,
|
||||
} = props;
|
||||
|
||||
const calculateIssueCount = (column_id: string) => {
|
||||
let issueCount = 0;
|
||||
issueIds?.[column_id] &&
|
||||
Object.keys(issueIds?.[column_id])?.forEach((_list: any) => {
|
||||
issueCount += issueIds?.[column_id]?.[_list]?.length || 0;
|
||||
const subGroupedIds = issueIds as TSubGroupedIssues;
|
||||
subGroupedIds?.[column_id] &&
|
||||
Object.keys(subGroupedIds?.[column_id])?.forEach((_list: any) => {
|
||||
issueCount += subGroupedIds?.[column_id]?.[_list]?.length || 0;
|
||||
});
|
||||
return issueCount;
|
||||
};
|
||||
|
|
@ -149,46 +118,36 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
|||
<div className="flex flex-shrink-0 flex-col">
|
||||
<div className="sticky top-[50px] z-[1] flex w-full items-center bg-custom-background-90 py-1">
|
||||
<div className="sticky left-0 flex-shrink-0 bg-custom-background-90 pr-2">
|
||||
<KanBanSubGroupByHeaderRoot
|
||||
column_id={getValueFromObject(_list, listKey) as string}
|
||||
column_value={_list}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
issues_count={calculateIssueCount(getValueFromObject(_list, listKey) as string)}
|
||||
<HeaderSubGroupByCard
|
||||
column_id={_list.id}
|
||||
icon={_list.Icon}
|
||||
title={_list.name || ""}
|
||||
count={calculateIssueCount(_list.id)}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full border-b border-dashed border-custom-border-400" />
|
||||
</div>
|
||||
{!kanBanToggle?.subgroupByIssuesVisibility.includes(getValueFromObject(_list, listKey) as string) && (
|
||||
{!kanBanToggle?.subgroupByIssuesVisibility.includes(_list.id) && (
|
||||
<div className="relative">
|
||||
<KanBan
|
||||
issues={issues}
|
||||
issueIds={issueIds?.[getValueFromObject(_list, listKey) as string]}
|
||||
issuesMap={issuesMap}
|
||||
issueIds={(issueIds as TSubGroupedIssues)?.[_list.id]}
|
||||
displayProperties={displayProperties}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_id={getValueFromObject(_list, listKey) as string}
|
||||
sub_group_id={_list.id}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
canEditProperties={canEditProperties}
|
||||
addIssuesToView={addIssuesToView}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -199,414 +158,101 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
|||
});
|
||||
|
||||
export interface IKanBanSwimLanes {
|
||||
issues: IIssueResponse;
|
||||
issueIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues;
|
||||
issuesMap: IIssueMap;
|
||||
issueIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues;
|
||||
displayProperties: IIssueDisplayProperties | undefined;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
order_by: string | null;
|
||||
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;
|
||||
displayProperties: IIssueDisplayProperties | null;
|
||||
handleIssues: (issue: TIssue, action: EIssueActions) => void;
|
||||
quickActions: (issue: TIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
showEmptyGroup: boolean;
|
||||
states: IState[] | null;
|
||||
stateGroups: any;
|
||||
priorities: any;
|
||||
labels: IIssueLabel[] | null;
|
||||
members: IUserLite[] | null;
|
||||
projects: IProject[] | null;
|
||||
isDragStarted?: boolean;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
currentStore?: TCreateModalStoreTypes;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<TIssue>;
|
||||
enableQuickIssueCreate: boolean;
|
||||
quickAddCallback?: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: IIssue,
|
||||
data: TIssue,
|
||||
viewId?: string
|
||||
) => Promise<IIssue | undefined>;
|
||||
) => Promise<TIssue | undefined>;
|
||||
viewId?: string;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
}
|
||||
|
||||
export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
const {
|
||||
issues,
|
||||
issuesMap,
|
||||
issueIds,
|
||||
displayProperties,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
order_by,
|
||||
handleIssues,
|
||||
quickActions,
|
||||
displayProperties,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
showEmptyGroup,
|
||||
states,
|
||||
stateGroups,
|
||||
priorities,
|
||||
labels,
|
||||
members,
|
||||
projects,
|
||||
isDragStarted,
|
||||
disableIssueCreation,
|
||||
enableQuickIssueCreate,
|
||||
canEditProperties,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
quickAddCallback,
|
||||
viewId,
|
||||
} = props;
|
||||
|
||||
const member = useMember();
|
||||
const project = useProject();
|
||||
const projectLabel = useLabel();
|
||||
const projectState = useProjectState();
|
||||
|
||||
const groupByList = getGroupByColumns(group_by as GroupByColumnTypes, project, projectLabel, projectState, member);
|
||||
const subGroupByList = getGroupByColumns(
|
||||
sub_group_by as GroupByColumnTypes,
|
||||
project,
|
||||
projectLabel,
|
||||
projectState,
|
||||
member
|
||||
);
|
||||
|
||||
if (!groupByList || !subGroupByList) return null;
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className="sticky top-0 z-[2] h-[50px] bg-custom-background-90">
|
||||
{group_by && group_by === "project" && (
|
||||
<SubGroupSwimlaneHeader
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
list={projects}
|
||||
listKey={`id`}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "state" && (
|
||||
<SubGroupSwimlaneHeader
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
list={states}
|
||||
listKey={`id`}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "state_detail.group" && (
|
||||
<SubGroupSwimlaneHeader
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
list={stateGroups}
|
||||
listKey={`key`}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "priority" && (
|
||||
<SubGroupSwimlaneHeader
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
list={priorities}
|
||||
listKey={`key`}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "labels" && (
|
||||
<SubGroupSwimlaneHeader
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
list={labels ? [...labels, { id: "None", name: "None" }] : labels}
|
||||
listKey={`id`}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "assignees" && (
|
||||
<SubGroupSwimlaneHeader
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
list={members ? [...members, { id: "None", display_name: "None" }] : members}
|
||||
listKey={`id`}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "created_by" && (
|
||||
<SubGroupSwimlaneHeader
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
list={members}
|
||||
listKey={`id`}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
<SubGroupSwimlaneHeader
|
||||
issueIds={issueIds}
|
||||
group_by={group_by}
|
||||
sub_group_by={sub_group_by}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
list={groupByList}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{sub_group_by && sub_group_by === "project" && (
|
||||
{sub_group_by && (
|
||||
<SubGroupSwimlane
|
||||
issues={issues}
|
||||
issuesMap={issuesMap}
|
||||
list={subGroupByList}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
displayProperties={displayProperties}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={projects}
|
||||
listKey={`id`}
|
||||
sub_group_by={sub_group_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
|
||||
{sub_group_by && sub_group_by === "state" && (
|
||||
<SubGroupSwimlane
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={states}
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
|
||||
{sub_group_by && sub_group_by === "state" && (
|
||||
<SubGroupSwimlane
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={states}
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
|
||||
{sub_group_by && sub_group_by === "state_detail.group" && (
|
||||
<SubGroupSwimlane
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={stateGroups}
|
||||
listKey={`key`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
|
||||
{sub_group_by && sub_group_by === "priority" && (
|
||||
<SubGroupSwimlane
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={priorities}
|
||||
listKey={`key`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
|
||||
{sub_group_by && sub_group_by === "labels" && (
|
||||
<SubGroupSwimlane
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={labels ? [...labels, { id: "None", name: "None" }] : labels}
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
|
||||
{sub_group_by && sub_group_by === "assignees" && (
|
||||
<SubGroupSwimlane
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={members ? [...members, { id: "None", display_name: "None" }] : members}
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
|
||||
{sub_group_by && sub_group_by === "created_by" && (
|
||||
<SubGroupSwimlane
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={members}
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
167
web/components/issues/issue-layouts/kanban/utils.ts
Normal file
167
web/components/issues/issue-layouts/kanban/utils.ts
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
import { DraggableLocation } from "@hello-pangea/dnd";
|
||||
import { ICycleIssues } from "store/issue/cycle";
|
||||
import { IDraftIssues } from "store/issue/draft";
|
||||
import { IModuleIssues } from "store/issue/module";
|
||||
import { IProfileIssues } from "store/issue/profile";
|
||||
import { IProjectIssues } from "store/issue/project";
|
||||
import { IProjectViewIssues } from "store/issue/project-views";
|
||||
import { IWorkspaceIssues } from "store/issue/workspace";
|
||||
import { TGroupedIssues, IIssueMap, TSubGroupedIssues, TUnGroupedIssues } from "@plane/types";
|
||||
|
||||
export const handleDragDrop = async (
|
||||
source: DraggableLocation | null | undefined,
|
||||
destination: DraggableLocation | null | undefined,
|
||||
workspaceSlug: string | undefined,
|
||||
projectId: string | undefined, // projectId for all views or user id in profile issues
|
||||
store:
|
||||
| IProjectIssues
|
||||
| ICycleIssues
|
||||
| IDraftIssues
|
||||
| IModuleIssues
|
||||
| IDraftIssues
|
||||
| IProjectViewIssues
|
||||
| IProfileIssues
|
||||
| IWorkspaceIssues,
|
||||
subGroupBy: string | null,
|
||||
groupBy: string | null,
|
||||
issueMap: IIssueMap,
|
||||
issueWithIds: TGroupedIssues | TSubGroupedIssues | TUnGroupedIssues | undefined,
|
||||
viewId: string | null = null // it can be moduleId, cycleId
|
||||
) => {
|
||||
if (!issueMap || !issueWithIds || !source || !destination || !workspaceSlug || !projectId) return;
|
||||
|
||||
let updateIssue: any = {};
|
||||
|
||||
const sourceColumnId = (source?.droppableId && source?.droppableId.split("__")) || null;
|
||||
const destinationColumnId = (destination?.droppableId && destination?.droppableId.split("__")) || null;
|
||||
|
||||
if (!sourceColumnId || !destinationColumnId) return;
|
||||
|
||||
const sourceGroupByColumnId = sourceColumnId[0] || null;
|
||||
const destinationGroupByColumnId = destinationColumnId[0] || null;
|
||||
|
||||
const sourceSubGroupByColumnId = sourceColumnId[1] || null;
|
||||
const destinationSubGroupByColumnId = destinationColumnId[1] || null;
|
||||
|
||||
if (
|
||||
!workspaceSlug ||
|
||||
!projectId ||
|
||||
!groupBy ||
|
||||
!sourceGroupByColumnId ||
|
||||
!destinationGroupByColumnId ||
|
||||
!sourceSubGroupByColumnId ||
|
||||
!sourceGroupByColumnId
|
||||
)
|
||||
return;
|
||||
|
||||
if (destinationGroupByColumnId === "issue-trash-box") {
|
||||
const sourceIssues: string[] = subGroupBy
|
||||
? (issueWithIds as TSubGroupedIssues)[sourceSubGroupByColumnId][sourceGroupByColumnId]
|
||||
: (issueWithIds as TGroupedIssues)[sourceGroupByColumnId];
|
||||
|
||||
const [removed] = sourceIssues.splice(source.index, 1);
|
||||
|
||||
if (removed) {
|
||||
if (viewId) return await store?.removeIssue(workspaceSlug, projectId, removed); //, viewId);
|
||||
else return await store?.removeIssue(workspaceSlug, projectId, removed);
|
||||
}
|
||||
} else {
|
||||
const sourceIssues = subGroupBy
|
||||
? (issueWithIds as TSubGroupedIssues)[sourceSubGroupByColumnId][sourceGroupByColumnId]
|
||||
: (issueWithIds as TGroupedIssues)[sourceGroupByColumnId];
|
||||
const destinationIssues = subGroupBy
|
||||
? (issueWithIds as TSubGroupedIssues)[sourceSubGroupByColumnId][destinationGroupByColumnId]
|
||||
: (issueWithIds as TGroupedIssues)[destinationGroupByColumnId];
|
||||
|
||||
const [removed] = sourceIssues.splice(source.index, 1);
|
||||
const removedIssueDetail = issueMap[removed];
|
||||
|
||||
if (subGroupBy && sourceSubGroupByColumnId && destinationSubGroupByColumnId) {
|
||||
updateIssue = {
|
||||
id: removedIssueDetail?.id,
|
||||
};
|
||||
|
||||
// for both horizontal and vertical dnd
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
...handleSortOrder(destinationIssues, destination.index, issueMap),
|
||||
};
|
||||
|
||||
if (sourceSubGroupByColumnId === destinationSubGroupByColumnId) {
|
||||
if (sourceGroupByColumnId != destinationGroupByColumnId) {
|
||||
if (groupBy === "state") updateIssue = { ...updateIssue, state: destinationGroupByColumnId };
|
||||
if (groupBy === "priority") updateIssue = { ...updateIssue, priority: destinationGroupByColumnId };
|
||||
}
|
||||
} else {
|
||||
if (subGroupBy === "state")
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
state: destinationSubGroupByColumnId,
|
||||
priority: destinationGroupByColumnId,
|
||||
};
|
||||
if (subGroupBy === "priority")
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
state: destinationGroupByColumnId,
|
||||
priority: destinationSubGroupByColumnId,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
updateIssue = {
|
||||
id: removedIssueDetail?.id,
|
||||
};
|
||||
|
||||
// for both horizontal and vertical dnd
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
...handleSortOrder(destinationIssues, destination.index, issueMap),
|
||||
};
|
||||
|
||||
// for horizontal dnd
|
||||
if (sourceColumnId != destinationColumnId) {
|
||||
if (groupBy === "state") updateIssue = { ...updateIssue, state: destinationGroupByColumnId };
|
||||
if (groupBy === "priority") updateIssue = { ...updateIssue, priority: destinationGroupByColumnId };
|
||||
}
|
||||
}
|
||||
|
||||
if (updateIssue && updateIssue?.id) {
|
||||
if (viewId) return await store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue); //, viewId);
|
||||
else return await store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleSortOrder = (destinationIssues: string[], destinationIndex: number, issueMap: IIssueMap) => {
|
||||
const sortOrderDefaultValue = 65535;
|
||||
let currentIssueState = {};
|
||||
|
||||
if (destinationIssues && destinationIssues.length > 0) {
|
||||
if (destinationIndex === 0) {
|
||||
const destinationIssueId = destinationIssues[destinationIndex];
|
||||
currentIssueState = {
|
||||
...currentIssueState,
|
||||
sort_order: issueMap[destinationIssueId].sort_order - sortOrderDefaultValue,
|
||||
};
|
||||
} else if (destinationIndex === destinationIssues.length) {
|
||||
const destinationIssueId = destinationIssues[destinationIndex - 1];
|
||||
currentIssueState = {
|
||||
...currentIssueState,
|
||||
sort_order: issueMap[destinationIssueId].sort_order + sortOrderDefaultValue,
|
||||
};
|
||||
} else {
|
||||
const destinationTopIssueId = destinationIssues[destinationIndex - 1];
|
||||
const destinationBottomIssueId = destinationIssues[destinationIndex];
|
||||
currentIssueState = {
|
||||
...currentIssueState,
|
||||
sort_order: (issueMap[destinationTopIssueId].sort_order + issueMap[destinationBottomIssueId].sort_order) / 2,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
currentIssueState = {
|
||||
...currentIssueState,
|
||||
sort_order: sortOrderDefaultValue,
|
||||
};
|
||||
}
|
||||
|
||||
return currentIssueState;
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue