mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge branch 'refactor/mobx-store' of github.com:makeplane/plane into refactor/mobx-store
This commit is contained in:
commit
794d33183b
@ -4,8 +4,8 @@ import { useTheme } from "next-themes";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Trash2 } from "lucide-react";
|
||||
import { mutate } from "swr";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// hooks
|
||||
@ -22,9 +22,7 @@ export const DeactivateAccountModal: React.FC<Props> = (props) => {
|
||||
// states
|
||||
const [isDeactivating, setIsDeactivating] = useState(false);
|
||||
|
||||
const {
|
||||
user: { deactivateAccount },
|
||||
} = useMobxStore();
|
||||
const { deactivateAccount } = useUser();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// services
|
||||
import { AuthService } from "services/auth.service";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import { GitHubSignInButton, GoogleSignInButton } from "components/account";
|
||||
@ -21,8 +20,8 @@ export const OAuthOptions: React.FC<Props> = observer((props) => {
|
||||
const { setToastAlert } = useToast();
|
||||
// mobx store
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
|
||||
const handleGoogleSignIn = async ({ clientId, credential }: any) => {
|
||||
try {
|
||||
|
@ -1,8 +1,7 @@
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useSignInRedirection from "hooks/use-sign-in-redirection";
|
||||
// components
|
||||
import { LatestFeatureBlock } from "components/common";
|
||||
@ -38,8 +37,8 @@ export const SignInRoot = observer(() => {
|
||||
const { handleRedirection } = useSignInRedirection();
|
||||
// mobx store
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
|
||||
const isOAuthEnabled = envConfig && (envConfig.google_client_id || envConfig.github_client_id);
|
||||
|
||||
|
@ -1,9 +1,7 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Control, Controller, UseFormSetValue } from "react-hook-form";
|
||||
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
// components
|
||||
import { SelectProject, SelectSegment, SelectXAxis, SelectYAxis } from "components/analytics";
|
||||
// types
|
||||
@ -20,12 +18,7 @@ type Props = {
|
||||
export const CustomAnalyticsSelectBar: React.FC<Props> = observer((props) => {
|
||||
const { control, setValue, params, fullScreen, isProjectLevel } = props;
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { project: projectStore } = useMobxStore();
|
||||
|
||||
const projectsList = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null;
|
||||
const { workspaceProjects } = useProject();
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -40,7 +33,11 @@ export const CustomAnalyticsSelectBar: React.FC<Props> = observer((props) => {
|
||||
name="project"
|
||||
control={control}
|
||||
render={({ field: { value, onChange } }) => (
|
||||
<SelectProject value={value ?? undefined} onChange={onChange} projects={projectsList ?? undefined} />
|
||||
<SelectProject
|
||||
value={value ?? undefined}
|
||||
onChange={onChange}
|
||||
projectIds={workspaceProjects ?? undefined}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,25 +1,33 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
// ui
|
||||
import { CustomSearchSelect } from "@plane/ui";
|
||||
// types
|
||||
import { IProject } from "types";
|
||||
|
||||
type Props = {
|
||||
value: string[] | undefined;
|
||||
onChange: (val: string[] | null) => void;
|
||||
projects: IProject[] | undefined;
|
||||
projectIds: string[] | undefined;
|
||||
};
|
||||
|
||||
export const SelectProject: React.FC<Props> = ({ value, onChange, projects }) => {
|
||||
const options = projects?.map((project) => ({
|
||||
value: project.id,
|
||||
query: project.name + project.identifier,
|
||||
content: (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[0.65rem] text-custom-text-200">{project.identifier}</span>
|
||||
{project.name}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
export const SelectProject: React.FC<Props> = observer((props) => {
|
||||
const { value, onChange, projectIds } = props;
|
||||
const { getProjectById } = useProject();
|
||||
|
||||
const options = projectIds?.map((projectId) => {
|
||||
const projectDetails = getProjectById(projectId);
|
||||
|
||||
return {
|
||||
value: projectDetails?.id,
|
||||
query: `${projectDetails?.name} ${projectDetails?.identifier}`,
|
||||
content: (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[0.65rem] text-custom-text-200">{projectDetails?.identifier}</span>
|
||||
{projectDetails?.name}
|
||||
</div>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<CustomSearchSelect
|
||||
@ -28,9 +36,9 @@ export const SelectProject: React.FC<Props> = ({ value, onChange, projects }) =>
|
||||
options={options}
|
||||
label={
|
||||
value && value.length > 0
|
||||
? projects
|
||||
?.filter((p) => value.includes(p.id))
|
||||
.map((p) => p.identifier)
|
||||
? projectIds
|
||||
?.filter((p) => value.includes(p))
|
||||
.map((p) => getProjectById(p)?.name)
|
||||
.join(", ")
|
||||
: "All projects"
|
||||
}
|
||||
@ -38,4 +46,4 @@ export const SelectProject: React.FC<Props> = ({ value, onChange, projects }) =>
|
||||
multiple
|
||||
/>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -1,65 +1,74 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
// icons
|
||||
import { Contrast, LayoutGrid, Users } from "lucide-react";
|
||||
// helpers
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
// types
|
||||
import { IProject } from "types";
|
||||
|
||||
type Props = {
|
||||
projects: IProject[];
|
||||
projectIds: string[];
|
||||
};
|
||||
|
||||
export const CustomAnalyticsSidebarProjectsList: React.FC<Props> = (props) => {
|
||||
const { projects } = props;
|
||||
export const CustomAnalyticsSidebarProjectsList: React.FC<Props> = observer((props) => {
|
||||
const { projectIds } = props;
|
||||
|
||||
const { getProjectById } = useProject();
|
||||
|
||||
return (
|
||||
<div className="hidden h-full overflow-hidden md:flex md:flex-col">
|
||||
<h4 className="font-medium">Selected Projects</h4>
|
||||
<div className="mt-4 h-full space-y-6 overflow-y-auto">
|
||||
{projects.map((project) => (
|
||||
<div key={project.id} className="w-full">
|
||||
<div className="flex items-center gap-1 text-sm">
|
||||
{project.emoji ? (
|
||||
<span className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(project.emoji)}</span>
|
||||
) : project.icon_prop ? (
|
||||
<div className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(project.icon_prop)}</div>
|
||||
) : (
|
||||
<span className="mr-1 grid h-6 w-6 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
||||
{project?.name.charAt(0)}
|
||||
</span>
|
||||
)}
|
||||
<h5 className="flex items-center gap-1">
|
||||
<p className="break-words">{truncateText(project.name, 20)}</p>
|
||||
<span className="ml-1 text-xs text-custom-text-200">({project.identifier})</span>
|
||||
</h5>
|
||||
</div>
|
||||
<div className="mt-4 w-full space-y-3 pl-2">
|
||||
<div className="flex items-center justify-between gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="text-custom-text-200" size={14} strokeWidth={2} />
|
||||
<h6>Total members</h6>
|
||||
</div>
|
||||
<span className="text-custom-text-200">{project.total_members}</span>
|
||||
{projectIds.map((projectId) => {
|
||||
const project = getProjectById(projectId);
|
||||
|
||||
if (!project) return;
|
||||
|
||||
return (
|
||||
<div key={projectId} className="w-full">
|
||||
<div className="flex items-center gap-1 text-sm">
|
||||
{project.emoji ? (
|
||||
<span className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(project.emoji)}</span>
|
||||
) : project.icon_prop ? (
|
||||
<div className="grid h-6 w-6 flex-shrink-0 place-items-center">{renderEmoji(project.icon_prop)}</div>
|
||||
) : (
|
||||
<span className="mr-1 grid h-6 w-6 flex-shrink-0 place-items-center rounded bg-gray-700 uppercase text-white">
|
||||
{project?.name.charAt(0)}
|
||||
</span>
|
||||
)}
|
||||
<h5 className="flex items-center gap-1">
|
||||
<p className="break-words">{truncateText(project.name, 20)}</p>
|
||||
<span className="ml-1 text-xs text-custom-text-200">({project.identifier})</span>
|
||||
</h5>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<Contrast className="text-custom-text-200" size={14} strokeWidth={2} />
|
||||
<h6>Total cycles</h6>
|
||||
<div className="mt-4 w-full space-y-3 pl-2">
|
||||
<div className="flex items-center justify-between gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<Users className="text-custom-text-200" size={14} strokeWidth={2} />
|
||||
<h6>Total members</h6>
|
||||
</div>
|
||||
<span className="text-custom-text-200">{project.total_members}</span>
|
||||
</div>
|
||||
<span className="text-custom-text-200">{project.total_cycles}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<LayoutGrid className="text-custom-text-200" size={14} strokeWidth={2} />
|
||||
<h6>Total modules</h6>
|
||||
<div className="flex items-center justify-between gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<Contrast className="text-custom-text-200" size={14} strokeWidth={2} />
|
||||
<h6>Total cycles</h6>
|
||||
</div>
|
||||
<span className="text-custom-text-200">{project.total_cycles}</span>
|
||||
</div>
|
||||
<div className="flex items-center justify-between gap-2 text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<LayoutGrid className="text-custom-text-200" size={14} strokeWidth={2} />
|
||||
<h6>Total modules</h6>
|
||||
</div>
|
||||
<span className="text-custom-text-200">{project.total_modules}</span>
|
||||
</div>
|
||||
<span className="text-custom-text-200">{project.total_modules}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useCycle, useModule, useProject } from "hooks/store";
|
||||
// helpers
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { renderShortDate } from "helpers/date-time.helper";
|
||||
@ -11,16 +10,15 @@ import { NETWORK_CHOICES } from "constants/project";
|
||||
|
||||
export const CustomAnalyticsSidebarHeader = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||
const { projectId, cycleId, moduleId } = router.query;
|
||||
|
||||
const { cycle: cycleStore, module: moduleStore, project: projectStore } = useMobxStore();
|
||||
const { getProjectById } = useProject();
|
||||
const { getCycleById } = useCycle();
|
||||
const { getModuleById } = useModule();
|
||||
|
||||
const cycleDetails = cycleId ? cycleStore.getCycleById(cycleId.toString()) : undefined;
|
||||
const moduleDetails = moduleId ? moduleStore.getModuleById(moduleId.toString()) : undefined;
|
||||
const projectDetails =
|
||||
workspaceSlug && projectId
|
||||
? projectStore.getProjectById(workspaceSlug.toString(), projectId.toString())
|
||||
: undefined;
|
||||
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
|
||||
const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : undefined;
|
||||
const projectDetails = projectId ? getProjectById(projectId.toString()) : undefined;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -5,8 +5,8 @@ import { mutate } from "swr";
|
||||
// services
|
||||
import { AnalyticsService } from "services/analytics.service";
|
||||
// hooks
|
||||
import { useCycle, useModule, useProject, useUser } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { CustomAnalyticsSidebarHeader, CustomAnalyticsSidebarProjectsList } from "components/analytics";
|
||||
// ui
|
||||
@ -29,172 +29,167 @@ type Props = {
|
||||
|
||||
const analyticsService = new AnalyticsService();
|
||||
|
||||
export const CustomAnalyticsSidebar: React.FC<Props> = observer(
|
||||
({ analytics, params, fullScreen, isProjectLevel = false }) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||
export const CustomAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||
const { analytics, params, fullScreen, isProjectLevel = false } = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, cycleId, moduleId } = router.query;
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
// store hooks
|
||||
const { currentUser } = useUser();
|
||||
const { workspaceProjects, getProjectById } = useProject();
|
||||
const { fetchCycleDetails, getCycleById } = useCycle();
|
||||
const { fetchModuleDetails, getModuleById } = useModule();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
const projectDetails = projectId ? getProjectById(projectId.toString()) ?? undefined : undefined;
|
||||
|
||||
const { user: userStore, project: projectStore, cycle: cycleStore, module: moduleStore } = useMobxStore();
|
||||
const trackExportAnalytics = () => {
|
||||
if (!currentUser) return;
|
||||
|
||||
const user = userStore.currentUser;
|
||||
|
||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
||||
const projectDetails =
|
||||
workspaceSlug && projectId
|
||||
? projectStore.getProjectById(workspaceSlug.toString(), projectId.toString()) ?? undefined
|
||||
: undefined;
|
||||
|
||||
const trackExportAnalytics = () => {
|
||||
if (!user) return;
|
||||
|
||||
const eventPayload: any = {
|
||||
workspaceSlug: workspaceSlug?.toString(),
|
||||
params: {
|
||||
x_axis: params.x_axis,
|
||||
y_axis: params.y_axis,
|
||||
group: params.segment,
|
||||
project: params.project,
|
||||
},
|
||||
};
|
||||
|
||||
if (projectDetails) {
|
||||
const workspaceDetails = projectDetails.workspace as IWorkspace;
|
||||
|
||||
eventPayload.workspaceId = workspaceDetails.id;
|
||||
eventPayload.workspaceName = workspaceDetails.name;
|
||||
eventPayload.projectId = projectDetails.id;
|
||||
eventPayload.projectIdentifier = projectDetails.identifier;
|
||||
eventPayload.projectName = projectDetails.name;
|
||||
}
|
||||
|
||||
if (cycleDetails || moduleDetails) {
|
||||
const details = cycleDetails || moduleDetails;
|
||||
|
||||
eventPayload.workspaceId = details?.workspace_detail?.id;
|
||||
eventPayload.workspaceName = details?.workspace_detail?.name;
|
||||
eventPayload.projectId = details?.project_detail.id;
|
||||
eventPayload.projectIdentifier = details?.project_detail.identifier;
|
||||
eventPayload.projectName = details?.project_detail.name;
|
||||
}
|
||||
|
||||
if (cycleDetails) {
|
||||
eventPayload.cycleId = cycleDetails.id;
|
||||
eventPayload.cycleName = cycleDetails.name;
|
||||
}
|
||||
|
||||
if (moduleDetails) {
|
||||
eventPayload.moduleId = moduleDetails.id;
|
||||
eventPayload.moduleName = moduleDetails.name;
|
||||
}
|
||||
};
|
||||
|
||||
const exportAnalytics = () => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
const data: IExportAnalyticsFormData = {
|
||||
const eventPayload: any = {
|
||||
workspaceSlug: workspaceSlug?.toString(),
|
||||
params: {
|
||||
x_axis: params.x_axis,
|
||||
y_axis: params.y_axis,
|
||||
};
|
||||
|
||||
if (params.segment) data.segment = params.segment;
|
||||
if (params.project) data.project = params.project;
|
||||
|
||||
analyticsService
|
||||
.exportAnalytics(workspaceSlug.toString(), data)
|
||||
.then((res) => {
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: res.message,
|
||||
});
|
||||
|
||||
trackExportAnalytics();
|
||||
})
|
||||
.catch(() =>
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "There was some error in exporting the analytics. Please try again.",
|
||||
})
|
||||
);
|
||||
group: params.segment,
|
||||
project: params.project,
|
||||
},
|
||||
};
|
||||
|
||||
const cycleDetails = cycleId ? cycleStore.getCycleById(cycleId.toString()) : undefined;
|
||||
const moduleDetails = moduleId ? moduleStore.getModuleById(moduleId.toString()) : undefined;
|
||||
if (projectDetails) {
|
||||
const workspaceDetails = projectDetails.workspace as IWorkspace;
|
||||
|
||||
// fetch cycle details
|
||||
useEffect(() => {
|
||||
if (!workspaceSlug || !projectId || !cycleId || cycleDetails) return;
|
||||
eventPayload.workspaceId = workspaceDetails.id;
|
||||
eventPayload.workspaceName = workspaceDetails.name;
|
||||
eventPayload.projectId = projectDetails.id;
|
||||
eventPayload.projectIdentifier = projectDetails.identifier;
|
||||
eventPayload.projectName = projectDetails.name;
|
||||
}
|
||||
|
||||
cycleStore.fetchCycleWithId(workspaceSlug.toString(), projectId.toString(), cycleId.toString());
|
||||
}, [cycleId, cycleDetails, cycleStore, projectId, workspaceSlug]);
|
||||
if (cycleDetails || moduleDetails) {
|
||||
const details = cycleDetails || moduleDetails;
|
||||
|
||||
// fetch module details
|
||||
useEffect(() => {
|
||||
if (!workspaceSlug || !projectId || !moduleId || moduleDetails) return;
|
||||
eventPayload.workspaceId = details?.workspace_detail?.id;
|
||||
eventPayload.workspaceName = details?.workspace_detail?.name;
|
||||
eventPayload.projectId = details?.project_detail.id;
|
||||
eventPayload.projectIdentifier = details?.project_detail.identifier;
|
||||
eventPayload.projectName = details?.project_detail.name;
|
||||
}
|
||||
|
||||
moduleStore.fetchModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString());
|
||||
}, [moduleId, moduleDetails, moduleStore, projectId, workspaceSlug]);
|
||||
if (cycleDetails) {
|
||||
eventPayload.cycleId = cycleDetails.id;
|
||||
eventPayload.cycleName = cycleDetails.name;
|
||||
}
|
||||
|
||||
const selectedProjects = params.project && params.project.length > 0 ? params.project : projects?.map((p) => p.id);
|
||||
if (moduleDetails) {
|
||||
eventPayload.moduleId = moduleDetails.id;
|
||||
eventPayload.moduleName = moduleDetails.name;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center justify-between space-y-2 px-5 py-2.5 ${
|
||||
fullScreen
|
||||
? "overflow-hidden border-l border-custom-border-200 md:h-full md:flex-col md:items-start md:space-y-4 md:border-l md:border-custom-border-200 md:py-5"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
const exportAnalytics = () => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
const data: IExportAnalyticsFormData = {
|
||||
x_axis: params.x_axis,
|
||||
y_axis: params.y_axis,
|
||||
};
|
||||
|
||||
if (params.segment) data.segment = params.segment;
|
||||
if (params.project) data.project = params.project;
|
||||
|
||||
analyticsService
|
||||
.exportAnalytics(workspaceSlug.toString(), data)
|
||||
.then((res) => {
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
message: res.message,
|
||||
});
|
||||
|
||||
trackExportAnalytics();
|
||||
})
|
||||
.catch(() =>
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: "There was some error in exporting the analytics. Please try again.",
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const cycleDetails = cycleId ? getCycleById(cycleId.toString()) : undefined;
|
||||
const moduleDetails = moduleId ? getModuleById(moduleId.toString()) : undefined;
|
||||
|
||||
// fetch cycle details
|
||||
useEffect(() => {
|
||||
if (!workspaceSlug || !projectId || !cycleId || cycleDetails) return;
|
||||
|
||||
fetchCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleId.toString());
|
||||
}, [cycleId, cycleDetails, fetchCycleDetails, projectId, workspaceSlug]);
|
||||
|
||||
// fetch module details
|
||||
useEffect(() => {
|
||||
if (!workspaceSlug || !projectId || !moduleId || moduleDetails) return;
|
||||
|
||||
fetchModuleDetails(workspaceSlug.toString(), projectId.toString(), moduleId.toString());
|
||||
}, [moduleId, moduleDetails, fetchModuleDetails, projectId, workspaceSlug]);
|
||||
|
||||
const selectedProjects = params.project && params.project.length > 0 ? params.project : workspaceProjects;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center justify-between space-y-2 px-5 py-2.5 ${
|
||||
fullScreen
|
||||
? "overflow-hidden border-l border-custom-border-200 md:h-full md:flex-col md:items-start md:space-y-4 md:border-l md:border-custom-border-200 md:py-5"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<div className="flex items-center gap-1 rounded-md bg-custom-background-80 px-3 py-1 text-xs text-custom-text-200">
|
||||
<LayersIcon height={14} width={14} />
|
||||
{analytics ? analytics.total : "..."} Issues
|
||||
</div>
|
||||
{isProjectLevel && (
|
||||
<div className="flex items-center gap-1 rounded-md bg-custom-background-80 px-3 py-1 text-xs text-custom-text-200">
|
||||
<LayersIcon height={14} width={14} />
|
||||
{analytics ? analytics.total : "..."} Issues
|
||||
<CalendarDays className="h-3.5 w-3.5" />
|
||||
{renderShortDate(
|
||||
(cycleId
|
||||
? cycleDetails?.created_at
|
||||
: moduleId
|
||||
? moduleDetails?.created_at
|
||||
: projectDetails?.created_at) ?? ""
|
||||
)}
|
||||
</div>
|
||||
{isProjectLevel && (
|
||||
<div className="flex items-center gap-1 rounded-md bg-custom-background-80 px-3 py-1 text-xs text-custom-text-200">
|
||||
<CalendarDays className="h-3.5 w-3.5" />
|
||||
{renderShortDate(
|
||||
(cycleId
|
||||
? cycleDetails?.created_at
|
||||
: moduleId
|
||||
? moduleDetails?.created_at
|
||||
: projectDetails?.created_at) ?? ""
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="h-full w-full overflow-hidden">
|
||||
{fullScreen ? (
|
||||
<>
|
||||
{!isProjectLevel && selectedProjects && selectedProjects.length > 0 && (
|
||||
<CustomAnalyticsSidebarProjectsList
|
||||
projects={projects?.filter((p) => selectedProjects.includes(p.id)) ?? []}
|
||||
/>
|
||||
)}
|
||||
<CustomAnalyticsSidebarHeader />
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2 justify-self-end">
|
||||
<Button
|
||||
variant="neutral-primary"
|
||||
prependIcon={<RefreshCw className="h-3.5 w-3.5" />}
|
||||
onClick={() => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
mutate(ANALYTICS(workspaceSlug.toString(), params));
|
||||
}}
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
<Button variant="primary" prependIcon={<Download className="h-3.5 w-3.5" />} onClick={exportAnalytics}>
|
||||
Export as CSV
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
<div className="h-full w-full overflow-hidden">
|
||||
{fullScreen ? (
|
||||
<>
|
||||
{!isProjectLevel && selectedProjects && selectedProjects.length > 0 && (
|
||||
<CustomAnalyticsSidebarProjectsList projectIds={selectedProjects} />
|
||||
)}
|
||||
<CustomAnalyticsSidebarHeader />
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2 justify-self-end">
|
||||
<Button
|
||||
variant="neutral-primary"
|
||||
prependIcon={<RefreshCw className="h-3.5 w-3.5" />}
|
||||
onClick={() => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
mutate(ANALYTICS(workspaceSlug.toString(), params));
|
||||
}}
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
<Button variant="primary" prependIcon={<Download className="h-3.5 w-3.5" />} onClick={exportAnalytics}>
|
||||
Export as CSV
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -1,12 +1,12 @@
|
||||
import React from "react";
|
||||
// next
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
// layouts
|
||||
import DefaultLayout from "layouts/default-layout";
|
||||
// hooks
|
||||
import useUser from "hooks/use-user";
|
||||
// images
|
||||
import ProjectNotAuthorizedImg from "public/auth/project-not-authorized.svg";
|
||||
import WorkspaceNotAuthorizedImg from "public/auth/workspace-not-authorized.svg";
|
||||
@ -16,9 +16,12 @@ type Props = {
|
||||
type: "project" | "workspace";
|
||||
};
|
||||
|
||||
export const NotAuthorizedView: React.FC<Props> = ({ actionButton, type }) => {
|
||||
const { user } = useUser();
|
||||
export const NotAuthorizedView: React.FC<Props> = observer((props) => {
|
||||
const { actionButton, type } = props;
|
||||
// router
|
||||
const { asPath: currentPath } = useRouter();
|
||||
// store hooks
|
||||
const { currentUser } = useUser();
|
||||
|
||||
return (
|
||||
<DefaultLayout>
|
||||
@ -34,9 +37,9 @@ export const NotAuthorizedView: React.FC<Props> = ({ actionButton, type }) => {
|
||||
<h1 className="text-xl font-medium text-custom-text-100">Oops! You are not authorized to view this page</h1>
|
||||
|
||||
<div className="w-full max-w-md text-base text-custom-text-200">
|
||||
{user ? (
|
||||
{currentUser ? (
|
||||
<p>
|
||||
You have signed in as {user.email}. <br />
|
||||
You have signed in as {currentUser.email}. <br />
|
||||
<Link href={`/?next=${currentPath}`}>
|
||||
<span className="font-medium text-custom-text-100">Sign in</span>
|
||||
</Link>{" "}
|
||||
@ -57,4 +60,4 @@ export const NotAuthorizedView: React.FC<Props> = ({ actionButton, type }) => {
|
||||
</div>
|
||||
</DefaultLayout>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { RootStore } from "store_legacy/root";
|
||||
// hooks
|
||||
import { useProject, useUser } from "hooks/store";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// icons
|
||||
@ -12,12 +11,13 @@ import { ClipboardList } from "lucide-react";
|
||||
import JoinProjectImg from "public/auth/project-not-authorized.svg";
|
||||
|
||||
export const JoinProject: React.FC = () => {
|
||||
// states
|
||||
const [isJoiningProject, setIsJoiningProject] = useState(false);
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
project: projectStore,
|
||||
user: { joinProject },
|
||||
}: RootStore = useMobxStore();
|
||||
membership: { joinProject },
|
||||
} = useUser();
|
||||
const { fetchProjects } = useProject();
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
@ -28,12 +28,8 @@ export const JoinProject: React.FC = () => {
|
||||
setIsJoiningProject(true);
|
||||
|
||||
joinProject(workspaceSlug.toString(), [projectId.toString()])
|
||||
.then(() => {
|
||||
projectStore.fetchProjects(workspaceSlug.toString());
|
||||
})
|
||||
.finally(() => {
|
||||
setIsJoiningProject(false);
|
||||
});
|
||||
.then(() => fetchProjects(workspaceSlug.toString()))
|
||||
.finally(() => setIsJoiningProject(false));
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useProject, useUser } from "hooks/store";
|
||||
// component
|
||||
import { CustomSelect, Loader, ToggleSwitch } from "@plane/ui";
|
||||
import { SelectMonthModal } from "components/automation";
|
||||
@ -23,13 +23,13 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
|
||||
const { handleChange } = props;
|
||||
// states
|
||||
const [monthModal, setmonthModal] = useState(false);
|
||||
// store hooks
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
const { user: userStore, project: projectStore } = useMobxStore();
|
||||
|
||||
const projectDetails = projectStore.currentProjectDetails;
|
||||
const userRole = userStore.currentProjectRole;
|
||||
|
||||
const isAdmin = userRole === EUserWorkspaceRoles.ADMIN;
|
||||
const isAdmin = currentProjectRole === EUserWorkspaceRoles.ADMIN;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -54,24 +54,28 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
|
||||
</div>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
value={projectDetails?.archive_in !== 0}
|
||||
value={currentProjectDetails?.archive_in !== 0}
|
||||
onChange={() =>
|
||||
projectDetails?.archive_in === 0 ? handleChange({ archive_in: 1 }) : handleChange({ archive_in: 0 })
|
||||
currentProjectDetails?.archive_in === 0
|
||||
? handleChange({ archive_in: 1 })
|
||||
: handleChange({ archive_in: 0 })
|
||||
}
|
||||
size="sm"
|
||||
disabled={!isAdmin}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{projectDetails ? (
|
||||
projectDetails.archive_in !== 0 && (
|
||||
{currentProjectDetails ? (
|
||||
currentProjectDetails.archive_in !== 0 && (
|
||||
<div className="ml-12">
|
||||
<div className="flex w-full items-center justify-between gap-2 rounded border border-custom-border-200 bg-custom-background-90 px-5 py-4">
|
||||
<div className="w-1/2 text-sm font-medium">Auto-archive issues that are closed for</div>
|
||||
<div className="w-1/2">
|
||||
<CustomSelect
|
||||
value={projectDetails?.archive_in}
|
||||
label={`${projectDetails?.archive_in} ${projectDetails?.archive_in === 1 ? "Month" : "Months"}`}
|
||||
value={currentProjectDetails?.archive_in}
|
||||
label={`${currentProjectDetails?.archive_in} ${
|
||||
currentProjectDetails?.archive_in === 1 ? "Month" : "Months"
|
||||
}`}
|
||||
onChange={(val: number) => {
|
||||
handleChange({ archive_in: val });
|
||||
}}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useProject, useProjectState, useUser } from "hooks/store";
|
||||
// component
|
||||
import { SelectMonthModal } from "components/automation";
|
||||
import { CustomSelect, CustomSearchSelect, ToggleSwitch, StateGroupIcon, DoubleCircleIcon, Loader } from "@plane/ui";
|
||||
@ -21,15 +21,16 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
||||
const { handleChange } = props;
|
||||
// states
|
||||
const [monthModal, setmonthModal] = useState(false);
|
||||
// store hooks
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { currentProjectDetails } = useProject();
|
||||
const { projectStates } = useProjectState();
|
||||
|
||||
const { user: userStore, project: projectStore, projectState: projectStateStore } = useMobxStore();
|
||||
|
||||
const userRole = userStore.currentProjectRole;
|
||||
const projectDetails = projectStore.currentProjectDetails;
|
||||
// const stateGroups = projectStateStore.groupedProjectStates ?? undefined;
|
||||
const states = projectStateStore.projectStates;
|
||||
|
||||
const options = states
|
||||
const options = projectStates
|
||||
?.filter((state) => state.group === "cancelled")
|
||||
.map((state) => ({
|
||||
value: state.id,
|
||||
@ -44,17 +45,17 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
||||
|
||||
const multipleOptions = (options ?? []).length > 1;
|
||||
|
||||
const defaultState = states?.find((s) => s.group === "cancelled")?.id || null;
|
||||
const defaultState = projectStates?.find((s) => s.group === "cancelled")?.id || null;
|
||||
|
||||
const selectedOption = states?.find((s) => s.id === projectDetails?.default_state ?? defaultState);
|
||||
const currentDefaultState = states?.find((s) => s.id === defaultState);
|
||||
const selectedOption = projectStates?.find((s) => s.id === currentProjectDetails?.default_state ?? defaultState);
|
||||
const currentDefaultState = projectStates?.find((s) => s.id === defaultState);
|
||||
|
||||
const initialValues: Partial<IProject> = {
|
||||
close_in: 1,
|
||||
default_state: defaultState,
|
||||
};
|
||||
|
||||
const isAdmin = userRole === EUserWorkspaceRoles.ADMIN;
|
||||
const isAdmin = currentProjectRole === EUserWorkspaceRoles.ADMIN;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -79,9 +80,9 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
||||
</div>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
value={projectDetails?.close_in !== 0}
|
||||
value={currentProjectDetails?.close_in !== 0}
|
||||
onChange={() =>
|
||||
projectDetails?.close_in === 0
|
||||
currentProjectDetails?.close_in === 0
|
||||
? handleChange({ close_in: 1, default_state: defaultState })
|
||||
: handleChange({ close_in: 0, default_state: null })
|
||||
}
|
||||
@ -90,16 +91,18 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{projectDetails ? (
|
||||
projectDetails.close_in !== 0 && (
|
||||
{currentProjectDetails ? (
|
||||
currentProjectDetails.close_in !== 0 && (
|
||||
<div className="ml-12">
|
||||
<div className="flex flex-col rounded border border-custom-border-200 bg-custom-background-90">
|
||||
<div className="flex w-full items-center justify-between gap-2 px-5 py-4">
|
||||
<div className="w-1/2 text-sm font-medium">Auto-close issues that are inactive for</div>
|
||||
<div className="w-1/2">
|
||||
<CustomSelect
|
||||
value={projectDetails?.close_in}
|
||||
label={`${projectDetails?.close_in} ${projectDetails?.close_in === 1 ? "Month" : "Months"}`}
|
||||
value={currentProjectDetails?.close_in}
|
||||
label={`${currentProjectDetails?.close_in} ${
|
||||
currentProjectDetails?.close_in === 1 ? "Month" : "Months"
|
||||
}`}
|
||||
onChange={(val: number) => {
|
||||
handleChange({ close_in: val });
|
||||
}}
|
||||
@ -118,7 +121,7 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
||||
className="flex w-full select-none items-center rounded px-1 py-1.5 text-custom-text-200 hover:bg-custom-background-80"
|
||||
onClick={() => setmonthModal(true)}
|
||||
>
|
||||
Customise Time Range
|
||||
Customize Time Range
|
||||
</button>
|
||||
</>
|
||||
</CustomSelect>
|
||||
@ -129,7 +132,7 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
||||
<div className="w-1/2 text-sm font-medium">Auto-close Status</div>
|
||||
<div className="w-1/2 ">
|
||||
<CustomSearchSelect
|
||||
value={projectDetails?.default_state ?? defaultState}
|
||||
value={currentProjectDetails?.default_state ?? defaultState}
|
||||
label={
|
||||
<div className="flex items-center gap-2">
|
||||
{selectedOption ? (
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Command } from "cmdk";
|
||||
import { FileText, GithubIcon, MessageSquare, Rocket } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// ui
|
||||
import { DiscordIcon } from "@plane/ui";
|
||||
|
||||
@ -14,7 +14,7 @@ export const CommandPaletteHelpActions: React.FC<Props> = (props) => {
|
||||
|
||||
const {
|
||||
commandPalette: { toggleShortcutModal },
|
||||
} = useMobxStore();
|
||||
} = useApplication();
|
||||
|
||||
return (
|
||||
<Command.Group heading="Help">
|
||||
|
@ -5,6 +5,8 @@ import { LinkIcon, Signal, Trash2, UserMinus2, UserPlus2 } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication, useUser } from "hooks/store";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { DoubleCircleIcon, UserGroupIcon } from "@plane/ui";
|
||||
@ -29,10 +31,12 @@ export const CommandPaletteIssueActions: React.FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
const {
|
||||
commandPalette: { toggleCommandPaletteModal, toggleDeleteIssueModal },
|
||||
projectIssues: { updateIssue },
|
||||
user: { currentUser },
|
||||
} = useMobxStore();
|
||||
const {
|
||||
commandPalette: { toggleCommandPaletteModal, toggleDeleteIssueModal },
|
||||
} = useApplication();
|
||||
const { currentUser } = useUser();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Command } from "cmdk";
|
||||
import { ContrastIcon, FileText } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// ui
|
||||
import { DiceIcon, PhotoFilterIcon } from "@plane/ui";
|
||||
|
||||
@ -14,8 +14,8 @@ export const CommandPaletteProjectActions: React.FC<Props> = (props) => {
|
||||
|
||||
const {
|
||||
commandPalette: { toggleCreateCycleModal, toggleCreateModuleModal, toggleCreatePageModal, toggleCreateViewModal },
|
||||
trackEvent: { setTrackElement },
|
||||
} = useMobxStore();
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -4,8 +4,8 @@ import { useTheme } from "next-themes";
|
||||
import { Settings } from "lucide-react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// constants
|
||||
import { THEME_OPTIONS } from "constants/themes";
|
||||
|
||||
@ -18,9 +18,7 @@ export const CommandPaletteThemeActions: FC<Props> = observer((props) => {
|
||||
// states
|
||||
const [mounted, setMounted] = useState(false);
|
||||
// store
|
||||
const {
|
||||
user: { updateCurrentUserTheme },
|
||||
} = useMobxStore();
|
||||
const { updateCurrentUserTheme } = useUser();
|
||||
// hooks
|
||||
const { setTheme } = useTheme();
|
||||
const { setToastAlert } = useToast();
|
||||
|
@ -5,8 +5,8 @@ import { Command } from "cmdk";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { FolderPlus, Search, Settings } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// services
|
||||
import { WorkspaceService } from "services/workspace.service";
|
||||
import { IssueService } from "services/issue";
|
||||
@ -62,8 +62,8 @@ export const CommandModal: React.FC = observer(() => {
|
||||
toggleCreateIssueModal,
|
||||
toggleCreateProjectModal,
|
||||
},
|
||||
trackEvent: { setTrackElement },
|
||||
} = useMobxStore();
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
|
@ -3,6 +3,7 @@ import { useRouter } from "next/router";
|
||||
import useSWR from "swr";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useApplication, useUser } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import { CommandModal, ShortcutsModal } from "components/command-palette";
|
||||
@ -19,8 +20,6 @@ import { copyTextToClipboard } from "helpers/string.helper";
|
||||
import { IssueService } from "services/issue";
|
||||
// fetch keys
|
||||
import { ISSUE_DETAILS } from "constants/fetch-keys";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
// services
|
||||
const issueService = new IssueService();
|
||||
@ -28,14 +27,14 @@ const issueService = new IssueService();
|
||||
export const CommandPalette: FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, issueId, cycleId, moduleId } = router.query;
|
||||
// store
|
||||
|
||||
const {
|
||||
commandPalette,
|
||||
theme: { toggleSidebar },
|
||||
user: { currentUser },
|
||||
trackEvent: { setTrackElement },
|
||||
projectIssues: { removeIssue },
|
||||
} = useMobxStore();
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
const { currentUser } = useUser();
|
||||
|
||||
const {
|
||||
toggleCommandPaletteModal,
|
||||
isCreateIssueModalOpen,
|
@ -1,5 +1,5 @@
|
||||
export * from "./actions";
|
||||
export * from "./shortcuts-modal";
|
||||
export * from "./command-modal";
|
||||
export * from "./command-pallette";
|
||||
export * from "./command-palette";
|
||||
export * from "./helpers";
|
||||
|
@ -6,8 +6,8 @@ import useSWR from "swr";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import { Tab, Transition, Popover } from "@headlessui/react";
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication, useWorkspace } from "hooks/store";
|
||||
// services
|
||||
import { FileService } from "services/file.service";
|
||||
// hooks
|
||||
@ -45,25 +45,24 @@ const fileService = new FileService();
|
||||
|
||||
export const ImagePickerPopover: React.FC<Props> = observer((props) => {
|
||||
const { label, value, control, onChange, disabled = false } = props;
|
||||
|
||||
// states
|
||||
const [image, setImage] = useState<File | null>(null);
|
||||
const [isImageUploading, setIsImageUploading] = useState(false);
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [searchParams, setSearchParams] = useState("");
|
||||
const [formData, setFormData] = useState({
|
||||
search: "",
|
||||
});
|
||||
|
||||
// refs
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
workspace: { currentWorkspace },
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const { data: unsplashImages, error: unsplashError } = useSWR(
|
||||
`UNSPLASH_IMAGES_${searchParams}`,
|
||||
|
@ -2,8 +2,8 @@ import React, { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import { Transition, Dialog } from "@headlessui/react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// services
|
||||
import { FileService } from "services/file.service";
|
||||
// hooks
|
||||
@ -32,12 +32,12 @@ export const UserImageUploadModal: React.FC<Props> = observer((props) => {
|
||||
// states
|
||||
const [image, setImage] = useState<File | null>(null);
|
||||
const [isImageUploading, setIsImageUploading] = useState(false);
|
||||
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
|
||||
const onDrop = (acceptedFiles: File[]) => setImage(acceptedFiles[0]);
|
||||
|
||||
|
@ -3,8 +3,8 @@ import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
import { Transition, Dialog } from "@headlessui/react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication, useWorkspace } from "hooks/store";
|
||||
// services
|
||||
import { FileService } from "services/file.service";
|
||||
// hooks
|
||||
@ -40,9 +40,9 @@ export const WorkspaceImageUploadModal: React.FC<Props> = observer((props) => {
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const {
|
||||
workspace: { currentWorkspace },
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const onDrop = (acceptedFiles: File[]) => setImage(acceptedFiles[0]);
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { useTheme } from "next-themes";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
// ui
|
||||
import { Button, InputColorPicker } from "@plane/ui";
|
||||
// types
|
||||
@ -25,8 +25,8 @@ const inputRules = {
|
||||
};
|
||||
|
||||
export const CustomThemeSelector: React.FC = observer(() => {
|
||||
const { user: userStore } = useMobxStore();
|
||||
const userTheme = userStore?.currentUser?.theme;
|
||||
const { currentUser, updateCurrentUser } = useUser();
|
||||
const userTheme = currentUser?.theme;
|
||||
// hooks
|
||||
const { setTheme } = useTheme();
|
||||
|
||||
@ -61,7 +61,7 @@ export const CustomThemeSelector: React.FC = observer(() => {
|
||||
|
||||
setTheme("custom");
|
||||
|
||||
return userStore.updateCurrentUser({ theme: payload });
|
||||
return updateCurrentUser({ theme: payload });
|
||||
};
|
||||
|
||||
const handleValueChange = (val: string | undefined, onChange: any) => {
|
||||
|
@ -3,9 +3,9 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR from "swr";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { useApplication } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { SingleProgressStats } from "components/core";
|
||||
@ -67,12 +67,15 @@ interface IActiveCycleDetails {
|
||||
}
|
||||
|
||||
export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props) => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
|
||||
const { workspaceSlug, projectId } = props;
|
||||
|
||||
const { cycle: cycleStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
|
||||
// store hooks
|
||||
const { cycle: cycleStore } = useMobxStore();
|
||||
const {
|
||||
commandPalette: { toggleCreateCycleModal },
|
||||
} = useApplication();
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
useSWR(
|
||||
@ -118,7 +121,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
||||
<button
|
||||
type="button"
|
||||
className="text-sm text-custom-primary-100 outline-none"
|
||||
onClick={() => commandPaletteStore.toggleCreateCycleModal(true)}
|
||||
onClick={() => toggleCreateCycleModal(true)}
|
||||
>
|
||||
Create a new cycle
|
||||
</button>
|
||||
@ -187,12 +190,12 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
||||
cycleStatus === "current"
|
||||
? "#09A953"
|
||||
: cycleStatus === "upcoming"
|
||||
? "#F7AE59"
|
||||
: cycleStatus === "completed"
|
||||
? "#3F76FF"
|
||||
: cycleStatus === "draft"
|
||||
? "rgb(var(--color-text-200))"
|
||||
: ""
|
||||
? "#F7AE59"
|
||||
: cycleStatus === "completed"
|
||||
? "#3F76FF"
|
||||
: cycleStatus === "draft"
|
||||
? "rgb(var(--color-text-200))"
|
||||
: ""
|
||||
}`}
|
||||
/>
|
||||
</span>
|
||||
@ -207,12 +210,12 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
||||
cycleStatus === "current"
|
||||
? "bg-green-600/5 text-green-600"
|
||||
: cycleStatus === "upcoming"
|
||||
? "bg-orange-300/5 text-orange-300"
|
||||
: cycleStatus === "completed"
|
||||
? "bg-blue-500/5 text-blue-500"
|
||||
: cycleStatus === "draft"
|
||||
? "bg-neutral-400/5 text-neutral-400"
|
||||
: ""
|
||||
? "bg-orange-300/5 text-orange-300"
|
||||
: cycleStatus === "completed"
|
||||
? "bg-blue-500/5 text-blue-500"
|
||||
: cycleStatus === "draft"
|
||||
? "bg-neutral-400/5 text-neutral-400"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
{cycleStatus === "current" ? (
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// components
|
||||
import { CyclePeekOverview, CyclesBoardCard } from "components/cycles";
|
||||
// types
|
||||
@ -17,8 +17,8 @@ export interface ICyclesBoard {
|
||||
|
||||
export const CyclesBoard: FC<ICyclesBoard> = observer((props) => {
|
||||
const { cycles, filter, workspaceSlug, projectId, peekCycle } = props;
|
||||
|
||||
const { commandPalette: commandPaletteStore } = useMobxStore();
|
||||
// store hooks
|
||||
const { commandPalette: commandPaletteStore } = useApplication();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// components
|
||||
import { CyclePeekOverview, CyclesListItem } from "components/cycles";
|
||||
// ui
|
||||
@ -18,11 +18,11 @@ export interface ICyclesList {
|
||||
|
||||
export const CyclesList: FC<ICyclesList> = observer((props) => {
|
||||
const { cycles, filter, workspaceSlug, projectId } = props;
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
commandPalette: commandPaletteStore,
|
||||
trackEvent: { setTrackElement },
|
||||
} = useMobxStore();
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -1,19 +1,18 @@
|
||||
import { FC } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { KeyedMutator } from "swr";
|
||||
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
// services
|
||||
import { CycleService } from "services/cycle.service";
|
||||
// hooks
|
||||
import useUser from "hooks/use-user";
|
||||
import useProjectDetails from "hooks/use-project-details";
|
||||
// components
|
||||
import { GanttChartRoot, IBlockUpdateData, CycleGanttSidebar } from "components/gantt-chart";
|
||||
import { CycleGanttBlock } from "components/cycles";
|
||||
// types
|
||||
import { ICycle } from "types";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
|
||||
type Props = {
|
||||
workspaceSlug: string;
|
||||
@ -24,15 +23,18 @@ type Props = {
|
||||
// services
|
||||
const cycleService = new CycleService();
|
||||
|
||||
export const CyclesListGanttChartView: FC<Props> = ({ cycles, mutateCycles }) => {
|
||||
export const CyclesListGanttChartView: FC<Props> = observer((props) => {
|
||||
const { cycles, mutateCycles } = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { user } = useUser();
|
||||
const { projectDetails } = useProjectDetails();
|
||||
// store hooks
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
|
||||
const handleCycleUpdate = (cycle: ICycle, payload: IBlockUpdateData) => {
|
||||
if (!workspaceSlug || !user) return;
|
||||
if (!workspaceSlug) return;
|
||||
mutateCycles &&
|
||||
mutateCycles((prevData: any) => {
|
||||
if (!prevData) return prevData;
|
||||
@ -76,7 +78,8 @@ export const CyclesListGanttChartView: FC<Props> = ({ cycles, mutateCycles }) =>
|
||||
}))
|
||||
: [];
|
||||
|
||||
const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
|
||||
const isAllowed =
|
||||
currentProjectRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentProjectRole);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full overflow-y-auto">
|
||||
@ -94,4 +97,4 @@ export const CyclesListGanttChartView: FC<Props> = ({ cycles, mutateCycles }) =>
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -1,11 +1,8 @@
|
||||
import React from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// store
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { Button, CustomMenu } from "@plane/ui";
|
||||
@ -27,10 +24,8 @@ export const EstimateListItem: React.FC<Props> = observer((props) => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// store
|
||||
const {
|
||||
project: { currentProjectDetails, updateProject },
|
||||
} = useMobxStore();
|
||||
// store hooks
|
||||
const { currentProjectDetails, updateProject } = useProject();
|
||||
// hooks
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
|
@ -2,8 +2,8 @@ import React, { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
// services
|
||||
import { ProjectExportService } from "services/project";
|
||||
// hooks
|
||||
@ -26,28 +26,30 @@ const projectExportService = new ProjectExportService();
|
||||
|
||||
export const Exporter: React.FC<Props> = observer((props) => {
|
||||
const { isOpen, handleClose, user, provider, mutateServices } = props;
|
||||
|
||||
// states
|
||||
const [exportLoading, setExportLoading] = useState(false);
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { project: projectStore } = useMobxStore();
|
||||
|
||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
||||
|
||||
// store hooks
|
||||
const { workspaceProjects, getProjectById } = useProject();
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const options = projects?.map((project) => ({
|
||||
value: project.id,
|
||||
query: project.name + project.identifier,
|
||||
content: (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[0.65rem] text-custom-text-200">{project.identifier}</span>
|
||||
{project.name}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
const options = workspaceProjects?.map((projectId) => {
|
||||
const projectDetails = getProjectById(projectId);
|
||||
|
||||
return {
|
||||
value: projectDetails?.id,
|
||||
query: `${projectDetails?.name} ${projectDetails?.identifier}`,
|
||||
content: (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[0.65rem] text-custom-text-200">{projectDetails?.identifier}</span>
|
||||
{projectDetails?.name}
|
||||
</div>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
const [value, setValue] = React.useState<string[]>([]);
|
||||
const [multiple, setMultiple] = React.useState<boolean>(false);
|
||||
@ -131,10 +133,12 @@ export const Exporter: React.FC<Props> = observer((props) => {
|
||||
input
|
||||
label={
|
||||
value && value.length > 0
|
||||
? projects &&
|
||||
projects
|
||||
.filter((p) => value.includes(p.id))
|
||||
.map((p) => p.identifier)
|
||||
? value
|
||||
.map((projectId) => {
|
||||
const projectDetails = getProjectById(projectId);
|
||||
|
||||
return projectDetails?.identifier;
|
||||
})
|
||||
.join(", ")
|
||||
: "All projects"
|
||||
}
|
||||
|
@ -2,8 +2,8 @@ import { FC } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Plus } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication, useProject, useUser } from "hooks/store";
|
||||
// ui
|
||||
import { Breadcrumbs, Button, ContrastIcon } from "@plane/ui";
|
||||
// helpers
|
||||
@ -14,14 +14,15 @@ export const CyclesHeader: FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// store
|
||||
// store hooks
|
||||
const {
|
||||
project: projectStore,
|
||||
user: { currentProjectRole },
|
||||
commandPalette: commandPaletteStore,
|
||||
trackEvent: { setTrackElement },
|
||||
} = useMobxStore();
|
||||
const { currentProjectDetails } = projectStore;
|
||||
commandPalette: { toggleCreateCycleModal },
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
const canUserCreateCycle =
|
||||
currentProjectRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentProjectRole);
|
||||
@ -63,7 +64,7 @@ export const CyclesHeader: FC = observer(() => {
|
||||
prependIcon={<Plus />}
|
||||
onClick={() => {
|
||||
setTrackElement("CYCLES_PAGE_HEADER");
|
||||
commandPaletteStore.toggleCreateCycleModal(true);
|
||||
toggleCreateCycleModal(true);
|
||||
}}
|
||||
>
|
||||
Add Cycle
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Plus } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication, useProject, useUser } from "hooks/store";
|
||||
import useLocalStorage from "hooks/use-local-storage";
|
||||
// ui
|
||||
import { Breadcrumbs, Button, Tooltip, DiceIcon } from "@plane/ui";
|
||||
@ -17,13 +16,12 @@ export const ModulesListHeader: React.FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// store
|
||||
// store hooks
|
||||
const { commandPalette: commandPaletteStore } = useApplication();
|
||||
const {
|
||||
project: projectStore,
|
||||
commandPalette: commandPaletteStore,
|
||||
user: { currentProjectRole },
|
||||
} = useMobxStore();
|
||||
const { currentProjectDetails } = projectStore;
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
const { storedValue: modulesView, setValue: setModulesView } = useLocalStorage("modules_view", "grid");
|
||||
|
||||
|
@ -1,21 +1,18 @@
|
||||
import { FC } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR from "swr";
|
||||
import { FileText, Plus } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication, useProject } from "hooks/store";
|
||||
// services
|
||||
import { PageService } from "services/page.service";
|
||||
|
||||
// constants
|
||||
import { PAGE_DETAILS } from "constants/fetch-keys";
|
||||
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// ui
|
||||
import { Breadcrumbs, Button } from "@plane/ui";
|
||||
// helper
|
||||
// helpers
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
|
||||
import useSWR from "swr";
|
||||
// fetch-keys
|
||||
import { PAGE_DETAILS } from "constants/fetch-keys";
|
||||
|
||||
export interface IPagesHeaderProps {
|
||||
showButton?: boolean;
|
||||
@ -28,8 +25,8 @@ export const PageDetailsHeader: FC<IPagesHeaderProps> = observer((props) => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, pageId } = router.query;
|
||||
|
||||
const { project: projectStore, commandPalette: commandPaletteStore } = useMobxStore();
|
||||
const { currentProjectDetails } = projectStore;
|
||||
const { commandPalette: commandPaletteStore } = useApplication();
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
const { data: pageDetails } = useSWR(
|
||||
workspaceSlug && currentProjectDetails?.id && pageId ? PAGE_DETAILS(pageId as string) : null,
|
||||
|
@ -2,10 +2,10 @@ import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { FileText, Plus } from "lucide-react";
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { useApplication, useProject, useUser } from "hooks/store";
|
||||
// ui
|
||||
import { Breadcrumbs, Button } from "@plane/ui";
|
||||
// helper
|
||||
// helpers
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
@ -14,12 +14,14 @@ export const PagesHeader = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// mobx store
|
||||
// store hooks
|
||||
const {
|
||||
user: { currentProjectRole },
|
||||
project: { currentProjectDetails },
|
||||
commandPalette: { toggleCreatePageModal },
|
||||
} = useMobxStore();
|
||||
} = useApplication();
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
const canUserCreatePage =
|
||||
currentProjectRole && [EUserWorkspaceRoles.ADMIN, EUserWorkspaceRoles.MEMBER].includes(currentProjectRole);
|
||||
|
@ -3,7 +3,7 @@ import useSWR from "swr";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { useProject } from "hooks/store";
|
||||
// ui
|
||||
import { Breadcrumbs, LayersIcon } from "@plane/ui";
|
||||
// types
|
||||
@ -18,12 +18,11 @@ import { renderEmoji } from "helpers/emoji.helper";
|
||||
const issueArchiveService = new IssueArchiveService();
|
||||
|
||||
export const ProjectArchivedIssueDetailsHeader: FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, archivedIssueId } = router.query;
|
||||
|
||||
const { project: projectStore } = useMobxStore();
|
||||
|
||||
const { currentProjectDetails } = projectStore;
|
||||
// store hooks
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
const { data: issueDetails } = useSWR<IIssue | undefined>(
|
||||
workspaceSlug && projectId && archivedIssueId ? ISSUE_DETAILS(archivedIssueId as string) : null,
|
||||
|
@ -3,7 +3,7 @@ import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Plus } from "lucide-react";
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { useProject } from "hooks/store";
|
||||
// ui
|
||||
import { Breadcrumbs, Button, LayersIcon } from "@plane/ui";
|
||||
// components
|
||||
@ -12,13 +12,13 @@ import { CreateInboxIssueModal } from "components/inbox";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
|
||||
export const ProjectInboxHeader: FC = observer(() => {
|
||||
// states
|
||||
const [createIssueModal, setCreateIssueModal] = useState(false);
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
const [createIssueModal, setCreateIssueModal] = useState(false);
|
||||
|
||||
const { project: projectStore } = useMobxStore();
|
||||
|
||||
const { currentProjectDetails } = projectStore;
|
||||
// store hooks
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
return (
|
||||
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
|
||||
|
@ -2,27 +2,26 @@ import { FC } from "react";
|
||||
import useSWR from "swr";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
// ui
|
||||
import { Breadcrumbs, LayersIcon } from "@plane/ui";
|
||||
// helper
|
||||
// helpers
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// services
|
||||
import { IssueService } from "services/issue";
|
||||
// constants
|
||||
import { ISSUE_DETAILS } from "constants/fetch-keys";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
// services
|
||||
const issueService = new IssueService();
|
||||
|
||||
export const ProjectIssueDetailsHeader: FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, issueId } = router.query;
|
||||
|
||||
const { project: projectStore } = useMobxStore();
|
||||
|
||||
const { currentProjectDetails } = projectStore;
|
||||
// store hooks
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
const { data: issueDetails } = useSWR(
|
||||
workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId as string) : null,
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { FC } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
// ui
|
||||
import { Breadcrumbs } from "@plane/ui";
|
||||
// helper
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useProject, useUser } from "hooks/store";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
|
||||
@ -17,14 +16,14 @@ export interface IProjectSettingHeader {
|
||||
|
||||
export const ProjectSettingHeader: FC<IProjectSettingHeader> = observer((props) => {
|
||||
const { title } = props;
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// store
|
||||
// store hooks
|
||||
const {
|
||||
project: projectStore,
|
||||
user: { currentProjectRole },
|
||||
} = useMobxStore();
|
||||
const { currentProjectDetails } = projectStore;
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
if (currentProjectRole && currentProjectRole <= EUserWorkspaceRoles.VIEWER) return null;
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Plus } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication, useProject } from "hooks/store";
|
||||
// components
|
||||
import { Breadcrumbs, PhotoFilterIcon, Button } from "@plane/ui";
|
||||
// helpers
|
||||
@ -12,9 +12,11 @@ export const ProjectViewsHeader: React.FC = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { project: projectStore, commandPalette } = useMobxStore();
|
||||
const { currentProjectDetails } = projectStore;
|
||||
// store hooks
|
||||
const {
|
||||
commandPalette: { toggleCreateViewModal },
|
||||
} = useApplication();
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -56,7 +58,7 @@ export const ProjectViewsHeader: React.FC = observer(() => {
|
||||
variant="primary"
|
||||
size="sm"
|
||||
prependIcon={<Plus className="h-3.5 w-3.5 stroke-2" />}
|
||||
onClick={() => commandPalette.toggleCreateViewModal(true)}
|
||||
onClick={() => toggleCreateViewModal(true)}
|
||||
>
|
||||
Create View
|
||||
</Button>
|
||||
|
@ -1,23 +1,17 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Search, Plus, Briefcase } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication, useProject } from "hooks/store";
|
||||
// ui
|
||||
import { Breadcrumbs, Button } from "@plane/ui";
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
export const ProjectsHeader = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// store
|
||||
// store hooks
|
||||
const {
|
||||
project: projectStore,
|
||||
commandPalette: commandPaletteStore,
|
||||
trackEvent: { setTrackElement },
|
||||
} = useMobxStore();
|
||||
|
||||
const projectsList = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : [];
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
const { workspaceProjects, searchQuery, setSearchQuery } = useProject();
|
||||
|
||||
return (
|
||||
<div className="relative z-10 flex h-[3.75rem] w-full flex-shrink-0 flex-row items-center justify-between gap-x-2 gap-y-4 border-b border-custom-border-200 bg-custom-sidebar-background-100 p-4">
|
||||
@ -33,13 +27,13 @@ export const ProjectsHeader = observer(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{projectsList?.length > 0 && (
|
||||
{workspaceProjects && workspaceProjects?.length > 0 && (
|
||||
<div className="flex w-full items-center justify-start gap-1 rounded-md border border-custom-border-200 bg-custom-background-100 px-2.5 py-1.5 text-custom-text-400">
|
||||
<Search className="h-3.5 w-3.5" />
|
||||
<input
|
||||
className="w-full min-w-[234px] border-none bg-transparent text-sm focus:outline-none"
|
||||
value={projectStore.searchQuery}
|
||||
onChange={(e) => projectStore.setSearchQuery(e.target.value)}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
placeholder="Search"
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication, useUser, useWorkspace } from "hooks/store";
|
||||
// components
|
||||
import { AddComment, IssueActivitySection } from "components/issues";
|
||||
// services
|
||||
@ -21,14 +21,15 @@ const issueService = new IssueService();
|
||||
const issueCommentService = new IssueCommentService();
|
||||
|
||||
export const InboxIssueActivity: React.FC<Props> = observer(({ issueDetails }) => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
user: userStore,
|
||||
trackEvent: { postHogEventTracker },
|
||||
workspace: { currentWorkspace },
|
||||
} = useMobxStore();
|
||||
eventTracker: { postHogEventTracker },
|
||||
} = useApplication();
|
||||
const { currentUser } = useUser();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
@ -39,13 +40,11 @@ export const InboxIssueActivity: React.FC<Props> = observer(({ issueDetails }) =
|
||||
: null
|
||||
);
|
||||
|
||||
const user = userStore.currentUser;
|
||||
|
||||
const handleCommentUpdate = async (commentId: string, data: Partial<any>) => {
|
||||
if (!workspaceSlug || !projectId || !issueDetails.id || !user) return;
|
||||
if (!workspaceSlug || !projectId || !issueDetails.id || !currentUser) return;
|
||||
|
||||
await issueCommentService
|
||||
.patchIssueComment(workspaceSlug as string, projectId as string, issueDetails.id as string, commentId, data)
|
||||
.patchIssueComment(workspaceSlug.toString(), projectId.toString(), issueDetails.id, commentId, data)
|
||||
.then((res) => {
|
||||
mutateIssueActivity();
|
||||
postHogEventTracker(
|
||||
@ -57,19 +56,19 @@ export const InboxIssueActivity: React.FC<Props> = observer(({ issueDetails }) =
|
||||
{
|
||||
isGrouping: true,
|
||||
groupType: "Workspace_metrics",
|
||||
gorupId: currentWorkspace?.id!,
|
||||
groupId: currentWorkspace?.id!,
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const handleCommentDelete = async (commentId: string) => {
|
||||
if (!workspaceSlug || !projectId || !issueDetails.id || !user) return;
|
||||
if (!workspaceSlug || !projectId || !issueDetails.id || !currentUser) return;
|
||||
|
||||
mutateIssueActivity((prevData: any) => prevData?.filter((p: any) => p.id !== commentId), false);
|
||||
|
||||
await issueCommentService
|
||||
.deleteIssueComment(workspaceSlug as string, projectId as string, issueDetails.id as string, commentId)
|
||||
.deleteIssueComment(workspaceSlug.toString(), projectId.toString(), issueDetails.id, commentId)
|
||||
.then(() => {
|
||||
mutateIssueActivity();
|
||||
postHogEventTracker(
|
||||
@ -80,14 +79,14 @@ export const InboxIssueActivity: React.FC<Props> = observer(({ issueDetails }) =
|
||||
{
|
||||
isGrouping: true,
|
||||
groupType: "Workspace_metrics",
|
||||
gorupId: currentWorkspace?.id!,
|
||||
groupId: currentWorkspace?.id!,
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddComment = async (formData: IIssueActivity) => {
|
||||
if (!workspaceSlug || !issueDetails || !user) return;
|
||||
if (!workspaceSlug || !issueDetails || !currentUser) return;
|
||||
|
||||
await issueCommentService
|
||||
.createIssueComment(workspaceSlug.toString(), issueDetails.project, issueDetails.id, formData)
|
||||
@ -102,7 +101,7 @@ export const InboxIssueActivity: React.FC<Props> = observer(({ issueDetails }) =
|
||||
{
|
||||
isGrouping: true,
|
||||
groupType: "Workspace_metrics",
|
||||
gorupId: currentWorkspace?.id!,
|
||||
groupId: currentWorkspace?.id!,
|
||||
}
|
||||
);
|
||||
})
|
||||
|
@ -1,15 +1,13 @@
|
||||
import { FC, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
// ui
|
||||
import { Button, Input } from "@plane/ui";
|
||||
// types
|
||||
import { IFormattedInstanceConfiguration } from "types/instance";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// icons
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
|
||||
export interface IInstanceAIForm {
|
||||
config: IFormattedInstanceConfiguration;
|
||||
@ -25,7 +23,7 @@ export const InstanceAIForm: FC<IInstanceAIForm> = (props) => {
|
||||
// states
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
// store
|
||||
const { instance: instanceStore } = useMobxStore();
|
||||
const { instance: instanceStore } = useApplication();
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
// form data
|
||||
|
@ -6,9 +6,8 @@ import { Eye, EyeOff } from "lucide-react";
|
||||
// types
|
||||
import { IFormattedInstanceConfiguration } from "types/instance";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
export interface IInstanceEmailForm {
|
||||
config: IFormattedInstanceConfiguration;
|
||||
@ -27,8 +26,8 @@ export const InstanceEmailForm: FC<IInstanceEmailForm> = (props) => {
|
||||
const { config } = props;
|
||||
// states
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
// store
|
||||
const { instance: instanceStore } = useMobxStore();
|
||||
// store hooks
|
||||
const { instance: instanceStore } = useApplication();
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
// form data
|
||||
|
@ -5,8 +5,8 @@ import { Button, Input } from "@plane/ui";
|
||||
// types
|
||||
import { IInstance, IInstanceAdmin } from "types/instance";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
export interface IInstanceGeneralForm {
|
||||
instance: IInstance;
|
||||
@ -20,8 +20,8 @@ export interface GeneralFormValues {
|
||||
|
||||
export const InstanceGeneralForm: FC<IInstanceGeneralForm> = (props) => {
|
||||
const { instance, instanceAdmins } = props;
|
||||
// store
|
||||
const { instance: instanceStore } = useMobxStore();
|
||||
// store hooks
|
||||
const { instance: instanceStore } = useApplication();
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
// form data
|
||||
|
@ -1,15 +1,13 @@
|
||||
import { FC, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Copy, Eye, EyeOff } from "lucide-react";
|
||||
// ui
|
||||
import { Button, Input } from "@plane/ui";
|
||||
// types
|
||||
import { IFormattedInstanceConfiguration } from "types/instance";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// icons
|
||||
import { Copy, Eye, EyeOff } from "lucide-react";
|
||||
|
||||
export interface IInstanceGithubConfigForm {
|
||||
config: IFormattedInstanceConfiguration;
|
||||
@ -24,8 +22,8 @@ export const InstanceGithubConfigForm: FC<IInstanceGithubConfigForm> = (props) =
|
||||
const { config } = props;
|
||||
// states
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
// store
|
||||
const { instance: instanceStore } = useMobxStore();
|
||||
// store hooks
|
||||
const { instance: instanceStore } = useApplication();
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
// form data
|
||||
|
@ -1,15 +1,13 @@
|
||||
import { FC } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Copy } from "lucide-react";
|
||||
// ui
|
||||
import { Button, Input } from "@plane/ui";
|
||||
// types
|
||||
import { IFormattedInstanceConfiguration } from "types/instance";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// icons
|
||||
import { Copy } from "lucide-react";
|
||||
|
||||
export interface IInstanceGoogleConfigForm {
|
||||
config: IFormattedInstanceConfiguration;
|
||||
@ -22,8 +20,8 @@ export interface GoogleConfigFormValues {
|
||||
|
||||
export const InstanceGoogleConfigForm: FC<IInstanceGoogleConfigForm> = (props) => {
|
||||
const { config } = props;
|
||||
// store
|
||||
const { instance: instanceStore } = useMobxStore();
|
||||
// store hooks
|
||||
const { instance: instanceStore } = useApplication();
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
// form data
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { FC, useState, useRef } from "react";
|
||||
import { Transition } from "@headlessui/react";
|
||||
import Link from "next/link";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// icons
|
||||
import { FileText, HelpCircle, MessagesSquare, MoveLeft } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// icons
|
||||
import { DiscordIcon, GithubIcon } from "@plane/ui";
|
||||
// assets
|
||||
import packageJson from "package.json";
|
||||
@ -39,7 +39,7 @@ export const InstanceHelpSection: FC = () => {
|
||||
// store
|
||||
const {
|
||||
theme: { sidebarCollapsed, toggleSidebar },
|
||||
} = useMobxStore();
|
||||
} = useApplication();
|
||||
// refs
|
||||
const helpOptionsRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
|
@ -1,15 +1,13 @@
|
||||
import { FC, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
// ui
|
||||
import { Button, Input } from "@plane/ui";
|
||||
// types
|
||||
import { IFormattedInstanceConfiguration } from "types/instance";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// icons
|
||||
import { Eye, EyeOff } from "lucide-react";
|
||||
|
||||
export interface IInstanceImageConfigForm {
|
||||
config: IFormattedInstanceConfiguration;
|
||||
@ -23,8 +21,8 @@ export const InstanceImageConfigForm: FC<IInstanceImageConfigForm> = (props) =>
|
||||
const { config } = props;
|
||||
// states
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
// store
|
||||
const { instance: instanceStore } = useMobxStore();
|
||||
// store hooks
|
||||
const { instance: instanceStore } = useApplication();
|
||||
// toast
|
||||
const { setToastAlert } = useToast();
|
||||
// form data
|
||||
|
@ -1,6 +1,8 @@
|
||||
import React, { useState } from "react";
|
||||
import Image from "next/image";
|
||||
import { useTheme } from "next-themes";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
import { UserCog2 } from "lucide-react";
|
||||
@ -8,17 +10,16 @@ import { UserCog2 } from "lucide-react";
|
||||
import instanceSetupDone from "public/instance-setup-done.webp";
|
||||
import PlaneBlackLogo from "public/plane-logos/black-horizontal-with-blue-logo.svg";
|
||||
import PlaneWhiteLogo from "public/plane-logos/white-horizontal-with-blue-logo.svg";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
export const InstanceSetupDone = () => {
|
||||
// states
|
||||
const [isRedirecting, setIsRedirecting] = useState(false);
|
||||
// next-themes
|
||||
const { resolvedTheme } = useTheme();
|
||||
// mobx store
|
||||
// store hooks
|
||||
const {
|
||||
instance: { fetchInstanceInfo },
|
||||
} = useMobxStore();
|
||||
} = useApplication();
|
||||
|
||||
const planeLogo = resolvedTheme === "dark" ? PlaneWhiteLogo : PlaneBlackLogo;
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
import { FC } from "react";
|
||||
import { useForm, Controller } from "react-hook-form";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { XCircle } from "lucide-react";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
// ui
|
||||
import { Input, Button } from "@plane/ui";
|
||||
// icons
|
||||
import { XCircle } from "lucide-react";
|
||||
// services
|
||||
import { AuthService } from "services/auth.service";
|
||||
const authService = new AuthService();
|
||||
@ -25,9 +24,8 @@ export interface IInstanceSetupEmailForm {
|
||||
|
||||
export const InstanceSetupSignInForm: FC<IInstanceSetupEmailForm> = (props) => {
|
||||
const { handleNextStep } = props;
|
||||
const {
|
||||
user: { fetchCurrentUser },
|
||||
} = useMobxStore();
|
||||
// store hooks
|
||||
const { fetchCurrentUser } = useUser();
|
||||
// form info
|
||||
const {
|
||||
control,
|
||||
|
@ -4,15 +4,13 @@ import Image from "next/image";
|
||||
// components
|
||||
import { InstanceSetupFormRoot } from "components/instance";
|
||||
// hooks
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { useUser } from "hooks/store";
|
||||
// images
|
||||
import BluePlaneLogoWithoutText from "public/plane-logos/blue-without-text.png";
|
||||
|
||||
export const InstanceSetupView = observer(() => {
|
||||
// store
|
||||
const {
|
||||
user: { fetchCurrentUser },
|
||||
} = useMobxStore();
|
||||
// store hooks
|
||||
const { fetchCurrentUser } = useUser();
|
||||
|
||||
const mutateUserInfo = useCallback(() => {
|
||||
fetchCurrentUser();
|
||||
|
@ -8,8 +8,8 @@ import { mutate } from "swr";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
// icons
|
||||
import { LogIn, LogOut, Settings, UserCog2 } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication, useUser } from "hooks/store";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
@ -26,13 +26,14 @@ const PROFILE_LINKS = [
|
||||
];
|
||||
|
||||
export const InstanceSidebarDropdown = observer(() => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
// store
|
||||
// store hooks
|
||||
const {
|
||||
theme: { sidebarCollapsed },
|
||||
workspace: { workspaceSlug },
|
||||
user: { signOut, currentUser, currentUserSettings },
|
||||
} = useMobxStore();
|
||||
router: { workspaceSlug },
|
||||
} = useApplication();
|
||||
const { signOut, currentUser, currentUserSettings } = useUser();
|
||||
// hooks
|
||||
const { setToastAlert } = useToast();
|
||||
const { setTheme } = useTheme();
|
||||
|
@ -1,9 +1,8 @@
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
// icons
|
||||
import { Image, BrainCog, Cog, Lock, Mail } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
|
||||
@ -41,9 +40,10 @@ const INSTANCE_ADMIN_LINKS = [
|
||||
];
|
||||
|
||||
export const InstanceAdminSidebarMenu = () => {
|
||||
// store hooks
|
||||
const {
|
||||
theme: { sidebarCollapsed },
|
||||
} = useMobxStore();
|
||||
} = useApplication();
|
||||
// router
|
||||
const router = useRouter();
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useIntegrationPopup from "hooks/use-integration-popup";
|
||||
// ui
|
||||
import { Button } from "@plane/ui";
|
||||
// types
|
||||
import { IWorkspaceIntegration } from "types";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
type Props = {
|
||||
workspaceIntegration: false | IWorkspaceIntegration | undefined;
|
||||
@ -13,9 +13,10 @@ type Props = {
|
||||
};
|
||||
|
||||
export const GithubAuth: React.FC<Props> = observer(({ workspaceIntegration, provider }) => {
|
||||
// store hooks
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
// hooks
|
||||
const { startAuth, isConnecting } = useIntegrationPopup({
|
||||
provider,
|
||||
|
@ -1,9 +1,8 @@
|
||||
import { FC } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Control, Controller, UseFormWatch } from "react-hook-form";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
// components
|
||||
import { SelectRepository, TFormValues, TIntegrationSteps } from "components/integration";
|
||||
// ui
|
||||
@ -22,21 +21,18 @@ type Props = {
|
||||
|
||||
export const GithubImportData: FC<Props> = observer((props) => {
|
||||
const { handleStepChange, integration, control, watch } = props;
|
||||
// store hooks
|
||||
const { workspaceProjects, getProjectById } = useProject();
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
const options = workspaceProjects?.map((projectId) => {
|
||||
const projectDetails = getProjectById(projectId);
|
||||
|
||||
const { project: projectStore } = useMobxStore();
|
||||
|
||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
||||
|
||||
const options = projects
|
||||
? projects.map((project) => ({
|
||||
value: project.id,
|
||||
query: project.name,
|
||||
content: <p>{truncateText(project.name, 25)}</p>,
|
||||
}))
|
||||
: undefined;
|
||||
return {
|
||||
value: `${projectDetails?.id}`,
|
||||
query: `${projectDetails?.name}`,
|
||||
content: <p>{truncateText(projectDetails?.name ?? "", 25)}</p>,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="mt-6">
|
||||
@ -74,7 +70,7 @@ export const GithubImportData: FC<Props> = observer((props) => {
|
||||
<p className="text-xs text-custom-text-200">Select the project to import the issues to.</p>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-4">
|
||||
{projects && (
|
||||
{workspaceProjects && (
|
||||
<Controller
|
||||
control={control}
|
||||
name="project"
|
||||
@ -82,11 +78,7 @@ export const GithubImportData: FC<Props> = observer((props) => {
|
||||
<CustomSearchSelect
|
||||
value={value}
|
||||
label={
|
||||
value ? (
|
||||
projects.find((p) => p.id === value)?.name
|
||||
) : (
|
||||
<span className="text-custom-text-200">Select Project</span>
|
||||
)
|
||||
value ? getProjectById(value)?.name : <span className="text-custom-text-200">Select Project</span>
|
||||
}
|
||||
onChange={onChange}
|
||||
options={options}
|
||||
|
@ -1,28 +1,23 @@
|
||||
import React from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import Link from "next/link";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useFormContext, Controller } from "react-hook-form";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// icons
|
||||
import { Plus } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication, useProject } from "hooks/store";
|
||||
// components
|
||||
import { CustomSelect, Input } from "@plane/ui";
|
||||
// types
|
||||
import { IJiraImporterForm } from "types";
|
||||
|
||||
export const JiraGetImportDetail: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
project: projectStore,
|
||||
commandPalette: commandPaletteStore,
|
||||
trackEvent: { setTrackElement },
|
||||
} = useMobxStore();
|
||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : undefined;
|
||||
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
const { workspaceProjects, getProjectById } = useProject();
|
||||
// form info
|
||||
const {
|
||||
control,
|
||||
formState: { errors },
|
||||
@ -170,20 +165,26 @@ export const JiraGetImportDetail: React.FC = observer(() => {
|
||||
onChange={onChange}
|
||||
label={
|
||||
<span>
|
||||
{value && value !== "" ? (
|
||||
projects?.find((p) => p.id === value)?.name
|
||||
{value && value.trim() !== "" ? (
|
||||
getProjectById(value)?.name
|
||||
) : (
|
||||
<span className="text-custom-text-200">Select a project</span>
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
>
|
||||
{projects && projects.length > 0 ? (
|
||||
projects.map((project) => (
|
||||
<CustomSelect.Option key={project.id} value={project.id}>
|
||||
{project.name}
|
||||
</CustomSelect.Option>
|
||||
))
|
||||
{workspaceProjects && workspaceProjects.length > 0 ? (
|
||||
workspaceProjects.map((projectId) => {
|
||||
const projectDetails = getProjectById(projectId);
|
||||
|
||||
if (!projectDetails) return;
|
||||
|
||||
return (
|
||||
<CustomSelect.Option key={projectId} value={projectId}>
|
||||
{projectDetails.name}
|
||||
</CustomSelect.Option>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className="flex cursor-pointer select-none items-center space-x-2 truncate rounded px-1 py-1.5 text-custom-text-200">
|
||||
<p>You don{"'"}t have any project. Please create a project first.</p>
|
||||
|
@ -2,12 +2,12 @@ import { useState } from "react";
|
||||
|
||||
import Image from "next/image";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
import useSWR, { mutate } from "swr";
|
||||
|
||||
// services
|
||||
import { IntegrationService } from "services/integrations";
|
||||
// hooks
|
||||
import { useApplication, useUser } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import useIntegrationPopup from "hooks/use-integration-popup";
|
||||
// ui
|
||||
@ -20,8 +20,6 @@ import { CheckCircle } from "lucide-react";
|
||||
import { IAppIntegration, IWorkspaceIntegration } from "types";
|
||||
// fetch-keys
|
||||
import { WORKSPACE_INTEGRATIONS } from "constants/fetch-keys";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
type Props = {
|
||||
integration: IAppIntegration;
|
||||
@ -44,20 +42,23 @@ const integrationDetails: { [key: string]: any } = {
|
||||
const integrationService = new IntegrationService();
|
||||
|
||||
export const SingleIntegrationCard: React.FC<Props> = observer(({ integration }) => {
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
user: { currentWorkspaceRole },
|
||||
} = useMobxStore();
|
||||
|
||||
const isUserAdmin = currentWorkspaceRole === 20;
|
||||
|
||||
// states
|
||||
const [deletingIntegration, setDeletingIntegration] = useState(false);
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
const {
|
||||
membership: { currentWorkspaceRole },
|
||||
} = useUser();
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const isUserAdmin = currentWorkspaceRole === 20;
|
||||
|
||||
const { startAuth, isConnecting: isInstalling } = useIntegrationPopup({
|
||||
provider: integration.provider,
|
||||
github_app_name: envConfig?.github_app_name || "",
|
||||
|
@ -2,18 +2,17 @@ import { useState, useEffect } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useIntegrationPopup from "hooks/use-integration-popup";
|
||||
// services
|
||||
import { AppInstallationService } from "services/app_installation.service";
|
||||
// ui
|
||||
import { Loader } from "@plane/ui";
|
||||
// hooks
|
||||
import useIntegrationPopup from "hooks/use-integration-popup";
|
||||
// types
|
||||
import { IWorkspaceIntegration, ISlackIntegration } from "types";
|
||||
// fetch-keys
|
||||
import { SLACK_CHANNEL_INFO } from "constants/fetch-keys";
|
||||
// lib
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
type Props = {
|
||||
integration: IWorkspaceIntegration;
|
||||
@ -22,10 +21,10 @@ type Props = {
|
||||
const appInstallationService = new AppInstallationService();
|
||||
|
||||
export const SelectChannel: React.FC<Props> = observer(({ integration }) => {
|
||||
// store
|
||||
// store hooks
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
// states
|
||||
const [slackChannelAvailabilityToggle, setSlackChannelAvailabilityToggle] = useState<boolean>(false);
|
||||
const [slackChannel, setSlackChannel] = useState<ISlackIntegration | null>(null);
|
||||
|
@ -3,12 +3,11 @@ import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { mutate } from "swr";
|
||||
import { useDropzone } from "react-dropzone";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// services
|
||||
import { IssueAttachmentService } from "services/issue";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// types
|
||||
import { IIssueAttachment } from "types";
|
||||
// fetch-keys
|
||||
@ -29,12 +28,12 @@ export const IssueAttachmentUpload: React.FC<Props> = observer((props) => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, issueId } = router.query;
|
||||
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
|
||||
const onDrop = useCallback((acceptedFiles: File[]) => {
|
||||
if (!acceptedFiles[0] || !workspaceSlug) return;
|
||||
|
@ -1,12 +1,13 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
// services
|
||||
import { FileService } from "services/file.service";
|
||||
// icons
|
||||
import { Check, Globe2, Lock, MessageSquare, Pencil, Trash2, X } from "lucide-react";
|
||||
// hooks
|
||||
import useUser from "hooks/use-user";
|
||||
// ui
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
import { CommentReaction } from "components/issues";
|
||||
@ -15,7 +16,6 @@ import { LiteTextEditorWithRef, LiteReadOnlyEditorWithRef } from "@plane/lite-te
|
||||
import { timeAgo } from "helpers/date-time.helper";
|
||||
// types
|
||||
import type { IIssueActivity } from "types";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
|
||||
// services
|
||||
const fileService = new FileService();
|
||||
@ -28,22 +28,18 @@ type Props = {
|
||||
workspaceSlug: string;
|
||||
};
|
||||
|
||||
export const CommentCard: React.FC<Props> = ({
|
||||
comment,
|
||||
handleCommentDeletion,
|
||||
onSubmit,
|
||||
showAccessSpecifier = false,
|
||||
workspaceSlug,
|
||||
}) => {
|
||||
const { user } = useUser();
|
||||
|
||||
export const CommentCard: React.FC<Props> = observer((props) => {
|
||||
const { comment, handleCommentDeletion, onSubmit, showAccessSpecifier = false, workspaceSlug } = props;
|
||||
// states
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
// refs
|
||||
const editorRef = React.useRef<any>(null);
|
||||
const showEditorRef = React.useRef<any>(null);
|
||||
|
||||
const editorSuggestions = useEditorSuggestions();
|
||||
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
// store hooks
|
||||
const { currentUser } = useUser();
|
||||
// form info
|
||||
const {
|
||||
formState: { isSubmitting },
|
||||
handleSubmit,
|
||||
@ -152,7 +148,7 @@ export const CommentCard: React.FC<Props> = ({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{user?.id === comment.actor && (
|
||||
{currentUser?.id === comment.actor && (
|
||||
<CustomMenu ellipsis>
|
||||
<CustomMenu.MenuItem onClick={() => setIsEditing(true)} className="flex items-center gap-1">
|
||||
<Pencil className="h-3 w-3" />
|
||||
@ -192,4 +188,4 @@ export const CommentCard: React.FC<Props> = ({
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { FC } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import useUser from "hooks/use-user";
|
||||
import { useUser } from "hooks/store";
|
||||
import useCommentReaction from "hooks/use-comment-reaction";
|
||||
// ui
|
||||
import { ReactionSelector } from "components/core";
|
||||
// helper
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// types
|
||||
import { IssueCommentReaction } from "types";
|
||||
|
||||
type Props = {
|
||||
@ -15,13 +17,13 @@ type Props = {
|
||||
readonly?: boolean;
|
||||
};
|
||||
|
||||
export const CommentReaction: FC<Props> = (props) => {
|
||||
export const CommentReaction: FC<Props> = observer((props) => {
|
||||
const { projectId, commentId, readonly = false } = props;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const { user } = useUser();
|
||||
// store hooks
|
||||
const { currentUser } = useUser();
|
||||
|
||||
const { commentReactions, groupedReactions, handleReactionCreate, handleReactionDelete } = useCommentReaction(
|
||||
workspaceSlug,
|
||||
@ -33,7 +35,7 @@ export const CommentReaction: FC<Props> = (props) => {
|
||||
if (!workspaceSlug || !projectId || !commentId) return;
|
||||
|
||||
const isSelected = commentReactions?.some(
|
||||
(r: IssueCommentReaction) => r.actor === user?.id && r.reaction === reaction
|
||||
(r: IssueCommentReaction) => r.actor === currentUser?.id && r.reaction === reaction
|
||||
);
|
||||
|
||||
if (isSelected) {
|
||||
@ -51,7 +53,7 @@ export const CommentReaction: FC<Props> = (props) => {
|
||||
position="top"
|
||||
value={
|
||||
commentReactions
|
||||
?.filter((reaction: IssueCommentReaction) => reaction.actor === user?.id)
|
||||
?.filter((reaction: IssueCommentReaction) => reaction.actor === currentUser?.id)
|
||||
.map((r: IssueCommentReaction) => r.reaction) || []
|
||||
}
|
||||
onSelect={handleReactionClick}
|
||||
@ -70,7 +72,9 @@ export const CommentReaction: FC<Props> = (props) => {
|
||||
}}
|
||||
key={reaction}
|
||||
className={`flex h-full items-center gap-1 rounded-md px-2 py-1 text-sm text-custom-text-100 ${
|
||||
commentReactions?.some((r: IssueCommentReaction) => r.actor === user?.id && r.reaction === reaction)
|
||||
commentReactions?.some(
|
||||
(r: IssueCommentReaction) => r.actor === currentUser?.id && r.reaction === reaction
|
||||
)
|
||||
? "bg-custom-primary-100/10"
|
||||
: "bg-custom-background-80"
|
||||
}`}
|
||||
@ -78,7 +82,9 @@ export const CommentReaction: FC<Props> = (props) => {
|
||||
<span>{renderEmoji(reaction)}</span>
|
||||
<span
|
||||
className={
|
||||
commentReactions?.some((r: IssueCommentReaction) => r.actor === user?.id && r.reaction === reaction)
|
||||
commentReactions?.some(
|
||||
(r: IssueCommentReaction) => r.actor === currentUser?.id && r.reaction === reaction
|
||||
)
|
||||
? "text-custom-primary-100"
|
||||
: ""
|
||||
}
|
||||
@ -90,4 +96,4 @@ export const CommentReaction: FC<Props> = (props) => {
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -1,9 +1,6 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// services
|
||||
import { IssueDraftService } from "services/issue";
|
||||
// hooks
|
||||
@ -24,17 +21,14 @@ type Props = {
|
||||
|
||||
const issueDraftService = new IssueDraftService();
|
||||
|
||||
export const DeleteDraftIssueModal: React.FC<Props> = observer((props) => {
|
||||
export const DeleteDraftIssueModal: React.FC<Props> = (props) => {
|
||||
const { isOpen, handleClose, data, onSubmit } = props;
|
||||
|
||||
// states
|
||||
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
|
||||
|
||||
const { user: userStore } = useMobxStore();
|
||||
const user = userStore.currentUser;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
useEffect(() => {
|
||||
@ -47,12 +41,12 @@ export const DeleteDraftIssueModal: React.FC<Props> = observer((props) => {
|
||||
};
|
||||
|
||||
const handleDeletion = async () => {
|
||||
if (!workspaceSlug || !data || !user) return;
|
||||
if (!workspaceSlug || !data) return;
|
||||
|
||||
setIsDeleteLoading(true);
|
||||
|
||||
await issueDraftService
|
||||
.deleteDraftIssue(workspaceSlug as string, data.project, data.id)
|
||||
.deleteDraftIssue(workspaceSlug.toString(), data.project, data.id)
|
||||
.then(() => {
|
||||
setIsDeleteLoading(false);
|
||||
handleClose();
|
||||
@ -138,4 +132,4 @@ export const DeleteDraftIssueModal: React.FC<Props> = observer((props) => {
|
||||
</Dialog>
|
||||
</Transition.Root>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
@ -1,12 +1,16 @@
|
||||
import React, { FC, useState, useEffect, useRef } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Sparkle, X } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import useLocalStorage from "hooks/use-local-storage";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
// services
|
||||
import { AIService } from "services/ai.service";
|
||||
import { FileService } from "services/file.service";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
import useLocalStorage from "hooks/use-local-storage";
|
||||
// components
|
||||
import { GptAssistantModal } from "components/core";
|
||||
import { ParentIssuesListModal } from "components/issues";
|
||||
@ -21,18 +25,11 @@ import {
|
||||
} from "components/issues/select";
|
||||
import { CreateStateModal } from "components/states";
|
||||
import { CreateLabelModal } from "components/labels";
|
||||
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
|
||||
// ui
|
||||
import {} from "components/ui";
|
||||
import { Button, CustomMenu, Input, ToggleSwitch } from "@plane/ui";
|
||||
// icons
|
||||
import { Sparkle, X } from "lucide-react";
|
||||
// types
|
||||
import type { IUser, IIssue, ISearchIssueResponse } from "types";
|
||||
// components
|
||||
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
const aiService = new AIService();
|
||||
const fileService = new FileService();
|
||||
@ -123,8 +120,8 @@ export const DraftIssueForm: FC<IssueFormProps> = observer((props) => {
|
||||
const { workspaceSlug } = router.query;
|
||||
// store
|
||||
const {
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
// form info
|
||||
const {
|
||||
formState: { errors, isSubmitting },
|
||||
|
@ -2,8 +2,10 @@ import React, { FC, useState, useEffect, useRef } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { LayoutPanelTop, Sparkle, X } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication, useUser } from "hooks/store";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
// services
|
||||
import { AIService } from "services/ai.service";
|
||||
import { FileService } from "services/file.service";
|
||||
@ -25,15 +27,11 @@ import {
|
||||
} from "components/issues/select";
|
||||
import { CreateStateModal } from "components/states";
|
||||
import { CreateLabelModal } from "components/labels";
|
||||
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
|
||||
// ui
|
||||
import { Button, CustomMenu, Input, ToggleSwitch } from "@plane/ui";
|
||||
// icons
|
||||
import { LayoutPanelTop, Sparkle, X } from "lucide-react";
|
||||
// types
|
||||
import type { IIssue, ISearchIssueResponse } from "types";
|
||||
// components
|
||||
import { RichTextEditorWithRef } from "@plane/rich-text-editor";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
|
||||
const defaultValues: Partial<IIssue> = {
|
||||
project: "",
|
||||
@ -106,12 +104,11 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// store
|
||||
// store hooks
|
||||
const {
|
||||
user: userStore,
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
const user = userStore.currentUser;
|
||||
config: { envConfig },
|
||||
} = useApplication();
|
||||
const {} = useUser();
|
||||
// hooks
|
||||
const editorSuggestion = useEditorSuggestions();
|
||||
const { setToastAlert } = useToast();
|
||||
@ -183,12 +180,12 @@ export const IssueForm: FC<IssueFormProps> = observer((props) => {
|
||||
};
|
||||
|
||||
const handleAutoGenerateDescription = async () => {
|
||||
if (!workspaceSlug || !projectId || !user) return;
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
setIAmFeelingLucky(true);
|
||||
|
||||
aiService
|
||||
.createGptTask(workspaceSlug as string, projectId as string, {
|
||||
.createGptTask(workspaceSlug.toString(), projectId.toString(), {
|
||||
prompt: issueName,
|
||||
task: "Generate a proper description for this issue.",
|
||||
})
|
||||
|
@ -2,9 +2,8 @@ import { useEffect, useRef, useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useProject, useWorkspace } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import useKeypress from "hooks/use-keypress";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
@ -13,7 +12,7 @@ import { createIssuePayload } from "helpers/issue.helper";
|
||||
// icons
|
||||
import { PlusIcon } from "lucide-react";
|
||||
// types
|
||||
import { IIssue, IProject } from "types";
|
||||
import { IIssue } from "types";
|
||||
|
||||
type Props = {
|
||||
formKey: keyof IIssue;
|
||||
@ -58,25 +57,22 @@ const Inputs = (props: any) => {
|
||||
|
||||
export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
|
||||
const { formKey, groupId, prePopulatedData, quickAddCallback, viewId } = props;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||
|
||||
const { workspace: workspaceStore, project: projectStore } = useMobxStore();
|
||||
|
||||
// ref
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// store hooks
|
||||
const { getProjectById } = useProject();
|
||||
const { getWorkspaceBySlug } = useWorkspace();
|
||||
// refs
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
// states
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
// derived values
|
||||
const workspaceDetail = (workspaceSlug && workspaceStore.getWorkspaceBySlug(workspaceSlug)) || null;
|
||||
const projectDetail: IProject | null =
|
||||
(workspaceSlug && projectId && projectStore.getProjectById(workspaceSlug, projectId)) || null;
|
||||
const workspaceDetail = (workspaceSlug && getWorkspaceBySlug(workspaceSlug.toString())) || null;
|
||||
const projectDetail = projectId ? getProjectById(projectId.toString()) : null;
|
||||
|
||||
const {
|
||||
reset,
|
||||
@ -112,7 +108,7 @@ export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
|
||||
}, [errors, setToastAlert]);
|
||||
|
||||
const onSubmitHandler = async (formData: IIssue) => {
|
||||
if (isSubmitting || !groupId || !workspaceDetail || !projectDetail) return;
|
||||
if (isSubmitting || !groupId || !workspaceDetail || !projectDetail || !workspaceSlug || !projectId) return;
|
||||
|
||||
reset({ ...defaultValues });
|
||||
|
||||
@ -124,8 +120,8 @@ export const CalendarQuickAddIssueForm: React.FC<Props> = observer((props) => {
|
||||
try {
|
||||
quickAddCallback &&
|
||||
(await quickAddCallback(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
{
|
||||
...payload,
|
||||
},
|
||||
|
@ -1,31 +1,24 @@
|
||||
// next
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { Plus, PlusIcon } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication, useProject } from "hooks/store";
|
||||
// components
|
||||
import { EmptyState } from "components/common";
|
||||
// assets
|
||||
import emptyIssue from "public/empty-state/issue.svg";
|
||||
import emptyProject from "public/empty-state/project.svg";
|
||||
// icons
|
||||
import { Plus, PlusIcon } from "lucide-react";
|
||||
|
||||
export const GlobalViewEmptyState: React.FC = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
commandPalette: commandPaletteStore,
|
||||
project: projectStore,
|
||||
trackEvent: { setTrackElement },
|
||||
} = useMobxStore();
|
||||
|
||||
const projects = workspaceSlug ? projectStore.projects[workspaceSlug.toString()] : null;
|
||||
commandPalette: { toggleCreateIssueModal, toggleCreateProjectModal },
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
const { workspaceProjects } = useProject();
|
||||
|
||||
return (
|
||||
<div className="grid h-full w-full place-items-center">
|
||||
{!projects || projects?.length === 0 ? (
|
||||
{!workspaceProjects || workspaceProjects?.length === 0 ? (
|
||||
<EmptyState
|
||||
image={emptyProject}
|
||||
title="No projects yet"
|
||||
@ -35,7 +28,7 @@ export const GlobalViewEmptyState: React.FC = observer(() => {
|
||||
text: "New Project",
|
||||
onClick: () => {
|
||||
setTrackElement("ALL_ISSUES_EMPTY_STATE");
|
||||
commandPaletteStore.toggleCreateProjectModal(true);
|
||||
toggleCreateProjectModal(true);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
@ -49,7 +42,7 @@ export const GlobalViewEmptyState: React.FC = observer(() => {
|
||||
icon: <PlusIcon className="h-3 w-3" strokeWidth={2} />,
|
||||
onClick: () => {
|
||||
setTrackElement("ALL_ISSUES_EMPTY_STATE");
|
||||
commandPaletteStore.toggleCreateIssueModal(true);
|
||||
toggleCreateIssueModal(true);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// components
|
||||
import { EmptyState } from "components/common";
|
||||
// assets
|
||||
@ -9,10 +9,11 @@ import emptyIssue from "public/empty-state/issue.svg";
|
||||
import { EProjectStore } from "store_legacy/command-palette.store";
|
||||
|
||||
export const ProjectViewEmptyState: React.FC = observer(() => {
|
||||
// store hooks
|
||||
const {
|
||||
commandPalette: commandPaletteStore,
|
||||
trackEvent: { setTrackElement },
|
||||
} = useMobxStore();
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
|
||||
return (
|
||||
<div className="grid h-full w-full place-items-center">
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useApplication } from "hooks/store";
|
||||
// components
|
||||
import { NewEmptyState } from "components/common/new-empty-state";
|
||||
// assets
|
||||
@ -9,10 +9,11 @@ import emptyIssue from "public/empty-state/empty_issues.webp";
|
||||
import { EProjectStore } from "store_legacy/command-palette.store";
|
||||
|
||||
export const ProjectEmptyState: React.FC = observer(() => {
|
||||
// store hooks
|
||||
const {
|
||||
commandPalette: commandPaletteStore,
|
||||
trackEvent: { setTrackElement },
|
||||
} = useMobxStore();
|
||||
eventTracker: { setTrackElement },
|
||||
} = useApplication();
|
||||
|
||||
return (
|
||||
<div className="grid h-full w-full place-items-center">
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
// components
|
||||
import { IssueGanttBlock, IssuePeekOverview } from "components/issues";
|
||||
import {
|
||||
@ -39,13 +39,13 @@ interface IBaseGanttRoot {
|
||||
|
||||
export const BaseGanttRoot: React.FC<IBaseGanttRoot> = observer((props: IBaseGanttRoot) => {
|
||||
const { issueFiltersStore, issueStore, viewId } = props;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, peekIssueId, peekProjectId } = router.query;
|
||||
|
||||
// store hooks
|
||||
const {
|
||||
user: { currentProjectRole },
|
||||
} = useMobxStore();
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
|
||||
const appliedDisplayFilters = issueFiltersStore.issueFilters?.displayFilters;
|
||||
|
||||
|
@ -3,12 +3,10 @@ import { useRouter } from "next/router";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useProject, useWorkspace } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import useKeypress from "hooks/use-keypress";
|
||||
import useProjectDetails from "hooks/use-project-details";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
// helpers
|
||||
import { renderDateFormat } from "helpers/date-time.helper";
|
||||
@ -55,16 +53,13 @@ const Inputs = (props: any) => {
|
||||
|
||||
export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
||||
const { prePopulatedData, quickAddCallback, viewId } = props;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||
|
||||
// store
|
||||
const { workspace: workspaceStore } = useMobxStore();
|
||||
|
||||
const { projectDetails } = useProjectDetails();
|
||||
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// store hooks
|
||||
const { getWorkspaceBySlug } = useWorkspace();
|
||||
const { currentProjectDetails } = useProject();
|
||||
// form info
|
||||
const {
|
||||
reset,
|
||||
handleSubmit,
|
||||
@ -87,7 +82,7 @@ export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
// derived values
|
||||
const workspaceDetail = workspaceStore.getWorkspaceBySlug(workspaceSlug?.toString()!);
|
||||
const workspaceDetail = getWorkspaceBySlug(workspaceSlug?.toString()!);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) reset({ ...defaultValues });
|
||||
@ -113,7 +108,7 @@ export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
||||
// resetting the form so that user can add another issue quickly
|
||||
reset({ ...defaultValues, ...(prePopulatedData ?? {}) });
|
||||
|
||||
const payload = createIssuePayload(workspaceDetail!, projectDetails!, {
|
||||
const payload = createIssuePayload(workspaceDetail!, currentProjectDetails!, {
|
||||
...(prePopulatedData ?? {}),
|
||||
...formData,
|
||||
start_date: renderDateFormat(new Date()),
|
||||
@ -122,7 +117,7 @@ export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
||||
|
||||
try {
|
||||
if (quickAddCallback) {
|
||||
await quickAddCallback(workspaceSlug, projectId, payload, viewId);
|
||||
await quickAddCallback(workspaceSlug.toString(), projectId.toString(), payload, viewId);
|
||||
}
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
@ -152,7 +147,7 @@ export const GanttInlineCreateIssueForm: React.FC<Props> = observer((props) => {
|
||||
onSubmit={handleSubmit(onSubmitHandler)}
|
||||
>
|
||||
<div className="h-3 w-3 flex-shrink-0 rounded-full border border-custom-border-1000" />
|
||||
<h4 className="text-xs text-custom-text-400">{projectDetails?.identifier ?? "..."}</h4>
|
||||
<h4 className="text-xs text-custom-text-400">{currentProjectDetails?.identifier ?? "..."}</h4>
|
||||
<Inputs register={register} setFocus={setFocus} />
|
||||
</form>
|
||||
)}
|
||||
|
@ -2,8 +2,9 @@ import { FC, useCallback, useState } from "react";
|
||||
import { DragDropContext, DragStart, DraggableLocation, DropResult, Droppable } from "@hello-pangea/dnd";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { Spinner } from "@plane/ui";
|
||||
// types
|
||||
@ -24,10 +25,6 @@ import {
|
||||
} from "store_legacy/issues";
|
||||
import { IQuickActionProps } from "../list/list-view-types";
|
||||
import { IIssueKanBanViewStore } from "store_legacy/issue";
|
||||
// hooks
|
||||
import useToast from "hooks/use-toast";
|
||||
// constants
|
||||
import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
|
||||
//components
|
||||
import { KanBan } from "./default";
|
||||
import { KanBanSwimLanes } from "./swimlanes";
|
||||
@ -94,20 +91,13 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, peekIssueId, peekProjectId } = router.query;
|
||||
// mobx store
|
||||
// store hooks
|
||||
const {
|
||||
project: { workspaceProjects },
|
||||
projectLabel: { projectLabels },
|
||||
projectMember: { projectMembers },
|
||||
projectState: projectStateStore,
|
||||
user: userStore,
|
||||
} = useMobxStore();
|
||||
|
||||
// hooks
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
const { currentProjectRole } = userStore;
|
||||
|
||||
const issues = issueStore?.getIssues || {};
|
||||
const issueIds = issueStore?.getIssuesIds || [];
|
||||
|
||||
@ -133,12 +123,15 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
|
||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||
|
||||
const canEditProperties = (projectId: string | undefined) => {
|
||||
const isEditingAllowedBasedOnProject =
|
||||
canEditPropertiesBasedOnProject && projectId ? canEditPropertiesBasedOnProject(projectId) : isEditingAllowed;
|
||||
const canEditProperties = useCallback(
|
||||
(projectId: string | undefined) => {
|
||||
const isEditingAllowedBasedOnProject =
|
||||
canEditPropertiesBasedOnProject && projectId ? canEditPropertiesBasedOnProject(projectId) : isEditingAllowed;
|
||||
|
||||
return enableInlineEditing && isEditingAllowedBasedOnProject;
|
||||
};
|
||||
return enableInlineEditing && isEditingAllowedBasedOnProject;
|
||||
},
|
||||
[canEditPropertiesBasedOnProject, enableInlineEditing, isEditingAllowed]
|
||||
);
|
||||
|
||||
const onDragStart = (dragStart: DragStart) => {
|
||||
setDragState({
|
||||
@ -185,7 +178,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
};
|
||||
|
||||
const handleIssues = useCallback(
|
||||
async (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => {
|
||||
async (issue: IIssue, action: EIssueActions) => {
|
||||
if (issueActions[action]) {
|
||||
await issueActions[action]!(issue);
|
||||
}
|
||||
@ -193,6 +186,23 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
[issueActions]
|
||||
);
|
||||
|
||||
const renderQuickActions = useCallback(
|
||||
(issue: IIssue, customActionButton?: React.ReactElement) => (
|
||||
<QuickActions
|
||||
customActionButton={customActionButton}
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(issue, EIssueActions.DELETE)}
|
||||
handleUpdate={
|
||||
issueActions[EIssueActions.UPDATE] ? async (data) => handleIssues(data, EIssueActions.UPDATE) : undefined
|
||||
}
|
||||
handleRemoveFromView={
|
||||
issueActions[EIssueActions.REMOVE] ? async () => handleIssues(issue, EIssueActions.REMOVE) : undefined
|
||||
}
|
||||
/>
|
||||
),
|
||||
[issueActions, handleIssues]
|
||||
);
|
||||
|
||||
const handleDeleteIssue = async () => {
|
||||
if (!handleDragDrop) return;
|
||||
await handleDragDrop(dragState.source, dragState.destination, sub_group_by, group_by, issues, issueIds).finally(
|
||||
@ -207,10 +217,6 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
kanbanViewStore.handleKanBanToggle(toggle, value);
|
||||
};
|
||||
|
||||
const states = projectStateStore?.projectStates || null;
|
||||
const priorities = ISSUE_PRIORITIES || null;
|
||||
const stateGroups = ISSUE_STATE_GROUPS || null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DeleteIssueModal
|
||||
@ -256,37 +262,13 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(sub_group_by, group_by, issue, customActionButton) => (
|
||||
<QuickActions
|
||||
customActionButton={customActionButton}
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, EIssueActions.DELETE)}
|
||||
handleUpdate={
|
||||
issueActions[EIssueActions.UPDATE]
|
||||
? async (data) => handleIssues(sub_group_by, group_by, data, EIssueActions.UPDATE)
|
||||
: undefined
|
||||
}
|
||||
handleRemoveFromView={
|
||||
issueActions[EIssueActions.REMOVE]
|
||||
? async () => handleIssues(sub_group_by, group_by, issue, EIssueActions.REMOVE)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
quickActions={renderQuickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanbanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={projectLabels}
|
||||
members={projectMembers?.map((m) => m.member) ?? null}
|
||||
projects={workspaceProjects}
|
||||
enableQuickIssueCreate={enableQuickAdd}
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
isDragStarted={isDragStarted}
|
||||
quickAddCallback={issueStore?.quickAddIssue}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
|
||||
@ -302,32 +284,10 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={(sub_group_by, group_by, issue, customActionButton) => (
|
||||
<QuickActions
|
||||
customActionButton={customActionButton}
|
||||
issue={issue}
|
||||
handleDelete={async () => handleIssues(sub_group_by, group_by, issue, EIssueActions.DELETE)}
|
||||
handleUpdate={
|
||||
issueActions[EIssueActions.UPDATE]
|
||||
? async (data) => handleIssues(sub_group_by, group_by, data, EIssueActions.UPDATE)
|
||||
: undefined
|
||||
}
|
||||
handleRemoveFromView={
|
||||
issueActions[EIssueActions.REMOVE]
|
||||
? async () => handleIssues(sub_group_by, group_by, issue, EIssueActions.REMOVE)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
quickActions={renderQuickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanbanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={projectLabels}
|
||||
members={projectMembers?.map((m) => m.member) ?? null}
|
||||
projects={workspaceProjects}
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
|
||||
@ -346,9 +306,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
workspaceSlug={workspaceSlug.toString()}
|
||||
projectId={peekProjectId.toString()}
|
||||
issueId={peekIssueId.toString()}
|
||||
handleIssue={async (issueToUpdate) =>
|
||||
await handleIssues(sub_group_by, group_by, issueToUpdate as IIssue, EIssueActions.UPDATE)
|
||||
}
|
||||
handleIssue={async (issueToUpdate) => await handleIssues(issueToUpdate as IIssue, EIssueActions.UPDATE)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
@ -17,31 +17,28 @@ interface IssueBlockProps {
|
||||
issue: IIssue;
|
||||
isDragDisabled: boolean;
|
||||
showEmptyGroup: boolean;
|
||||
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
|
||||
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
|
||||
handleIssues: (issue: IIssue, action: EIssueActions) => void;
|
||||
quickActions: (issue: IIssue) => React.ReactNode;
|
||||
displayProperties: IIssueDisplayProperties | null;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
}
|
||||
|
||||
interface IssueDetailsBlockProps {
|
||||
sub_group_id: string;
|
||||
columnId: string;
|
||||
issue: IIssue;
|
||||
showEmptyGroup: boolean;
|
||||
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
|
||||
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
|
||||
handleIssues: (issue: IIssue, action: EIssueActions) => void;
|
||||
quickActions: (issue: IIssue) => React.ReactNode;
|
||||
displayProperties: IIssueDisplayProperties | null;
|
||||
isReadOnly: boolean;
|
||||
}
|
||||
|
||||
const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = (props) => {
|
||||
const { sub_group_id, columnId, issue, showEmptyGroup, handleIssues, quickActions, displayProperties, isReadOnly } =
|
||||
props;
|
||||
const { issue, showEmptyGroup, handleIssues, quickActions, displayProperties, isReadOnly } = props;
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const updateIssue = (sub_group_by: string | null, group_by: string | null, issueToUpdate: IIssue) => {
|
||||
if (issueToUpdate) handleIssues(sub_group_by, group_by, issueToUpdate, EIssueActions.UPDATE);
|
||||
const updateIssue = (issueToUpdate: IIssue) => {
|
||||
if (issueToUpdate) handleIssues(issueToUpdate, EIssueActions.UPDATE);
|
||||
};
|
||||
|
||||
const handleIssuePeekOverview = () => {
|
||||
@ -60,13 +57,7 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = (props) => {
|
||||
<div className="line-clamp-1 text-xs text-custom-text-300">
|
||||
{issue.project_detail.identifier}-{issue.sequence_id}
|
||||
</div>
|
||||
<div className="absolute -top-1 right-0 hidden group-hover/kanban-block:block">
|
||||
{quickActions(
|
||||
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
|
||||
!columnId && columnId === "null" ? null : columnId,
|
||||
issue
|
||||
)}
|
||||
</div>
|
||||
<div className="absolute -top-1 right-0 hidden group-hover/kanban-block:block">{quickActions(issue)}</div>
|
||||
</div>
|
||||
)}
|
||||
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
|
||||
@ -76,8 +67,6 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = (props) => {
|
||||
</Tooltip>
|
||||
<div>
|
||||
<KanBanProperties
|
||||
sub_group_id={sub_group_id}
|
||||
columnId={columnId}
|
||||
issue={issue}
|
||||
handleIssues={updateIssue}
|
||||
displayProperties={displayProperties}
|
||||
@ -138,8 +127,6 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
|
||||
} ${snapshot.isDragging ? `border-custom-primary-100` : `border-transparent`}`}
|
||||
>
|
||||
<KanbanIssueMemoBlock
|
||||
sub_group_id={sub_group_id}
|
||||
columnId={columnId}
|
||||
issue={issue}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
|
@ -1,8 +1,10 @@
|
||||
// components
|
||||
import { KanbanIssueBlock } from "components/issues";
|
||||
import { memo } from "react";
|
||||
//types
|
||||
import { IIssueDisplayProperties, IIssue } from "types";
|
||||
import { EIssueActions } from "../types";
|
||||
import { IIssueResponse } from "store_legacy/issues/types";
|
||||
// components
|
||||
import { KanbanIssueBlock } from "components/issues";
|
||||
|
||||
interface IssueBlocksListProps {
|
||||
sub_group_id: string;
|
||||
@ -11,18 +13,13 @@ interface IssueBlocksListProps {
|
||||
issueIds: string[];
|
||||
isDragDisabled: boolean;
|
||||
showEmptyGroup: boolean;
|
||||
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
|
||||
quickActions: (
|
||||
sub_group_by: string | null,
|
||||
group_by: string | null,
|
||||
issue: IIssue,
|
||||
customActionButton?: React.ReactElement
|
||||
) => React.ReactNode;
|
||||
handleIssues: (issue: IIssue, action: EIssueActions) => void;
|
||||
quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
displayProperties: IIssueDisplayProperties | null;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
}
|
||||
|
||||
export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = (props) => {
|
||||
const KanbanIssueBlocksListMemo: React.FC<IssueBlocksListProps> = (props) => {
|
||||
const {
|
||||
sub_group_id,
|
||||
columnId,
|
||||
@ -62,13 +59,9 @@ export const KanbanIssueBlocksList: React.FC<IssueBlocksListProps> = (props) =>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
!isDragDisabled && (
|
||||
<div className="absolute left-0 top-0 flex h-full w-full items-center justify-center">
|
||||
{/* <div className="text-custom-text-300 text-sm">Drop here</div> */}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const KanbanIssueBlocksList = memo(KanbanIssueBlocksListMemo);
|
||||
|
@ -1,15 +1,14 @@
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Droppable } from "@hello-pangea/dnd";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { KanBanGroupByHeaderRoot } from "./headers/group-by-root";
|
||||
import { KanbanIssueBlocksList, KanBanQuickAddIssueForm } from "components/issues";
|
||||
import { HeaderGroupByCard } from "./headers/group-by-card";
|
||||
import { KanbanGroup } from "./kanban-group";
|
||||
// types
|
||||
import { IIssueDisplayProperties, IIssue, IState } from "types";
|
||||
import { IIssueDisplayProperties, IIssue } from "types";
|
||||
// constants
|
||||
import { getValueFromObject } from "constants/issue";
|
||||
import { columnTypes, getKanbanColumns, IKanbanColumn } from "./utils";
|
||||
import { EIssueActions } from "../types";
|
||||
import { IIssueResponse, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "store_legacy/issues/types";
|
||||
import { EProjectStore } from "store_legacy/command-palette.store";
|
||||
@ -19,25 +18,15 @@ export interface IGroupByKanBan {
|
||||
issueIds: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
order_by: string | null;
|
||||
sub_group_id: string;
|
||||
list: any;
|
||||
listKey: string;
|
||||
states: IState[] | null;
|
||||
isDragDisabled: boolean;
|
||||
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
|
||||
handleIssues: (issue: IIssue, action: EIssueActions) => void;
|
||||
showEmptyGroup: boolean;
|
||||
quickActions: (
|
||||
sub_group_by: string | null,
|
||||
group_by: string | null,
|
||||
issue: IIssue,
|
||||
customActionButton?: React.ReactElement
|
||||
) => React.ReactNode;
|
||||
quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
displayProperties: IIssueDisplayProperties | null;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
enableQuickIssueCreate?: boolean;
|
||||
isDragStarted?: boolean;
|
||||
quickAddCallback?: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
@ -57,11 +46,7 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
||||
issueIds,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
order_by,
|
||||
sub_group_id = "null",
|
||||
list,
|
||||
listKey,
|
||||
isDragDisabled,
|
||||
handleIssues,
|
||||
showEmptyGroup,
|
||||
@ -70,8 +55,6 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
enableQuickIssueCreate,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
isDragStarted,
|
||||
quickAddCallback,
|
||||
viewId,
|
||||
disableIssueCreation,
|
||||
@ -80,102 +63,63 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
||||
canEditProperties,
|
||||
} = props;
|
||||
|
||||
const verticalAlignPosition = (_list: any) =>
|
||||
kanBanToggle?.groupByHeaderMinMax.includes(getValueFromObject(_list, listKey) as string);
|
||||
const { project, projectLabel, projectMember, projectState } = useMobxStore();
|
||||
|
||||
const list = getKanbanColumns(group_by as columnTypes, project, projectLabel, projectMember, projectState);
|
||||
|
||||
if (!list) return null;
|
||||
|
||||
const verticalAlignPosition = (_list: IKanbanColumn) => kanBanToggle?.groupByHeaderMinMax.includes(_list.id);
|
||||
|
||||
return (
|
||||
<div className="relative flex h-full w-full gap-3">
|
||||
{list &&
|
||||
list.length > 0 &&
|
||||
list.map((_list: any) => (
|
||||
<div
|
||||
className={`relative flex flex-shrink-0 flex-col ${!verticalAlignPosition(_list) ? `w-[340px]` : ``} group`}
|
||||
>
|
||||
{sub_group_by === null && (
|
||||
<div className="sticky top-0 z-[2] w-full flex-shrink-0 bg-custom-background-90 py-1">
|
||||
<KanBanGroupByHeaderRoot
|
||||
column_id={getValueFromObject(_list, listKey) as string}
|
||||
column_value={_list}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
issues_count={issueIds?.[getValueFromObject(_list, listKey) as string]?.length || 0}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
list.map((_list: IKanbanColumn) => {
|
||||
const verticalPosition = verticalAlignPosition(_list);
|
||||
|
||||
<div
|
||||
className={`${
|
||||
verticalAlignPosition(_list) ? `min-h-[150px] w-[0px] overflow-hidden` : `w-full transition-all`
|
||||
}`}
|
||||
>
|
||||
<Droppable droppableId={`${getValueFromObject(_list, listKey) as string}__${sub_group_id}`}>
|
||||
{(provided: any, snapshot: any) => (
|
||||
<div
|
||||
className={`relative h-full w-full transition-all ${
|
||||
snapshot.isDraggingOver ? `bg-custom-background-80` : ``
|
||||
}`}
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
{issues && !verticalAlignPosition(_list) ? (
|
||||
<KanbanIssueBlocksList
|
||||
sub_group_id={sub_group_id}
|
||||
columnId={getValueFromObject(_list, listKey) as string}
|
||||
issues={issues}
|
||||
issueIds={issueIds?.[getValueFromObject(_list, listKey) as string] || []}
|
||||
isDragDisabled={isDragDisabled}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
) : (
|
||||
isDragDisabled && (
|
||||
<div className="absolute left-0 top-0 flex h-full w-full items-center justify-center text-sm">
|
||||
{/* <div className="text-custom-text-300 text-sm">Drop here</div> */}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
|
||||
<div className="sticky bottom-0 z-[0] w-full flex-shrink-0 bg-custom-background-90 py-1">
|
||||
{enableQuickIssueCreate && !disableIssueCreation && (
|
||||
<KanBanQuickAddIssueForm
|
||||
formKey="name"
|
||||
groupId={getValueFromObject(_list, listKey) as string}
|
||||
subGroupId={sub_group_id}
|
||||
prePopulatedData={{
|
||||
...(group_by && { [group_by]: getValueFromObject(_list, listKey) }),
|
||||
...(sub_group_by && sub_group_id !== "null" && { [sub_group_by]: sub_group_id }),
|
||||
}}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
return (
|
||||
<div className={`relative flex flex-shrink-0 flex-col ${!verticalPosition ? `w-[340px]` : ``} group`}>
|
||||
{sub_group_by === null && (
|
||||
<div className="sticky top-0 z-[2] w-full flex-shrink-0 bg-custom-background-90 py-1">
|
||||
<HeaderGroupByCard
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
column_id={_list.id}
|
||||
icon={_list.Icon}
|
||||
title={_list.name}
|
||||
count={issueIds?.[_list.id]?.length || 0}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
issuePayload={_list.payload}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* {isDragStarted && isDragDisabled && (
|
||||
<div className="invisible group-hover:visible transition-all text-sm absolute top-12 bottom-10 left-0 right-0 bg-custom-background-100/40 text-center">
|
||||
<div className="rounded inline-flex mt-80 h-8 px-3 justify-center items-center bg-custom-background-80 text-custom-text-100 font-medium">
|
||||
{`This board is ordered by "${replaceUnderscoreIfSnakeCase(
|
||||
order_by ? (order_by[0] === "-" ? order_by.slice(1) : order_by) : "created_at"
|
||||
)}"`}
|
||||
</div>
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
))}
|
||||
)}
|
||||
<KanbanGroup
|
||||
groupId={_list.id}
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
isDragDisabled={isDragDisabled}
|
||||
handleIssues={handleIssues}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
canEditProperties={canEditProperties}
|
||||
verticalPosition={verticalPosition}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@ -185,27 +129,14 @@ export interface IKanBan {
|
||||
issueIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
order_by: string | null;
|
||||
sub_group_id?: string;
|
||||
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
|
||||
quickActions: (
|
||||
sub_group_by: string | null,
|
||||
group_by: string | null,
|
||||
issue: IIssue,
|
||||
customActionButton?: React.ReactElement
|
||||
) => React.ReactNode;
|
||||
handleIssues: (issue: IIssue, action: EIssueActions) => void;
|
||||
quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
displayProperties: IIssueDisplayProperties | null;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
showEmptyGroup: boolean;
|
||||
states: any;
|
||||
stateGroups: any;
|
||||
priorities: any;
|
||||
labels: any;
|
||||
members: any;
|
||||
projects: any;
|
||||
enableQuickIssueCreate?: boolean;
|
||||
isDragStarted?: boolean;
|
||||
quickAddCallback?: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
@ -225,7 +156,6 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
issueIds,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
order_by,
|
||||
sub_group_id = "null",
|
||||
handleIssues,
|
||||
quickActions,
|
||||
@ -233,14 +163,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
showEmptyGroup,
|
||||
states,
|
||||
stateGroups,
|
||||
priorities,
|
||||
labels,
|
||||
members,
|
||||
projects,
|
||||
enableQuickIssueCreate,
|
||||
isDragStarted,
|
||||
quickAddCallback,
|
||||
viewId,
|
||||
disableIssueCreation,
|
||||
@ -253,208 +176,27 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
|
||||
return (
|
||||
<div className="relative h-full w-full">
|
||||
{group_by && group_by === "project" && (
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
list={projects}
|
||||
listKey={`id`}
|
||||
states={states}
|
||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "state" && (
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
list={states}
|
||||
listKey={`id`}
|
||||
states={states}
|
||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "state_detail.group" && (
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
list={stateGroups}
|
||||
listKey={`key`}
|
||||
states={states}
|
||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "priority" && (
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
list={priorities}
|
||||
listKey={`key`}
|
||||
states={states}
|
||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "labels" && (
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
list={labels ? [...labels, { id: "None", name: "None" }] : labels}
|
||||
listKey={`id`}
|
||||
states={states}
|
||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "assignees" && (
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
list={members ? [...members, { id: "None", display_name: "None" }] : members}
|
||||
listKey={`id`}
|
||||
states={states}
|
||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "created_by" && (
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
list={members}
|
||||
listKey={`id`}
|
||||
states={states}
|
||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
)}
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
group_by={group_by}
|
||||
sub_group_by={sub_group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
isDragDisabled={!issueKanBanViewStore?.canUserDragDrop}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -1,74 +0,0 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { HeaderGroupByCard } from "./group-by-card";
|
||||
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
||||
// ui
|
||||
import { Avatar } from "@plane/ui";
|
||||
import { EProjectStore } from "store_legacy/command-palette.store";
|
||||
import { IIssue } from "types";
|
||||
|
||||
export interface IAssigneesHeader {
|
||||
column_id: string;
|
||||
column_value: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
header_type: "group_by" | "sub_group_by";
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
}
|
||||
|
||||
export const Icon = ({ user }: any) => <Avatar name={user.display_name} src={user.avatar} size="md" />;
|
||||
|
||||
export const AssigneesHeader: FC<IAssigneesHeader> = observer((props) => {
|
||||
const {
|
||||
column_id,
|
||||
column_value,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
header_type,
|
||||
issues_count,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
disableIssueCreation,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
} = props;
|
||||
|
||||
const assignee = column_value ?? null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{assignee &&
|
||||
(sub_group_by && header_type === "sub_group_by" ? (
|
||||
<HeaderSubGroupByCard
|
||||
column_id={column_id}
|
||||
icon={<Icon user={assignee} />}
|
||||
title={assignee?.display_name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<HeaderGroupByCard
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
column_id={column_id}
|
||||
icon={<Icon user={assignee} />}
|
||||
title={assignee?.display_name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
issuePayload={{ assignees: [assignee?.id] }}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
@ -1,71 +0,0 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { HeaderGroupByCard } from "./group-by-card";
|
||||
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
||||
import { Icon } from "./assignee";
|
||||
import { EProjectStore } from "store_legacy/command-palette.store";
|
||||
import { IIssue } from "types";
|
||||
|
||||
export interface ICreatedByHeader {
|
||||
column_id: string;
|
||||
column_value: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
header_type: "group_by" | "sub_group_by";
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
}
|
||||
|
||||
export const CreatedByHeader: FC<ICreatedByHeader> = observer((props) => {
|
||||
const {
|
||||
column_id,
|
||||
column_value,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
header_type,
|
||||
issues_count,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
disableIssueCreation,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
} = props;
|
||||
|
||||
const createdBy = column_value ?? null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{createdBy &&
|
||||
(sub_group_by && header_type === "sub_group_by" ? (
|
||||
<HeaderSubGroupByCard
|
||||
column_id={column_id}
|
||||
icon={<Icon user={createdBy} />}
|
||||
title={createdBy?.display_name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<HeaderGroupByCard
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
column_id={column_id}
|
||||
icon={<Icon user={createdBy} />}
|
||||
title={createdBy?.display_name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
issuePayload={{ created_by: createdBy?.id }}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
@ -1,149 +0,0 @@
|
||||
// components
|
||||
import { ProjectHeader } from "./project";
|
||||
import { StateHeader } from "./state";
|
||||
import { StateGroupHeader } from "./state-group";
|
||||
import { AssigneesHeader } from "./assignee";
|
||||
import { PriorityHeader } from "./priority";
|
||||
import { LabelHeader } from "./label";
|
||||
import { CreatedByHeader } from "./created_by";
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { EProjectStore } from "store_legacy/command-palette.store";
|
||||
import { IIssue } from "types";
|
||||
|
||||
export interface IKanBanGroupByHeaderRoot {
|
||||
column_id: string;
|
||||
column_value: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
}
|
||||
|
||||
export const KanBanGroupByHeaderRoot: React.FC<IKanBanGroupByHeaderRoot> = observer(
|
||||
({
|
||||
column_id,
|
||||
column_value,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
issues_count,
|
||||
kanBanToggle,
|
||||
disableIssueCreation,
|
||||
handleKanBanToggle,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
}) => (
|
||||
<>
|
||||
{group_by && group_by === "project" && (
|
||||
<ProjectHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "state" && (
|
||||
<StateHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
{group_by && group_by === "state_detail.group" && (
|
||||
<StateGroupHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
{group_by && group_by === "priority" && (
|
||||
<PriorityHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
{group_by && group_by === "labels" && (
|
||||
<LabelHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
{group_by && group_by === "assignees" && (
|
||||
<AssigneesHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
{group_by && group_by === "created_by" && (
|
||||
<CreatedByHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
);
|
@ -1,74 +0,0 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { HeaderGroupByCard } from "./group-by-card";
|
||||
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
||||
import { EProjectStore } from "store_legacy/command-palette.store";
|
||||
import { IIssue } from "types";
|
||||
|
||||
export interface ILabelHeader {
|
||||
column_id: string;
|
||||
column_value: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
header_type: "group_by" | "sub_group_by";
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
}
|
||||
|
||||
const Icon = ({ color }: any) => (
|
||||
<div className="h-[12px] w-[12px] rounded-full" style={{ backgroundColor: color ? color : "#666" }} />
|
||||
);
|
||||
|
||||
export const LabelHeader: FC<ILabelHeader> = observer((props) => {
|
||||
const {
|
||||
column_id,
|
||||
column_value,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
header_type,
|
||||
issues_count,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
disableIssueCreation,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
} = props;
|
||||
|
||||
const label = column_value ?? null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{label &&
|
||||
(sub_group_by && header_type === "sub_group_by" ? (
|
||||
<HeaderSubGroupByCard
|
||||
column_id={column_id}
|
||||
icon={<Icon color={label?.color} />}
|
||||
title={label?.name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<HeaderGroupByCard
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
column_id={column_id}
|
||||
icon={<Icon />}
|
||||
title={label?.name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
issuePayload={{ labels: [label?.id] }}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
@ -1,73 +0,0 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { HeaderGroupByCard } from "./group-by-card";
|
||||
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
||||
|
||||
// Icons
|
||||
import { PriorityIcon } from "@plane/ui";
|
||||
import { EProjectStore } from "store_legacy/command-palette.store";
|
||||
import { IIssue } from "types";
|
||||
|
||||
export interface IPriorityHeader {
|
||||
column_id: string;
|
||||
column_value: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
header_type: "group_by" | "sub_group_by";
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
}
|
||||
|
||||
export const PriorityHeader: FC<IPriorityHeader> = observer((props) => {
|
||||
const {
|
||||
column_id,
|
||||
column_value,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
header_type,
|
||||
issues_count,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
disableIssueCreation,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
} = props;
|
||||
|
||||
const priority = column_value || null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{priority &&
|
||||
(sub_group_by && header_type === "sub_group_by" ? (
|
||||
<HeaderSubGroupByCard
|
||||
column_id={column_id}
|
||||
icon={<PriorityIcon priority={priority?.key} />}
|
||||
title={priority?.title || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<HeaderGroupByCard
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
column_id={column_id}
|
||||
icon={<PriorityIcon priority={priority?.key} />}
|
||||
title={priority?.title || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
issuePayload={{ priority: priority?.key }}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
@ -1,74 +0,0 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { HeaderGroupByCard } from "./group-by-card";
|
||||
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
||||
// emoji helper
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { EProjectStore } from "store_legacy/command-palette.store";
|
||||
import { IIssue } from "types";
|
||||
|
||||
export interface IProjectHeader {
|
||||
column_id: string;
|
||||
column_value: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
header_type: "group_by" | "sub_group_by";
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
}
|
||||
|
||||
const Icon = ({ emoji }: any) => <div className="h-6 w-6">{renderEmoji(emoji)}</div>;
|
||||
|
||||
export const ProjectHeader: FC<IProjectHeader> = observer((props) => {
|
||||
const {
|
||||
column_id,
|
||||
column_value,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
header_type,
|
||||
issues_count,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
disableIssueCreation,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
} = props;
|
||||
|
||||
const project = column_value ?? null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{project &&
|
||||
(sub_group_by && header_type === "sub_group_by" ? (
|
||||
<HeaderSubGroupByCard
|
||||
column_id={column_id}
|
||||
icon={<Icon emoji={project?.emoji} />}
|
||||
title={project?.name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<HeaderGroupByCard
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
column_id={column_id}
|
||||
icon={<Icon emoji={project?.emoji} />}
|
||||
title={project?.name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
issuePayload={{ project: project?.id }}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
@ -1,77 +0,0 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { HeaderGroupByCard } from "./group-by-card";
|
||||
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
||||
import { StateGroupIcon } from "@plane/ui";
|
||||
import { EProjectStore } from "store_legacy/command-palette.store";
|
||||
import { IIssue } from "types";
|
||||
|
||||
export interface IStateGroupHeader {
|
||||
column_id: string;
|
||||
column_value: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
header_type: "group_by" | "sub_group_by";
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
}
|
||||
|
||||
export const Icon = ({ stateGroup, color }: { stateGroup: any; color?: any }) => (
|
||||
<div className="h-3.5 w-3.5 rounded-full">
|
||||
<StateGroupIcon stateGroup={stateGroup} color={color || null} width="14" height="14" />
|
||||
</div>
|
||||
);
|
||||
|
||||
export const StateGroupHeader: FC<IStateGroupHeader> = observer((props) => {
|
||||
const {
|
||||
column_id,
|
||||
column_value,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
header_type,
|
||||
issues_count,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
disableIssueCreation,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
} = props;
|
||||
|
||||
const stateGroup = column_value || null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{stateGroup &&
|
||||
(sub_group_by && header_type === "sub_group_by" ? (
|
||||
<HeaderSubGroupByCard
|
||||
column_id={column_id}
|
||||
icon={<Icon stateGroup={stateGroup?.key} />}
|
||||
title={stateGroup?.key || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<HeaderGroupByCard
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
column_id={column_id}
|
||||
icon={<Icon stateGroup={stateGroup?.key} />}
|
||||
title={stateGroup?.key || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
issuePayload={{}}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
@ -1,71 +0,0 @@
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { HeaderGroupByCard } from "./group-by-card";
|
||||
import { HeaderSubGroupByCard } from "./sub-group-by-card";
|
||||
import { Icon } from "./state-group";
|
||||
import { EProjectStore } from "store_legacy/command-palette.store";
|
||||
import { IIssue } from "types";
|
||||
|
||||
export interface IStateHeader {
|
||||
column_id: string;
|
||||
column_value: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
header_type: "group_by" | "sub_group_by";
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
}
|
||||
|
||||
export const StateHeader: FC<IStateHeader> = observer((props) => {
|
||||
const {
|
||||
column_id,
|
||||
column_value,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
header_type,
|
||||
issues_count,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
disableIssueCreation,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
} = props;
|
||||
|
||||
const state = column_value ?? null;
|
||||
|
||||
return (
|
||||
<>
|
||||
{state &&
|
||||
(sub_group_by && header_type === "sub_group_by" ? (
|
||||
<HeaderSubGroupByCard
|
||||
column_id={column_id}
|
||||
icon={<Icon stateGroup={state?.group} color={state?.color} />}
|
||||
title={state?.name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
/>
|
||||
) : (
|
||||
<HeaderGroupByCard
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
column_id={column_id}
|
||||
icon={<Icon stateGroup={state?.group} color={state?.color} />}
|
||||
title={state?.name || ""}
|
||||
count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
issuePayload={{ state: state?.id }}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
@ -1,134 +0,0 @@
|
||||
// mobx
|
||||
import { observer } from "mobx-react-lite";
|
||||
// components
|
||||
import { StateHeader } from "./state";
|
||||
import { StateGroupHeader } from "./state-group";
|
||||
import { AssigneesHeader } from "./assignee";
|
||||
import { PriorityHeader } from "./priority";
|
||||
import { LabelHeader } from "./label";
|
||||
import { CreatedByHeader } from "./created_by";
|
||||
import { EProjectStore } from "store_legacy/command-palette.store";
|
||||
import { IIssue } from "types";
|
||||
|
||||
export interface IKanBanSubGroupByHeaderRoot {
|
||||
column_id: string;
|
||||
column_value: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
issues_count: number;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
}
|
||||
|
||||
export const KanBanSubGroupByHeaderRoot: React.FC<IKanBanSubGroupByHeaderRoot> = observer((props) => {
|
||||
const {
|
||||
column_id,
|
||||
column_value,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
issues_count,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
disableIssueCreation,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
{sub_group_by && sub_group_by === "state" && (
|
||||
<StateHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`sub_group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
{sub_group_by && sub_group_by === "state_detail.group" && (
|
||||
<StateGroupHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`sub_group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
{sub_group_by && sub_group_by === "priority" && (
|
||||
<PriorityHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`sub_group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
{sub_group_by && sub_group_by === "labels" && (
|
||||
<LabelHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`sub_group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
{sub_group_by && sub_group_by === "assignees" && (
|
||||
<AssigneesHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`sub_group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
{sub_group_by && sub_group_by === "created_by" && (
|
||||
<CreatedByHeader
|
||||
column_id={column_id}
|
||||
column_value={column_value}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
header_type={`sub_group_by`}
|
||||
issues_count={issues_count}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
});
|
102
web/components/issues/issue-layouts/kanban/kanban-group.tsx
Normal file
102
web/components/issues/issue-layouts/kanban/kanban-group.tsx
Normal file
@ -0,0 +1,102 @@
|
||||
import { Droppable } from "@hello-pangea/dnd";
|
||||
//types
|
||||
import { IIssue, IIssueDisplayProperties, IIssueResponse } from "types";
|
||||
import { EIssueActions } from "../types";
|
||||
//components
|
||||
import { KanBanQuickAddIssueForm, KanbanIssueBlocksList } from ".";
|
||||
|
||||
interface IKanbanGroup {
|
||||
groupId: string;
|
||||
issues: IIssueResponse;
|
||||
issueIds: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
sub_group_id: string;
|
||||
isDragDisabled: boolean;
|
||||
handleIssues: (issue: IIssue, action: EIssueActions) => void;
|
||||
showEmptyGroup: boolean;
|
||||
quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
displayProperties: IIssueDisplayProperties | null;
|
||||
enableQuickIssueCreate?: boolean;
|
||||
quickAddCallback?: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
data: IIssue,
|
||||
viewId?: string
|
||||
) => Promise<IIssue | undefined>;
|
||||
viewId?: string;
|
||||
disableIssueCreation?: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
verticalPosition: any;
|
||||
}
|
||||
|
||||
export const KanbanGroup = (props: IKanbanGroup) => {
|
||||
const {
|
||||
groupId,
|
||||
sub_group_id,
|
||||
group_by,
|
||||
sub_group_by,
|
||||
issues,
|
||||
verticalPosition,
|
||||
issueIds,
|
||||
isDragDisabled,
|
||||
showEmptyGroup,
|
||||
handleIssues,
|
||||
quickActions,
|
||||
displayProperties,
|
||||
canEditProperties,
|
||||
enableQuickIssueCreate,
|
||||
disableIssueCreation,
|
||||
quickAddCallback,
|
||||
viewId,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div className={`${verticalPosition ? `min-h-[150px] w-[0px] overflow-hidden` : `w-full transition-all`}`}>
|
||||
<Droppable droppableId={`${groupId}__${sub_group_id}`}>
|
||||
{(provided: any, snapshot: any) => (
|
||||
<div
|
||||
className={`relative h-full w-full transition-all ${
|
||||
snapshot.isDraggingOver ? `bg-custom-background-80` : ``
|
||||
}`}
|
||||
{...provided.droppableProps}
|
||||
ref={provided.innerRef}
|
||||
>
|
||||
{issues && !verticalPosition ? (
|
||||
<KanbanIssueBlocksList
|
||||
sub_group_id={sub_group_id}
|
||||
columnId={groupId}
|
||||
issues={issues}
|
||||
issueIds={issueIds?.[groupId] || []}
|
||||
isDragDisabled={isDragDisabled}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
|
||||
<div className="sticky bottom-0 z-[0] w-full flex-shrink-0 bg-custom-background-90 py-1">
|
||||
{enableQuickIssueCreate && !disableIssueCreation && (
|
||||
<KanBanQuickAddIssueForm
|
||||
formKey="name"
|
||||
groupId={groupId}
|
||||
subGroupId={sub_group_id}
|
||||
prePopulatedData={{
|
||||
...(group_by && { [group_by]: groupId }),
|
||||
...(sub_group_by && sub_group_id !== "null" && { [sub_group_by]: sub_group_id }),
|
||||
}}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -13,72 +13,42 @@ import { Tooltip } from "@plane/ui";
|
||||
import { IIssue, IIssueDisplayProperties, IState, TIssuePriorities } from "types";
|
||||
|
||||
export interface IKanBanProperties {
|
||||
sub_group_id: string;
|
||||
columnId: string;
|
||||
issue: IIssue;
|
||||
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => void;
|
||||
handleIssues: (issue: IIssue) => void;
|
||||
displayProperties: IIssueDisplayProperties | null;
|
||||
showEmptyGroup: boolean;
|
||||
isReadOnly: boolean;
|
||||
}
|
||||
|
||||
export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) => {
|
||||
const { sub_group_id, columnId: group_id, issue, handleIssues, displayProperties, isReadOnly } = props;
|
||||
const { issue, handleIssues, displayProperties, isReadOnly } = props;
|
||||
|
||||
const handleState = (state: IState) => {
|
||||
handleIssues(
|
||||
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
|
||||
!group_id && group_id === "null" ? null : group_id,
|
||||
{ ...issue, state: state.id }
|
||||
);
|
||||
handleIssues({ ...issue, state: state.id });
|
||||
};
|
||||
|
||||
const handlePriority = (value: TIssuePriorities) => {
|
||||
handleIssues(
|
||||
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
|
||||
!group_id && group_id === "null" ? null : group_id,
|
||||
{ ...issue, priority: value }
|
||||
);
|
||||
handleIssues({ ...issue, priority: value });
|
||||
};
|
||||
|
||||
const handleLabel = (ids: string[]) => {
|
||||
handleIssues(
|
||||
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
|
||||
!group_id && group_id === "null" ? null : group_id,
|
||||
{ ...issue, labels: ids }
|
||||
);
|
||||
handleIssues({ ...issue, labels: ids });
|
||||
};
|
||||
|
||||
const handleAssignee = (ids: string[]) => {
|
||||
handleIssues(
|
||||
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
|
||||
!group_id && group_id === "null" ? null : group_id,
|
||||
{ ...issue, assignees: ids }
|
||||
);
|
||||
handleIssues({ ...issue, assignees: ids });
|
||||
};
|
||||
|
||||
const handleStartDate = (date: string) => {
|
||||
handleIssues(
|
||||
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
|
||||
!group_id && group_id === "null" ? null : group_id,
|
||||
{ ...issue, start_date: date }
|
||||
);
|
||||
handleIssues({ ...issue, start_date: date });
|
||||
};
|
||||
|
||||
const handleTargetDate = (date: string) => {
|
||||
handleIssues(
|
||||
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
|
||||
!group_id && group_id === "null" ? null : group_id,
|
||||
{ ...issue, target_date: date }
|
||||
);
|
||||
handleIssues({ ...issue, target_date: date });
|
||||
};
|
||||
|
||||
const handleEstimate = (value: number | null) => {
|
||||
handleIssues(
|
||||
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
|
||||
!group_id && group_id === "null" ? null : group_id,
|
||||
{ ...issue, estimate_point: value }
|
||||
);
|
||||
handleIssues({ ...issue, estimate_point: value });
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -1,18 +1,17 @@
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
// hooks
|
||||
import { useProject, useWorkspace } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import useKeypress from "hooks/use-keypress";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
// helpers
|
||||
import { createIssuePayload } from "helpers/issue.helper";
|
||||
// types
|
||||
import { IIssue, IProject } from "types";
|
||||
import { IIssue } from "types";
|
||||
|
||||
const Inputs = (props: any) => {
|
||||
const { register, setFocus, projectDetail } = props;
|
||||
@ -56,16 +55,15 @@ const defaultValues: Partial<IIssue> = {
|
||||
|
||||
export const KanBanQuickAddIssueForm: React.FC<IKanBanQuickAddIssueForm> = observer((props) => {
|
||||
const { formKey, groupId, prePopulatedData, quickAddCallback, viewId } = props;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// store hooks
|
||||
const { getWorkspaceBySlug } = useWorkspace();
|
||||
const { getProjectById } = useProject();
|
||||
|
||||
const { workspace: workspaceStore, project: projectStore } = useMobxStore();
|
||||
|
||||
const workspaceDetail = (workspaceSlug && workspaceStore.getWorkspaceBySlug(workspaceSlug)) || null;
|
||||
const projectDetail: IProject | null =
|
||||
(workspaceSlug && projectId && projectStore.getProjectById(workspaceSlug, projectId)) || null;
|
||||
const workspaceDetail = workspaceSlug ? getWorkspaceBySlug(workspaceSlug.toString()) : null;
|
||||
const projectDetail = projectId ? getProjectById(projectId.toString()) : null;
|
||||
|
||||
const ref = useRef<HTMLFormElement>(null);
|
||||
|
||||
@ -89,7 +87,7 @@ export const KanBanQuickAddIssueForm: React.FC<IKanBanQuickAddIssueForm> = obser
|
||||
}, [isOpen, reset]);
|
||||
|
||||
const onSubmitHandler = async (formData: IIssue) => {
|
||||
if (isSubmitting || !groupId || !workspaceDetail || !projectDetail) return;
|
||||
if (isSubmitting || !groupId || !workspaceDetail || !projectDetail || !workspaceSlug || !projectId) return;
|
||||
|
||||
reset({ ...defaultValues });
|
||||
|
||||
@ -101,8 +99,8 @@ export const KanBanQuickAddIssueForm: React.FC<IKanBanQuickAddIssueForm> = obser
|
||||
try {
|
||||
quickAddCallback &&
|
||||
(await quickAddCallback(
|
||||
workspaceSlug,
|
||||
projectId,
|
||||
workspaceSlug.toString(),
|
||||
projectId.toString(),
|
||||
{
|
||||
...payload,
|
||||
},
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMemo } from "react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { ProjectIssueQuickActions } from "components/issues";
|
||||
import { BaseKanBanRoot } from "../base-kanban-root";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
// constants
|
||||
import { EIssueActions } from "../../types";
|
||||
import { BaseKanBanRoot } from "../base-kanban-root";
|
||||
import { EProjectStore } from "store_legacy/command-palette.store";
|
||||
import { IGroupedIssues, IIssueResponse, ISubGroupedIssues, TUnGroupedIssues } from "store_legacy/issues/types";
|
||||
|
||||
@ -25,18 +26,21 @@ export const KanBanLayout: React.FC = observer(() => {
|
||||
kanBanHelpers: kanBanHelperStore,
|
||||
} = useMobxStore();
|
||||
|
||||
const issueActions = {
|
||||
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
const issueActions = useMemo(
|
||||
() => ({
|
||||
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
await issueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue);
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
await issueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue);
|
||||
},
|
||||
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug) return;
|
||||
|
||||
await issueStore.removeIssue(workspaceSlug, issue.project, issue.id);
|
||||
},
|
||||
};
|
||||
await issueStore.removeIssue(workspaceSlug, issue.project, issue.id);
|
||||
},
|
||||
}),
|
||||
[issueStore]
|
||||
);
|
||||
|
||||
const handleDragDrop = async (
|
||||
source: any,
|
||||
|
@ -1,96 +1,63 @@
|
||||
import React from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
//mobx
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// components
|
||||
import { KanBanGroupByHeaderRoot } from "./headers/group-by-root";
|
||||
import { KanBanSubGroupByHeaderRoot } from "./headers/sub-group-by-root";
|
||||
import { KanBan } from "./default";
|
||||
import { HeaderSubGroupByCard } from "./headers/sub-group-by-card";
|
||||
import { HeaderGroupByCard } from "./headers/group-by-card";
|
||||
// types
|
||||
import { IIssue, IIssueDisplayProperties, IIssueLabel, IProject, IState, IUserLite } from "types";
|
||||
import { IIssue, IIssueDisplayProperties } from "types";
|
||||
import { IIssueResponse, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "store_legacy/issues/types";
|
||||
// constants
|
||||
import { getValueFromObject } from "constants/issue";
|
||||
import { EIssueActions } from "../types";
|
||||
import { EProjectStore } from "store_legacy/command-palette.store";
|
||||
import { IKanbanColumn, columnTypes, getKanbanColumns } from "./utils";
|
||||
|
||||
interface ISubGroupSwimlaneHeader {
|
||||
issues: IIssueResponse;
|
||||
issueIds: any;
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
list: any;
|
||||
listKey: string;
|
||||
list: IKanbanColumn[];
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
}
|
||||
const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
|
||||
issueIds,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
list,
|
||||
listKey,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
disableIssueCreation,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
}) => {
|
||||
const calculateIssueCount = (column_id: string) => {
|
||||
let issueCount = 0;
|
||||
issueIds &&
|
||||
Object.keys(issueIds)?.forEach((_issueKey: any) => {
|
||||
issueCount += issueIds?.[_issueKey]?.[column_id]?.length || 0;
|
||||
});
|
||||
return issueCount;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative flex h-max min-h-full w-full items-center">
|
||||
{list &&
|
||||
list.length > 0 &&
|
||||
list.map((_list: any) => (
|
||||
<div
|
||||
key={`${sub_group_by}_${getValueFromObject(_list, listKey) as string}`}
|
||||
className="flex w-[340px] flex-shrink-0 flex-col"
|
||||
>
|
||||
<KanBanGroupByHeaderRoot
|
||||
column_id={getValueFromObject(_list, listKey) as string}
|
||||
column_value={_list}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
issues_count={calculateIssueCount(getValueFromObject(_list, listKey) as string)}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}) => (
|
||||
<div className="relative flex h-max min-h-full w-full items-center">
|
||||
{list &&
|
||||
list.length > 0 &&
|
||||
list.map((_list: IKanbanColumn) => (
|
||||
<div key={`${sub_group_by}_${_list.id}`} className="flex w-[340px] flex-shrink-0 flex-col">
|
||||
<HeaderGroupByCard
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
column_id={_list.id}
|
||||
icon={_list.Icon}
|
||||
title={_list.name}
|
||||
count={issueIds?.[_list.id]?.length || 0}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
issuePayload={_list.payload}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
||||
issues: IIssueResponse;
|
||||
issueIds: any;
|
||||
order_by: string | null;
|
||||
showEmptyGroup: boolean;
|
||||
states: IState[] | null;
|
||||
stateGroups: any;
|
||||
priorities: any;
|
||||
labels: IIssueLabel[] | null;
|
||||
members: IUserLite[] | null;
|
||||
projects: IProject[] | null;
|
||||
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
|
||||
quickActions: (
|
||||
sub_group_by: string | null,
|
||||
group_by: string | null,
|
||||
issue: IIssue,
|
||||
customActionButton?: React.ReactElement
|
||||
) => React.ReactNode;
|
||||
handleIssues: (issue: IIssue, action: EIssueActions) => void;
|
||||
quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
displayProperties: IIssueDisplayProperties | null;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
@ -99,6 +66,7 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
|
||||
currentStore?: EProjectStore;
|
||||
enableQuickIssueCreate: boolean;
|
||||
canEditProperties: (projectId: string | undefined) => boolean;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
quickAddCallback?: (
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
@ -112,23 +80,13 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||
issueIds,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
order_by,
|
||||
list,
|
||||
listKey,
|
||||
handleIssues,
|
||||
quickActions,
|
||||
displayProperties,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
showEmptyGroup,
|
||||
states,
|
||||
stateGroups,
|
||||
priorities,
|
||||
labels,
|
||||
members,
|
||||
projects,
|
||||
isDragStarted,
|
||||
disableIssueCreation,
|
||||
enableQuickIssueCreate,
|
||||
canEditProperties,
|
||||
addIssuesToView,
|
||||
@ -152,43 +110,32 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
|
||||
<div className="flex flex-shrink-0 flex-col">
|
||||
<div className="sticky top-[50px] z-[1] flex w-full items-center bg-custom-background-90 py-1">
|
||||
<div className="sticky left-0 flex-shrink-0 bg-custom-background-90 pr-2">
|
||||
<KanBanSubGroupByHeaderRoot
|
||||
column_id={getValueFromObject(_list, listKey) as string}
|
||||
column_value={_list}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
issues_count={calculateIssueCount(getValueFromObject(_list, listKey) as string)}
|
||||
<HeaderSubGroupByCard
|
||||
column_id={_list.id}
|
||||
icon={_list.Icon}
|
||||
title={_list.name || ""}
|
||||
count={calculateIssueCount(_list.id)}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full border-b border-dashed border-custom-border-400" />
|
||||
</div>
|
||||
{!kanBanToggle?.subgroupByIssuesVisibility.includes(getValueFromObject(_list, listKey) as string) && (
|
||||
{!kanBanToggle?.subgroupByIssuesVisibility.includes(_list.id) && (
|
||||
<div className="relative">
|
||||
<KanBan
|
||||
issues={issues}
|
||||
issueIds={issueIds?.[getValueFromObject(_list, listKey) as string]}
|
||||
issueIds={issueIds?.[_list.id]}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
sub_group_id={getValueFromObject(_list, listKey) as string}
|
||||
sub_group_id={_list.id}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
isDragStarted={isDragStarted}
|
||||
canEditProperties={canEditProperties}
|
||||
addIssuesToView={addIssuesToView}
|
||||
quickAddCallback={quickAddCallback}
|
||||
@ -207,23 +154,12 @@ export interface IKanBanSwimLanes {
|
||||
sub_group_by: string | null;
|
||||
group_by: string | null;
|
||||
order_by: string | null;
|
||||
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
|
||||
quickActions: (
|
||||
sub_group_by: string | null,
|
||||
group_by: string | null,
|
||||
issue: IIssue,
|
||||
customActionButton?: React.ReactElement
|
||||
) => React.ReactNode;
|
||||
handleIssues: (issue: IIssue, action: EIssueActions) => void;
|
||||
quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode;
|
||||
displayProperties: IIssueDisplayProperties | null;
|
||||
kanBanToggle: any;
|
||||
handleKanBanToggle: any;
|
||||
showEmptyGroup: boolean;
|
||||
states: IState[] | null;
|
||||
stateGroups: any;
|
||||
priorities: any;
|
||||
labels: IIssueLabel[] | null;
|
||||
members: IUserLite[] | null;
|
||||
projects: IProject[] | null;
|
||||
isDragStarted?: boolean;
|
||||
disableIssueCreation?: boolean;
|
||||
currentStore?: EProjectStore;
|
||||
@ -251,363 +187,58 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
showEmptyGroup,
|
||||
states,
|
||||
stateGroups,
|
||||
priorities,
|
||||
labels,
|
||||
members,
|
||||
projects,
|
||||
isDragStarted,
|
||||
disableIssueCreation,
|
||||
enableQuickIssueCreate,
|
||||
canEditProperties,
|
||||
currentStore,
|
||||
addIssuesToView,
|
||||
quickAddCallback,
|
||||
} = props;
|
||||
|
||||
const { project, projectLabel, projectMember, projectState } = useMobxStore();
|
||||
|
||||
const groupByList = getKanbanColumns(group_by as columnTypes, project, projectLabel, projectMember, projectState);
|
||||
const subGroupByList = getKanbanColumns(
|
||||
sub_group_by as columnTypes,
|
||||
project,
|
||||
projectLabel,
|
||||
projectMember,
|
||||
projectState
|
||||
);
|
||||
|
||||
if (!groupByList || !subGroupByList) return null;
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className="sticky top-0 z-[2] h-[50px] bg-custom-background-90">
|
||||
{group_by && group_by === "project" && (
|
||||
<SubGroupSwimlaneHeader
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
list={projects}
|
||||
listKey={`id`}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "state" && (
|
||||
<SubGroupSwimlaneHeader
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
list={states}
|
||||
listKey={`id`}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "state_detail.group" && (
|
||||
<SubGroupSwimlaneHeader
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
list={stateGroups}
|
||||
listKey={`key`}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "priority" && (
|
||||
<SubGroupSwimlaneHeader
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
list={priorities}
|
||||
listKey={`key`}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "labels" && (
|
||||
<SubGroupSwimlaneHeader
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
list={labels ? [...labels, { id: "None", name: "None" }] : labels}
|
||||
listKey={`id`}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "assignees" && (
|
||||
<SubGroupSwimlaneHeader
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
list={members ? [...members, { id: "None", display_name: "None" }] : members}
|
||||
listKey={`id`}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
|
||||
{group_by && group_by === "created_by" && (
|
||||
<SubGroupSwimlaneHeader
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
list={members}
|
||||
listKey={`id`}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
)}
|
||||
<SubGroupSwimlaneHeader
|
||||
issueIds={issueIds}
|
||||
group_by={group_by}
|
||||
sub_group_by={sub_group_by}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
list={groupByList}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{sub_group_by && sub_group_by === "project" && (
|
||||
{sub_group_by && (
|
||||
<SubGroupSwimlane
|
||||
issues={issues}
|
||||
list={subGroupByList}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
sub_group_by={sub_group_by}
|
||||
order_by={order_by}
|
||||
list={projects}
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
|
||||
{sub_group_by && sub_group_by === "state" && (
|
||||
<SubGroupSwimlane
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={states}
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
|
||||
{sub_group_by && sub_group_by === "state" && (
|
||||
<SubGroupSwimlane
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={states}
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
|
||||
{sub_group_by && sub_group_by === "state_detail.group" && (
|
||||
<SubGroupSwimlane
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={stateGroups}
|
||||
listKey={`key`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
|
||||
{sub_group_by && sub_group_by === "priority" && (
|
||||
<SubGroupSwimlane
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={priorities}
|
||||
listKey={`key`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
|
||||
{sub_group_by && sub_group_by === "labels" && (
|
||||
<SubGroupSwimlane
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={labels ? [...labels, { id: "None", name: "None" }] : labels}
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
|
||||
{sub_group_by && sub_group_by === "assignees" && (
|
||||
<SubGroupSwimlane
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={members ? [...members, { id: "None", display_name: "None" }] : members}
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
)}
|
||||
|
||||
{sub_group_by && sub_group_by === "created_by" && (
|
||||
<SubGroupSwimlane
|
||||
issues={issues}
|
||||
issueIds={issueIds}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
order_by={order_by}
|
||||
list={members}
|
||||
listKey={`id`}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
states={states}
|
||||
stateGroups={stateGroups}
|
||||
priorities={priorities}
|
||||
labels={labels}
|
||||
members={members}
|
||||
projects={projects}
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={disableIssueCreation}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
addIssuesToView={addIssuesToView}
|
||||
canEditProperties={canEditProperties}
|
||||
quickAddCallback={quickAddCallback}
|
||||
/>
|
||||
|
149
web/components/issues/issue-layouts/kanban/utils.tsx
Normal file
149
web/components/issues/issue-layouts/kanban/utils.tsx
Normal file
@ -0,0 +1,149 @@
|
||||
import { Avatar, PriorityIcon, StateGroupIcon } from "@plane/ui";
|
||||
import { ISSUE_PRIORITIES, ISSUE_STATE_GROUPS } from "constants/issue";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { ReactElement } from "react";
|
||||
import { IProjectLabelStore, IProjectMemberStore, IProjectStateStore, IProjectStore } from "store_legacy/project";
|
||||
import { IIssue } from "types";
|
||||
|
||||
export type columnTypes =
|
||||
| "project"
|
||||
| "state"
|
||||
| "state_detail.group"
|
||||
| "priority"
|
||||
| "labels"
|
||||
| "assignees"
|
||||
| "created_by";
|
||||
|
||||
export interface IKanbanColumn {
|
||||
id: string;
|
||||
name: string;
|
||||
Icon: ReactElement;
|
||||
payload: Partial<IIssue>;
|
||||
}
|
||||
|
||||
export const getKanbanColumns = (
|
||||
groupBy: columnTypes | null,
|
||||
project: IProjectStore,
|
||||
projectLabel: IProjectLabelStore,
|
||||
projectMember: IProjectMemberStore,
|
||||
projectState: IProjectStateStore
|
||||
): IKanbanColumn[] | undefined => {
|
||||
switch (groupBy) {
|
||||
case "project":
|
||||
return getProjectColumns(project);
|
||||
case "state":
|
||||
return getStateColumns(projectState);
|
||||
case "state_detail.group":
|
||||
return getStateGroupColumns();
|
||||
case "priority":
|
||||
return getPriorityColumns();
|
||||
case "labels":
|
||||
return getLabelsColumns(projectLabel);
|
||||
case "assignees":
|
||||
return getAssigneeColumns(projectMember);
|
||||
case "created_by":
|
||||
return getCreatedByColumns(projectMember);
|
||||
}
|
||||
};
|
||||
|
||||
const getProjectColumns = (project: IProjectStore): IKanbanColumn[] | undefined => {
|
||||
const { workspaceProjects: projects } = project;
|
||||
|
||||
if (!projects) return;
|
||||
|
||||
return projects.map((project) => ({
|
||||
id: project.id,
|
||||
name: project.name,
|
||||
Icon: <div className="w-6 h-6">{renderEmoji(project.emoji || "")}</div>,
|
||||
payload: { project: project.id },
|
||||
}));
|
||||
};
|
||||
|
||||
const getStateColumns = (projectState: IProjectStateStore): IKanbanColumn[] | undefined => {
|
||||
const { projectStates } = projectState;
|
||||
if (!projectStates) return;
|
||||
|
||||
return projectStates.map((state) => ({
|
||||
id: state.id,
|
||||
name: state.name,
|
||||
Icon: (
|
||||
<div className="w-3.5 h-3.5 rounded-full">
|
||||
<StateGroupIcon stateGroup={state.group} color={state.color} width="14" height="14" />
|
||||
</div>
|
||||
),
|
||||
payload: { state: state.id },
|
||||
}));
|
||||
};
|
||||
|
||||
const getStateGroupColumns = () => {
|
||||
const stateGroups = ISSUE_STATE_GROUPS;
|
||||
|
||||
return stateGroups.map((stateGroup) => ({
|
||||
id: stateGroup.key,
|
||||
name: stateGroup.title,
|
||||
Icon: (
|
||||
<div className="w-3.5 h-3.5 rounded-full">
|
||||
<StateGroupIcon stateGroup={stateGroup.key} width="14" height="14" />
|
||||
</div>
|
||||
),
|
||||
payload: {},
|
||||
}));
|
||||
};
|
||||
|
||||
const getPriorityColumns = () => {
|
||||
const priorities = ISSUE_PRIORITIES;
|
||||
|
||||
return priorities.map((priority) => ({
|
||||
id: priority.key,
|
||||
name: priority.title,
|
||||
Icon: <PriorityIcon priority={priority?.key} />,
|
||||
payload: { priority: priority.key },
|
||||
}));
|
||||
};
|
||||
|
||||
const getLabelsColumns = (projectLabel: IProjectLabelStore) => {
|
||||
const { projectLabels } = projectLabel;
|
||||
|
||||
if (!projectLabels) return;
|
||||
|
||||
const labels = [...projectLabels, { id: "None", name: "None", color: "#666" }];
|
||||
|
||||
return labels.map((label) => ({
|
||||
id: label.id,
|
||||
name: label.name,
|
||||
Icon: (
|
||||
<div className="w-[12px] h-[12px] rounded-full" style={{ backgroundColor: label.color ? label.color : "#666" }} />
|
||||
),
|
||||
payload: { labels: [label.id] },
|
||||
}));
|
||||
};
|
||||
|
||||
const getAssigneeColumns = (projectMember: IProjectMemberStore) => {
|
||||
const { projectMembers: users } = projectMember;
|
||||
if (!users) return;
|
||||
|
||||
return users.map((user) => {
|
||||
const member = user.member;
|
||||
return {
|
||||
id: member?.id,
|
||||
name: member?.display_name || "",
|
||||
Icon: <Avatar name={member?.display_name} src={member?.avatar} size="md" />,
|
||||
payload: { assignees: [member?.id] },
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const getCreatedByColumns = (projectMember: IProjectMemberStore) => {
|
||||
const { projectMembers: users } = projectMember;
|
||||
if (!users) return;
|
||||
|
||||
return users.map((user) => {
|
||||
const member = user.member;
|
||||
return {
|
||||
id: member?.id,
|
||||
name: member?.display_name || "",
|
||||
Icon: <Avatar name={member?.display_name} src={member?.avatar} size="md" />,
|
||||
payload: { created_by: member?.id },
|
||||
};
|
||||
});
|
||||
};
|
@ -4,11 +4,10 @@ import { useForm } from "react-hook-form";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useProject, useWorkspace } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import useKeypress from "hooks/use-keypress";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// constants
|
||||
import { IIssue, IProject } from "types";
|
||||
// types
|
||||
@ -60,15 +59,12 @@ const defaultValues: Partial<IIssue> = {
|
||||
|
||||
export const ListQuickAddIssueForm: FC<IListQuickAddIssueForm> = observer((props) => {
|
||||
const { prePopulatedData, quickAddCallback, viewId } = props;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||
|
||||
const { workspace: workspaceStore, project: projectStore } = useMobxStore();
|
||||
|
||||
const workspaceDetail = (workspaceSlug && workspaceStore.getWorkspaceBySlug(workspaceSlug)) || null;
|
||||
const projectDetail: IProject | null =
|
||||
(workspaceSlug && projectId && projectStore.getProjectById(workspaceSlug, projectId)) || null;
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// store hooks
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
const ref = useRef<HTMLFormElement>(null);
|
||||
|
||||
@ -92,17 +88,18 @@ export const ListQuickAddIssueForm: FC<IListQuickAddIssueForm> = observer((props
|
||||
}, [isOpen, reset]);
|
||||
|
||||
const onSubmitHandler = async (formData: IIssue) => {
|
||||
if (isSubmitting || !workspaceDetail || !projectDetail) return;
|
||||
if (isSubmitting || !currentWorkspace || !currentProjectDetails || !workspaceSlug || !projectId) return;
|
||||
|
||||
reset({ ...defaultValues });
|
||||
|
||||
const payload = createIssuePayload(workspaceDetail, projectDetail, {
|
||||
const payload = createIssuePayload(currentWorkspace, currentProjectDetails, {
|
||||
...(prePopulatedData ?? {}),
|
||||
...formData,
|
||||
});
|
||||
|
||||
try {
|
||||
quickAddCallback && (await quickAddCallback(workspaceSlug, projectId, { ...payload }, viewId));
|
||||
quickAddCallback &&
|
||||
(await quickAddCallback(workspaceSlug.toString(), projectId.toString(), { ...payload }, viewId));
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
@ -130,7 +127,12 @@ export const ListQuickAddIssueForm: FC<IListQuickAddIssueForm> = observer((props
|
||||
onSubmit={handleSubmit(onSubmitHandler)}
|
||||
className="flex w-full items-center gap-x-3 border-[0.5px] border-t-0 border-custom-border-100 bg-custom-background-100 px-3"
|
||||
>
|
||||
<Inputs formKey={"name"} register={register} setFocus={setFocus} projectDetail={projectDetail} />
|
||||
<Inputs
|
||||
formKey={"name"}
|
||||
register={register}
|
||||
setFocus={setFocus}
|
||||
projectDetail={currentProjectDetails ?? null}
|
||||
/>
|
||||
</form>
|
||||
<div className="px-3 py-2 text-xs italic text-custom-text-200">{`Press 'Enter' to add another issue`}</div>
|
||||
</div>
|
||||
|
@ -1,18 +1,15 @@
|
||||
import { Fragment, useState } from "react";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
// hooks
|
||||
import { usePopper } from "react-popper";
|
||||
// ui
|
||||
import { Placement } from "@popperjs/core";
|
||||
import { Combobox } from "@headlessui/react";
|
||||
import { StateGroupIcon, Tooltip } from "@plane/ui";
|
||||
import { Check, ChevronDown, Search } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication, useProjectState } from "hooks/store";
|
||||
// ui
|
||||
import { StateGroupIcon, Tooltip } from "@plane/ui";
|
||||
// types
|
||||
import { IState } from "types";
|
||||
import { Placement } from "@popperjs/core";
|
||||
import { RootStore } from "store_legacy/root";
|
||||
|
||||
export interface IIssuePropertyState {
|
||||
projectId: string | null;
|
||||
@ -40,25 +37,24 @@ export const IssuePropertyState: React.FC<IIssuePropertyState> = observer((props
|
||||
optionsClassName = "",
|
||||
placement,
|
||||
} = props;
|
||||
|
||||
const { workspace: workspaceStore, projectState: projectStateStore }: RootStore = useMobxStore();
|
||||
const workspaceSlug = workspaceStore?.workspaceSlug;
|
||||
|
||||
// states
|
||||
const [query, setQuery] = useState("");
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
const [isLoading, setIsLoading] = useState<Boolean>(false);
|
||||
// store hooks
|
||||
const {
|
||||
router: { workspaceSlug },
|
||||
} = useApplication();
|
||||
const { projectStates: storeStates, fetchProjectStates } = useProjectState();
|
||||
|
||||
let projectStates: IState[] = defaultOptions;
|
||||
const storeStates = projectId ? projectStateStore.states[projectId] : [];
|
||||
if (storeStates && storeStates.length > 0) projectStates = storeStates;
|
||||
|
||||
const fetchProjectStates = () => {
|
||||
const handleFetchProjectStates = () => {
|
||||
setIsLoading(true);
|
||||
if (workspaceSlug && projectId)
|
||||
workspaceSlug &&
|
||||
projectId &&
|
||||
projectStateStore.fetchProjectStates(workspaceSlug, projectId).then(() => setIsLoading(false));
|
||||
workspaceSlug && projectId && fetchProjectStates(workspaceSlug, projectId).then(() => setIsLoading(false));
|
||||
};
|
||||
|
||||
const selectedOption: IState | undefined =
|
||||
@ -121,7 +117,7 @@ export const IssuePropertyState: React.FC<IIssuePropertyState> = observer((props
|
||||
className={`flex h-5 w-full items-center justify-between gap-1 rounded border-[0.5px] border-custom-border-300 px-2.5 py-1 text-xs ${
|
||||
disabled ? "cursor-not-allowed text-custom-text-200" : "cursor-pointer hover:bg-custom-background-80"
|
||||
} ${buttonClassName}`}
|
||||
onClick={() => !storeStates && fetchProjectStates()}
|
||||
onClick={() => !storeStates && handleFetchProjectStates()}
|
||||
>
|
||||
{label}
|
||||
{!hideDropdownArrow && !disabled && <ChevronDown className="h-3 w-3" aria-hidden="true" />}
|
||||
|
@ -2,9 +2,8 @@ import { useState } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
import { Copy, Link, Pencil, Trash2 } from "lucide-react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// components
|
||||
import { CreateUpdateIssueModal, DeleteIssueModal } from "components/issues";
|
||||
@ -19,18 +18,17 @@ import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
|
||||
export const ProjectIssueQuickActions: React.FC<IQuickActionProps> = (props) => {
|
||||
const { issue, handleDelete, handleUpdate, customActionButton } = props;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
// states
|
||||
const [createUpdateIssueModal, setCreateUpdateIssueModal] = useState(false);
|
||||
const [issueToEdit, setIssueToEdit] = useState<IIssue | null>(null);
|
||||
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
|
||||
|
||||
const { user: userStore } = useMobxStore();
|
||||
|
||||
const { currentProjectRole } = userStore;
|
||||
// store hooks
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
|
||||
const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
// components
|
||||
import { SpreadsheetColumn } from "components/issues";
|
||||
// types
|
||||
@ -31,10 +32,8 @@ export const SpreadsheetColumnsList: React.FC<Props> = observer((props) => {
|
||||
labels,
|
||||
states,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
project: { currentProjectDetails },
|
||||
} = useMobxStore();
|
||||
// store hooks
|
||||
const { currentProjectDetails } = useProject();
|
||||
|
||||
const isEstimateEnabled: boolean = currentProjectDetails?.estimate !== null;
|
||||
|
||||
|
@ -1,18 +1,16 @@
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { PlusIcon } from "lucide-react";
|
||||
// hooks
|
||||
import { useProject, useWorkspace } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import useKeypress from "hooks/use-keypress";
|
||||
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// helpers
|
||||
import { createIssuePayload } from "helpers/issue.helper";
|
||||
// types
|
||||
import { IIssue, IProject } from "types";
|
||||
import { IIssue } from "types";
|
||||
|
||||
type Props = {
|
||||
formKey: keyof IIssue;
|
||||
@ -57,14 +55,10 @@ const Inputs = (props: any) => {
|
||||
|
||||
export const SpreadsheetQuickAddIssueForm: React.FC<Props> = observer((props) => {
|
||||
const { formKey, prePopulatedData, quickAddCallback, viewId } = props;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
|
||||
|
||||
// store
|
||||
const { workspace: workspaceStore, project: projectStore } = useMobxStore();
|
||||
|
||||
// store hooks
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { currentProjectDetails } = useProject();
|
||||
// form info
|
||||
const {
|
||||
reset,
|
||||
handleSubmit,
|
||||
@ -86,11 +80,6 @@ export const SpreadsheetQuickAddIssueForm: React.FC<Props> = observer((props) =>
|
||||
useOutsideClickDetector(ref, handleClose);
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
// derived values
|
||||
const workspaceDetail = (workspaceSlug && workspaceStore.getWorkspaceBySlug(workspaceSlug)) || null;
|
||||
const projectDetail: IProject | null =
|
||||
(workspaceSlug && projectId && projectStore.getProjectById(workspaceSlug, projectId)) || null;
|
||||
|
||||
useEffect(() => {
|
||||
setFocus("name");
|
||||
}, [setFocus, isOpen]);
|
||||
@ -155,17 +144,18 @@ export const SpreadsheetQuickAddIssueForm: React.FC<Props> = observer((props) =>
|
||||
// };
|
||||
|
||||
const onSubmitHandler = async (formData: IIssue) => {
|
||||
if (isSubmitting || !workspaceDetail || !projectDetail) return;
|
||||
if (isSubmitting || !currentWorkspace || !currentProjectDetails) return;
|
||||
|
||||
reset({ ...defaultValues });
|
||||
|
||||
const payload = createIssuePayload(workspaceDetail, projectDetail, {
|
||||
const payload = createIssuePayload(currentWorkspace, currentProjectDetails, {
|
||||
...(prePopulatedData ?? {}),
|
||||
...formData,
|
||||
});
|
||||
|
||||
try {
|
||||
quickAddCallback && (await quickAddCallback(workspaceSlug, projectId, { ...payload } as IIssue, viewId));
|
||||
quickAddCallback &&
|
||||
(await quickAddCallback(currentWorkspace.slug, currentProjectDetails.id, { ...payload } as IIssue, viewId));
|
||||
setToastAlert({
|
||||
type: "success",
|
||||
title: "Success!",
|
||||
@ -190,7 +180,12 @@ export const SpreadsheetQuickAddIssueForm: React.FC<Props> = observer((props) =>
|
||||
onSubmit={handleSubmit(onSubmitHandler)}
|
||||
className="z-10 flex items-center gap-x-5 border-[0.5px] border-t-0 border-custom-border-100 bg-custom-background-100 px-4 shadow-custom-shadow-sm"
|
||||
>
|
||||
<Inputs formKey={formKey} register={register} setFocus={setFocus} projectDetails={projectDetail} />
|
||||
<Inputs
|
||||
formKey={formKey}
|
||||
register={register}
|
||||
setFocus={setFocus}
|
||||
projectDetails={currentProjectDetails ?? null}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { observer } from "mobx-react-lite";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
import useIssueReaction from "hooks/use-issue-reaction";
|
||||
// components
|
||||
import { ReactionSelector } from "components/core";
|
||||
// string helpers
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
// types
|
||||
type Props = {
|
||||
@ -17,9 +17,7 @@ type Props = {
|
||||
export const IssueReaction: React.FC<Props> = observer((props) => {
|
||||
const { workspaceSlug, projectId, issueId } = props;
|
||||
|
||||
const {
|
||||
user: { currentUser },
|
||||
} = useMobxStore();
|
||||
const { currentUser } = useUser();
|
||||
|
||||
const { reactions, groupedReactions, handleReactionCreate, handleReactionDelete } = useIssueReaction(
|
||||
workspaceSlug,
|
||||
|
@ -3,9 +3,8 @@ import { Controller, useForm } from "react-hook-form";
|
||||
import debounce from "lodash/debounce";
|
||||
// packages
|
||||
import { RichTextEditor } from "@plane/rich-text-editor";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
import useReloadConfirmations from "hooks/use-reload-confirmation";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
// components
|
||||
@ -46,8 +45,9 @@ export const PeekOverviewIssueDetails: FC<IPeekOverviewIssueDetails> = (props) =
|
||||
setIsSubmitting,
|
||||
} = props;
|
||||
// store
|
||||
const { user: userStore } = useMobxStore();
|
||||
const { currentProjectRole } = userStore;
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
const isAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER;
|
||||
// states
|
||||
const [characterLimit, setCharacterLimit] = useState(false);
|
||||
|
@ -1,14 +1,12 @@
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { usePopper } from "react-popper";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// ui
|
||||
import { Combobox } from "@headlessui/react";
|
||||
import { Check, Search } from "lucide-react";
|
||||
// hooks
|
||||
import { useModule } from "hooks/store";
|
||||
// icons
|
||||
import { DiceIcon } from "@plane/ui";
|
||||
// icons
|
||||
import { Check, Search } from "lucide-react";
|
||||
|
||||
export interface IssueModuleSelectProps {
|
||||
workspaceSlug: string;
|
||||
@ -19,37 +17,39 @@ export interface IssueModuleSelectProps {
|
||||
|
||||
export const IssueModuleSelect: React.FC<IssueModuleSelectProps> = observer((props) => {
|
||||
const { workspaceSlug, projectId, value, onChange } = props;
|
||||
// states
|
||||
const [query, setQuery] = useState("");
|
||||
|
||||
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
// store hooks
|
||||
const { projectModules, getModuleById, fetchModules } = useModule();
|
||||
// popper-js
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: "bottom-start",
|
||||
});
|
||||
|
||||
const { module: moduleStore } = useMobxStore();
|
||||
|
||||
const fetchModules = () => {
|
||||
if (workspaceSlug && projectId) moduleStore.fetchModules(workspaceSlug, projectId);
|
||||
const handleFetchModules = () => {
|
||||
if (workspaceSlug && projectId) fetchModules(workspaceSlug, projectId);
|
||||
};
|
||||
|
||||
const modules = projectId ? moduleStore.modules[projectId] : undefined;
|
||||
const selectedModule = value ? getModuleById(value) : null;
|
||||
|
||||
const selectedModule = modules ? modules?.find((i) => i.id === value) : undefined;
|
||||
const options = projectModules?.map((moduleId) => {
|
||||
const moduleDetails = getModuleById(moduleId);
|
||||
|
||||
const options = modules?.map((module) => ({
|
||||
value: module.id,
|
||||
query: module.name,
|
||||
content: (
|
||||
<div className="flex items-center gap-1.5 truncate">
|
||||
<span className="flex h-3.5 w-3.5 flex-shrink-0 items-center justify-center">
|
||||
<DiceIcon />
|
||||
</span>
|
||||
<span className="flex-grow truncate">{module.name}</span>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
return {
|
||||
value: `${moduleDetails?.id}`,
|
||||
query: `${moduleDetails?.name}`,
|
||||
content: (
|
||||
<div className="flex items-center gap-1.5 truncate">
|
||||
<span className="flex h-3.5 w-3.5 flex-shrink-0 items-center justify-center">
|
||||
<DiceIcon />
|
||||
</span>
|
||||
<span className="flex-grow truncate">{moduleDetails?.name}</span>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
const filteredOptions =
|
||||
query === "" ? options : options?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase()));
|
||||
@ -73,7 +73,7 @@ export const IssueModuleSelect: React.FC<IssueModuleSelectProps> = observer((pro
|
||||
ref={setReferenceElement}
|
||||
type="button"
|
||||
className="flex w-full cursor-pointer items-center justify-between gap-1 rounded border-[0.5px] border-custom-border-300 px-2 py-1 text-xs text-custom-text-200 hover:bg-custom-background-80"
|
||||
onClick={fetchModules}
|
||||
onClick={handleFetchModules}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
|
@ -1,16 +1,13 @@
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import type { FieldError } from "react-hook-form";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
// popper js
|
||||
import { usePopper } from "react-popper";
|
||||
// ui
|
||||
import { Combobox } from "@headlessui/react";
|
||||
import { Check, Clipboard, Search } from "lucide-react";
|
||||
// hooks
|
||||
import { useProject } from "hooks/store";
|
||||
// helpers
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
// icons
|
||||
import { Check, Clipboard, Search } from "lucide-react";
|
||||
|
||||
export interface IssueProjectSelectProps {
|
||||
value: string;
|
||||
@ -28,25 +25,31 @@ export const IssueProjectSelect: React.FC<IssueProjectSelectProps> = observer((p
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: "bottom-start",
|
||||
});
|
||||
// store hooks
|
||||
const { joinedProjects, getProjectById } = useProject();
|
||||
|
||||
const {
|
||||
project: { joinedProjects },
|
||||
} = useMobxStore();
|
||||
const selectedProject = getProjectById(value);
|
||||
|
||||
const selectedProject = joinedProjects?.find((i) => i.id === value);
|
||||
const options = joinedProjects?.map((projectId) => {
|
||||
const projectDetails = getProjectById(projectId);
|
||||
|
||||
const options = joinedProjects?.map((project) => ({
|
||||
value: project.id,
|
||||
query: project.name,
|
||||
content: (
|
||||
<div className="flex items-center gap-1.5 truncate">
|
||||
<span className="grid flex-shrink-0 place-items-center">
|
||||
{project.emoji ? renderEmoji(project.emoji) : project.icon_prop ? renderEmoji(project.icon_prop) : null}
|
||||
</span>
|
||||
<span className="flex-grow truncate">{project.name}</span>
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
return {
|
||||
value: `${projectDetails?.id}`,
|
||||
query: `${projectDetails?.name}`,
|
||||
content: (
|
||||
<div className="flex items-center gap-1.5 truncate">
|
||||
<span className="grid flex-shrink-0 place-items-center">
|
||||
{projectDetails?.emoji
|
||||
? renderEmoji(projectDetails?.emoji)
|
||||
: projectDetails?.icon_prop
|
||||
? renderEmoji(projectDetails?.icon_prop)
|
||||
: null}
|
||||
</span>
|
||||
<span className="flex-grow truncate">{projectDetails?.name}</span>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
const filteredOptions =
|
||||
query === "" ? options : options?.filter((option) => option.query.toLowerCase().includes(query.toLowerCase()));
|
||||
|
@ -2,12 +2,11 @@ import React from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import useSWR from "swr";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { Plus } from "lucide-react";
|
||||
// hooks
|
||||
import { useProjectState } from "hooks/store";
|
||||
// ui
|
||||
import { CustomSearchSelect, DoubleCircleIcon, StateGroupIcon } from "@plane/ui";
|
||||
// icons
|
||||
import { Plus } from "lucide-react";
|
||||
|
||||
type Props = {
|
||||
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
@ -18,23 +17,18 @@ type Props = {
|
||||
|
||||
export const IssueStateSelect: React.FC<Props> = observer((props) => {
|
||||
const { setIsOpen, value, onChange, projectId } = props;
|
||||
|
||||
// states
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
const {
|
||||
projectState: { states: projectStates, fetchProjectStates },
|
||||
} = useMobxStore();
|
||||
// store hooks
|
||||
const { projectStates, fetchProjectStates } = useProjectState();
|
||||
|
||||
useSWR(
|
||||
workspaceSlug && projectId ? `STATES_LIST_${projectId.toUpperCase()}` : null,
|
||||
workspaceSlug && projectId ? () => fetchProjectStates(workspaceSlug.toString(), projectId) : null
|
||||
);
|
||||
|
||||
const states = projectStates?.[projectId] || [];
|
||||
|
||||
const options = states?.map((state) => ({
|
||||
const options = projectStates?.map((state) => ({
|
||||
value: state.id,
|
||||
query: state.name,
|
||||
content: (
|
||||
@ -45,8 +39,8 @@ export const IssueStateSelect: React.FC<Props> = observer((props) => {
|
||||
),
|
||||
}));
|
||||
|
||||
const selectedOption = states?.find((s) => s.id === value);
|
||||
const currentDefaultState = states?.find((s) => s.default);
|
||||
const selectedOption = projectStates?.find((s) => s.id === value);
|
||||
const currentDefaultState = projectStates?.find((s) => s.default);
|
||||
|
||||
return (
|
||||
<CustomSearchSelect
|
||||
|
@ -1,17 +1,16 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
// react-hook-form
|
||||
import { UseFormWatch } from "react-hook-form";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { X } from "lucide-react";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import useUser from "hooks/use-user";
|
||||
// services
|
||||
import { IssueService } from "services/issue";
|
||||
// components
|
||||
import { ExistingIssuesListModal } from "components/core";
|
||||
// icons
|
||||
import { X } from "lucide-react";
|
||||
import { BlockedIcon } from "@plane/ui";
|
||||
// types
|
||||
import { BlockeIssueDetail, IIssue, ISearchIssueResponse } from "types";
|
||||
@ -26,14 +25,17 @@ type Props = {
|
||||
// services
|
||||
const issueService = new IssueService();
|
||||
|
||||
export const SidebarBlockedSelect: React.FC<Props> = ({ issueId, submitChanges, watch, disabled = false }) => {
|
||||
export const SidebarBlockedSelect: React.FC<Props> = observer((props) => {
|
||||
const { issueId, submitChanges, watch, disabled = false } = props;
|
||||
// states
|
||||
const [isBlockedModalOpen, setIsBlockedModalOpen] = useState(false);
|
||||
|
||||
const { user } = useUser();
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
// store hooks
|
||||
const { currentUser } = useUser();
|
||||
|
||||
const handleClose = () => {
|
||||
setIsBlockedModalOpen(false);
|
||||
@ -65,7 +67,7 @@ export const SidebarBlockedSelect: React.FC<Props> = ({ issueId, submitChanges,
|
||||
},
|
||||
}));
|
||||
|
||||
if (!user) return;
|
||||
if (!currentUser) return;
|
||||
|
||||
issueService
|
||||
.createIssueRelation(workspaceSlug as string, projectId as string, issueId as string, {
|
||||
@ -128,7 +130,7 @@ export const SidebarBlockedSelect: React.FC<Props> = ({ issueId, submitChanges,
|
||||
related_issues: updatedRelations,
|
||||
});
|
||||
|
||||
if (!user) return;
|
||||
if (!currentUser) return;
|
||||
|
||||
issueService.deleteIssueRelation(
|
||||
workspaceSlug as string,
|
||||
@ -158,4 +160,4 @@ export const SidebarBlockedSelect: React.FC<Props> = ({ issueId, submitChanges,
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
@ -1,18 +1,16 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
// react-hook-form
|
||||
import { UseFormWatch } from "react-hook-form";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { X } from "lucide-react";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
import useUser from "hooks/use-user";
|
||||
// components
|
||||
import { ExistingIssuesListModal } from "components/core";
|
||||
// services
|
||||
import { IssueService } from "services/issue";
|
||||
// icons
|
||||
import { X } from "lucide-react";
|
||||
import { BlockerIcon } from "@plane/ui";
|
||||
// types
|
||||
import { BlockeIssueDetail, IIssue, ISearchIssueResponse } from "types";
|
||||
@ -27,14 +25,17 @@ type Props = {
|
||||
// services
|
||||
const issueService = new IssueService();
|
||||
|
||||
export const SidebarBlockerSelect: React.FC<Props> = ({ issueId, submitChanges, watch, disabled = false }) => {
|
||||
export const SidebarBlockerSelect: React.FC<Props> = observer((props) => {
|
||||
const { issueId, submitChanges, watch, disabled = false } = props;
|
||||
// states
|
||||
const [isBlockerModalOpen, setIsBlockerModalOpen] = useState(false);
|
||||
|
||||
const { user } = useUser();
|
||||
const { setToastAlert } = useToast();
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// toast alert
|
||||
const { setToastAlert } = useToast();
|
||||
// store hooks
|
||||
const { currentUser } = useUser();
|
||||
|
||||
const handleClose = () => {
|
||||
setIsBlockerModalOpen(false);
|
||||
@ -66,7 +67,7 @@ export const SidebarBlockerSelect: React.FC<Props> = ({ issueId, submitChanges,
|
||||
},
|
||||
}));
|
||||
|
||||
if (!user) return;
|
||||
if (!currentUser) return;
|
||||
|
||||
issueService
|
||||
.createIssueRelation(workspaceSlug as string, projectId as string, issueId as string, {
|
||||
@ -138,7 +139,7 @@ export const SidebarBlockerSelect: React.FC<Props> = ({ issueId, submitChanges,
|
||||
issue_relations: updatedBlockers,
|
||||
});
|
||||
|
||||
if (!user) return;
|
||||
if (!currentUser) return;
|
||||
|
||||
issueService.deleteIssueRelation(
|
||||
workspaceSlug as string,
|
||||
@ -168,4 +169,4 @@ export const SidebarBlockerSelect: React.FC<Props> = ({ issueId, submitChanges,
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user