mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge branch 'refactor/space-app' of github.com:makeplane/plane into refactor/space-app
This commit is contained in:
commit
0f05be355f
@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
params: {
|
params: {
|
||||||
@ -6,10 +8,10 @@ type Props = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProjectIssuesLayout = async (props: Props) => {
|
const IssuesLayout = (props: Props) => {
|
||||||
const { children } = props;
|
const { children } = props;
|
||||||
|
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProjectIssuesLayout;
|
export default IssuesLayout;
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { notFound, useSearchParams } from "next/navigation";
|
import { notFound, useSearchParams, useRouter } from "next/navigation";
|
||||||
// components
|
// components
|
||||||
import { LogoSpinner } from "@/components/common";
|
import { LogoSpinner } from "@/components/common";
|
||||||
// helpers
|
|
||||||
import { navigate } from "@/helpers/actions";
|
|
||||||
// services
|
// services
|
||||||
import PublishService from "@/services/publish.service";
|
import PublishService from "@/services/publish.service";
|
||||||
const publishService = new PublishService();
|
const publishService = new PublishService();
|
||||||
@ -17,15 +15,17 @@ type Props = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProjectIssuesPage = (props: Props) => {
|
const IssuesPage = (props: Props) => {
|
||||||
const { params } = props;
|
const { params } = props;
|
||||||
const { workspaceSlug, projectId } = params;
|
const { workspaceSlug, projectId } = params;
|
||||||
// states
|
// states
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
|
// router
|
||||||
|
const router = useRouter();
|
||||||
// params
|
// params
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const board = searchParams.get("board") || undefined;
|
const board = searchParams.get("board");
|
||||||
const peekId = searchParams.get("peekId") || undefined;
|
const peekId = searchParams.get("peekId");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!workspaceSlug || !projectId) return;
|
if (!workspaceSlug || !projectId) return;
|
||||||
@ -39,15 +39,16 @@ const ProjectIssuesPage = (props: Props) => {
|
|||||||
if (board) params.append("board", board);
|
if (board) params.append("board", board);
|
||||||
if (peekId) params.append("peekId", peekId);
|
if (peekId) params.append("peekId", peekId);
|
||||||
if (params.toString()) url += `?${params.toString()}`;
|
if (params.toString()) url += `?${params.toString()}`;
|
||||||
navigate(url);
|
router.push(url);
|
||||||
|
// navigate(url);
|
||||||
} else throw Error("Invalid entity name");
|
} else throw Error("Invalid entity name");
|
||||||
})
|
})
|
||||||
.catch(() => setError(true));
|
.catch(() => setError(true));
|
||||||
}, [board, peekId, projectId, workspaceSlug]);
|
}, [board, peekId, projectId, router, workspaceSlug]);
|
||||||
|
|
||||||
if (error) notFound();
|
if (error) notFound();
|
||||||
|
|
||||||
return <LogoSpinner />;
|
return <LogoSpinner />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProjectIssuesPage;
|
export default IssuesPage;
|
||||||
|
@ -1,38 +1,47 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Image from "next/image";
|
// ui
|
||||||
import { useTheme } from "next-themes";
|
|
||||||
import { Button } from "@plane/ui";
|
import { Button } from "@plane/ui";
|
||||||
// assets
|
|
||||||
import InstanceFailureDarkImage from "@/public/instance/instance-failure-dark.svg";
|
|
||||||
import InstanceFailureImage from "@/public/instance/instance-failure.svg";
|
|
||||||
|
|
||||||
export default function InstanceError() {
|
|
||||||
const { resolvedTheme } = useTheme();
|
|
||||||
|
|
||||||
const instanceImage = resolvedTheme === "dark" ? InstanceFailureDarkImage : InstanceFailureImage;
|
|
||||||
|
|
||||||
|
const ErrorPage = () => {
|
||||||
const handleRetry = () => {
|
const handleRetry = () => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-screen overflow-x-hidden overflow-y-auto container px-5 mx-auto flex justify-center items-center">
|
<div className="grid h-screen place-items-center p-4">
|
||||||
<div className="w-auto max-w-2xl relative space-y-8 py-10">
|
<div className="space-y-8 text-center">
|
||||||
<div className="relative flex flex-col justify-center items-center space-y-4">
|
<div className="space-y-2">
|
||||||
<Image src={instanceImage} alt="Plane instance failure image" />
|
<h3 className="text-lg font-semibold">Exception Detected!</h3>
|
||||||
<h3 className="font-medium text-2xl text-white">Unable to fetch instance details.</h3>
|
<p className="mx-auto w-1/2 text-sm text-custom-text-200">
|
||||||
<p className="font-medium text-base text-center">
|
We{"'"}re Sorry! An exception has been detected, and our engineering team has been notified. We apologize
|
||||||
We were unable to fetch the details of the instance. <br />
|
for any inconvenience this may have caused. Please reach out to our engineering team at{" "}
|
||||||
Fret not, it might just be a connectivity issue.
|
<a href="mailto:support@plane.so" className="text-custom-primary">
|
||||||
|
support@plane.so
|
||||||
|
</a>{" "}
|
||||||
|
or on our{" "}
|
||||||
|
<a
|
||||||
|
href="https://discord.com/invite/A92xrEGCge"
|
||||||
|
target="_blank"
|
||||||
|
className="text-custom-primary"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
Discord
|
||||||
|
</a>{" "}
|
||||||
|
server for further assistance.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-center">
|
<div className="flex items-center justify-center gap-2">
|
||||||
<Button size="md" onClick={handleRetry}>
|
<Button variant="primary" size="md" onClick={handleRetry}>
|
||||||
Retry
|
Refresh
|
||||||
</Button>
|
</Button>
|
||||||
|
{/* <Button variant="neutral-primary" size="md" onClick={() => {}}>
|
||||||
|
Sign out
|
||||||
|
</Button> */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default ErrorPage;
|
||||||
|
@ -5,7 +5,7 @@ import Image from "next/image";
|
|||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
// components
|
// components
|
||||||
import { LogoSpinner } from "@/components/common";
|
import { LogoSpinner } from "@/components/common";
|
||||||
import IssueNavbar from "@/components/issues/navbar";
|
import { IssuesNavbarRoot } from "@/components/issues";
|
||||||
// hooks
|
// hooks
|
||||||
import { usePublish, usePublishList } from "@/hooks/store";
|
import { usePublish, usePublishList } from "@/hooks/store";
|
||||||
// assets
|
// assets
|
||||||
@ -18,7 +18,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProjectIssuesLayout = observer((props: Props) => {
|
const IssuesLayout = observer((props: Props) => {
|
||||||
const { children, params } = props;
|
const { children, params } = props;
|
||||||
// params
|
// params
|
||||||
const { anchor } = params;
|
const { anchor } = params;
|
||||||
@ -33,7 +33,7 @@ const ProjectIssuesLayout = observer((props: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div className="relative flex h-screen min-h-[500px] w-screen flex-col overflow-hidden">
|
<div className="relative flex h-screen min-h-[500px] w-screen flex-col overflow-hidden">
|
||||||
<div className="relative flex h-[60px] flex-shrink-0 select-none items-center border-b border-custom-border-300 bg-custom-sidebar-background-100">
|
<div className="relative flex h-[60px] flex-shrink-0 select-none items-center border-b border-custom-border-300 bg-custom-sidebar-background-100">
|
||||||
<IssueNavbar publishSettings={publishSettings} />
|
<IssuesNavbarRoot publishSettings={publishSettings} />
|
||||||
</div>
|
</div>
|
||||||
<div className="relative h-full w-full overflow-hidden bg-custom-background-90">{children}</div>
|
<div className="relative h-full w-full overflow-hidden bg-custom-background-90">{children}</div>
|
||||||
<a
|
<a
|
||||||
@ -53,4 +53,4 @@ const ProjectIssuesLayout = observer((props: Props) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ProjectIssuesLayout;
|
export default IssuesLayout;
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
// components
|
// components
|
||||||
import { ProjectDetailsView } from "@/components/views";
|
import { IssuesLayoutsRoot } from "@/components/issues";
|
||||||
// hooks
|
// hooks
|
||||||
import { usePublish } from "@/hooks/store";
|
import { usePublish } from "@/hooks/store";
|
||||||
|
|
||||||
@ -13,7 +13,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProjectIssuesPage =observer ((props: Props) => {
|
const IssuesPage = observer((props: Props) => {
|
||||||
const { params } = props;
|
const { params } = props;
|
||||||
const { anchor } = params;
|
const { anchor } = params;
|
||||||
// params
|
// params
|
||||||
@ -24,7 +24,7 @@ const ProjectIssuesPage =observer ((props: Props) => {
|
|||||||
|
|
||||||
if (!publishSettings) return null;
|
if (!publishSettings) return null;
|
||||||
|
|
||||||
return <ProjectDetailsView peekId={peekId} publishSettings={publishSettings} />;
|
return <IssuesLayoutsRoot peekId={peekId} publishSettings={publishSettings} />;
|
||||||
});
|
});
|
||||||
|
|
||||||
export default ProjectIssuesPage;
|
export default IssuesPage;
|
||||||
|
@ -4,7 +4,7 @@ import { observer } from "mobx-react-lite";
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
// components
|
// components
|
||||||
import { UserAvatar } from "@/components/issues/navbar/user-avatar";
|
import { UserAvatar } from "@/components/issues";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser } from "@/hooks/store";
|
import { useUser } from "@/hooks/store";
|
||||||
// assets
|
// assets
|
||||||
@ -25,7 +25,7 @@ export const UserLoggedIn = observer(() => {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-screen w-screen">
|
<div className="flex flex-col h-screen w-screen">
|
||||||
<div className="relative flex w-full items-center justify-between gap-4 border-b border-custom-border-200 px-6 py-5">
|
<div className="relative flex w-full items-center justify-between gap-4 border-b border-custom-border-200 px-6 py-5">
|
||||||
<div>
|
<div className="h-[30px] w-[133px]">
|
||||||
<Image src={logo} alt="Plane logo" />
|
<Image src={logo} alt="Plane logo" />
|
||||||
</div>
|
</div>
|
||||||
<UserAvatar />
|
<UserAvatar />
|
||||||
|
@ -1,3 +1,2 @@
|
|||||||
export * from "./latest-feature-block";
|
|
||||||
export * from "./project-logo";
|
export * from "./project-logo";
|
||||||
export * from "./logo-spinner";
|
export * from "./logo-spinner";
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
import Image from "next/image";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useTheme } from "next-themes";
|
|
||||||
// icons
|
|
||||||
import { Lightbulb } from "lucide-react";
|
|
||||||
// images
|
|
||||||
import latestFeatures from "public/onboarding/onboarding-pages.svg";
|
|
||||||
|
|
||||||
export const LatestFeatureBlock = () => {
|
|
||||||
const { resolvedTheme } = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="mx-auto mt-16 flex rounded-[3.5px] border border-onboarding-border-200 bg-onboarding-background-100 py-2 sm:w-96">
|
|
||||||
<Lightbulb className="mx-3 mr-2 h-7 w-7" />
|
|
||||||
<p className="text-left text-sm text-onboarding-text-100">
|
|
||||||
Pages gets a facelift! Write anything and use Galileo to help you start.{" "}
|
|
||||||
<Link href="https://plane.so/changelog" target="_blank" rel="noopener noreferrer">
|
|
||||||
<span className="text-sm font-medium underline hover:cursor-pointer">Learn more</span>
|
|
||||||
</Link>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`mx-auto mt-8 overflow-hidden rounded-md border border-onboarding-border-200 object-cover sm:h-52 sm:w-96 ${
|
|
||||||
resolvedTheme === "dark" ? "bg-onboarding-background-100" : "bg-custom-primary-70"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="h-[90%]">
|
|
||||||
<Image
|
|
||||||
src={latestFeatures}
|
|
||||||
alt="Plane Issues"
|
|
||||||
className={`-mt-2 ml-10 h-full rounded-md ${
|
|
||||||
resolvedTheme === "dark" ? "bg-onboarding-background-100" : "bg-custom-primary-70"
|
|
||||||
} `}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,2 +1 @@
|
|||||||
export * from "./not-ready-view";
|
|
||||||
export * from "./instance-failure-view";
|
export * from "./instance-failure-view";
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { FC } from "react";
|
|
||||||
import Image from "next/image";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useTheme } from "next-themes";
|
|
||||||
// ui
|
|
||||||
import { Button } from "@plane/ui";
|
|
||||||
// helper
|
|
||||||
import { GOD_MODE_URL, SPACE_BASE_PATH } from "@/helpers/common.helper";
|
|
||||||
// images
|
|
||||||
import PlaneTakeOffImage from "@/public/instance/plane-takeoff.png";
|
|
||||||
import PlaneBackgroundPatternDark from "public/auth/background-pattern-dark.svg";
|
|
||||||
import PlaneBackgroundPattern from "public/auth/background-pattern.svg";
|
|
||||||
import BlackHorizontalLogo from "public/plane-logos/black-horizontal-with-blue-logo.png";
|
|
||||||
import WhiteHorizontalLogo from "public/plane-logos/white-horizontal-with-blue-logo.png";
|
|
||||||
|
|
||||||
export const InstanceNotReady: FC = () => {
|
|
||||||
const { resolvedTheme } = useTheme();
|
|
||||||
const patternBackground = resolvedTheme === "dark" ? PlaneBackgroundPatternDark : PlaneBackgroundPattern;
|
|
||||||
|
|
||||||
const logo = resolvedTheme === "light" ? BlackHorizontalLogo : WhiteHorizontalLogo;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative">
|
|
||||||
<div className="h-screen w-full overflow-hidden overflow-y-auto flex flex-col">
|
|
||||||
<div className="container h-[110px] flex-shrink-0 mx-auto px-5 lg:px-0 flex items-center justify-between gap-5 z-50">
|
|
||||||
<div className="flex items-center gap-x-2 py-10">
|
|
||||||
<Link href={`${SPACE_BASE_PATH}/`} className="h-[30px] w-[133px]">
|
|
||||||
<Image src={logo} alt="Plane logo" />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="absolute inset-0 z-0">
|
|
||||||
<Image src={patternBackground} className="w-screen h-full object-cover" alt="Plane background pattern" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="relative z-10 mb-[110px] flex-grow">
|
|
||||||
<div className="h-full w-full relative container px-5 mx-auto flex justify-center items-center">
|
|
||||||
<div className="w-auto max-w-2xl relative space-y-8 py-10">
|
|
||||||
<div className="relative flex flex-col justify-center items-center space-y-4">
|
|
||||||
<h1 className="text-3xl font-bold pb-3">Welcome aboard Plane!</h1>
|
|
||||||
<Image src={PlaneTakeOffImage} alt="Plane Logo" />
|
|
||||||
<p className="font-medium text-base text-onboarding-text-400">
|
|
||||||
Get started by setting up your instance and workspace
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a href={GOD_MODE_URL}>
|
|
||||||
<Button size="lg" className="w-full">
|
|
||||||
Get started
|
|
||||||
</Button>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,10 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
export const IssueBlockDownVotes = ({ number }: { number: number }) => (
|
|
||||||
<div className="flex h-6 items-center rounded border-[0.5px] border-custom-border-300 px-1.5 py-1 pl-1 text-xs text-custom-text-300">
|
|
||||||
<span className="material-symbols-rounded !m-0 rotate-180 !p-0 text-base text-custom-text-300">
|
|
||||||
arrow_upward_alt
|
|
||||||
</span>
|
|
||||||
{number}
|
|
||||||
</div>
|
|
||||||
);
|
|
@ -1,59 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
// helpers
|
|
||||||
import { renderFullDate } from "@/helpers/date-time.helper";
|
|
||||||
|
|
||||||
export const dueDateIconDetails = (
|
|
||||||
date: string,
|
|
||||||
stateGroup: string
|
|
||||||
): {
|
|
||||||
iconName: string;
|
|
||||||
className: string;
|
|
||||||
} => {
|
|
||||||
let iconName = "calendar_today";
|
|
||||||
let className = "";
|
|
||||||
|
|
||||||
if (!date || ["completed", "cancelled"].includes(stateGroup)) {
|
|
||||||
iconName = "calendar_today";
|
|
||||||
className = "";
|
|
||||||
} else {
|
|
||||||
const today = new Date();
|
|
||||||
today.setHours(0, 0, 0, 0);
|
|
||||||
const targetDate = new Date(date);
|
|
||||||
targetDate.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
const timeDifference = targetDate.getTime() - today.getTime();
|
|
||||||
|
|
||||||
if (timeDifference < 0) {
|
|
||||||
iconName = "event_busy";
|
|
||||||
className = "text-red-500";
|
|
||||||
} else if (timeDifference === 0) {
|
|
||||||
iconName = "today";
|
|
||||||
className = "text-red-500";
|
|
||||||
} else if (timeDifference === 24 * 60 * 60 * 1000) {
|
|
||||||
iconName = "event";
|
|
||||||
className = "text-yellow-500";
|
|
||||||
} else {
|
|
||||||
iconName = "calendar_today";
|
|
||||||
className = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
iconName,
|
|
||||||
className,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const IssueBlockDueDate = ({ due_date, group }: { due_date: string; group: string }) => {
|
|
||||||
const iconDetails = dueDateIconDetails(due_date, group);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex items-center gap-1 rounded border-[0.5px] border-custom-border-300 px-2.5 py-1 text-xs text-custom-text-100">
|
|
||||||
<span className={`material-symbols-rounded -my-0.5 text-sm ${iconDetails.className}`}>
|
|
||||||
{iconDetails.iconName}
|
|
||||||
</span>
|
|
||||||
{renderFullDate(due_date)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,19 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
export const IssueBlockLabels = ({ labels }: any) => (
|
|
||||||
<div className="relative flex flex-wrap items-center gap-1">
|
|
||||||
{labels &&
|
|
||||||
labels.length > 0 &&
|
|
||||||
labels.map((_label: any) => (
|
|
||||||
<div
|
|
||||||
key={_label?.id}
|
|
||||||
className="flex flex-shrink-0 cursor-default items-center rounded-md border border-custom-border-300 px-2.5 py-1 text-xs shadow-sm"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-1.5 text-custom-text-200">
|
|
||||||
<div className="h-2 w-2 rounded-full" style={{ backgroundColor: `${_label?.color}` }} />
|
|
||||||
<div className="text-xs">{_label?.name}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
@ -1,18 +0,0 @@
|
|||||||
// ui
|
|
||||||
import { StateGroupIcon } from "@plane/ui";
|
|
||||||
// constants
|
|
||||||
import { issueGroupFilter } from "@/constants/issue";
|
|
||||||
|
|
||||||
export const IssueBlockState = ({ state }: any) => {
|
|
||||||
const stateGroup = issueGroupFilter(state.group);
|
|
||||||
|
|
||||||
if (stateGroup === null) return <></>;
|
|
||||||
return (
|
|
||||||
<div className="flex w-full items-center justify-between gap-1 rounded border-[0.5px] border-custom-border-300 px-2.5 py-1 text-xs shadow-sm duration-300 focus:outline-none">
|
|
||||||
<div className="flex w-full items-center gap-1.5 text-custom-text-200">
|
|
||||||
<StateGroupIcon stateGroup={state.group} color={state.color} />
|
|
||||||
<div className="text-xs">{state?.name}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,8 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
export const IssueBlockUpVotes = ({ number }: { number: number }) => (
|
|
||||||
<div className="flex h-6 items-center rounded border-[0.5px] border-custom-border-300 px-1.5 py-1 pl-1 text-xs text-custom-text-300">
|
|
||||||
<span className="material-symbols-rounded !m-0 !p-0 text-base text-custom-text-300">arrow_upward_alt</span>
|
|
||||||
{number}
|
|
||||||
</div>
|
|
||||||
);
|
|
@ -1 +0,0 @@
|
|||||||
export const IssueCalendarView = () => <div> </div>;
|
|
@ -1 +0,0 @@
|
|||||||
export const IssueGanttView = () => <div> </div>;
|
|
@ -1,28 +0,0 @@
|
|||||||
"use client";
|
|
||||||
// mobx react lite
|
|
||||||
import { observer } from "mobx-react-lite";
|
|
||||||
// ui
|
|
||||||
import { StateGroupIcon } from "@plane/ui";
|
|
||||||
// constants
|
|
||||||
import { issueGroupFilter } from "@/constants/issue";
|
|
||||||
// mobx hook
|
|
||||||
// import { useIssue } from "@/hooks/store";
|
|
||||||
// interfaces
|
|
||||||
import { IIssueState } from "@/types/issue";
|
|
||||||
|
|
||||||
export const IssueKanBanHeader = observer(({ state }: { state: IIssueState }) => {
|
|
||||||
// const { getCountOfIssuesByState } = useIssue();
|
|
||||||
const stateGroup = issueGroupFilter(state.group);
|
|
||||||
|
|
||||||
if (stateGroup === null) return <></>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex items-center gap-2 px-2 pb-2">
|
|
||||||
<div className="flex h-3.5 w-3.5 flex-shrink-0 items-center justify-center">
|
|
||||||
<StateGroupIcon stateGroup={state.group} color={state.color} height="14" width="14" />
|
|
||||||
</div>
|
|
||||||
<div className="mr-1 truncate font-semibold capitalize text-custom-text-200">{state?.name}</div>
|
|
||||||
{/* <span className="flex-shrink-0 rounded-full text-custom-text-300">{getCountOfIssuesByState(state.id)}</span> */}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
@ -1 +0,0 @@
|
|||||||
export const IssueSpreadsheetView = () => <div> </div>;
|
|
@ -1,10 +1,10 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
// icons
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
import { IIssueLabel, IIssueState, TFilters } from "@/types/issue";
|
import { IStateLite } from "@plane/types";
|
||||||
|
import { IIssueLabel, TFilters } from "@/types/issue";
|
||||||
// components
|
// components
|
||||||
import { AppliedPriorityFilters } from "./priority";
|
import { AppliedPriorityFilters } from "./priority";
|
||||||
import { AppliedStateFilters } from "./state";
|
import { AppliedStateFilters } from "./state";
|
||||||
@ -14,7 +14,7 @@ type Props = {
|
|||||||
handleRemoveAllFilters: () => void;
|
handleRemoveAllFilters: () => void;
|
||||||
handleRemoveFilter: (key: keyof TFilters, value: string | null) => void;
|
handleRemoveFilter: (key: keyof TFilters, value: string | null) => void;
|
||||||
labels?: IIssueLabel[] | undefined;
|
labels?: IIssueLabel[] | undefined;
|
||||||
states?: IIssueState[] | undefined;
|
states?: IStateLite[] | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const replaceUnderscoreIfSnakeCase = (str: string) => str.replace(/_/g, " ");
|
export const replaceUnderscoreIfSnakeCase = (str: string) => str.replace(/_/g, " ");
|
||||||
|
@ -80,7 +80,7 @@ export const IssueAppliedFilters: FC<TIssueAppliedFilters> = observer((props) =>
|
|||||||
if (Object.keys(appliedFilters).length === 0) return null;
|
if (Object.keys(appliedFilters).length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border-b border-custom-border-200 p-5 py-3">
|
<div className="border-b border-custom-border-200 bg-custom-background-100 p-4">
|
||||||
<AppliedFiltersList
|
<AppliedFiltersList
|
||||||
appliedFilters={appliedFilters || {}}
|
appliedFilters={appliedFilters || {}}
|
||||||
handleRemoveFilter={handleFilters as any}
|
handleRemoveFilter={handleFilters as any}
|
||||||
|
@ -2,13 +2,14 @@
|
|||||||
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
import { StateGroupIcon } from "@plane/ui";
|
|
||||||
// types
|
// types
|
||||||
import { IIssueState } from "@/types/issue";
|
import { IStateLite } from "@plane/types";
|
||||||
|
// ui
|
||||||
|
import { StateGroupIcon } from "@plane/ui";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
handleRemove: (val: string) => void;
|
handleRemove: (val: string) => void;
|
||||||
states: IIssueState[];
|
states: IStateLite[];
|
||||||
values: string[];
|
values: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,7 +4,8 @@ import React, { useState } from "react";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Search, X } from "lucide-react";
|
import { Search, X } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
import { IIssueState, IIssueLabel, IIssueFilterOptions, TIssueFilterKeys } from "@/types/issue";
|
import { IStateLite } from "@plane/types";
|
||||||
|
import { IIssueLabel, IIssueFilterOptions, TIssueFilterKeys } from "@/types/issue";
|
||||||
// components
|
// components
|
||||||
import { FilterPriority, FilterState } from "./";
|
import { FilterPriority, FilterState } from "./";
|
||||||
|
|
||||||
@ -13,7 +14,7 @@ type Props = {
|
|||||||
handleFilters: (key: keyof IIssueFilterOptions, value: string | string[]) => void;
|
handleFilters: (key: keyof IIssueFilterOptions, value: string | string[]) => void;
|
||||||
layoutDisplayFiltersOptions: TIssueFilterKeys[];
|
layoutDisplayFiltersOptions: TIssueFilterKeys[];
|
||||||
labels?: IIssueLabel[] | undefined;
|
labels?: IIssueLabel[] | undefined;
|
||||||
states?: IIssueState[] | undefined;
|
states?: IStateLite[] | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FilterSelection: React.FC<Props> = observer((props) => {
|
export const FilterSelection: React.FC<Props> = observer((props) => {
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
// types
|
||||||
|
import { IStateLite } from "@plane/types";
|
||||||
|
// ui
|
||||||
import { Loader, StateGroupIcon } from "@plane/ui";
|
import { Loader, StateGroupIcon } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { FilterHeader, FilterOption } from "@/components/issues/filters/helpers";
|
import { FilterHeader, FilterOption } from "@/components/issues/filters/helpers";
|
||||||
// types
|
|
||||||
import { IIssueState } from "@/types/issue";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
appliedFilters: string[] | null;
|
appliedFilters: string[] | null;
|
||||||
handleUpdate: (val: string) => void;
|
handleUpdate: (val: string) => void;
|
||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
states: IIssueState[] | undefined;
|
states: IStateLite[] | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FilterState: React.FC<Props> = (props) => {
|
export const FilterState: React.FC<Props> = (props) => {
|
||||||
|
2
space/components/issues/index.ts
Normal file
2
space/components/issues/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./issue-layouts";
|
||||||
|
export * from "./navbar";
|
4
space/components/issues/issue-layouts/index.ts
Normal file
4
space/components/issues/issue-layouts/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./kanban";
|
||||||
|
export * from "./list";
|
||||||
|
export * from "./properties";
|
||||||
|
export * from "./root";
|
@ -2,11 +2,10 @@
|
|||||||
|
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import Link from "next/link";
|
||||||
|
import { useSearchParams } from "next/navigation";
|
||||||
// components
|
// components
|
||||||
import { IssueBlockDueDate } from "@/components/issues/board-views/block-due-date";
|
import { IssueBlockDueDate, IssueBlockPriority, IssueBlockState } from "@/components/issues";
|
||||||
import { IssueBlockPriority } from "@/components/issues/board-views/block-priority";
|
|
||||||
import { IssueBlockState } from "@/components/issues/board-views/block-state";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { queryParamGenerator } from "@/helpers/query-param-generator";
|
import { queryParamGenerator } from "@/helpers/query-param-generator";
|
||||||
// hooks
|
// hooks
|
||||||
@ -14,45 +13,43 @@ import { useIssueDetails, usePublish } from "@/hooks/store";
|
|||||||
// interfaces
|
// interfaces
|
||||||
import { IIssue } from "@/types/issue";
|
import { IIssue } from "@/types/issue";
|
||||||
|
|
||||||
type IssueKanBanBlockProps = {
|
type Props = {
|
||||||
anchor: string;
|
anchor: string;
|
||||||
issue: IIssue;
|
issue: IIssue;
|
||||||
params: any;
|
params: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueKanBanBlock: FC<IssueKanBanBlockProps> = observer((props) => {
|
export const IssueKanBanBlock: FC<Props> = observer((props) => {
|
||||||
const { anchor, issue } = props;
|
const { anchor, issue } = props;
|
||||||
// router
|
|
||||||
const router = useRouter();
|
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
// query params
|
// query params
|
||||||
const board = searchParams.get("board") || undefined;
|
const board = searchParams.get("board");
|
||||||
const state = searchParams.get("state") || undefined;
|
const state = searchParams.get("state");
|
||||||
const priority = searchParams.get("priority") || undefined;
|
const priority = searchParams.get("priority");
|
||||||
const labels = searchParams.get("labels") || undefined;
|
const labels = searchParams.get("labels");
|
||||||
// store hooks
|
// store hooks
|
||||||
const { project_details } = usePublish(anchor);
|
const { project_details } = usePublish(anchor);
|
||||||
const { setPeekId } = useIssueDetails();
|
const { setPeekId } = useIssueDetails();
|
||||||
|
|
||||||
|
const { queryParam } = queryParamGenerator({ board, peekId: issue.id, priority, state, labels });
|
||||||
|
|
||||||
const handleBlockClick = () => {
|
const handleBlockClick = () => {
|
||||||
setPeekId(issue.id);
|
setPeekId(issue.id);
|
||||||
const { queryParam } = queryParamGenerator({ board, peekId: issue.id, priority, state, labels });
|
|
||||||
router.push(`/issues/${anchor}?${queryParam}`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-1.5 space-y-2 rounded border-[0.5px] border-custom-border-200 bg-custom-background-100 px-3 py-2 text-sm shadow-custom-shadow-2xs">
|
<Link
|
||||||
|
href={`/issues/${anchor}?${queryParam}`}
|
||||||
|
onClick={handleBlockClick}
|
||||||
|
className="flex flex-col gap-1.5 space-y-2 rounded border-[0.5px] border-custom-border-200 bg-custom-background-100 px-3 py-2 text-sm shadow-custom-shadow-2xs select-none"
|
||||||
|
>
|
||||||
{/* id */}
|
{/* id */}
|
||||||
<div className="break-words text-xs text-custom-text-300">
|
<div className="break-words text-xs text-custom-text-300">
|
||||||
{project_details?.identifier}-{issue?.sequence_id}
|
{project_details?.identifier}-{issue?.sequence_id}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* name */}
|
{/* name */}
|
||||||
<h6
|
<h6 role="button" className="line-clamp-2 cursor-pointer break-words text-sm">
|
||||||
onClick={handleBlockClick}
|
|
||||||
role="button"
|
|
||||||
className="line-clamp-2 cursor-pointer break-words text-sm font-medium"
|
|
||||||
>
|
|
||||||
{issue.name}
|
{issue.name}
|
||||||
</h6>
|
</h6>
|
||||||
|
|
||||||
@ -76,6 +73,6 @@ export const IssueKanBanBlock: FC<IssueKanBanBlockProps> = observer((props) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Link>
|
||||||
);
|
);
|
||||||
});
|
});
|
25
space/components/issues/issue-layouts/kanban/header.tsx
Normal file
25
space/components/issues/issue-layouts/kanban/header.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
// types
|
||||||
|
import { IStateLite } from "@plane/types";
|
||||||
|
// ui
|
||||||
|
import { StateGroupIcon } from "@plane/ui";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
state: IStateLite;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IssueKanBanHeader: React.FC<Props> = observer((props) => {
|
||||||
|
const { state } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2 px-2 pb-2">
|
||||||
|
<div className="flex h-3.5 w-3.5 flex-shrink-0 items-center justify-center">
|
||||||
|
<StateGroupIcon stateGroup={state.group} color={state.color} height="14" width="14" />
|
||||||
|
</div>
|
||||||
|
<div className="mr-1 truncate font-medium capitalize text-custom-text-200">{state?.name}</div>
|
||||||
|
{/* <span className="flex-shrink-0 rounded-full text-custom-text-300">{getCountOfIssuesByState(state.id)}</span> */}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
3
space/components/issues/issue-layouts/kanban/index.ts
Normal file
3
space/components/issues/issue-layouts/kanban/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./block";
|
||||||
|
export * from "./header";
|
||||||
|
export * from "./root";
|
@ -3,18 +3,17 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// components
|
// components
|
||||||
import { IssueKanBanBlock } from "@/components/issues/board-views/kanban/block";
|
import { IssueKanBanBlock, IssueKanBanHeader } from "@/components/issues";
|
||||||
import { IssueKanBanHeader } from "@/components/issues/board-views/kanban/header";
|
|
||||||
// ui
|
// ui
|
||||||
import { Icon } from "@/components/ui";
|
import { Icon } from "@/components/ui";
|
||||||
// mobx hook
|
// mobx hook
|
||||||
import { useIssue } from "@/hooks/store";
|
import { useIssue } from "@/hooks/store";
|
||||||
|
|
||||||
type IssueKanbanViewProps = {
|
type Props = {
|
||||||
anchor: string;
|
anchor: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueKanbanView: FC<IssueKanbanViewProps> = observer((props) => {
|
export const IssueKanbanLayoutRoot: FC<Props> = observer((props) => {
|
||||||
const { anchor } = props;
|
const { anchor } = props;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { states, getFilteredIssuesByState } = useIssue();
|
const { states, getFilteredIssuesByState } = useIssue();
|
@ -1,29 +1,26 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import Link from "next/link";
|
||||||
|
import { useSearchParams } from "next/navigation";
|
||||||
// components
|
// components
|
||||||
import { IssueBlockDueDate } from "@/components/issues/board-views/block-due-date";
|
import { IssueBlockDueDate, IssueBlockLabels, IssueBlockPriority, IssueBlockState } from "@/components/issues";
|
||||||
import { IssueBlockLabels } from "@/components/issues/board-views/block-labels";
|
|
||||||
import { IssueBlockPriority } from "@/components/issues/board-views/block-priority";
|
|
||||||
import { IssueBlockState } from "@/components/issues/board-views/block-state";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { queryParamGenerator } from "@/helpers/query-param-generator";
|
import { queryParamGenerator } from "@/helpers/query-param-generator";
|
||||||
// hook
|
// hook
|
||||||
import { useIssueDetails, usePublish } from "@/hooks/store";
|
import { useIssueDetails, usePublish } from "@/hooks/store";
|
||||||
// interfaces
|
// types
|
||||||
import { IIssue } from "@/types/issue";
|
import { IIssue } from "@/types/issue";
|
||||||
// store
|
|
||||||
|
|
||||||
type IssueListBlockProps = {
|
type IssueListBlockProps = {
|
||||||
anchor: string;
|
anchor: string;
|
||||||
issue: IIssue;
|
issue: IIssue;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueListBlock: FC<IssueListBlockProps> = observer((props) => {
|
export const IssueListLayoutBlock: FC<IssueListBlockProps> = observer((props) => {
|
||||||
const { anchor, issue } = props;
|
const { anchor, issue } = props;
|
||||||
const searchParams = useSearchParams();
|
|
||||||
// query params
|
// query params
|
||||||
|
const searchParams = useSearchParams();
|
||||||
const board = searchParams.get("board") || undefined;
|
const board = searchParams.get("board") || undefined;
|
||||||
const state = searchParams.get("state") || undefined;
|
const state = searchParams.get("state") || undefined;
|
||||||
const priority = searchParams.get("priority") || undefined;
|
const priority = searchParams.get("priority") || undefined;
|
||||||
@ -31,25 +28,25 @@ export const IssueListBlock: FC<IssueListBlockProps> = observer((props) => {
|
|||||||
// store hooks
|
// store hooks
|
||||||
const { setPeekId } = useIssueDetails();
|
const { setPeekId } = useIssueDetails();
|
||||||
const { project_details } = usePublish(anchor);
|
const { project_details } = usePublish(anchor);
|
||||||
// router
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
|
const { queryParam } = queryParamGenerator({ board, peekId: issue.id, priority, state, labels });
|
||||||
const handleBlockClick = () => {
|
const handleBlockClick = () => {
|
||||||
setPeekId(issue.id);
|
setPeekId(issue.id);
|
||||||
|
|
||||||
const { queryParam } = queryParamGenerator({ board, peekId: issue.id, priority, state, labels });
|
|
||||||
router.push(`/issues/${anchor}?${queryParam}`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex items-center gap-10 bg-custom-background-100 p-3">
|
<Link
|
||||||
|
href={`/issues/${anchor}?${queryParam}`}
|
||||||
|
onClick={handleBlockClick}
|
||||||
|
className="relative flex items-center gap-10 bg-custom-background-100 p-3"
|
||||||
|
>
|
||||||
<div className="relative flex w-full flex-grow items-center gap-3 overflow-hidden">
|
<div className="relative flex w-full flex-grow items-center gap-3 overflow-hidden">
|
||||||
{/* id */}
|
{/* id */}
|
||||||
<div className="flex-shrink-0 text-xs font-medium text-custom-text-300">
|
<div className="flex-shrink-0 text-xs font-medium text-custom-text-300">
|
||||||
{project_details?.identifier}-{issue?.sequence_id}
|
{project_details?.identifier}-{issue?.sequence_id}
|
||||||
</div>
|
</div>
|
||||||
{/* name */}
|
{/* name */}
|
||||||
<div onClick={handleBlockClick} className="flex-grow cursor-pointer truncate text-sm font-medium">
|
<div onClick={handleBlockClick} className="flex-grow cursor-pointer truncate text-sm">
|
||||||
{issue.name}
|
{issue.name}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -83,6 +80,6 @@ export const IssueListBlock: FC<IssueListBlockProps> = observer((props) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Link>
|
||||||
);
|
);
|
||||||
});
|
});
|
@ -1,20 +1,18 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
// types
|
||||||
|
import { IStateLite } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { StateGroupIcon } from "@plane/ui";
|
import { StateGroupIcon } from "@plane/ui";
|
||||||
// constants
|
|
||||||
import { issueGroupFilter } from "@/constants/issue";
|
|
||||||
// mobx hook
|
|
||||||
// import { useIssue } from "@/hooks/store";
|
|
||||||
// types
|
|
||||||
import { IIssueState } from "@/types/issue";
|
|
||||||
|
|
||||||
export const IssueListHeader = observer(({ state }: { state: IIssueState }) => {
|
type Props = {
|
||||||
// const { getCountOfIssuesByState } = useIssue();
|
state: IStateLite;
|
||||||
const stateGroup = issueGroupFilter(state.group);
|
};
|
||||||
// const count = getCountOfIssuesByState(state.id);
|
|
||||||
|
|
||||||
if (stateGroup === null) return <></>;
|
export const IssueListLayoutHeader: React.FC<Props> = observer((props) => {
|
||||||
|
const { state } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2 p-3">
|
<div className="flex items-center gap-2 p-3">
|
3
space/components/issues/issue-layouts/list/index.ts
Normal file
3
space/components/issues/issue-layouts/list/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from "./block";
|
||||||
|
export * from "./header";
|
||||||
|
export * from "./root";
|
@ -2,16 +2,15 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
// components
|
// components
|
||||||
import { IssueListBlock } from "@/components/issues/board-views/list/block";
|
import { IssueListLayoutBlock, IssueListLayoutHeader } from "@/components/issues";
|
||||||
import { IssueListHeader } from "@/components/issues/board-views/list/header";
|
|
||||||
// mobx hook
|
// mobx hook
|
||||||
import { useIssue } from "@/hooks/store";
|
import { useIssue } from "@/hooks/store";
|
||||||
|
|
||||||
type IssueListViewProps = {
|
type Props = {
|
||||||
anchor: string;
|
anchor: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const IssueListView: FC<IssueListViewProps> = observer((props) => {
|
export const IssuesListLayoutRoot: FC<Props> = observer((props) => {
|
||||||
const { anchor } = props;
|
const { anchor } = props;
|
||||||
// store hooks
|
// store hooks
|
||||||
const { states, getFilteredIssuesByState } = useIssue();
|
const { states, getFilteredIssuesByState } = useIssue();
|
||||||
@ -23,11 +22,11 @@ export const IssueListView: FC<IssueListViewProps> = observer((props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={state.id} className="relative w-full">
|
<div key={state.id} className="relative w-full">
|
||||||
<IssueListHeader state={state} />
|
<IssueListLayoutHeader state={state} />
|
||||||
{issues && issues.length > 0 ? (
|
{issues && issues.length > 0 ? (
|
||||||
<div className="divide-y divide-custom-border-200">
|
<div className="divide-y divide-custom-border-200">
|
||||||
{issues.map((issue) => (
|
{issues.map((issue) => (
|
||||||
<IssueListBlock key={issue.id} anchor={anchor} issue={issue} />
|
<IssueListLayoutBlock key={issue.id} anchor={anchor} issue={issue} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
@ -0,0 +1,32 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { CalendarCheck2 } from "lucide-react";
|
||||||
|
// types
|
||||||
|
import { TStateGroups } from "@plane/types";
|
||||||
|
// helpers
|
||||||
|
import { cn } from "@/helpers/common.helper";
|
||||||
|
import { renderFormattedDate } from "@/helpers/date-time.helper";
|
||||||
|
import { shouldHighlightIssueDueDate } from "@/helpers/issue.helper";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
due_date: string;
|
||||||
|
group: TStateGroups;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IssueBlockDueDate = (props: Props) => {
|
||||||
|
const { due_date, group } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-1 rounded border-[0.5px] border-custom-border-300 px-2.5 py-1 text-xs text-custom-text-100",
|
||||||
|
{
|
||||||
|
"text-red-500": shouldHighlightIssueDueDate(due_date, group),
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<CalendarCheck2 className="size-3 flex-shrink-0" />
|
||||||
|
{renderFormattedDate(due_date)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,4 @@
|
|||||||
|
export * from "./due-date";
|
||||||
|
export * from "./labels";
|
||||||
|
export * from "./priority";
|
||||||
|
export * from "./state";
|
17
space/components/issues/issue-layouts/properties/labels.tsx
Normal file
17
space/components/issues/issue-layouts/properties/labels.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
export const IssueBlockLabels = ({ labels }: any) => (
|
||||||
|
<div className="relative flex flex-wrap items-center gap-1">
|
||||||
|
{labels?.map((_label: any) => (
|
||||||
|
<div
|
||||||
|
key={_label?.id}
|
||||||
|
className="flex flex-shrink-0 cursor-default items-center rounded-md border border-custom-border-300 px-2.5 py-1 text-xs shadow-sm"
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-1.5 text-custom-text-200">
|
||||||
|
<div className="h-2 w-2 rounded-full" style={{ backgroundColor: `${_label?.color}` }} />
|
||||||
|
<div className="text-xs">{_label?.name}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
@ -1,11 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
// types
|
// types
|
||||||
import { issuePriorityFilter } from "@/constants/issue";
|
import { TIssuePriorities } from "@plane/types";
|
||||||
import { TIssueFilterPriority } from "@/types/issue";
|
|
||||||
// constants
|
// constants
|
||||||
|
import { issuePriorityFilter } from "@/constants/issue";
|
||||||
|
|
||||||
export const IssueBlockPriority = ({ priority }: { priority: TIssueFilterPriority | null }) => {
|
export const IssueBlockPriority = ({ priority }: { priority: TIssuePriorities | null }) => {
|
||||||
const priority_detail = priority != null ? issuePriorityFilter(priority) : null;
|
const priority_detail = priority != null ? issuePriorityFilter(priority) : null;
|
||||||
|
|
||||||
if (priority_detail === null) return <></>;
|
if (priority_detail === null) return <></>;
|
11
space/components/issues/issue-layouts/properties/state.tsx
Normal file
11
space/components/issues/issue-layouts/properties/state.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// ui
|
||||||
|
import { StateGroupIcon } from "@plane/ui";
|
||||||
|
|
||||||
|
export const IssueBlockState = ({ state }: any) => (
|
||||||
|
<div className="flex w-full items-center justify-between gap-1 rounded border-[0.5px] border-custom-border-300 px-2.5 py-1 text-xs shadow-sm duration-300 focus:outline-none">
|
||||||
|
<div className="flex w-full items-center gap-1.5">
|
||||||
|
<StateGroupIcon stateGroup={state.group} color={state.color} />
|
||||||
|
<div className="text-xs">{state?.name}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
@ -6,11 +6,7 @@ import Image from "next/image";
|
|||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
// components
|
// components
|
||||||
import { IssueCalendarView } from "@/components/issues/board-views/calendar";
|
import { IssueKanbanLayoutRoot, IssuesListLayoutRoot } from "@/components/issues";
|
||||||
import { IssueGanttView } from "@/components/issues/board-views/gantt";
|
|
||||||
import { IssueKanbanView } from "@/components/issues/board-views/kanban";
|
|
||||||
import { IssueListView } from "@/components/issues/board-views/list";
|
|
||||||
import { IssueSpreadsheetView } from "@/components/issues/board-views/spreadsheet";
|
|
||||||
import { IssueAppliedFilters } from "@/components/issues/filters/applied-filters/root";
|
import { IssueAppliedFilters } from "@/components/issues/filters/applied-filters/root";
|
||||||
import { IssuePeekOverview } from "@/components/issues/peek-overview";
|
import { IssuePeekOverview } from "@/components/issues/peek-overview";
|
||||||
// hooks
|
// hooks
|
||||||
@ -20,12 +16,12 @@ import { PublishStore } from "@/store/publish/publish.store";
|
|||||||
// assets
|
// assets
|
||||||
import SomethingWentWrongImage from "public/something-went-wrong.svg";
|
import SomethingWentWrongImage from "public/something-went-wrong.svg";
|
||||||
|
|
||||||
type ProjectDetailsViewProps = {
|
type Props = {
|
||||||
peekId: string | undefined;
|
peekId: string | undefined;
|
||||||
publishSettings: PublishStore;
|
publishSettings: PublishStore;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProjectDetailsView: FC<ProjectDetailsViewProps> = observer((props) => {
|
export const IssuesLayoutsRoot: FC<Props> = observer((props) => {
|
||||||
const { peekId, publishSettings } = props;
|
const { peekId, publishSettings } = props;
|
||||||
// query params
|
// query params
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
@ -84,17 +80,14 @@ export const ProjectDetailsView: FC<ProjectDetailsViewProps> = observer((props)
|
|||||||
|
|
||||||
{activeLayout === "list" && (
|
{activeLayout === "list" && (
|
||||||
<div className="relative h-full w-full overflow-y-auto">
|
<div className="relative h-full w-full overflow-y-auto">
|
||||||
<IssueListView anchor={anchor} />
|
<IssuesListLayoutRoot anchor={anchor} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{activeLayout === "kanban" && (
|
{activeLayout === "kanban" && (
|
||||||
<div className="relative mx-auto h-full w-full p-5">
|
<div className="relative mx-auto h-full w-full p-5">
|
||||||
<IssueKanbanView anchor={anchor} />
|
<IssueKanbanLayoutRoot anchor={anchor} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{activeLayout === "calendar" && <IssueCalendarView />}
|
|
||||||
{activeLayout === "spreadsheet" && <IssueSpreadsheetView />}
|
|
||||||
{activeLayout === "gantt" && <IssueGanttView />}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
@ -4,10 +4,8 @@ import { useEffect, FC } from "react";
|
|||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
// components
|
// components
|
||||||
|
import { IssuesLayoutSelection, NavbarTheme, UserAvatar } from "@/components/issues";
|
||||||
import { IssueFiltersDropdown } from "@/components/issues/filters";
|
import { IssueFiltersDropdown } from "@/components/issues/filters";
|
||||||
import { NavbarIssueBoardView } from "@/components/issues/navbar/issue-board-view";
|
|
||||||
import { NavbarTheme } from "@/components/issues/navbar/theme";
|
|
||||||
import { UserAvatar } from "@/components/issues/navbar/user-avatar";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { queryParamGenerator } from "@/helpers/query-param-generator";
|
import { queryParamGenerator } from "@/helpers/query-param-generator";
|
||||||
// hooks
|
// hooks
|
||||||
@ -105,7 +103,7 @@ export const NavbarControls: FC<NavbarControlsProps> = observer((props) => {
|
|||||||
<>
|
<>
|
||||||
{/* issue views */}
|
{/* issue views */}
|
||||||
<div className="relative flex flex-shrink-0 items-center gap-1 transition-all delay-150 ease-in-out">
|
<div className="relative flex flex-shrink-0 items-center gap-1 transition-all delay-150 ease-in-out">
|
||||||
<NavbarIssueBoardView anchor={anchor} />
|
<IssuesLayoutSelection anchor={anchor} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* issue filters */}
|
{/* issue filters */}
|
||||||
|
5
space/components/issues/navbar/index.ts
Normal file
5
space/components/issues/navbar/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export * from "./controls";
|
||||||
|
export * from "./layout-selection";
|
||||||
|
export * from "./root";
|
||||||
|
export * from "./theme";
|
||||||
|
export * from "./user-avatar";
|
@ -1,71 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { FC } from "react";
|
|
||||||
import { observer } from "mobx-react-lite";
|
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
|
||||||
// constants
|
|
||||||
import { issueLayoutViews } from "@/constants/issue";
|
|
||||||
// helpers
|
|
||||||
import { queryParamGenerator } from "@/helpers/query-param-generator";
|
|
||||||
// hooks
|
|
||||||
import { useIssueFilter } from "@/hooks/store";
|
|
||||||
// mobx
|
|
||||||
import { TIssueLayout } from "@/types/issue";
|
|
||||||
|
|
||||||
type NavbarIssueBoardViewProps = {
|
|
||||||
anchor: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const NavbarIssueBoardView: FC<NavbarIssueBoardViewProps> = observer((props) => {
|
|
||||||
const { anchor } = props;
|
|
||||||
// router
|
|
||||||
const router = useRouter();
|
|
||||||
const searchParams = useSearchParams();
|
|
||||||
// query params
|
|
||||||
const labels = searchParams.get("labels") || undefined;
|
|
||||||
const state = searchParams.get("state") || undefined;
|
|
||||||
const priority = searchParams.get("priority") || undefined;
|
|
||||||
const peekId = searchParams.get("peekId") || undefined;
|
|
||||||
// hooks
|
|
||||||
const { layoutOptions, getIssueFilters, updateIssueFilters } = useIssueFilter();
|
|
||||||
// derived values
|
|
||||||
const issueFilters = getIssueFilters(anchor);
|
|
||||||
const activeLayout = issueFilters?.display_filters?.layout || undefined;
|
|
||||||
|
|
||||||
const handleCurrentBoardView = (boardView: TIssueLayout) => {
|
|
||||||
updateIssueFilters(anchor, "display_filters", "layout", boardView);
|
|
||||||
const { queryParam } = queryParamGenerator({ board: boardView, peekId, priority, state, labels });
|
|
||||||
router.push(`/issues/${anchor}?${queryParam}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{issueLayoutViews &&
|
|
||||||
Object.keys(issueLayoutViews).map((key: string) => {
|
|
||||||
const layoutKey = key as TIssueLayout;
|
|
||||||
if (layoutOptions[layoutKey]) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
key={layoutKey}
|
|
||||||
className={`flex h-[28px] w-[28px] cursor-pointer items-center justify-center rounded-sm ${
|
|
||||||
layoutKey === activeLayout
|
|
||||||
? `bg-custom-background-80 text-custom-text-200`
|
|
||||||
: `text-custom-text-300 hover:bg-custom-background-80`
|
|
||||||
}`}
|
|
||||||
onClick={() => handleCurrentBoardView(layoutKey)}
|
|
||||||
title={layoutKey}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={`material-symbols-rounded text-[18px] ${
|
|
||||||
issueLayoutViews[layoutKey]?.className ? issueLayoutViews[layoutKey]?.className : ``
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{issueLayoutViews[layoutKey]?.icon}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
});
|
|
67
space/components/issues/navbar/layout-selection.tsx
Normal file
67
space/components/issues/navbar/layout-selection.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { FC } from "react";
|
||||||
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
|
// ui
|
||||||
|
import { Tooltip } from "@plane/ui";
|
||||||
|
// constants
|
||||||
|
import { ISSUE_LAYOUTS } from "@/constants/issue";
|
||||||
|
// helpers
|
||||||
|
import { queryParamGenerator } from "@/helpers/query-param-generator";
|
||||||
|
// hooks
|
||||||
|
import { useIssueFilter } from "@/hooks/store";
|
||||||
|
// mobx
|
||||||
|
import { TIssueLayout } from "@/types/issue";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
anchor: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const IssuesLayoutSelection: FC<Props> = observer((props) => {
|
||||||
|
const { anchor } = props;
|
||||||
|
// router
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
// query params
|
||||||
|
const labels = searchParams.get("labels");
|
||||||
|
const state = searchParams.get("state");
|
||||||
|
const priority = searchParams.get("priority");
|
||||||
|
const peekId = searchParams.get("peekId");
|
||||||
|
// hooks
|
||||||
|
const { layoutOptions, getIssueFilters, updateIssueFilters } = useIssueFilter();
|
||||||
|
// derived values
|
||||||
|
const issueFilters = getIssueFilters(anchor);
|
||||||
|
const activeLayout = issueFilters?.display_filters?.layout || undefined;
|
||||||
|
|
||||||
|
const handleCurrentBoardView = (boardView: TIssueLayout) => {
|
||||||
|
updateIssueFilters(anchor, "display_filters", "layout", boardView);
|
||||||
|
const { queryParam } = queryParamGenerator({ board: boardView, peekId, priority, state, labels });
|
||||||
|
router.push(`/issues/${anchor}?${queryParam}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-1 rounded bg-custom-background-80 p-1">
|
||||||
|
{ISSUE_LAYOUTS.map((layout) => {
|
||||||
|
if (!layoutOptions[layout.key]) return;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip key={layout.key} tooltipContent={layout.title}>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`group grid h-[22px] w-7 place-items-center overflow-hidden rounded transition-all hover:bg-custom-background-100 ${
|
||||||
|
activeLayout == layout.key ? "bg-custom-background-100 shadow-custom-shadow-2xs" : ""
|
||||||
|
}`}
|
||||||
|
onClick={() => handleCurrentBoardView(layout.key)}
|
||||||
|
>
|
||||||
|
<layout.icon
|
||||||
|
strokeWidth={2}
|
||||||
|
className={`size-3.5 ${activeLayout == layout.key ? "text-custom-text-100" : "text-custom-text-200"}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
@ -4,15 +4,15 @@ import { observer } from "mobx-react-lite";
|
|||||||
import { Briefcase } from "lucide-react";
|
import { Briefcase } from "lucide-react";
|
||||||
// components
|
// components
|
||||||
import { ProjectLogo } from "@/components/common";
|
import { ProjectLogo } from "@/components/common";
|
||||||
import { NavbarControls } from "@/components/issues/navbar/controls";
|
import { NavbarControls } from "@/components/issues";
|
||||||
// store
|
// store
|
||||||
import { PublishStore } from "@/store/publish/publish.store";
|
import { PublishStore } from "@/store/publish/publish.store";
|
||||||
|
|
||||||
type IssueNavbarProps = {
|
type Props = {
|
||||||
publishSettings: PublishStore;
|
publishSettings: PublishStore;
|
||||||
};
|
};
|
||||||
|
|
||||||
const IssueNavbar: FC<IssueNavbarProps> = observer((props) => {
|
export const IssuesNavbarRoot: FC<Props> = observer((props) => {
|
||||||
const { publishSettings } = props;
|
const { publishSettings } = props;
|
||||||
// hooks
|
// hooks
|
||||||
const { project_details } = publishSettings;
|
const { project_details } = publishSettings;
|
||||||
@ -41,5 +41,3 @@ const IssueNavbar: FC<IssueNavbarProps> = observer((props) => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default IssueNavbar;
|
|
@ -1,10 +1,9 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { MoveRight } from "lucide-react";
|
import { Link2, MoveRight } from "lucide-react";
|
||||||
import { Listbox, Transition } from "@headlessui/react";
|
import { Listbox, Transition } from "@headlessui/react";
|
||||||
// ui
|
// ui
|
||||||
import { setToast, TOAST_TYPE } from "@plane/ui";
|
import { CenterPanelIcon, FullScreenPanelIcon, setToast, SidePanelIcon, TOAST_TYPE } from "@plane/ui";
|
||||||
import { Icon } from "@/components/ui";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { copyTextToClipboard } from "@/helpers/string.helper";
|
import { copyTextToClipboard } from "@/helpers/string.helper";
|
||||||
// hooks
|
// hooks
|
||||||
@ -18,21 +17,21 @@ type Props = {
|
|||||||
issueDetails: IIssue | undefined;
|
issueDetails: IIssue | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const peekModes: {
|
const PEEK_MODES: {
|
||||||
key: IPeekMode;
|
key: IPeekMode;
|
||||||
icon: string;
|
icon: any;
|
||||||
label: string;
|
label: string;
|
||||||
}[] = [
|
}[] = [
|
||||||
{ key: "side", icon: "side_navigation", label: "Side Peek" },
|
{ key: "side", icon: SidePanelIcon, label: "Side Peek" },
|
||||||
{
|
{
|
||||||
key: "modal",
|
key: "modal",
|
||||||
icon: "dialogs",
|
icon: CenterPanelIcon,
|
||||||
label: "Modal Peek",
|
label: "Modal",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "full",
|
key: "full",
|
||||||
icon: "nearby",
|
icon: FullScreenPanelIcon,
|
||||||
label: "Full Screen Peek",
|
label: "Full Screen",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -47,20 +46,22 @@ export const PeekOverviewHeader: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
copyTextToClipboard(urlToCopy).then(() => {
|
copyTextToClipboard(urlToCopy).then(() => {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.INFO,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
title: "Link copied!",
|
title: "Link copied!",
|
||||||
message: "Issue link copied to clipboard",
|
message: "Issue link copied to clipboard.",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Icon = PEEK_MODES.find((m) => m.key === peekMode)?.icon ?? SidePanelIcon;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
{peekMode === "side" && (
|
{peekMode === "side" && (
|
||||||
<button type="button" onClick={handleClose}>
|
<button type="button" onClick={handleClose} className="text-custom-text-300 hover:text-custom-text-200">
|
||||||
<MoveRight className="h-4 w-4" strokeWidth={2} />
|
<MoveRight className="size-4" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<Listbox
|
<Listbox
|
||||||
@ -69,8 +70,10 @@ export const PeekOverviewHeader: React.FC<Props> = observer((props) => {
|
|||||||
onChange={(val) => setPeekMode(val)}
|
onChange={(val) => setPeekMode(val)}
|
||||||
className="relative flex-shrink-0 text-left"
|
className="relative flex-shrink-0 text-left"
|
||||||
>
|
>
|
||||||
<Listbox.Button className={`grid place-items-center ${peekMode === "full" ? "rotate-45" : ""}`}>
|
<Listbox.Button
|
||||||
<Icon iconName={peekModes.find((m) => m.key === peekMode)?.icon ?? ""} className="text-[1rem]" />
|
className={`grid place-items-center text-custom-text-300 hover:text-custom-text-200 ${peekMode === "full" ? "rotate-45" : ""}`}
|
||||||
|
>
|
||||||
|
<Icon className="h-4 w-4 text-custom-text-300 hover:text-custom-text-200" />
|
||||||
</Listbox.Button>
|
</Listbox.Button>
|
||||||
|
|
||||||
<Transition
|
<Transition
|
||||||
@ -84,7 +87,7 @@ export const PeekOverviewHeader: React.FC<Props> = observer((props) => {
|
|||||||
>
|
>
|
||||||
<Listbox.Options className="absolute left-0 z-10 mt-1 min-w-[8rem] origin-top-left overflow-y-auto whitespace-nowrap rounded-md border border-custom-border-300 bg-custom-background-90 text-xs shadow-lg focus:outline-none">
|
<Listbox.Options className="absolute left-0 z-10 mt-1 min-w-[8rem] origin-top-left overflow-y-auto whitespace-nowrap rounded-md border border-custom-border-300 bg-custom-background-90 text-xs shadow-lg focus:outline-none">
|
||||||
<div className="space-y-1 p-2">
|
<div className="space-y-1 p-2">
|
||||||
{peekModes.map((mode) => (
|
{PEEK_MODES.map((mode) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={mode.key}
|
key={mode.key}
|
||||||
value={mode.key}
|
value={mode.key}
|
||||||
@ -117,8 +120,13 @@ export const PeekOverviewHeader: React.FC<Props> = observer((props) => {
|
|||||||
</div>
|
</div>
|
||||||
{isClipboardWriteAllowed && (peekMode === "side" || peekMode === "modal") && (
|
{isClipboardWriteAllowed && (peekMode === "side" || peekMode === "modal") && (
|
||||||
<div className="flex flex-shrink-0 items-center gap-2">
|
<div className="flex flex-shrink-0 items-center gap-2">
|
||||||
<button type="button" onClick={handleCopyLink} className="-rotate-45 focus:outline-none" tabIndex={1}>
|
<button
|
||||||
<Icon iconName="link" className="text-[1rem]" />
|
type="button"
|
||||||
|
onClick={handleCopyLink}
|
||||||
|
className="focus:outline-none text-custom-text-300 hover:text-custom-text-200"
|
||||||
|
tabIndex={1}
|
||||||
|
>
|
||||||
|
<Link2 className="h-4 w-4 -rotate-45" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -16,10 +16,10 @@ export const PeekOverviewIssueDetails: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<h6 className="font-medium text-custom-text-200">
|
<h6 className="text-base font-medium text-custom-text-400">
|
||||||
{issueDetails.project_detail.identifier}-{issueDetails.sequence_id}
|
{issueDetails.project_detail?.identifier}-{issueDetails?.sequence_id}
|
||||||
</h6>
|
</h6>
|
||||||
<h4 className="break-words text-2xl font-semibold">{issueDetails.name}</h4>
|
<h4 className="break-words text-2xl font-medium">{issueDetails.name}</h4>
|
||||||
{description !== "" && description !== "<p></p>" && (
|
{description !== "" && description !== "<p></p>" && (
|
||||||
<RichTextReadOnlyEditor
|
<RichTextReadOnlyEditor
|
||||||
initialValue={
|
initialValue={
|
||||||
|
@ -1,16 +1,17 @@
|
|||||||
|
import { CalendarCheck2, Signal } from "lucide-react";
|
||||||
// ui
|
// ui
|
||||||
import { StateGroupIcon, TOAST_TYPE, setToast } from "@plane/ui";
|
import { DoubleCircleIcon, StateGroupIcon, TOAST_TYPE, setToast } from "@plane/ui";
|
||||||
// icons
|
// components
|
||||||
import { Icon } from "@/components/ui";
|
import { Icon } from "@/components/ui";
|
||||||
// constants
|
// constants
|
||||||
import { issueGroupFilter, issuePriorityFilter } from "@/constants/issue";
|
import { issuePriorityFilter } from "@/constants/issue";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderFullDate } from "@/helpers/date-time.helper";
|
import { cn } from "@/helpers/common.helper";
|
||||||
|
import { renderFormattedDate } from "@/helpers/date-time.helper";
|
||||||
|
import { shouldHighlightIssueDueDate } from "@/helpers/issue.helper";
|
||||||
import { copyTextToClipboard, addSpaceIfCamelCase } from "@/helpers/string.helper";
|
import { copyTextToClipboard, addSpaceIfCamelCase } from "@/helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { IIssue, IPeekMode } from "@/types/issue";
|
import { IIssue, IPeekMode } from "@/types/issue";
|
||||||
// components
|
|
||||||
import { dueDateIconDetails } from "../board-views/block-due-date";
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issueDetails: IIssue;
|
issueDetails: IIssue;
|
||||||
@ -19,12 +20,9 @@ type Props = {
|
|||||||
|
|
||||||
export const PeekOverviewIssueProperties: React.FC<Props> = ({ issueDetails, mode }) => {
|
export const PeekOverviewIssueProperties: React.FC<Props> = ({ issueDetails, mode }) => {
|
||||||
const state = issueDetails.state_detail;
|
const state = issueDetails.state_detail;
|
||||||
const stateGroup = issueGroupFilter(state.group);
|
|
||||||
|
|
||||||
const priority = issueDetails.priority ? issuePriorityFilter(issueDetails.priority) : null;
|
const priority = issueDetails.priority ? issuePriorityFilter(issueDetails.priority) : null;
|
||||||
|
|
||||||
const dueDateIcon = dueDateIconDetails(issueDetails.target_date, state.group);
|
|
||||||
|
|
||||||
const handleCopyLink = () => {
|
const handleCopyLink = () => {
|
||||||
const urlToCopy = window.location.href;
|
const urlToCopy = window.location.href;
|
||||||
|
|
||||||
@ -51,28 +49,22 @@ export const PeekOverviewIssueProperties: React.FC<Props> = ({ issueDetails, mod
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={`space-y-4 ${mode === "full" ? "pt-3" : ""}`}>
|
<div className={`space-y-2 ${mode === "full" ? "pt-3" : ""}`}>
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-3 h-8">
|
||||||
<div className="flex w-1/4 flex-shrink-0 items-center gap-2 font-medium">
|
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
|
||||||
<Icon iconName="radio_button_checked" className="flex-shrink-0 !text-base" />
|
<DoubleCircleIcon className="size-4 flex-shrink-0" />
|
||||||
<span className="flex-grow truncate">State</span>
|
<span>State</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-3/4">
|
<div className="w-3/4 flex items-center gap-1.5 py-0.5 text-sm">
|
||||||
{stateGroup && (
|
<StateGroupIcon stateGroup={state.group} color={state.color} />
|
||||||
<div className="inline-flex rounded bg-custom-background-80 px-2.5 py-0.5 text-sm">
|
{addSpaceIfCamelCase(state?.name ?? "")}
|
||||||
<div className="flex items-center gap-1.5 text-left text-custom-text-100">
|
|
||||||
<StateGroupIcon stateGroup={state.group} color={state.color} />
|
|
||||||
{addSpaceIfCamelCase(state?.name ?? "")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-3 h-8">
|
||||||
<div className="flex w-1/4 flex-shrink-0 items-center gap-2 font-medium">
|
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
|
||||||
<Icon iconName="signal_cellular_alt" className="flex-shrink-0 !text-base" />
|
<Signal className="size-4 flex-shrink-0" />
|
||||||
<span className="flex-grow truncate">Priority</span>
|
<span>Priority</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-3/4">
|
<div className="w-3/4">
|
||||||
<div
|
<div
|
||||||
@ -97,18 +89,24 @@ export const PeekOverviewIssueProperties: React.FC<Props> = ({ issueDetails, mod
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 text-sm">
|
|
||||||
<div className="flex w-1/4 flex-shrink-0 items-center gap-2 font-medium">
|
<div className="flex items-center gap-3 h-8">
|
||||||
<Icon iconName="calendar_today" className="flex-shrink-0 !text-base" />
|
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
|
||||||
<span className="flex-grow truncate">Due date</span>
|
<CalendarCheck2 className="size-4 flex-shrink-0" />
|
||||||
|
<span>Due date</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{issueDetails.target_date ? (
|
{issueDetails.target_date ? (
|
||||||
<div className="flex h-6 items-center gap-1 rounded border border-custom-border-100 bg-custom-background-80 px-2.5 py-1 text-xs text-custom-text-100">
|
<div
|
||||||
<span className={`material-symbols-rounded -my-0.5 text-sm ${dueDateIcon.className}`}>
|
className={cn("flex items-center gap-1.5 rounded py-0.5 text-xs text-custom-text-100", {
|
||||||
{dueDateIcon.iconName}
|
"text-red-500": shouldHighlightIssueDueDate(
|
||||||
</span>
|
issueDetails.target_date,
|
||||||
{renderFullDate(issueDetails.target_date)}
|
issueDetails.state_detail.group
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<CalendarCheck2 className="size-3" />
|
||||||
|
{renderFormattedDate(issueDetails.target_date)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-custom-text-200">Empty</span>
|
<span className="text-custom-text-200">Empty</span>
|
||||||
|
@ -1,142 +0,0 @@
|
|||||||
import { Fragment, useState, useRef } from "react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { Check, ChevronLeft } from "lucide-react";
|
|
||||||
import { Popover, Transition } from "@headlessui/react";
|
|
||||||
// hooks
|
|
||||||
import useOutSideClick from "hooks/use-outside-click";
|
|
||||||
|
|
||||||
type ItemOptionType = {
|
|
||||||
display: React.ReactNode;
|
|
||||||
as?: "button" | "link" | "div";
|
|
||||||
href?: string;
|
|
||||||
isSelected?: boolean;
|
|
||||||
onClick?: () => void;
|
|
||||||
children?: ItemOptionType[] | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
type DropdownItemProps = {
|
|
||||||
item: ItemOptionType;
|
|
||||||
};
|
|
||||||
|
|
||||||
type DropDownListProps = {
|
|
||||||
open: boolean;
|
|
||||||
handleClose?: () => void;
|
|
||||||
items: ItemOptionType[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type DropdownProps = {
|
|
||||||
button: React.ReactNode | (() => React.ReactNode);
|
|
||||||
items: ItemOptionType[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const DropdownList: React.FC<DropDownListProps> = (props) => {
|
|
||||||
const { open, items, handleClose } = props;
|
|
||||||
|
|
||||||
const ref = useRef(null);
|
|
||||||
|
|
||||||
useOutSideClick(ref, () => {
|
|
||||||
if (handleClose) handleClose();
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover className="absolute -left-1">
|
|
||||||
<Transition
|
|
||||||
show={open}
|
|
||||||
as={Fragment}
|
|
||||||
enter="transition ease-out duration-200"
|
|
||||||
enterFrom="opacity-0 translate-y-1"
|
|
||||||
enterTo="opacity-100 translate-y-0"
|
|
||||||
leave="transition ease-in duration-150"
|
|
||||||
leaveFrom="opacity-100 translate-y-0"
|
|
||||||
leaveTo="opacity-0 translate-y-1"
|
|
||||||
>
|
|
||||||
<Popover.Panel
|
|
||||||
ref={ref}
|
|
||||||
className="absolute left-1/2 z-10 mt-1 max-w-[9rem] origin-top-right -translate-x-full select-none rounded-md border border-custom-border-300 bg-custom-background-90 text-xs shadow-lg focus:outline-none"
|
|
||||||
>
|
|
||||||
<div className="w-full rounded-md text-sm shadow-lg">
|
|
||||||
{items.map((item, index) => (
|
|
||||||
<DropdownItem key={index} item={item} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</Popover.Panel>
|
|
||||||
</Transition>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const DropdownItem: React.FC<DropdownItemProps> = (props) => {
|
|
||||||
const { item } = props;
|
|
||||||
const { display, children, as: itemAs, href, onClick, isSelected } = item;
|
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="group relative flex w-full gap-x-6 rounded-lg p-1">
|
|
||||||
{(!itemAs || itemAs === "button" || itemAs === "div") && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => {
|
|
||||||
if (!children) {
|
|
||||||
if (onClick) onClick();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setOpen((prev) => !prev);
|
|
||||||
}}
|
|
||||||
className={`flex w-full items-center gap-1 rounded px-1 py-1.5 text-custom-text-200 hover:bg-custom-background-80 ${
|
|
||||||
isSelected ? "bg-custom-background-80" : ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{children && <ChevronLeft className="h-4 w-4 transform transition-transform" strokeWidth={2} />}
|
|
||||||
{!children && <span />}
|
|
||||||
<span className="truncate text-xs">{display}</span>
|
|
||||||
<Check className={`h-3 w-3 opacity-0 ${isSelected ? "opacity-100" : ""}`} strokeWidth={2} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{itemAs === "link" && <Link href={href || "#"}>{display}</Link>}
|
|
||||||
|
|
||||||
{children && <DropdownList open={open} handleClose={() => setOpen(false)} items={children} />}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const Dropdown: React.FC<DropdownProps> = (props) => {
|
|
||||||
const { button, items } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover className="relative">
|
|
||||||
{({ open }) => (
|
|
||||||
<>
|
|
||||||
<Popover.Button
|
|
||||||
className={`group flex items-center justify-between gap-2 rounded-md border border-custom-border-200 px-3 py-1.5 text-xs shadow-sm duration-300 hover:bg-custom-background-90 hover:text-custom-text-100 focus:outline-none ${
|
|
||||||
open ? "bg-custom-background-90 text-custom-text-100" : "text-custom-text-200"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{typeof button === "function" ? button() : button}
|
|
||||||
</Popover.Button>
|
|
||||||
|
|
||||||
<Transition
|
|
||||||
as={Fragment}
|
|
||||||
enter="transition ease-out duration-200"
|
|
||||||
enterFrom="opacity-0 translate-y-1"
|
|
||||||
enterTo="opacity-100 translate-y-0"
|
|
||||||
leave="transition ease-in duration-150"
|
|
||||||
leaveFrom="opacity-100 translate-y-0"
|
|
||||||
leaveTo="opacity-0 translate-y-1"
|
|
||||||
>
|
|
||||||
<Popover.Panel className="absolute left-full z-10 mt-1 w-36 origin-top-right -translate-x-full select-none rounded-md border border-custom-border-300 bg-custom-background-90 text-xs shadow-lg focus:outline-none">
|
|
||||||
<div className="w-full">
|
|
||||||
{items.map((item, index) => (
|
|
||||||
<DropdownItem key={index} item={item} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</Popover.Panel>
|
|
||||||
</Transition>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { Dropdown };
|
|
@ -1,3 +1,2 @@
|
|||||||
export * from "./dropdown";
|
|
||||||
export * from "./icon";
|
export * from "./icon";
|
||||||
export * from "./reaction-selector";
|
export * from "./reaction-selector";
|
||||||
|
@ -1,2 +1 @@
|
|||||||
export * from "./auth";
|
export * from "./auth";
|
||||||
export * from "./project-details";
|
|
||||||
|
@ -1,13 +1,7 @@
|
|||||||
// interfaces
|
import { Calendar, GanttChartSquare, Kanban, List, Sheet } from "lucide-react";
|
||||||
import {
|
// types
|
||||||
TIssueLayout,
|
import { TIssuePriorities } from "@plane/types";
|
||||||
TIssueLayoutViews,
|
import { TIssueLayout, TIssueFilterKeys, TIssueFilterPriorityObject } from "@/types/issue";
|
||||||
TIssueFilterKeys,
|
|
||||||
TIssueFilterPriority,
|
|
||||||
TIssueFilterPriorityObject,
|
|
||||||
TIssueFilterState,
|
|
||||||
TIssueFilterStateObject,
|
|
||||||
} from "types/issue";
|
|
||||||
|
|
||||||
// issue filters
|
// issue filters
|
||||||
export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { [key in TIssueLayout]: Record<"filters", TIssueFilterKeys[]> } = {
|
export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { [key in TIssueLayout]: Record<"filters", TIssueFilterKeys[]> } = {
|
||||||
@ -28,20 +22,18 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { [key in TIssueLayout]: Record<"f
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const issueLayoutViews: Partial<TIssueLayoutViews> = {
|
export const ISSUE_LAYOUTS: {
|
||||||
list: {
|
key: TIssueLayout;
|
||||||
title: "List View",
|
title: string;
|
||||||
icon: "format_list_bulleted",
|
icon: any;
|
||||||
className: "",
|
}[] = [
|
||||||
},
|
{ key: "list", title: "List", icon: List },
|
||||||
kanban: {
|
{ key: "kanban", title: "Kanban", icon: Kanban },
|
||||||
title: "Board View",
|
{ key: "calendar", title: "Calendar", icon: Calendar },
|
||||||
icon: "grid_view",
|
{ key: "spreadsheet", title: "Spreadsheet", icon: Sheet },
|
||||||
className: "",
|
{ key: "gantt", title: "Gantt chart", icon: GanttChartSquare },
|
||||||
},
|
];
|
||||||
};
|
|
||||||
|
|
||||||
// issue priority filters
|
|
||||||
export const issuePriorityFilters: TIssueFilterPriorityObject[] = [
|
export const issuePriorityFilters: TIssueFilterPriorityObject[] = [
|
||||||
{
|
{
|
||||||
key: "urgent",
|
key: "urgent",
|
||||||
@ -75,7 +67,7 @@ export const issuePriorityFilters: TIssueFilterPriorityObject[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const issuePriorityFilter = (priorityKey: TIssueFilterPriority): TIssueFilterPriorityObject | undefined => {
|
export const issuePriorityFilter = (priorityKey: TIssuePriorities): TIssueFilterPriorityObject | undefined => {
|
||||||
const currentIssuePriority: TIssueFilterPriorityObject | undefined =
|
const currentIssuePriority: TIssueFilterPriorityObject | undefined =
|
||||||
issuePriorityFilters && issuePriorityFilters.length > 0
|
issuePriorityFilters && issuePriorityFilters.length > 0
|
||||||
? issuePriorityFilters.find((_priority) => _priority.key === priorityKey)
|
? issuePriorityFilters.find((_priority) => _priority.key === priorityKey)
|
||||||
@ -84,55 +76,3 @@ export const issuePriorityFilter = (priorityKey: TIssueFilterPriority): TIssueFi
|
|||||||
if (currentIssuePriority) return currentIssuePriority;
|
if (currentIssuePriority) return currentIssuePriority;
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
// issue group filters
|
|
||||||
export const issueGroupColors: {
|
|
||||||
[key in TIssueFilterState]: string;
|
|
||||||
} = {
|
|
||||||
backlog: "#d9d9d9",
|
|
||||||
unstarted: "#3f76ff",
|
|
||||||
started: "#f59e0b",
|
|
||||||
completed: "#16a34a",
|
|
||||||
cancelled: "#dc2626",
|
|
||||||
};
|
|
||||||
|
|
||||||
export const issueGroups: TIssueFilterStateObject[] = [
|
|
||||||
{
|
|
||||||
key: "backlog",
|
|
||||||
title: "Backlog",
|
|
||||||
color: "#d9d9d9",
|
|
||||||
className: `text-[#d9d9d9] bg-[#d9d9d9]/10`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "unstarted",
|
|
||||||
title: "Unstarted",
|
|
||||||
color: "#3f76ff",
|
|
||||||
className: `text-[#3f76ff] bg-[#3f76ff]/10`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "started",
|
|
||||||
title: "Started",
|
|
||||||
color: "#f59e0b",
|
|
||||||
className: `text-[#f59e0b] bg-[#f59e0b]/10`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "completed",
|
|
||||||
title: "Completed",
|
|
||||||
color: "#16a34a",
|
|
||||||
className: `text-[#16a34a] bg-[#16a34a]/10`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "cancelled",
|
|
||||||
title: "Cancelled",
|
|
||||||
color: "#dc2626",
|
|
||||||
className: `text-[#dc2626] bg-[#dc2626]/10`,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const issueGroupFilter = (issueKey: TIssueFilterState): TIssueFilterStateObject | undefined => {
|
|
||||||
const currentIssueStateGroup: TIssueFilterStateObject | undefined =
|
|
||||||
issueGroups && issueGroups.length > 0 ? issueGroups.find((group) => group.key === issueKey) : undefined;
|
|
||||||
|
|
||||||
if (currentIssueStateGroup) return currentIssueStateGroup;
|
|
||||||
return undefined;
|
|
||||||
};
|
|
||||||
|
37
space/constants/state.ts
Normal file
37
space/constants/state.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { TStateGroups } from "@plane/types";
|
||||||
|
|
||||||
|
export const STATE_GROUPS: {
|
||||||
|
[key in TStateGroups]: {
|
||||||
|
key: TStateGroups;
|
||||||
|
label: string;
|
||||||
|
color: string;
|
||||||
|
};
|
||||||
|
} = {
|
||||||
|
backlog: {
|
||||||
|
key: "backlog",
|
||||||
|
label: "Backlog",
|
||||||
|
color: "#d9d9d9",
|
||||||
|
},
|
||||||
|
unstarted: {
|
||||||
|
key: "unstarted",
|
||||||
|
label: "Unstarted",
|
||||||
|
color: "#3f76ff",
|
||||||
|
},
|
||||||
|
started: {
|
||||||
|
key: "started",
|
||||||
|
label: "Started",
|
||||||
|
color: "#f59e0b",
|
||||||
|
},
|
||||||
|
completed: {
|
||||||
|
key: "completed",
|
||||||
|
label: "Completed",
|
||||||
|
color: "#16a34a",
|
||||||
|
},
|
||||||
|
cancelled: {
|
||||||
|
key: "cancelled",
|
||||||
|
label: "Canceled",
|
||||||
|
color: "#dc2626",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ARCHIVABLE_STATE_GROUPS = [STATE_GROUPS.completed.key, STATE_GROUPS.cancelled.key];
|
@ -1,12 +0,0 @@
|
|||||||
export const USER_ROLES = [
|
|
||||||
{ value: "Product / Project Manager", label: "Product / Project Manager" },
|
|
||||||
{ value: "Development / Engineering", label: "Development / Engineering" },
|
|
||||||
{ value: "Founder / Executive", label: "Founder / Executive" },
|
|
||||||
{ value: "Freelancer / Consultant", label: "Freelancer / Consultant" },
|
|
||||||
{ value: "Marketing / Growth", label: "Marketing / Growth" },
|
|
||||||
{ value: "Sales / Business Development", label: "Sales / Business Development" },
|
|
||||||
{ value: "Support / Operations", label: "Support / Operations" },
|
|
||||||
{ value: "Student / Professor", label: "Student / Professor" },
|
|
||||||
{ value: "Human Resources", label: "Human Resources" },
|
|
||||||
{ value: "Other", label: "Other" },
|
|
||||||
];
|
|
@ -1,5 +0,0 @@
|
|||||||
"use server";
|
|
||||||
|
|
||||||
import { redirect } from "next/navigation";
|
|
||||||
|
|
||||||
export const navigate = async (path: string) => redirect(path);
|
|
@ -1,3 +1,6 @@
|
|||||||
|
import { format, isValid } from "date-fns";
|
||||||
|
import isNumber from "lodash/isNumber";
|
||||||
|
|
||||||
export const timeAgo = (time: any) => {
|
export const timeAgo = (time: any) => {
|
||||||
switch (typeof time) {
|
switch (typeof time) {
|
||||||
case "number":
|
case "number":
|
||||||
@ -14,24 +17,43 @@ export const timeAgo = (time: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Returns date and month, if date is of the current year
|
* This method returns a date from string of type yyyy-mm-dd
|
||||||
* @description Returns date, month adn year, if date is of a different year than current
|
* This method is recommended to use instead of new Date() as this does not introduce any timezone offsets
|
||||||
* @param {string} date
|
* @param date
|
||||||
* @example renderFullDate("2023-01-01") // 1 Jan
|
* @returns date or undefined
|
||||||
* @example renderFullDate("2021-01-01") // 1 Jan, 2021
|
|
||||||
*/
|
*/
|
||||||
|
export const getDate = (date: string | Date | undefined | null): Date | undefined => {
|
||||||
|
try {
|
||||||
|
if (!date || date === "") return;
|
||||||
|
|
||||||
export const renderFullDate = (date: string): string => {
|
if (typeof date !== "string" && !(date instanceof String)) return date;
|
||||||
if (!date) return "";
|
|
||||||
|
|
||||||
const months: string[] = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
const [yearString, monthString, dayString] = date.substring(0, 10).split("-");
|
||||||
|
const year = parseInt(yearString);
|
||||||
|
const month = parseInt(monthString);
|
||||||
|
const day = parseInt(dayString);
|
||||||
|
if (!isNumber(year) || !isNumber(month) || !isNumber(day)) return;
|
||||||
|
|
||||||
const currentDate: Date = new Date();
|
return new Date(year, month - 1, day);
|
||||||
const [year, month, day]: number[] = date.split("-").map(Number);
|
} catch (e) {
|
||||||
|
return undefined;
|
||||||
const formattedMonth: string = months[month - 1];
|
}
|
||||||
const formattedDay: string = day < 10 ? `0${day}` : day.toString();
|
};
|
||||||
|
|
||||||
if (currentDate.getFullYear() === year) return `${formattedDay} ${formattedMonth}`;
|
/**
|
||||||
else return `${formattedDay} ${formattedMonth}, ${year}`;
|
* @returns {string | null} formatted date in the format of MMM dd, yyyy
|
||||||
|
* @description Returns date in the formatted format
|
||||||
|
* @param {Date | string} date
|
||||||
|
* @example renderFormattedDate("2024-01-01") // Jan 01, 2024
|
||||||
|
*/
|
||||||
|
export const renderFormattedDate = (date: string | Date | undefined | null): string | null => {
|
||||||
|
// Parse the date to check if it is valid
|
||||||
|
const parsedDate = getDate(date);
|
||||||
|
// return if undefined
|
||||||
|
if (!parsedDate) return null;
|
||||||
|
// Check if the parsed date is valid before formatting
|
||||||
|
if (!isValid(parsedDate)) return null; // Return null for invalid dates
|
||||||
|
// Format the date in format (MMM dd, yyyy)
|
||||||
|
const formattedDate = format(parsedDate, "MMM dd, yyyy");
|
||||||
|
return formattedDate;
|
||||||
};
|
};
|
||||||
|
@ -1,23 +1,3 @@
|
|||||||
export const getRandomEmoji = () => {
|
|
||||||
const emojis = [
|
|
||||||
"8986",
|
|
||||||
"9200",
|
|
||||||
"128204",
|
|
||||||
"127773",
|
|
||||||
"127891",
|
|
||||||
"127947",
|
|
||||||
"128076",
|
|
||||||
"128077",
|
|
||||||
"128187",
|
|
||||||
"128188",
|
|
||||||
"128512",
|
|
||||||
"128522",
|
|
||||||
"128578",
|
|
||||||
];
|
|
||||||
|
|
||||||
return emojis[Math.floor(Math.random() * emojis.length)];
|
|
||||||
};
|
|
||||||
|
|
||||||
export const renderEmoji = (
|
export const renderEmoji = (
|
||||||
emoji:
|
emoji:
|
||||||
| string
|
| string
|
||||||
|
30
space/helpers/issue.helper.ts
Normal file
30
space/helpers/issue.helper.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { differenceInCalendarDays } from "date-fns";
|
||||||
|
// types
|
||||||
|
import { TStateGroups } from "@plane/types";
|
||||||
|
// constants
|
||||||
|
import { STATE_GROUPS } from "@/constants/state";
|
||||||
|
// helpers
|
||||||
|
import { getDate } from "@/helpers/date-time.helper";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description check if the issue due date should be highlighted
|
||||||
|
* @param date
|
||||||
|
* @param stateGroup
|
||||||
|
* @returns boolean
|
||||||
|
*/
|
||||||
|
export const shouldHighlightIssueDueDate = (
|
||||||
|
date: string | Date | null,
|
||||||
|
stateGroup: TStateGroups | undefined
|
||||||
|
): boolean => {
|
||||||
|
if (!date || !stateGroup) return false;
|
||||||
|
// if the issue is completed or cancelled, don't highlight the due date
|
||||||
|
if ([STATE_GROUPS.completed.key, STATE_GROUPS.cancelled.key].includes(stateGroup)) return false;
|
||||||
|
|
||||||
|
const parsedDate = getDate(date);
|
||||||
|
if (!parsedDate) return false;
|
||||||
|
|
||||||
|
const targetDateDistance = differenceInCalendarDays(parsedDate, new Date());
|
||||||
|
|
||||||
|
// if the issue is overdue, highlight the due date
|
||||||
|
return targetDateDistance <= 0;
|
||||||
|
};
|
@ -3,7 +3,7 @@ import DOMPurify from "dompurify";
|
|||||||
export const addSpaceIfCamelCase = (str: string) => str.replace(/([a-z])([A-Z])/g, "$1 $2");
|
export const addSpaceIfCamelCase = (str: string) => str.replace(/([a-z])([A-Z])/g, "$1 $2");
|
||||||
|
|
||||||
const fallbackCopyTextToClipboard = (text: string) => {
|
const fallbackCopyTextToClipboard = (text: string) => {
|
||||||
var textArea = document.createElement("textarea");
|
const textArea = document.createElement("textarea");
|
||||||
textArea.value = text;
|
textArea.value = text;
|
||||||
|
|
||||||
// Avoid scrolling to bottom
|
// Avoid scrolling to bottom
|
||||||
@ -18,7 +18,7 @@ const fallbackCopyTextToClipboard = (text: string) => {
|
|||||||
try {
|
try {
|
||||||
// FIXME: Even though we are using this as a fallback, execCommand is deprecated 👎. We should find a better way to do this.
|
// FIXME: Even though we are using this as a fallback, execCommand is deprecated 👎. We should find a better way to do this.
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand
|
// https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand
|
||||||
var successful = document.execCommand("copy");
|
document.execCommand("copy");
|
||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
|
|
||||||
document.body.removeChild(textArea);
|
document.body.removeChild(textArea);
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { useRef, useEffect } from "react";
|
import { useRef, useEffect } from "react";
|
||||||
import useSWR from "swr";
|
import useSWR from "swr";
|
||||||
|
// types
|
||||||
import { IUser } from "@plane/types";
|
import { IUser } from "@plane/types";
|
||||||
import { UserService } from "services/user.service";
|
// services
|
||||||
|
import { UserService } from "@/services/user.service";
|
||||||
|
|
||||||
export const useMention = () => {
|
export const useMention = () => {
|
||||||
const userService = new UserService();
|
const userService = new UserService();
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
"@sentry/nextjs": "^8",
|
"@sentry/nextjs": "^8",
|
||||||
"axios": "^1.3.4",
|
"axios": "^1.3.4",
|
||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
|
"date-fns": "^3.6.0",
|
||||||
"dompurify": "^3.0.11",
|
"dompurify": "^3.0.11",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import { observable, action, makeObservable, runInAction } from "mobx";
|
import { observable, action, makeObservable, runInAction } from "mobx";
|
||||||
import { computedFn } from "mobx-utils";
|
import { computedFn } from "mobx-utils";
|
||||||
|
// types
|
||||||
|
import { IStateLite } from "@plane/types";
|
||||||
// services
|
// services
|
||||||
import IssueService from "@/services/issue.service";
|
import IssueService from "@/services/issue.service";
|
||||||
// types
|
// types
|
||||||
import { IIssue, IIssueState, IIssueLabel } from "@/types/issue";
|
import { IIssue, IIssueLabel } from "@/types/issue";
|
||||||
// store
|
// store
|
||||||
import { RootStore } from "./root.store";
|
import { RootStore } from "./root.store";
|
||||||
|
|
||||||
@ -12,7 +14,7 @@ export interface IIssueStore {
|
|||||||
error: any;
|
error: any;
|
||||||
// observables
|
// observables
|
||||||
issues: IIssue[];
|
issues: IIssue[];
|
||||||
states: IIssueState[];
|
states: IStateLite[];
|
||||||
labels: IIssueLabel[];
|
labels: IIssueLabel[];
|
||||||
// filter observables
|
// filter observables
|
||||||
filteredStates: string[];
|
filteredStates: string[];
|
||||||
@ -29,7 +31,7 @@ export class IssueStore implements IIssueStore {
|
|||||||
loader: boolean = false;
|
loader: boolean = false;
|
||||||
error: any | null = null;
|
error: any | null = null;
|
||||||
// observables
|
// observables
|
||||||
states: IIssueState[] = [];
|
states: IStateLite[] = [];
|
||||||
labels: IIssueLabel[] = [];
|
labels: IIssueLabel[] = [];
|
||||||
issues: IIssue[] = [];
|
issues: IIssue[] = [];
|
||||||
// filter observables
|
// filter observables
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import { observable, makeObservable, computed } from "mobx";
|
import { observable, makeObservable, computed } from "mobx";
|
||||||
|
// types
|
||||||
|
import { IWorkspaceLite } from "@plane/types";
|
||||||
// store types
|
// store types
|
||||||
import { RootStore } from "@/store/root.store";
|
import { RootStore } from "@/store/root.store";
|
||||||
// types
|
// types
|
||||||
import { TProjectDetails, TViewDetails, TWorkspaceDetails } from "@/types/project";
|
import { TProjectDetails, TViewDetails } from "@/types/project";
|
||||||
import { TPublishEntityType, TPublishSettings } from "@/types/publish";
|
import { TPublishEntityType, TPublishSettings } from "@/types/publish";
|
||||||
|
|
||||||
export interface IPublishStore extends TPublishSettings {
|
export interface IPublishStore extends TPublishSettings {
|
||||||
@ -31,7 +33,7 @@ export class PublishStore implements IPublishStore {
|
|||||||
view_props: TViewDetails | undefined;
|
view_props: TViewDetails | undefined;
|
||||||
votes: boolean;
|
votes: boolean;
|
||||||
workspace: string | undefined;
|
workspace: string | undefined;
|
||||||
workspace_detail: TWorkspaceDetails | undefined;
|
workspace_detail: IWorkspaceLite | undefined;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private store: RootStore,
|
private store: RootStore,
|
||||||
|
@ -38,13 +38,15 @@ export class PublishListStore implements IPublishListStore {
|
|||||||
*/
|
*/
|
||||||
fetchPublishSettings = async (anchor: string) => {
|
fetchPublishSettings = async (anchor: string) => {
|
||||||
try {
|
try {
|
||||||
const publishSettings = await this.publishService.fetchPublishSettings(anchor);
|
const response = await this.publishService.fetchPublishSettings(anchor);
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
if (publishSettings.anchor)
|
if (response.anchor && response.view_props) {
|
||||||
set(this.publishMap, [publishSettings.anchor], new PublishStore(this.store, publishSettings));
|
this.store.issueFilter.updateLayoutOptions(response?.view_props);
|
||||||
|
set(this.publishMap, [response.anchor], new PublishStore(this.store, response));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return publishSettings;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
@ -302,6 +302,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
font-variant-ligatures: none;
|
||||||
|
-webkit-font-variant-ligatures: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: rgba(var(--color-text-100));
|
||||||
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 5px;
|
width: 5px;
|
||||||
height: 5px;
|
height: 5px;
|
||||||
|
14
space/types/app.d.ts
vendored
14
space/types/app.d.ts
vendored
@ -1,14 +0,0 @@
|
|||||||
export interface IAppConfig {
|
|
||||||
email_password_login: boolean;
|
|
||||||
file_size_limit: number;
|
|
||||||
google_client_id: string | null;
|
|
||||||
github_app_name: string | null;
|
|
||||||
github_client_id: string | null;
|
|
||||||
magic_login: boolean;
|
|
||||||
slack_client_id: string | null;
|
|
||||||
posthog_api_key: string | null;
|
|
||||||
posthog_host: string | null;
|
|
||||||
has_openai_configured: boolean;
|
|
||||||
has_unsplash_configured: boolean;
|
|
||||||
is_self_managed: boolean;
|
|
||||||
}
|
|
75
space/types/issue.d.ts
vendored
75
space/types/issue.d.ts
vendored
@ -1,27 +1,17 @@
|
|||||||
|
import { IStateLite, IWorkspaceLite, TIssuePriorities, TStateGroups } from "@plane/types";
|
||||||
|
|
||||||
export type TIssueLayout = "list" | "kanban" | "calendar" | "spreadsheet" | "gantt";
|
export type TIssueLayout = "list" | "kanban" | "calendar" | "spreadsheet" | "gantt";
|
||||||
export type TIssueLayoutOptions = {
|
export type TIssueLayoutOptions = {
|
||||||
[key in TIssueLayout]: boolean;
|
[key in TIssueLayout]: boolean;
|
||||||
};
|
};
|
||||||
export type TIssueLayoutViews = {
|
|
||||||
[key in TIssueLayout]: { title: string; icon: string; className: string };
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TIssueFilterPriority = "urgent" | "high" | "medium" | "low" | "none";
|
|
||||||
export type TIssueFilterPriorityObject = {
|
export type TIssueFilterPriorityObject = {
|
||||||
key: TIssueFilterPriority;
|
key: TIssuePriorities;
|
||||||
title: string;
|
title: string;
|
||||||
className: string;
|
className: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TIssueFilterState = "backlog" | "unstarted" | "started" | "completed" | "cancelled";
|
|
||||||
export type TIssueFilterStateObject = {
|
|
||||||
key: TIssueFilterState;
|
|
||||||
title: string;
|
|
||||||
color: string;
|
|
||||||
className: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TIssueFilterKeys = "priority" | "state" | "labels";
|
export type TIssueFilterKeys = "priority" | "state" | "labels";
|
||||||
|
|
||||||
export type TDisplayFilters = {
|
export type TDisplayFilters = {
|
||||||
@ -29,8 +19,8 @@ export type TDisplayFilters = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TFilters = {
|
export type TFilters = {
|
||||||
state: TIssueFilterState[];
|
state: TStateGroups[];
|
||||||
priority: TIssueFilterPriority[];
|
priority: TIssuePriorities[];
|
||||||
labels: string[];
|
labels: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -44,7 +34,7 @@ export type TIssueQueryFilters = Partial<TFilters>;
|
|||||||
export type TIssueQueryFiltersParams = Partial<Record<keyof TFilters, string>>;
|
export type TIssueQueryFiltersParams = Partial<Record<keyof TFilters, string>>;
|
||||||
|
|
||||||
export type TIssuesResponse = {
|
export type TIssuesResponse = {
|
||||||
states: IIssueState[];
|
states: IStateLite[];
|
||||||
labels: IIssueLabel[];
|
labels: IIssueLabel[];
|
||||||
issues: IIssue[];
|
issues: IIssue[];
|
||||||
};
|
};
|
||||||
@ -74,13 +64,6 @@ export interface IIssue {
|
|||||||
|
|
||||||
export type IPeekMode = "side" | "modal" | "full";
|
export type IPeekMode = "side" | "modal" | "full";
|
||||||
|
|
||||||
export interface IIssueState {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
group: TIssueGroupKey;
|
|
||||||
color: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IIssueLabel {
|
export interface IIssueLabel {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@ -121,7 +104,7 @@ export interface Comment {
|
|||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
updated_by: string;
|
updated_by: string;
|
||||||
workspace: string;
|
workspace: string;
|
||||||
workspace_detail: WorkspaceDetail;
|
workspace_detail: IWorkspaceLite;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IIssueReaction {
|
export interface IIssueReaction {
|
||||||
@ -182,52 +165,8 @@ export interface ProjectDetail {
|
|||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkspaceDetail {
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IssueDetailType {
|
|
||||||
[issueId: string]: {
|
|
||||||
issue: IIssue;
|
|
||||||
comments: Comment[];
|
|
||||||
reactions: any[];
|
|
||||||
votes: any[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TIssueGroupByOptions = "state" | "priority" | "labels" | null;
|
|
||||||
|
|
||||||
export type TIssueParams = "priority" | "state" | "labels";
|
|
||||||
|
|
||||||
export interface IIssueFilterOptions {
|
export interface IIssueFilterOptions {
|
||||||
state?: string[] | null;
|
state?: string[] | null;
|
||||||
labels?: string[] | null;
|
labels?: string[] | null;
|
||||||
priority?: string[] | null;
|
priority?: string[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// issues
|
|
||||||
export interface IGroupedIssues {
|
|
||||||
[group_id: string]: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISubGroupedIssues {
|
|
||||||
[sub_grouped_id: string]: {
|
|
||||||
[group_id: string]: string[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TUnGroupedIssues = string[];
|
|
||||||
|
|
||||||
export interface IIssueResponse {
|
|
||||||
[issue_id: string]: IIssue;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TLoader = "init-loader" | "mutation" | undefined;
|
|
||||||
|
|
||||||
export interface ViewFlags {
|
|
||||||
enableQuickAdd: boolean;
|
|
||||||
enableIssueCreation: boolean;
|
|
||||||
enableInlineEditing: boolean;
|
|
||||||
}
|
|
||||||
|
6
space/types/project.d.ts
vendored
6
space/types/project.d.ts
vendored
@ -1,11 +1,5 @@
|
|||||||
import { TLogoProps } from "@plane/types";
|
import { TLogoProps } from "@plane/types";
|
||||||
|
|
||||||
export type TWorkspaceDetails = {
|
|
||||||
name: string;
|
|
||||||
slug: string;
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TViewDetails = {
|
export type TViewDetails = {
|
||||||
list: boolean;
|
list: boolean;
|
||||||
gantt: boolean;
|
gantt: boolean;
|
||||||
|
5
space/types/publish.d.ts
vendored
5
space/types/publish.d.ts
vendored
@ -1,4 +1,5 @@
|
|||||||
import { TProjectDetails, TViewDetails, TWorkspaceDetails } from "./project";
|
import { IWorkspaceLite } from "@plane/types";
|
||||||
|
import { TProjectDetails, TViewDetails } from "@/types/project";
|
||||||
|
|
||||||
export type TPublishEntityType = "project";
|
export type TPublishEntityType = "project";
|
||||||
|
|
||||||
@ -19,5 +20,5 @@ export type TPublishSettings = {
|
|||||||
view_props: TViewDetails | undefined;
|
view_props: TViewDetails | undefined;
|
||||||
votes: boolean;
|
votes: boolean;
|
||||||
workspace: string | undefined;
|
workspace: string | undefined;
|
||||||
workspace_detail: TWorkspaceDetails | undefined;
|
workspace_detail: IWorkspaceLite | undefined;
|
||||||
};
|
};
|
||||||
|
4
space/types/theme.d.ts
vendored
4
space/types/theme.d.ts
vendored
@ -1,4 +0,0 @@
|
|||||||
export interface IThemeStore {
|
|
||||||
theme: string;
|
|
||||||
setTheme: (theme: "light" | "dark" | string) => void;
|
|
||||||
}
|
|
30
space/types/user.d.ts
vendored
30
space/types/user.d.ts
vendored
@ -1,30 +0,0 @@
|
|||||||
export interface IUser {
|
|
||||||
avatar: string;
|
|
||||||
cover_image: string | null;
|
|
||||||
created_at: Date;
|
|
||||||
created_location: string;
|
|
||||||
date_joined: Date;
|
|
||||||
email: string;
|
|
||||||
display_name: string;
|
|
||||||
first_name: string;
|
|
||||||
id: string;
|
|
||||||
is_email_verified: boolean;
|
|
||||||
is_onboarded: boolean;
|
|
||||||
is_tour_completed: boolean;
|
|
||||||
last_location: string;
|
|
||||||
last_login: Date;
|
|
||||||
last_name: string;
|
|
||||||
mobile_number: string;
|
|
||||||
role: string;
|
|
||||||
is_password_autoset: boolean;
|
|
||||||
onboarding_step: {
|
|
||||||
workspace_join?: boolean;
|
|
||||||
profile_complete?: boolean;
|
|
||||||
workspace_create?: boolean;
|
|
||||||
workspace_invite?: boolean;
|
|
||||||
};
|
|
||||||
token: string;
|
|
||||||
updated_at: Date;
|
|
||||||
username: string;
|
|
||||||
user_timezone: string;
|
|
||||||
}
|
|
@ -25,6 +25,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type FormData = {
|
type FormData = {
|
||||||
|
anchor: string;
|
||||||
id: string | null;
|
id: string | null;
|
||||||
comments: boolean;
|
comments: boolean;
|
||||||
reactions: boolean;
|
reactions: boolean;
|
||||||
@ -34,6 +35,7 @@ type FormData = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const defaultValues: FormData = {
|
const defaultValues: FormData = {
|
||||||
|
anchor: "",
|
||||||
id: null,
|
id: null,
|
||||||
comments: false,
|
comments: false,
|
||||||
reactions: false,
|
reactions: false,
|
||||||
@ -48,34 +50,27 @@ const viewOptions: {
|
|||||||
}[] = [
|
}[] = [
|
||||||
{ key: "list", label: "List" },
|
{ key: "list", label: "List" },
|
||||||
{ key: "kanban", label: "Kanban" },
|
{ key: "kanban", label: "Kanban" },
|
||||||
// { key: "calendar", label: "Calendar" },
|
|
||||||
// { key: "gantt", label: "Gantt" },
|
|
||||||
// { key: "spreadsheet", label: "Spreadsheet" },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const PublishProjectModal: React.FC<Props> = observer((props) => {
|
export const PublishProjectModal: React.FC<Props> = observer((props) => {
|
||||||
const { isOpen, project, onClose } = props;
|
const { isOpen, project, onClose } = props;
|
||||||
// hooks
|
|
||||||
// const { instance } = useInstance();
|
|
||||||
// states
|
// states
|
||||||
const [isUnPublishing, setIsUnPublishing] = useState(false);
|
const [isUnPublishing, setIsUnPublishing] = useState(false);
|
||||||
const [isUpdateRequired, setIsUpdateRequired] = useState(false);
|
const [isUpdateRequired, setIsUpdateRequired] = useState(false);
|
||||||
|
|
||||||
// const plane_deploy_url = instance?.config?.space_base_url || "";
|
|
||||||
const SPACE_URL = (SPACE_BASE_URL === "" ? window.location.origin : SPACE_BASE_URL) + SPACE_BASE_PATH;
|
|
||||||
|
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug } = router.query;
|
const { workspaceSlug } = router.query;
|
||||||
// store hooks
|
// store hooks
|
||||||
const {
|
const {
|
||||||
projectPublishSettings,
|
fetchPublishSettings,
|
||||||
getProjectSettingsAsync,
|
getPublishSettingsByProjectID,
|
||||||
publishProject,
|
publishProject,
|
||||||
updateProjectSettingsAsync,
|
updatePublishSettings,
|
||||||
unPublishProject,
|
unPublishProject,
|
||||||
fetchSettingsLoader,
|
fetchSettingsLoader,
|
||||||
} = useProjectPublish();
|
} = useProjectPublish();
|
||||||
|
// derived values
|
||||||
|
const projectPublishSettings = getPublishSettingsByProjectID(project.id);
|
||||||
// form info
|
// form info
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
@ -97,44 +92,44 @@ export const PublishProjectModal: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
// prefill form with the saved settings if the project is already published
|
// prefill form with the saved settings if the project is already published
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (projectPublishSettings && projectPublishSettings !== "not-initialized") {
|
if (!projectPublishSettings) return;
|
||||||
let userBoards: TProjectPublishViews[] = [];
|
|
||||||
|
|
||||||
if (projectPublishSettings?.views) {
|
let userBoards: TProjectPublishViews[] = [];
|
||||||
const savedViews = projectPublishSettings?.views;
|
|
||||||
|
|
||||||
if (!savedViews) return;
|
if (projectPublishSettings?.view_props) {
|
||||||
|
const savedViews = projectPublishSettings?.view_props;
|
||||||
|
|
||||||
if (savedViews.list) userBoards.push("list");
|
if (!savedViews) return;
|
||||||
if (savedViews.kanban) userBoards.push("kanban");
|
|
||||||
if (savedViews.calendar) userBoards.push("calendar");
|
|
||||||
if (savedViews.gantt) userBoards.push("gantt");
|
|
||||||
if (savedViews.spreadsheet) userBoards.push("spreadsheet");
|
|
||||||
|
|
||||||
userBoards = userBoards && userBoards.length > 0 ? userBoards : ["list"];
|
if (savedViews.list) userBoards.push("list");
|
||||||
}
|
if (savedViews.kanban) userBoards.push("kanban");
|
||||||
|
if (savedViews.calendar) userBoards.push("calendar");
|
||||||
|
if (savedViews.gantt) userBoards.push("gantt");
|
||||||
|
if (savedViews.spreadsheet) userBoards.push("spreadsheet");
|
||||||
|
|
||||||
const updatedData = {
|
userBoards = userBoards && userBoards.length > 0 ? userBoards : ["list"];
|
||||||
id: projectPublishSettings?.id || null,
|
|
||||||
comments: projectPublishSettings?.comments || false,
|
|
||||||
reactions: projectPublishSettings?.reactions || false,
|
|
||||||
votes: projectPublishSettings?.votes || false,
|
|
||||||
inbox: projectPublishSettings?.inbox || null,
|
|
||||||
views: userBoards,
|
|
||||||
};
|
|
||||||
|
|
||||||
reset({ ...updatedData });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updatedData = {
|
||||||
|
id: projectPublishSettings?.id || null,
|
||||||
|
comments: projectPublishSettings?.comments || false,
|
||||||
|
reactions: projectPublishSettings?.reactions || false,
|
||||||
|
votes: projectPublishSettings?.votes || false,
|
||||||
|
inbox: projectPublishSettings?.inbox || null,
|
||||||
|
views: userBoards,
|
||||||
|
};
|
||||||
|
|
||||||
|
reset({ ...updatedData });
|
||||||
}, [reset, projectPublishSettings, isOpen]);
|
}, [reset, projectPublishSettings, isOpen]);
|
||||||
|
|
||||||
// fetch publish settings
|
// fetch publish settings
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!workspaceSlug || !isOpen) return;
|
if (!workspaceSlug || !isOpen) return;
|
||||||
|
|
||||||
if (projectPublishSettings === "not-initialized") {
|
if (!projectPublishSettings) {
|
||||||
getProjectSettingsAsync(workspaceSlug.toString(), project.id);
|
fetchPublishSettings(workspaceSlug.toString(), project.id);
|
||||||
}
|
}
|
||||||
}, [isOpen, workspaceSlug, project, projectPublishSettings, getProjectSettingsAsync]);
|
}, [fetchPublishSettings, isOpen, project, projectPublishSettings, workspaceSlug]);
|
||||||
|
|
||||||
const handlePublishProject = async (payload: IProjectPublishSettings) => {
|
const handlePublishProject = async (payload: IProjectPublishSettings) => {
|
||||||
if (!workspaceSlug) return;
|
if (!workspaceSlug) return;
|
||||||
@ -145,7 +140,7 @@ export const PublishProjectModal: React.FC<Props> = observer((props) => {
|
|||||||
const handleUpdatePublishSettings = async (payload: IProjectPublishSettings) => {
|
const handleUpdatePublishSettings = async (payload: IProjectPublishSettings) => {
|
||||||
if (!workspaceSlug) return;
|
if (!workspaceSlug) return;
|
||||||
|
|
||||||
await updateProjectSettingsAsync(workspaceSlug.toString(), project.id, payload.id ?? "", payload)
|
await updatePublishSettings(workspaceSlug.toString(), project.id, payload.id ?? "", payload)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
@ -172,7 +167,7 @@ export const PublishProjectModal: React.FC<Props> = observer((props) => {
|
|||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.ERROR,
|
type: TOAST_TYPE.ERROR,
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message: "Something went wrong while un-publishing the project.",
|
message: "Something went wrong while unpublishing the project.",
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.finally(() => setIsUnPublishing(false));
|
.finally(() => setIsUnPublishing(false));
|
||||||
@ -214,7 +209,7 @@ export const PublishProjectModal: React.FC<Props> = observer((props) => {
|
|||||||
reactions: formData.reactions,
|
reactions: formData.reactions,
|
||||||
votes: formData.votes,
|
votes: formData.votes,
|
||||||
inbox: formData.inbox,
|
inbox: formData.inbox,
|
||||||
views: {
|
view_props: {
|
||||||
list: formData.views.includes("list"),
|
list: formData.views.includes("list"),
|
||||||
kanban: formData.views.includes("kanban"),
|
kanban: formData.views.includes("kanban"),
|
||||||
calendar: formData.views.includes("calendar"),
|
calendar: formData.views.includes("calendar"),
|
||||||
@ -223,13 +218,18 @@ export const PublishProjectModal: React.FC<Props> = observer((props) => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (project.is_deployed) await handleUpdatePublishSettings({ id: watch("id") ?? "", ...payload });
|
if (project.is_deployed)
|
||||||
|
await handleUpdatePublishSettings({
|
||||||
|
anchor: watch("anchor") ?? "",
|
||||||
|
id: watch("id") ?? "",
|
||||||
|
...payload,
|
||||||
|
});
|
||||||
else await handlePublishProject(payload);
|
else await handlePublishProject(payload);
|
||||||
};
|
};
|
||||||
|
|
||||||
// check if an update is required or not
|
// check if an update is required or not
|
||||||
const checkIfUpdateIsRequired = () => {
|
const checkIfUpdateIsRequired = () => {
|
||||||
if (!projectPublishSettings || projectPublishSettings === "not-initialized") return;
|
if (!projectPublishSettings || !projectPublishSettings) return;
|
||||||
|
|
||||||
const currentSettings = projectPublishSettings;
|
const currentSettings = projectPublishSettings;
|
||||||
const newSettings = getValues();
|
const newSettings = getValues();
|
||||||
@ -245,7 +245,7 @@ export const PublishProjectModal: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
let viewCheckFlag = 0;
|
let viewCheckFlag = 0;
|
||||||
viewOptions.forEach((option) => {
|
viewOptions.forEach((option) => {
|
||||||
if (currentSettings.views[option.key] !== newSettings.views.includes(option.key)) viewCheckFlag++;
|
if (currentSettings.view_props?.[option.key] !== newSettings.views.includes(option.key)) viewCheckFlag++;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (viewCheckFlag !== 0) {
|
if (viewCheckFlag !== 0) {
|
||||||
@ -256,6 +256,8 @@ export const PublishProjectModal: React.FC<Props> = observer((props) => {
|
|||||||
setIsUpdateRequired(false);
|
setIsUpdateRequired(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SPACE_URL = (SPACE_BASE_URL === "" ? window.location.origin : SPACE_BASE_URL) + SPACE_BASE_PATH;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={isOpen} as={Fragment}>
|
<Transition.Root show={isOpen} as={Fragment}>
|
||||||
<Dialog as="div" className="relative z-20" onClose={handleClose}>
|
<Dialog as="div" className="relative z-20" onClose={handleClose}>
|
||||||
@ -293,7 +295,7 @@ export const PublishProjectModal: React.FC<Props> = observer((props) => {
|
|||||||
onClick={() => handleUnPublishProject(watch("id") ?? "")}
|
onClick={() => handleUnPublishProject(watch("id") ?? "")}
|
||||||
loading={isUnPublishing}
|
loading={isUnPublishing}
|
||||||
>
|
>
|
||||||
{isUnPublishing ? "Un-publishing..." : "Un-publish"}
|
{isUnPublishing ? "Unpublishing" : "Unpublish"}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -308,12 +310,12 @@ export const PublishProjectModal: React.FC<Props> = observer((props) => {
|
|||||||
</Loader>
|
</Loader>
|
||||||
) : (
|
) : (
|
||||||
<div className="px-6">
|
<div className="px-6">
|
||||||
{project.is_deployed && (
|
{project.is_deployed && projectPublishSettings && (
|
||||||
<>
|
<>
|
||||||
<div className="relative flex items-center gap-2 rounded-md border border-custom-border-100 bg-custom-background-80 px-3 py-2">
|
<div className="relative flex items-center gap-2 rounded-md border border-custom-border-100 bg-custom-background-80 px-3 py-2">
|
||||||
<div className="flex-grow truncate text-sm">{`${SPACE_URL}/issues/`}</div>
|
<div className="flex-grow truncate text-sm">{`${SPACE_URL}/issues/${projectPublishSettings.anchor}`}</div>
|
||||||
<div className="relative flex flex-shrink-0 items-center gap-1">
|
<div className="relative flex flex-shrink-0 items-center gap-1">
|
||||||
<CopyLinkToClipboard copy_link={`${SPACE_URL}/issues`} />
|
<CopyLinkToClipboard copy_link={`${SPACE_URL}/issues/${projectPublishSettings.anchor}`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-3 flex items-center gap-1 text-custom-primary-100">
|
<div className="mt-3 flex items-center gap-1 text-custom-primary-100">
|
||||||
|
@ -34,7 +34,7 @@ export class ProjectPublishService extends APIService {
|
|||||||
projectID: string,
|
projectID: string,
|
||||||
project_publish_id: string,
|
project_publish_id: string,
|
||||||
data: IProjectPublishSettings
|
data: IProjectPublishSettings
|
||||||
): Promise<any> {
|
): Promise<IProjectPublishSettings> {
|
||||||
return this.patch(
|
return this.patch(
|
||||||
`/api/workspaces/${workspaceSlug}/projects/${projectID}/project-deploy-boards/${project_publish_id}/`,
|
`/api/workspaces/${workspaceSlug}/projects/${projectID}/project-deploy-boards/${project_publish_id}/`,
|
||||||
data
|
data
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import set from "lodash/set";
|
import set from "lodash/set";
|
||||||
|
import unset from "lodash/unset";
|
||||||
import { observable, action, makeObservable, runInAction } from "mobx";
|
import { observable, action, makeObservable, runInAction } from "mobx";
|
||||||
// types
|
// types
|
||||||
import { ProjectPublishService } from "@/services/project";
|
import { ProjectPublishService } from "@/services/project";
|
||||||
@ -12,12 +13,13 @@ export type TProjectPublishViewsSettings = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export interface IProjectPublishSettings {
|
export interface IProjectPublishSettings {
|
||||||
|
anchor?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
project?: string;
|
project?: string;
|
||||||
comments: boolean;
|
comments: boolean;
|
||||||
reactions: boolean;
|
reactions: boolean;
|
||||||
votes: boolean;
|
votes: boolean;
|
||||||
views: TProjectPublishViewsSettings;
|
view_props: TProjectPublishViewsSettings;
|
||||||
inbox: string | null;
|
inbox: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,31 +28,31 @@ export interface IProjectPublishStore {
|
|||||||
generalLoader: boolean;
|
generalLoader: boolean;
|
||||||
fetchSettingsLoader: boolean;
|
fetchSettingsLoader: boolean;
|
||||||
// observables
|
// observables
|
||||||
projectPublishSettings: IProjectPublishSettings | "not-initialized";
|
publishSettingsMap: Record<string, IProjectPublishSettings>; // projectID => IProjectPublishSettings
|
||||||
// project settings actions
|
// helpers
|
||||||
getProjectSettingsAsync: (workspaceSlug: string, projectId: string) => Promise<IProjectPublishSettings>;
|
getPublishSettingsByProjectID: (projectID: string) => IProjectPublishSettings | undefined;
|
||||||
updateProjectSettingsAsync: (
|
// actions
|
||||||
|
fetchPublishSettings: (workspaceSlug: string, projectID: string) => Promise<IProjectPublishSettings>;
|
||||||
|
updatePublishSettings: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectID: string,
|
||||||
projectPublishId: string,
|
projectPublishId: string,
|
||||||
data: IProjectPublishSettings
|
data: IProjectPublishSettings
|
||||||
) => Promise<void>;
|
) => Promise<IProjectPublishSettings>;
|
||||||
// project publish actions
|
|
||||||
publishProject: (
|
publishProject: (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectID: string,
|
||||||
data: IProjectPublishSettings
|
data: IProjectPublishSettings
|
||||||
) => Promise<IProjectPublishSettings>;
|
) => Promise<IProjectPublishSettings>;
|
||||||
unPublishProject: (workspaceSlug: string, projectId: string, projectPublishId: string) => Promise<void>;
|
unPublishProject: (workspaceSlug: string, projectID: string, projectPublishId: string) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ProjectPublishStore implements IProjectPublishStore {
|
export class ProjectPublishStore implements IProjectPublishStore {
|
||||||
// states
|
// states
|
||||||
generalLoader: boolean = false;
|
generalLoader: boolean = false;
|
||||||
fetchSettingsLoader: boolean = false;
|
fetchSettingsLoader: boolean = false;
|
||||||
// actions
|
// observables
|
||||||
project_id: string | null = null;
|
publishSettingsMap: Record<string, IProjectPublishSettings> = {};
|
||||||
projectPublishSettings: IProjectPublishSettings | "not-initialized" = "not-initialized";
|
|
||||||
// root store
|
// root store
|
||||||
projectRootStore: ProjectRootStore;
|
projectRootStore: ProjectRootStore;
|
||||||
// services
|
// services
|
||||||
@ -62,12 +64,10 @@ export class ProjectPublishStore implements IProjectPublishStore {
|
|||||||
generalLoader: observable.ref,
|
generalLoader: observable.ref,
|
||||||
fetchSettingsLoader: observable.ref,
|
fetchSettingsLoader: observable.ref,
|
||||||
// observables
|
// observables
|
||||||
project_id: observable.ref,
|
publishSettingsMap: observable,
|
||||||
projectPublishSettings: observable.ref,
|
// actions
|
||||||
// project settings actions
|
fetchPublishSettings: action,
|
||||||
getProjectSettingsAsync: action,
|
updatePublishSettings: action,
|
||||||
updateProjectSettingsAsync: action,
|
|
||||||
// project publish actions
|
|
||||||
publishProject: action,
|
publishProject: action,
|
||||||
unPublishProject: action,
|
unPublishProject: action,
|
||||||
});
|
});
|
||||||
@ -77,29 +77,31 @@ export class ProjectPublishStore implements IProjectPublishStore {
|
|||||||
this.projectPublishService = new ProjectPublishService();
|
this.projectPublishService = new ProjectPublishService();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description returns the publish settings of a particular project
|
||||||
|
* @param {string} projectID
|
||||||
|
* @returns {IProjectPublishSettings | undefined}
|
||||||
|
*/
|
||||||
|
getPublishSettingsByProjectID = (projectID: string): IProjectPublishSettings | undefined =>
|
||||||
|
this.publishSettingsMap?.[projectID] ?? undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches project publish settings
|
* Fetches project publish settings
|
||||||
* @param workspaceSlug
|
* @param workspaceSlug
|
||||||
* @param projectId
|
* @param projectID
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
getProjectSettingsAsync = async (workspaceSlug: string, projectId: string) => {
|
fetchPublishSettings = async (workspaceSlug: string, projectID: string) => {
|
||||||
try {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.fetchSettingsLoader = true;
|
this.fetchSettingsLoader = true;
|
||||||
});
|
});
|
||||||
const response = await this.projectPublishService.getProjectSettingsAsync(workspaceSlug, projectId);
|
const response = await this.projectPublishService.getProjectSettingsAsync(workspaceSlug, projectID);
|
||||||
if (response) {
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.projectPublishSettings = response;
|
set(this.publishSettingsMap, [projectID], response);
|
||||||
this.fetchSettingsLoader = false;
|
this.fetchSettingsLoader = false;
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
runInAction(() => {
|
|
||||||
this.projectPublishSettings = "not-initialized";
|
|
||||||
this.fetchSettingsLoader = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
@ -112,23 +114,21 @@ export class ProjectPublishStore implements IProjectPublishStore {
|
|||||||
/**
|
/**
|
||||||
* Publishes project and updates project publish status in the store
|
* Publishes project and updates project publish status in the store
|
||||||
* @param workspaceSlug
|
* @param workspaceSlug
|
||||||
* @param projectId
|
* @param projectID
|
||||||
* @param data
|
* @param data
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
publishProject = async (workspaceSlug: string, projectId: string, data: IProjectPublishSettings) => {
|
publishProject = async (workspaceSlug: string, projectID: string, data: IProjectPublishSettings) => {
|
||||||
try {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.generalLoader = true;
|
this.generalLoader = true;
|
||||||
});
|
});
|
||||||
const response = await this.projectPublishService.createProjectSettingsAsync(workspaceSlug, projectId, data);
|
const response = await this.projectPublishService.createProjectSettingsAsync(workspaceSlug, projectID, data);
|
||||||
if (response) {
|
runInAction(() => {
|
||||||
runInAction(() => {
|
set(this.publishSettingsMap, [projectID], response);
|
||||||
this.projectPublishSettings = response;
|
set(this.projectRootStore.project.projectMap, [projectID, "is_deployed"], true);
|
||||||
set(this.projectRootStore.project.projectMap, [projectId, "is_deployed"], true);
|
this.generalLoader = false;
|
||||||
this.generalLoader = false;
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
@ -141,14 +141,14 @@ export class ProjectPublishStore implements IProjectPublishStore {
|
|||||||
/**
|
/**
|
||||||
* Updates project publish settings
|
* Updates project publish settings
|
||||||
* @param workspaceSlug
|
* @param workspaceSlug
|
||||||
* @param projectId
|
* @param projectID
|
||||||
* @param projectPublishId
|
* @param projectPublishId
|
||||||
* @param data
|
* @param data
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
updateProjectSettingsAsync = async (
|
updatePublishSettings = async (
|
||||||
workspaceSlug: string,
|
workspaceSlug: string,
|
||||||
projectId: string,
|
projectID: string,
|
||||||
projectPublishId: string,
|
projectPublishId: string,
|
||||||
data: IProjectPublishSettings
|
data: IProjectPublishSettings
|
||||||
) => {
|
) => {
|
||||||
@ -158,26 +158,15 @@ export class ProjectPublishStore implements IProjectPublishStore {
|
|||||||
});
|
});
|
||||||
const response = await this.projectPublishService.updateProjectSettingsAsync(
|
const response = await this.projectPublishService.updateProjectSettingsAsync(
|
||||||
workspaceSlug,
|
workspaceSlug,
|
||||||
projectId,
|
projectID,
|
||||||
projectPublishId,
|
projectPublishId,
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
if (response) {
|
runInAction(() => {
|
||||||
const _projectPublishSettings: IProjectPublishSettings = {
|
set(this.publishSettingsMap, [projectID], response);
|
||||||
id: response?.id || null,
|
this.generalLoader = false;
|
||||||
comments: response?.comments || false,
|
});
|
||||||
reactions: response?.reactions || false,
|
return response;
|
||||||
votes: response?.votes || false,
|
|
||||||
views: { ...response?.views },
|
|
||||||
inbox: response?.inbox || null,
|
|
||||||
project: response?.project || null,
|
|
||||||
};
|
|
||||||
runInAction(() => {
|
|
||||||
this.projectPublishSettings = _projectPublishSettings;
|
|
||||||
this.generalLoader = false;
|
|
||||||
});
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.generalLoader = false;
|
this.generalLoader = false;
|
||||||
@ -189,23 +178,23 @@ export class ProjectPublishStore implements IProjectPublishStore {
|
|||||||
/**
|
/**
|
||||||
* Unpublishes project and updates project publish status in the store
|
* Unpublishes project and updates project publish status in the store
|
||||||
* @param workspaceSlug
|
* @param workspaceSlug
|
||||||
* @param projectId
|
* @param projectID
|
||||||
* @param projectPublishId
|
* @param projectPublishId
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
unPublishProject = async (workspaceSlug: string, projectId: string, projectPublishId: string) => {
|
unPublishProject = async (workspaceSlug: string, projectID: string, projectPublishId: string) => {
|
||||||
try {
|
try {
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.generalLoader = true;
|
this.generalLoader = true;
|
||||||
});
|
});
|
||||||
const response = await this.projectPublishService.deleteProjectSettingsAsync(
|
const response = await this.projectPublishService.deleteProjectSettingsAsync(
|
||||||
workspaceSlug,
|
workspaceSlug,
|
||||||
projectId,
|
projectID,
|
||||||
projectPublishId
|
projectPublishId
|
||||||
);
|
);
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.projectPublishSettings = "not-initialized";
|
unset(this.publishSettingsMap, [projectID]);
|
||||||
set(this.projectRootStore.project.projectMap, [projectId, "is_deployed"], false);
|
set(this.projectRootStore.project.projectMap, [projectID, "is_deployed"], false);
|
||||||
this.generalLoader = false;
|
this.generalLoader = false;
|
||||||
});
|
});
|
||||||
return response;
|
return response;
|
||||||
|
43
yarn.lock
43
yarn.lock
@ -6281,6 +6281,11 @@ date-fns@^2.30.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.21.0"
|
"@babel/runtime" "^7.21.0"
|
||||||
|
|
||||||
|
date-fns@^3.6.0:
|
||||||
|
version "3.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.6.0.tgz#f20ca4fe94f8b754951b24240676e8618c0206bf"
|
||||||
|
integrity sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==
|
||||||
|
|
||||||
debug@2.6.9:
|
debug@2.6.9:
|
||||||
version "2.6.9"
|
version "2.6.9"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||||
@ -10681,7 +10686,7 @@ prelude-ls@^1.2.1:
|
|||||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||||
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
||||||
|
|
||||||
"prettier-fallback@npm:prettier@^3":
|
"prettier-fallback@npm:prettier@^3", prettier@^3.1.1, prettier@^3.2.5, prettier@latest:
|
||||||
version "3.3.0"
|
version "3.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.0.tgz#d173ea0524a691d4c0b1181752f2b46724328cdf"
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.0.tgz#d173ea0524a691d4c0b1181752f2b46724328cdf"
|
||||||
integrity sha512-J9odKxERhCQ10OC2yb93583f6UnYutOeiV5i0zEDS7UGTdUt0u+y8erxl3lBKvwo/JHyyoEdXjwp4dke9oyZ/g==
|
integrity sha512-J9odKxERhCQ10OC2yb93583f6UnYutOeiV5i0zEDS7UGTdUt0u+y8erxl3lBKvwo/JHyyoEdXjwp4dke9oyZ/g==
|
||||||
@ -10708,11 +10713,6 @@ prettier@^2.8.8:
|
|||||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
|
||||||
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
|
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
|
||||||
|
|
||||||
prettier@^3.1.1, prettier@^3.2.5, prettier@latest:
|
|
||||||
version "3.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.0.tgz#d173ea0524a691d4c0b1181752f2b46724328cdf"
|
|
||||||
integrity sha512-J9odKxERhCQ10OC2yb93583f6UnYutOeiV5i0zEDS7UGTdUt0u+y8erxl3lBKvwo/JHyyoEdXjwp4dke9oyZ/g==
|
|
||||||
|
|
||||||
pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
|
pretty-bytes@^5.3.0, pretty-bytes@^5.4.1:
|
||||||
version "5.6.0"
|
version "5.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
|
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb"
|
||||||
@ -12112,16 +12112,7 @@ string-argv@~0.3.2:
|
|||||||
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
|
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
|
||||||
integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
|
integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0":
|
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||||
version "4.2.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
|
||||||
dependencies:
|
|
||||||
emoji-regex "^8.0.0"
|
|
||||||
is-fullwidth-code-point "^3.0.0"
|
|
||||||
strip-ansi "^6.0.1"
|
|
||||||
|
|
||||||
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
@ -12217,14 +12208,7 @@ stringify-object@^3.3.0:
|
|||||||
is-obj "^1.0.1"
|
is-obj "^1.0.1"
|
||||||
is-regexp "^1.0.0"
|
is-regexp "^1.0.0"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||||
version "6.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
|
||||||
dependencies:
|
|
||||||
ansi-regex "^5.0.1"
|
|
||||||
|
|
||||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
@ -13648,16 +13632,7 @@ workbox-window@6.6.1, workbox-window@^6.5.4:
|
|||||||
"@types/trusted-types" "^2.0.2"
|
"@types/trusted-types" "^2.0.2"
|
||||||
workbox-core "6.6.1"
|
workbox-core "6.6.1"
|
||||||
|
|
||||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
||||||
version "7.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
|
||||||
dependencies:
|
|
||||||
ansi-styles "^4.0.0"
|
|
||||||
string-width "^4.1.0"
|
|
||||||
strip-ansi "^6.0.0"
|
|
||||||
|
|
||||||
wrap-ansi@^7.0.0:
|
|
||||||
version "7.0.0"
|
version "7.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
|
Loading…
Reference in New Issue
Block a user