chore: update issue detail store to handle peek overview (#2237)

* chore: dynamic position dropdown (#2138)

* chore: dynamic position state dropdown for issue view

* style: state select dropdown styling

* fix: state icon attribute names

* chore: state select dynamic dropdown

* chore: member select dynamic dropdown

* chore: label select dynamic dropdown

* chore: priority select dynamic dropdown

* chore: label select dropdown improvement

* refactor: state dropdown location

* chore: dropdown improvement and code refactor

* chore: dynamic dropdown hook type added

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>

* fix: fields not getting selected in the create issue form (#2212)

* fix: hydration error and draft issue workflow

* fix: build error

* fix: properties getting de-selected after create, module & cycle not getting auto-select on the form

* fix: display layout, props being updated directly

* chore: sub issues count in individual issue (#2221)

* Implemented nested issues in the sub issues section in issue detail page (#2233)

* feat: subissues infinte level

* feat: updated UI for sub issues

* feat: subissues new ui and nested sub issues in issue detail

* chore: removed repeated code

* refactor: product updates modal layout (#2225)

* fix: handle no issues in custom analytics (#2226)

* fix: activity label color (#2227)

* fix: profile issues layout switch (#2228)

* chore: update service imports

* chore: update issue detail store to handle peek overview

---------

Co-authored-by: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com>
Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com>
Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com>
Co-authored-by: guru_sainath <gurusainath007@gmail.com>
This commit is contained in:
Aaryan Khandelwal 2023-09-21 17:50:43 +05:30 committed by GitHub
parent 9b41b5baf5
commit daa3094911
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 1039 additions and 198 deletions

View File

@ -9,7 +9,6 @@ import { findStringWithMostCharacters } from "helpers/array.helper";
import { generateBarColor } from "helpers/analytics.helper";
// types
import { IAnalyticsParams, IAnalyticsResponse } from "types";
// constants
type Props = {
analytics: IAnalyticsResponse;

View File

@ -1,5 +1,9 @@
import { useRouter } from "next/router";
import useSWR from "swr";
// services
import issuesService from "services/issue.service";
// icons
import { Icon, Tooltip } from "components/ui";
import { CopyPlus } from "lucide-react";
@ -10,26 +14,22 @@ import { renderShortDateWithYearFormat } from "helpers/date-time.helper";
import { capitalizeFirstLetter } from "helpers/string.helper";
// types
import { IIssueActivity } from "types";
// fetch-keys
import { WORKSPACE_LABELS } from "constants/fetch-keys";
const IssueLink = ({ activity }: { activity: IIssueActivity }) => {
const router = useRouter();
const { workspaceSlug } = router.query;
return (
<Tooltip
tooltipContent={
activity.issue_detail ? activity.issue_detail.name : "This issue has been deleted"
}
>
<Tooltip tooltipContent={activity.issue_detail ? activity.issue_detail.name : "This issue has been deleted"}>
<a
href={`/${workspaceSlug}/projects/${activity.project}/issues/${activity.issue}`}
target="_blank"
rel="noopener noreferrer"
className="font-medium text-custom-text-100 inline-flex items-center gap-1 hover:underline"
>
{activity.issue_detail
? `${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}`
: "Issue"}
{activity.issue_detail ? `${activity.project_detail.identifier}-${activity.issue_detail.sequence_id}` : "Issue"}
<Icon iconName="launch" className="!text-xs" />
</a>
</Tooltip>
@ -52,13 +52,29 @@ const UserLink = ({ activity }: { activity: IIssueActivity }) => {
);
};
const LabelPill = ({ labelId }: { labelId: string }) => {
const router = useRouter();
const { workspaceSlug } = router.query;
const { data: labels } = useSWR(
workspaceSlug ? WORKSPACE_LABELS(workspaceSlug.toString()) : null,
workspaceSlug ? () => issuesService.getWorkspaceLabels(workspaceSlug.toString()) : null
);
return (
<span
className="h-1.5 w-1.5 rounded-full"
style={{
backgroundColor: labels?.find((l) => l.id === labelId)?.color ?? "#000000",
}}
aria-hidden="true"
/>
);
};
const activityDetails: {
[key: string]: {
message: (
activity: IIssueActivity,
showIssue: boolean,
workspaceSlug: string
) => React.ReactNode;
message: (activity: IIssueActivity, showIssue: boolean, workspaceSlug: string) => React.ReactNode;
icon: React.ReactNode;
};
} = {
@ -151,8 +167,7 @@ const activityDetails: {
else
return (
<>
removed the blocking issue{" "}
<span className="font-medium text-custom-text-100">{activity.old_value}</span>.
removed the blocking issue <span className="font-medium text-custom-text-100">{activity.old_value}</span>.
</>
);
},
@ -208,8 +223,7 @@ const activityDetails: {
else
return (
<>
removed the relation from{" "}
<span className="font-medium text-custom-text-100">{activity.old_value}</span>.
removed the relation from <span className="font-medium text-custom-text-100">{activity.old_value}</span>.
</>
);
},
@ -298,8 +312,7 @@ const activityDetails: {
else
return (
<>
set the estimate point to{" "}
<span className="font-medium text-custom-text-100">{activity.new_value}</span>
set the estimate point to <span className="font-medium text-custom-text-100">{activity.new_value}</span>
{showIssue && (
<>
{" "}
@ -325,14 +338,8 @@ const activityDetails: {
return (
<>
added a new label{" "}
<span className="inline-flex items-center gap-3 rounded-full border border-custom-border-300 px-2 py-0.5 text-xs">
<span
className="h-1.5 w-1.5 rounded-full"
style={{
backgroundColor: "#000000",
}}
aria-hidden="true"
/>
<span className="inline-flex items-center gap-2 rounded-full border border-custom-border-300 px-2 py-0.5 text-xs">
<LabelPill labelId={activity.new_identifier ?? ""} />
<span className="font-medium text-custom-text-100">{activity.new_value}</span>
</span>
{showIssue && (
@ -348,13 +355,7 @@ const activityDetails: {
<>
removed the label{" "}
<span className="inline-flex items-center gap-3 rounded-full border border-custom-border-300 px-2 py-0.5 text-xs">
<span
className="h-1.5 w-1.5 rounded-full"
style={{
backgroundColor: "#000000",
}}
aria-hidden="true"
/>
<LabelPill labelId={activity.old_identifier ?? ""} />
<span className="font-medium text-custom-text-100">{activity.old_value}</span>
</span>
{showIssue && (
@ -509,8 +510,7 @@ const activityDetails: {
if (!activity.new_value)
return (
<>
removed the parent{" "}
<span className="font-medium text-custom-text-100">{activity.old_value}</span>
removed the parent <span className="font-medium text-custom-text-100">{activity.old_value}</span>
{showIssue && (
<>
{" "}
@ -523,8 +523,7 @@ const activityDetails: {
else
return (
<>
set the parent to{" "}
<span className="font-medium text-custom-text-100">{activity.new_value}</span>
set the parent to <span className="font-medium text-custom-text-100">{activity.new_value}</span>
{showIssue && (
<>
{" "}
@ -592,8 +591,7 @@ const activityDetails: {
state: {
message: (activity, showIssue) => (
<>
set the state to{" "}
<span className="font-medium text-custom-text-100">{activity.new_value}</span>
set the state to <span className="font-medium text-custom-text-100">{activity.new_value}</span>
{showIssue && (
<>
{" "}
@ -645,13 +643,7 @@ export const ActivityIcon = ({ activity }: { activity: IIssueActivity }) => (
<>{activityDetails[activity.field as keyof typeof activityDetails]?.icon}</>
);
export const ActivityMessage = ({
activity,
showIssue = false,
}: {
activity: IIssueActivity;
showIssue?: boolean;
}) => {
export const ActivityMessage = ({ activity, showIssue = false }: { activity: IIssueActivity; showIssue?: boolean }) => {
const router = useRouter();
const { workspaceSlug } = router.query;

View File

@ -78,7 +78,7 @@ export const AllBoards: React.FC<Props> = ({
readOnly={disableUserActions}
/>
{groupedIssues ? (
<div className="horizontal-scroll-enable flex h-full gap-x-4 p-8">
<div className="horizontal-scroll-enable flex h-full gap-x-4 p-8 bg-custom-background-90">
{Object.keys(groupedIssues).map((singleGroup, index) => {
const currentState =
displayFilters?.group_by === "state"

View File

@ -35,9 +35,17 @@ type Props = {
data: IIssue | null;
user: ICurrentUserResponse | undefined;
onSubmit?: () => Promise<void>;
redirection?: boolean;
};
export const DeleteIssueModal: React.FC<Props> = ({ isOpen, handleClose, data, user, onSubmit }) => {
export const DeleteIssueModal: React.FC<Props> = ({
isOpen,
handleClose,
data,
user,
onSubmit,
redirection = true,
}) => {
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const router = useRouter();
@ -122,7 +130,7 @@ export const DeleteIssueModal: React.FC<Props> = ({ isOpen, handleClose, data, u
message: "Issue deleted successfully",
});
if (issueId) router.back();
if (issueId && redirection) router.back();
})
.catch((error) => {
console.log(error);

View File

@ -138,8 +138,7 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = (props) =
// issue. This has more priority than the project in the url.
if (data && data.project) return setActiveProject(data.project);
if (prePopulateData && prePopulateData.project && !activeProject)
return setActiveProject(prePopulateData.project);
if (prePopulateData && prePopulateData.project && !activeProject) return setActiveProject(prePopulateData.project);
if (prePopulateData && prePopulateData.project) return setActiveProject(prePopulateData.project);
@ -349,8 +348,7 @@ export const CreateUpdateDraftIssueModal: React.FC<IssuesModalProps> = (props) =
};
if (action === "createDraft") await createDraftIssue(payload);
else if (action === "updateDraft" || action === "convertToNewIssue")
await updateDraftIssue(payload);
else if (action === "updateDraft" || action === "convertToNewIssue") await updateDraftIssue(payload);
else if (action === "createNewIssue") await createIssue(payload);
clearDraftIssueLocalStorage();

View File

@ -12,7 +12,6 @@ export * from "./main-content";
export * from "./modal";
export * from "./parent-issues-list-modal";
export * from "./sidebar";
export * from "./sub-issues-list";
export * from "./label";
export * from "./issue-reaction";
export * from "./peek-overview";

View File

@ -18,9 +18,9 @@ import {
IssueAttachmentUpload,
IssueAttachments,
IssueDescriptionForm,
SubIssuesList,
IssueReaction,
} from "components/issues";
import { SubIssuesRoot } from "./sub-issues";
// ui
import { CustomMenu } from "components/ui";
// icons
@ -170,7 +170,7 @@ export const IssueMainContent: React.FC<Props> = ({ issueDetails, submitChanges,
<IssueReaction workspaceSlug={workspaceSlug} issueId={issueId} projectId={projectId} />
<div className="mt-2 space-y-2">
<SubIssuesList parentIssue={issueDetails} user={user} disabled={uneditable} />
<SubIssuesRoot parentIssue={issueDetails} user={user} editable={uneditable} />
</div>
</div>
<div className="flex flex-col gap-3 py-3">

View File

@ -36,7 +36,7 @@ export const IssuePeekOverview: React.FC<Props> = observer(({ handleMutation, pr
const { peekIssue } = router.query;
const { issueDetail: issueDetailStore } = useMobxStore();
const { deleteIssue, getIssueById, issues, updateIssue } = issueDetailStore;
const { deleteIssue, fetchIssueDetails, issues, updateIssue } = issueDetailStore;
const issue = issues[peekIssue?.toString() ?? ""];
@ -72,8 +72,8 @@ export const IssuePeekOverview: React.FC<Props> = observer(({ handleMutation, pr
useEffect(() => {
if (!peekIssue) return;
getIssueById(workspaceSlug, projectId, peekIssue.toString());
}, [getIssueById, peekIssue, projectId, workspaceSlug]);
fetchIssueDetails(workspaceSlug, projectId, peekIssue.toString());
}, [fetchIssueDetails, peekIssue, projectId, workspaceSlug]);
useEffect(() => {
if (peekIssue) {

View File

@ -0,0 +1 @@
export * from "./root";

View File

@ -0,0 +1,171 @@
import React from "react";
// next imports
import Link from "next/link";
// lucide icons
import {
ChevronDown,
ChevronRight,
X,
Pencil,
Trash,
Link as LinkIcon,
Loader,
} from "lucide-react";
// components
import { SubIssuesRootList } from "./issues-list";
import { IssueProperty } from "./properties";
// ui
import { Tooltip, CustomMenu } from "components/ui";
// types
import { ICurrentUserResponse, IIssue } from "types";
export interface ISubIssues {
workspaceSlug: string;
projectId: string;
parentIssue: IIssue;
issue: any;
spacingLeft?: number;
user: ICurrentUserResponse | undefined;
editable: boolean;
removeIssueFromSubIssues: (parentIssueId: string, issue: IIssue) => void;
issuesVisibility: string[];
handleIssuesVisibility: (issueId: string) => void;
copyText: (text: string) => void;
handleIssueCrudOperation: (
key: "create" | "existing" | "edit" | "delete",
issueId: string,
issue?: IIssue | null
) => void;
}
export const SubIssues: React.FC<ISubIssues> = ({
workspaceSlug,
projectId,
parentIssue,
issue,
spacingLeft = 0,
user,
editable,
removeIssueFromSubIssues,
issuesVisibility,
handleIssuesVisibility,
copyText,
handleIssueCrudOperation,
}) => (
<div>
{issue && (
<div
className="relative flex items-center gap-2 py-1 px-2 w-full h-full hover:bg-custom-background-90 group transition-all border-b border-custom-border-100"
style={{ paddingLeft: `${spacingLeft}px` }}
>
<div className="flex-shrink-0 w-[22px] h-[22px]">
{issue?.sub_issues_count > 0 && (
<>
{true ? (
<div
className="w-full h-full flex justify-center items-center rounded-sm hover:bg-custom-background-80 transition-all cursor-pointer"
onClick={() => handleIssuesVisibility(issue?.id)}
>
{issuesVisibility && issuesVisibility.includes(issue?.id) ? (
<ChevronDown width={14} strokeWidth={2} />
) : (
<ChevronRight width={14} strokeWidth={2} />
)}
</div>
) : (
<Loader width={14} strokeWidth={2} className="animate-spin" />
)}
</>
)}
</div>
<Link href={`/${workspaceSlug}/projects/${issue.project}/issues/${issue.id}`}>
<a className="w-full flex items-center gap-2">
<div
className="flex-shrink-0 w-[6px] h-[6px] rounded-full"
style={{
backgroundColor: issue.state_detail.color,
}}
/>
<div className="flex-shrink-0 text-xs text-custom-text-200">
{issue.project_detail.identifier}-{issue?.sequence_id}
</div>
<Tooltip tooltipHeading="Title" tooltipContent={`${issue?.name}`}>
<div className="line-clamp-1 text-xs text-custom-text-100">{issue?.name}</div>
</Tooltip>
</a>
</Link>
<div className="flex-shrink-0 text-sm">
<IssueProperty
workspaceSlug={workspaceSlug}
projectId={projectId}
parentIssue={parentIssue}
issue={issue}
user={user}
editable={editable}
/>
</div>
<div className="flex-shrink-0 text-sm">
<CustomMenu width="auto" ellipsis>
{editable && (
<CustomMenu.MenuItem
onClick={() => handleIssueCrudOperation("edit", parentIssue?.id, issue)}
>
<div className="flex items-center justify-start gap-2">
<Pencil width={14} strokeWidth={2} />
<span>Edit issue</span>
</div>
</CustomMenu.MenuItem>
)}
{editable && (
<CustomMenu.MenuItem
onClick={() => handleIssueCrudOperation("delete", parentIssue?.id, issue)}
>
<div className="flex items-center justify-start gap-2">
<Trash width={14} strokeWidth={2} />
<span>Delete issue</span>
</div>
</CustomMenu.MenuItem>
)}
<CustomMenu.MenuItem onClick={copyText}>
<div className="flex items-center justify-start gap-2">
<LinkIcon width={14} strokeWidth={2} />
<span>Copy issue link</span>
</div>
</CustomMenu.MenuItem>
</CustomMenu>
</div>
{editable && (
<div
className="flex-shrink-0 invisible group-hover:visible w-[22px] h-[22px] flex justify-center items-center rounded-sm hover:bg-custom-background-80 transition-all cursor-pointer overflow-hidden"
onClick={() => removeIssueFromSubIssues(parentIssue?.id, issue)}
>
<X width={14} strokeWidth={2} />
</div>
)}
</div>
)}
{issuesVisibility.includes(issue?.id) && issue?.sub_issues_count > 0 && (
<SubIssuesRootList
workspaceSlug={workspaceSlug}
projectId={projectId}
parentIssue={issue}
spacingLeft={spacingLeft + 22}
user={user}
editable={editable}
removeIssueFromSubIssues={removeIssueFromSubIssues}
issuesVisibility={issuesVisibility}
handleIssuesVisibility={handleIssuesVisibility}
copyText={copyText}
handleIssueCrudOperation={handleIssueCrudOperation}
/>
)}
</div>
);

View File

@ -0,0 +1,80 @@
import React from "react";
// swr
import useSWR from "swr";
// components
import { SubIssues } from "./issue";
// types
import { ICurrentUserResponse, IIssue } from "types";
// services
import issuesService from "services/issue.service";
// fetch keys
import { SUB_ISSUES } from "constants/fetch-keys";
export interface ISubIssuesRootList {
workspaceSlug: string;
projectId: string;
parentIssue: IIssue;
spacingLeft?: number;
user: ICurrentUserResponse | undefined;
editable: boolean;
removeIssueFromSubIssues: (parentIssueId: string, issue: IIssue) => void;
issuesVisibility: string[];
handleIssuesVisibility: (issueId: string) => void;
copyText: (text: string) => void;
handleIssueCrudOperation: (
key: "create" | "existing" | "edit" | "delete",
issueId: string,
issue?: IIssue | null
) => void;
}
export const SubIssuesRootList: React.FC<ISubIssuesRootList> = ({
workspaceSlug,
projectId,
parentIssue,
spacingLeft = 10,
user,
editable,
removeIssueFromSubIssues,
issuesVisibility,
handleIssuesVisibility,
copyText,
handleIssueCrudOperation,
}) => {
const { data: issues, isLoading } = useSWR(
workspaceSlug && projectId && parentIssue && parentIssue?.id ? SUB_ISSUES(parentIssue?.id) : null,
workspaceSlug && projectId && parentIssue && parentIssue?.id
? () => issuesService.subIssues(workspaceSlug, projectId, parentIssue.id)
: null
);
return (
<div className="relative">
{issues &&
issues.sub_issues &&
issues.sub_issues.length > 0 &&
issues.sub_issues.map((issue: IIssue) => (
<SubIssues
key={`${issue?.id}`}
workspaceSlug={workspaceSlug}
projectId={projectId}
parentIssue={parentIssue}
issue={issue}
spacingLeft={spacingLeft}
user={user}
editable={editable}
removeIssueFromSubIssues={removeIssueFromSubIssues}
issuesVisibility={issuesVisibility}
handleIssuesVisibility={handleIssuesVisibility}
copyText={copyText}
handleIssueCrudOperation={handleIssueCrudOperation}
/>
))}
<div
className={`absolute top-0 bottom-0 ${spacingLeft > 10 ? `border-l border-custom-border-100` : ``}`}
style={{ left: `${spacingLeft - 12}px` }}
/>
</div>
);
};

View File

@ -0,0 +1,25 @@
export interface IProgressBar {
total: number;
done: number;
}
export const ProgressBar = ({ total = 0, done = 0 }: IProgressBar) => {
const calPercentage = (doneValue: number, totalValue: number): string => {
if (doneValue === 0 || totalValue === 0) return (0).toFixed(0);
return ((100 * doneValue) / totalValue).toFixed(0);
};
return (
<div className="relative flex items-center gap-2">
<div className="w-full">
<div className="w-full rounded-full bg-custom-background-80 overflow-hidden shadow">
<div
className="bg-green-500 h-[6px] rounded-full"
style={{ width: `${calPercentage(done, total)}%` }}
/>
</div>
</div>
<div className="flex-shrink-0 text-xs font-medium">{calPercentage(done, total)}% Done</div>
</div>
);
};

View File

@ -0,0 +1,193 @@
import React from "react";
// swr
import { mutate } from "swr";
// services
import issuesService from "services/issue.service";
import trackEventServices from "services/track_event.service";
// components
import { ViewDueDateSelect, ViewStartDateSelect } from "components/issues";
import { MembersSelect, PrioritySelect } from "components/project";
import { StateSelect } from "components/states";
// hooks
import useIssuesProperties from "hooks/use-issue-properties";
// types
import { ICurrentUserResponse, IIssue, IState } from "types";
// fetch-keys
import { SUB_ISSUES } from "constants/fetch-keys";
export interface IIssueProperty {
workspaceSlug: string;
projectId: string;
parentIssue: IIssue;
issue: IIssue;
user: ICurrentUserResponse | undefined;
editable: boolean;
}
export const IssueProperty: React.FC<IIssueProperty> = ({
workspaceSlug,
projectId,
parentIssue,
issue,
user,
editable,
}) => {
const [properties] = useIssuesProperties(workspaceSlug, projectId);
const handlePriorityChange = (data: any) => {
partialUpdateIssue({ priority: data });
trackEventServices.trackIssuePartialPropertyUpdateEvent(
{
workspaceSlug,
workspaceId: issue.workspace,
projectId: issue.project_detail.id,
projectIdentifier: issue.project_detail.identifier,
projectName: issue.project_detail.name,
issueId: issue.id,
},
"ISSUE_PROPERTY_UPDATE_PRIORITY",
user
);
};
const handleStateChange = (data: string, states: IState[] | undefined) => {
const oldState = states?.find((s) => s.id === issue.state);
const newState = states?.find((s) => s.id === data);
partialUpdateIssue({
state: data,
state_detail: newState,
});
trackEventServices.trackIssuePartialPropertyUpdateEvent(
{
workspaceSlug,
workspaceId: issue.workspace,
projectId: issue.project_detail.id,
projectIdentifier: issue.project_detail.identifier,
projectName: issue.project_detail.name,
issueId: issue.id,
},
"ISSUE_PROPERTY_UPDATE_STATE",
user
);
if (oldState?.group !== "completed" && newState?.group !== "completed") {
trackEventServices.trackIssueMarkedAsDoneEvent(
{
workspaceSlug: issue.workspace_detail.slug,
workspaceId: issue.workspace_detail.id,
projectId: issue.project_detail.id,
projectIdentifier: issue.project_detail.identifier,
projectName: issue.project_detail.name,
issueId: issue.id,
},
user
);
}
};
const handleAssigneeChange = (data: any) => {
const newData = issue.assignees ?? [];
if (newData.includes(data)) newData.splice(newData.indexOf(data), 1);
else newData.push(data);
partialUpdateIssue({ assignees_list: data });
trackEventServices.trackIssuePartialPropertyUpdateEvent(
{
workspaceSlug,
workspaceId: issue.workspace,
projectId: issue.project_detail.id,
projectIdentifier: issue.project_detail.identifier,
projectName: issue.project_detail.name,
issueId: issue.id,
},
"ISSUE_PROPERTY_UPDATE_ASSIGNEE",
user
);
};
const partialUpdateIssue = async (data: Partial<IIssue>) => {
mutate(
workspaceSlug && parentIssue ? SUB_ISSUES(parentIssue.id) : null,
(elements: any) => {
const _elements = { ...elements };
const _issues = _elements.sub_issues.map((element: IIssue) =>
element.id === issue.id ? { ...element, ...data } : element
);
_elements["sub_issues"] = [..._issues];
return _elements;
},
false
);
const issueResponse = await issuesService.patchIssue(workspaceSlug as string, issue.project, issue.id, data, user);
mutate(
SUB_ISSUES(parentIssue.id),
(elements: any) => {
const _elements = elements.sub_issues.map((element: IIssue) =>
element.id === issue.id ? issueResponse : element
);
elements["sub_issues"] = _elements;
return elements;
},
true
);
};
return (
<div className="relative flex items-center gap-1">
{properties.priority && (
<div className="flex-shrink-0">
<PrioritySelect
value={issue.priority}
onChange={handlePriorityChange}
hideDropdownArrow
disabled={!editable}
/>
</div>
)}
{properties.state && (
<div className="flex-shrink-0">
<StateSelect value={issue.state_detail} onChange={handleStateChange} hideDropdownArrow disabled={!editable} />
</div>
)}
{properties.start_date && issue.start_date && (
<div className="flex-shrink-0 w-[104px]">
<ViewStartDateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
user={user}
isNotAllowed={!editable}
/>
</div>
)}
{properties.due_date && issue.target_date && (
<div className="flex-shrink-0 w-[104px]">
<ViewDueDateSelect
issue={issue}
partialUpdateIssue={partialUpdateIssue}
user={user}
isNotAllowed={!editable}
/>
</div>
)}
{properties.assignee && (
<div className="flex-shrink-0">
<MembersSelect
value={issue.assignees}
onChange={handleAssigneeChange}
membersDetails={issue.assignee_details}
hideDropdownArrow
disabled={!editable}
/>
</div>
)}
</div>
);
};

View File

@ -0,0 +1,269 @@
import React from "react";
// next imports
import { useRouter } from "next/router";
// swr
import useSWR, { mutate } from "swr";
// lucide icons
import { Plus, ChevronRight, ChevronDown } from "lucide-react";
// components
import { ExistingIssuesListModal } from "components/core";
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
import { SubIssuesRootList } from "./issues-list";
import { ProgressBar } from "./progressbar";
// ui
import { CustomMenu } from "components/ui";
// hooks
import { useProjectMyMembership } from "contexts/project-member.context";
// helpers
import { copyTextToClipboard } from "helpers/string.helper";
// types
import { ICurrentUserResponse, IIssue, ISearchIssueResponse } from "types";
// services
import issuesService from "services/issue.service";
// fetch keys
import { SUB_ISSUES } from "constants/fetch-keys";
export interface ISubIssuesRoot {
parentIssue: IIssue;
user: ICurrentUserResponse | undefined;
editable: boolean;
}
export const SubIssuesRoot: React.FC<ISubIssuesRoot> = ({ parentIssue, user, editable }) => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
const { memberRole } = useProjectMyMembership();
const { data: issues } = useSWR(
workspaceSlug && projectId && parentIssue && parentIssue?.id ? SUB_ISSUES(parentIssue?.id) : null,
workspaceSlug && projectId && parentIssue && parentIssue?.id
? () => issuesService.subIssues(workspaceSlug, projectId, parentIssue.id)
: null
);
const [issuesVisibility, setIssuesVisibility] = React.useState<string[]>([parentIssue?.id]);
const handleIssuesVisibility = (issueId: string) => {
if (issuesVisibility.includes(issueId)) {
setIssuesVisibility(issuesVisibility.filter((i: string) => i !== issueId));
} else {
setIssuesVisibility([...issuesVisibility, issueId]);
}
};
const [issueCrudOperation, setIssueCrudOperation] = React.useState<{
create: { toggle: boolean; issueId: string | null };
existing: { toggle: boolean; issueId: string | null };
edit: { toggle: boolean; issueId: string | null; issue: IIssue | null };
delete: { toggle: boolean; issueId: string | null; issue: IIssue | null };
}>({
create: {
toggle: false,
issueId: null,
},
existing: {
toggle: false,
issueId: null,
},
edit: {
toggle: false,
issueId: null, // parent issue id for mutation
issue: null,
},
delete: {
toggle: false,
issueId: null, // parent issue id for mutation
issue: null,
},
});
const handleIssueCrudOperation = (
key: "create" | "existing" | "edit" | "delete",
issueId: string | null,
issue: IIssue | null = null
) => {
setIssueCrudOperation({
...issueCrudOperation,
[key]: {
toggle: !issueCrudOperation[key].toggle,
issueId: issueId,
issue: issue,
},
});
};
const addAsSubIssueFromExistingIssues = async (data: ISearchIssueResponse[]) => {
if (!workspaceSlug || !parentIssue || issueCrudOperation?.existing?.issueId === null) return;
const issueId = issueCrudOperation?.existing?.issueId;
const payload = {
sub_issue_ids: data.map((i) => i.id),
};
await issuesService.addSubIssues(workspaceSlug, projectId, issueId, payload).finally(() => {
if (issueId) mutate(SUB_ISSUES(issueId));
});
};
const removeIssueFromSubIssues = async (parentIssueId: string, issue: IIssue) => {
if (!workspaceSlug || !parentIssue || !issue?.id) return;
issuesService.patchIssue(workspaceSlug, projectId, issue.id, { parent: null }, user).finally(() => {
if (parentIssueId) mutate(SUB_ISSUES(parentIssueId));
});
};
const copyText = (text: string) => {
const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : "";
copyTextToClipboard(`${originURL}/${text}`).then(() => {
// setToastAlert({
// type: "success",
// title: "Link Copied!",
// message: "Issue link copied to clipboard.",
// });
});
};
const isEditable = memberRole?.isGuest || memberRole?.isViewer ? false : true;
const mutateSubIssues = (parentIssueId: string | null) => {
if (parentIssueId) mutate(SUB_ISSUES(parentIssueId));
};
return (
<div className="w-full h-full space-y-2">
{parentIssue && parentIssue?.sub_issues_count > 0 ? (
<>
{/* header */}
<div className="relative flex items-center gap-4 text-xs">
<div
className="rounded border border-custom-border-100 shadow p-1.5 px-2 flex items-center gap-1 hover:bg-custom-background-80 transition-all cursor-pointer select-none"
onClick={() => handleIssuesVisibility(parentIssue?.id)}
>
<div className="flex-shrink-0 w-[16px] h-[16px] flex justify-center items-center">
{issuesVisibility.includes(parentIssue?.id) ? (
<ChevronDown width={16} strokeWidth={2} />
) : (
<ChevronRight width={14} strokeWidth={2} />
)}
</div>
<div>Sub-issues</div>
<div>({parentIssue?.sub_issues_count})</div>
</div>
<div className="w-full max-w-[250px] select-none">
<ProgressBar
total={parentIssue?.sub_issues_count}
done={(issues?.state_distribution?.cancelled || 0) + (issues?.state_distribution?.completed || 0)}
/>
</div>
{isEditable && issuesVisibility.includes(parentIssue?.id) && (
<div className="ml-auto flex-shrink-0 flex items-center gap-2 select-none">
<div
className="hover:bg-custom-background-80 transition-all cursor-pointer p-1.5 px-2 rounded border border-custom-border-100 shadow"
onClick={() => handleIssueCrudOperation("create", parentIssue?.id)}
>
Add sub-issue
</div>
<div
className="hover:bg-custom-background-80 transition-all cursor-pointer p-1.5 px-2 rounded border border-custom-border-100 shadow"
onClick={() => handleIssueCrudOperation("existing", parentIssue?.id)}
>
Add an existing issue
</div>
</div>
)}
</div>
{/* issues */}
{issuesVisibility.includes(parentIssue?.id) && (
<div className="border border-b-0 border-custom-border-100">
<SubIssuesRootList
workspaceSlug={workspaceSlug}
projectId={projectId}
parentIssue={parentIssue}
user={undefined}
editable={isEditable}
removeIssueFromSubIssues={removeIssueFromSubIssues}
issuesVisibility={issuesVisibility}
handleIssuesVisibility={handleIssuesVisibility}
copyText={copyText}
handleIssueCrudOperation={handleIssueCrudOperation}
/>
</div>
)}
</>
) : (
isEditable && (
<div className="text-xs py-2 text-custom-text-300 font-medium">
<div className="py-3 text-center">No sub issues are available</div>
<>
<CustomMenu
label={
<>
<Plus className="h-3 w-3" />
Add sub-issue
</>
}
buttonClassName="whitespace-nowrap"
position="left"
noBorder
noChevron
>
<CustomMenu.MenuItem onClick={() => handleIssueCrudOperation("create", parentIssue?.id)}>
Create new
</CustomMenu.MenuItem>
<CustomMenu.MenuItem onClick={() => handleIssueCrudOperation("existing", parentIssue?.id)}>
Add an existing issue
</CustomMenu.MenuItem>
</CustomMenu>
</>
</div>
)
)}
{isEditable && issueCrudOperation?.create?.toggle && (
<CreateUpdateIssueModal
isOpen={issueCrudOperation?.create?.toggle}
prePopulateData={{
parent: issueCrudOperation?.create?.issueId,
}}
handleClose={() => handleIssueCrudOperation("create", null)}
/>
)}
{isEditable && issueCrudOperation?.existing?.toggle && issueCrudOperation?.existing?.issueId && (
<ExistingIssuesListModal
isOpen={issueCrudOperation?.existing?.toggle}
handleClose={() => handleIssueCrudOperation("existing", null)}
searchParams={{ sub_issue: true, issue_id: issueCrudOperation?.existing?.issueId }}
handleOnSubmit={addAsSubIssueFromExistingIssues}
workspaceLevelToggle
/>
)}
{isEditable && issueCrudOperation?.edit?.toggle && issueCrudOperation?.edit?.issueId && (
<CreateUpdateIssueModal
isOpen={issueCrudOperation?.edit?.toggle}
handleClose={() => {
mutateSubIssues(issueCrudOperation?.edit?.issueId);
handleIssueCrudOperation("edit", null, null);
}}
data={issueCrudOperation?.edit?.issue}
/>
)}
{isEditable && issueCrudOperation?.delete?.toggle && issueCrudOperation?.delete?.issueId && (
<DeleteIssueModal
isOpen={issueCrudOperation?.delete?.toggle}
handleClose={() => {
mutateSubIssues(issueCrudOperation?.delete?.issueId);
handleIssueCrudOperation("delete", null, null);
}}
data={issueCrudOperation?.delete?.issue}
user={user}
redirection={false}
/>
)}
</div>
);
};

View File

@ -49,7 +49,6 @@ export const ProfileIssuesView = () => {
groupedIssues,
mutateProfileIssues,
displayFilters,
setDisplayFilters,
isEmpty,
filters,
setFilters,

View File

@ -74,15 +74,13 @@ export const MembersSelect: React.FC<Props> = ({
}));
const filteredOptions =
query === ""
? options
: options?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase()));
query === "" ? options : options?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase()));
const label = (
<Tooltip
tooltipHeading="Assignee"
tooltipContent={
membersDetails.length > 0
membersDetails && membersDetails.length > 0
? membersDetails.map((assignee) => assignee?.display_name).join(", ")
: "No Assignee"
}
@ -126,15 +124,11 @@ export const MembersSelect: React.FC<Props> = ({
ref={dropdownBtn}
type="button"
className={`flex items-center justify-between gap-1 w-full text-xs ${
disabled
? "cursor-not-allowed text-custom-text-200"
: "cursor-pointer hover:bg-custom-background-80"
disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer hover:bg-custom-background-80"
} ${buttonClassName}`}
>
{label}
{!hideDropdownArrow && !disabled && (
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
)}
{!hideDropdownArrow && !disabled && <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />}
</Combobox.Button>
<div className={`${open ? "fixed z-20 top-0 left-0 h-full w-full cursor-auto" : ""}`}>
<Combobox.Options

View File

@ -1,15 +1,16 @@
import React from "react";
import useSWR from "swr";
// headless ui
import { Dialog, Transition } from "@headlessui/react";
// component
import { MarkdownRenderer, Spinner } from "components/ui";
// icons
import { XMarkIcon } from "@heroicons/react/20/solid";
// services
import workspaceService from "services/workspace.service";
// helper
// components
import { Loader, MarkdownRenderer } from "components/ui";
// icons
import { XMarkIcon } from "@heroicons/react/20/solid";
// helpers
import { renderLongDateFormat } from "helpers/date-time.helper";
type Props = {
@ -34,8 +35,8 @@ export const ProductUpdatesModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
<div className="fixed inset-0 bg-custom-backdrop bg-opacity-50 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-20 overflow-y-auto">
<div className="grid place-items-center min-h-full text-center p-4">
<div className="fixed inset-0 z-20 h-full w-full">
<div className="grid place-items-center h-full w-full p-4">
<Transition.Child
as={React.Fragment}
enter="ease-out duration-300"
@ -45,49 +46,62 @@ export const ProductUpdatesModal: React.FC<Props> = ({ isOpen, setIsOpen }) => {
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative transform overflow-hidden rounded-lg bg-custom-background-100 border border-custom-border-100 text-left shadow-xl transition-all grid place-items-center sm:w-full sm:max-w-2xl">
<div className="max-h-[90vh] overflow-y-auto p-5">
<div className="sm:flex sm:items-start">
<div className="flex w-full flex-col gap-y-4 text-center sm:text-left">
<Dialog.Title
as="h3"
className="flex justify-between text-lg font-medium leading-6 text-custom-text-100"
>
<span>Product Updates</span>
<span>
<button type="button" onClick={() => setIsOpen(false)}>
<XMarkIcon
className="h-6 w-6 text-custom-text-200 hover:text-custom-text-100"
aria-hidden="true"
/>
</button>
</span>
</Dialog.Title>
{updates && updates.length > 0 ? (
updates.map((item, index) => (
<React.Fragment key={item.id}>
<div className="flex items-center gap-3 text-xs text-custom-text-200">
<span className="flex items-center rounded-full border border-custom-border-200 bg-custom-background-90 px-3 py-1.5 text-xs">
{item.tag_name}
<Dialog.Panel className="relative overflow-hidden rounded-lg bg-custom-background-100 border border-custom-border-100 shadow-custom-shadow-rg] min-w-[100%] sm:min-w-[50%] sm:max-w-[50%]">
<div className="flex flex-col p-4 max-h-[90vh] w-full">
<Dialog.Title
as="h3"
className="flex items-center justify-between text-lg font-semibold"
>
<span>Product Updates</span>
<span>
<button type="button" onClick={() => setIsOpen(false)}>
<XMarkIcon
className="h-6 w-6 text-custom-text-200 hover:text-custom-text-100"
aria-hidden="true"
/>
</button>
</span>
</Dialog.Title>
{updates && updates.length > 0 ? (
<div className="h-full overflow-y-auto mt-4 space-y-4">
{updates.map((item, index) => (
<React.Fragment key={item.id}>
<div className="flex items-center gap-3 text-xs text-custom-text-200">
<span className="flex items-center rounded-full border border-custom-border-200 bg-custom-background-90 px-3 py-1.5 text-xs">
{item.tag_name}
</span>
<span>{renderLongDateFormat(item.published_at)}</span>
{index === 0 && (
<span className="flex items-center rounded-full border border-custom-border-200 bg-custom-primary px-3 py-1.5 text-xs text-white">
New
</span>
<span>{renderLongDateFormat(item.published_at)}</span>
{index === 0 && (
<span className="flex items-center rounded-full border border-custom-border-200 bg-custom-primary px-3 py-1.5 text-xs text-white">
New
</span>
)}
</div>
<MarkdownRenderer markdown={item.body} />
</React.Fragment>
))
) : (
<div className="flex h-full w-full items-center justify-center">
<Spinner />
Loading...
</div>
)}
)}
</div>
<MarkdownRenderer markdown={item.body} />
</React.Fragment>
))}
</div>
</div>
) : (
<div className="grid place-items-center w-full mt-4">
<Loader className="space-y-6 w-full">
<div className="space-y-3">
<Loader.Item height="30px" />
<Loader.Item height="20px" width="80%" />
<Loader.Item height="20px" width="80%" />
</div>
<div className="space-y-3">
<Loader.Item height="30px" />
<Loader.Item height="20px" width="80%" />
<Loader.Item height="20px" width="80%" />
</div>
<div className="space-y-3">
<Loader.Item height="30px" />
<Loader.Item height="20px" width="80%" />
<Loader.Item height="20px" width="80%" />
</div>
</Loader>
</div>
)}
</div>
</Dialog.Panel>
</Transition.Child>

View File

@ -71,9 +71,8 @@ export const reducer: ReducerFunctionType = (state, action) => {
...state,
display_filters: {
...state.display_filters,
...payload,
...payload?.display_filters,
},
issueView: payload?.display_filters?.layout || "list",
};
return {
@ -100,7 +99,7 @@ export const reducer: ReducerFunctionType = (state, action) => {
case "SET_PROPERTIES": {
const newState = {
...state,
properties: {
display_properties: {
...state.display_properties,
...payload?.display_properties,
},
@ -129,7 +128,6 @@ export const ProfileIssuesContextProvider: React.FC<{ children: React.ReactNode
type: "SET_DISPLAY_FILTERS",
payload: {
display_filters: {
...state.display_filters,
...displayFilter,
},
},
@ -179,7 +177,6 @@ export const ProfileIssuesContextProvider: React.FC<{ children: React.ReactNode
type: "SET_PROPERTIES",
payload: {
display_properties: {
...state.display_properties,
[key]: !state.display_properties[key],
},
},

View File

@ -38,10 +38,13 @@ export const orderArrayBy = (
export const checkDuplicates = (array: any[]) => new Set(array).size !== array.length;
export const findStringWithMostCharacters = (strings: string[]) =>
strings.reduce((longestString, currentString) =>
export const findStringWithMostCharacters = (strings: string[]): string => {
if (!strings || strings.length === 0) return "";
return strings.reduce((longestString, currentString) =>
currentString.length > longestString.length ? currentString : longestString
);
};
export const checkIfArraysHaveSameElements = (arr1: any[] | null, arr2: any[] | null): boolean => {
if (!arr1 || !arr2) return false;

View File

@ -5,6 +5,19 @@ import { RootStore } from "./root";
export interface IIssueStore {
loader: boolean;
error: any | null;
issues: {
[project_id: string]: {
grouped: {
[issueId: string]: IIssue[];
};
ungrouped: IIssue[];
};
};
addIssueToIssuesStore: (projectId: string, issue: IIssue) => void;
updateIssueInIssuesStore: (projectId: string, issue: IIssue) => void;
deleteIssueFromIssuesStore: (projectId: string, issueId: string) => void;
}
class IssueStore implements IIssueStore {
@ -12,7 +25,9 @@ class IssueStore implements IIssueStore {
error: any | null = null;
issues: {
[project_id: string]: {
grouped: any;
grouped: {
[issueId: string]: IIssue[];
};
ungrouped: IIssue[];
};
} = {};
@ -25,11 +40,56 @@ class IssueStore implements IIssueStore {
loader: observable.ref,
error: observable.ref,
issues: observable.ref,
addIssueToIssuesStore: action,
updateIssueInIssuesStore: action,
deleteIssueFromIssuesStore: action,
});
this.rootStore = _rootStore;
}
fetchIssuesWithParams() {}
addIssueToIssuesStore = (projectId: string, issue: IIssue) => {
runInAction(() => {
this.rootStore.issue.issues = {
...this.rootStore.issue.issues,
[projectId]: {
...this.rootStore.issue.issues[projectId],
ungrouped: [...this.rootStore.issue.issues[projectId].ungrouped, issue],
},
};
});
};
updateIssueInIssuesStore = (projectId: string, issue: IIssue) => {
const newUngroupedIssues = this.rootStore.issue.issues[projectId].ungrouped.map((i) => ({
...i,
...(i.id === issue.id ? issue : {}),
}));
runInAction(() => {
this.rootStore.issue.issues = {
...this.rootStore.issue.issues,
[projectId]: {
...this.rootStore.issue.issues[projectId],
ungrouped: newUngroupedIssues,
},
};
});
};
deleteIssueFromIssuesStore = (projectId: string, issueId: string) => {
const newUngroupedIssues = this.rootStore.issue.issues[projectId].ungrouped.filter((i) => i.id !== issueId);
runInAction(() => {
this.rootStore.issue.issues = {
...this.rootStore.issue.issues,
[projectId]: {
...this.rootStore.issue.issues[projectId],
ungrouped: newUngroupedIssues,
},
};
});
};
}
export default IssueStore;

View File

@ -1,9 +1,10 @@
import { observable, action, makeObservable, runInAction } from "mobx";
// types
import { RootStore } from "./root";
// services
import { IssueService } from "services/issue.service";
import { IIssue } from "types";
// types
import { RootStore } from "./root";
import { ICurrentUserResponse, IIssue } from "types";
export type IPeekMode = "side" | "modal" | "full";
@ -15,7 +16,7 @@ export interface IIssueDetailStore {
peekMode: IPeekMode | null;
issues: {
[key: string]: IIssue;
[issueId: string]: IIssue;
};
setPeekId: (issueId: string | null) => void;
@ -23,11 +24,17 @@ export interface IIssueDetailStore {
// fetch issue details
fetchIssueDetails: (workspaceId: string, projectId: string, issueId: string) => void;
// creating issue
createIssue: (workspaceId: string, projectId: string, issueId: string, data: any) => void;
createIssue: (workspaceId: string, projectId: string, data: Partial<IIssue>, user: ICurrentUserResponse) => void;
// updating issue
updateIssue: (workspaceId: string, projectId: string, issueId: string, data: any) => void;
updateIssue: (
workspaceId: string,
projectId: string,
issueId: string,
data: Partial<IIssue>,
user: ICurrentUserResponse
) => void;
// deleting issue
deleteIssue: (workspaceId: string, projectId: string, issueId: string) => void;
deleteIssue: (workspaceId: string, projectId: string, issueId: string, user: ICurrentUserResponse) => void;
}
class IssueDetailStore implements IIssueDetailStore {
@ -38,7 +45,7 @@ class IssueDetailStore implements IIssueDetailStore {
peekMode: IPeekMode | null = null;
issues: {
[key: string]: IIssue;
[issueId: string]: IIssue;
} = {};
// root store
@ -85,88 +92,120 @@ class IssueDetailStore implements IIssueDetailStore {
this.loader = false;
this.error = null;
this.issues = {
...this.issues,
[issueId]: issueDetailsResponse,
};
});
} catch (error) {
console.log("error in fetching issue details", error);
runInAction(() => {
this.loader = false;
this.error = error;
});
return error;
}
};
createIssue = async (workspaceId: string, projectId: string, data: Partial<IIssue>, user: ICurrentUserResponse) => {
try {
runInAction(() => {
this.loader = true;
this.error = null;
});
const response = await this.issueService.createIssues(workspaceId, projectId, data, user);
if (response) this.rootStore.issue.addIssueToIssuesStore(projectId, response);
runInAction(() => {
this.loader = false;
this.error = null;
this.issues = {
...this.issues,
[response.id]: response,
};
});
} catch (error) {
this.loader = false;
this.error = error;
return error;
}
};
createIssue = async (workspaceId: string, projectId: string, issueId: string, data: any) => {
try {
this.loader = true;
this.error = null;
updateIssue = async (
workspaceId: string,
projectId: string,
issueId: string,
data: Partial<IIssue>,
user: ICurrentUserResponse
) => {
const newIssues = { ...this.issues };
newIssues[issueId] = {
...newIssues[issueId],
...data,
};
console.log("workspaceId", workspaceId);
console.log("projectId", projectId);
console.log("issueId", issueId);
console.log("data", data);
try {
runInAction(() => {
this.loader = true;
this.error = null;
this.issues = newIssues;
});
const response = await this.issueService.patchIssue(workspaceId, projectId, issueId, data, user);
if (response) this.rootStore.issue.updateIssueInIssuesStore(projectId, response);
runInAction(() => {
this.loader = false;
this.error = null;
this.issues = {
...this.issues,
[issueId]: {
...this.issues[issueId],
...response,
},
};
});
} catch (error) {
this.fetchIssueDetails(workspaceId, projectId, issueId);
runInAction(() => {
this.loader = false;
this.error = error;
});
return error;
}
};
deleteIssue = async (workspaceId: string, projectId: string, issueId: string, user: ICurrentUserResponse) => {
const newIssues = { ...this.issues };
delete newIssues[issueId];
try {
runInAction(() => {
this.loader = true;
this.error = null;
this.issues = newIssues;
});
await this.issueService.deleteIssue(workspaceId, projectId, issueId, user);
this.rootStore.issue.deleteIssueFromIssuesStore(projectId, issueId);
runInAction(() => {
this.loader = false;
this.error = null;
});
} catch (error) {
console.log("error in fetching issue details", error);
this.loader = false;
this.error = error;
return error;
}
};
updateIssue = async (workspaceId: string, projectId: string, issueId: string, data: any) => {
try {
this.loader = true;
this.error = null;
const filteredParams = this.rootStore.issueFilter.getComputedFilters(
workspaceId,
projectId,
null,
null,
null,
this.rootStore.issueFilters.issueView || "issues"
);
const issueResponse = await this.issueService.patchIssue(workspaceId, projectId, issueId, data, undefined);
const issueList = (await this.issueService.getIssuesWithParams(workspaceId, projectId, filteredParams)) as any;
console.log("issueList", issueList);
if (issueResponse) {
runInAction(() => {
this.loader = false;
this.error = null;
this.rootStore.issueView.issues[workspaceId].project_issues[projectId].issues.list = issueList;
});
}
} catch (error) {
console.log("error in fetching issue details", error);
this.loader = false;
this.error = error;
return error;
}
};
deleteIssue = async (workspaceId: string, projectId: string, issueId: string) => {
try {
this.loader = true;
this.error = null;
console.log("workspaceId", workspaceId);
console.log("projectId", projectId);
console.log("issueId", issueId);
this.fetchIssueDetails(workspaceId, projectId, issueId);
runInAction(() => {
this.loader = false;
this.error = null;
this.error = error;
});
} catch (error) {
console.log("error in fetching issue details", error);
this.loader = false;
this.error = error;
return error;
}
};