[WEB-3251] improvement: optimize projects API (#6542)
This commit is contained in:
parent
c14fb814c4
commit
10b5c625ef
52 changed files with 535 additions and 316 deletions
|
|
@ -1,6 +1,8 @@
|
|||
import { observer } from "mobx-react";
|
||||
// icons
|
||||
import { Contrast, LayoutGrid, Users } from "lucide-react";
|
||||
import { Contrast, LayoutGrid, Users, Loader as Spinner } from "lucide-react";
|
||||
// plane imports
|
||||
import { Loader } from "@plane/ui";
|
||||
// components
|
||||
import { Logo } from "@/components/common";
|
||||
// helpers
|
||||
|
|
@ -10,19 +12,25 @@ import { useProject } from "@/hooks/store";
|
|||
|
||||
type Props = {
|
||||
projectIds: string[];
|
||||
isLoading: boolean;
|
||||
isUpdating: boolean;
|
||||
};
|
||||
|
||||
export const CustomAnalyticsSidebarProjectsList: React.FC<Props> = observer((props) => {
|
||||
const { projectIds } = props;
|
||||
|
||||
const { getProjectById } = useProject();
|
||||
const { projectIds, isLoading, isUpdating } = props;
|
||||
// store hooks
|
||||
const { getProjectById, getProjectAnalyticsCountById } = useProject();
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col gap-4 h-full">
|
||||
<h4 className="font-medium">Selected Projects</h4>
|
||||
<div className="flex gap-2 items-center">
|
||||
<h4 className="font-medium">Selected Projects</h4>
|
||||
{isUpdating && <Spinner className="animate-spin size-3" />}
|
||||
</div>
|
||||
<div className="relative space-y-6 overflow-hidden overflow-y-auto vertical-scrollbar scrollbar-md">
|
||||
{projectIds.map((projectId) => {
|
||||
const project = getProjectById(projectId);
|
||||
const projectAnalyticsCount = getProjectAnalyticsCountById(projectId);
|
||||
|
||||
if (!project) return;
|
||||
|
||||
|
|
@ -38,27 +46,37 @@ export const CustomAnalyticsSidebarProjectsList: React.FC<Props> = observer((pro
|
|||
</h5>
|
||||
</div>
|
||||
<div className="mt-4 w-full space-y-3 px-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>
|
||||
<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>
|
||||
{isLoading ? (
|
||||
<Loader className="space-y-3">
|
||||
<Loader.Item height="16px" />
|
||||
<Loader.Item height="16px" />
|
||||
<Loader.Item height="16px" />
|
||||
</Loader>
|
||||
) : (
|
||||
<>
|
||||
<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">{projectAnalyticsCount?.total_members}</span>
|
||||
</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>
|
||||
<span className="text-custom-text-200">{projectAnalyticsCount?.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">{projectAnalyticsCount?.total_modules}</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import { useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { mutate } from "swr";
|
||||
import useSWR, { mutate } from "swr";
|
||||
// icons
|
||||
import { CalendarDays, Download, RefreshCw } from "lucide-react";
|
||||
// types
|
||||
|
|
@ -30,19 +30,27 @@ type Props = {
|
|||
|
||||
const analyticsService = new AnalyticsService();
|
||||
|
||||
const PROJECT_ANALYTICS_COUNT_PARAMS = {
|
||||
fields: "total_members,total_cycles,total_modules",
|
||||
};
|
||||
|
||||
export const CustomAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||
const { analytics, params, isProjectLevel = false } = props;
|
||||
// router
|
||||
const { workspaceSlug, projectId, cycleId, moduleId } = useParams();
|
||||
// store hooks
|
||||
const { data: currentUser } = useUser();
|
||||
const { workspaceProjectIds, getProjectById } = useProject();
|
||||
const { workspaceProjectIds, getProjectById, fetchProjectAnalyticsCount } = useProject();
|
||||
const { getWorkspaceById } = useWorkspace();
|
||||
|
||||
const { fetchCycleDetails, getCycleById } = useCycle();
|
||||
const { fetchModuleDetails, getModuleById } = useModule();
|
||||
// fetch project analytics count
|
||||
const { isLoading: isProjectAnalyticsLoading, isValidating: isProjectAnalyticsUpdating } = useSWR(
|
||||
workspaceSlug ? ["projectAnalyticsCount", workspaceSlug] : null,
|
||||
workspaceSlug ? () => fetchProjectAnalyticsCount(workspaceSlug.toString(), PROJECT_ANALYTICS_COUNT_PARAMS) : null
|
||||
);
|
||||
|
||||
const projectDetails = projectId ? getProjectById(projectId.toString()) ?? undefined : undefined;
|
||||
const projectDetails = projectId ? (getProjectById(projectId.toString()) ?? undefined) : undefined;
|
||||
|
||||
const trackExportAnalytics = () => {
|
||||
if (!currentUser) return;
|
||||
|
|
@ -171,7 +179,11 @@ export const CustomAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
|||
<div className={cn("h-full w-full overflow-hidden", isProjectLevel ? "hidden" : "block")}>
|
||||
<>
|
||||
{!isProjectLevel && selectedProjects && selectedProjects.length > 0 && (
|
||||
<CustomAnalyticsSidebarProjectsList projectIds={selectedProjects} />
|
||||
<CustomAnalyticsSidebarProjectsList
|
||||
projectIds={selectedProjects}
|
||||
isLoading={isProjectAnalyticsLoading}
|
||||
isUpdating={isProjectAnalyticsUpdating}
|
||||
/>
|
||||
)}
|
||||
<CustomAnalyticsSidebarHeader />
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export const JoinProject: React.FC = () => {
|
|||
const [isJoiningProject, setIsJoiningProject] = useState(false);
|
||||
// store hooks
|
||||
const { joinProject } = useUserPermissions();
|
||||
const { fetchProjects } = useProject();
|
||||
const { fetchProjectDetails } = useProject();
|
||||
|
||||
const { workspaceSlug, projectId } = useParams();
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ export const JoinProject: React.FC = () => {
|
|||
setIsJoiningProject(true);
|
||||
|
||||
joinProject(workspaceSlug.toString(), projectId.toString())
|
||||
.then(() => fetchProjects(workspaceSlug.toString()))
|
||||
.then(() => fetchProjectDetails(workspaceSlug.toString(), projectId.toString()))
|
||||
.finally(() => setIsJoiningProject(false));
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { ArchiveRestore } from "lucide-react";
|
||||
// types
|
||||
import { IProject } from "@plane/types";
|
||||
|
|
@ -23,6 +24,8 @@ const initialValues: Partial<IProject> = { archive_in: 1 };
|
|||
|
||||
export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
|
||||
const { handleChange } = props;
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
// states
|
||||
const [monthModal, setmonthModal] = useState(false);
|
||||
// store hooks
|
||||
|
|
@ -33,9 +36,9 @@ export const AutoArchiveAutomation: React.FC<Props> = observer((props) => {
|
|||
const isAdmin = allowPermissions(
|
||||
[EUserPermissions.ADMIN],
|
||||
EUserPermissionsLevel.PROJECT,
|
||||
currentProjectDetails?.workspace_detail?.slug,
|
||||
workspaceSlug?.toString(),
|
||||
currentProjectDetails?.id
|
||||
);
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import React, { useState } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// icons
|
||||
import { ArchiveX } from "lucide-react";
|
||||
// types
|
||||
|
|
@ -22,6 +23,8 @@ type Props = {
|
|||
|
||||
export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
||||
const { handleChange } = props;
|
||||
// router
|
||||
const { workspaceSlug } = useParams();
|
||||
// states
|
||||
const [monthModal, setmonthModal] = useState(false);
|
||||
// store hooks
|
||||
|
|
@ -59,7 +62,7 @@ export const AutoCloseAutomation: React.FC<Props> = observer((props) => {
|
|||
const isAdmin = allowPermissions(
|
||||
[EUserPermissions.ADMIN],
|
||||
EUserPermissionsLevel.PROJECT,
|
||||
currentProjectDetails?.workspace_detail?.slug,
|
||||
workspaceSlug?.toString(),
|
||||
currentProjectDetails?.id
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ export const SelectMonthModal: React.FC<Props> = ({ type, initialValues, isOpen,
|
|||
id="close_in"
|
||||
name="close_in"
|
||||
type="number"
|
||||
value={value.toString()}
|
||||
value={value?.toString()}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.close_in)}
|
||||
|
|
@ -127,7 +127,7 @@ export const SelectMonthModal: React.FC<Props> = ({ type, initialValues, isOpen,
|
|||
id="archive_in"
|
||||
name="archive_in"
|
||||
type="number"
|
||||
value={value.toString()}
|
||||
value={value?.toString()}
|
||||
onChange={onChange}
|
||||
ref={ref}
|
||||
hasError={Boolean(errors.archive_in)}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,14 @@ import { PROJECT_BACKGROUND_COLORS } from "@/constants/dashboard";
|
|||
// helpers
|
||||
import { getFileURL } from "@/helpers/file.helper";
|
||||
// hooks
|
||||
import { useEventTracker, useDashboard, useProject, useCommandPalette, useUserPermissions } from "@/hooks/store";
|
||||
import {
|
||||
useEventTracker,
|
||||
useDashboard,
|
||||
useProject,
|
||||
useCommandPalette,
|
||||
useUserPermissions,
|
||||
useMember,
|
||||
} from "@/hooks/store";
|
||||
// plane web constants
|
||||
import { EUserPermissions, EUserPermissionsLevel } from "@/plane-web/constants/user-permissions";
|
||||
|
||||
|
|
@ -31,6 +38,8 @@ const ProjectListItem: React.FC<ProjectListItemProps> = observer((props) => {
|
|||
const { projectId, workspaceSlug } = props;
|
||||
// store hooks
|
||||
const { getProjectById } = useProject();
|
||||
const { getUserDetails } = useMember();
|
||||
// derived values
|
||||
const projectDetails = getProjectById(projectId);
|
||||
|
||||
const randomBgColor = PROJECT_BACKGROUND_COLORS[Math.floor(Math.random() * PROJECT_BACKGROUND_COLORS.length)];
|
||||
|
|
@ -52,13 +61,13 @@ const ProjectListItem: React.FC<ProjectListItemProps> = observer((props) => {
|
|||
</h6>
|
||||
<div className="mt-2">
|
||||
<AvatarGroup>
|
||||
{projectDetails.members?.map((member) => (
|
||||
<Avatar
|
||||
key={member.member_id}
|
||||
src={getFileURL(member.member__avatar_url)}
|
||||
name={member.member__display_name}
|
||||
/>
|
||||
))}
|
||||
{projectDetails.members?.map((memberId) => {
|
||||
const userDetails = getUserDetails(memberId);
|
||||
if (!userDetails) return null;
|
||||
return (
|
||||
<Avatar key={userDetails.id} src={getFileURL(userDetails.avatar_url)} name={userDetails.display_name} />
|
||||
);
|
||||
})}
|
||||
</AvatarGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ export const RecentActivityWidget: React.FC<TRecentWidgetProps> = observer((prop
|
|||
}
|
||||
};
|
||||
|
||||
if (!loader && !isWikiApp && joinedProjectIds?.length === 0) return <NoProjectsEmptyState />;
|
||||
if (loader === "loaded" && !isWikiApp && joinedProjectIds?.length === 0) return <NoProjectsEmptyState />;
|
||||
|
||||
if (!isLoading && recents?.length === 0)
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -19,15 +19,11 @@ import useSize from "@/hooks/use-window-size";
|
|||
|
||||
export const WorkspaceDashboardView = observer(() => {
|
||||
// store hooks
|
||||
const {
|
||||
// captureEvent,
|
||||
setTrackElement,
|
||||
} = useEventTracker();
|
||||
const { captureEvent, setTrackElement } = useEventTracker();
|
||||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { workspaceSlug } = useParams();
|
||||
const { data: currentUser } = useUser();
|
||||
const { data: currentUserProfile, updateTourCompleted } = useUserProfile();
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { homeDashboardId, fetchHomeDashboardWidgets } = useDashboard();
|
||||
const { joinedProjectIds, loader } = useProject();
|
||||
|
||||
|
|
@ -63,7 +59,7 @@ export const WorkspaceDashboardView = observer(() => {
|
|||
)}
|
||||
{homeDashboardId && joinedProjectIds && (
|
||||
<>
|
||||
{joinedProjectIds.length > 0 || loader ? (
|
||||
{joinedProjectIds.length > 0 || loader === "init-loader" ? (
|
||||
<>
|
||||
<IssuePeekOverview />
|
||||
<ContentWrapper
|
||||
|
|
|
|||
|
|
@ -24,17 +24,19 @@ export const ProjectCardList = observer((props: TProjectCardListProps) => {
|
|||
const { toggleCreateProjectModal } = useCommandPalette();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const {
|
||||
loader,
|
||||
fetchStatus,
|
||||
workspaceProjectIds: storeWorkspaceProjectIds,
|
||||
filteredProjectIds: storeFilteredProjectIds,
|
||||
getProjectById,
|
||||
loader,
|
||||
} = useProject();
|
||||
const { searchQuery, currentWorkspaceDisplayFilters } = useProjectFilter();
|
||||
// derived values
|
||||
const workspaceProjectIds = totalProjectIdsProps ?? storeWorkspaceProjectIds;
|
||||
const filteredProjectIds = filteredProjectIdsProps ?? storeFilteredProjectIds;
|
||||
|
||||
if (!filteredProjectIds || !workspaceProjectIds || loader) return <ProjectsLoader />;
|
||||
if (!filteredProjectIds || !workspaceProjectIds || loader === "init-loader" || fetchStatus !== "complete")
|
||||
return <ProjectsLoader />;
|
||||
|
||||
if (workspaceProjectIds?.length === 0 && !currentWorkspaceDisplayFilters?.archived_projects)
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import { renderFormattedDate } from "@/helpers/date-time.helper";
|
|||
import { getFileURL } from "@/helpers/file.helper";
|
||||
import { copyUrlToClipboard } from "@/helpers/string.helper";
|
||||
// hooks
|
||||
import { useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { useMember, useProject, useUserPermissions } from "@/hooks/store";
|
||||
import { useAppRouter } from "@/hooks/use-app-router";
|
||||
import { usePlatformOS } from "@/hooks/use-platform-os";
|
||||
// plane-web constants
|
||||
|
|
@ -51,12 +51,13 @@ export const ProjectCard: React.FC<Props> = observer((props) => {
|
|||
const router = useAppRouter();
|
||||
const { workspaceSlug } = useParams();
|
||||
// store hooks
|
||||
const { getUserDetails } = useMember();
|
||||
const { addProjectToFavorites, removeProjectFromFavorites } = useProject();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
// derived values
|
||||
const projectMembersIds = project.members?.map((member) => member.member_id);
|
||||
const projectMembersIds = project.members;
|
||||
const shouldRenderFavorite = allowPermissions(
|
||||
[EUserPermissions.ADMIN, EUserPermissions.MEMBER],
|
||||
EUserPermissionsLevel.WORKSPACE
|
||||
|
|
@ -249,7 +250,7 @@ export const ProjectCard: React.FC<Props> = observer((props) => {
|
|||
if (project.is_favorite) handleRemoveFromFavorites();
|
||||
else handleAddToFavorites();
|
||||
}}
|
||||
selected={project.is_favorite}
|
||||
selected={!!project.is_favorite}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -281,14 +282,10 @@ export const ProjectCard: React.FC<Props> = observer((props) => {
|
|||
<div className="flex cursor-pointer items-center gap-2 text-custom-text-200">
|
||||
<AvatarGroup showTooltip={false}>
|
||||
{projectMembersIds.map((memberId) => {
|
||||
const member = project.members?.find((m) => m.member_id === memberId);
|
||||
const member = getUserDetails(memberId);
|
||||
if (!member) return null;
|
||||
return (
|
||||
<Avatar
|
||||
key={member.id}
|
||||
name={member.member__display_name}
|
||||
src={getFileURL(member.member__avatar_url)}
|
||||
/>
|
||||
<Avatar key={member.id} name={member.display_name} src={getFileURL(member.avatar_url)} />
|
||||
);
|
||||
})}
|
||||
</AvatarGroup>
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ const ProjectCreateHeader: React.FC<Props> = (props) => {
|
|||
label={t("change_cover")}
|
||||
onChange={onChange}
|
||||
control={control}
|
||||
value={value}
|
||||
value={value ?? null}
|
||||
tabIndex={getIndex("cover_image")}
|
||||
/>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -222,7 +222,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
label="Change cover"
|
||||
control={control}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
value={value ?? null}
|
||||
disabled={!isAdmin}
|
||||
projectId={project.id}
|
||||
/>
|
||||
|
|
@ -263,7 +263,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
<span className="text-xs text-red-500">{errors?.name?.message}</span>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
<h4 className="text-sm">Description</h4>
|
||||
<h4 className="text-sm">Summary</h4>
|
||||
<Controller
|
||||
name="description"
|
||||
control={control}
|
||||
|
|
@ -272,7 +272,7 @@ export const ProjectDetailsForm: FC<IProjectDetailsForm> = (props) => {
|
|||
id="description"
|
||||
name="description"
|
||||
value={value}
|
||||
placeholder="Enter project description"
|
||||
placeholder="Enter project summary"
|
||||
onChange={onChange}
|
||||
className="min-h-[102px] text-sm font-medium"
|
||||
hasError={Boolean(errors?.description)}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ export const JoinProjectModal: React.FC<TJoinProjectModalProps> = (props) => {
|
|||
const [isJoiningLoading, setIsJoiningLoading] = useState(false);
|
||||
// store hooks
|
||||
const { joinProject } = useUserPermissions();
|
||||
const { fetchProjects } = useProject();
|
||||
const { fetchProjectDetails } = useProject();
|
||||
// router
|
||||
const router = useAppRouter();
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ export const JoinProjectModal: React.FC<TJoinProjectModalProps> = (props) => {
|
|||
joinProject(workspaceSlug, project.id)
|
||||
.then(() => {
|
||||
router.push(`/${workspaceSlug}/projects/${project.id}/issues`);
|
||||
fetchProjects(workspaceSlug);
|
||||
fetchProjectDetails(workspaceSlug, project.id);
|
||||
handleClose();
|
||||
})
|
||||
.finally(() => {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
|
|||
// store hooks
|
||||
const { leaveProject } = useUserPermissions();
|
||||
const { data: currentUser } = useUser();
|
||||
const { fetchProjects } = useProject();
|
||||
const { fetchProjectDetails } = useProject();
|
||||
const {
|
||||
project: { removeMemberFromProject },
|
||||
} = useMember();
|
||||
|
|
@ -45,7 +45,7 @@ export const ProjectMemberListItem: React.FC<Props> = observer((props) => {
|
|||
state: "SUCCESS",
|
||||
element: "Project settings members page",
|
||||
});
|
||||
await fetchProjects(workspaceSlug.toString());
|
||||
await fetchProjectDetails(workspaceSlug.toString(), projectId.toString());
|
||||
})
|
||||
.catch((err) =>
|
||||
setToast({
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export const ProjectSettingsMemberDefaults: React.FC = observer(() => {
|
|||
const isAdmin = allowPermissions(
|
||||
[EUserPermissions.ADMIN],
|
||||
EUserPermissionsLevel.PROJECT,
|
||||
currentProjectDetails?.workspace_detail?.slug,
|
||||
workspaceSlug?.toString(),
|
||||
currentProjectDetails?.id
|
||||
);
|
||||
// form info
|
||||
|
|
@ -176,7 +176,7 @@ export const ProjectSettingsMemberDefaults: React.FC = observer(() => {
|
|||
</p>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
value={currentProjectDetails?.guest_view_all_features}
|
||||
value={!!currentProjectDetails?.guest_view_all_features}
|
||||
onChange={() => toggleGuestViewAllIssues(!currentProjectDetails?.guest_view_all_features)}
|
||||
disabled={!isAdmin}
|
||||
size="md"
|
||||
|
|
|
|||
|
|
@ -38,13 +38,13 @@ export const ProjectNavigation: FC<TProjectItemsProps> = observer((props) => {
|
|||
// store hooks
|
||||
const { t } = useTranslation();
|
||||
const { sidebarCollapsed: isSidebarCollapsed, toggleSidebar } = useAppTheme();
|
||||
const { getProjectById } = useProject();
|
||||
const { getPartialProjectById } = useProject();
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// pathname
|
||||
const pathname = usePathname();
|
||||
// derived values
|
||||
const project = getProjectById(projectId);
|
||||
const project = getPartialProjectById(projectId);
|
||||
// handlers
|
||||
const handleProjectClick = () => {
|
||||
if (window.innerWidth < 768) {
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
|
|||
const { sidebarCollapsed: isSidebarCollapsed } = useAppTheme();
|
||||
const { t } = useTranslation();
|
||||
const { setTrackElement } = useEventTracker();
|
||||
const { addProjectToFavorites, removeProjectFromFavorites, getProjectById } = useProject();
|
||||
const { addProjectToFavorites, removeProjectFromFavorites, getPartialProjectById } = useProject();
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
// states
|
||||
|
|
@ -70,7 +70,7 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
|
|||
const router = useRouter();
|
||||
const { workspaceSlug, projectId: URLProjectId } = useParams();
|
||||
// derived values
|
||||
const project = getProjectById(projectId);
|
||||
const project = getPartialProjectById(projectId);
|
||||
// auth
|
||||
const isAdmin = allowPermissions(
|
||||
[EUserPermissions.ADMIN],
|
||||
|
|
@ -337,7 +337,8 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
|
|||
placement="bottom-start"
|
||||
useCaptureForOutsideClick
|
||||
>
|
||||
{isAuthorized && (
|
||||
{/* TODO: Removed is_favorite logic due to the optimization in projects API */}
|
||||
{/* {isAuthorized && (
|
||||
<CustomMenu.MenuItem
|
||||
onClick={project.is_favorite ? handleRemoveFromFavorites : handleAddToFavorites}
|
||||
>
|
||||
|
|
@ -350,7 +351,7 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
|
|||
<span>{project.is_favorite ? t("remove_from_favorites") : t("add_to_favorites")}</span>
|
||||
</span>
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
)} */}
|
||||
|
||||
{/* publish project settings */}
|
||||
{isAdmin && (
|
||||
|
|
@ -359,20 +360,10 @@ export const SidebarProjectsListItem: React.FC<Props> = observer((props) => {
|
|||
<div className="flex h-4 w-4 cursor-pointer items-center justify-center rounded text-custom-sidebar-text-200 transition-all duration-300 hover:bg-custom-sidebar-background-80">
|
||||
<Share2 className="h-3.5 w-3.5 stroke-[1.5]" />
|
||||
</div>
|
||||
<div>{project.anchor ? t("publish_settings") : t("publish")}</div>
|
||||
<div>{t("publish_settings")}</div>
|
||||
</div>
|
||||
</CustomMenu.MenuItem>
|
||||
)}
|
||||
{/* {isAuthorized && (
|
||||
<CustomMenu.MenuItem>
|
||||
<Link href={`/${workspaceSlug}/projects/${project?.id}/draft-issues/`}>
|
||||
<div className="flex items-center justify-start gap-2">
|
||||
<PenSquare className="h-3.5 w-3.5 stroke-[1.5] text-custom-text-300" />
|
||||
<span>Draft issues</span>
|
||||
</div>
|
||||
</Link>
|
||||
</CustomMenu.MenuItem>
|
||||
)} */}
|
||||
<CustomMenu.MenuItem onClick={handleCopyText}>
|
||||
<span className="flex items-center justify-start gap-2">
|
||||
<LinkIcon className="h-3.5 w-3.5 stroke-[1.5]" />
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { Briefcase, ChevronRight, Plus } from "lucide-react";
|
|||
import { Disclosure, Transition } from "@headlessui/react";
|
||||
import { useTranslation } from "@plane/i18n";
|
||||
// ui
|
||||
import { TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
|
||||
import { Loader, TOAST_TYPE, Tooltip, setToast } from "@plane/ui";
|
||||
// components
|
||||
import { CreateProjectModal } from "@/components/project";
|
||||
import { SidebarProjectsListItem } from "@/components/workspace";
|
||||
|
|
@ -41,7 +41,7 @@ export const SidebarProjectsList: FC = observer(() => {
|
|||
const { setTrackElement } = useEventTracker();
|
||||
const { allowPermissions } = useUserPermissions();
|
||||
|
||||
const { getProjectById, joinedProjectIds: joinedProjects, updateProjectView } = useProject();
|
||||
const { loader, getPartialProjectById, joinedProjectIds: joinedProjects, updateProjectView } = useProject();
|
||||
// router params
|
||||
const { workspaceSlug } = useParams();
|
||||
const pathname = usePathname();
|
||||
|
|
@ -72,7 +72,7 @@ export const SidebarProjectsList: FC = observer(() => {
|
|||
|
||||
const joinedProjectsList: TProject[] = [];
|
||||
joinedProjects.map((projectId) => {
|
||||
const projectDetails = getProjectById(projectId);
|
||||
const projectDetails = getPartialProjectById(projectId);
|
||||
if (projectDetails) joinedProjectsList.push(projectDetails);
|
||||
});
|
||||
|
||||
|
|
@ -232,6 +232,13 @@ export const SidebarProjectsList: FC = observer(() => {
|
|||
leaveFrom="transform scale-100 opacity-100"
|
||||
leaveTo="transform scale-95 opacity-0"
|
||||
>
|
||||
{loader === "init-loader" && (
|
||||
<Loader className="w-full space-y-1.5">
|
||||
{Array.from({ length: 4 }).map((_, index) => (
|
||||
<Loader.Item key={index} height="28px" />
|
||||
))}
|
||||
</Loader>
|
||||
)}
|
||||
{isAllProjectsListOpen && (
|
||||
<Disclosure.Panel
|
||||
as="div"
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
|
|||
if (projectExists && projectId && hasPermissionToCurrentProject === false) return <JoinProject />;
|
||||
|
||||
// check if the project info is not found.
|
||||
if (!loader && !projectExists && projectId && !!hasPermissionToCurrentProject === false)
|
||||
if (loader === "loaded" && !projectExists && projectId && !!hasPermissionToCurrentProject === false)
|
||||
return (
|
||||
<div className="grid h-screen place-items-center bg-custom-background-100">
|
||||
<EmptyState
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export const WorkspaceAuthWrapper: FC<IWorkspaceAuthWrapper> = observer((props)
|
|||
const { resolvedTheme } = useTheme();
|
||||
// store hooks
|
||||
const { signOut, data: currentUser } = useUser();
|
||||
const { fetchProjects } = useProject();
|
||||
const { fetchPartialProjects } = useProject();
|
||||
const { fetchFavorite } = useFavorite();
|
||||
const {
|
||||
workspace: { fetchWorkspaceMembers },
|
||||
|
|
@ -74,8 +74,8 @@ export const WorkspaceAuthWrapper: FC<IWorkspaceAuthWrapper> = observer((props)
|
|||
|
||||
// fetching workspace projects
|
||||
useSWR(
|
||||
workspaceSlug && currentWorkspace ? `WORKSPACE_PROJECTS_${workspaceSlug}` : null,
|
||||
workspaceSlug && currentWorkspace ? () => fetchProjects(workspaceSlug.toString()) : null,
|
||||
workspaceSlug && currentWorkspace ? `WORKSPACE_PARTIAL_PROJECTS_${workspaceSlug}` : null,
|
||||
workspaceSlug && currentWorkspace ? () => fetchPartialProjects(workspaceSlug.toString()) : null,
|
||||
{ revalidateIfStale: false, revalidateOnFocus: false }
|
||||
);
|
||||
// fetch workspace members
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
import type { GithubRepositoriesResponse, ISearchIssueResponse, TProjectIssuesSearchParams } from "@plane/types";
|
||||
import type {
|
||||
GithubRepositoriesResponse,
|
||||
ISearchIssueResponse,
|
||||
TProjectAnalyticsCount,
|
||||
TProjectAnalyticsCountParams,
|
||||
TProjectIssuesSearchParams,
|
||||
} from "@plane/types";
|
||||
// helpers
|
||||
import { API_BASE_URL } from "@/helpers/common.helper";
|
||||
// plane web types
|
||||
import { TProject } from "@/plane-web/types";
|
||||
import { TProject, TPartialProject } from "@/plane-web/types";
|
||||
// services
|
||||
import { APIService } from "@/services/api.service";
|
||||
|
||||
|
|
@ -31,7 +37,7 @@ export class ProjectService extends APIService {
|
|||
});
|
||||
}
|
||||
|
||||
async getProjects(workspaceSlug: string): Promise<TProject[]> {
|
||||
async getProjectsLite(workspaceSlug: string): Promise<TPartialProject[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
|
|
@ -39,6 +45,14 @@ export class ProjectService extends APIService {
|
|||
});
|
||||
}
|
||||
|
||||
async getProjects(workspaceSlug: string): Promise<TProject[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/details/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async getProject(workspaceSlug: string, projectId: string): Promise<TProject> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/`)
|
||||
.then((response) => response?.data)
|
||||
|
|
@ -47,6 +61,19 @@ export class ProjectService extends APIService {
|
|||
});
|
||||
}
|
||||
|
||||
async getProjectAnalyticsCount(
|
||||
workspaceSlug: string,
|
||||
params?: TProjectAnalyticsCountParams
|
||||
): Promise<TProjectAnalyticsCount[]> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/project-stats/`, {
|
||||
params,
|
||||
})
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async updateProject(workspaceSlug: string, projectId: string, data: Partial<TProject>): Promise<TProject> {
|
||||
return this.patch(`/api/workspaces/${workspaceSlug}/projects/${projectId}/`, data)
|
||||
.then((response) => response?.data)
|
||||
|
|
@ -63,14 +90,6 @@ export class ProjectService extends APIService {
|
|||
});
|
||||
}
|
||||
|
||||
async fetchProjectEpicProperties(workspaceSlug: string, projectId: string): Promise<any> {
|
||||
return this.get(`/api/workspaces/${workspaceSlug}/projects/${projectId}/epic-properties/`)
|
||||
.then((response) => response?.data)
|
||||
.catch((error) => {
|
||||
throw error?.response?.data;
|
||||
});
|
||||
}
|
||||
|
||||
async setProjectView(
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
|
|
|
|||
|
|
@ -1,15 +1,11 @@
|
|||
import set from "lodash/set";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import uniq from "lodash/uniq";
|
||||
import update from "lodash/update";
|
||||
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
||||
import { computedFn } from "mobx-utils";
|
||||
// types
|
||||
import {
|
||||
IProjectBulkAddFormData,
|
||||
IProjectMember,
|
||||
IProjectMemberLite,
|
||||
IProjectMembership,
|
||||
IUserLite,
|
||||
} from "@plane/types";
|
||||
import { IProjectBulkAddFormData, IProjectMember, IProjectMembership, IUserLite } from "@plane/types";
|
||||
// plane-web constants
|
||||
import { EUserPermissions } from "@/plane-web/constants/user-permissions";
|
||||
// services
|
||||
|
|
@ -166,8 +162,11 @@ export class ProjectMemberStore implements IProjectMemberStore {
|
|||
set(this.projectMemberMap, [projectId, member.member], member);
|
||||
});
|
||||
});
|
||||
this.projectRoot.projectMap[projectId].members = this.projectRoot.projectMap?.[projectId]?.members.concat(
|
||||
data.members as unknown as IProjectMemberLite[]
|
||||
update(this.projectRoot.projectMap, [projectId, "members"], (memberIds) =>
|
||||
uniq([...memberIds, ...data.members.map((m) => m.member_id)])
|
||||
);
|
||||
this.projectRoot.projectMap[projectId].members = this.projectRoot.projectMap?.[projectId]?.members?.concat(
|
||||
data.members.map((m) => m.member_id)
|
||||
);
|
||||
|
||||
return response;
|
||||
|
|
@ -218,8 +217,8 @@ export class ProjectMemberStore implements IProjectMemberStore {
|
|||
runInAction(() => {
|
||||
delete this.projectMemberMap?.[projectId]?.[userId];
|
||||
});
|
||||
this.projectRoot.projectMap[projectId].members = this.projectRoot.projectMap?.[projectId]?.members.filter(
|
||||
(member) => member.id !== userId
|
||||
this.projectRoot.projectMap[projectId].members = this.projectRoot.projectMap?.[projectId]?.members?.filter(
|
||||
(memberId) => memberId !== userId
|
||||
);
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
import cloneDeep from "lodash/cloneDeep";
|
||||
import set from "lodash/set";
|
||||
import sortBy from "lodash/sortBy";
|
||||
import update from "lodash/update";
|
||||
import { observable, action, computed, makeObservable, runInAction } from "mobx";
|
||||
import { computedFn } from "mobx-utils";
|
||||
// plane imports
|
||||
import { TFetchStatus, TLoader, TProjectAnalyticsCount, TProjectAnalyticsCountParams } from "@plane/types";
|
||||
// helpers
|
||||
import { orderProjects, shouldFilterProject } from "@/helpers/project.helper";
|
||||
// services
|
||||
import { TProject } from "@/plane-web/types/projects";
|
||||
import { TProject, TPartialProject } from "@/plane-web/types/projects";
|
||||
import { IssueLabelService, IssueService } from "@/services/issue";
|
||||
import { ProjectService, ProjectStateService, ProjectArchiveService } from "@/services/project";
|
||||
// store
|
||||
|
|
@ -16,10 +20,10 @@ type ProjectOverviewCollapsible = "links" | "attachments";
|
|||
export interface IProjectStore {
|
||||
// observables
|
||||
isUpdatingProject: boolean;
|
||||
loader: boolean;
|
||||
projectMap: {
|
||||
[projectId: string]: TProject; // projectId: project Info
|
||||
};
|
||||
loader: TLoader;
|
||||
fetchStatus: TFetchStatus;
|
||||
projectMap: Record<string, TProject>; // projectId: project info
|
||||
projectAnalyticsCountMap: Record<string, TProjectAnalyticsCount>; // projectId: project analytics count
|
||||
// computed
|
||||
filteredProjectIds: string[] | undefined;
|
||||
workspaceProjectIds: string[] | undefined;
|
||||
|
|
@ -30,7 +34,9 @@ export interface IProjectStore {
|
|||
currentProjectDetails: TProject | undefined;
|
||||
// actions
|
||||
getProjectById: (projectId: string | undefined | null) => TProject | undefined;
|
||||
getPartialProjectById: (projectId: string | undefined | null) => TPartialProject | undefined;
|
||||
getProjectIdentifierById: (projectId: string | undefined | null) => string;
|
||||
getProjectAnalyticsCountById: (projectId: string | undefined | null) => TProjectAnalyticsCount | undefined;
|
||||
// collapsible
|
||||
openCollapsibleSection: ProjectOverviewCollapsible[];
|
||||
lastCollapsibleAction: ProjectOverviewCollapsible | null;
|
||||
|
|
@ -40,8 +46,13 @@ export interface IProjectStore {
|
|||
toggleOpenCollapsibleSection: (section: ProjectOverviewCollapsible) => void;
|
||||
|
||||
// fetch actions
|
||||
fetchPartialProjects: (workspaceSlug: string) => Promise<TPartialProject[]>;
|
||||
fetchProjects: (workspaceSlug: string) => Promise<TProject[]>;
|
||||
fetchProjectDetails: (workspaceSlug: string, projectId: string) => Promise<TProject>;
|
||||
fetchProjectAnalyticsCount: (
|
||||
workspaceSlug: string,
|
||||
params?: TProjectAnalyticsCountParams
|
||||
) => Promise<TProjectAnalyticsCount[]>;
|
||||
// favorites actions
|
||||
addProjectToFavorites: (workspaceSlug: string, projectId: string) => Promise<any>;
|
||||
removeProjectFromFavorites: (workspaceSlug: string, projectId: string) => Promise<any>;
|
||||
|
|
@ -59,10 +70,10 @@ export interface IProjectStore {
|
|||
export class ProjectStore implements IProjectStore {
|
||||
// observables
|
||||
isUpdatingProject: boolean = false;
|
||||
loader: boolean = false;
|
||||
projectMap: {
|
||||
[projectId: string]: TProject; // projectId: project Info
|
||||
} = {};
|
||||
loader: TLoader = "init-loader";
|
||||
fetchStatus: TFetchStatus = undefined;
|
||||
projectMap: Record<string, TProject> = {};
|
||||
projectAnalyticsCountMap: Record<string, TProjectAnalyticsCount> = {};
|
||||
openCollapsibleSection: ProjectOverviewCollapsible[] = [];
|
||||
lastCollapsibleAction: ProjectOverviewCollapsible | null = null;
|
||||
|
||||
|
|
@ -80,7 +91,9 @@ export class ProjectStore implements IProjectStore {
|
|||
// observables
|
||||
isUpdatingProject: observable,
|
||||
loader: observable.ref,
|
||||
fetchStatus: observable.ref,
|
||||
projectMap: observable,
|
||||
projectAnalyticsCountMap: observable,
|
||||
openCollapsibleSection: observable.ref,
|
||||
lastCollapsibleAction: observable.ref,
|
||||
// computed
|
||||
|
|
@ -92,8 +105,10 @@ export class ProjectStore implements IProjectStore {
|
|||
joinedProjectIds: computed,
|
||||
favoriteProjectIds: computed,
|
||||
// fetch actions
|
||||
fetchPartialProjects: action,
|
||||
fetchProjects: action,
|
||||
fetchProjectDetails: action,
|
||||
fetchProjectAnalyticsCount: action,
|
||||
// favorites actions
|
||||
addProjectToFavorites: action,
|
||||
removeProjectFromFavorites: action,
|
||||
|
|
@ -241,6 +256,31 @@ export class ProjectStore implements IProjectStore {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* get Workspace projects partial data using workspace slug
|
||||
* @param workspaceSlug
|
||||
* @returns Promise<TPartialProject[]>
|
||||
*
|
||||
*/
|
||||
fetchPartialProjects = async (workspaceSlug: string) => {
|
||||
try {
|
||||
this.loader = "init-loader";
|
||||
const projectsResponse = await this.projectService.getProjectsLite(workspaceSlug);
|
||||
runInAction(() => {
|
||||
projectsResponse.forEach((project) => {
|
||||
update(this.projectMap, [project.id], (p) => ({ ...p, ...project }));
|
||||
});
|
||||
this.loader = "loaded";
|
||||
this.fetchStatus = "partial";
|
||||
});
|
||||
return projectsResponse;
|
||||
} catch (error) {
|
||||
console.log("Failed to fetch project from workspace store");
|
||||
this.loader = "loaded";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* get Workspace projects using workspace slug
|
||||
* @param workspaceSlug
|
||||
|
|
@ -249,18 +289,23 @@ export class ProjectStore implements IProjectStore {
|
|||
*/
|
||||
fetchProjects = async (workspaceSlug: string) => {
|
||||
try {
|
||||
this.loader = true;
|
||||
if (this.workspaceProjectIds && this.workspaceProjectIds.length > 0) {
|
||||
this.loader = "mutation";
|
||||
} else {
|
||||
this.loader = "init-loader";
|
||||
}
|
||||
const projectsResponse = await this.projectService.getProjects(workspaceSlug);
|
||||
runInAction(() => {
|
||||
projectsResponse.forEach((project) => {
|
||||
set(this.projectMap, [project.id], project);
|
||||
update(this.projectMap, [project.id], (p) => ({ ...p, ...project }));
|
||||
});
|
||||
this.loader = false;
|
||||
this.loader = "loaded";
|
||||
this.fetchStatus = "complete";
|
||||
});
|
||||
return projectsResponse;
|
||||
} catch (error) {
|
||||
console.log("Failed to fetch project from workspace store");
|
||||
this.loader = false;
|
||||
this.loader = "loaded";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
|
@ -275,7 +320,7 @@ export class ProjectStore implements IProjectStore {
|
|||
try {
|
||||
const response = await this.projectService.getProject(workspaceSlug, projectId);
|
||||
runInAction(() => {
|
||||
set(this.projectMap, [projectId], response);
|
||||
update(this.projectMap, [projectId], (p) => ({ ...p, ...response }));
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
|
|
@ -284,6 +329,30 @@ export class ProjectStore implements IProjectStore {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches project analytics count using workspace slug and project id
|
||||
* @param workspaceSlug
|
||||
* @param params TProjectAnalyticsCountParams
|
||||
* @returns Promise<TProjectAnalyticsCount[]>
|
||||
*/
|
||||
fetchProjectAnalyticsCount = async (
|
||||
workspaceSlug: string,
|
||||
params?: TProjectAnalyticsCountParams
|
||||
): Promise<TProjectAnalyticsCount[]> => {
|
||||
try {
|
||||
const response = await this.projectService.getProjectAnalyticsCount(workspaceSlug, params);
|
||||
runInAction(() => {
|
||||
for (const analyticsData of response) {
|
||||
set(this.projectAnalyticsCountMap, [analyticsData.id], analyticsData);
|
||||
}
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.log("Failed to fetch project analytics count", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns project details using project id
|
||||
* @param projectId
|
||||
|
|
@ -294,6 +363,17 @@ export class ProjectStore implements IProjectStore {
|
|||
return projectInfo;
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns project lite using project id
|
||||
* This method is used just for type safety
|
||||
* @param projectId
|
||||
* @returns TPartialProject | null
|
||||
*/
|
||||
getPartialProjectById = computedFn((projectId: string | undefined | null) => {
|
||||
const projectInfo = this.projectMap[projectId ?? ""] || undefined;
|
||||
return projectInfo;
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns project identifier using project id
|
||||
* @param projectId
|
||||
|
|
@ -304,6 +384,16 @@ export class ProjectStore implements IProjectStore {
|
|||
return projectInfo?.identifier;
|
||||
});
|
||||
|
||||
/**
|
||||
* Returns project analytics count using project id
|
||||
* @param projectId
|
||||
* @returns TProjectAnalyticsCount[]
|
||||
*/
|
||||
getProjectAnalyticsCountById = computedFn((projectId: string | undefined | null) => {
|
||||
if (!projectId) return undefined;
|
||||
return this.projectAnalyticsCountMap?.[projectId];
|
||||
});
|
||||
|
||||
/**
|
||||
* Adds project to favorites and updates project favorite status in the store
|
||||
* @param workspaceSlug
|
||||
|
|
@ -415,8 +505,8 @@ export class ProjectStore implements IProjectStore {
|
|||
* @returns Promise<TProject>
|
||||
*/
|
||||
updateProject = async (workspaceSlug: string, projectId: string, data: Partial<TProject>) => {
|
||||
const projectDetails = cloneDeep(this.getProjectById(projectId));
|
||||
try {
|
||||
const projectDetails = this.getProjectById(projectId);
|
||||
runInAction(() => {
|
||||
set(this.projectMap, [projectId], { ...projectDetails, ...data });
|
||||
this.isUpdatingProject = true;
|
||||
|
|
@ -428,9 +518,8 @@ export class ProjectStore implements IProjectStore {
|
|||
return response;
|
||||
} catch (error) {
|
||||
console.log("Failed to create project from project store");
|
||||
this.fetchProjects(workspaceSlug);
|
||||
this.fetchProjectDetails(workspaceSlug, projectId);
|
||||
runInAction(() => {
|
||||
set(this.projectMap, [projectId], projectDetails);
|
||||
this.isUpdatingProject = false;
|
||||
});
|
||||
throw error;
|
||||
|
|
@ -454,7 +543,6 @@ export class ProjectStore implements IProjectStore {
|
|||
});
|
||||
} catch (error) {
|
||||
console.log("Failed to delete project from project store");
|
||||
this.fetchProjects(workspaceSlug);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
|
@ -476,8 +564,6 @@ export class ProjectStore implements IProjectStore {
|
|||
})
|
||||
.catch((error) => {
|
||||
console.log("Failed to archive project from project store");
|
||||
this.fetchProjects(workspaceSlug);
|
||||
this.fetchProjectDetails(workspaceSlug, projectId);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
|
@ -498,8 +584,6 @@ export class ProjectStore implements IProjectStore {
|
|||
})
|
||||
.catch((error) => {
|
||||
console.log("Failed to restore project from project store");
|
||||
this.fetchProjects(workspaceSlug);
|
||||
this.fetchProjectDetails(workspaceSlug, projectId);
|
||||
throw error;
|
||||
});
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue