mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
chore: replace workspace slug and project id with anchor
This commit is contained in:
parent
dc436f60fa
commit
1c3a27deaa
@ -1,8 +1,8 @@
|
|||||||
type Props = {
|
type Props = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
params: {
|
params: {
|
||||||
workspace_slug: string;
|
workspaceSlug: string;
|
||||||
project_id: string;
|
projectId: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -12,14 +12,14 @@ const publishService = new PublishService();
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
params: {
|
params: {
|
||||||
workspace_slug: string;
|
workspaceSlug: string;
|
||||||
project_id: string;
|
projectId: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProjectIssuesPage = (props: Props) => {
|
const ProjectIssuesPage = (props: Props) => {
|
||||||
const { params } = props;
|
const { params } = props;
|
||||||
const { workspace_slug, project_id } = params;
|
const { workspaceSlug, projectId } = params;
|
||||||
// states
|
// states
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
// params
|
// params
|
||||||
@ -28,9 +28,9 @@ const ProjectIssuesPage = (props: Props) => {
|
|||||||
const peekId = searchParams.get("peekId") || undefined;
|
const peekId = searchParams.get("peekId") || undefined;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!workspace_slug || !project_id) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
publishService
|
publishService
|
||||||
.fetchAnchorFromOldDetails(workspace_slug, project_id)
|
.fetchAnchorFromOldDetails(workspaceSlug, projectId)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
let url = `/${res.anchor}`;
|
let url = `/${res.anchor}`;
|
||||||
const params = new URLSearchParams();
|
const params = new URLSearchParams();
|
||||||
@ -40,7 +40,7 @@ const ProjectIssuesPage = (props: Props) => {
|
|||||||
navigate(url);
|
navigate(url);
|
||||||
})
|
})
|
||||||
.catch(() => setError(true));
|
.catch(() => setError(true));
|
||||||
}, [board, peekId, project_id, workspace_slug]);
|
}, [board, peekId, projectId, workspaceSlug]);
|
||||||
|
|
||||||
if (error) notFound();
|
if (error) notFound();
|
||||||
|
|
@ -1,4 +1,3 @@
|
|||||||
import { observer } from "mobx-react";
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
// components
|
// components
|
@ -16,7 +16,8 @@ const PageDetailsLayout = (props: Props) => {
|
|||||||
const { anchor } = params;
|
const { anchor } = params;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { fetchPublishSettings } = usePublishList();
|
const { fetchPublishSettings } = usePublishList();
|
||||||
const { id, workspace_detail, project } = usePublish(anchor);
|
const publishSettings = usePublish(anchor);
|
||||||
|
const { id, workspace_detail, project } = publishSettings;
|
||||||
|
|
||||||
useSWR(anchor ? `PUBLISH_SETTINGS_${anchor}` : null, anchor ? () => fetchPublishSettings(anchor) : null);
|
useSWR(anchor ? `PUBLISH_SETTINGS_${anchor}` : null, anchor ? () => fetchPublishSettings(anchor) : null);
|
||||||
|
|
||||||
@ -25,7 +26,7 @@ const PageDetailsLayout = (props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div className="relative flex h-screen min-h-[500px] w-screen flex-col overflow-hidden">
|
<div className="relative flex h-screen min-h-[500px] w-screen flex-col overflow-hidden">
|
||||||
<div className="relative flex h-[60px] flex-shrink-0 select-none items-center border-b border-custom-border-300 bg-custom-sidebar-background-100">
|
<div className="relative flex h-[60px] flex-shrink-0 select-none items-center border-b border-custom-border-300 bg-custom-sidebar-background-100">
|
||||||
<IssueNavbar workspaceSlug={workspace_detail.slug} projectId={project} />
|
<IssueNavbar publishSettings={publishSettings} />
|
||||||
</div>
|
</div>
|
||||||
<div className="relative h-full w-full overflow-hidden bg-custom-background-90">{children}</div>
|
<div className="relative h-full w-full overflow-hidden bg-custom-background-90">{children}</div>
|
||||||
<a
|
<a
|
||||||
|
@ -10,18 +10,19 @@ import { IssueBlockState } from "@/components/issues/board-views/block-state";
|
|||||||
// helpers
|
// helpers
|
||||||
import { queryParamGenerator } from "@/helpers/query-param-generator";
|
import { queryParamGenerator } from "@/helpers/query-param-generator";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssueDetails, useProject } from "@/hooks/store";
|
import { useIssueDetails, usePublish } from "@/hooks/store";
|
||||||
// interfaces
|
// interfaces
|
||||||
import { IIssue } from "@/types/issue";
|
import { IIssue } from "@/types/issue";
|
||||||
|
|
||||||
type IssueKanBanBlockProps = {
|
type IssueKanBanBlockProps = {
|
||||||
|
anchor: string;
|
||||||
issue: IIssue;
|
issue: IIssue;
|
||||||
workspaceSlug: string;
|
|
||||||
projectId: string;
|
|
||||||
params: any;
|
params: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueKanBanBlock: FC<IssueKanBanBlockProps> = observer((props) => {
|
export const IssueKanBanBlock: FC<IssueKanBanBlockProps> = observer((props) => {
|
||||||
|
const { anchor, issue } = props;
|
||||||
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
// query params
|
// query params
|
||||||
@ -29,23 +30,21 @@ export const IssueKanBanBlock: FC<IssueKanBanBlockProps> = observer((props) => {
|
|||||||
const state = searchParams.get("state") || undefined;
|
const state = searchParams.get("state") || undefined;
|
||||||
const priority = searchParams.get("priority") || undefined;
|
const priority = searchParams.get("priority") || undefined;
|
||||||
const labels = searchParams.get("labels") || undefined;
|
const labels = searchParams.get("labels") || undefined;
|
||||||
// props
|
// store hooks
|
||||||
const { workspaceSlug, projectId, issue } = props;
|
const { project_details } = usePublish(anchor);
|
||||||
// hooks
|
|
||||||
const { project } = useProject();
|
|
||||||
const { setPeekId } = useIssueDetails();
|
const { setPeekId } = useIssueDetails();
|
||||||
|
|
||||||
const handleBlockClick = () => {
|
const handleBlockClick = () => {
|
||||||
setPeekId(issue.id);
|
setPeekId(issue.id);
|
||||||
const { queryParam } = queryParamGenerator({ board, peekId: issue.id, priority, state, labels });
|
const { queryParam } = queryParamGenerator({ board, peekId: issue.id, priority, state, labels });
|
||||||
router.push(`/${workspaceSlug}/${projectId}?${queryParam}`);
|
router.push(`/issues/${anchor}?${queryParam}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-1.5 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">
|
<div className="flex flex-col gap-1.5 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">
|
||||||
{/* id */}
|
{/* id */}
|
||||||
<div className="break-words text-xs text-custom-text-300">
|
<div className="break-words text-xs text-custom-text-300">
|
||||||
{project?.identifier}-{issue?.sequence_id}
|
{project_details?.identifier}-{issue?.sequence_id}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* name */}
|
{/* name */}
|
||||||
|
@ -9,39 +9,31 @@ import { IssueKanBanHeader } from "@/components/issues/board-views/kanban/header
|
|||||||
import { Icon } from "@/components/ui";
|
import { Icon } from "@/components/ui";
|
||||||
// mobx hook
|
// mobx hook
|
||||||
import { useIssue } from "@/hooks/store";
|
import { useIssue } from "@/hooks/store";
|
||||||
// interfaces
|
|
||||||
import { IIssueState, IIssue } from "@/types/issue";
|
|
||||||
|
|
||||||
type IssueKanbanViewProps = {
|
type IssueKanbanViewProps = {
|
||||||
workspaceSlug: string;
|
anchor: string;
|
||||||
projectId: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueKanbanView: FC<IssueKanbanViewProps> = observer((props) => {
|
export const IssueKanbanView: FC<IssueKanbanViewProps> = observer((props) => {
|
||||||
const { workspaceSlug, projectId } = props;
|
const { anchor } = props;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { states, getFilteredIssuesByState } = useIssue();
|
const { states, getFilteredIssuesByState } = useIssue();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex h-full w-full gap-3 overflow-hidden overflow-x-auto">
|
<div className="relative flex h-full w-full gap-3 overflow-hidden overflow-x-auto">
|
||||||
{states &&
|
{states?.map((state) => {
|
||||||
states.length > 0 &&
|
const issues = getFilteredIssuesByState(state.id);
|
||||||
states.map((_state: IIssueState) => (
|
|
||||||
<div key={_state.id} className="relative flex h-full w-[340px] flex-shrink-0 flex-col">
|
return (
|
||||||
|
<div key={state.id} className="relative flex h-full w-[340px] flex-shrink-0 flex-col">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<IssueKanBanHeader state={_state} />
|
<IssueKanBanHeader state={state} />
|
||||||
</div>
|
</div>
|
||||||
<div className="hide-vertical-scrollbar h-full w-full overflow-hidden overflow-y-auto">
|
<div className="hide-vertical-scrollbar h-full w-full overflow-hidden overflow-y-auto">
|
||||||
{getFilteredIssuesByState(_state.id) && getFilteredIssuesByState(_state.id).length > 0 ? (
|
{issues && issues.length > 0 ? (
|
||||||
<div className="space-y-3 px-2 pb-2">
|
<div className="space-y-3 px-2 pb-2">
|
||||||
{getFilteredIssuesByState(_state.id).map((_issue: IIssue) => (
|
{issues.map((issue) => (
|
||||||
<IssueKanBanBlock
|
<IssueKanBanBlock key={issue.id} anchor={anchor} issue={issue} params={{}} />
|
||||||
key={_issue.id}
|
|
||||||
issue={_issue}
|
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
params={{}}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@ -52,7 +44,8 @@ export const IssueKanbanView: FC<IssueKanbanViewProps> = observer((props) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -10,28 +10,27 @@ import { IssueBlockState } from "@/components/issues/board-views/block-state";
|
|||||||
// helpers
|
// helpers
|
||||||
import { queryParamGenerator } from "@/helpers/query-param-generator";
|
import { queryParamGenerator } from "@/helpers/query-param-generator";
|
||||||
// hook
|
// hook
|
||||||
import { useIssueDetails, useProject } from "@/hooks/store";
|
import { useIssueDetails, usePublish } from "@/hooks/store";
|
||||||
// interfaces
|
// interfaces
|
||||||
import { IIssue } from "@/types/issue";
|
import { IIssue } from "@/types/issue";
|
||||||
// store
|
// store
|
||||||
|
|
||||||
type IssueListBlockProps = {
|
type IssueListBlockProps = {
|
||||||
|
anchor: string;
|
||||||
issue: IIssue;
|
issue: IIssue;
|
||||||
workspaceSlug: string;
|
|
||||||
projectId: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueListBlock: FC<IssueListBlockProps> = observer((props) => {
|
export const IssueListBlock: FC<IssueListBlockProps> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, issue } = props;
|
const { anchor, issue } = props;
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
// query params
|
// query params
|
||||||
const board = searchParams.get("board") || undefined;
|
const board = searchParams.get("board") || undefined;
|
||||||
const state = searchParams.get("state") || undefined;
|
const state = searchParams.get("state") || undefined;
|
||||||
const priority = searchParams.get("priority") || undefined;
|
const priority = searchParams.get("priority") || undefined;
|
||||||
const labels = searchParams.get("labels") || undefined;
|
const labels = searchParams.get("labels") || undefined;
|
||||||
// store
|
// store hooks
|
||||||
const { project } = useProject();
|
|
||||||
const { setPeekId } = useIssueDetails();
|
const { setPeekId } = useIssueDetails();
|
||||||
|
const { project_details } = usePublish(anchor);
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@ -39,7 +38,7 @@ export const IssueListBlock: FC<IssueListBlockProps> = observer((props) => {
|
|||||||
setPeekId(issue.id);
|
setPeekId(issue.id);
|
||||||
|
|
||||||
const { queryParam } = queryParamGenerator({ board, peekId: issue.id, priority, state, labels });
|
const { queryParam } = queryParamGenerator({ board, peekId: issue.id, priority, state, labels });
|
||||||
router.push(`/${workspaceSlug}/${projectId}?${queryParam}`);
|
router.push(`/issues/${anchor}?${queryParam}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -47,7 +46,7 @@ export const IssueListBlock: FC<IssueListBlockProps> = observer((props) => {
|
|||||||
<div className="relative flex w-full flex-grow items-center gap-3 overflow-hidden">
|
<div className="relative flex w-full flex-grow items-center gap-3 overflow-hidden">
|
||||||
{/* id */}
|
{/* id */}
|
||||||
<div className="flex-shrink-0 text-xs font-medium text-custom-text-300">
|
<div className="flex-shrink-0 text-xs font-medium text-custom-text-300">
|
||||||
{project?.identifier}-{issue?.sequence_id}
|
{project_details?.identifier}-{issue?.sequence_id}
|
||||||
</div>
|
</div>
|
||||||
{/* name */}
|
{/* name */}
|
||||||
<div onClick={handleBlockClick} className="flex-grow cursor-pointer truncate text-sm font-medium">
|
<div onClick={handleBlockClick} className="flex-grow cursor-pointer truncate text-sm font-medium">
|
||||||
|
@ -6,37 +6,36 @@ import { IssueListBlock } from "@/components/issues/board-views/list/block";
|
|||||||
import { IssueListHeader } from "@/components/issues/board-views/list/header";
|
import { IssueListHeader } from "@/components/issues/board-views/list/header";
|
||||||
// mobx hook
|
// mobx hook
|
||||||
import { useIssue } from "@/hooks/store";
|
import { useIssue } from "@/hooks/store";
|
||||||
// types
|
|
||||||
import { IIssueState, IIssue } from "@/types/issue";
|
|
||||||
|
|
||||||
type IssueListViewProps = {
|
type IssueListViewProps = {
|
||||||
workspaceSlug: string;
|
anchor: string;
|
||||||
projectId: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueListView: FC<IssueListViewProps> = observer((props) => {
|
export const IssueListView: FC<IssueListViewProps> = observer((props) => {
|
||||||
const { workspaceSlug, projectId } = props;
|
const { anchor } = props;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { states, getFilteredIssuesByState } = useIssue();
|
const { states, getFilteredIssuesByState } = useIssue();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{states &&
|
{states?.map((state) => {
|
||||||
states.length > 0 &&
|
const issues = getFilteredIssuesByState(state.id);
|
||||||
states.map((_state: IIssueState) => (
|
|
||||||
<div key={_state.id} className="relative w-full">
|
return (
|
||||||
<IssueListHeader state={_state} />
|
<div key={state.id} className="relative w-full">
|
||||||
{getFilteredIssuesByState(_state.id) && getFilteredIssuesByState(_state.id).length > 0 ? (
|
<IssueListHeader state={state} />
|
||||||
|
{issues && issues.length > 0 ? (
|
||||||
<div className="divide-y divide-custom-border-200">
|
<div className="divide-y divide-custom-border-200">
|
||||||
{getFilteredIssuesByState(_state.id).map((_issue: IIssue) => (
|
{issues.map((issue) => (
|
||||||
<IssueListBlock key={_issue.id} issue={_issue} workspaceSlug={workspaceSlug} projectId={projectId} />
|
<IssueListBlock key={issue.id} anchor={anchor} issue={issue} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="bg-custom-background-100 p-3 text-sm text-custom-text-400">No issues.</div>
|
<div className="bg-custom-background-100 p-3 text-sm text-custom-text-400">No issues.</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
);
|
||||||
|
})}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -5,24 +5,24 @@ import cloneDeep from "lodash/cloneDeep";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssue, useIssueFilter } from "@/hooks/store";
|
import { useIssue, useIssueFilter, usePublish } from "@/hooks/store";
|
||||||
// store
|
// store
|
||||||
import { TIssueQueryFilters } from "@/types/issue";
|
import { TIssueQueryFilters } from "@/types/issue";
|
||||||
// components
|
// components
|
||||||
import { AppliedFiltersList } from "./filters-list";
|
import { AppliedFiltersList } from "./filters-list";
|
||||||
|
|
||||||
type TIssueAppliedFilters = {
|
type TIssueAppliedFilters = {
|
||||||
workspaceSlug: string;
|
anchor: string;
|
||||||
projectId: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueAppliedFilters: FC<TIssueAppliedFilters> = observer((props) => {
|
export const IssueAppliedFilters: FC<TIssueAppliedFilters> = observer((props) => {
|
||||||
|
const { anchor } = props;
|
||||||
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
// props
|
// store hooks
|
||||||
const { workspaceSlug, projectId } = props;
|
|
||||||
// hooks
|
|
||||||
const { issueFilters, initIssueFilters, updateIssueFilters } = useIssueFilter();
|
const { issueFilters, initIssueFilters, updateIssueFilters } = useIssueFilter();
|
||||||
const { states, labels } = useIssue();
|
const { states, labels } = useIssue();
|
||||||
|
const { project: projectID } = usePublish(anchor);
|
||||||
|
|
||||||
const activeLayout = issueFilters?.display_filters?.layout || undefined;
|
const activeLayout = issueFilters?.display_filters?.layout || undefined;
|
||||||
const userFilters = issueFilters?.filters || {};
|
const userFilters = issueFilters?.filters || {};
|
||||||
@ -46,30 +46,30 @@ export const IssueAppliedFilters: FC<TIssueAppliedFilters> = observer((props) =>
|
|||||||
if (labels.length > 0) params = { ...params, labels: labels.join(",") };
|
if (labels.length > 0) params = { ...params, labels: labels.join(",") };
|
||||||
params = new URLSearchParams(params).toString();
|
params = new URLSearchParams(params).toString();
|
||||||
|
|
||||||
router.push(`/${workspaceSlug}/${projectId}?${params}`);
|
router.push(`/issues/${anchor}?${params}`);
|
||||||
},
|
},
|
||||||
[workspaceSlug, projectId, activeLayout, issueFilters, router]
|
[activeLayout, anchor, issueFilters, router]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFilters = useCallback(
|
const handleFilters = useCallback(
|
||||||
(key: keyof TIssueQueryFilters, value: string | null) => {
|
(key: keyof TIssueQueryFilters, value: string | null) => {
|
||||||
if (!projectId) return;
|
if (!projectID) return;
|
||||||
|
|
||||||
let newValues = cloneDeep(issueFilters?.filters?.[key]) ?? [];
|
let newValues = cloneDeep(issueFilters?.filters?.[key]) ?? [];
|
||||||
|
|
||||||
if (value === null) newValues = [];
|
if (value === null) newValues = [];
|
||||||
else if (newValues.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
else if (newValues.includes(value)) newValues.splice(newValues.indexOf(value), 1);
|
||||||
|
|
||||||
updateIssueFilters(projectId, "filters", key, newValues);
|
updateIssueFilters(projectID, "filters", key, newValues);
|
||||||
updateRouteParams(key, newValues);
|
updateRouteParams(key, newValues);
|
||||||
},
|
},
|
||||||
[projectId, issueFilters, updateIssueFilters, updateRouteParams]
|
[projectID, issueFilters, updateIssueFilters, updateRouteParams]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleRemoveAllFilters = () => {
|
const handleRemoveAllFilters = () => {
|
||||||
if (!projectId) return;
|
if (!projectID) return;
|
||||||
|
|
||||||
initIssueFilters(projectId, {
|
initIssueFilters(projectID, {
|
||||||
display_filters: { layout: activeLayout || "list" },
|
display_filters: { layout: activeLayout || "list" },
|
||||||
filters: {
|
filters: {
|
||||||
state: [],
|
state: [],
|
||||||
@ -78,7 +78,7 @@ export const IssueAppliedFilters: FC<TIssueAppliedFilters> = observer((props) =>
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
router.push(`/${workspaceSlug}/${projectId}?${`board=${activeLayout || "list"}`}`);
|
router.push(`/issues/${anchor}?${`board=${activeLayout || "list"}`}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Object.keys(appliedFilters).length === 0) return null;
|
if (Object.keys(appliedFilters).length === 0) return null;
|
||||||
|
@ -8,7 +8,7 @@ import { TOAST_TYPE, setToast } from "@plane/ui";
|
|||||||
// editor components
|
// editor components
|
||||||
import { LiteTextEditor } from "@/components/editor/lite-text-editor";
|
import { LiteTextEditor } from "@/components/editor/lite-text-editor";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssueDetails, useProject, useUser } from "@/hooks/store";
|
import { useIssueDetails, usePublish, useUser } from "@/hooks/store";
|
||||||
// types
|
// types
|
||||||
import { Comment } from "@/types/issue";
|
import { Comment } from "@/types/issue";
|
||||||
|
|
||||||
@ -17,22 +17,18 @@ const defaultValues: Partial<Comment> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
anchor: string;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
workspaceSlug: string;
|
|
||||||
projectId: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AddComment: React.FC<Props> = observer((props) => {
|
export const AddComment: React.FC<Props> = observer((props) => {
|
||||||
// const { disabled = false } = props;
|
const { anchor } = props;
|
||||||
const { workspaceSlug, projectId } = props;
|
|
||||||
// refs
|
// refs
|
||||||
const editorRef = useRef<EditorRefApi>(null);
|
const editorRef = useRef<EditorRefApi>(null);
|
||||||
// store hooks
|
// store hooks
|
||||||
const { workspace } = useProject();
|
|
||||||
const { peekId: issueId, addIssueComment } = useIssueDetails();
|
const { peekId: issueId, addIssueComment } = useIssueDetails();
|
||||||
const { data: currentUser } = useUser();
|
const { data: currentUser } = useUser();
|
||||||
// derived values
|
const { workspaceSlug, workspace: workspaceID } = usePublish(anchor);
|
||||||
const workspaceId = workspace?.id;
|
|
||||||
// form info
|
// form info
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@ -43,9 +39,9 @@ export const AddComment: React.FC<Props> = observer((props) => {
|
|||||||
} = useForm<Comment>({ defaultValues });
|
} = useForm<Comment>({ defaultValues });
|
||||||
|
|
||||||
const onSubmit = async (formData: Comment) => {
|
const onSubmit = async (formData: Comment) => {
|
||||||
if (!workspaceSlug || !projectId || !issueId || isSubmitting || !formData.comment_html) return;
|
if (!anchor || !issueId || isSubmitting || !formData.comment_html) return;
|
||||||
|
|
||||||
await addIssueComment(workspaceSlug, projectId, issueId, formData)
|
await addIssueComment(anchor, issueId, formData)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
reset(defaultValues);
|
reset(defaultValues);
|
||||||
editorRef.current?.clearEditor();
|
editorRef.current?.clearEditor();
|
||||||
@ -71,8 +67,8 @@ export const AddComment: React.FC<Props> = observer((props) => {
|
|||||||
onEnterKeyPress={(e) => {
|
onEnterKeyPress={(e) => {
|
||||||
if (currentUser) handleSubmit(onSubmit)(e);
|
if (currentUser) handleSubmit(onSubmit)(e);
|
||||||
}}
|
}}
|
||||||
workspaceId={workspaceId as string}
|
workspaceId={workspaceID?.toString() ?? ""}
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug?.toString() ?? ""}
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
initialValue={
|
initialValue={
|
||||||
!value || value === "" || (typeof value === "object" && Object.keys(value).length === 0)
|
!value || value === "" || (typeof value === "object" && Object.keys(value).length === 0)
|
||||||
|
@ -10,25 +10,23 @@ import { CommentReactions } from "@/components/issues/peek-overview";
|
|||||||
// helpers
|
// helpers
|
||||||
import { timeAgo } from "@/helpers/date-time.helper";
|
import { timeAgo } from "@/helpers/date-time.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssueDetails, useProject, useUser } from "@/hooks/store";
|
import { useIssueDetails, usePublish, useUser } from "@/hooks/store";
|
||||||
import useIsInIframe from "@/hooks/use-is-in-iframe";
|
import useIsInIframe from "@/hooks/use-is-in-iframe";
|
||||||
// types
|
// types
|
||||||
import { Comment } from "@/types/issue";
|
import { Comment } from "@/types/issue";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workspaceSlug: string;
|
anchor: string;
|
||||||
comment: Comment;
|
comment: Comment;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CommentCard: React.FC<Props> = observer((props) => {
|
export const CommentCard: React.FC<Props> = observer((props) => {
|
||||||
const { comment, workspaceSlug } = props;
|
const { anchor, comment } = props;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { workspace } = useProject();
|
|
||||||
const { peekId, deleteIssueComment, updateIssueComment } = useIssueDetails();
|
const { peekId, deleteIssueComment, updateIssueComment } = useIssueDetails();
|
||||||
const { data: currentUser } = useUser();
|
const { data: currentUser } = useUser();
|
||||||
|
const { workspaceSlug, workspace: workspaceID } = usePublish(anchor);
|
||||||
const isInIframe = useIsInIframe();
|
const isInIframe = useIsInIframe();
|
||||||
// derived values
|
|
||||||
const workspaceId = workspace?.id;
|
|
||||||
|
|
||||||
// states
|
// states
|
||||||
const [isEditing, setIsEditing] = useState(false);
|
const [isEditing, setIsEditing] = useState(false);
|
||||||
@ -45,13 +43,13 @@ export const CommentCard: React.FC<Props> = observer((props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleDelete = () => {
|
const handleDelete = () => {
|
||||||
if (!workspaceSlug || !peekId) return;
|
if (!anchor || !peekId) return;
|
||||||
deleteIssueComment(workspaceSlug, comment.project, peekId, comment.id);
|
deleteIssueComment(anchor, peekId, comment.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCommentUpdate = async (formData: Comment) => {
|
const handleCommentUpdate = async (formData: Comment) => {
|
||||||
if (!workspaceSlug || !peekId) return;
|
if (!anchor || !peekId) return;
|
||||||
updateIssueComment(workspaceSlug, comment.project, peekId, comment.id, formData);
|
updateIssueComment(anchor, peekId, comment.id, formData);
|
||||||
setIsEditing(false);
|
setIsEditing(false);
|
||||||
editorRef.current?.setEditorValue(formData.comment_html);
|
editorRef.current?.setEditorValue(formData.comment_html);
|
||||||
showEditorRef.current?.setEditorValue(formData.comment_html);
|
showEditorRef.current?.setEditorValue(formData.comment_html);
|
||||||
@ -103,8 +101,8 @@ export const CommentCard: React.FC<Props> = observer((props) => {
|
|||||||
name="comment_html"
|
name="comment_html"
|
||||||
render={({ field: { onChange, value } }) => (
|
render={({ field: { onChange, value } }) => (
|
||||||
<LiteTextEditor
|
<LiteTextEditor
|
||||||
workspaceId={workspaceId as string}
|
workspaceId={workspaceID?.toString() ?? ""}
|
||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug?.toString() ?? ""}
|
||||||
onEnterKeyPress={handleSubmit(handleCommentUpdate)}
|
onEnterKeyPress={handleSubmit(handleCommentUpdate)}
|
||||||
ref={editorRef}
|
ref={editorRef}
|
||||||
initialValue={value}
|
initialValue={value}
|
||||||
@ -135,7 +133,7 @@ export const CommentCard: React.FC<Props> = observer((props) => {
|
|||||||
</form>
|
</form>
|
||||||
<div className={`${isEditing ? "hidden" : ""}`}>
|
<div className={`${isEditing ? "hidden" : ""}`}>
|
||||||
<LiteTextReadOnlyEditor ref={showEditorRef} initialValue={comment.comment_html} />
|
<LiteTextReadOnlyEditor ref={showEditorRef} initialValue={comment.comment_html} />
|
||||||
<CommentReactions commentId={comment.id} projectId={comment.project} workspaceSlug={workspaceSlug} />
|
<CommentReactions anchor={anchor} commentId={comment.id} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -13,12 +13,12 @@ import { useIssueDetails, useUser } from "@/hooks/store";
|
|||||||
import useIsInIframe from "@/hooks/use-is-in-iframe";
|
import useIsInIframe from "@/hooks/use-is-in-iframe";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
anchor: string;
|
||||||
commentId: string;
|
commentId: string;
|
||||||
projectId: string;
|
|
||||||
workspaceSlug: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CommentReactions: React.FC<Props> = observer((props) => {
|
export const CommentReactions: React.FC<Props> = observer((props) => {
|
||||||
|
const { anchor, commentId } = props;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const pathName = usePathname();
|
const pathName = usePathname();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
@ -28,7 +28,6 @@ export const CommentReactions: React.FC<Props> = observer((props) => {
|
|||||||
const priority = searchParams.get("priority") || undefined;
|
const priority = searchParams.get("priority") || undefined;
|
||||||
const labels = searchParams.get("labels") || undefined;
|
const labels = searchParams.get("labels") || undefined;
|
||||||
|
|
||||||
const { commentId, projectId, workspaceSlug } = props;
|
|
||||||
// hooks
|
// hooks
|
||||||
const { addCommentReaction, removeCommentReaction, details, peekId } = useIssueDetails();
|
const { addCommentReaction, removeCommentReaction, details, peekId } = useIssueDetails();
|
||||||
const { data: user } = useUser();
|
const { data: user } = useUser();
|
||||||
@ -40,13 +39,13 @@ export const CommentReactions: React.FC<Props> = observer((props) => {
|
|||||||
const userReactions = commentReactions?.filter((r) => r?.actor_detail?.id === user?.id);
|
const userReactions = commentReactions?.filter((r) => r?.actor_detail?.id === user?.id);
|
||||||
|
|
||||||
const handleAddReaction = (reactionHex: string) => {
|
const handleAddReaction = (reactionHex: string) => {
|
||||||
if (!workspaceSlug || !projectId || !peekId) return;
|
if (!anchor || !peekId) return;
|
||||||
addCommentReaction(workspaceSlug, projectId, peekId, commentId, reactionHex);
|
addCommentReaction(anchor, peekId, commentId, reactionHex);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveReaction = (reactionHex: string) => {
|
const handleRemoveReaction = (reactionHex: string) => {
|
||||||
if (!workspaceSlug || !projectId || !peekId) return;
|
if (!anchor || !peekId) return;
|
||||||
removeCommentReaction(workspaceSlug, projectId, peekId, commentId, reactionHex);
|
removeCommentReaction(anchor, peekId, commentId, reactionHex);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReactionClick = (reactionHex: string) => {
|
const handleReactionClick = (reactionHex: string) => {
|
||||||
|
@ -11,14 +11,13 @@ import {
|
|||||||
import { IIssue } from "@/types/issue";
|
import { IIssue } from "@/types/issue";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
anchor: string;
|
||||||
handleClose: () => void;
|
handleClose: () => void;
|
||||||
issueDetails: IIssue | undefined;
|
issueDetails: IIssue | undefined;
|
||||||
workspaceSlug: string;
|
|
||||||
projectId: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FullScreenPeekView: React.FC<Props> = observer((props) => {
|
export const FullScreenPeekView: React.FC<Props> = observer((props) => {
|
||||||
const { handleClose, issueDetails, workspaceSlug, projectId } = props;
|
const { anchor, handleClose, issueDetails } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid h-full w-full grid-cols-10 divide-x divide-custom-border-200 overflow-hidden">
|
<div className="grid h-full w-full grid-cols-10 divide-x divide-custom-border-200 overflow-hidden">
|
||||||
@ -36,11 +35,7 @@ export const FullScreenPeekView: React.FC<Props> = observer((props) => {
|
|||||||
<div className="my-5 h-[1] w-full border-t border-custom-border-200" />
|
<div className="my-5 h-[1] w-full border-t border-custom-border-200" />
|
||||||
{/* issue activity/comments */}
|
{/* issue activity/comments */}
|
||||||
<div className="w-full pb-5">
|
<div className="w-full pb-5">
|
||||||
<PeekOverviewIssueActivity
|
<PeekOverviewIssueActivity anchor={anchor} issueDetails={issueDetails} />
|
||||||
issueDetails={issueDetails}
|
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
@ -13,13 +13,12 @@ import useIsInIframe from "@/hooks/use-is-in-iframe";
|
|||||||
import { IIssue } from "@/types/issue";
|
import { IIssue } from "@/types/issue";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
anchor: string;
|
||||||
issueDetails: IIssue;
|
issueDetails: IIssue;
|
||||||
workspaceSlug: string;
|
|
||||||
projectId: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PeekOverviewIssueActivity: React.FC<Props> = observer((props) => {
|
export const PeekOverviewIssueActivity: React.FC<Props> = observer((props) => {
|
||||||
const { workspaceSlug, projectId } = props;
|
const { anchor } = props;
|
||||||
// router
|
// router
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
// store
|
// store
|
||||||
@ -33,35 +32,33 @@ export const PeekOverviewIssueActivity: React.FC<Props> = observer((props) => {
|
|||||||
return (
|
return (
|
||||||
<div className="pb-10">
|
<div className="pb-10">
|
||||||
<h4 className="font-medium">Comments</h4>
|
<h4 className="font-medium">Comments</h4>
|
||||||
{workspaceSlug && (
|
<div className="mt-4">
|
||||||
<div className="mt-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-4">
|
{comments.map((comment) => (
|
||||||
{comments.map((comment: any) => (
|
<CommentCard key={comment.id} anchor={anchor} comment={comment} />
|
||||||
<CommentCard key={comment.id} comment={comment} workspaceSlug={workspaceSlug?.toString()} />
|
))}
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
{!isInIframe &&
|
|
||||||
(currentUser ? (
|
|
||||||
<>
|
|
||||||
{canComment && (
|
|
||||||
<div className="mt-4">
|
|
||||||
<AddComment disabled={!currentUser} workspaceSlug={workspaceSlug} projectId={projectId} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<div className="mt-4 flex items-center justify-between gap-2 rounded border border-custom-border-300 bg-custom-background-80 px-2 py-2.5">
|
|
||||||
<p className="flex gap-2 overflow-hidden break-words text-sm text-custom-text-200">
|
|
||||||
<Icon iconName="lock" className="!text-sm" />
|
|
||||||
Sign in to add your comment
|
|
||||||
</p>
|
|
||||||
<Link href={`/?next_path=${pathname}`}>
|
|
||||||
<Button variant="primary">Sign in</Button>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
{!isInIframe &&
|
||||||
|
(currentUser ? (
|
||||||
|
<>
|
||||||
|
{canComment && (
|
||||||
|
<div className="mt-4">
|
||||||
|
<AddComment anchor={anchor} disabled={!currentUser} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="mt-4 flex items-center justify-between gap-2 rounded border border-custom-border-300 bg-custom-background-80 px-2 py-2.5">
|
||||||
|
<p className="flex gap-2 overflow-hidden break-words text-sm text-custom-text-200">
|
||||||
|
<Icon iconName="lock" className="!text-sm" />
|
||||||
|
Sign in to add your comment
|
||||||
|
</p>
|
||||||
|
<Link href={`/?next_path=${pathname}`}>
|
||||||
|
<Button variant="primary">Sign in</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -9,7 +9,7 @@ import useIsInIframe from "@/hooks/use-is-in-iframe";
|
|||||||
// };
|
// };
|
||||||
|
|
||||||
export const IssueReactions: React.FC = () => {
|
export const IssueReactions: React.FC = () => {
|
||||||
const { workspace_slug: workspaceSlug, project_id: projectId } = useParams<any>();
|
const { workspaceSlug, projectId } = useParams<any>();
|
||||||
|
|
||||||
const { canVote, canReact } = useProject();
|
const { canVote, canReact } = useProject();
|
||||||
const isInIframe = useIsInIframe();
|
const isInIframe = useIsInIframe();
|
||||||
|
@ -10,13 +10,12 @@ import { FullScreenPeekView, SidePeekView } from "@/components/issues/peek-overv
|
|||||||
import { useIssue, useIssueDetails } from "@/hooks/store";
|
import { useIssue, useIssueDetails } from "@/hooks/store";
|
||||||
|
|
||||||
type TIssuePeekOverview = {
|
type TIssuePeekOverview = {
|
||||||
workspaceSlug: string;
|
anchor: string;
|
||||||
projectId: string;
|
|
||||||
peekId: string;
|
peekId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssuePeekOverview: FC<TIssuePeekOverview> = observer((props) => {
|
export const IssuePeekOverview: FC<TIssuePeekOverview> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, peekId } = props;
|
const { anchor, peekId } = props;
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
// query params
|
// query params
|
||||||
@ -34,21 +33,23 @@ export const IssuePeekOverview: FC<TIssuePeekOverview> = observer((props) => {
|
|||||||
const issueDetails = issueDetailStore.peekId && peekId ? issueDetailStore.details[peekId.toString()] : undefined;
|
const issueDetails = issueDetailStore.peekId && peekId ? issueDetailStore.details[peekId.toString()] : undefined;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (workspaceSlug && projectId && peekId && issueStore.issues && issueStore.issues.length > 0) {
|
if (anchor && peekId && issueStore.issues && issueStore.issues.length > 0) {
|
||||||
if (!issueDetails) {
|
if (!issueDetails) {
|
||||||
issueDetailStore.fetchIssueDetails(workspaceSlug.toString(), projectId.toString(), peekId.toString());
|
issueDetailStore.fetchIssueDetails(anchor, peekId.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [workspaceSlug, projectId, issueDetailStore, issueDetails, peekId, issueStore.issues]);
|
}, [anchor, issueDetailStore, issueDetails, peekId, issueStore.issues]);
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
issueDetailStore.setPeekId(null);
|
issueDetailStore.setPeekId(null);
|
||||||
let queryParams: any = { board: board };
|
let queryParams: any = {
|
||||||
|
board,
|
||||||
|
};
|
||||||
if (priority && priority.length > 0) queryParams = { ...queryParams, priority: priority };
|
if (priority && priority.length > 0) queryParams = { ...queryParams, priority: priority };
|
||||||
if (state && state.length > 0) queryParams = { ...queryParams, state: state };
|
if (state && state.length > 0) queryParams = { ...queryParams, state: state };
|
||||||
if (labels && labels.length > 0) queryParams = { ...queryParams, labels: labels };
|
if (labels && labels.length > 0) queryParams = { ...queryParams, labels: labels };
|
||||||
queryParams = new URLSearchParams(queryParams).toString();
|
queryParams = new URLSearchParams(queryParams).toString();
|
||||||
router.push(`/${workspaceSlug}/${projectId}?${queryParams}`);
|
router.push(`/issues/${anchor}?${queryParams}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -80,12 +81,7 @@ export const IssuePeekOverview: FC<TIssuePeekOverview> = observer((props) => {
|
|||||||
leaveTo="translate-x-full"
|
leaveTo="translate-x-full"
|
||||||
>
|
>
|
||||||
<Dialog.Panel className="fixed right-0 top-0 z-20 h-full w-1/2 bg-custom-background-100 shadow-custom-shadow-sm">
|
<Dialog.Panel className="fixed right-0 top-0 z-20 h-full w-1/2 bg-custom-background-100 shadow-custom-shadow-sm">
|
||||||
<SidePeekView
|
<SidePeekView anchor={anchor} handleClose={handleClose} issueDetails={issueDetails} />
|
||||||
handleClose={handleClose}
|
|
||||||
issueDetails={issueDetails}
|
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
/>
|
|
||||||
</Dialog.Panel>
|
</Dialog.Panel>
|
||||||
</Transition.Child>
|
</Transition.Child>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@ -119,20 +115,10 @@ export const IssuePeekOverview: FC<TIssuePeekOverview> = observer((props) => {
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{issueDetailStore.peekMode === "modal" && (
|
{issueDetailStore.peekMode === "modal" && (
|
||||||
<SidePeekView
|
<SidePeekView anchor={anchor} handleClose={handleClose} issueDetails={issueDetails} />
|
||||||
handleClose={handleClose}
|
|
||||||
issueDetails={issueDetails}
|
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{issueDetailStore.peekMode === "full" && (
|
{issueDetailStore.peekMode === "full" && (
|
||||||
<FullScreenPeekView
|
<FullScreenPeekView anchor={anchor} handleClose={handleClose} issueDetails={issueDetails} />
|
||||||
handleClose={handleClose}
|
|
||||||
issueDetails={issueDetails}
|
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Dialog.Panel>
|
</Dialog.Panel>
|
||||||
|
@ -7,22 +7,21 @@ import {
|
|||||||
PeekOverviewIssueDetails,
|
PeekOverviewIssueDetails,
|
||||||
PeekOverviewIssueProperties,
|
PeekOverviewIssueProperties,
|
||||||
} from "@/components/issues/peek-overview";
|
} from "@/components/issues/peek-overview";
|
||||||
// hooks
|
// store hooks
|
||||||
import { useProject } from "@/hooks/store";
|
import { usePublish } from "@/hooks/store";
|
||||||
// types
|
// types
|
||||||
import { IIssue } from "@/types/issue";
|
import { IIssue } from "@/types/issue";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
|
anchor: string;
|
||||||
handleClose: () => void;
|
handleClose: () => void;
|
||||||
issueDetails: IIssue | undefined;
|
issueDetails: IIssue | undefined;
|
||||||
workspaceSlug: string;
|
|
||||||
projectId: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SidePeekView: React.FC<Props> = observer((props) => {
|
export const SidePeekView: React.FC<Props> = observer((props) => {
|
||||||
const { handleClose, issueDetails, workspaceSlug, projectId } = props;
|
const { anchor, handleClose, issueDetails } = props;
|
||||||
|
// store hooks
|
||||||
const { settings } = useProject();
|
const { comments } = usePublish(anchor);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full w-full flex-col overflow-hidden">
|
<div className="flex h-full w-full flex-col overflow-hidden">
|
||||||
@ -42,13 +41,9 @@ export const SidePeekView: React.FC<Props> = observer((props) => {
|
|||||||
{/* divider */}
|
{/* divider */}
|
||||||
<div className="my-5 h-[1] w-full border-t border-custom-border-200" />
|
<div className="my-5 h-[1] w-full border-t border-custom-border-200" />
|
||||||
{/* issue activity/comments */}
|
{/* issue activity/comments */}
|
||||||
{settings?.comments && (
|
{comments && (
|
||||||
<div className="w-full pb-5">
|
<div className="w-full pb-5">
|
||||||
<PeekOverviewIssueActivity
|
<PeekOverviewIssueActivity anchor={anchor} issueDetails={issueDetails} />
|
||||||
issueDetails={issueDetails}
|
|
||||||
workspaceSlug={workspaceSlug}
|
|
||||||
projectId={projectId}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -37,12 +37,11 @@ export const ProjectDetailsView: FC<ProjectDetailsViewProps> = observer((props)
|
|||||||
const { loader, issues, error, fetchPublicIssues } = useIssue();
|
const { loader, issues, error, fetchPublicIssues } = useIssue();
|
||||||
const issueDetailStore = useIssueDetails();
|
const issueDetailStore = useIssueDetails();
|
||||||
// derived values
|
// derived values
|
||||||
const { workspace_detail, project } = publishSettings;
|
const { anchor } = publishSettings;
|
||||||
const workspaceSlug = workspace_detail?.slug;
|
|
||||||
|
|
||||||
useSWR(
|
useSWR(
|
||||||
workspaceSlug && project ? `WORKSPACE_PROJECT_PUBLIC_ISSUES_${workspaceSlug}_${project}` : null,
|
anchor ? `PUBLIC_ISSUES_${anchor}` : null,
|
||||||
workspaceSlug && project ? () => fetchPublicIssues(workspaceSlug, project, { states, priority, labels }) : null
|
anchor ? () => fetchPublicIssues(anchor, { states, priority, labels }) : null
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -54,11 +53,11 @@ export const ProjectDetailsView: FC<ProjectDetailsViewProps> = observer((props)
|
|||||||
// derived values
|
// derived values
|
||||||
const activeLayout = issueFilters?.display_filters?.layout || undefined;
|
const activeLayout = issueFilters?.display_filters?.layout || undefined;
|
||||||
|
|
||||||
if (!workspaceSlug || !project) return null;
|
if (!anchor) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-full w-full overflow-hidden">
|
<div className="relative h-full w-full overflow-hidden">
|
||||||
{peekId && <IssuePeekOverview workspaceSlug={workspaceSlug} projectId={project} peekId={peekId} />}
|
{peekId && <IssuePeekOverview anchor={anchor} peekId={peekId} />}
|
||||||
|
|
||||||
{loader && !issues ? (
|
{loader && !issues ? (
|
||||||
<div className="py-10 text-center text-sm text-custom-text-100">Loading...</div>
|
<div className="py-10 text-center text-sm text-custom-text-100">Loading...</div>
|
||||||
@ -80,16 +79,16 @@ export const ProjectDetailsView: FC<ProjectDetailsViewProps> = observer((props)
|
|||||||
activeLayout && (
|
activeLayout && (
|
||||||
<div className="relative flex h-full w-full flex-col overflow-hidden">
|
<div className="relative flex h-full w-full flex-col overflow-hidden">
|
||||||
{/* applied filters */}
|
{/* applied filters */}
|
||||||
<IssueAppliedFilters workspaceSlug={workspaceSlug} projectId={project} />
|
<IssueAppliedFilters anchor={anchor} />
|
||||||
|
|
||||||
{activeLayout === "list" && (
|
{activeLayout === "list" && (
|
||||||
<div className="relative h-full w-full overflow-y-auto">
|
<div className="relative h-full w-full overflow-y-auto">
|
||||||
<IssueListView workspaceSlug={workspaceSlug} projectId={project} />
|
<IssueListView anchor={anchor} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{activeLayout === "kanban" && (
|
{activeLayout === "kanban" && (
|
||||||
<div className="relative mx-auto h-full w-full p-5">
|
<div className="relative mx-auto h-full w-full p-5">
|
||||||
<IssueKanbanView workspaceSlug={workspaceSlug} projectId={project} />
|
<IssueKanbanView anchor={anchor} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{activeLayout === "calendar" && <IssueCalendarView />}
|
{activeLayout === "calendar" && <IssueCalendarView />}
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||||
// services
|
// services
|
||||||
import { APIService } from "@/services/api.service";
|
import { APIService } from "@/services/api.service";
|
||||||
|
// types
|
||||||
|
import { TIssuesResponse } from "@/types/issue";
|
||||||
|
|
||||||
class IssueService extends APIService {
|
class IssueService extends APIService {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(API_BASE_URL);
|
super(API_BASE_URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPublicIssues(workspace_slug: string, project_slug: string, params: any): Promise<any> {
|
async fetchPublicIssues(anchor: string, params: any): Promise<TIssuesResponse> {
|
||||||
return this.get(`/api/public/workspaces/${workspace_slug}/project-boards/${project_slug}/issues/`, {
|
return this.get(`/api/public/anchor/${anchor}/issues/`, {
|
||||||
params,
|
params,
|
||||||
})
|
})
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
@ -17,115 +19,88 @@ class IssueService extends APIService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getIssueById(workspaceSlug: string, projectId: string, issueId: string): Promise<any> {
|
async getIssueById(anchor: string, issueID: string): Promise<any> {
|
||||||
return this.get(`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/`)
|
return this.get(`/api/public/anchor/${anchor}/issues/${issueID}/`)
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response;
|
throw error?.response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getIssueVotes(workspaceSlug: string, projectId: string, issueId: string): Promise<any> {
|
async getIssueVotes(anchor: string, issueID: string): Promise<any> {
|
||||||
return this.get(`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/votes/`)
|
return this.get(`/api/public/anchor/${anchor}/issues/${issueID}/votes/`)
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response;
|
throw error?.response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async createIssueVote(workspaceSlug: string, projectId: string, issueId: string, data: any): Promise<any> {
|
async createIssueVote(anchor: string, issueID: string, data: any): Promise<any> {
|
||||||
return this.post(
|
return this.post(`/api/public/anchor/${anchor}/issues/${issueID}/votes/`, data)
|
||||||
`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/votes/`,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response;
|
throw error?.response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteIssueVote(workspaceSlug: string, projectId: string, issueId: string): Promise<any> {
|
async deleteIssueVote(anchor: string, issueID: string): Promise<any> {
|
||||||
return this.delete(`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/votes/`)
|
return this.delete(`/api/public/anchor/${anchor}/issues/${issueID}/votes/`)
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response;
|
throw error?.response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getIssueReactions(workspaceSlug: string, projectId: string, issueId: string): Promise<any> {
|
async getIssueReactions(anchor: string, issueID: string): Promise<any> {
|
||||||
return this.get(`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/reactions/`)
|
return this.get(`/api/public/anchor/${anchor}/issues/${issueID}/reactions/`)
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response;
|
throw error?.response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async createIssueReaction(workspaceSlug: string, projectId: string, issueId: string, data: any): Promise<any> {
|
async createIssueReaction(anchor: string, issueID: string, data: any): Promise<any> {
|
||||||
return this.post(
|
return this.post(`/api/public/anchor/${anchor}/issues/${issueID}/reactions/`, data)
|
||||||
`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/reactions/`,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response;
|
throw error?.response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteIssueReaction(
|
async deleteIssueReaction(anchor: string, issueID: string, reactionId: string): Promise<any> {
|
||||||
workspaceSlug: string,
|
return this.delete(`/api/public/anchor/${anchor}/issues/${issueID}/reactions/${reactionId}/`)
|
||||||
projectId: string,
|
|
||||||
issueId: string,
|
|
||||||
reactionId: string
|
|
||||||
): Promise<any> {
|
|
||||||
return this.delete(
|
|
||||||
`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/reactions/${reactionId}/`
|
|
||||||
)
|
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response;
|
throw error?.response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getIssueComments(workspaceSlug: string, projectId: string, issueId: string): Promise<any> {
|
async getIssueComments(anchor: string, issueID: string): Promise<any> {
|
||||||
return this.get(`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/comments/`)
|
return this.get(`/api/public/anchor/${anchor}/issues/${issueID}/comments/`)
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response;
|
throw error?.response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async createIssueComment(workspaceSlug: string, projectId: string, issueId: string, data: any): Promise<any> {
|
async createIssueComment(anchor: string, issueID: string, data: any): Promise<any> {
|
||||||
return this.post(
|
return this.post(`/api/public/anchor/${anchor}/issues/${issueID}/comments/`, data)
|
||||||
`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/comments/`,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response;
|
throw error?.response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateIssueComment(
|
async updateIssueComment(anchor: string, issueID: string, commentId: string, data: any): Promise<any> {
|
||||||
workspaceSlug: string,
|
return this.patch(`/api/public/anchor/${anchor}/issues/${issueID}/comments/${commentId}/`, data)
|
||||||
projectId: string,
|
|
||||||
issueId: string,
|
|
||||||
commentId: string,
|
|
||||||
data: any
|
|
||||||
): Promise<any> {
|
|
||||||
return this.patch(
|
|
||||||
`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/comments/${commentId}/`,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response;
|
throw error?.response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteIssueComment(workspaceSlug: string, projectId: string, issueId: string, commentId: string): Promise<any> {
|
async deleteIssueComment(anchor: string, issueID: string, commentId: string): Promise<any> {
|
||||||
return this.delete(
|
return this.delete(`/api/public/anchor/${anchor}/issues/${issueID}/comments/${commentId}/`)
|
||||||
`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/issues/${issueId}/comments/${commentId}/`
|
|
||||||
)
|
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response;
|
throw error?.response;
|
||||||
@ -133,32 +108,21 @@ class IssueService extends APIService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async createCommentReaction(
|
async createCommentReaction(
|
||||||
workspaceSlug: string,
|
anchor: string,
|
||||||
projectId: string,
|
|
||||||
commentId: string,
|
commentId: string,
|
||||||
data: {
|
data: {
|
||||||
reaction: string;
|
reaction: string;
|
||||||
}
|
}
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
return this.post(
|
return this.post(`/api/public/anchor/${anchor}/comments/${commentId}/reactions/`, data)
|
||||||
`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/comments/${commentId}/reactions/`,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response;
|
throw error?.response;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteCommentReaction(
|
async deleteCommentReaction(anchor: string, commentId: string, reactionHex: string): Promise<any> {
|
||||||
workspaceSlug: string,
|
return this.delete(`/api/public/anchor/${anchor}/comments/${commentId}/reactions/${reactionHex}/`)
|
||||||
projectId: string,
|
|
||||||
commentId: string,
|
|
||||||
reactionHex: string
|
|
||||||
): Promise<any> {
|
|
||||||
return this.delete(
|
|
||||||
`/api/public/workspaces/${workspaceSlug}/project-boards/${projectId}/comments/${commentId}/reactions/${reactionHex}/`
|
|
||||||
)
|
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response;
|
throw error?.response;
|
||||||
|
@ -9,16 +9,16 @@ export class ProjectMemberService extends APIService {
|
|||||||
super(API_BASE_URL);
|
super(API_BASE_URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchProjectMembers(workspaceSlug: string, projectId: string): Promise<IProjectMembership[]> {
|
async fetchProjectMembers(anchor: string): Promise<IProjectMembership[]> {
|
||||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/`)
|
return this.get(`/api/anchor/${anchor}/members/`)
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response?.data;
|
throw error?.response?.data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProjectMember(workspaceSlug: string, projectId: string, memberId: string): Promise<IProjectMember> {
|
async getProjectMember(anchor: string, memberID: string): Promise<IProjectMember> {
|
||||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/members/${memberId}/`)
|
return this.get(`/api/anchor/${anchor}/members/${memberID}/`)
|
||||||
.then((response) => response?.data)
|
.then((response) => response?.data)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw error?.response?.data;
|
throw error?.response?.data;
|
||||||
|
@ -10,108 +10,102 @@ import { IIssue, IPeekMode, IVote } from "@/types/issue";
|
|||||||
export interface IIssueDetailStore {
|
export interface IIssueDetailStore {
|
||||||
loader: boolean;
|
loader: boolean;
|
||||||
error: any;
|
error: any;
|
||||||
// peek info
|
// observables
|
||||||
peekId: string | null;
|
peekId: string | null;
|
||||||
peekMode: IPeekMode;
|
peekMode: IPeekMode;
|
||||||
details: {
|
details: {
|
||||||
[key: string]: IIssue;
|
[key: string]: IIssue;
|
||||||
};
|
};
|
||||||
// peek actions
|
// actions
|
||||||
setPeekId: (issueId: string | null) => void;
|
setPeekId: (issueID: string | null) => void;
|
||||||
setPeekMode: (mode: IPeekMode) => void;
|
setPeekMode: (mode: IPeekMode) => void;
|
||||||
// issue details
|
// issue actions
|
||||||
fetchIssueDetails: (workspaceId: string, projectId: string, issueId: string) => void;
|
fetchIssueDetails: (anchor: string, issueID: string) => void;
|
||||||
// issue comments
|
// comment actions
|
||||||
addIssueComment: (workspaceId: string, projectId: string, issueId: string, data: any) => Promise<void>;
|
addIssueComment: (anchor: string, issueID: string, data: any) => Promise<void>;
|
||||||
updateIssueComment: (
|
updateIssueComment: (anchor: string, issueID: string, commentID: string, data: any) => Promise<any>;
|
||||||
workspaceId: string,
|
deleteIssueComment: (anchor: string, issueID: string, commentID: string) => void;
|
||||||
projectId: string,
|
addCommentReaction: (anchor: string, issueID: string, commentID: string, reactionHex: string) => void;
|
||||||
issueId: string,
|
removeCommentReaction: (anchor: string, issueID: string, commentID: string, reactionHex: string) => void;
|
||||||
comment_id: string,
|
// reaction actions
|
||||||
data: any
|
addIssueReaction: (anchor: string, issueID: string, reactionHex: string) => void;
|
||||||
) => Promise<any>;
|
removeIssueReaction: (anchor: string, issueID: string, reactionHex: string) => void;
|
||||||
deleteIssueComment: (workspaceId: string, projectId: string, issueId: string, comment_id: string) => void;
|
// vote actions
|
||||||
addCommentReaction: (
|
addIssueVote: (anchor: string, issueID: string, data: { vote: 1 | -1 }) => Promise<void>;
|
||||||
workspaceId: string,
|
removeIssueVote: (anchor: string, issueID: string) => Promise<void>;
|
||||||
projectId: string,
|
|
||||||
issueId: string,
|
|
||||||
commentId: string,
|
|
||||||
reactionHex: string
|
|
||||||
) => void;
|
|
||||||
removeCommentReaction: (
|
|
||||||
workspaceId: string,
|
|
||||||
projectId: string,
|
|
||||||
issueId: string,
|
|
||||||
commentId: string,
|
|
||||||
reactionHex: string
|
|
||||||
) => void;
|
|
||||||
// issue reactions
|
|
||||||
addIssueReaction: (workspaceId: string, projectId: string, issueId: string, reactionHex: string) => void;
|
|
||||||
removeIssueReaction: (workspaceId: string, projectId: string, issueId: string, reactionHex: string) => void;
|
|
||||||
// issue votes
|
|
||||||
addIssueVote: (workspaceId: string, projectId: string, issueId: string, data: { vote: 1 | -1 }) => Promise<void>;
|
|
||||||
removeIssueVote: (workspaceId: string, projectId: string, issueId: string) => Promise<void>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class IssueDetailStore implements IIssueDetailStore {
|
export class IssueDetailStore implements IIssueDetailStore {
|
||||||
loader: boolean = false;
|
loader: boolean = false;
|
||||||
error: any = null;
|
error: any = null;
|
||||||
|
// observables
|
||||||
peekId: string | null = null;
|
peekId: string | null = null;
|
||||||
peekMode: IPeekMode = "side";
|
peekMode: IPeekMode = "side";
|
||||||
details: {
|
details: {
|
||||||
[key: string]: IIssue;
|
[key: string]: IIssue;
|
||||||
} = {};
|
} = {};
|
||||||
issueService;
|
// root store
|
||||||
rootStore: RootStore;
|
rootStore: RootStore;
|
||||||
|
// services
|
||||||
|
issueService: IssueService;
|
||||||
|
|
||||||
constructor(_rootStore: RootStore) {
|
constructor(_rootStore: RootStore) {
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
loader: observable.ref,
|
loader: observable.ref,
|
||||||
error: observable.ref,
|
error: observable.ref,
|
||||||
// peek
|
// observables
|
||||||
peekId: observable.ref,
|
peekId: observable.ref,
|
||||||
peekMode: observable.ref,
|
peekMode: observable.ref,
|
||||||
details: observable.ref,
|
details: observable,
|
||||||
// actions
|
// actions
|
||||||
setPeekId: action,
|
setPeekId: action,
|
||||||
setPeekMode: action,
|
setPeekMode: action,
|
||||||
|
// issue actions
|
||||||
fetchIssueDetails: action,
|
fetchIssueDetails: action,
|
||||||
|
// comment actions
|
||||||
addIssueComment: action,
|
addIssueComment: action,
|
||||||
updateIssueComment: action,
|
updateIssueComment: action,
|
||||||
deleteIssueComment: action,
|
deleteIssueComment: action,
|
||||||
addCommentReaction: action,
|
addCommentReaction: action,
|
||||||
removeCommentReaction: action,
|
removeCommentReaction: action,
|
||||||
|
// reaction actions
|
||||||
addIssueReaction: action,
|
addIssueReaction: action,
|
||||||
removeIssueReaction: action,
|
removeIssueReaction: action,
|
||||||
|
// vote actions
|
||||||
addIssueVote: action,
|
addIssueVote: action,
|
||||||
removeIssueVote: action,
|
removeIssueVote: action,
|
||||||
});
|
});
|
||||||
this.issueService = new IssueService();
|
|
||||||
this.rootStore = _rootStore;
|
this.rootStore = _rootStore;
|
||||||
|
this.issueService = new IssueService();
|
||||||
}
|
}
|
||||||
|
|
||||||
setPeekId = (issueId: string | null) => {
|
setPeekId = (issueID: string | null) => {
|
||||||
this.peekId = issueId;
|
this.peekId = issueID;
|
||||||
};
|
};
|
||||||
|
|
||||||
setPeekMode = (mode: IPeekMode) => {
|
setPeekMode = (mode: IPeekMode) => {
|
||||||
this.peekMode = mode;
|
this.peekMode = mode;
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchIssueDetails = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
/**
|
||||||
|
* @description fetc
|
||||||
|
* @param {string} anchor
|
||||||
|
* @param {string} issueID
|
||||||
|
*/
|
||||||
|
fetchIssueDetails = async (anchor: string, issueID: string) => {
|
||||||
try {
|
try {
|
||||||
this.loader = true;
|
this.loader = true;
|
||||||
this.error = null;
|
this.error = null;
|
||||||
|
|
||||||
const issueDetails = this.rootStore.issue.issues?.find((i) => i.id === issueId);
|
const issueDetails = this.rootStore.issue.issues?.find((i) => i.id === issueID);
|
||||||
const commentsResponse = await this.issueService.getIssueComments(workspaceSlug, projectId, issueId);
|
const commentsResponse = await this.issueService.getIssueComments(anchor, issueID);
|
||||||
|
|
||||||
if (issueDetails) {
|
if (issueDetails) {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.details = {
|
this.details = {
|
||||||
...this.details,
|
...this.details,
|
||||||
[issueId]: {
|
[issueID]: {
|
||||||
...(this.details[issueId] ?? issueDetails),
|
...(this.details[issueID] ?? issueDetails),
|
||||||
comments: commentsResponse,
|
comments: commentsResponse,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -123,17 +117,17 @@ export class IssueDetailStore implements IIssueDetailStore {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
addIssueComment = async (workspaceSlug: string, projectId: string, issueId: string, data: any) => {
|
addIssueComment = async (anchor: string, issueID: string, data: any) => {
|
||||||
try {
|
try {
|
||||||
const issueDetails = this.rootStore.issue.issues?.find((i) => i.id === issueId);
|
const issueDetails = this.rootStore.issue.issues?.find((i) => i.id === issueID);
|
||||||
const issueCommentResponse = await this.issueService.createIssueComment(workspaceSlug, projectId, issueId, data);
|
const issueCommentResponse = await this.issueService.createIssueComment(anchor, issueID, data);
|
||||||
if (issueDetails) {
|
if (issueDetails) {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.details = {
|
this.details = {
|
||||||
...this.details,
|
...this.details,
|
||||||
[issueId]: {
|
[issueID]: {
|
||||||
...issueDetails,
|
...issueDetails,
|
||||||
comments: [...this.details[issueId].comments, issueCommentResponse],
|
comments: [...this.details[issueID].comments, issueCommentResponse],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -145,36 +139,30 @@ export class IssueDetailStore implements IIssueDetailStore {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
updateIssueComment = async (
|
updateIssueComment = async (anchor: string, issueID: string, commentID: string, data: any) => {
|
||||||
workspaceSlug: string,
|
|
||||||
projectId: string,
|
|
||||||
issueId: string,
|
|
||||||
commentId: string,
|
|
||||||
data: any
|
|
||||||
) => {
|
|
||||||
try {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.details = {
|
this.details = {
|
||||||
...this.details,
|
...this.details,
|
||||||
[issueId]: {
|
[issueID]: {
|
||||||
...this.details[issueId],
|
...this.details[issueID],
|
||||||
comments: this.details[issueId].comments.map((c) => ({
|
comments: this.details[issueID].comments.map((c) => ({
|
||||||
...c,
|
...c,
|
||||||
...(c.id === commentId ? data : {}),
|
...(c.id === commentID ? data : {}),
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.issueService.updateIssueComment(workspaceSlug, projectId, issueId, commentId, data);
|
await this.issueService.updateIssueComment(anchor, issueID, commentID, data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const issueComments = await this.issueService.getIssueComments(workspaceSlug, projectId, issueId);
|
const issueComments = await this.issueService.getIssueComments(anchor, issueID);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.details = {
|
this.details = {
|
||||||
...this.details,
|
...this.details,
|
||||||
[issueId]: {
|
[issueID]: {
|
||||||
...this.details[issueId],
|
...this.details[issueID],
|
||||||
comments: issueComments,
|
comments: issueComments,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -182,15 +170,15 @@ export class IssueDetailStore implements IIssueDetailStore {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
deleteIssueComment = async (workspaceSlug: string, projectId: string, issueId: string, comment_id: string) => {
|
deleteIssueComment = async (anchor: string, issueID: string, commentID: string) => {
|
||||||
try {
|
try {
|
||||||
await this.issueService.deleteIssueComment(workspaceSlug, projectId, issueId, comment_id);
|
await this.issueService.deleteIssueComment(anchor, issueID, commentID);
|
||||||
const remainingComments = this.details[issueId].comments.filter((c) => c.id != comment_id);
|
const remainingComments = this.details[issueID].comments.filter((c) => c.id != commentID);
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.details = {
|
this.details = {
|
||||||
...this.details,
|
...this.details,
|
||||||
[issueId]: {
|
[issueID]: {
|
||||||
...this.details[issueId],
|
...this.details[issueID],
|
||||||
comments: remainingComments,
|
comments: remainingComments,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -200,47 +188,41 @@ export class IssueDetailStore implements IIssueDetailStore {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
addCommentReaction = async (
|
addCommentReaction = async (anchor: string, issueID: string, commentID: string, reactionHex: string) => {
|
||||||
workspaceSlug: string,
|
|
||||||
projectId: string,
|
|
||||||
issueId: string,
|
|
||||||
commentId: string,
|
|
||||||
reactionHex: string
|
|
||||||
) => {
|
|
||||||
const newReaction = {
|
const newReaction = {
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
comment: commentId,
|
comment: commentID,
|
||||||
reaction: reactionHex,
|
reaction: reactionHex,
|
||||||
actor_detail: this.rootStore.user.currentActor,
|
actor_detail: this.rootStore.user.currentActor,
|
||||||
};
|
};
|
||||||
const newComments = this.details[issueId].comments.map((comment) => ({
|
const newComments = this.details[issueID].comments.map((comment) => ({
|
||||||
...comment,
|
...comment,
|
||||||
comment_reactions:
|
comment_reactions:
|
||||||
comment.id === commentId ? [...comment.comment_reactions, newReaction] : comment.comment_reactions,
|
comment.id === commentID ? [...comment.comment_reactions, newReaction] : comment.comment_reactions,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.details = {
|
this.details = {
|
||||||
...this.details,
|
...this.details,
|
||||||
[issueId]: {
|
[issueID]: {
|
||||||
...this.details[issueId],
|
...this.details[issueID],
|
||||||
comments: [...newComments],
|
comments: [...newComments],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.issueService.createCommentReaction(workspaceSlug, projectId, commentId, {
|
await this.issueService.createCommentReaction(anchor, commentID, {
|
||||||
reaction: reactionHex,
|
reaction: reactionHex,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const issueComments = await this.issueService.getIssueComments(workspaceSlug, projectId, issueId);
|
const issueComments = await this.issueService.getIssueComments(anchor, issueID);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.details = {
|
this.details = {
|
||||||
...this.details,
|
...this.details,
|
||||||
[issueId]: {
|
[issueID]: {
|
||||||
...this.details[issueId],
|
...this.details[issueID],
|
||||||
comments: issueComments,
|
comments: issueComments,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -248,39 +230,33 @@ export class IssueDetailStore implements IIssueDetailStore {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
removeCommentReaction = async (
|
removeCommentReaction = async (anchor: string, issueID: string, commentID: string, reactionHex: string) => {
|
||||||
workspaceSlug: string,
|
|
||||||
projectId: string,
|
|
||||||
issueId: string,
|
|
||||||
commentId: string,
|
|
||||||
reactionHex: string
|
|
||||||
) => {
|
|
||||||
try {
|
try {
|
||||||
const comment = this.details[issueId].comments.find((c) => c.id === commentId);
|
const comment = this.details[issueID].comments.find((c) => c.id === commentID);
|
||||||
const newCommentReactions = comment?.comment_reactions.filter((r) => r.reaction !== reactionHex) ?? [];
|
const newCommentReactions = comment?.comment_reactions.filter((r) => r.reaction !== reactionHex) ?? [];
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.details = {
|
this.details = {
|
||||||
...this.details,
|
...this.details,
|
||||||
[issueId]: {
|
[issueID]: {
|
||||||
...this.details[issueId],
|
...this.details[issueID],
|
||||||
comments: this.details[issueId].comments.map((c) => ({
|
comments: this.details[issueID].comments.map((c) => ({
|
||||||
...c,
|
...c,
|
||||||
comment_reactions: c.id === commentId ? newCommentReactions : c.comment_reactions,
|
comment_reactions: c.id === commentID ? newCommentReactions : c.comment_reactions,
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.issueService.deleteCommentReaction(workspaceSlug, projectId, commentId, reactionHex);
|
await this.issueService.deleteCommentReaction(anchor, commentID, reactionHex);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const issueComments = await this.issueService.getIssueComments(workspaceSlug, projectId, issueId);
|
const issueComments = await this.issueService.getIssueComments(anchor, issueID);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.details = {
|
this.details = {
|
||||||
...this.details,
|
...this.details,
|
||||||
[issueId]: {
|
[issueID]: {
|
||||||
...this.details[issueId],
|
...this.details[issueID],
|
||||||
comments: issueComments,
|
comments: issueComments,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -288,18 +264,18 @@ export class IssueDetailStore implements IIssueDetailStore {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
addIssueReaction = async (workspaceSlug: string, projectId: string, issueId: string, reactionHex: string) => {
|
addIssueReaction = async (anchor: string, issueID: string, reactionHex: string) => {
|
||||||
try {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.details = {
|
this.details = {
|
||||||
...this.details,
|
...this.details,
|
||||||
[issueId]: {
|
[issueID]: {
|
||||||
...this.details[issueId],
|
...this.details[issueID],
|
||||||
reactions: [
|
reactions: [
|
||||||
...this.details[issueId].reactions,
|
...this.details[issueID].reactions,
|
||||||
{
|
{
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
issue: issueId,
|
issue: issueID,
|
||||||
reaction: reactionHex,
|
reaction: reactionHex,
|
||||||
actor_detail: this.rootStore.user.currentActor,
|
actor_detail: this.rootStore.user.currentActor,
|
||||||
},
|
},
|
||||||
@ -308,17 +284,17 @@ export class IssueDetailStore implements IIssueDetailStore {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.issueService.createIssueReaction(workspaceSlug, projectId, issueId, {
|
await this.issueService.createIssueReaction(anchor, issueID, {
|
||||||
reaction: reactionHex,
|
reaction: reactionHex,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Failed to add issue vote");
|
console.log("Failed to add issue vote");
|
||||||
const issueReactions = await this.issueService.getIssueReactions(workspaceSlug, projectId, issueId);
|
const issueReactions = await this.issueService.getIssueReactions(anchor, issueID);
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.details = {
|
this.details = {
|
||||||
...this.details,
|
...this.details,
|
||||||
[issueId]: {
|
[issueID]: {
|
||||||
...this.details[issueId],
|
...this.details[issueID],
|
||||||
reactions: issueReactions,
|
reactions: issueReactions,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -326,31 +302,31 @@ export class IssueDetailStore implements IIssueDetailStore {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
removeIssueReaction = async (workspaceSlug: string, projectId: string, issueId: string, reactionHex: string) => {
|
removeIssueReaction = async (anchor: string, issueID: string, reactionHex: string) => {
|
||||||
try {
|
try {
|
||||||
const newReactions = this.details[issueId].reactions.filter(
|
const newReactions = this.details[issueID].reactions.filter(
|
||||||
(_r) => !(_r.reaction === reactionHex && _r.actor_detail.id === this.rootStore.user.data?.id)
|
(_r) => !(_r.reaction === reactionHex && _r.actor_detail.id === this.rootStore.user.data?.id)
|
||||||
);
|
);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.details = {
|
this.details = {
|
||||||
...this.details,
|
...this.details,
|
||||||
[issueId]: {
|
[issueID]: {
|
||||||
...this.details[issueId],
|
...this.details[issueID],
|
||||||
reactions: newReactions,
|
reactions: newReactions,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.issueService.deleteIssueReaction(workspaceSlug, projectId, issueId, reactionHex);
|
await this.issueService.deleteIssueReaction(anchor, issueID, reactionHex);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Failed to remove issue reaction");
|
console.log("Failed to remove issue reaction");
|
||||||
const reactions = await this.issueService.getIssueReactions(workspaceSlug, projectId, issueId);
|
const reactions = await this.issueService.getIssueReactions(anchor, issueID);
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.details = {
|
this.details = {
|
||||||
...this.details,
|
...this.details,
|
||||||
[issueId]: {
|
[issueID]: {
|
||||||
...this.details[issueId],
|
...this.details[issueID],
|
||||||
reactions: reactions,
|
reactions: reactions,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -358,39 +334,44 @@ export class IssueDetailStore implements IIssueDetailStore {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
addIssueVote = async (workspaceSlug: string, projectId: string, issueId: string, data: { vote: 1 | -1 }) => {
|
addIssueVote = async (anchor: string, issueID: string, data: { vote: 1 | -1 }) => {
|
||||||
|
const publishSettings = this.rootStore.publishList?.publishMap?.[anchor];
|
||||||
|
const projectID = publishSettings?.project;
|
||||||
|
const workspaceSlug = publishSettings?.workspace_detail?.slug;
|
||||||
|
if (!projectID || !workspaceSlug) throw new Error("Publish settings not found");
|
||||||
|
|
||||||
const newVote: IVote = {
|
const newVote: IVote = {
|
||||||
actor: this.rootStore.user.data?.id ?? "",
|
actor: this.rootStore.user.data?.id ?? "",
|
||||||
actor_detail: this.rootStore.user.currentActor,
|
actor_detail: this.rootStore.user.currentActor,
|
||||||
issue: issueId,
|
issue: issueID,
|
||||||
project: projectId,
|
project: projectID,
|
||||||
workspace: workspaceSlug,
|
workspace: workspaceSlug,
|
||||||
vote: data.vote,
|
vote: data.vote,
|
||||||
};
|
};
|
||||||
|
|
||||||
const filteredVotes = this.details[issueId].votes.filter((v) => v.actor !== this.rootStore.user.data?.id);
|
const filteredVotes = this.details[issueID].votes.filter((v) => v.actor !== this.rootStore.user.data?.id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.details = {
|
this.details = {
|
||||||
...this.details,
|
...this.details,
|
||||||
[issueId]: {
|
[issueID]: {
|
||||||
...this.details[issueId],
|
...this.details[issueID],
|
||||||
votes: [...filteredVotes, newVote],
|
votes: [...filteredVotes, newVote],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.issueService.createIssueVote(workspaceSlug, projectId, issueId, data);
|
await this.issueService.createIssueVote(anchor, issueID, data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Failed to add issue vote");
|
console.log("Failed to add issue vote");
|
||||||
const issueVotes = await this.issueService.getIssueVotes(workspaceSlug, projectId, issueId);
|
const issueVotes = await this.issueService.getIssueVotes(anchor, issueID);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.details = {
|
this.details = {
|
||||||
...this.details,
|
...this.details,
|
||||||
[issueId]: {
|
[issueID]: {
|
||||||
...this.details[issueId],
|
...this.details[issueID],
|
||||||
votes: issueVotes,
|
votes: issueVotes,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -398,30 +379,30 @@ export class IssueDetailStore implements IIssueDetailStore {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
removeIssueVote = async (workspaceSlug: string, projectId: string, issueId: string) => {
|
removeIssueVote = async (anchor: string, issueID: string) => {
|
||||||
const newVotes = this.details[issueId].votes.filter((v) => v.actor !== this.rootStore.user.data?.id);
|
const newVotes = this.details[issueID].votes.filter((v) => v.actor !== this.rootStore.user.data?.id);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.details = {
|
this.details = {
|
||||||
...this.details,
|
...this.details,
|
||||||
[issueId]: {
|
[issueID]: {
|
||||||
...this.details[issueId],
|
...this.details[issueID],
|
||||||
votes: newVotes,
|
votes: newVotes,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.issueService.deleteIssueVote(workspaceSlug, projectId, issueId);
|
await this.issueService.deleteIssueVote(anchor, issueID);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Failed to remove issue vote");
|
console.log("Failed to remove issue vote");
|
||||||
const issueVotes = await this.issueService.getIssueVotes(workspaceSlug, projectId, issueId);
|
const issueVotes = await this.issueService.getIssueVotes(anchor, issueID);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.details = {
|
this.details = {
|
||||||
...this.details,
|
...this.details,
|
||||||
[issueId]: {
|
[issueID]: {
|
||||||
...this.details[issueId],
|
...this.details[issueID],
|
||||||
votes: issueVotes,
|
votes: issueVotes,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,87 +1,85 @@
|
|||||||
import { observable, action, makeObservable, runInAction } from "mobx";
|
import { observable, action, makeObservable, runInAction } from "mobx";
|
||||||
|
import { computedFn } from "mobx-utils";
|
||||||
// services
|
// services
|
||||||
import IssueService from "@/services/issue.service";
|
import IssueService from "@/services/issue.service";
|
||||||
// types
|
// types
|
||||||
import { IIssue, IIssueState, IIssueLabel } from "@/types/issue";
|
import { IIssue, IIssueState, IIssueLabel } from "@/types/issue";
|
||||||
// store
|
// store
|
||||||
import { RootStore } from "./root.store";
|
import { RootStore } from "./root.store";
|
||||||
// import { IssueDetailType, TIssueBoardKeys } from "types/issue";
|
|
||||||
|
|
||||||
export interface IIssueStore {
|
export interface IIssueStore {
|
||||||
loader: boolean;
|
loader: boolean;
|
||||||
error: any;
|
error: any;
|
||||||
// issue options
|
// observables
|
||||||
issues: IIssue[] | null;
|
issues: IIssue[];
|
||||||
states: IIssueState[] | null;
|
states: IIssueState[];
|
||||||
labels: IIssueLabel[] | null;
|
labels: IIssueLabel[];
|
||||||
// filtering
|
// filter observables
|
||||||
filteredStates: string[];
|
filteredStates: string[];
|
||||||
filteredLabels: string[];
|
filteredLabels: string[];
|
||||||
filteredPriorities: string[];
|
filteredPriorities: string[];
|
||||||
// service
|
|
||||||
issueService: any;
|
|
||||||
// actions
|
// actions
|
||||||
fetchPublicIssues: (workspace_slug: string, project_slug: string, params: any) => Promise<void>;
|
fetchPublicIssues: (anchor: string, params: any) => Promise<void>;
|
||||||
getCountOfIssuesByState: (state: string) => number;
|
// helpers
|
||||||
getFilteredIssuesByState: (state: string) => IIssue[];
|
getCountOfIssuesByState: (stateID: string) => number;
|
||||||
|
getFilteredIssuesByState: (stateID: string) => IIssue[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class IssueStore implements IIssueStore {
|
export class IssueStore implements IIssueStore {
|
||||||
loader: boolean = false;
|
loader: boolean = false;
|
||||||
error: any | null = null;
|
error: any | null = null;
|
||||||
|
// observables
|
||||||
states: IIssueState[] | null = [];
|
states: IIssueState[] = [];
|
||||||
labels: IIssueLabel[] | null = [];
|
labels: IIssueLabel[] = [];
|
||||||
|
issues: IIssue[] = [];
|
||||||
|
// filter observables
|
||||||
filteredStates: string[] = [];
|
filteredStates: string[] = [];
|
||||||
filteredLabels: string[] = [];
|
filteredLabels: string[] = [];
|
||||||
filteredPriorities: string[] = [];
|
filteredPriorities: string[] = [];
|
||||||
|
// root store
|
||||||
issues: IIssue[] | null = [];
|
|
||||||
issue_detail: any = {};
|
|
||||||
|
|
||||||
rootStore: RootStore;
|
rootStore: RootStore;
|
||||||
issueService: any;
|
// services
|
||||||
|
issueService: IssueService;
|
||||||
|
|
||||||
constructor(_rootStore: any) {
|
constructor(_rootStore: RootStore) {
|
||||||
makeObservable(this, {
|
makeObservable(this, {
|
||||||
// observable
|
loader: observable.ref,
|
||||||
loader: observable,
|
|
||||||
error: observable,
|
error: observable,
|
||||||
// issue options
|
// observables
|
||||||
states: observable.ref,
|
states: observable,
|
||||||
labels: observable.ref,
|
labels: observable,
|
||||||
// filtering
|
issues: observable,
|
||||||
filteredStates: observable.ref,
|
// filter observables
|
||||||
filteredLabels: observable.ref,
|
filteredStates: observable,
|
||||||
filteredPriorities: observable.ref,
|
filteredLabels: observable,
|
||||||
// issues
|
filteredPriorities: observable,
|
||||||
issues: observable.ref,
|
|
||||||
issue_detail: observable.ref,
|
|
||||||
// actions
|
// actions
|
||||||
fetchPublicIssues: action,
|
fetchPublicIssues: action,
|
||||||
getFilteredIssuesByState: action,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.rootStore = _rootStore;
|
this.rootStore = _rootStore;
|
||||||
this.issueService = new IssueService();
|
this.issueService = new IssueService();
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchPublicIssues = async (workspaceSlug: string, projectId: string, params: any) => {
|
/**
|
||||||
|
* @description fetch issues, states and labels
|
||||||
|
* @param {string} anchor
|
||||||
|
* @param params
|
||||||
|
*/
|
||||||
|
fetchPublicIssues = async (anchor: string, params: any) => {
|
||||||
try {
|
try {
|
||||||
this.loader = true;
|
runInAction(() => {
|
||||||
this.error = null;
|
this.loader = true;
|
||||||
|
this.error = null;
|
||||||
|
});
|
||||||
|
|
||||||
const response = await this.issueService.getPublicIssues(workspaceSlug, projectId, params);
|
const response = await this.issueService.fetchPublicIssues(anchor, params);
|
||||||
|
|
||||||
if (response) {
|
if (response) {
|
||||||
const states: IIssueState[] = [...response?.states];
|
|
||||||
const labels: IIssueLabel[] = [...response?.labels];
|
|
||||||
const issues: IIssue[] = [...response?.issues];
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.states = states;
|
this.states = response.states;
|
||||||
this.labels = labels;
|
this.labels = response.labels;
|
||||||
this.issues = issues;
|
this.issues = response.issues;
|
||||||
this.loader = false;
|
this.loader = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -91,11 +89,21 @@ export class IssueStore implements IIssueStore {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// computed
|
/**
|
||||||
getCountOfIssuesByState(state_id: string): number {
|
* @description get total count of issues under a particular state
|
||||||
return this.issues?.filter((issue) => issue.state == state_id).length || 0;
|
* @param {string} stateID
|
||||||
}
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
getCountOfIssuesByState = computedFn(
|
||||||
|
(stateID: string) => this.issues?.filter((issue) => issue.state == stateID).length || 0
|
||||||
|
);
|
||||||
|
|
||||||
getFilteredIssuesByState = (state_id: string): IIssue[] | [] =>
|
/**
|
||||||
this.issues?.filter((issue) => issue.state == state_id) || [];
|
* @description get array of issues under a particular state
|
||||||
|
* @param {string} stateID
|
||||||
|
* @returns {IIssue[]}
|
||||||
|
*/
|
||||||
|
getFilteredIssuesByState = computedFn(
|
||||||
|
(stateID: string) => this.issues?.filter((issue) => issue.state == stateID) || []
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ export interface IProjectStore {
|
|||||||
canComment: boolean;
|
canComment: boolean;
|
||||||
canVote: boolean;
|
canVote: boolean;
|
||||||
// actions
|
// actions
|
||||||
fetchProjectSettings: (workspace_slug: string, project_slug: string) => Promise<void>;
|
fetchProjectSettings: (workspaceSlug: string, project_slug: string) => Promise<void>;
|
||||||
hydrate: (projectSettings: any) => void;
|
hydrate: (projectSettings: any) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,12 +64,12 @@ export class ProjectStore implements IProjectStore {
|
|||||||
return this.settings?.votes ?? false;
|
return this.settings?.votes ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchProjectSettings = async (workspace_slug: string, project_slug: string) => {
|
fetchProjectSettings = async (workspaceSlug: string, project_slug: string) => {
|
||||||
try {
|
try {
|
||||||
this.loader = true;
|
this.loader = true;
|
||||||
this.error = null;
|
this.error = null;
|
||||||
|
|
||||||
const response = await this.projectService.getProjectSettings(workspace_slug, project_slug);
|
const response = await this.projectService.getProjectSettings(workspaceSlug, project_slug);
|
||||||
|
|
||||||
if (response) {
|
if (response) {
|
||||||
this.store.issueFilter.updateLayoutOptions(response?.views);
|
this.store.issueFilter.updateLayoutOptions(response?.views);
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import { observable, makeObservable } from "mobx";
|
import { observable, makeObservable, computed } from "mobx";
|
||||||
// store types
|
// store types
|
||||||
import { RootStore } from "@/store/root.store";
|
import { RootStore } from "@/store/root.store";
|
||||||
// types
|
// types
|
||||||
import { TProjectDetails, TViewDetails, TWorkspaceDetails } from "@/types/project";
|
import { TProjectDetails, TViewDetails, TWorkspaceDetails } from "@/types/project";
|
||||||
import { TPublishSettings } from "@/types/publish";
|
import { TPublishSettings } from "@/types/publish";
|
||||||
|
|
||||||
export interface IPublishStore extends TPublishSettings {}
|
export interface IPublishStore extends TPublishSettings {
|
||||||
|
// computed
|
||||||
|
workspaceSlug: string | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export class PublishStore implements IPublishStore {
|
export class PublishStore implements IPublishStore {
|
||||||
// observables
|
// observables
|
||||||
@ -62,6 +65,12 @@ export class PublishStore implements IPublishStore {
|
|||||||
votes: observable.ref,
|
votes: observable.ref,
|
||||||
workspace: observable.ref,
|
workspace: observable.ref,
|
||||||
workspace_detail: observable,
|
workspace_detail: observable,
|
||||||
|
// computed
|
||||||
|
workspaceSlug: computed,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get workspaceSlug() {
|
||||||
|
return this?.workspace_detail?.slug ?? undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
7
space/types/issue.d.ts
vendored
7
space/types/issue.d.ts
vendored
@ -43,6 +43,12 @@ export type TIssueQueryFilters = Partial<TFilters>;
|
|||||||
|
|
||||||
export type TIssueQueryFiltersParams = Partial<Record<keyof TFilters, string>>;
|
export type TIssueQueryFiltersParams = Partial<Record<keyof TFilters, string>>;
|
||||||
|
|
||||||
|
export type TIssuesResponse = {
|
||||||
|
states: IIssueState[];
|
||||||
|
labels: IIssueLabel[];
|
||||||
|
issues: IIssue[];
|
||||||
|
};
|
||||||
|
|
||||||
export interface IIssue {
|
export interface IIssue {
|
||||||
id: string;
|
id: string;
|
||||||
comments: Comment[];
|
comments: Comment[];
|
||||||
@ -79,6 +85,7 @@ export interface IIssueLabel {
|
|||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
color: string;
|
color: string;
|
||||||
|
parent: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IVote {
|
export interface IVote {
|
||||||
|
Loading…
Reference in New Issue
Block a user